Force reload of index - ember.js

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.

Related

Ember2.8 : Refresh absolutely everything on transitionToRoute('/route')

So I currently have a card game that when finished loads up a modal with a button to transition back to the home route. This is the action that is called when the modal's close button is clicked.
goBackHome() {
this.transitionToRoute('/games');
},
It does redirect to the requested route, but all the changes to back-end data I did still remain. I've also tried passing it the model itself
goBackHome() {
this.transitionToRoute('games');
},
Basically, it doesn't do a hard refresh of everything. What I would like is when transitionToRoute is called to reload the route as if I was inputting the exact URL into the browser myself.
Well I try to write possible way to do this, hope you can pick one and solve your problem.
you need to add this.refresh(); to either your method or in model() to force reload the model to get new data (depends on what you need)! if you want to reload the location the whole windows I mean just add location.reload();
one example should be
model(id){
var post = this.get('store').find('post', id); // Find the post from the store
post.reload(); // Force a reload
return post; // Return the fetched post NOW and the information will be updated.
}
Moreover, you can do something else in your model() look at the example :
model(params) {
return this.store.findRecord('whatever', params.id, { reload: true });
}
this { reload: true } enforces the model to get fresh data, it can be implemented with findAll as well.
ref: At the moment I'm just using the afterModel hook to force a reload, but it would be good to get the reload flag working again.
so, you can do also another way which is you can send a param to the function and get that in route and in route actions you can have this.referesh()! to reload the model. In this case, you need to add this.sendAction('actionName'); and in route actionName(){this.referesh();}

Ember DS.Store.findAll() returns empty collection

Hey I need to modify some records which I get from the DataStore. If I add the following code in my router I can see that the requests get passed to my template, but I can't modify each request of the collection because the collection is empty.
model() {
return this.store.findAll('user').then(function(users) {
console.log(users.get('length')); // 0
return users;
});
}
I thought that the promise gets resolved when all the records have been fetched from the server but this doesn't seem to be the case. Or did I completely miss something.
I also tried to modify the model in the afterModel callback with the same result.
I'm using Ember 1.13.0 (with Ember-CLI), Ember-Data 1.13.4 and ember-cli-mirage for Mocking my HTTP Requests.
UPDATE:
I managed to create a workaround for this issue. In my controller, I created a new property which listens for model.#each and then I was able to modify model and pass it to the view.
export default Ember.Controller.extend({
users: function() {
return this.get('model.users').filter(function(user) {
// The Promise is resolved twice
// The first time with an empty model and the second time with
// the actual data. So I filter the empty model.
return user.get('id');
}).map(function(user) {
// do fancy stuff with our user
return user
});
}.property('model.#each')
});
Ember Data 1.13
So after spending some time on this topic i found the solution to this issue. It's basically the way how ember works. So under the hood findAll is returning two promises.
findAll without data in the store
find records from the store (resolve first promise -> length 0,
because no data is in the store)
fetch new data in the background (resolves second promise)
findAll with data in the store
find records from the store (resolve first promise with cached data)
fetch new data in the background (resolves second promise with new
data)
If you want to wait for all the data to be loaded you can use query which is returning only one promise.
model() {
return this.store.query('user', {});
}
For findRecord I found the following workaround, which is only working if your backend supports any kind of filtering on the id of your record.
model() {
return this.store.query('user', {
'filter[id]': 1
}).then((users) => {
return users.objectAt(0);
});
}
You can have a look on the following discussion on github
Ember Data 2.0
On Ember Data 2.0 this issue is resolved.
First you should make sure the data is coming in from Mirage as you expect. Open your Ember inspector and verify the models made it into your store. If not, you likely have a problem with the format of the JSON response from your mock route.
To diagnose, check out your console for a log of the JSON response, and ensure it matches what you expect. If you have a custom route handler in your /mirage/config.js for this route, you could also put a debugger statement in there and verify the data is what you think it should be.
If you're using default Ember Data 1.13, it probably means you're using the JSON API serializer/adapter. Is this what you intend? What is the backend for this app ultimately going to look like? If it's going to be JSON API, you'll need to do a bit more work in the Mirage config.js file for now, something like
this.get('/contacts', function(db, request) {
return {
data: db.contacts.map(attrs => {
type: 'contacts',
id: attrs.id,
attributes: attrs
})
};
});
I had a similar problem to the one you are describing when using Ember and Ember data version 1.13. At the time, I was reading the updated documentation of Ember 2.0 without Ember Data 2.0. Once I upgraded both libraries I was able to get the behavior you are trying to achieve with the first code snippet. Namely, the promise is handled correctly with nonzero records with ember data 2.0.

Using ember-data only for fetching data (not for committing). How not to mark records as 'uncommitted' when changing attributes?

I'm using ember-data for fetching data from API (only using find methods). The data is periodically refreshed from the server. Commit operations are handled outside of ember-data.
However, when changing an attribute the record is marked as uncommitted and fails to refresh in the next find request, since it needs to be in the loaded.saved state.
Is there any way to force a record to remain in the loaded.saved whenever an attribute is changed?
I have done something vaguely similar to this. Try this out. Untested code!
Override the init method on your DS.Model and add an observer to each attribute on your DS.Model. The observer delegates to a callback which forces your model state back 'loaded.saved'
App.YourModel = DS.Model.extend({
init: function(){
this._super();
this.eachAttribute(function(attributeName){
this.addObserver(attributeName, this, this.revertToLoadedSavedState);
}, this);
},
revertToLoadedSavedState: function() {
//hackity hack
this.get('stateManager').transitionTo('loaded.saved');
}
}
If that works your models will continually be in the 'loaded.saved' state. However, the number of observers is going to increase out of control as you load entity instances.
Maybe you could just iterate through your dirty models prior to doing a refresh from the server and use the stateManager to force each dirty instance to 'loaded.saved' state.

Load single objects from "index" allways

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!

Ember.js 1.0-pre4 + jQ UI sortable + localstorage adapter

Day 2 learning ember.js...
I'm working on a offline app that needs to save draggable/sortable tile positions to localstorage, and if there is no existing data, load & save from a fixture.
Using: ember 1.0.0-pre4, ember-data rev11, ember-localstorage-adapter, jQ 1.9, jQ UI 1.9
https://github.com/rpflorence/ember-localstorage-adapter
It's working, but I'm a bit of a novice, feel it's not pretty and could use some community advice.
http://jsfiddle.net/Nsbcu/4/
Questions
What is the proper way to check if your DS.Store has loaded and is empty? My method of looking directly at localstorage didn't feel right.
After I createRecords from the App.Tile.DEFAULTS I feel I should commit them, but an error is thrown. I don't have to commit the known defaults, but curious what causes the error and how I should go about committing properly. Also is the App.ready() callback the right place for loading defaults? Error only happens when localstorage is empty
Uncaught Error: Attempted to handle event loadedData on <App.Tile:ember231:1> while in state rootState.loaded.created.inFlight. Called with undefined
On the TilesController I'm using sortProperties which works great until jQ UI Sortable changes the DOM and Ember wants to update my tile order, before I get a chance to set the new order. My current solution is to turn off sortProperties temporarily while updating the model. Again this feels hacky, suggestions on proper way to do this?
=== Edit Feb 3 ===
If I do an async commit the initial error in question #2 is avoided.
App.TilesRoute = Ember.Route.extend({
model: function() {
return App.Tile.find();
},
setupController: function(controller) {
if (localStorage.getItem('fusion-emberjs') == null) {
App.Tile.DEFAULTS.forEach(function(item) {
App.Tile.createRecord(item);
});
// Commit async, else generates error
var _this = this;
setTimeout(function() {
_this.store.commit();
}, 1);
}
}
});
I would put any initial code inside the application or the index Route within the setupController method
if (localStorage.getItem('fusion-emberjs') == null) {
App.Tile.DEFAULTS.forEach(function(item) {
App.Tile.createRecord(item);
});
//*** WARNING: Generates Error ***/
App.Tile.find().get('store').commit();
}
Once you move the code inside the route, replace App.Tile.find().get('store').commit(); by App.store.commit() inside your route
Create your own transaction instead of using the default one, each time you make a call to the store directly you're using the default transaction. You can create a transaction this way
var transaction = App.store.transaction()
transaction.createRecord(App.Foo);
transaction.commit()
transaction.rollback();
Any call to App.store assumes you already created a store, right now you're only extending the DS.Store. Try instead
App.Store = DS.Store.create({
revision: 11,
adapter: 'App.LSAdapter'
});
I would suggest that you do any event handling or transaction management in the router unless it's purely for styling or animation. In that case, the view is the right place for it. I like the router to orchestrate communication between all the assets (controllers, routes, models, views)
A good pattern to remember is a view talks only to a controller, a controller is a mere proxy to a model, a router orchestrates communication between controllers and manages routes.