IronRouter: template is rendered before call in waitOn is finished

不羁的心 提交于 2019-12-25 04:27:12

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!