Reputation: 23
I'm trying to build a front-end for a metrics tool with Ember. The code that I've written so far has been very much influenced by Eviltrout's emberreddit application
https://github.com/eviltrout/emberreddit
The goal is to have two classes that depend on each other: metrics and filters.
1) Once the application initializes, the filters, which are instances of the Filter-class, are loaded from the server. Once the filters have loaded, they are displayed as checkboxes on the screen. After that, the metrics objects should take the filters as parameters and query the server for data.
2) Once the user changes the checkboxes and thus updates the filter objects, the application should take the filters as parameters again and fetch new metrics data from the server.
My problem is that I don't know how to handle the dependencies between these two sets of objects with asynchronous ajax calls. At it's current state, my application doesn't finish loading the filters when it already starts loading the metrics. Therefore, the filters don't get passed as parameters for the metrics ajax-call.
My question is: What's the best way to do this ember? There surely has to be a way to handle the order of ajax calls. My intuition is that manually adding observers isn't the way to go.
Here are the models of my application:
//FILTER MODELS
var defaultFilters = ['dates', 'devices'];
//set Filter class. The Filter object will be multiplied for each filter.
App.Filter = Ember.Object.extend({
//capitalize first letter to get title
filterTitle: function() {
return this.get('id').charAt(0).toUpperCase() + this.get('id').slice(1);
}.property('id'),
//set attribute to see if filter has loaded
loadedFilter: false,
//create method to load filter values from server
loadValues: function() {
var filter = this;
return Ember.Deferred.promise(function (p) {
if (filter.get('loadedFilter')) {
p.resolve(filter.get('values'));
} else {
p.resolve($.getJSON("http://127.0.0.1:13373/options/" + filter.get('id')).then(function(response) {
var values = Ember.A();
response[filter.get('id')].forEach(function(value) {
values.push(value);
});
filter.setProperties({values: values, loadedFilter: true});
return values;
}))
}})}
}
);
//reopen class to create "all" method which returns all instances of Filter class
App.Filter.reopenClass({
all: function() {
if (this._all) {return this._all; }
var all = Ember.A();
defaultFilters.forEach(function(id) {
all.pushObject(App.Filter.create({id: id}));
});
this._all = all;
return all;
}});
//Create a Filters array to store all the filters.
App.Filters = App.Filter.all();
//METRIC MODELS
App.Metric = Ember.Object.extend({
metricTitle: function() {
return this.get('id').charAt(0).toUpperCase() + this.get('id').slice(1);
}.property('id'),
loadedMetric: false,
filtersBinding: 'App.Filters',
loadValues: function() {
var metric = this;
var filters = metric.get('filters');
if (filters.get('loadedFilters'))
console.log('loading metrics');
return Ember.Deferred.promise(function (p) {
if (metric.get('loadedMetric')) {
p.resolve(metric.get('values'));
} else {
p.resolve(
console.log('sending ajax'),
$.ajax({
url: "http://127.0.0.1:13373/" + metric.get('id') + "/",
data: JSON.stringify(metric.get('filters')),
}).then(function(response) {
var values = Ember.A();
response[metric.get('id')].forEach(function(value) {
values.push(value);
});
metric.setProperties({"values": values, "loadedMetric": true});
return values;
}))
}})}
});
App.Metric.reopenClass({
findByView: function(searchView) {
if (this._metrics) {return this._metrics; }
var metrics = Ember.A();
defaultMetricsSettings.forEach(function(metric) {
if (metric.view == searchView)
metrics.pushObject(App.Metric.create({id: metric.id},{view: metric.view}, {calculation: metric.calculation}, {format: metric.format}, {width: metric.width}));
});
this._metrics = metrics;
return metrics;
}
});
And here are the routes:
App.ApplicationRoute = Ember.Route.extend({
//set application routes model to all filters
model: function() {
return App.Filter.all();
},
//after filter has loaded, let's load its values
afterModel: function(model) {
return model.forEach(function(item) {
item.loadValues();
});
},
//create a controller called ApplicationController and pass the filter as its model
setupController: function(controller, filter) {
controller.set('model', filter);
}
});
App.DashboardRoute = Ember.Route.extend({
model: function() {
return App.Metric.findByView('Dashboard');
},
afterModel: function(model) {
return model.forEach(function(item) {
item.loadValues();
});
},
setupController: function(controller, metric) {
controller.set('model', metric);
}
});
Controllers:
App.ApplicationController = Ember.ArrayController.extend({
//ApplicationController controls all the filters. Let's create a controller to handle each instance of a filter
itemController: 'filter'
});
App.FilterController = Ember.ObjectController.extend({
//this sets the titleId property that is used only for binding html attributes in template. Stupid way to do this.
titleId: function() {
return "#" + this.get('filterTitle');}.property('filterTitle')
});
Upvotes: 2
Views: 590
Reputation: 5075
Your afterModel
hook could do this in a sequence of dependent promises. The current implementation is returning immediately, instead you chain the promise and finally return the last promise as the result of the hook. The router will wait for the whole set of calls to complete before continuing to setupController
.
afterModel: function(model) {
var promise;
model.forEach(function(item)) {
if (promise) {
promise = promise.then(function() {
item.loadValues();
});
} else {
promise = item.loadValues();
}
}
return promise;
}
I'm not sure how many of the calls you have, but you may want to batch some of these together to reduce the number of HTTP requests.
Upvotes: 1