Using Meteor, I am trying to loop through and display a list of notes from a database with an option to delete each note.
Here is the HTML (using Handlebars.js)
<template name="Notes">
{{#each NoteArr}}
<div class="Note">
<h2>{{Title}}</h2>
<p>{{Body}}</p>
<span class="deleteNote">Delete</span>
</div>
{{/each}}
</template>
And here is the client Javascript
Template.Notes.events = {
"click .deleteNote" : function(){
noteID = $('.deleteNote').parent().attr("id");
Notes.remove({ID:noteID});
}
};
This grabs the first instance of .deleteNote, so unless I'm trying to delete the first one, that won't help. How can I grab the parent of the particular instance of .deleteNote that was clicked, not just the first one it finds?
The reason why the first element is deleted is.. in your .click event, you are accesssing the div directly as $('.deleteNote').parent() which grabs the first node in the html which has a class .deleteNode.
Now to remove the specific notes, from the collection: Every document in the collection has a unique _id attribute which is generated automatically. assign that unique _id of the document to the span element as <span id= "{{_id}}" class="deleteNote">Delete</span>.
So the cilck event will look like:
Template.Notes.events = {
"click .deleteNote" : function(e){
var noteID = e.currentTarget.id;
Notes.remove({_id:noteID});
}
};
And the template will look like:
<template name="Notes">
{{#each NoteArr}}
<div class="Note">
<h2>{{Title}}</h2>
<p>{{Body}}</p>
<span id= "{{_id}}" class="deleteNote">Delete</span>
</div>
{{/each}}
</template>
Untested code, but hope this will help solving your issue.
The _id of a note is stored in 'this' as well. In addition, the remove function accepts '_id' as a string. So this should work as well:
Template.Notes.events = {
'click .deleteNote': function(){ return Notes.remove(this._id)}
}
A few benefits here. Less querying the DOM for information. Less jQuery. Fewer lines of code to think about. Cleaner templates.
Related
The below code is expected to insert a document of 3 pairs in the FooterButtons collection, then those values are shown as labels on 3 buttons in the footer template.
But when the "click .menuItem" is called, it only insert "YES" in the collection. Any idea why it is broker and best way to fix it? Thanks
Server and client code
FooterButtons = new Mongo.Collection('footerButtons');
Server code
Meteor.publish('footerButtons', function(){
return FooterButtons.find();
});
Client code
Meteor.subscribe('footerButtons');
//---main_menu.js--------------------
Template.mainMenu.events({
'click .menuItem': function (event) {
FooterButtons.insert({button:"NO", button:"EXTRA", button:"YES"});
}
});
//---footer.html---------------
<template name="footer">
{{#each footerButtons}}
<h1>
<button class="col-xs-4" type="button">{{button}}</button>
</h1>
{{/each}}
</template>
//---footer.js---------------
Template.footer.helpers({
footerButtons: function(){
return FooterButtons.find();
}
});
This command is totally wrong for the mongo insertion
FooterButtons.insert({button:"NO", button:"EXTRA", button:"YES"});
If you create a javascript object,
var obj = {button:"NO", button:"EXTRA", button:"YES"};
since all the keys is duplicated, your object will only has one key which has the value of the last one: Yes
You need to insert one by one
FooterButtons.insert({button:"NO"});
FooterButtons.insert({button:"EXTRA"});
FooterButtons.insert({button:"YES"});
I have the following templates (.html) with their respected managers (.js files):
adminManageCategories
adminAddCategory
adminUpdateCategory
Consider the following:
<template name="adminManageCategories">
{{#each category}}
<div class="clickme">{{title}}</div>
{{/each}}
{{> adminUpdateCategory}}
</template>
Notice the {{> adminUpdateCategory}} is outside of the iteration. This is also a form, and I want to keep it on the same page.
And admin_manage_categories.js
Template.adminManageCategories.events({
"click .clickme": function(event) {
event.preventDefault();
console.log(this._id);
}
});
Notice the console.log() function, which works, as the template manager is smart enough to know the ID of the item that was clicked.
What I want to do is load this items values into the form when clicked. My example above is slim, but in my real data I have a title, sort order, among other things.
So my question is, what is the proper way to pass the _id from the adminManageCategories template to the adminUpdateCategory template, which is the form?
I can hack at this with JavaScript and make things happen, but I think I'm missing a "Meteor way" of doing things.
You need to use a ReactiveVar to store the currently clicked item.
First you need to run meteor add reactive-var, as it's not a package added by default in a standard meteor web app.
JS:
Template.adminManageCategories.created=function(){
// instantiate the reactive-var in the created callback
// we store it as a property of the template instance
this.currentItemId=new ReactiveVar(null);
};
Template.adminManageCategories.helpers({
// this helper reactively returns the currently clicked item
currentItem:function(){
// retrieve the reactive-var from the template instance...
var currentItemId=Template.instance().currentItemId.get();
// ...to fetch the correct collection document
return Items.findOne(currentItemId);
}
});
Template.adminManageCategories.events({
"click .clickme": function(event,template) {
event.preventDefault();
// assign the correct item id to the reactive-var attached to this template instance
template.currentItemId.set(this._id);
}
});
HTML:
<template name="adminManageCategories">
{{#each category}}
<div class="clickme">{{title}}</div>
{{/each}}
<p>Current item title is : {{currentItem.title}}</p>
{{! pass the currentItem as a parameter to your child template this will be
accessible as {{item}} in the HTML and "this.item" in JS helpers or
"this.data.item" in created/rendered/destroyed callbacks}}
{{> adminUpdateCategory item=currentItem}}
</template>
EDIT:
When I initialize the reactive-var in the created callback, I set it to null, this means that until one item is clicked, the helper will return null too and when you'll try to access this.item._id in the adminUpdateCategory this will fail.
The simplest way to solve this issue is maybe to not initialize the variable to null but to the first item in the collection.
Template.adminManageCategories.created=function(){
var firstItem=Items.findOne({},{
sort:{
sortedField:1
}
});
this.currentItemId=new ReactiveVar(firstItem && firstItem._id);
};
There may still be a case when you have 0 items in the collection, so you'll probably end up having to guard against the existence of the item in the JS.
Template.adminUpdateCategory.helpers({
itemProperty:function(){
return this.item && this.item.property;
}
});
I'm struggling with an issue that I will explain giving a simple demo.
There's following very simple document in People Collection.
{
"_id" : "vxmLRndHgNocZouJg",
"fname" : "John" ,
"nicks" : [ "Johnny" , "Jo"]
}
Now let's consider following templates. Basically I display username and a list of nicknames with input field for adding more nicknames.
<head>
<title>test</title>
</head>
<body>
{{> name}}<br/>
{{> nicks}}
</body>
<template name="name">
<input type="text" value="{{fname}}"/>
</template>
<template name="nicks">
{{#each nicks}}
<div>{{this}}</div>
{{else}}
no nicks yet
{{/each}}
<input type="text" name="nicks"/>
<input type="submit"/>
</template>
My client javascript code is as follows:
Template.name.fname = function() {
return People.findOne({"fname" : "John"},{
transform : function(doc) {
return doc.fname;
}
});
}
Template.name.rendered = function() {
console.log('Template "name" rendered!');
}
Template.nicks.nicks = function() {
var john = People.findOne({"fname" : "John"});
if(john) return john.nicks;
}
Template.nicks.events({
'click input[type="submit"]' : function () {
var johnId = People.findOne({"fname" : "John"})._id; // demo code
People.update(johnId,{
$addToSet : {
nicks : $('input[name="nicks"]').val()
}
})
}
});
My problem is that after adding nickname (update of nicks field in a document) template name is re-rendered (I know because I console.log it). When I query People collection to provide data for name template I use transform option so changes in nicks field shouldn't have impact on name template.
Meteor docs supports this:
Cursors are a reactive data source. The first time you retrieve a cursor's documents with fetch, map, or forEach inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation.
Why template name is re-rendered then?
The template is re-rendered because you change the People collection.
When you alter the People collection, Meteor automatically assumes that everything that it provides data to needs to be recalculated. (Which your name template does via Template.name.fname.
Even though you transform the output of the cursor, the People collection has changed in some way. The query is done before the transform is used, in other words, its not the transform that is looked at but the whole collection.
Meteor thinks that perhaps your document with {'fname':'John'} may have some other field that might have changed and it needs to requery it to check (which the nicks field has been altered). The transform is then applied after the requery.
Your HTML might not actually change at this point, only if the cursor returns something different will the html be changed.
If it becomes an issue in any scenario (i.e forms being cleared or DOM being changed when it shouldn't be) you can use the {{#isolate}} {{/isolate}} blocks to ensure that only everything inside them is re-rendered and nothing outside.
I am new to Meteor and wondering how to solve what seems to me is a common problem.
Let's say I have a handlebars template listing restaurants:
<template name="Restaurants">
{{#each Restaurant}}
{{name}}
{{/each}}
</template>
Now when user clicks on a restaurant template I want to display a menu for that restaurant.
I added a subtemplate named "menuItems" that contains all menu items for a given restaurant:
<template name="Restaurants">
{{#each Restaurant}}
{{name}}
{{> menuItems}}
{{/each}}
</template>
I want to render only one instance of menuItems subtemplate when user clicks anywhere on Restaurant template (render only the menu items for the selected restaurant).
It should go something like:
Template.Restaurants.events({
'click' : function (e) {
// This is where I need help - what's the right way to display only one subtemplate instance?
}
});
My question is - how I can select and display only the correct menuItems template instance?
Also I would like to place menuItems template instance in DOM only after the click and not before (having all the menu items for all restaurants and only hiding those divs is not an option because of high number of those items in db).
If you think I should approach the solution in some other way please let me know, thanks!
You should use {{#if}} and Session. Like this:
<template name="Restaurants">
{{#each Restaurant}}
{{name}}
{{#if restaurantSelected}}
{{> menuItems}}
{{/if}}
{{/each}}
</template>
By using Session, a reactive data source, you can set a global flag indicating whether a restaurant is selected.
Template.Restaurants.restaurantSelected = function() {
// check whether this restaurant is selected. "this" refers to the current
// context, eg. the current restaurant in the loop
return Session.equals("restaurantSelected", this._id);
}
Whenever you change that session key, the value will update and the template will be redrawn. So, you can toggle it when clicking a restaurant:
Template.Restaurants.events({
'click' : function (e) {
// store the current restaurant ID
// make sure the event selector is correct!
Session.set("restaurantSelected", this._id);
}
});
Edit For clarity's sake I created a complete example that you can copy into your project and try out.
I almost always avoid Session. I think it pollutes the global scope. Also it prevents you from running multiple instances of the template. I recommend using a reactiveVar or reactiveDict scoped to the template instance. Thanks to Rahul for starting a demo project. I took his example and modified it to show my recommended approach.
attach a reactiveDict to the template instance onCreate. Use this to store state instead of global Session var!
Template.Restaurants.onCreated(function() {
this.state = new ReactiveDict;
this.state.set('currentRestaurant', null); // could set a init value here
});
this event handler will set the state of the reactiveDict on click
'click': function(e, t) {
t.state.set('currentRestaurant', this._id);
}
this helper is used to show/hide the menu template
currentRestaurant: function() {
// check whether this restaurant is selected. "this" refers to the current
// context, eg. the current restaurant in the loop
return Template.instance().state.equals("currentRestaurant", this._id);
},
menu template receives the selected id from data context instead of from Session
<template name="Restaurants">
<ul>
{{#each Restaurant}}
<li>
{{name}}
{{#if currentRestaurant}}
{{> menuItems restaurant=_id}}
{{/if}}
</li>
{{/each}}
</ul>
</template>
<template name="menuItems">
<ul>
<li class="menu">I'm a menu for {{restaurantName}}!</li>
</ul>
</template>
added this helper just to show we really got the id
Template.menuItems.helpers({
restaurantName: function() {
var restaurantMenu = Restaurants.findOne(this.restaurant);
return restaurantMenu.name;
},
})
Posted a fully working project to github.
https://github.com/white-rabbit-japan/scopedReactivityDemo
App is hosted on meteor.com
http://scopedreactitivydemo.meteor.com/
I am getting started with Meteor, and adapting the todo example to include nested tag groups. I have the following HTML, which outputs each name of each tag group, plus the list of tags in that each group:
<template name="tag_filter">
{{#each tag_types }}
{{ tag_name }}
{{#each values }}
<div data-taggroup="{{ ../tag_name }}">
{{ name }} ({{ count }})
</div>
{{/each}}
{{/each}}
</template>
My question is this: how do I adapt the event handler for clicks on the tags to access the value of the parent group's tag_name? (i.e. the data from the outer each loop).
Currently I have the code below, but this object only gives me access to name and count.
Template.tag_filter.events({
'mousedown .tag': function () {
console.log('tag mousedown', this);
// How do I get the value of tag_name?
}
});
As you can see, I've used Handlebars parent paths to add a data-taggroup attribute containing the name, but I'm not sure how to access that from within the event handler.
I think this question is related, but I don't understand the OP's solution (partly because I'm not using Coffeescript). There's also an closed Meteor issue which is related.
I found the solution to access parent data:
Template.nestedTemplate.events({
'click a.class':function(event,template){
var parentID = template.data._id;
console.log(parentID);
}
});
The .events handler function receives two arguments: event, an object with information about the event, and template, a template instance for the template where the handler is defined. Took me a really long time to figure this one out. Don't use the handlebars solution, it shows your data!
I'm not sure whether you can get parent template data, but in your event handler you can access DOM elements: event.currentTarget will get the clicked element. Then just use jQuery to access attributes. If needed, event.currentTarget.parentNode will get the parent element in the DOM.
Ex: I don't know where you put the tag class, but let's say it's the class of your div where data-taggroup is defined. Then you could get the tag name using:
$(event.currentTarget).attr('data-taggroup')
"click selected":function(e){
// this._id
var doc_id = $(e.currentTarget).parent().parent().attr("uid")
console.log(doc_id)
},
//specify the each id in the div above the nearest #each
//this will work in events but not in helpers`