How can I access DOM Elements declared within a template outside Template.myTemplate.events, Template.myTemplate.rendered etc - meteor

I have a template with few elements (input, radioButton etc). If I want to access to these DOM elements within mytemplate I can either access them within events
Template.myForm.events({
'click #submitButton' : function (event, template) {
//template variable here gives me access to the
//current template instance, so I can get to any
//DOM element within this template.
}
})
OR within
Template.myForm.rendered = function () {
//within this function I have access to "this" which points to template instance
}
I was wondering if there is a way to access the DOM Elements that a declared within a template outside of these event functions and rendered callback?
Thanks in advance

You can but you need to reference the template instance.
The reason for this is a single template can be used multiple times. In this case a single easy to use way to access the template would not know which instance it would belong to. This is why you need to use a reference, such as done in the example below.
You have to store the instance somewhere when it is rendered:
TheTemplateInstance = null;
Template.myForm.rendered = function() {
TheTemplateInstance = this;
}
Then you can use TheTemplateInstance anywhere you want, provided the template is on the DOM.
If you use myForm many times then it will only have access to the one created last.
Also You did not give a use case for your intentions. But there are several better ways to do most things with a template:
JQuery modding something when some variable changes (the most common use case where helpers aren't useful)
Template.myForm.rendered = function() {
var self = this;
this.autorun(function() {
var xx = something.findOne();
self.$("something").autoform() //Some jquery call
});
}
and helpers:
Template.myForm.helpers({
someName: function() {
return Session.get("name");
}
});
You can then use {{someName}} in your template's html where it can change when you use Session.set("name", "a new value");

Related

Meteor: Using If condition to conditionally display templates when user clicks a navigation link

I have some templates corresponding to different places. I am using a navigation bar which has links to different places(Manali). I want the corresponding template to be displayed when a particular link is being clicked. I tried assigning id to each anchor link and use it inside the #if loop of the main file. Like below.
{{#if equals id 'badrinath'}}
{{> Manali}}
{{/if}
I created a helper function also for the comparison purpose.
UI.registerHelper('equals', function(a, b) {
return a == b;
});
But it isn't working. Can anyone suggest a solution. What property of the link can I capture and use it to display the template accordingly.
You sound to be looking for "routing" functionality.
You might be interested in Iron Router or Flow Router.
You can still implement your functionality without router, as it sounds still a simple situation as described. You are probably just lacking some event listeners to set your id variable to the correct value.
Probably something like:
<a data-role="changetemplate" href="targetTemplate">To Target Template</a>
var id = new ReactiveVar(); // add the reactive-var package
Template.myTemplate.helpers({
id: function () {
return id.get();
}
});
Template.myTemplate.events({
"click a[data-role='changetemplate']": function (event) {
event.preventDefault();
id.set(event.currentTarget.href);
}
});

Meteor.js: template.<html>.events vs Template.<template>.events 'this' binding seems inconsistent

I'm looking through the Meteor simple tutorial and the way that 'this' binding in the different Template objects works seems inconsistent to me in my unknowledgeable state.
Template.body.events({
"submit .new-task": function(event) {
console.log(this); // Logs an empty object
}
})
Template.task.events({
"click .toggle-checked": function() {
console.log(this); // logs a task
}
});
I can see that task is an xml template defined in the view, which is a visual representation of the items returned by a function in the Template.body.helpers object.
I guess that the task objects are bound the html representation of each object (though I can't see how as there doesn't seem to be any identifying property within the li elements??)
Anyhow. When I click the task, this is the task. But when I submit the form, I was expecting this to be the body. Why is it not?
I was expecting Meteor to handle Template.body and Template.task in a similar way
In Meteor this referes to the data context. You define it with helpers or with the route controller ( IronRouter or FlowRouter)
Example:
{{#with myData}}
<h1>{{title}}</h1>
{{/with}}
js
Template.yourTemplate.helpers({
myData : function(){
return {
title : "My title"
}
}
})
You need to use the "event" argument
Template.task.events({
"click .toggle-checked": function( event , instance ) {
console.log( event );
}
});
The instance argument is also very useful. You have access to a jQuery selector like: instance.$() and it will only search for elements on your template and also child templates.
Personally I use the instance a lot. My Favorite pattern is:
Template.task.onCreated(function(){
this.vars = new ReactiveDict();
this.data = "some data";
});
Later if you want to access vars or data:
Events - You get this on the arguments
Helpers - var instance = Template.instance();
With instance you avoid storing states in the global namespace, like Session, and your code is a lot easier to maintain and understand. I hope this helps you to understand how template works in Blaze.

Know when child templates have been rendered

<template name="FrameItems">
<div class="frame-items">
{{#each frames}}
{{> FrameItem}}
{{/each}}
</div>
</template>
In the above example, I want to know when all FrameItem templates inside FrameItems template have been rendered. I thought onRendered of the parent would be invoked when all the child templates have been rendered, but it was just called right away. What's the conventional way of making sure all the child templates are rendered?
One way to do it is to use a counter and increment it until it reaches a certain value.
Here the counter would in Session and incremented until it reaches the length of your Frames iterable thing:
Template.FrameItems.onRendered(function() {
Session.set('frameCounter', 0);
});
Template.FrameItem.onRendered(function() {
Session.set('frameCounter', Session.get('frameCounter') + 1);
});
Then you simply use a tracker:
//Where template is your template instance, for example 'this' in an onCreated callback
template.autorun(function doStuffWhenFramesRendered(computation) {
if(Session.get('frameCounter') === template.frames.length) {
doStuff();
//Stop observing
computation.stop();
}
});
Note that it takes into account the fact that FrameItem may render at weird times (avoiding race conditions if any), but it doesn't take into account new frames. To take those into account you would not stop the computation.
Here is how I would proceed:
You create a pageSession reactive variable or reactive dictionary entry. Let's call it lastRendered.
You update it in the onRendered function of your FrameItem template using the _id of the related frames item. This way, each time a FrameItem template is rendered, you now which one it is.
You create an helper in your parent template watching your lastRendered reactive variable and checking if it matches your last frames item. It could look like that (untested code):
lastFrameIsRendered: function() {
var lastId = frames.find().limit(1).sort({$natural:-1}).fetch()._id;
return pageSession.get ("lastRendered") === lastId;
},
Alternatively, if you need to get a feedback in your parent template onRendered function, you can wrap this code into a this.autorun(function() { (tracker) like this:
var lastId = frames.find().limit(1).sort({$natural:-1}).fetch()._id;
this.autorun(function() {
if (pageSession.get ("lastRendered") === lastId) {
//do your stuff
}
});
It will be executed each time there is a change in your parent template.

Template empty initially but renders properly on changing and coming back to route

I have a template named profile which contains three other templates. One of these templates is {{> postlist}}
and the helper function for this template is
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : postsArr}});
}
});
The problem is on going to the route, postlist template is empty, since postsArr is calculated later after the dom has loaded on the basis of other two templates. But, if I click on other route and come back to this route, the template renders properly.
What should I do that template renders properly initially itself?
The easiest way would be to us Session, though it's probably the worst option:
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : Session.get('postsArr') }});
}
});
If you now call Session.set('postArr', ...) anywhere in your code the posts helper will update automatically. The second option is to use a shared reactive variable:
var postsArr = new ReactiveVar();
and then inside your helper:
return Posts.find({rph: {$in : posts.Arr.get() }});
Now you can do postsArr.set(...) and everything should work fine. Just remember to meteor add reactive-var do your project.
One last doubt is: where to put that reactive variable declaration? In most cases you can do away with putting in a single "controller" file. It will work as long as:
- you only have one instance of your template a time
- the code which sets ad gets the value of you reactive variable may be put in the same file
If one of the above conditions does not hold, then the only option to go, which is BTW the best possible, is to put your state variable in your template's scope. This is how you do it:
Template.postsList.created = function () {
this.postsArr = new ReactiveVar();
};
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : Template.instance().postsArr.get() }});
}
});
From helpers you can always access postsArr using the Template.instance() routine which always return the current template instance, for which the helper was called. From event handlers, note that the second argument of your handler is always the template instance, which you're interested in.
If you need to access it from another templates, then you should probably put your state variable on the corresponding route controller. Assuming you're using iron-router, that would be:
Iron.controller().state.get('postsArr');
The Iron.controller routine grants you access to the current route controller. Read this for more details.

Render callback to all templates in meteor blaze

I am forced to assign rendered callbacks to all my templates.
Until 0.9.0 I used to do it like this:
_.each( Template, function( template, name ) {
//...
template.rendered = function() {
//...
};
});
But now, Template is a constructor and not an object, so this method won't work here. Is there any way to pass callback function to all templates or fire function when all templates were rendered using Blaze?
Here is a quick workaround I came up with, iterating over every Template property to find out if it corresponds to a template definition, and if it does, assign the onRendered callback.
// make sure this code is executed after all your templates have been defined
Meteor.startup(function(){
for(var property in Template){
// check if the property is actually a blaze template
if(Blaze.isTemplate(Template[property])){
var template=Template[property];
// assign the template an onRendered callback who simply prints the view name
template.onRendered(function(){
console.log(this.view.name);
});
}
}
});
I don't know what's your use case so there may be better solutions depending on it.
With Meteor 1.2.1 the Template object has an onRendered(hook) function to accomplish an 'all template' onRendered behaviour.
Template.onRendered(function(){
var template = this;
Deps.afterFlush(function() {
console.log("triggering Jquery mobile component creation for "+template.view.name);
$(template.firstNode.parentElement).trigger("create");
});
});
The postponed update via Deps.afterFlush(callback) is optional and subject to your application needs.

Resources