I have a page where the model is initially loading, but on subsequent link-to's to the same route, it's not updating correctly. I'm using the Ember data store.
For the sake of example, my page is a list of foods. I have a navigation with fruits, vegetables, meats, etc. A user clicks on this and a link-to sends them to foods.food route.
The model is
model(params) {
return RSVP.hash({
foodName: params.foodName,
foodItems: this.store.findAll('food-items', {adapterOptions: {foodName: params.foodName}} )
});
}
In my afterModel( food, transition )
food.foodItemsArr = food.foodItems.filter((foodKey) => {
return (foodKey.get('foodName') === food.foodName);
});
Then in my hbs template
{{#each model.foodItemsArr as |key|}}
...
{{/each}}
When a user initially clicks (fruit) on the list of food types, it loads up fruits as it should. When they then click on a different type (vegetable) after that, the food.foodItemsArr is giving a length of 0. Then if they click to a third food (meat) and then back to vegetable, the vegetables show up. It's almost as if the data store isnt getting updated fast enough by the model before afterModel fires. I've checked and the data is being filtered and returned properly by the server on each call. Help!
Credit to #torazaburo for pointing me in the right direction.
I needed to add { reload: true } to
foodItems:
this.store.findAll('food-items', {adapterOptions: {foodName: params.foodName}, reload: true} )
to force the model to reload.
Related
How can I change the data of the model on a route as a reaction on a user interaction like pushing the button?
I want to have a productsearch-route where the model holds the items. This set on items can change when the user clicks sets criteria and clicks search. I have a component which sends an action every time the user fills a form and presses the button "search". The route handles the action in
//rounte.js
action: {
searchClicked: {
var newdata = this.get('store').query('item', {...});
this.set('model', newdata); //<<<< this is not working!!
}
}
If im doing so, exceptions will approaching. Also im afraid that this can't work even if if there are no exceptions because the template wont update itself I guess.
However:
How can I set(/completely exchange) the model from actions in a route?
I could think of few ways:
1) use query params and force route to refresh the model when criteria changes. More can be found here : https://guides.emberjs.com/v2.3.0/routing/query-params/, specially Opting into a full transition. But this might not suite your need if you can have multiple criterion.
2) Set model to the controller not the route.
actions: {
searchClicked() {
let promise = this.get('store').query('item', {...});
promise.then(newData => {
this.controller.set('model', newdata);
});
}
}
but with controllers going away in future iteration of ember, I don't know if this is best solution either.
I'm making a simple web chat system with Ember.
I have a route /chatrooms that lists a few chatrooms, and then I also have /chatrooms/:chatroom_id that should show the actual chatroom with messages.
The second route is within the first one, like this:
this.resource('chatrooms', function() {
this.route('show', {
path: ':chatroom_id'
});
});
When I access /chatrooms, a call is made to the server (/api/chatrooms) is a list of rooms is returned and displayed, like expected.
When I click a room, the application transitions to /chatrooms/id, but no call is made to retrieve the messages (available at /api/chatrooms/id), even when I try to define a model.
I have a similar scenario with the users. A list of users is retrieved, then displayed. When a name is clicked, the profile is shown. No second call is made, but that's okay since Ember knows everything about the user already.
In my current case, when a list is first returned, it includes all the information except the messages. I believe that would be too much otherwise (10 chatrooms * 100 last messages = 1000 elements in my JSON for each request). So I want to call the server for messages only when a chatroom is selected.
Do you know how to do it, or maybe there's something wrong I'm doing in the first place?
Updates
Template code from app/templates/chatrooms.hbs
<h1>Chatrooms</h1>
<ul class="sub-menu nobullet flex mas">
{{#each chatroom in model}}
<li class="mrs">{{link-to chatroom.name "chatrooms.show" chatroom class="pat"}}</li>
{{/each}}
</ul>
{{outlet}}
In this case, model is an array of chatrooms.
My routes:
app/routes/chatrooms.js
export default Ember.Route.extend({
model: function() {
return this.store.find('chatroom');
}
});
app/routes/chatrooms/show.js
export default Ember.Route.extend({
model: function(params) {
return this.store.get('chatroom', params.chatroom_id);
},
actions: {
send: function() {
...
}
}
});
As discussed in this thread, when you link-to a route and the model is already loaded, the model hook of the route is not fired because there’s no need to reload the data.
If you transition to a route and all the context objects -the objects which will serve as models to templates- are passed in, the beforeModel and model hooks will not be called.
Later in the thread balint corrects:
In fact, the beforeModel hook still gets called in that case, it is only the model hook that does not.
If you want to force the model to be reloaded, you can change your link to use the ID instead of the model:
{{link-to chatroom.name "chatrooms.show" chatroom.id class="pat"}}
You could also load the data in the beforeModel or afterModel hooks, or setupController.
Also, in the chatrooms/show route, you are getting the already-loaded model from the Ember Data store rather than loading it from the server. Try this:
return this.store.find('chatroom', params.chatroom_id);
I ended up adding a links property to the JSON response for chatrooms. When the content of a chatroom has to be displayed, the link is used and the messages retrieved. It only requires two requests, and there's not need to preload all the messages from all the chatrooms and no need to make a request for each message.
I have an ember app with a data model and the usual CRUD views/routes/controllers for listing and adding new items.
When I click 'add new' from the index page, I get the expected form, with 'Save' and 'Cancel' buttons. If I press cancel, the view goes back to the index view and the model is not saved, as expected.
This is the code of the new items controller:
App.ItemsNewController = Ember.ObjectController.extend({
actions: {
saveChanges: function() {
this.model.save();
this.transitionTo('items');
},
cancel: function() {
this.model.rollback(); // how to run this when the back button is pressed?
this.transitionTo('items');
},
},
});
But if I press the back button, I also end up in the index page, but an extra empty row appears in the index listing, corresponding to the new record that was not saved in the 'new' view. This record was not persisted to the data layer, but it somehow still exists in memory, and this.store.find('items') returns a collection that includes it. If I refresh the page, the blank row disappears. Also, as stated above, if I exit the form using the cancel button, the record is discarded, because in the 'cancel' action method I invoke this.model.rollback().
So bottomline, I guess I would have to rollback the model when the back button is pressed, but there's no route callback that I know of to hook some code to this event.
In case it's important, I'm using Ember 1.9.1, Ember Data 1.0.0-beta.15-canary, Handlebars 2.0.0, jQuery 1.10.2
Well, this is the second time in a row that I post to stackoverflow and I find the answer shortly after, so I guess I should refrain from asking here unless I'm literally clueless and hopeless ;)
Thanks to this other question, I found out that there is an exit event on the route. So the key to solve this is on the route, and not in the controller. I ended up putting the model rollback in the route's exit event method, as shown below:
UPDATE: #NicholasJohn16 points out that it is better to use the deactivate hook, instead of exit. See the details in the comments below. The code example has been changed accordingly.
App.ItemsNewRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('item', {
title: 'Untitled',
status: 'new',
});
},
deactivate: function() {
model = this.controllerFor('notifications.new').get('model');
model.rollback();
},
});
Once this is done, you can even remove the model.rollback() call from the cancel action in the controller, and leave just the transitionTo('items') call. Since the route's deactivate event will always be called, regardless of the way you leave the view, the model will always be discarded.
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}}
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.