问题
I want to create charts based on data from my collection. This data is called by an aggregate pull in my router-settings and set by Session.data in the template.rendered function.
The Meteor.call is placed in the waitOn-function. If the template is rendered, the data is not present.
I tried onBeforeAction, action, setTimeout... but i can't set the render-function to wait until the call-data is present.
I tried to set the calls in the onBeforeAction and onRun hooks, in the action, waitOn and data functions both on my RouteController and Router.route.
I wrapped my rendered-code with setTimeout, but it didn't work.
Router.configure({
layoutTemplate: 'global',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
});
Router.onBeforeAction("loading");
is set in my global routing settings.
I've already tried following solutions:
question 23575826
question 26198531
https://github.com/EventedMind/iron-router/issues/554#issuecomment-39002306
and more in the last days.
Is there any suggestion for my router settings or another way to solve this problem and get the data rendered in time? I consider to pick the npm-modules fiber/future, but i've no idea how to embed and use them.
My settings: Meteor is v1.0.2.1
router.js with own controller
StatsController = RouteController.extend({
template: 'statsShow',
waitOn: function () {
return [
Meteor.call('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'SellerOne', 2014, function(error, result){
if(!error)
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
}),
Meteor.call('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'SellerTwo', 2014, function(error, result){
if(!error)
Session.set('info3Units', result['units']);
Session.set('info3Volumes', result['volumes']);
}),
Meteor.call('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'SellerThree', 2014, function(error, result){
if(!error)
Session.set('avaUnits', result['units']);
Session.set('avaVolumes', result['volumes']);
})
];
},
data: function () {
return Books.findOne({_id: this.params._id});
},
action: function () {
if (!this.ready()) {
this.render('Loading');
} else {
this.render();
}
}
});
Router.route('stats/show/', {
name: 'stats.show',
controller: 'TestController'
});
methods.js
Meteor.methods({
saleGetDataPerYear: function(bookId, seller, year) {
var sellerUnits = [];
var sellerVolumes = [];
var resultData = {};
var pipeline = [
{
$match : { bookId: bookId, salesSeller: seller, salesYear: year }
},
{
$group : {
_id : {
sale: { "salesMonth": "$salesMonth" }
},
units: { $sum: "$salesUnits" },
volumes: { $sum: "$salesVolumes" },
month: { $first: "$salesMonth" },
year: { $first: "$salesYear" },
seller: { $first: "$salesSeller" }
}
},
{
$sort : {
month: 1
}
}
];
result = Sales.aggregate(pipeline);
if(result){
sellerUnits.push(seller);
sellerVolumes.push(seller);
result.forEach(function(data){
sellerUnits.push(data.units);
sellerVolumes.push(data.volumes);
});
resultData['units'] = sellerUnits;
resultData['volumes'] = sellerVolumes;
}
if(resultData){
return resultData;
} else {
throw new Meteor.Error("no-data", "No Data collected");
}
}
template
//-- template rendered functions
Template.statsShow.rendered = function(){
var chartUnitsBrockhaus = Session.get('brockhausUnits');
var chartUnitsInfo3 = Session.get('info3Units');
var chartUnitsAva = Session.get('avaUnits');
var chartUnitsSumme = Session.get('sumUnits');
console.log(chartUnitsBrockhaus);
var chartUnits = c3.generate({
bindto: this.find('.chartUnits'),
data: {
columns: [
chartUnitsBrockhaus,
chartUnitsInfo3,
chartUnitsAva,
chartUnitsSumme
],
type: 'bar',
types: {
Summe: 'spline',
},
},
axis: {
x: {
type: 'category',
categories: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
}
},
bar: {
width: {
ratio: 0.5
}
}
});
};
packages
accounts-password 1.0.5
accounts-ui 1.1.4
alanning:roles 1.2.13
aldeed:autoform 4.2.2
aldeed:autoform-select2 1.0.3
aldeed:collection2 2.3.1
aldeed:simple-schema 1.3.0
anti:fake 0.4.1
chrismbeckett:fontawesome4 4.2.2
coffeescript 1.0.5
ctjp:meteor-bootstrap-switch 3.3.1_1
dburles:collection-helpers 1.0.2
francocatena:status 1.0.3
iron:router 1.0.7
lepozepo:accounting 1.0.0
less 1.0.12
matteodem:easy-search 1.4.6
meteor-platform 1.2.1
meteorhacks:aggregate 1.1.0
mrt:jquery-csv 0.7.1
natestrauser:select2 3.5.1
nemo64:bootstrap 3.3.1_1
ongoworks:security 1.0.1
peerlibrary:xml2js 0.4.4_3
peernohell:c3 1.1.2
sacha:spin 2.0.4
service-configuration 1.0.3
underscore 1.0.2
zimme:select2-bootstrap3-css 1.4.1
Edit
as @DavidWeldon mentioned i changed my waitOn function to:
waitOn: function () {
return [
// first call
Meteor.callWithReady('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'Brockhaus', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
};
}),
// second call
Meteor.callWithReady('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'Info3', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
};
}),
// third call
Meteor.callWithReady('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'AVA', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
};
}),
// fourth call
Meteor.callWithReady('saleGetSumDataPerYear', 'nYWpgxR3kEY8kwBkA', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('sumUnits', result['units']);
Session.set('sumVolumes', result['volumes']);
}
})
];
},
and added test.coffee under /lib:
_.defaults Meteor,
callWithReady: (method, options...) ->
dep = new Deps.Dependency
ready = false
lastOption = _.last options
if _.isFunction lastOption
Meteor.apply method, _.initial(options), (err, result) ->
lastOption err, result
ready = true
dep.changed()
else
Meteor.apply method, options, (err, result) ->
ready = true
dep.changed()
ready: ->
dep.depend()
ready
result is: my calls loop.
I tested the answer from @apendua.
function waitUntilDone (action) {
var isReady = new ReactiveVar(false);
action(function () {
isReady.set(true);
});
return {
ready: function () {
return isReady.get();
}
};
}
waitOn: function () {
return [
// first call
waitUntilDone(function(done) {
Meteor.callWithReady('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'Brockhaus', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
};
done();
})
}),
// second call
waitUntilDone(function(done) {
Meteor.call('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'Info3', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
done();
};
})
}),
// third call
waitUntilDone(function(done) {
Meteor.call('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'AVA', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
done();
};
})
}),
// fourth call
waitUntilDone(function(done) {
Meteor.call('saleGetSumDataPerYear', 'nYWpgxR3kEY8kwBkA', 2014, function(error, result){
if(!error) {
console.log(result);
Session.set('sumUnits', result['units']);
Session.set('sumVolumes', result['volumes']);
done();
}
})
})
];
},
or
waitOn: function () {
return [
// first call
waitUntilDone(function(done) {
Meteor.callWithReady('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'Brockhaus', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
};
done();
}),
Meteor.call('saleGetDataPerYear', 'nYWpgxR3kEY8kwBkA', 'Info3', 2014, function(error, result){
if(!error) {
console.log(result); //debug
Session.set('brockhausUnits', result['units']);
Session.set('brockhausVolumes', result['volumes']);
done();
};
})
[...]
})
];
},
both results are: my calls loop.
回答1:
What you're missing here is that in your waitOn
you need to return a list of objects, each of them having a ready
method which acts as a reactive data source. Unfortunately, Meteor.call
does not return this kind of object, but for example Meteor.subscribe
does.
What you can do is to use the following wrapper but, make sure you have reactive-var
package added to your project first.
function waitUntilDone (action) {
var isReady = new ReactiveVar(false);
action(function () {
isReady.set(true);
});
return {
ready: function () {
return isReady.get();
}
};
}
Now, instead of returning a list of results of Meteor.call
like this
waitOn: function () {
return [
Meteor.call(..., function () { ... }),
Meteor.call(..., function () { ... }),
// ...
]
}
use the above wrapper in the following way
waitOn: function () {
return [
waitUntilDone(function(done) {
Meteor.call(..., function () {
// ...
done();
}),
}),
// ...
]
}
回答2:
Update: After some days of testing i changed the functions to Meteor.publish
instead of Meteor.method
so the waitOn
function is now working.
i didn't realise that this also work with publish. The examples for aggregate db-calls are all with Meteor.method
.
publications.js
Meteor.publish('saleGetAllDataPerYear', function(bookId, year) {
self = this;
var pipeBH = [];
var resultBH = '';
var unitsBH = [];
var volumesBH = [];
var monthBH = [];
var pipeI3 = [];
var resultI3 = '';
var unitsI3 = [];
var volumesI3 = [];
var monthI3 = [];
var pipeAVA = [];
var resultAVA = '';
var unitsAVA = [];
var volumesAVA = [];
var monthAVA = [];
var pipeSum = [];
var resultSum = '';
var unitsSum = [];
var volumesSum = [];
var monthSum = [];
// Set Brockhaus data
pipeBH = [
{ $match : { bookId: bookId, salesSeller: 'Brockhaus', salesYear: year } },
{ $group : { _id : { sale: { "salesMonth": "$salesMonth" } },
units: { $sum: "$salesUnits" }, volumes: { $sum: "$salesVolumes" }, month: { $first: "$salesMonth" }, year: { $first: "$salesYear" }, seller: { $first: "$salesSeller" }
}
},
{ $sort : { month: 1 } }
];
resultBH = Sales.aggregate(pipeBH);
if(resultBH != ''){
unitsBH.push('Brockhaus');
volumesBH.push('Brockhaus');
resultBH.forEach(function(data){
unitsBH.push(data.units);
volumesBH.push(data.volumes);
monthBH.push(data.month);
});
self.added('stats', Random.id(), {seller: 'Brockhaus', units: unitsBH, volumes: volumesBH, month: monthBH, year: year});
self.ready();
} else {
self.ready();
}
// Set Info3 data
pipeI3 = [
{ $match : { bookId: bookId, salesSeller: 'Info3', salesYear: year } },
{ $group : { _id : { sale: { "salesMonth": "$salesMonth" } },
units: { $sum: "$salesUnits" }, volumes: { $sum: "$salesVolumes" }, month: { $first: "$salesMonth" }, year: { $first: "$salesYear" }, seller: { $first: "$salesSeller" }
}
},
{ $sort : { month: 1 } }
];
resultI3 = Sales.aggregate(pipeI3);
if(resultI3 != ''){
unitsI3.push('Info3');
volumesI3.push('Info3');
resultI3.forEach(function(data){
unitsI3.push(data.units);
volumesI3.push(data.volumes);
monthI3.push(data.month);
});
self.added('stats', Random.id(), {seller: 'Info3', units: unitsI3, volumes: volumesI3, month: monthI3, year: year});
self.ready();
} else {
self.ready();
}
// Set AVA data
pipeAVA = [
{ $match : { bookId: bookId, salesSeller: 'AVA', salesYear: year } },
{ $group : { _id : { sale: { "salesMonth": "$salesMonth" } },
units: { $sum: "$salesUnits" }, volumes: { $sum: "$salesVolumes" }, month: { $first: "$salesMonth" }, year: { $first: "$salesYear" }, seller: { $first: "$salesSeller" }
}
},
{ $sort : { month: 1 } }
];
resultAVA = Sales.aggregate(pipeAVA);
if(resultAVA != ''){
unitsAVA.push('AVA');
volumesAVA.push('AVA');
resultAVA.forEach(function(data){
unitsAVA.push(data.units);
volumesAVA.push(data.volumes);
monthAVA.push(data.month);
});
self.added('stats', Random.id(), {seller: 'AVA', units: unitsAVA, volumes: volumesAVA, month: monthAVA, year: year});
self.ready();
} else {
self.ready();
}
// Set Sum data
pipeSum = [
{ $match : { bookId: bookId, salesYear: year } },
{ $group : { _id : { sale: { "salesMonth": "$salesMonth" } },
units: { $sum: "$salesUnits" }, volumes: { $sum: "$salesVolumes" }, month: { $first: "$salesMonth" }, year: { $first: "$salesYear" }, seller: { $first: "$salesSeller" }
}
},
{ $sort : { month: 1 } }
];
resultSum = Sales.aggregate(pipeSum);
if(resultSum != ''){
unitsSum.push('Summe');
volumesSum.push('Summe');
resultSum.forEach(function(data){
unitsSum.push(data.units);
volumesSum.push(data.volumes);
monthSum.push(data.month);
});
self.added('stats', Random.id(), {seller: 'Summe', units: unitsSum, volumes: volumesSum, month: monthSum, year: year});
self.ready();
} else {
self.ready();
}
});
router.js
waitOn: function () {
year = Number(Session.get('year'));
return [
Meteor.subscribe('saleGetAllDataPerYear', this.params._id, year),
Meteor.subscribe('getStats')
];
},
Thanks to @JeremyS for the inspiration on another way. That sounds like the better solution because now the waitOn
function works but the data is not rendered in my chart without manual refresh of my template.
来源:https://stackoverflow.com/questions/27990337/ironrouter-template-is-rendered-before-call-in-waiton-is-finished