Best way to rollback all changes to an Ember Data model - ember.js

I am using ember-data to edit a user with automatic model resolution
this.route('user', function() {
this.route('edit', { path: ':user_id'})
});
This works fine and the user can modify all aspects of the model DS.attribute, DS.belongsTo and DS.hasMany.
The user can navigate away in a number of ways, in which case all changes should be removed from the model.
Clicking a Cancel button
Clicking the Broswer Back button
Clicking the Save button, the remote request fails, then navigating away from the page.
Just Clicking some other link on the page which takes them somewhere else.
The changes should only be applied if the user explicitly wants them to by clicking the Save button and the server request is successful.
I considered using ember-buffered-proxy but I am not sure how this will cope with DS.belongsTo and DS.hasMany relationships. Regardless, before saving the model I will need to do buffer.applyBufferedChanges(); before saving and if the server fails I am in the save situation as before.
The willTransition hook in the route seems like the obvious place to do this but how can I ensure all changes are removed from the model given rollbackAttributes() only works for DS.attribute controls.

Try utilizing the Ember.Route refresh() method inside the route's willTransition() action hook, like this:
action: {
willTransition() {
const unsavedModel = this.get('unsaved');
if (unsavedModel) {
this.refresh();
}
}
}
The refresh() method can be used for "re-querying the server for the latest information using the same parameters as when the route was first entered."
I've suggested a flag named unsaved, which can default to true, unless it has been set to false at some point during a successful save, prior to transitioning.

Related

How to trigger didReceiveAttrs in Ember component

Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.

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.js route hook to be called on every transition

Is there a route hook in Ember.js that is called on every transition, even if the new route is the same as the old route (for example, clicking a top-level navigation link to the same route).
I tried activate, but it's only being called once, and is not being called again when I use the top-level navigation to go to the same route I'm already in.
Example jsFiddle: When I click "Test" the first time, the activate hook is called, but when I click it a second time, it does not.
You can setup a didTransition in the router, exactly how Ember does it for Google Analytics.
App.Router.reopen({
doSomething: function() {
// do something here
return;
}.on('didTransition')
});
See example here: http://emberjs.com/guides/cookbook/helpers_and_components/adding_google_analytics_tracking/
UPDATE: (new link since old is broken)
https://guides.emberjs.com/v1.10.0/cookbook/helpers_and_components/adding_google_analytics_tracking/
Activate is not being called a second time because This hook is executed when the router enters the route... And when you click on that link-to a second time, the router isn't doing anything... As in, no transition is being made (although it is "attempted").
http://emberjs.com/api/classes/Ember.Route.html#method_activate
The method that I have found to work best is observing the currentPath from within a controller. I use this for animations between routes.
In your application controller you can do something like the following:
currentPathChange: function () {
switch(this.get('currentPath')){
case 'test.index':
this.doSomething();
break;
case 'test.new':
this.doSomethingElse();
break;
}
}.observes('currentPath')
You should be able to access almost any part of your app from the application controller, so it's a nice "root hook," I suppose.
Example: http://jsfiddle.net/mattblancarte/jxWjh/2/
Did you already consider the hook willTransition?
http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
App.SomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
// do your stuff
}
}
});
Alter/Hack EmberJS code and add a jQuery event trigger inside the doTransition() Function. This is Best but kind of defeating the point.
As of today, 1 year later and Ember 2.0 kind of out, there is NO OTHER WAY :(
Ember does not provide a way to track route-change attempts! This includes URLattemts(history), link-to attempts, hash change attempts etc..

Detect model/URL change for Ember Route

My EmberJS application has a ProjectRoute (/project/:project_id) and a corresponding ProjectController. When viewing a particular project, users can edit its properties, and I'd like the project to automatically be saved when the user stops looking at it.
Currently, what I'm doing is something like this:
Application.ProjectRoute = Ember.Route.extend({
...
exit: function() {
this.get('controller').saveProject();
}
...
});
This works when the user simply closest the project view. However, if the user simply switches to viewing a different project (e.g. goes directly from /project/1 to /project/2), the same route is used (it just uses a different model), and exit is not called.
What I need is a way to detect this transition and call the saveProject function before it happens. Any ideas?
I "solved" it by adding the following to my controller:
Application.ProjectController = Ember.ObjectController.extend({
...
saveOnChange: function() {
var previousProject = this.get('target.projectToSave');
if (previousProject && previousProject.get('isDirty')) {
Application.store.commit();
}
this.set('target.projectToSave', this.get('content'));
}.observes('content')
...
});
This seems to work well. Note that I'm storing the projectToSave property in the route (target) as opposed to the controller, because the controller gets wiped every time the model changes. It feels a little weird/hacky to store this in the route, but the only alternative I could think of was to store it in ApplicationController, which seemed overly broad.

Why does router.transitionTo abort when transitioning to the same model twice?

If I get my ember-data model from the store and transition to a route with it
var model = App.Foo.find(1);
router.transitionTo('foo', model);
It transition to the route below and I see the console.log
App.FooRoute = Ember.Route.extend({
redirect: function() {
console.log("redirect ...");
this.transitionTo('bar');
}
});
If I change the model and transition again, it still does the console log and everything works. But if I do a find on the same model 2x in a row, the console log never happens. When I step through ember source (RC3) I can't see why it would abort in this case.
Why does the transition get aborted in ember when I do this?
I'm not sure what you're asking -- can you provide the exact code for what you mean by "change the model and transition again" and "do a find on the same model 2x in a row"?
Besides that... a route isn't 're-entered' if you're transitionToing an already active route, although I'm not sure if that's even relevant here -- could you please clarify what you're doing?
My workaround is to call a store method from the route transition button, which then emits to my desired component. A little "hacky" for my taste, but what works works.