I'm looking at how to turn off reactivity within a template helper function. I only want the data rendered when the template is initially populated and not when the underlying data changes.
My current helper has two reactive variables: one is a Session.get() and the other is a Collection.findOne(). I've tried to wrap the Session.get() in a Tracker.nonreactive() call and set the reactive option to false on the Collection.findOne() but I'm still seeing the reactive behavior.
Template.characterSkills.helpers({
data : function () {
var characterID = Tracker.nonreactive(function () {
return Session.get("currentCharacterID");
});
if(characterID) {
var record = Characters.findOne(
{ _id : characterID },
{
reactive : false,
fields : { skills : 1 }
}
);
if(record && record.skills)
return record.skills;
}
}
});
I've been trying to work this issue for about half a day now. Any help would be greatly appreciated.
You're using the name data for your helper which is reserved. So if you have another helper or an iron router route with data in it, or this template is a subtemplate of another template its likely to redraw too.
Try renaming it to something else.
You can use a closure to find and set the record when the template is rendered and then return a static object in the template helper:
Template.characterSkills.helpers({
dataItem: function(){
return record.get();
}
});
Template.characterSkills.onCreated(function(){
record.set(Session.get("currentCharacterID"));
});
var record = function(){
var local = {};
return {
set: function(characterId){
if ( characterID) local = Characters.findOne({_id: characterID},{fields : { skills : 1 }});
else local = null;
},
get: function(){
return local;
}
}();
Although even this feels like too much work. Surely there's an easier way.
Related
How can I use reactive template variables (from Template.data) in an anonymous function within the template rendered function? (I want to keep it reactive).
Template.templateName.rendered = function() {
function testFunction(){
//Log the field 'title' associated with the current template
console.log(this.data.title);
}
});
Not sure exactly what you are trying to do (like printing this.data.title whenever it changes?), but you should:
use a Reactive variable (add reactive-var package, then create a var myVar = new ReactiveVar()
If necessary, wrap your function with Tracker.autorun (or this.autorun in a template creation / rendered event).
So you could have like:
Parent template HTML:
<template name="parentTemplateName">
{{> templateName title=myReactiveVar}}
</template>
Parent template JS:
Template.parentTemplateName.helpers({
myReactiveVar: function () {
return new ReactiveVar("My Title!");
}
});
Template JS:
Template.templateName.onRendered(function() {
// Re-run whenever a ReactiveVar in the function block changes.
this.autorun(function () {
//Print the field 'title' associated with the current template
console.log(getValue(this.data.title));
});
});
function getValue(variable) {
return (variable instanceof ReactiveVar) ? variable.get() : variable;
}
What worked for me was simple using autorun() AND using Template.currentData() to grab the values from within autorun():
let title;
Template.templateName.rendered = function() {
this.autorun(function(){
title = Template.currentData().title;
});
function testFunction(){
console.log(title);
}
});
Template.templateName.onRendered(function(){
console.log(this.data.title);
});
Consider the following code in a javascript library;
document.registerElement('my-component', { prototype: { foo: true }});
It seems registerElement returns a function which can be used as a constructor.
How can I get a reference to this function later ?
var tempDom = document.createElement('my-component')
console.log(tempDom.__proto__)
Seems working but it requires creating an instance first.
I think you just need to save the return from registerElement() in a variable, and then use that variable later. If you do not save the return then I believe it is lost.
// Save the return in a variable
var mycomp = document.registerElement('my-component');
// Use the var to create the element
document.body.appendChild(new mycomp());
// Then you can do things with the new tag
var mytag = document.getElementsByTagName("my-component")[0];
mytag.textContent = "I am a my-component element.";
prototype method will give you an expected result.
var mc = document.registerElement(
'my-component', { prototype: { foo: true }}
);
console.log(mc.prototype);
//⇒ my-component {foo: true}
Hope it helps.
This is a bit puzzling to me. I set data in the router (which I'm using very simply intentionally at this stage of my project), as follows :
Router.route('/groups/:_id',function() {
this.render('groupPage', {
data : function() {
return Groups.findOne({_id : this.params._id});
}
}, { sort : {time: -1} } );
});
The data you would expect, is now available in the template helpers, but if I have a look at 'this' in the rendered function its null
Template.groupPage.rendered = function() {
console.log(this);
};
I'd love to understand why (presuming its an expected result), or If its something I'm doing / not doing that causes this?
From my experience, this isn't uncommon. Below is how I handle it in my routes.
From what I understand, the template gets rendered client-side while the client is subscribing, so the null is actually what data is available.
Once the client recieves data from the subscription (server), it is added to the collection which causes the template to re-render.
Below is the pattern I use for routes. Notice the if(!this.ready()) return;
which handles the no data situation.
Router.route('landing', {
path: '/b/:b/:brandId/:template',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var brand = Brands.findOne(this.params.brandId);
if (!brand) return false;
this.title = brand.title;
return brand;
},
waitOn: function() {
return [
Meteor.subscribe('landingPageByBrandId', this.params.brandId),
Meteor.subscribe('myProfile'), // For verification
];
},
});
Issue
I was experiencing this myself today. I believe that there is a race condition between the Template.rendered callback and the iron router data function. I have since raised a question as an IronRouter issue on github to deal with the core issue.
In the meantime, workarounds:
Option 1: Wrap your code in a window.setTimeout()
Template.groupPage.rendered = function() {
var data_context = this.data;
window.setTimeout(function() {
console.log(data_context);
}, 100);
};
Option 2: Wrap your code in a this.autorun()
Template.groupPage.rendered = function() {
var data_context = this.data;
this.autorun(function() {
console.log(data_context);
});
};
Note: in this option, the function will run every time that the template's data context changes! The autorun will be destroyed along with the template though, unlike Tracker.autorun calls.
I try to get the returned data in my Template.rendered function.
The current code is:
this.route('editCat', {
layoutTemplate : 'layoutCol2Left',
template : 'modCategoriesEdit',
path : '/mod/categories/edit/:_id',
yieldTemplates : _.extend(defaultYieldTemplates, {
'navigationBackend' : {to : 'contentLeft'}
}),
waitOn : function () {
return Meteor.subscribe('oneCat', this.params._id);
},
data : function () {
return Categories.findOne({_id : this.params._id});
}
});
In this block i wait on the subscribtion of the Collection Document and return the Document as data.
Now i can use the returned Document in my Template like this:
<template name="modCategoriesEdit">
<h1>Edit {{name}}</h1>
</template>
My problem is that i have to use the returned data in my rendered function like this:
Template.modCategoriesEdit.rendered = function () {
console.log(this.data);
}
But this returns "null".
So my question is:
How is it possible to get access to the returned data in the rendered function ?
Solution:
Just add the following to your iron-router route() method.
action : function () {
if (this.ready()) {
this.render();
}
}
Than the Template will rendered after all is loaded correctly.
There are 3 solutions if you want to wait until the waitOn data is ready before rendering:
1- Add an action hook to each route
Router.map(function()
{
this.route('myRoute',
{
action: function()
{
if (this.ready())
this.render();
}
}
}
2- Use the onBeforeAction hook globally or on every route
Sample code for the global hook:
Router.onBeforeAction(function(pause)
{
if (!this.ready())
{
pause();
this.render('myLoading');
}
});
myLoading (or whatever name) must be a template you have defined somewhere.
Don't forget the this.render line, otherwise the problem will occur when leaving the route (while the original problem occurs when loading the route).
3- Use the built-in onBeforeAction('loading') hook
Router.configure(
{
loadingTemplate: 'myLoading',
});
Router.onBeforeAction('loading');
myLoading (or whatever name) must be a template you have defined somewhere.
Using the action hook to check for this.ready() works, but it looks like the official way to do this is to call the following:
Router.onBeforeAction("loading");
Reference: https://github.com/EventedMind/iron-router/issues/679
Like #Sean said, the right solution is to use a loading template:
Router.onBeforeAction("loading");
But if you don't want it, like me, I came up with this solution:
Template.xxx.rendered = function() {
var self = this;
this.autorun(function(a) {
var data = Template.currentData(self.view);
if(!data) return;
console.log("has data! do stuff...");
console.dir(data);
//...
});
}
Template.currentData is reactive, so in the first time it is null and in the second it has your data.
Hope it helps.
-- Tested on Meteor v1.0 with Iron Router v1.0
I have a Template named movies, that has a method that returns a list of objects from a collection.
The query to generate that list of objects is created dynamically using data from another template method.
I would like to re-render the template, or just the components associated with that specific template method, whenever the filter data changes.
Here are the two methods used:
Template.movies.filter = function () {
if (Session.equals("filter", undefined)) {
return {};
}
return Session.get("filter");
};
Template.movies.movies = function () {
return Movies.find(Template.movies.filter(), {sort: [["Poster", "desc"]]});
};
On the HTML side it's a simple {{#each movies}}{{> movie}}{{/each}} to show the results from the movies method.
The problem is that when Session.get("filter") changes and therefore so does Template.movies.filter(), the HTML component relying on Template.movies.movies() data won't be updated with the new query results.
How would I achieve that behavior?
The easiest way is to just make a javascript function that both helpers utilize:
var getFilter = function() {
if (Session.equals("filter", undefined)) {
return {};
}
return Session.get("filter")
}
Template.movies.filter = function() { return getFilter(); }
Template.movies.movies = function () {
return Movies.find(getFilter(), {sort: [["Poster", "desc"]]});
};
This will react as you expect.