In an app I'm currently developing, I need to load some data before deciding what data to load next and display. My initial approach was to define a route with a dynamic section like this:
this.route( "chart", { path: "/chart/:type" } );
The idea would be to use type to download some metadata and then request the actual chart data based on the metadata. This is a bit awkward in Ember, at least to the extent of my knowledge, as it would require doing an AJAX request in a controller without using ember-data to wrap it.
As the chart will be displayed using the same code independently of the metadata and there are some interface elements that will be rendered based on that metadata, I thought it would be a good idea to use Ember's router to handle the calls doing something like:
this.route( "chart", { path: "/chart/" }, function () {
this.route( "display-chart", { path: "/score/" } );
this.route( "display-chart", { path: "/ranking/" } );
this.route( "display-chart", { path: "/matches/" } );
} );
There is only a limited number of values for the dynamic segment so for each segment I will create a route handled by the same handler. The chart template will load the metadata and render the common UI elements, leaving the actual chart to the display-chart handler.
I have two questions:
Using the first approach, a route with a dynamic segment, is it possible to load the additional data (which depends on first downloading metadata) within the model hook? Something like RSVP but using previously downloaded data to download the next bit.
The second approach, using nested routes, is this the right way to do it? Can you think of any issues with this approach?
You could chain promises in your model hook, i.e.
model() {
return this.get('ajax').request(/*some url*/).then(data => {
/* Now here you can use your requested data
to decide what to load in the store
and then just return that promise back, like this: */
return this.store.findAll('someModel');
/* The result of the model hook
will be the result of the findAll method */
})
}
Related
I am still very new to the world of Ember, and I'm still trying to understand EmberJS and Ember Data (latest version). In my previous (non-Ember) Node app, I included a library that handled all my REST calls to where my data was stored. It set up the connection to the server and handled all the error handling and parsing into a nice and tidy JSON object, and even handled multiple calls to the server in case the response was too big for one call. I can fetch individual records, but if I wanted to fetch a bunch of records, all I had to do was initialize the library object ('myObj') and call myObj.fetchAll(config) to initiate the fetch. Then I just have to wait on several events.
Example
myObj.on('record', function() { // Each record is an event }
myObj.on('error', function () { ...}
myObj.on('end', function () { // After the last record is retrieved }}
I would very much still like to use this library in Ember, but I have no idea how to go about setting it up. I haven't been able to find any examples of creating my own Adapter (is that the right terminology) that would allow me to do this.
Is this something I can do with Ember, or is it not recommended?
I would strongly suggest you use ember-data before attempting something non-standard as you're learning. Virtually all the documentation, and help will specifically be about ember-data. This is a good starting point: http://guides.emberjs.com/v1.13.0/models/
It's perfectly possible to use your own models and use a custom rest interface. You initiate your myObj.fetchAll(config) call on the router. If it's waiting for an event, return a promise and resolve it when the event returns. I don't know anything about your library but it would look something like:
export default Ember.Route.extend({
model() {
return Ember.RSVP.Promise(function(resolve){
var records = [];
myObj.on("record", (record) => {
records.pushObject(record);
});
myObj.on("end", () => {
resolve(records);
});
myObj.fetchAll(ENV.config);
});
}
});
In imperfect contrast, this is how you glue things together from your adapter to your template normally in ember:
Configuring a REST endpoint:
export default DS.RESTAdapter.extend({
host: 'https://api.example.com'
});
Defining a model:
export default Model.extend({
name: attr('string')
});
Fetching data in your route:
export default Ember.Route.extend({
model() {
return this.store.findAll('person');
}
});
Rendering the data:
{{#each model as |person|}}
{{person.name}}
{{/each}}
It's all pretty straight forward if you stick to the default way of doing things.
I'm trying to wrap my head around Ember.js. I'm just writing a simple app that will need to load some data from local storage. When would I do this? Am I supposed to do this within the Route's setupController method? Or, am I supposed to do this in the Controller?
You can create Ember.Object that is handler for saving/reading from local storage(has function read(), save(), some properties etc.) and use it as model in Ember.Controller. Then, you might have actions in this controller that call Model's functions, eg. using:
this.get('model').getLatestData();
Or instead of actions you can use computed properties like this:
directory: function() {
downloadPath = this.get('model.keys').filterBy('Name', 'downloadPath')[0];
if(downloadPath.Value != '' && downloadPath.Value !== undefined)
this.get('model').setValue('downloadPath', downloadPath.Value, db);
this.get('model').getLatestData(db);
return downloadPath.Value;
}.property('model.keys.#each.Value')
You can see more code here on GitHub. I'm using Web Storage, but local storage is simplier.
My application has a nodes pool, and several sub-nodes (queues, services, ...). I need to constantly access the nodes pool, and I need to make sure that the data is up-to-date with the data in the backend. The data in the backend can change due to several reasons, for example:
side effects of working with other objects: the backend will modify not only the affected object, but related objects too. For example, an update of a service document could affect a queue. This is done in the backend, and the ember application is not aware of this.
Maybe another user has modified objects in the backend, and I want to get a fresh copy.
Usually the objects are loaded when accessing the route #/nodes/index, but sometimes I would like to force a refresh of the store, without hoping that the user performs an access to the right route. How can I trigger this programatically?
With ember data you can reload the data using. recordArray.update(), and using window.setInterval to schedule to your desired time. A RecordArray instance is get when the Model.find() is resolved.
Model.find().then(function(recordArray) {
});
The easy way to do this is in the ember way, is returning your data in the model hook, and getting the recordArray instance in afterModel hook. So use this instance to perform an update at some specific time using setInverval, by example:
App.NodesIndexRoute = Ember.Route.extend({
model: function() {
return App.Nodes.find();
},
afterModel: function(recordArray) {
setInterval(function() {
recordArray.update();
}, 1000); //each second
}
});
Just be aware with the transition to nodes index route, because every time will be created a new setInterval, and things will load n times more to each transition to that route.
To fix this, store the id returned of setInterval, and use the clearInterval to remove the previous task:
afterModel: function() {
if (this.get('jobId')) {
clearInterval(jobId);
}
var jobId = setInterval(function() {
record.update()
}, 1000); // each second
this.set('jobId', jobId);
}
I have created a sample here.
I would like to have a route substate not show up in the URL, but still be able to take advantage of having a route class on which I can define renderTemplate, model, setupController, etc. hooks. Is this possible with the v2 router? I am using Ember release candidate 2.
Here's an example.
Suppose I have the routes:
/exercise/:exercise_id
/exercise/:exercise_id/correct
/exercise/:exercise_id/incorrect
I would like all of these to show up in the URL as:
/exercise/:exercise_id
As I don't want the student to just directly type in /correct onto the end of the ULR and get to the correct answer. And although I have a way to prevent that from working, the full route still shows up in the URL. From the student's perspective, I only want them to think about the state as /exercise/:exercise_id.
Of course I could just store the state correct vs. incorrect in some controller variable, but then I loose the convenience of having route classes, ExerciseCorrectRoute and ExerciseIncorrectRoute, which I want to behave differently, and so the hooks, like renderTemplate and setupController, are nice to have defined cleanly in separate places.
Thoughts?
Kevin
UPDATE:
I went with Dan Gebhardt's suggestion because I like to keep things as much as possible within the framework's considered design cases, as this seems to reduce headaches given Ember is still evolving. Also I didn't get a chance to try out inDream's hack.
Although I still think it would be nice if the router added a feature to mask substates from the URL.
Every route must be associated with a URL for Ember's current router.
Instead of using multiple routes, I'd recommend that you use conditionals in your exercise template to call the appropriate {{render}} based on the state of the exercise. In this way you can still maintain separate templates and controllers for each state.
You can reference to my answer in Ember.js - Prevent re-render when switching route.
Reopen the location API you're using and set window.suppressUpdateURL to true if you want to handle the state manually.
Ember.HistoryLocation:
Ember.HistoryLocation.reopen({
onUpdateURL: function(callback) {
var guid = Ember.guidFor(this),
self = this;
Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
if(window.suppressUpdateURL)return;
// Ignore initial page load popstate event in Chrome
if(!popstateFired) {
popstateFired = true;
if (self.getURL() === self._initialUrl) { return; }
}
callback(self.getURL());
});
}
});
Ember.HashLocation:
Ember.HashLocation.reopen({
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
if(window.suppressUpdateURL)return;
Ember.run(function() {
var path = location.hash.substr(1);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(path);
});
});
}
});
Using the latest revision of Ember-Data and the RESTAdapter, is there a way of doing the following?
I have a resource called App and a API that responds to /apps by returning the correct JSON (with { apps: [...] }etc.)
Since this gets served from a static json on our server, it is quiet inappropriate to create server-side resources for every app that can be fetched as /apps/:app_id. Instead, it would be good if the RESTAdapter allways loaded /apps, even if it then only uses one single app out of the fetched ones.
Do I have to write my own Adapter to achieve this? If yes, what would be a good point to "hook into"?
Supposing you have an app model App.App, it should be enough to call App.App.find() when your application loads. This will make the AJAX call to /apps. Even if you don't cache the result in a variable, your data store will be populated with the returned records. Now whenever you call App.App.find(id), Ember Data will check your store and return the record if it has it. If it doesn't have the record, then it will try to call /apps/:id, but this shouldn't happen if your application is designed to use only a static collection.
There are a few different places you could put the App.App.find() call. I would probably put it in App.ready:
App = Ember.Application.create({
ready: function() {
// pre-load apps
App.App.find();
}
});
App.App = DS.Model.extend({
//...
});
It seems a little hacky (and probably is), but it looks like one can achieve this by overwriting the DS.Adapter:find().
In my case, to block calls to /app/:app_id I wrote this:
find: function(store, type, id) {
// Terminate calls for single app
if (type === App.App) {
// instead, load all apps and drop this request
App.App.find();
return;
}
// or continue as usual
this._super(store, type, id);
}
This also works when you have a hierarchy of embedded: 'always' records and Ember thinks it has to load a middle level. Just make sure you load the parent for sure when dropping requests like this!