Adding params to route model breaks ArrayController - ember.js

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');
}

Related

Show the newly created record on .save()

Ember 2.5
I have a page that displays a list of all types of commutes (/commutetypes). To create a new commute type, I go to commutetypes/new.
Once I save the new commute type and transition back to /commutetypes. The new commute type does not display on the list. Can I refresh the model to display it?
// save action in route.
save: function() {
var _this = this;
var com = this.store.createRecord('commutetype', {
name: document.getElementById('nameInput').value,
description: document.getElementById('descriptionInput').value
});
com.save().then(function(){
_this.transitionTo('commutetypes');
});
}
Added model for commutetypes route.
model: function(params) {
return Ember.RSVP.hash({
commutetypes: this.store.query('commutetype', params)
});
},
Can you try to pass in your model inside transitionTo. Also make sure that you endpoint returns a 201 with model instance
com.save().then(function(data){
_this.transitionTo('commutetypes', data);
}
I would also need to make sure that from server side you get instance of model back to ember.
Can you post your route code commute types...maybe you are not loading data there properly
hope it helps

How to get page to update with new records from server without a route change?

I have an Ember app that uses server-side storage. In the products route, I have a list of products that I display in my product route (which are fetched in the usual way upon entering the route)
{{#each item in sortedProducts}}
{{/each}}
....
fetch
App.ProductRoute = Ember.Route.extend({
model: function(){
return Ember.RSVP.hash({
store: this.store.find('product'),
//other code ommitted
})
}
})
In the controller, I do the sorting
sortProperties: ['date:desc'] //dated added
sortedProducts: Ember.computed.sort('model', 'sortProperties'),
This works, however, I give the user the option to filter the records displayed. Upon clicking a button, an action is called that queries the server for a subset of records (it doesn't just filter the records that are already in the store cache)
actions: {
filterByPriceAndColor: function(){
this.store.find('product', {price: pricevariable, color: colorvariable});
}
}
This queries and returns the desired records, but the list on the page isn't updated i.e. the list on the page still displays all the records that are fetched upon application load.
Question: how do I get the page to update with the new records fetched from the server without a route change, (and will the solution integrate with the computed sort that already exists for ordering the entries by date)
To update your model from an action (or anywhere else) you simple need to set a new value for it and Ember will to the hard work for you.
In your case it should look like this:
actions: {
filterByPriceAndColor: function() {
var promise = this.store.find('product', {price: pricevariable, color: colorvariable});
var self = this;
promise.then(function(data) {
self.set('model', data);
});
}
}
Here is a JSBin demonstrating how it works: http://emberjs.jsbin.com/walunokaro/3/edit

Computed property for the number of records in the store?

This may be abusing Ember, but I want to create a computed property for the number of items in the store.
I'm trying to prototype a UI that exists entirely client-side. I'm using fixture data with the local storage adapter. That way, I can start off with canned data, but I can also add data and have it persist across reloads.
As I'm currently working on the data layer, I've built a settings route that gives me a UI to reset various models. I would like to add a Handlebars expression like {{modelCount}} so I can see how many records there are in the store. That's quicker than using the Ember Data Chrome extension, which resets to the routes tab on every page reload.
The following will show me the number of records once, but does not change when the number of records changes:
modelCount: function() {
var self = this;
this.store.find("my_model").then(function(records) {
self.set("modelCount", records.get("length"));
});
}.property()
I get that the store is supposed to proxy an API in the real world, and find returns a promise, but that's about the limit of my knowledge. I don't know how tell Ember to that I want to know how many records there are, or if this is even a valid question.
Load the result of store.find into an Ember.ArrayController's content and then bind the length of content to modelCount. An example:
App.SomeRoute = Ember.Route.extend({
model: function(){
return this.store.find('my_model');
}
});
App.SomeController = Ember.ArrayController.extend({
modelCount: Ember.computed.alias('content.length')
});
See a working example in http://jsbin.com/iCuzIJE/1/edit.
I found a workable solution by combining the answer from #panagiotis, and a similar question, How to load multiple models sequentially in Ember JS route.
In my router, I sequentially load my models:
model: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.store.find("model1").then(function(model1) {
self.store.find("model2").then(function(model2) {
self.store.find("model3").then(function(model3) {
resolve({
model1: model1,
model2: model2,
model3: model3
});
});
});
});
});
},
Then in my controller, I define simple computed properties:
model1Count: function() {
return this.get("model1.length");
}.property("model1.length"),
...

Ember retrieve last instert id

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.

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}}