Accessing data from outside events callback - meteor

<template name="SideNav">
<ul class='block-items white'>
{{#each blocks}}
<li class='block-item'>
<i class="fa fa-fw fa-folder"></i>
<i class="fa fa-fw fa-folder-open"></i>
<a href='#' class='block-item-link'>
{{name}}
...
{{/each blocks}}
</template>
Given this, I can access each block-item's id when it's clicked by doing
Template.SideNav.events({
"click .block-item": function (e, tem) {
//var blockItemId = this._id;
}
});
How can I borrow the same feature from other places, like onRendered()? Take a look at the following example:
Template.SideNav.onRendered(function() {
this.$('.block-items').sortable({
update: function (e, ui) {
_.each($('.block-item'), function (blockItem) {
// How do I get blockId?
})
console.log("block item rearranged");
}
});
update is a callback function that's invoked when there was a change in the order of block items in the ul list. I need a way to iterate through all the block items and get their corresponding Mongo id's. How can I do it?
Related Documents:
The context of "this" in Meteor template event handlers (using Handlebars for templating)

Update: the "Meteor way"
If you are looking for how Blaze gets this data context for events and helpers, it turns out there is a magical Blaze.getData() function which takes a Blaze view or a DOM object and returns its data context. As far as I could tell by looking at the code, it seems to be the tool Blaze uses for providing data contexts to helpers and events.
So in your case, you could use:
Template.SideNav.onRendered(function() {
this.$('.block-items').sortable({
update: function (e, ui) {
_.each($('.block-item'), function (blockItem) {
var context = Blaze.getData(blockItem.get(0));
var blockId = context._id;
})
console.log("block item rearranged");
}
});
Original answer
An easy way to get the id of a document when working with DOM manipulation (other than blaze events) is to explicitly set it as an attribute in your template, such as:
<template name="SideNav">
<ul class='block-items white'>
{{#each blocks}}
<li class='block-item' id='{{_id}}'>
<i class="fa fa-fw fa-folder"></i>
<i class="fa fa-fw fa-folder-open"></i>
<a href='#' class='block-item-link'>
{{name}}
...
{{/each blocks}}
</template>
This way, you can just fetch the id using jquery's attr method:
Template.SideNav.onRendered(function() {
this.$('.block-items').sortable({
update: function (e, ui) {
_.each($('.block-item'), function (blockItem) {
var blockId = blockItem.attr('id');
})
console.log("block item rearranged");
}
});

Related

Meteor delete list item with selected class

I am trying to add a delete function that if i click on a list item(template) it adds a selected class to the li. I then want be able to click a button that finds the li with a class of selected and passes the data to a meteor method that removes the data from a collection. How do i access this data.
I have tried a few ways but this is what i have so far.
sidebar.js
Template.Sidebar.events({
'click .button-collapse': function() {
console.log("here")
$(".button-collapse").sideNav();
},
'click #delete i': function() {
Meteor.call('deleteListItem', $( "li.selected" )._id);
}
})
sidebar.html
<template name="Sidebar">
<ul id="slide-out" class="side-nav fixed grey darken-3">
<li class="action-bar">
<span id="add-new" data-target="modal-add" class="modal-trigger"><i class="small material-icons">add</i></span>
<span id="save"><i class="small material-icons">note_add</i></span>
<span id="rename"><i class="small material-icons">mode_edit</i></span>
<span id="delete"><i class="small material-icons">delete</i></span>
<span data-activates="slide-out" id="close" class="button-collapse close "><i class="small material-icons right">reorder</i></span>
</li>
<!-- Load save items-->
{{#if Template.subscriptionsReady}}
{{#each userSaves}}
{{>ListItem}}
{{/each}}
{{else}}
<p>Loading</p>
{{/if}}
</ul>
<i class="material-icons">menu</i>
<!-- Modal form to add new simulator file -->
<!-- Modal Structure -->
<div id="modal-add" class="modal">
<div class="modal-content">
<h4>New Simulator</h4>
{{> quickForm collection=saves id="newSimulator" type="insert" buttonClasses="modal-action modal-close btn waves-effect waves-light" buttonContent="Add"}}
</div>
</div>
</template>
list class
Meteor.methods({
deleteListItem: function(id) {
Saves.remove(id);
}
});
You can not retrieve the ID of an object like that. Either your button has to be inside the document you're gonna remove or you have to include the _id in your HTML attributes and get that value.
So simply add data-value="{{_id}}" to your <li> in your loop and get the selected like this:
//this will retrieve the id of one selected element only
//you need to use each/forEach if you want to remove multiple
$('li.selected').attr('data-value')
EDIT:
Another solution would be using Session. Since you click on an item to select it, you can set a Session in that event
//in the click event where you select a list item
Session.set('itemId', this._id);
//in Sidebar events
'click #delete i': function() {
const itemId = Session.get('itemId');
Meteor.call('deleteListItem', itemId);
}
EDIT 2:
If you use Session solution, don't forget to remove the Session when the template is destroyed. Otherwise (for your use case) when you go to another route and come back without deleting a selected item and click the delete button, it will delete the document even though it doesn't appear to be selected.
Template.templateName.onDestroyed(function(){
Session.set('sessionName', undefined);
});

How to fetch query parameters in a template?

I am using Meteor 1.2.1 + iron-router with autopublish turned on
I would like to construct an anchor href based on collection values returned by a helper and query params
Is it possible to do this in the template tag, e.g. query_param1 should be read from the URL?
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?name={{name}}&color=<query_param1>">
Data name
</a>
</li>
{{/each}}
</template>
Above, {{name}} is returned by the collection and query parameters are appended to that to create a full hyperlink for the href.
You can use #Stephen's suggestion like this.
In your template html,
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?{{queryParams}}">
Data name
</a>
</li>
{{/each}}
</template>
In your template JS,
Template.dataEntry.helpers({
"queryParams": function () {
var name = "";
//get name from collection here like...
//name = Meteor.user().profile.firstName;
var color = Router.current().params.color;
return "name=" + name + "&color=" + color;
}
});
Or you can use two separate helpers
In your template html,
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?name={{name}}&color={{color}}">
Data name
</a>
</li>
{{/each}}
</template>
In your template JS,
Template.dataEntry.helpers({
"name": function () {
var name = "";
//get name from collection here like...
//name = Meteor.user().profile.firstName;
return name;
},
"color": function () {
return Router.current().params.color;
}
});
You can access an IronRouter param through a helper, e.g.:
Router.current().params.query_param1

Why does my click event not fire when I add each blocks into my template to iterate through a collection (Meteor)

The event fires only when I remove the each blocks. What the event does is apply a vertical accordion slide down transition on an element. What I want to do is add the same slide down transition to all my documents when they are displayed in my views.
Right now, when I add an each block to iterate through my collection and display all the documents from my collection, the slide down event stops working.
Here's my template:
<template name="auctionsList">
<div class="container">
<div id='cssmenu'>
<ul>
{{#each auctions}}
{{>auction}}
{{/each}}
</ul>
</div> <!-- end cssmenu -->
</div><!-- end container -->
{{#if isReady}}
{{#if hasMoreauctions}}
<a class="load-more btn btn-default center-block text-uppercase" id="loadMore" href="#" style="margin-top:10px;">Load More</a>
{{/if}}
{{else}}
<div class="loading">{{>spinner}}</div>
{{/if}}
</template>
<template name="auction">
<li class='has-sub'>
<a href='#'>
<div class="auction-image">
<img src="brand_logos/DominosPizza.png" class="img-responsive" height="200" width="200">
</div>
{{> durationLeft}}
</a>
<ul>
<li><a href='#'>
<span>Sub Product</span></a></li>
</ul>
</li>
</template>
Here's my rendered/helper
Template.auctionsList.rendered = function () {
$('#cssmenu li.has-sub>a').on('click', function(){
$(this).removeAttr('href');
var element = $(this).parent('li');
if (element.hasClass('open')) {
element.removeClass('open');
element.find('li').removeClass('open');
element.find('ul').slideUp();
}
else {
element.addClass('open');
element.children('ul').slideDown();
element.siblings('li').children('ul').slideUp();
element.siblings('li').removeClass('open');
element.siblings('li').find('li').removeClass('open');
element.siblings('li').find('ul').slideUp();
}
});
}
Template.auctionsList.helpers({
auctions: function () {
return Template.instance().userauctions();
}
});
Template.auctionsList.events({
'click #cssmenu li.has-sub>a' : function(event, template) {
$(this).removeAttr('href');
var element = $(this).parent('li');
if (element.hasClass('open')) {
element.removeClass('open');
element.find('li').removeClass('open');
element.find('ul').slideUp();
}
else {
element.addClass('open');
element.children('ul').slideDown();
element.siblings('li').children('ul').slideUp();
element.siblings('li').removeClass('open');
element.siblings('li').find('li').removeClass('open');
element.siblings('li').find('ul').slideUp();
}
}
});
You should move this from rendered to events. $('#cssmenu li.has-sub>a').on('click', function(){ }
Put the event within the auction template where your <li> tag exists.
Updated
Couple of things based on the gists you published:
1) you should try not use script tags directly in the template - i havent seen this often if at all. Move all this code to
Template.accordion.rendered = function(){ //here };
or better make it work in auctionsList because then its not duplicated x times for every each iteration.
But what would be even better is not to have the 'on' event at all. You should use a meteor event... like you mentioned in your earlier post. Meteor events have access to 'this' which is the current context item.
I would try put it in a template event on the master auctionsList template and later on worry about how it will work with the sub templates.
2) I dont think you need to add everything into another template like you have, the accordion template doesnt really need to exist, you can probably get away with putting it in the auction template (consider using even auctions to do the click event because if you do it that way your js snippet wont be repeated x times per post listing.You only need this once..
3) When you create the li in the auction template give it an ID at that point
<li id={{new accordion code in auction template}}>
You can then reference this id on your template click event. If you are new to meteor do yourself a favor and do the following in a click event to better understand what is going on (you will most likely find the ID you need within one of the values - most likely this:
Template.YourTemplate.events({
'click any button in your each': function(event,bl,value)
{
console.log(event);
console.log(bl);
console.log(value);
//and most importantly
console.log(this);
}
});
I know its not "an answer" but i hope it leads you in the right direction.

Meteor + Iron Router to create breadcrumbs

Ok, so I found this post: Meteor breadcrumb
But lets say I have the following:
<template name="somePage">
<h1>Page Title</h1>
{{> breadcrumb}}
</template>
<template name="breadcrumb">
<ul class="breadcrumb">
<li>
Home
</li>
{{#each path}}
<li>
{{this}}
</li>
</ul>
</template>
Helper:
Template.breadcrumb.helpers({
path: function() {
return Router.current().path.split( "/" );
}
});
Ok so the linked question at the top got me the basics. I'm trying to understand how to do a few more things here that should be obvious. I want the first to be for the home page, and the result returned from the path: function() includes an empty "", "page", "page", etc. in the beginning of it.
I'd like to be able to incorporate the proper paths. To be clear, I'd love to pull this off:
<template name="breadcrumb">
<ul class="breadcrumb">
<li>
Home
</li>
~ pseudo logic
{{#each path that isn't current page}}
<li>
{{this}}
</li>
{{/each}}
<li>
{{ currentPage }}
</li>
</ul>
</template>
Has anyone done this or found a reference that I haven't stumbled across yet?
I'll give you my own recipe for breadcrumbs using iron:router.
It works by supplying additional options to your routes in order to establish a hierarchy between them, with parent-children relations. Then we define a helper on the Router to give us a list of parent routes (up to home) for the current route. When you have this list of route names you can iterate over them to create your breadcrumbs.
First, we need to define our breadcrumbs template which is actually very similar to your pseudo-code. I'm using bootstrap and font-awesome, as well as some newly introduced iron:router#1.0.0-pre features.
<template name="breadcrumbs">
<ol class="breadcrumb">
<li>
{{#linkTo route="home"}}
<i class="fa fa-lg fa-fw fa-home"></i>
{{/linkTo}}
</li>
{{#each intermediateRoutes}}
<li>
{{#linkTo route=name}}
<strong>{{label}}</strong>
{{/linkTo}}
</li>
{{/each}}
<li class="active">
<strong>{{currentRouteLabel}}</strong>
</li>
</ol>
</template>
The {{#linkTo}} block helper is new in iron:router#1.0.0-pre, it simply outputs an anchor tag with an href attribute which value is {{pathFor "route"}}.
Let's define the helpers from our breadcrumbs template:
Template.breadcrumbs.helpers({
intermediateRoutes: function() {
if (!Router.current()) {
return;
}
// get rid of both the first item, which is always assumed to be "home",
// and the last item which we won't display as a link
var routes = Router.parentRoutes().slice(1, -1);
return _.map(routes, function(route) {
// extract name and label properties from the route
return {
name: route.getName(),
label: route.options.label
};
});
},
currentRouteLabel: function() {
// return the label property from the current route options
return Router.current() && Router.current().route.options.label;
}
});
Notice that we rely on the existence of a special option named 'label' which represents what we're going to put in our anchors, we could also have used the name for testing purpose.
The parentRoutes method is something we need to extend the Router with:
_.extend(Router, {
parentRoutes: function() {
if (!this.current()) {
return;
}
var routes = [];
for (var route = this.current().route; !_.isUndefined(route); route = this.routes[route.options.parent]) {
routes.push(route);
}
return routes.reverse();
}
});
Again, this function assumes that every route (except "home") has a parent property which contains the name of its parent route, we then iterate to traverse the route hierarchy (think of a tree, like a file system structure) from the current route up to the root route, collecting each intermediate route in an array, along with the current route.
Finally, don't forget to declare your routes with our two additional properties that our code relies on, along with a name which is now mandatory as routes are indexed by name in the Router.routes property:
Router.route("/", {
name: "home"
});
Router.route("/nested1", {
name: "nested1",
parent: "home"
});
Router.route("/nested1/nested2", {
name: "nested2",
parent: "nested1"
});
// etc...
This example is pretty basic and certainly doesn't cover every use case, but should give you a solid start in terms of design logic toward implementing your own breadcrumbs.
Inspired by #saimeunt I created a meteor breadcrumb plugin which can be found here: https://atmospherejs.com/monbro/iron-router-breadcrumb. You also specify a parent route and a title for the route itself.
I used saimeunt answer but had to make small changes to the template and the template helpers because I have parameters in some of my route paths. Here are my changes.
Template changes: add data=getParameter to #linkTo for intermediate routes
<template name="breadcrumbs">
<ol class="breadcrumb">
<li>
{{#linkTo route="dashboard"}}
<i class="fa fa-lg fa-fw fa-home"></i>
{{/linkTo}}
</li>
{{#each intermediateRoutes}}
<li>
{{#linkTo route=name data=getParameters}}
<strong>{{label}}</strong>
{{/linkTo}}
</li>
{{/each}}
<li class='active'>
<strong>{{currentRouteLabel}}</strong>
</li>
</ol>
</template>
Template helper changes: add helper function getParameters to get parameters from current route.
Template.breadcrumbs.helpers({
intermediateRoutes: function () {
if (!Router.current()) {
return;
}
var parentRoutes = Router.parentRoutes();
var routes = parentRoutes.slice(1, -1);
var intermediateRoutes = _.map(routes, function (route) {
return {
name: route.getName(),
label: route.options.label
};
});
return intermediateRoutes;
},
currentRouteLabel: function () {
var currentRouteLabel = Router.current() && Router.current().route.options.label;
return currentRouteLabel;
},
getParameters: function(){
var currentRoute = Router.current();
var parameters = currentRoute.params;
return parameters;
}
});

Meteor js - how to hook into the 'rendered' event of a recursive template?

I've got a situation where I'm rendering a Handlebars partial recursively based on a Mongodb tree structure, something like this :
<template name='menu'>
<ul class='menu'>
{{#each topLevelChildren}}
{{>menu-item}}
{{/each}}
</ul>
</template>
<template name='menu-item'>
<li>{{name}}
{{#if listChildren.count}}
<ul>
{{#each listChildren}}
{{>menu-item}}
{{/each}}
</ul>
{{/if}}
</li>
</template>
where the mongodb docs look like this :
{ _id : ObjectId("525eb7245359090f41b65106"),
name : 'Foo',
children : [ ObjectId("525eb60c5359090f41b65104"), ObjectId("525eb6ca5359090f41b65105") ]
}
and listChildren just returns a cursor containing the full docs for each element in the children array of the parent.
I want to do a bit of jquery makeup on the rendered tree, but I can't seem to hook into the 'rendered' event for the entire tree, something like
Template.menu-completed.rendered = function(){
// console.log('done!');
}
Trying this
Template.menu.rendered = function(){
console.log($('menu li'));
}
Not only doesn't this return the right results (brackets filled with commas), it also freezes web inspector (but not the app...).
Any help would be much appreciated !
This is an interesting problem :)
In the current version of Meteor (0.7.x) the rendered callback is called once when the template is first rendered, and then again once any partial inside the template is re-rendered. (This behavior will change when Meteor 0.8 - the shark branch or "Meteor UI" - lands.)
I've never tried doing things recursively, but it's going to be very hard to tell which callback you get is actually for the parent. One thing you might want to try is to start the recursive rendering in a template called menu-parent or something. That way, when the rendered callback is called for the first time (and as long as your data is loaded), you know the whole tree is rendered.
However, I suspect there might be a better way to do this. Generally, you should not be using jQuery to modify classes and attributes on DOM elements, as you'll confuse yourself as to what Meteor is doing and what jQuery is doing. Can you elaborate on what you are trying to achieve with the following, and I'll update my answer?
I need to find a specific anchor tag within the fully rendered template and add a class to it... and then add a class to each of its parents
Petrov, your code actually works for me, with just some minor modification to the jquery part. But perhaps I'm misunderstanding. Here is what I did:
Created a brand new project.
Added your template in the .html with minor change: <li><span class="name">{{name}}</span>
Created a new collection List.
Template.menu.topLevelChildren = function() { return List.find(); };
Then I added this handler:
Template.menu.rendered = function(e){
$('.name').each(function(i, e) {
console.log(e);
});
}
Now, upon render, for instance when new data is inserted into the List collection, I get a printout on the console like this:
<span class=​"name">​second​</span>​
<span class=​"name">​second2​</span>​
<span class=​"name">​second21​</span>​
<span class=​"name">​second22​</span>​
<span class=​"name">​third​</span>​
<span class=​"name">​third2​</span>​
<span class=​"name">​third21​</span>​
<span class=​"name">​third22​</span>​
<span class=​"name">​third​</span>​
<span class=​"name">​third2​</span>​
<span class=​"name">​third21​</span>​
<span class=​"name">​third22​</span>​
This is just a flat list of the tree elements I had added (where second2 is nested in second, and second2x is nested in second2, etc.). So from what I understand you could just take the selection coming back from jquery and find the tag you are looking for and change its class, as you wanted. Please let me know if I misunderstood something.
Complete code below.
test.html:
<head>
<title>test</title>
</head>
<body>
{{> menu }}
</body>
<template name='menu'>
<ul class='menu'>
{{#each topLevelChildren}}
{{>menu-item}}
{{/each}}
</ul>
</template>
<template name='menu-item'>
<li><span class="name">{{name}}</span>
<ul>
{{#each children}}
{{>menu-item}}
{{/each}}
</ul>
</li>
</template>
test.js:
List = new Meteor.Collection("List");
if (Meteor.isClient) {
Template.menu.topLevelChildren = function() {
return List.find();
};
Template.menu.rendered = function(e){
$('.name').each(function(i, e) {
console.log(e);
});
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}

Resources