How to clone template's model? - ember.js

I am new to Ember and developing simple app that interact with user through form. If the user clicks 'reset' i want to reset the model to initial data.
To achieve this, i am cloning the model and set into the controller as 'oldModel'. If the user clicks reset i want to replace the model with oldModel.
jsbin: http://jsbin.com/EJISAne/673/edit
Please suggest me how can i achieve this by following the best practices.

In your setupController , change this
controller.set('oldModel', Ember.copy(model));
to
controller.set('oldModel', Ember.copy(model,true));
The true option is the key here. It will make a deep clone of the object.
Also there was a typo in your template.
<button action 'reset'>Reset</button>
should be
<button {{action 'reset'}}>Reset</button>
Working jsbin.
EDIT : The earlier jsbin was also throwing the assertion. The assertion was thrown because, Ember.Object doesn't implement Ember.Copyable mixin as told in the exception.
In the method App.parseSource
arr.push(Ember.Object.create(item))
can be changed to just,
arr.push(item)
This won't throw any exception as the check for implementation of copy will be done only for instances of Ember.Object
Update jsbin without exception

Ive implemented my reset like this
My ROUTE
The "routeHasBeenLoaded" property now lets user to change routes, and come back to route without losing any data previously inserted. On the other hand no properties have to be set manually after "save, edit"
e.g this.set('property1', []); after save is no needed. All you do is this.set('routeHasBeenLoaded', null);
import RouteResetter from 'appkit/misc/resetmixin';
export default Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, RouteResetter, {
model : function(){
var self = this;
if(Ember.isNone(self.get('controller.routeHasBeenLoaded'))){
return Ember.RSVP.hash({
property1: this.store.findAll('das/dasdasfa'),
property2: [],
});
} else {
return;
}
}
});
Controller on load
routeHasBeenLoaded : null,
init : function(){
this._super();
this.set('routeHasBeenLoaded', true);
},
RouteResetterMixin
export default Ember.Mixin.create({
theModel: null,
theModelFunction : null,
afterModel : function(model){
this._super();
this.set('theModel', model);
this.set('theModelFunction', this.model.bind(this));
},
actions : {
triggerReset : function(){
var self = this;
this.get('theModelFunction')().then(function(resolvedModel){
self.set('controller.model', resolvedModel);
self.set('controller.routeHasBeenLoaded', true);
});
}
}
});
So im storing my inital model as well as model(); which i get in afterModel hook from parameter (model). And on reset, i reset the model to initial date.
I hope this helps.
Would like to see other solutions on that as well.

Related

How to access model from controller?

I can't seem to get the model from inside the controller, even though the controller seems to have a model property set. The following:
export default Ember.ObjectController.extend({
init: function() {
this._super();
console.log(this.get('model'));
console.log(this.model);
console.log(this);
}
}
prints out:
Any ideas?
So it turns out that when I examine the model by setting a break point it is empty. I assume the console shows content because it updates the model once the content arrives.
In init() the model is unreachable:
init: function() {
this._super();
console.log(this.get('model')); // null
}
Same for any method .on('init'):
onInit: function() {
console.log(this.get('model')); // null
}.on('init'),
But the model is accessible to actions (I'm assuming because the model has been set up by the time the action is called):
someAction: function() {
console.log(this.get('model')); // model object as expected
}
So to answer my question, this.get('model') can be used to access the model from the controller, but just not in init() or .on('init') methods.
The an Ember.ObjectController is a proxy for the model. So the model can be referenced using this as you found in your example. So then in a template {{this.aModelAttr}} or just {{aModelAttr}}. Like you question suggests it's a bit confusing.
The ObjectController is being deprecated as of Ember 1.11.0. So to simplify use Ember.Controller and in your controller you can reference the model by this.get('model')
Then in the template use {{model.aModelAttr}}
To give the model a domain specific ie: books or user name use
export default Ember.Controller.extend({
domainSpecificName: Ember.computed.alias('model')
}
Then in your templates you can use {{domainSpecificName.aModelAttr}}

Is it required to use createRecord before creating a new model instance?

While building my first app with ember and ember-data I've noticed that at one point I started getting the following error when typing in an input field for a newly created model:
Assertion Failed: Cannot delegate set('notes', t) to the 'content' property of object proxy : its 'content' is undefined.
I solved this issue by adding the following code to the route:
App.LessonNewRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('lesson');
}
});
My understanding is that this error started happening when I created (instead of letting ember generate) the LessonController using ObjectController.
I'm now wondering:
Is it required to use createRecord before creating a new model instance?
Is this the best way of preventing this error from happening?
As far as I understand, Your approach is good.
In order to use a model in a view, you have to have the model instance available somehow. So if you try to use content when nothing has been assigned to it, it will fail. I go around that with a different approach, but creating the record within an action handler.
For some scenarios, specially with small models, I generally create corresponding properties in the controller (sort of a viewModel approach) with an action to handle the save. Then I actually create the record in that action, passing the controller properties arguments of createRecord.
Example (totally conceptual):
...
App.Person = DS.Model.extend({
name: DS.attr('string')
});
...
App.PersonAddController = Em.ObjectController.extend({
personName: null,
actions: {
save: function() {
var theName = this.get('personName');
var person = this.store.createRecord('person', {name: theName});
person.save().then(
...pointers to success and failure handlers go here...
).done(
...transition goes here...
);
},
cancel: function {
this.set('personName', null);
}
}
})
Then in the template, I bind the input to the controller prop rather than a model.
{{input type="text" value=controller.personName}}
With this approach, my Route#model doesn't output a blank model to be added to my store, so I don't have to deal with rollback or anything.

How to "fork" a model in Ember Data

I'm not sure if it's the correct way to express my requirement. But the word "fork" appears in the roadmap of Ember Data github page. And it's a killer feature in EPF. I'm wondering if I can do it in Ember Data.
The fork feature is useful when we have an edit page and bind a model. When we edit the information, I don't want the model properties to be changed because if the model properties are also displayed in other place they will be changed automatically. That's not what I want.
An example is a list on the left side of the page and a edit form for a specific model on the right side of the page. When I modify role name in the text field, the role name on the left side is changed because of data binding.
EPF solves this problem by "fork" the existing model and set it in a child session. The session in EPF is similar with store in Ember Data. When you modify the forked model it does not effect the model in the main session. After the forked model is updated it can be merged back to main session, and the corresponding model in main session are updated.
What I can think a solution in Ember Data is to create a different store and copy the model to that store. But it is a bit complicated. Does anyone have a better solution? I searched on stackoverflow and ember discuss forum and didn't find an answer.
I'm not sure if there is a standard or common way to do this in Ember, but I have written a Mixin that I can put on my routes to give some basic 'buffering' of the model:
App.BufferedRouteMixin = Ember.Mixin.create({
setupController: function(controller, model) {
this.setBufferFromModel(controller, model);
this._super(controller, model);
},
setBufferFromModel: function(controller, model) {
var buffer = {};
controller.set('model', model);
model.eachAttribute(function(name, meta) {
buffer[name] = model.get(name);
});
controller.set('buffer', buffer);
},
setModelFromBuffer: function() {
var model = this.get('model'),
buffer = this.get('buffer');
model.eachAttribute(function(name, meta) {
if (buffer[name]) {
model.set(name, buffer[name]);
}
});
}
});
Once this is added to my Edit Route, I can call setModelFromBuffer in my save action. In my templates, I can use the {{#with buffer}} helper.
What I believe to be the simplest solution, is to have an Ember.Object that mimics the structure of your model. When entering an edit mode, copy the properties from the model to the Ember.Object and then have them update there until the user clicks 'Save' or whichever action you wish to merge the changes back in. One thing I did that was important was to add the mixin Ember.Copyable to my object. Below is some code I used to solve this issue for myself.
NOTE:: This code was to prevent a model from being created before it was submitted, so instead of edit, mine is create new.
App.SomeModel = DS.Model.extend({
user: DS.belongsTo('user'),
home: DS.belongsTo('home'),
cost: DS.attr('number'),
title: DS.attr('string'),
description: DS.attr('string'),
category: DS.attr('number'),
categoryName: function () {
return Roomy.enums.TransactionCategories[this.get('category')]
}.property('category'),
date: DS.attr('date', {
defaultValue: function() { return new Date(); }
}),
fuzzyDate: function () {
return moment(this.get('date')).fromNow();
}.property('date'),
split: DS.attr('boolean'),
contributors: DS.attr('array'),
points: DS.attr('number')
});
App.SomeModelNew = Ember.Object.extend(Ember.Copyable, {
user: null,
home: null,
cost: null,
title: null,
description: null,
category: null,
date: new Date(),
split: false,
contributors: [],
points: null,
copy: function () {
return this.getProperties('cost', 'title', 'description', 'category', 'date', 'split', 'contributors', 'user', 'home');
}
});
Then to save this model I did something like this.
NOTE:: The code with User and Home I had to use because of the relationships, simply copying the json form of the User and Home would not persist the relationship and give the model the ID's it needed in the database.
Contoller code below:
//Before this function is called, all the inputs in the form have been filled in and the instance now has values for all the fields that were defined for it
saveTxn: function (txn) {
// txn is the App.SomeModelNew instance
copy = this.store.createRecord('transaction', txn); // this returns the App.SomeModelNew.copy() object
copy.set('user', txn.get('user')); // OVerwrite user for relationship
copy.set('home', txn.get('home')); // Overwrite home for relationship
return copy.save();
}
I hope this helps.

How to load an object from an id in an Ember View?

I'm trying to build a view to display a user card, from their id. So ideally my calling handlebars would look something like:
<p>{{view App.UserThumb authorId}}
{{comment}}</p>
And then in my UserThumb view, I'd like to be able to load a model, in some sort of setup method or model function, sort of how I'm using controllers:
App.UserThumb = Ember.View.extend({
model: function(view, authorId) {
User.find(authorId, function(user) { view.set('content', user); } );
}
}
Can anyone help me understand the 'right' or at least a workable way to do this? Worst case I can go and create the objects first, but I'd like to just keep the id around for a bit first, unless that is just totally at odds with the philosphy of Ember.
This should do
{{#each id in listOfIds}}
{{view App.UserThumb idBinding="id"}}
{{/each}}
App.UserThumb = Ember.View.extend({
didInsertElement: function() {
var authorId = this.get('id');
User.find(authorId, function(user) { view.set('content', user); } );
}
}
Only after the view is inserted the didInsertElement hook gets executed which gets the required user
The model hook you re using in your View is only available inside a Route. The model hook can be used to setup the model for a controller.
See here the docs for more info on that.
In the spirit of DRY (Dont Repeat Yourself) I'd like to link to this great SO answer that will help setup a functional application, the ember way.
Hope it helps

How to rollback relationship changes in EmberData

I have two models with parent-child relationship: training and exercise:
App.Training = DS.Model.extend({
exercises: DS.hasMany('App.Exercise')
})
App.Exercise = DS.Model.extend({
training: DS.belongsTo('App.Training')
})
I want to have a page where a training with all its related exercises is displayed. If the user presses the Edit button, the page becomes editable with the possibility of adding new exercises. I also want to have a Cancel button which discards all the changes made.
Here is my controller:
App.TrainingsShowController = Em.ObjectController.extend({
editing: false,
edit: function() {
this.set('editing', true);
transaction = this.get('store').transaction();
transaction.add(this.get('model'));
this.get('model.exercises').forEach(function(x){
transaction.add(x);
});
},
cancel: function() {
this.set('editing', false);
this.get('model.transaction').rollback();
},
save: function() {
this.set('editing', false);
this.get('model.transaction').commit();
},
addExercise: function() {
this.get('model.exercises').createRecord({});
}
})
There are four event handlers in the controller:
edit: The user pressed the Edit button: a transaction is created, the page is put into "Editing" mode.
cancel: The user pressed the Cancel button: transaction is rolled back and back to "Normal" mode.
save: The user pressed the Save button: transaction is commited and back to "Normal" mode.
addExercise: The user pressed the Add exercise button: a new exercise is created (in the same transaction) and added to the trainings.
The rollback functionality works fine except for newly created records: if I push the Edit button, add a new exercise and push the Cancel button, the newly created exercise stays on the page.
What is the best way to get rid of the discarded child record?
UPDATE:
I've created a jsFiddle to reproduce problem, but it worked. Unlike my application here I used DS.FixtureAdapter: http://jsfiddle.net/tothda/LaXLG/13/
Then I've created an other one using DS.RESTAdapter and the problem showed up: http://jsfiddle.net/tothda/qwZc4/5/
In the fiddle try: Edit, Add new and then Rollback.
I figured it out, that in case of the RESTAdapter when I add a new child record to a hasMany relationship, the parent record won't become dirty. Which seems fine, but when I rollback the transaction, the newly created child record stays in the parent's ManyArray.
I still don't know, what's the best way to handle the situation.
A proper dirty check and rollback for hasMany and belongsTo relationships are sorely lacking in Ember Data. The way it currently behaves is often reported as a bug. This is a big pain point for a lot of developers and there is an ongoing discussion on how to resolve this here:
https://github.com/emberjs/rfcs/pull/21
Until there's a proper solution in place, you can workaround this problem by using the following approach.
First, you'll want to reopen DS.Model and extend it. If you're using globals, you can can just put this (e.g. DS.Model.reopen({})) anywhere, but if you're using Ember CLI, it's best to create an initializer (e.g. ember g initializer model):
import DS from 'ember-data';
export function initialize(/* container, application */) {
DS.Model.reopen({
saveOriginalRelations: function() {
this.originalRelations = {};
this.constructor.eachRelationship(function(key, relationship) {
if (relationship.kind === 'belongsTo')
this.originalRelations[key] = this.get(key);
if (relationship.kind === 'hasMany')
this.originalRelations[key] = this.get(key).toArray();
}, this);
},
onLoad: function() {
this.saveOriginalRelations();
}.on('didLoad', 'didCreate', 'didUpdate'),
onReloading: function() {
if (!this.get('isReloading'))
this.saveOriginalRelations();
}.observes('isReloading'),
rollback: function() {
this._super();
if (!this.originalRelations)
return;
Ember.keys(this.originalRelations).forEach(function(key) {
// careful, as Ember.typeOf for ArrayProxy is 'instance'
if (Ember.isArray(this.get(key))) {
this.get(key).setObjects(this.originalRelations[key]);
this.get(key).filterBy('isDirty').invoke('rollback');
return;
}
if (Ember.typeOf(this.get(key)) === 'instance') {
this.set(key, this.originalRelations[key]);
return;
}
}, this);
},
isDeepDirty: function() {
if (this._super('isDirty'))
return true;
if (!this.originalRelations)
return false;
return Ember.keys(this.originalRelations).any(function(key) {
if (Ember.isArray(this.get(key))) {
if (this.get(key).anyBy('isDirty'))
return true;
if (this.get(key).get('length') !== this.originalRelations[key].length)
return true;
var dirty = false;
this.get(key).forEach(function(item, index) {
if (item.get('id') !== this.originalRelations[key][index].get('id'))
dirty = true;
}, this);
return dirty;
}
return this.get(key).get('isDirty') || this.get(key).get('id') !== this.originalRelations[key].get('id');
}, this);
}
});
};
export default {
name: 'model',
initialize: initialize
};
The code above essentially stores the original relationships on load or update so that it can later be used for rollback and dirty checking.
model.rollback() should now roll back everything, including hasMany and belongsTo relationships. We still haven't fully addressed the 'isDirty' check though. To do that, we need to override isDirty in the concrete implementation of a model. The reason why we need to do it here and we can't do it generically in DS.Model is because DS.Model doesn't know what property changes to watch for. Here's an example using Ember CLI. The same approach would be used with globals, except that you'd assign this class to something like App.Book:
import DS from 'ember-data';
var Book = DS.Model.extend({
publisher: DS.belongsTo('publisher'),
authors: DS.hasMany('author'),
isDirty: function() {
return this.isDeepDirty();
}.property('currentState', 'publisher', 'authors.[]', 'authors.#each.isDirty').readOnly()
});
export default Book;
For the dependent arguments of isDirty, make sure to include all belongsTo relationships and also include 'array.[]' and 'array.#each.isDirty' for every hasMany relationship. Now isDirty should work as expected.
This isn't pretty but you can force it to rollback by manually dirtying the parent record:
parent.send('becomeDirty');
parent.rollback();
parent.get('children.length'); // => 0
#tothda and other readers to follow. As of Ember Data : 1.0.0-beta.10+canary.7db210f29a the parent is still not designed to make parentTraining.isDirty() a value of true when a child is rolled back. Ember Data does consider a parent record to be dirty when an attribute is changed, but not when a DS.hasMany array has changes (this allows save() to work, so you can updated any changes to the parent's attributes on the server).
The way around this for the case mentioned, where you want to do a rollback() on a newly created child, is to replace the .rollback() with a .deleteRecord() on the child record you want to discard. Ember Data then automatically knows to remove it from the DS.hasMany array then, and you can pat yourself on the back for a rollback well done!
Late to the party, but here we go:
I created an addon that resolves this issue.
Just call rollbackRelationships() and it will rollback all your relationships (belongsTo & hasMany). Look at the README for more options.
https://www.npmjs.com/package/ember-rollback-relationships