问题
I was wondering whether anyone has built their own waitOn function? I am not using subscribe to drive the waitOn but rather I want to wait on a helper to trigger the ready state, something like this:
this.route('edit_Record', {
path: '/record/edit/:_id',
waitOn: function() {
return XXXXX;
}
});
Template.edit_Record.helpers({
selectedRecord: function () {
wait(3000);
var x = myRecords.findOne({_id: this.editid});
//XXXXX This is where I want to set 'ready'
return x;
}
});
回答1:
I've designed my own pattern to work with a custom waitOn. You should be aware that IronRouter won't render your template (and thus none of its helpers except if you call them manually which is usually odd) UNLESS every handles specified in the waitOn function are ready.
waitOn is a reactive computation, so the handles you specify should be reactive data sources, and as their ready state evolves, waitOn will be reevaluated automatically and ultimately it will notify the IronRouter that it's OK to render the template.
So if we want to use waitOn with something else than subscription handles, we have to implement our own object with a reactive ready() method (this is from the docs). We'll call this object a "Waiter" because its role is to wait until some event happens and then it sets his internal state to ready.
I'll introduce you a simple example which solves a common problem : image preloading. Suppose you have a template rendering image elements whose src attributes are stored in a Collection : you'd like to render the template only when the images are loaded client-side.
<template name="view">
<div>
<h1>{{title}}</h1>
<img src="{{firstImageUrl}}" />
<img src="{{secondImageUrl}}" />
</div>
</template>
I came up with the following interface :
this.route("view",{
path:"/view/:_id",
loadingTemplate:"loadingTemplate",
template:"view",
// our Waiter object handle designed to wait until images are loaded
imagePreloadingWaiter:new ImagePreloadingWaiter(),
// load is called only once each time the route is triggered
load:function(){
// reset our waiter
this.imagePreloadingWaiter.reset();
},
// before : reactive computation that will be rerun until the route template is rendered
before:function(){
// setup collection subscription
var subscriptionHandle=this.subscribe("collectionById",this.params._id);
if(subscriptionHandle.ready()){
// get the route data context
var collection=this.data();
// collect the images URLs we want to preload
var params={
images:[
collection.firstImageUrl,
collection.secondImageUrl
]
};
// fire the preloader
this.imagePreloadingWaiter.fire(params);
}
},
// we specify that we want to wait on our ImagePreloadingWaiter handle
waitOn:function(){
return this.imagePreloadingWaiter;
},
// return the data context used by this route
data:function(){
return Collection.findOne(this.params._id);
}
});
Using this route definition, we display the loading template until the images URLs stored in our collection are finally loaded, thanks to the waitOn method which waits on our ImagePreloadingWaiter interface handle.
Ok so now that we have an overview of the interface we'd like to use, let's actually implement it :
// Simple interface to use with the IronRouter waitOn method
Waiter=function(){
// avoid firing the waiter multiple time in a Deps.Computation context
this.isFired=false;
// reactive data source : have we been waiting long enough ?
this.isReady=false;
this.dependency=new Deps.Dependency();
};
_.extend(Waiter.prototype,{
// reset method, clear the waiter state
reset:function(){
this.isFired=false;
//
this.isReady=false;
this.dependency.changed();
},
// reactive ready method : this is the interface needed by waitOn
ready:function(){
this.dependency.depend();
return this.isReady;
},
// fire the Waiter object only once before being resetted
fire:function(params){
if(!this.isFired){
this.isFired=true;
// this abstract method must be overloaded in child classes
this.wait(params);
}
},
// must be called in Waiter.wait() to acknowledge we're done waiting
waitedEnough:function(){
// if we have reset the Waiter meanwhile, silently discard the notification
if(this.isFired){
this.isReady=true;
this.dependency.changed();
}
}
});
// Simple waiter that simply waits N seconds before getting ready
TimeoutWaiter=function(){
Waiter.call(this);
};
TimeoutWaiter.prototype=Object.create(Waiter.prototype);
_.extend(TimeoutWaiter.prototype,{
wait:function(params){
var self=this;
// after N seconds, notify that we are done waiting
Meteor.setTimeout(function(){
self.waitedEnough();
},params.seconds*1000);
}
});
// Image preloader for the IronRouter
ImagePreloadingWaiter=function(){
Waiter.call(this);
};
ImagePreloadingWaiter.prototype=Object.create(Waiter.prototype);
_.extend(ImagePreloadingWaiter.prototype,{
wait:function(params){
var self=this;
//
if(images.length>0){
var imageLoadedCounter=0;
_.each(images,function(imageUrl){
function onImageLoadOrError(){
imageLoadedCounter++;
if(imageLoadedCounter==images.length){
self.waitedEnough();
}
}
//
var image=$("<img/>");
image.load(onImageLoadOrError);
image.error(onImageLoadOrError);
image.prop("src",imageUrl);
});
}
else{
self.waitedEnough();
}
}
});
Using this example I'm sure you will figure out a nice solution to answer your question.
In particular, I think you may want to move your "helper" logic code inside the before IronRouter hook. Don't hesitate to ask questions if my code is unclear.
回答2:
You could use something without ironRouter i.e in your template. Assuming you have a template called 'loading' and you're using a layout called 'layout' set with ironrouter
HTML
<template name="layout">
{{#if isready}}
{{yield}}
{{else}}
{{>loading}}
{{/if
</template>
Javascript (Client side)
Template.layout.isready = function() {
return !Session.get("isnotready");
}
Template.edit_Record.helpers({
selectedRecord: function () {
Session.set("isnotready", true);
wait(3000);
var x = myRecords.findOne({_id: this.editid});
Session.set("isnotready, false);
return x;
}
});
来源:https://stackoverflow.com/questions/20968383/meteor-iron-router-with-custom-waiton