I have a helper
Template.home.helpers({
songId: function(){
return SongManager.getSongId();
},
});
that uses the getSongId method from the SongManager
SongManager = {
init: function(songId){
this.dep = new Tracker.Dependency;
},
getSongId: function(){
this.dep.depend();
return this.songId;
},
setSongId: function(arg){
this.songId = arg;
this.dep.changed();
},
}
But it doesn't cause the template to update reactively when setSongId is called. Am I doing anything wrong? If I use Session.get and Session.set, then everything works properly.
SongManager = {
init: function(songId){
},
getSongId: function(){
return Session.get('songId');
},
setSongId: function(arg){
Session.set('songId', arg);
},
}
I solved it but I'm quite surprised but the results: Placing inside Meteor.startup() doesn't work. I need to investigate why, there's is something about Blaze that I don't know yet.
Check the repo I created for this: https://github.com/mariorodriguespt/stack-song-manager
Related
Session.set('coursesReady', false); on startup.
UPDATE:
I made it into a simpler problem. Consider the following code.
Inside router.js
Router.route('/', function () {
Meteor.subscribe("courses", function() {
console.log("data ready")
Session.set("coursesReady", true);
});
}
and inside main template Main.js
Template.Main.rendered = function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
The message "inject success" is not printed after "data ready" is printed. How come reactivity does not work here?
Reactivity "didn't work" because rendered only executes once (it isn't reactive). You'd need to wrap your session checks inside of a template autorun in order for them to get reevaluated:
Template.Main.rendered = function() {
this.autorun(function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
});
};
Probably a better solution is to wait on the subscription if you want to ensure your data is loaded prior to rendering the template.
Router.route('/', {
// this template will be rendered until the subscriptions are ready
loadingTemplate: 'loading',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('courses');
},
action: function () {
this.render('Main');
}
});
And now your rendered can just do this:
Template.Main.rendered = function() {
Meteor.typeahead.inject();
};
Don't forget to add a loading template.
To Solve Your Problem
Template.registerHelper("course_data", function() {
console.log("course_data helper is called");
if (Session.get('coursesReady')) {
var courses = Courses.find().fetch();
var result = [ { **Changed**
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
}];
Session.set('courseResult', result); **New line**
return Session.get('courseResult'); **New line**
,
Explanation
The answer is at the return of the helper function needs to have be associated with reactivity in order for Blaze, template renderer, to know when to rerender.
Non-reactive (Doesn't change in the DOM as values changes)
Template.Main.helpers({
course_data: UI._globalHelpers.course_data ** Not reactive
});
Essentially: UI._globalHelpers.course_data returns an array of objects which is not reactive:
return [
{
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
},
Reactive
From Meteor Documentation:
http://docs.meteor.com/#/full/template_helpers
Template.myTemplate.helpers({
foo: function () {
return Session.get("foo"); ** Reactive
}
});
Returning Session.get function to Blaze is reactive; thus, the template will change as the values changes.
I have a route I call many times. I have to subscribe two collections for having all datas, here's a snapshot:
var one = new Blaze.ReactiveVar(false);
var two = new Blaze.ReactiveVar(false);
this.route('stopIndex', {
path: '/stop/:section/:stop_id',
waitOn: function() {
Meteor.call('getTripIdsForStop', {
stop_id: this.params.stop_id,
from: fromNow(),
to: toMax(),
db: prefix
}, function(err, ids) {
DEBUG && console.log('TRIP_IDS:', ids);
Meteor.subscribe(prefix + '_trips', {
trip_id: {$in: ids}
}, function() {
one.set(true);
});
Meteor.subscribe(prefix + '_stop_times', {
trip_id: {$in: ids}
}, function() {
two.set(true);
});
});
return [
function () { return one.get(); },
function () { return two.get(); }
];
},
The first time I call the route, all goes fine. The second time, the one and two vars are already setted to true so the waitOn doesn't wait and I get a no data message on my template for some seconds, until collections responds. I've tried putting on the first lines of waitOk method:
one.set(false);
two.set(false);
but this makes the waitOn to wait forever. Am I doing something wrong or missing something? Thanks for the help.
I've solved this way:
Router.onStop(function() {
one.set(false);
two.set(false);
});
that invalidates ReactiveVars and will wait. I've also moved all code from waitOn to data. Now the waitOn is like this:
return [
function () { return one.get(); },
function () { return two.get(); }
];
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'm building an app that has versions of pages. I'd like users to be able to visit the page without having to specify the version they wish to start working with. In case no version is specified in the URL, I'd like to look up the latest version of that page and redirect to it.
The problem I'm having is that I can't do any collection lookups on the router level to figure out what version I'm looking for because the collections simply aren't there.
Here's my code so far:
Router.route('page', {
path: '/page/:slug/:version?',
onBeforeAction: function() {
var versionId = this.params.version;
if (!versionId) {
console.log('no version! oh noes!');
var p = Pages.find({slug: this.params.slug});
var newestVersion = Versions.findOne({page: p._id}, {sort: {createdAt: -1}});
versionId = newestVersion._id;
Router.redirect('page', this.params.slug, versionId);
}
this.next();
},
waitOn: function() {
return Meteor.subscribe('page', this.params.slug, this.params.state);
},
data: function() {
return {
page: Pages.findOne({slug: this.params.slug}),
version: Versions.findOne(this.params.version)
}
}
});
Any help or insights are very much appreciated!
Here's the solution I found. The problem is that the onBeforeAction didn't actually wait for the waitOn unless you tell it to.
onBeforeAction: function() {
if (this.ready()) {
if (!this.params.version) {
var p = Pages.findOne({slug: this.params.slug}),
sortedVersions = _.sortBy(Versions.find({page: p._id}).fetch(), function(s) {
return v.createdAt;
});
var newest_version_id = sortedVersions[0]._id;
this.redirect('page', {slug: this.params.slug, state: newest_version_id});
}
this.next();
}
}
I think the key is in the this.ready() check which is only true when all subscriptions in the waitOn are true. I assumed all this happened automagically by simply using the waitOn.
I am using a new Tracker.Dependency for tracking several things, but it causes the autorun in the code below to run infinitely. What is wrong? The code below is okay once I separate the getSong and getSongId to depend on dep and dep2, instead of just dep.
SongManager = {
dep: new Tracker.Dependency,
dep2: new Tracker.Dependency,
init: function(songId) {
var self = this;
this.setSongId(songId);
Meteor.subscribe('song', songId);
Tracker.autorun(function(){
var songs = self.getSongCursor().fetch();
if (songs.length > 0) {
self.song = songs[0];
self.dep.changed();
}
})
},
getSongCursor: function(){
return Songs.find({_id: this.getSongId()});
},
getSong: function(){
this.dep.depend();
return this.song;
},
getSongId: function(){
this.dep2.depend();
return this.songId;
},
setSongId: function(arg){
this.songId = arg;
this.dep2.changed();
},
};
The problem is that you're creating a circular dependency. I would recommend using ReactiveVar for this rather than working with the lower-level dependency API.
meteor add reactive-var
Then you can just do this:
SongManager = {
song: new ReactiveVar(),
songId: new ReactiveVar(),
init: function(songId) {
this.songId.set(songId);
this.computation = Tracker.autorun(_.bind(this.update, this));
},
update: function() {
var songId = this.songId.get();
Meteor.subscribe('song', songId);
this.song.set(Songs.findOne(songId));
},
stop: function() {
this.computation.stop();
}
};
SongManager.init(oldSongId);
SongManager.songId.set(newSongId);
// After enough time has passed for the subscription to update and tracker to flush:
var currentSong = SongManager.song.get();
console.log(currentSong._id === newSongId); // true
I also added a way for you to stop your autorun computation so it doesn't keep running in the background when it's no longer necessary. Note that since the subscription is run within an autorun, it will automatically be stopped and restarted when the songId changes. The update function will actually be run twice, but Meteor knows not to send two identical subscription requests.