I am testing my application, so I am doing the following:
I show an index view (#/locators/index), of Locator objects, which I initially load with App.Locator.find();
I modify the backend manually
Manually (with a button/action) I trigger a refresh of the data in the ember frontend, without changing the route. I do this with App.Locator.find().then(function(recordArray) {recordArray.update();});. I see via console logging that a list request is sent to the backend, and that the up-to-date data is received. I assume this is used to update the store.
BUT: The view does not update itself to show this new data
Why does the view not get automatically updated when the store receives new data? Isn't that the whole point of the data binding in Ember?
If I now do the following:
Open any other route
Go back to the locators index route (#/locators/index)
Ember sends a new request to list the locators
The index view is shown, with the correct data (since it was already in the store?)
New data is received
(I am not 100% sure that 4 and 5 happen in that order, but I am quite certain)
So, my impression is that the data is properly updated in the store, but that somehow a full re-rendering of the view is needed to display this new data, for example by leaving and re-entering the route. Is this true? Can I force this re-rendering programmatically?
Ember changes view data when the underlying model is changed by the controller(Which is binded to the view)
(Only when the state of the application changes(url changes) router hooks are called)
Your problem could be solved when you do this.refesh() inside your route by capturing the action triggered by your view.
App.IndexRoute = Ember.Route.extend({
actions: {
dataChanged: function() {
this.refresh();
}
},
//rest of your code goes here
});
for this to work your handlebar template which modifies the data shoud have an action called dataChanged
example :
Assume this action is responsible for changing/modifying/deleting the underlying data
<button {{action 'dataChanged'}}> Change Data </button>
Refresh method actually does a model refresh and passes it to the corresponding controller which indeed changes the view.
There a couple of things that come to mind you could try:
If you are inside of an ArrayController force the content to be replaced with the new data:
this.replaceContent(0, recordArray.get('length'), recordArray);
Or try to call reload on every single record trough looping the recordArray:
App.Locator.find().then(function(recordArray) {
recordArray.forEach(function(index, record) {
record.reload();
}
}
And if the second approach works, you could also override the didLoad hook in your model class without having to loop over them one by one:
App.Locator = DS.Model.extend({
...
didLoad: function(){
this.reload();
}
});
If this works and you need this behaviour in more model classes consider creating a general mixin to use in more model classes:
App.AutoReloadMixin = Ember.Mixin.create({
didLoad: function() {
this._super();
this.reload();
}
});
App.Locator = DS.Model.extend(App.AutoReloadMixin, {
...
});
App.Phone = DS.Model.extend(App.AutoReloadMixin, {
...
});
Update in response to your answer
Handlebars.registerHelper is not binding aware, I'm sure this was causing your binding not to fire. You should have used Handlebars.registerBoundHelper or simply Handlebars.helper which is equivalent:
Handlebars.helper('grayOutIfUndef', function(property, txt_if_not_def) {
...
});
Hope this helps.
Somehow this seems to be due to the fact that I am using custom handlebar helpers, like the following:
Handlebars.registerHelper('grayOutIfUndef', function(property, txt_if_not_def) {
// HANDLEBARS passes a context object in txt_if_not_def if we do not give a default value
if (typeof txt_if_not_def !== 'string') { txt_if_not_def = DEFAULT_UNDEFINED_STR; }
// If property is not defined, we return the grayed out txt_if_not_def
var value = Ember.Handlebars.get(this, property);
if (!value) { value = App.grayOut(txt_if_not_def); }
return new Handlebars.SafeString(value);
});
Which I have been using like this:
{{grayOutIfUndef formattedStartnode}
Now I have moved to a view:
{{view App.NodeIconView nodeIdBinding="outputs.startnode"}}
Which is implemented like this:
App.NodeIconView = Ember.View.extend({
render: function(buffer) {
var nodeId = this.get('nodeId'), node, html;
if (nodeId) {
node = App.getNode(nodeId);
}
if (node) {
html = App.formattedLabel.call(node, true);
} else {
html = App.grayOut(UNDEFINED_NODE_NAME);
}
return buffer.push(html);
}
});
I am not sure why, but it seems the use of the custom handlebars helper breaks the property binding mechanism (maybe my implementation was wrong)
Related
I'm using this.store.push to push a record into the store from with the application controller (this action is being called from a socket service that is initialized in the application controller), using ember 2.2.1 I am achieving this like
var newStoreRecord = this.store.push({
data: {
id: id,
type: 'cart',
attributes: newCartItem
}
});
This adds this new item into the store but the template doesn't update to show the new item, I also tried adding something like this
this.get('cart.model').pushObject(newStoreRecord); assuming that I had something like cart: Ember.inject.controller(), at the top of the controller, might have had that one wrong anyway.
In the cart route I have my model being defined as so
model(params) {
this.set('routeParams',params.event_url);
return Ember.RSVP.hash({
event: null,
items: null
});
},
actions: {
didTransition() {
this.store.findRecord('event',this.get('routeParams')).then((result)=>{
this.controller.set('model.event',result);
});
this.controller.set('noItems',false);
this.store.query('cart',{auction_id:this.get('routeParams'),user:this.get('user.user.user_id'),combine:true}).then((result)=>{
if(!result.get('length')){
this.controller.set('noItems',true);
return null;
}else{
this.controller.set('model.items',result);
}
});
},
}
Not sure if I'm having troubles with getting the template to update because I'm not use the model hook? (btw, we're not using the model hook because of the bad performance on android we'd rather load an empty template with a loader and THEN load data rather than the other way around.
I have several thoughts here:
To answer your question specifically, when you set a variable from the store, like you're doing, it will only reference what was in the store at that time. It will not update automatically.
Your best bet is to add two new computed properties to your controller:
items: Ember.computed(function() {
return this.store.peekAll('cart');
}),
// You'll need to flesh this one out further
filteredItems: Ember.computed('items.#each.auction_id', function() {
return this.get('items').filter(...);
})
Reference filteredItems in your template and it should work.
Sidenote, I'd highly recommend refactoring a couple things.
I would use the setupController hook instead of didTransition. It runs after the model hook is complete so will be similar to what you're looking for
You can access the params at any time in the route, so you don't need to save them in the model hook
You don't need to return an a promise in the model hook if you're not doing any async data. Just return the object. You may need even need to do that.
Hope this helps.
I am just starting with ember and trying to do a simple test.
Which, also very simple, got me stuck for some reason and I cant find the answer anywhere.
So I need load data from the server without transition to another route and do it from within a submit action (or any other action for that matter).
I have a simple input form where I type in manually an object ID and
I want it to be loaded say right underneath. Simple enough. Seams to be a three minutes job in angular. Here, I just cant get the hang of communication between route and controller.
So given this little emblem
form submit="submit"
= input type="text" value=oid
button type="submit" Submit
#display
= person
And this route
import Ember from 'ember';
export default Ember.Route.extend({
model: {
person: null
},
actions: {
submit: function() {
var oid = this.controllerFor('application').get('oid');
var person = this.store.find('person', oid);
this.modelFor('application').set('person', person);
}
}
});
This is as far as I could think. I want to click submit with ID of an object and I want that object loaded and displayed in the div#display.
So what am I doing wrong? What is the right way to do it?
First, I don't even know where to put such an action? Controller or route?
If I put it in controller, I don't know how to refresh the model. If I put it in route, I am stuck with the above. Would be also nice to see how to do it if action was placed in the controller.
For simplicity I just do it all in application route, template, controller ...
Thank you
The best place to put your code is on Controller given it responds to UI, so doing that on your controller the code is much more simple.
On this jsfiddle I have put some dummy code which tries to do something what you want to achieve.
//Index Route
App.IndexRoute = Ember.Route.extend({
model: function () {
return ['red', 'yellow', 'blue'];
}
});
//Here my dummy controller.
App.IndexController = Ember.Controller.extend({
oid: 1,
actions: {
submitAction() {
//Here your logic to find record given the input and attach
//the response to the model object as UI is binding to model
//if you add/remove new records they will show up.
//On this example I have added a new object.
this.get('model').addObject('green');
}
}
})
Enjoy!
For reasons beyond the scope of this question, I have to populate an Ember data model named Activity in my SearchRoute using Ember.$.getJSON in the model hook like this:
App.SearchRoute = Ember.Route.extend({
model: function (params) {
// Create a promise to return to the model hook. The promise will return a DS.RecordArray.
var modelPromise = new Ember.RSVP.Promise(function (resolve, reject) {
// Make the AJAX call to retrieve activities that match the search criteria
Ember.$.getJSON('/services/activities?query=' + params.q).then(function (data) {
data.activities.forEach(function (activity) {
// If the current activity does not already exist in the store...
if (!this.store.hasRecordForId('activity', activity.id)) {
// add the activity to the store
this.store.createRecord('activity', {
id: activity.id,
title: activity.propertyBag.title
});
}
}.bind(this));
resolve(this.store.all('activity', { query: params.q }));
}.bind(this));
}.bind(this));
// Return the DS.RecordArray as the model for the search route
return modelPromise;
}
});
Then, in my SearchController I do some model sorting and filtering before returning the results from a computed property that is bound to a template that displays the results, like this:
App.SearchController = Ember.ArrayController.extend({
filteredActivities: function () {
var model = this.get('model');
// complete various model sorting and filtering operations
return model;
}.property('model')
});
Here's the template:
<script type="text/x-handlebars" data-template-name="activities">
{{#each item in filteredActivities}}
{{item.title}}
{{/each}}
</script>
Every time a search is executed, the model hook in the SearchRoute is refreshed, the AJAX request is made, and the store is updated with new Activity records, if necessary.
The problem is, even if I do create new records in the store using createRecord and return the new store query results to my model hook, the filteredActivities property does not get fired and the template does not update.
I would think that because I'm returning a newly updated DS.RecordArray to the model hook, that Ember would consider my model as having changed and fire any computed properties watching for changes to the model, but I must be missing something.
Does anybody have any ideas?
Sorry for the long post, and thank you so much for taking the time to consider my issue!
Don't use createRecord. Use push.
http://guides.emberjs.com/v1.10.0/models/pushing-records-into-the-store/
Do you try model.[] or model.#each.propertyNameToObserve in computed property filteredActivities?
Examples with #each: http://guides.emberjs.com/v1.10.0/object-model/computed-properties-and-aggregate-data/,
http://guides.emberjs.com/v1.10.0/controllers/representing-multiple-models-with-arraycontroller/
What's the proper way to find item in the Ember.js ArrayController? I have set of contacts in the controller:
App.contactsController = Em.ArrayController.create({
content:[],
});
There are objects in the controller, they are displayed and everything works fine. Then, I want to implement router with serialization/deserialization:
...
deserialize:function (router, params) {
var contact = App.contactsController.find(function(item) {
return item.id == params.contact_id;
});
},
...
However, the find function does not appear to do any iteration. What could be the reason? Is it possible that the Router tries to do the routing before the application calls its ready method? That's the place I fill the controller with data.
EDIT: Well, I have found that router tries to make the transition before I fill my arrayController by the data (in Ember.Application.ready method). Is it possible to "delay" routing after the data is properly set?
var contact = App.contactsController.filter(function(item) {
return item.id == params.contact_id;
});
I think you can run Application.initialize() when router has been set. You can use observer to detect data set.
dataChanged: function() {
console.log(this.get('content.length'));
// before emberjs 1.0pre
// console.log(this.getPath('content.length'));
}.observes('content')
The problem was actually caused by insertind data into arrayController after the Router did its deserialization. Putting it before App.initialize() solved the problem.
the correct answer is:
var contact = App.contactsController.content.find(function(item) {
return item.id == params.contact_id;
});
It will not return an item if you don't point to the content array.
How can you disregard a record that you have changed in an EmberJS view with Ember Data?
Something like delete without actually deleting it from the persistent storage.
I thought App.store.removeFromRecordArrays(record); would work.
You could extend DS.Model with a flag if this case (deleted client-side but do not delete server-side) is present for this model. Additionally a method is convenient to set this status (call model.deleteLocal()).
DS.Model.reopen({
deleteLocalFlag: false,
deleteLocal: function () {
this.set('deleteLocalFlag',true);
this.deleteRecord();
}
});
Then you need to customize the deleteRecords method in your adapter.
DS.YourAdapter.reopen({
deleteRecord: function(store, type, model) {
if (!model.get('deleteLocalFlag') {
// code for deleting in persitence layer
}
store.didDeleteRecord(model, model.toJSON({associations: true}));
}
});
Warning: This code was not tested, but works in my head ;)
May be a cleaner solution would be to use the stateManager of the object and transition into a different state instead of setting the flag. But I find the code around stateManager quite difficult to understand und probably is not worth the hassle.
1) You can use a transaction and rolback the transaction.
2) Or you can just rollback a record by using it's statemanager.
if(record.isDirty)
record.get('transaction').rollback();
you can for instance loop all records in the stores recordCache and rollback al the dirty records.
I personally use a record rollback mechanism on the willDestroyElement event in a view, so if a user leaves the view, he will be asked to save dirty records.
PatientTransport.FirmView = Ember.View.extend({
templateName: 'firm-view',
willDestroyElement: function() {
if (this.getPath('controller.content.isDirty')) {
var self = this;
Bootstrap.ConfirmBox.popup({
heading: "Some data has changed.",
message: "Do you want to save changes?",
callback: function(options, event) {
if (options.primary) {
self.getPath('controller.content.transaction').commit();
} else {
self.getPath('controller.content.transaction').rollback();
}
}
});
}
}
});