I am trying to use meteor autosubscribe function on the client but sometimes it works and sometimes it doesn't. So here is the case:
Working version: I have dropdown which is populated with channels. When user clicks on the channel I set session variable and start loading threads:
Template.channelDropdown.events({
"click #channelLink": function() {
Session.set("currentChannel", this);
}
});
html
<ul class="dropdown-menu">
{{#each channels}}
<li>
<a id="channelLink" href="#">{{name}}</a>
</li>
{{/each}}
</ul>
and
Tracker.autorun(function() {
Meteor.subscribe("threadsByChannel", Session.get("currentChannel"));
});
Meteor.publish("threadsByChannel", function (channel) {
return threads.find({channel: channel});
});
and loading threads:
"channelThreads": function() {
return threads.find({channel: Session.get("currentChannel")}).fetch();
},
Now this works. However I have other method to open channel which doesn't work. It is possible to enter channel name and if it doesn't exist it is created, otherwise existing one is returned.
Template.channelSearchBar.events({
"submit #joinChannelForm": function() {
event.preventDefault();
var channelName = $("#channelNameField").val();
Meteor.call("getChannelByName", channelName, function(error, result) {
if (error) {
// TODO error handling
} else {
Session.set("currentChannel", result);
}
});
$("#channelNameField").val("");
}
});
server:
'getChannelByName': function (channelName) {
var channel = channels.findOne({name: channelName});
if (channel) {
return channel;
} else {
var newChannel = {
name: channelName
}
return channels.insert(newChannel);
}
}
html
<template name="channelSearchBar">
<form id="joinChannelForm" class="navbar-form navbar-left" role="search">
<div class="form-group">
<input id="channelNameField" type="text" class="form-control" placeholder="Enter channel name">
</div>
<button type="submit" class="btn btn-default">Join</button>
</form>
</template>
Now the only difference is that session variable is set in callback. I'm pretty sure this is the problem as it is asynchronious call to the server and somehow threads are not populated in client when requested. When I set breakpoint in loading threads function (threads.find() on client), I see that session variable is correctly set, but it just does not return anything. Also sometimes it is called two times (for example in working first case first call returns nothing and then second call returns real results for some reason. Is this is how it suppose to work?). I am just beginning to learn meteor and trying to understand how it all works. Would be glad if someone could explain or direct me to the right way.
EDIT: Its very strange. I have put breakpoint in publish function and it seems it works fine - exactly like it should. However on the not working case it simply returns nothing right from the server side even though both working and not working situations provides (seemingly) exactly the same channel object. It seems that the problem is related with mongodb query.
Why don't remove the Meteor.call, and do everything on the client side?, the subscription on the Autorun seems to be fine, lets try with this code, just make sure you have the allow/deny permissions in order.
Template.channelSearchBar.events({
"submit #joinChannelForm": function() {
event.preventDefault();
var channel = channels.findOne({name: channelName}),
channelName = $("#channelNameField").val();
if (channel) {
return channel;
} else {
var newChannel = {
name: channelName
}
var chanelCreated = channels.insert(newChannel);
Session.set("currentChannel", chanelCreated);
$("#channelNameField").val("");
}
}
});
OK it seems the real problem was not that of meteor publish/subscribe mistake but because of mongodb query which was not recognizing channel object. Problem was solved by changing this:
threads.find({channel: channel})
to this:
threads.find({"channel.name": channel.name})
I have found that mongo queries cares about order of object parameters, but channel had only one parameter (name) at the moment, so I'm still not sure why they were not considered equal. One channel was returned from findOne query and another from find. One from find was recognized.
Related
Using v-on:click I'd like to set a variable with the id of the div in Vue.JS - how do I reference this?
<div id="foo" v-on:click="select">...</div>
<script>
new Vue({
el: '#app',
data: {
},
methods: {
select: function(){
divID = this.id // ??
alert(divID)
}
}
})
</script>
You can extend your event handler with the event object $event. That should fit your needs:
<div id="foo" v-on:click="select($event)">...</div>
The event is passed on in javascript:
export default {
methods: {
select: function(event) {
targetId = event.currentTarget.id;
console.log(targetId); // returns 'foo'
}
}
}
As mentioned in the comments, `$event` is not strictly necessary, when using it as the only parameter. It's a nice reminder that this property is passed on, when writing it explicitly.
However, nobody will stop you from writing the short notation:
<div id="foo" #click="select">...</div>
Beware that the method will not receive the `$event` object when you add another parameter. You need to explicitly add it at the position you will handle it in the listener. Any parameter order will work:
<div id="foo" #click="select(bar, $event)">...</div>
To find more options of the v-on directive, you can look through the corresponding entry in the vue documentation:
Vue API Documentation - v-on
Inspired by #nirazul's answer, to retrieve data attributes:
HTML:
<ul>
<li
v-for="style in styles"
:key="style.id"
#click="doFilter"
data-filter-section="data_1"
:data-filter-size="style.size"
>
{{style.name}}
</li>
</ul>
JS:
export default {
methods: {
doFilter(e) {
let curTarget = e.currentTarget;
let curTargetData = curTarget.dataset;
if (curTargetData) {
console.log("Section: " + curTargetData.filterSection);
console.log("Size: " + curTargetData.filterSize);
}
}
}
}
Just to highlight another option than the selected answer for the same question, I have a delete button on a record and want to perform an action with the record's unique id (a number). I could do the selected answer as before:
<button :id="record.id" #click="del">×</button>
This leaves the unfortunate reality that my del function needs to pull the id attribute out of the javascript event, which is more about the API (the DOM) than my domain (my app). Also using a number as an element id isn't ideal and could cause a conflict if I do it more than once in a view. So here's something that's just as clear and avoids any future confusion:
<button #click="()=>del(record.id)">×</button>
methods: {
del(id) {
fetch(`/api/item/${id}`, {method:"DELETE"})
}
}
You see, now my del function takes the record id instead of an event, simplifying things.
Note that if you do this wrong, you will invoke your delete function immediately, which is not what you want. Don't do this:~~
<button #click="del(record.id)">×</button>
If you end up doing that, Vue will call the del function every time this html fragment is rendered. Using the anonymous function ()=>del(record.id) will return a function that's ready to be executed when the click event happens.
Actually #nirazul proved this is fine. Not sure what my issue was.
I am trying to make a template render something on the client; I think I tried everything possible (apart from the correct thing apparently).
Html:
<head>
<title>Groups</title>
</head>
<body>
{{loginButtons}}
{{>TplGroups}}
</body>
<template name="TplGroups">
groups found: {{ GroupCount }}
{{#each GetAllGroups}}
<div> hello, {{name}} group! </div>
{{/each}}
</template>
serverStartup.js:
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Meteor.publish("GroupCount"), function()
{
return Groups.find({});
}
});
}
and the Groups.js collection which exposes the two methods GroupCount and GetAllGroups, which I want to access on client side:
var Groups = new Meteor.Collection("groups");
Groups.insert({name: "John"});
if(Meteor.is_client)
{
Meteor.subscribe("GetAllGroups");
Meteor.subscribe("GroupCount");
Template.TplGroup.GetAllGroups = function()
{
return Groups.find({});
}
Template.TplGroup.GroupCount = function()
{
return Groups.find().count();
}
}
I have removed "insecure" and "autopublish" packages.
Where is my mistake? The two functions won't show on client.
Also what is the difference between declaring the functions as "publish" or declaring them as Template functions?
In browser console I get this:
event.returnValue is deprecated. Please use the standard event.preventDefault() instead. (jquery.js)
The publish method should look more or less like this
Meteor.publish("GetAllGroups", function () {
return Groups.find({});
});
#apendua pointed to the right solution. I took your code and refactored it to make the solution a little clearer:
server.js:
if (Meteor.isServer) {
// Publish groups
Meteor.publish('groups', function() {
return Groups.find();
});
}
groups.js
Groups = new Meteor.Collection('groups');
Groups.insert({name: 'John'});
if (Meteor.isClient) {
// Subscribe to groups
Meteor.subscribe('groups');
Template.TplGroup.GetAllGroups = function() {
return Groups.find();
}
Template.TplGroup.GroupCount = function() {
return Groups.find().count();
}
}
It is enough to publish just groups. In your groups.js you try to subscribe to a publication that does not exist (GetAllGroups). Better to just publish and subscribe to simply 'groups' and return the groups count as described above. Also with a newer version of meteor you should use Meteor.isClient and not Meteor.is_client.
The jQuery error you described is not related to your code and appears (at least what I think) because of some issue with Meteor and/or jQuery itself. Don't worry about that.
oups you just forgot "s" in your template name in your js file :
<template name="TplGroups"> <!-- what you wrote -->
and in your js you wrote :
Template.TplGroup.xxx
instead of :
Template.TplGroups.xxx
I know this is completely simple, but it's also completely is stumping me on why it isn't working. This gets to the point of rendering the html and showing Hello World with a message below "Welcome to chat" and a button "say hello back" but what it ISN'T doing is then change the message to "work".
I have a .js file which is:
if (Meteor.isClient) {
var message="welcome to chat";
function template(message){
Template.hello.greeting = function () {
return message;
};};
template(message);
Template.hello.events({
'click input' : function () {
template("work ");
}
});
}
and a html follow as shown:
<head>
<title>chat</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<button value="Click">Say Hello Back!</button>
</template>
And it's embarrassingly simple but I just can't figure out what I'm doing wrong. I know I shouldn't re render the page because the whole point of using meteor is for it's live html so what do I do have to do?
I figured out the main problem!
For my html I was using a button class but I should've been using a input type="button" instead!
To make it "reactive" you should use Session that meteor provides. You can simplify your code to make it easier to read and understand.
Session provides a global object on the client that you can use to
store an arbitrary set of key-value pairs. Use it to store things like
the currently selected item in a list.
You set the session variable to "welcome to chat" first. In your click event you would set the Session variable to "work". Then you would set the template to return the value of the Session variable when it changes. Your javascript could look something like this.
if (Meteor.isClient) {
Session.set("message", "welcome to chat");
Template.hello.greeting = function () {
return Session.get("message");
}
Template.hello.events({
'click input' : function () {
Session.set("message", "work");
}
});
}
This is untested but give it a try.
i am not quite sure about what do you mean by "not working". But i am sure you will have to do following.
List item you need to prevent calling default event by the browser.
ex:-
Template.hello.events({
'click input' : function () {
//your code here
return false;
}
});
2 . use meteor methods instead of having template()
Ok so I'm not sure why I can't render the code. First if I console.log users.content I get the content I want but I'm some how not able to pass it to a textarea so that it show's it...
Users = new Meteor.Collection("users");
if(Meteor.is_client){
Template.inputUser.code = function(){
var el = Users.find({name:"oscar"});
el.forEach(function(users){
console.log(users.content);
})
}
}
And then on my html template I have
<body>{{> inputUser}}</body>
<template name="inputUser">
<textarea>{{content}}</textarea>
</template>
And I would have a record on the db suck as so
if(Meteor.is_server)
Users.insert({name:"oscar",content:"hello world"})
Thanks for your help guys.
Firstly your method Template.inputUser.code should return something, you should also note that it wouldn't be called with that template either as it needs a {{code}} call in it rather than {{content}}
The second point is database contents are not always available if you have disabled the autopublish package, if so check out using publish(in the server code) and subscribe(in the client code): http://docs.meteor.com/#meteor_subscribe you can use this to check when the client has all the data to display. Something like:
Meteor.subscribe('allusers', function() {
Template.inputUser.code = function(){
var user = Users.findOne({name:"oscar"});
return user.content;
}
});
...
Meteor.publish('allusers', function() {
return Users.find();
});
Ok so I've got my template in its own file named myApp.html. My template code is as follows
<template name="initialInsertion">
<div class="greeting">Hello there, {{first}} {{last}}!</div>
</template>
Now I want to insert this template into the DOM upon clicking a button. I've got my button rendered in the DOM and I have a click event tied to it as follows
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
Meteor.ui.render(function () {
$("body").append(Template.initialInsertion({first: "Alyssa", last: "Hacker"}));
})
}
}
Now obviously the $("body").append part is wrong but returning Template.initialInsertion... doesn't insert that template into the DOM. I've tried putting a partia {{> initialInsertion}}but that just errors out because I dont have first and last set yet... any clues?
Thanks guys
In meteor 1.x
'click .zaButton':function(){
Blaze.renderWithData(Template.someTemplate, {my: "data"}, $("#parrent-node")[0])
}
In meteor 0.8.3
'click .zaButton':function(){
var t = UI.renderWithData(Template.someTemplate, {my: "data"})
UI.insert(t, $(".some-parrent-to-append"))
}
Is first and last going into a Meteor.Collection eventually?
If not, the simplest way I know is to put the data into the session:
Template.chooseWhatToDo.events = {
'click .zaButton' : function () {
Session.set('first', 'Alyssa');
Session.set('last', 'Hacker');
}
}
Then you would define:
Template.initialInsertion.first = function () {
return Session.get('first');
}
Template.initialInsertion.last = function () {
return Session.get('last');
}
Template.initialInsertion.has_name = function () {
return Template.initialInsertion.first() && Template.initialInsertion.last();
}
Finally, adjust your .html template like this:
<template name="initialInsertion">
{{#if has_name}}
<div class="greeting">Hello there, {{first}} {{last}}!</div>
{{/if}}
</template>
This is the exact opposite solution to your question, but it seems like the "Meteor way". (Basically, don't worry about manipulating the DOM yourself, just embrace the sessions, collections and template system.) BTW, I'm still new with Meteor, so if this is not the "Meteor way", someone please let me know :-)
I think you may want to use Meteor.render within your append statement. Also, note that if you are passing data into your Template, then you must wrap Template.initialInsertion in an anonymous function, since that's what Meteor.render expects. I'm doing something similar that seems to be working:
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
$("body").append(Meteor.render(function() {
return Template.initialInsertion({first: "Alyssa", last: "Hacker"})
}));
}
}
Hope this helps!
Many answer here are going to have problems with the new Blaze engine. Here is a pattern that works in Meteor 0.8.0 with Blaze.
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
More info in this seccion of Meteor docs