Ember retrieve last instert id - ember.js

In every example I see, we always use data like if we knew what was the next ID. I'm building a web application that multiple users will be using at the same time. When a user click on the button "Create a category", I create a new record, send it to my API that saves it in the DB. Unfortunately Ember has no idea of my new Category ID. Let's say he made a typo. He click on "Modify". The app has no idea of the ID and therefor, cannot complete the route :product_id/edit.
I see two solution :
One thing I tried, that would potentially fix my problem is returning the ID in a the header. So I would send my data, return Status 201 (Created) if everything went well, then get the content of the Header and assign the created ID. Looks good on paper, but I have no idea how to get the content of my header with Ember.
App.CategoriesAddRoute = Ember.Route.extend({
setupController: function(controller) {
controller.newRecord();
},
actions: {
save: function() {
controller = this.controllerFor("category");
// Here I'd like to use something like .then( getHeaderContent('id') )
// and assign that value to the category
controller.get('content').save()
this.transitionTo('categories.index');
}
},
});
Reload my categories data with a new query. I would like to avoid that but if I have no other choice I'll go for this.

It's standard practice on save of a new model (POST) to return the model back from the server with the new id. After the server returns the model Ember Data will update the model so the model client side should have the id.

Related

Adding params to route model breaks ArrayController

Since adding a param to our route's model, we can no longer add new items in our Documents ArrayController:
Old Route:
model: function(params) {
return this.store.find('document');
}
New Route:
queryParams: {
owner: {
refreshModel: true
}
},
model: function(params) {
var ownerID = this.get('ownerID')
return this.store.find('document', {owner : ownerID});
}
ArrayController:
actions: {
addItem: function() {
this.store.createRecord('document');
}
The code still loads the documents, but when the addItem action is called, the newly created record is not added to the ArrayController's content. In the old code, clicking addItem immediately added a new document to those on screen. I have confirmed that the server is returning an array, so am not sure why new documents can no longer be added. Any help would be greatly appreciated.
Background
The setup is that a user has documents,but can also view documents of other users. This is the reason why we need to dynamically change the ownerID in the url of documents so we can easily display documents belonging to a specific user.
Find by query (which is what find('foo', {bar:'baz'}) is not a live record set. It only shows the records which were returned by the call to the server.
When you create a new record client side, Ember doesn't know whether or not that server side criteria matches. You either need to use filter with server side and client side criteria (which will make a call to the server and also keep filtering all client side records by the client side filter)
this.store.filter('documents', {document : 'asdf'}, function(record){
return record.get('title') == 'So I Married an Axe Murderer';
});
or if you just want it to always show all client side records you can do this
// make the call
model: function(params){
this.store.find('document', {owner : params.ownerID});
return this.store.all('document');
}

Load data from server after event on front end

At the moment, I try to connect my ember.js application with my webserver. The web application has a datepicker. After a date was selected I like my model to "reload". With reload I mean asking my webserver for the new data containing the specific date.
Below you see my route contacting the server for the required information.
App.PicturesRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('http://api.<server>.com/pictures?date=' + params.date).then(function(data) {
return data.pictures.map(function(picture) {
picture.body = picture.content;
return event;
});
});
}
});
In the case that I write the date manually in the string everything works fine and I receive data. Now, I have the problem that I don't figure out how to do it dynamically. How should I create the best connection between UI and model. Of course I can implement an action in my controller but how should this controller call/reload the model?
Since the date is part of your URL you should just use transitionTo or transitionToRoute. You probably have a route set up that allows you to match URLs that look something like /pictures/2013-10-09. Things get a little funky since 2013-10-09 isn't really an object id. Usually with transitionToRoute Ember expects that you'll pass in a live model that represents the content you're transitioning to. This would be the same object that Ember would look up by executing the model hook if the route is hit directly (without link-to or transitionTo). Since the date is really a query param and not an id you can use the setupController method to get around the funkiness.
So, your route might look something like this (this is simplified, you'll want to use the appropriate AJAX calls, of course) :
App.PicturesRoute = Ember.Route.extend({
model : function(params){
console.log('calling model for PicturesRoute');
return { date : params.date }; // return a fake model
},
setupController : function(controller, model){
// Make sure not to call super since we don't want to set
// a single object instead of an array
// this._super(controller,model); <-- do not use!
console.log('calling setupController for PicturesRoute');
// Instead set the `date` property directly
controller.set('date',model.date);
// Then find/build an array and set it as the model
var pictures = [
{name : "Pic 1 - " + model.date},
{name : "Pic 2 - " + model.date}
];
controller.set('model',pictures);
console.log(model);
}
});
Then within the app when you detect a change from the date picker you'd call something like this :
var dateFromPicker = ... // however you get a hold of the date string from the picker
var fakeModel = { date : dateFromPicker };
this.transitionTo('pictures',fakeModel);
Here's a JSBin showing a very simplified version of this idea : http://jsbin.com/ucanam/1396/edit
I hope that makes sense.

Adding New Ember.js Objects/Records when DB Provides ID

I'm experimenting with Ember.js, Node.js and MongoDB. I've based my noodling on the excellent video on the Ember site and Creating a REST API using Node.js, Express, and MongoDB. I've hit a roadblock on the Ember.js side trying to get my create record functionality working.
Currently, when a user creates a record in my sample application, two will show up in the listing. This is happening because when I save my record to the server, a new ID is created for that record by MongoDB. When the response is returned (containing the object with the new ID), the record is duplicated until I refresh the page. One has the new Mongo-supplied ID and the other has a NULL.
Here is where I create the new object:
App.NewwineRoute = Ember.Route.extend({
model: function() {
return App.Wine.createRecord();
}
});
Here is where I store the record to MongoDB:
App.NewwineController = Ember.ObjectController.extend({
doneEditing: function() {
this.get('store').commit();
this.transitionToRoute('wines');
}
});
I'm curious what the best way to handle this is when using ember-data? I've tried all kinds of tricks and worn my connection out searching for examples.
The closest I've been is a nasty hack of setting an id of -1 to the new object/record and then attempting to remove it after the commit. Sadly, the object/record wouldn't really be removed, just show up blank in my list. Plus, I couldn't create any objects/records with an id of -1 from that point on (because one already exists). Seems like a dead-end.
Thanks in advance.
>'.'<
SOLUTION:
I was able to glean the solution to the problem from the following AMAZING examples:
Ember.js CRUD REST
Node.js REST Server for Ember
For others that have had the ID problem, the App.Adapter in the above example handles the mapping from "_id" to "id".
App.Adapter = DS.RESTAdapter.extend({
serializer: DS.RESTSerializer.extend({
primaryKey: function (type){
return '_id';
}
})
});
Inside of the example's Node.js service, the DB calls map "id" to "_id":
collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) {
Thanks again to ddewaele for sending over the example, it was a great tutorial for linking these technologies together.

When to call createRecord and how to handle store transactions

I just started with Ember.js and have a very simple flow I'd like to implement.
On an overview screen I have a new button that allows the user to add a record. Upon saving the user is returned to the overview screen where he sees the newly created record in the overview.
The user navigates to the "new record" page using a linkTo
{{#linkTo locations.new}} Insert location {{/linkTo}}
This triggers the following route where a new model is prepared so that it can be bound to the screen.
App.LocationsNewRoute = Ember.Route.extend({
model: function() {
return App.Location.createRecord();
}
});
The handlebars template for the new location screen looks like this :
<script type="text/x-handlebars" data-template-name="locations/new">
<h1>New location</h1>
Latitude : {{view Ember.TextField valueBinding="latitude"}}
Longitude : {{view Ember.TextField valueBinding="longitude"}}
<p><button {{action addItem this}}>Add record</button></p>
</script>
The addItem is implemented like this:
App.LocationsNewController = Ember.ObjectController.extend({
addItem: function(location) {
this.get("store").commit();
this.get("target").transitionTo("locations");
}
});
A couple of issues I encountered with this :
Model store transactions
As soon as the user navigates (using linkTo) to the new location page an empty record is created (using createRecord). I believe this is required to bind the controls to the model.
I noticed that when the user fills in a latitude, but decides to return to the overview (using a linkTo), the partially filled model is put into the overview.
Refreshing the page removes the partially filled object
I've noticed this a lot while working in Ember.js ... the difference between transitioning to a new route via linkTo and refreshing it in the browser is sometimes huge.
Question : what pattern do I need to use to implement the "Add new record" flow. I simply want to navigate to a new screen and have a save / cancel button. I only want the record added when clicking save. Should createRecord only be called when clicking Save, but then how do I bind the controls ?
Model duplicates
When my REST service returned the following (non ember.js standard) JSON after doing a POST (adding a record):
{
"latitude": "1.123",
"longitude": "1.123",
"accuracy": null,
"_id": "517d2dcf377dcffc11000009"
}
I got the following behavior :
When the user filled in a latitude / longitude and clicks the save button, he returned to the overview to find his newly created record twice in the overview.
After clicking save, an error was shown in the console : Your server returned a hash with the key latitude but you have no mapping for it.
Refreshing the page got rid of the duplicate.
I was able to solve this by returning the ember.js standard JSON after doing a POST
{
"location": {
"latitude": "1.123",
"longitude": "1.123",
"accuracy": null,
"_id": "517d31457b40fcbc2a000003"
}
}
The app is configured to use _id as a primary key as I'm using mongodb.
App.Adapter = DS.RESTAdapter.extend({
serializer: DS.RESTSerializer.extend({
primaryKey: function (type){
return '_id';
}
})
});
I can now understand why Ember.js complained about the latitude key (as it was expecting a location root element), but I don't understand why it added the model twice to the collection (using only 1 POST)
So 2 questions :
What is wrong with my flow and how can I fix it ?
Why did I end up with duplicates when the REST api returned non-ember.js standard JSON.
Model store transactions
Wrap your creation into a new transaction. This won't prevent the record to be visible in the list (you can always filter your list to reject new records). In your router, you can rollback the transaction when exiting the state. Something like:
App.LocationsNewRoute = Ember.Route.extend({
activate: function() {
this.transaction = DS.defaultStore.transaction(),
},
model: function() {
return this.transaction.createRecord(App.Location);
}
});
In your save handler, you want to commit the record's transaction (and not the store's default one).
UPDATE: do not rollback on deactivate
Model duplicates
"Your server returned a hash with the key latitude but you have no mapping for it." Ember data tried to sideload each attribute/key that was returned in your JSON. Ember data expects the returned object to be under a root key in your JSON (as you have noticed).
I don't know why you had duplicates.
UPDATE 2: how to not display newly created records
I said something wrong in my comments, records created within a transaction belong to the array returned by find. I forgot that I have updated my code to do it:
App.LocationsController = Ember.ArrayController.extend({
persisted: function() {
return this.get('arrangedContent').filter( function(contact) {
if( !contact.get('isNew') ) {
return true;
}
});
}.property('arrangedContent.#each.isNew', 'arrangedContent')
})
And then in your template:
{{#each persisted}}
// whatever you need
{{/each}}

ember-data how to reload/sort store records

Is there a way to reload the whole store in ember-data? Or to sort items inside?
It should go like that:
user clicks "add" button
new record is added
store is commited
user is transmited to the state where items are listed
his new entry is there in correct order
To do this I have to either transmit in model.didCreate which shouldn't be (it's not the role of a model!) or refresh store after transmiting.
Anyone had similar issues?
I am using ember-data revision 12
EDIT
I used:
Ember.run.later(this, function(){
this.transitionToRoute('list');
}, 500);
To give store some time. But then again new entry is always at the bottom of the list. I still need to reload whole store or sort it somehow.
EDIT#2
So I did little rework here. In my template I use:
{{#each arrangedContent}}
To use sorted data and in the route I have:
App.AdsRoute = Ember.Route.extend({
model: function(){
return App.Ad.find();
},
sortProperties: ['id']
});
It's not working though. Looks like Route is not ArrayController. And if I make additional AdsController extending from ArrayController it is not working neither.
Ember ArrayControllers have a few options for sorting the content:
http://emberjs.com/api/classes/Ember.ArrayController.html#property_sortAscending
You can set which fields to sort by and which order you want to use. When you use these you will want to bind views to arrangedContent and not just plain old content.
In my applications I typically return to the list state after the record has been persisted:
var observer, _this = this;
observer = function(target, path) {
if (target.get(path) === 'saved') {
target.removeObserver(path, null, observer);
return _this.get('target.router').transitionTo('apps.index');
}
};
I'm not sure if the store has a reload mechanism (individual records do), but I can update this response if I find an answer.