I've been looking for mechanism to update the model of a Route, and has the Component (called from within the template associated with that route) reacts to that event, and re-render itself.
So I have the index template like this (I pass in the model of the IndexController, which to my understanding is just a proxy to IndexRoute -- I don't have IndexController defined, by the way):
<script type="text/x-handlebars" id="index">
Below is the bar-chart component
<br/>
{{bar-chart model=model}}
</script>
And I have my component template like this:
<script type="text/x-handlebars" data-template-name="components/bar-chart">
</script>
My component is implemented in a separate JS file, like this:
App.BarChartComponent = Ember.Component.extend({
classNames: ['chart'],
model: null,
chart: BarChart(),
didInsertElement: function() {
Ember.run.once(this, 'update');
},
update: function() {
var data = this.get('model').map(function(sales) {
return sales.get('amount');
});
d3.select(this.$()[0]).selectAll('div.h-bar')
.data(data)
.call(this.get('chart'));
}
});
The BarChart() function is simply returns a function object that performs the DOM manipulation to generate the graph using D3.
My IndexRoute is defined like this:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('sales');
}
});
During this experiment, I use fixture:
App.Sales = DS.Model.extend({
amount: DS.attr('number')
});
idx = 1;
App.Sales.FIXTURES = [
{id: idx++, amount: 2}, {id: idx++, amount: 6}, {id: idx++, amount: 12},
{id: idx++, amount: 17}, {id: idx++, amount: 8}
];
I need to implement a mechanism to periodically poll the store and update the model of the Route, and has EmberJS's magic invoke again the render function (the value assigned to "chart" field in the BarChart component).
What's the correct way to do that? I've been trying to use setInterval and calling refresh() method of the Route, but have not been successful so far.
Thanks for your help!, Raka
ADDITION (I put my additional comment here for the formatting).
I added the call to setInterval in my app.js, like this:
setInterval(function () {
App.Sales.FIXTURES.shift();
App.Sales.FIXTURES.push({
id: idx++,
amount: Math.round(Math.random() * 20)
});
App.IndexRoute.refresh();
}, 1500);
But I'm getting JavaScript error, telling me that App.IndexRoute is undefined. I intend to call the 'refresh' method on the Route object because I'm hoping the model hook to be re-executed. How do I obtain a reference to instance of IndexRoute from my setInterval function?
Is this the correct / best way to trigger the refresh, btw?
(and, following the suggestion from Oliver below, I also added observes('model') to my 'update' function in the controller. So it is like this now:
App.BarChartComponent = Ember.Component.extend({
classNames: ['chart'],
model: null,
chart: BarChart(),
didInsertElement: function() {
...
},
update: function() {
...
}.observes('model')
});
ADDITION 2 (response to EmberJS, polling, update route's model, re-render component )
Got it! Thx.
Now for the updating use case (the number of elements in the backend stays the same, the ids stay the same, only the "amount" changes over time). I modified setInterval block to this:
setInterval(function () {
App.Sales.FIXTURES.forEach(function(elem) {
elem.amount = elem.amount + 5;
});
console.log('z');
}, 1500);
The problem now, the "update" method in BarChartComponent that observes "model.@each" never gets called (as if the changes I did in the elements of the fixture wasn't heard by the BarChartComponent).
What instruction(s) do I need to add?
ADDITION 3 (detail for EmberJS, polling, update route's model, re-render component ):
I added the definition of IndexController to my code, just to confirm that my changes to the elements in the FIXTURE was heard at least by the Controller (it is).
So, the problem now is making that change is also heard by the Component. How? Should I call some "render" function from my controller to ask the component to redraw itself?
App.IndexController = Ember.ArrayController.extend({
totalAmount: function() {
console.log("iiii");
var total = 0;
this.forEach(function(sales) {
console.log("a... " + sales.get('amount'));
total += sales.get('amount');
});
return total;
}.property('@each.amount')
});
App.IndexRoute
is actually a class definition, not an instance.
For your particular case there are some important things to note here, find('type')
returns the all
filter which automatically updates as you add/remove items from the store. So you could just call find
again anywhere in the code (for that type), and it would automatically update your collection. Additionally you would want to control the updating at the route level, that way you don't keep updating when you aren't in scope.
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('sales');
},
setupController: function(controller, model){
this._super(controller, model); // do the default implementation since I'm overriding this func
this.startRefreshing();
},
startRefreshing: function(){
this.set('refreshing', true);
Em.run.later(this, this.refresh, 30000);
},
refresh: function(){
if(!this.get('refreshing'))
return;
this.store.find('sales')
Em.run.later(this, this.refresh, 30000);
},
actions:{
willTransition: function(){
this.set('refreshing', false);
}
}
});
Example: http://emberjs.jsbin.com/OxIDiVU/825/edit
Additionally, for your component, you'd want to watch the array, not just the model itself (since the model reference won't update, meaning the observes method wouldn't be called). You would do something like this
watchingForModelChange: function(){
}.observes('model.[]')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With