Rendering a template and invoking model hook in Ember.js - ember.js

I am using JSON requests to populate the model for a template called example.
App.ExampleRoute = Ember.Route.extend({
model: function() {
var url = "example/GetExamples";
return Ember.$.getJSON(url).then(function(data) {
return data;
});
}
});
The example template perfectly renders this data. Now my backend might change from time to time as I have another template called addExample. It just sends a JSON request to the server to add an entry in the database from this other template. After adding the examples, I try to transition to example template but I see old data only. On refreshing the page, the new data with the added entry comes up.
App.AddController=Ember.Controller.extend({
postAddRequest: function() {
var request = $.post("/example/AddExample", {params});
request.then(this.success.bind(this), this.failure.bind(this));
},
success: function(data) {
this.transitionToRoute('example');
}
});
So is there any way I can force Ember to re render 'example' template and invoke its model hook in the route without refreshing the page. I can't use ember-data for persistence as it is still in beta stage.

Transition to the route and send the data you'd like to use on the route. Additionally the route should be using an id/slug in the url. If not see below
this.transitionToRoute('example', data);
If you are already on the route and you want to update the model of a currently set controller you can grab that controller and set the model.
From the controller
App.SomeController = Em.Controller.extend({
needs: ['example']
someFunction: function(){
var model = ... get new model;
this.get('controllers.example').set('model', model);
}
});

Related

How do I reload data and update a bound element after a user clicks a button?

Why is it that when I click 'Random', the information in the template isn't reset and the data isn't update?
I have data that I want to display after a REST endpoint is successfully reached. The REST data that's returned is a random database record, so I don't need to worry about randomizing my request or anything. I only need to reach the server via that URL. In this case, the URL is: localhost:8000/api/verses/0
My handlebars template looks like this:
app/templates/verses.hbs
<div id="panel">
<h3>{{model.reference_number}}
<h3>{{model.body}}</h3>
<button {{action "getAnotherVerse"}}>Random</button>
</div>
{{outlet}}
So, when the 'Random' button is clicked, the following should be invoked:
app/controllers/verses.js
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.ObjectController.extend({
actions: {
getAnotherVerse: function() {
this.get('model').reload();
// This is where the text should be reset to the new data.
}
}
});
app/routers/verses.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('verse', '0');
}
});
When you fire getAnotherVerse you just take the current record(model) and simply reload it to fetch its latest data. I guess you want to call model method of your route once again, so model will be reset and you'll get brand new record from your server.
Move getAnotherVerse to your VersesRoute where you specify model for VersesController and try following code:
# app/routes/verses.js
model: function() {
return this.store.find('verse', '0');
},
actions: {
getAnotherVerse: function() {
this.refresh(); # beforeModel, model, afterModel, setupController will re-fire
}
}
If this still doesn't work, please try this:
# app/routes/verses.js
model: function() {
return this.store.fetch('verse', '0');
},
actions: {
getAnotherVerse: function() {
this.store.unloadAll('verse'); # I assume `verse` is your Model name
this.refresh(); # beforeModel, model, afterModel, setupController will re-fire
}
}
Your telling Ember Data to find the record with id = 0. Just guessing that your API endpoint is treating 0 as a special case and returning a record that does have an actual id.
Because Ember Data is using an identity map under the hood I'm guessing that when you call reload the data is creating a new record in the store. And therefore isn't triggering updates on the record that is being used for the model.
A better approach would be to just use
var that = this;
Ember.$.get('localhost:8000/api/verses/0')
.then(function(data) {
that.set('model', data);
});
You could push the data into the store too http://emberjs.com/guides/models/pushing-records-into-the-store/ and then it would be available if you need to find it by id later.
Another approach would be to create a custom adapter / serializer that could hide some of this, really depends on how your using ember data outside of this use case.

Ember-Data parent template not updating when model is changed

I am using EAK with a simple nested routing structure, but changing the parent model in the child controller does not change the top-level view. For example if I have the following router.js file:
this.resource('similar', function() {
this.resource('list', { path: '/list/:phone_id' });
this.resource('upload');
this.resource('new');
});
For the 'similar' route model I am using ember-data together with the RESTAdapter which is backed Flask.
export default Ember.Route.extend({
model: function() {
return this.store.find('phone');
}
});
If I manipulate the model inside the 'upload' controller then the changes are not reflected in the template, e.g.
var record = this.store.createRecord('phone', {
numbers: [1,2,3,4]
});
record.save();
will not change "{{#each list in model}} {{list.numbers}} {{/each}}".
If I reload the page it works fine. What am I doing wrong?
Instead of store.find which hits the server and stores that exact list, try using store.filter. store.filter "remains up to date as new records are loaded into the store or created locally"
http://emberjs.com/api/data/classes/DS.Store.html#method_filter
The problem here was that the REST endpoint did not return the new record (with the id attribute set).

Find query concept for route and controller

my question is a little bit general. What is the best concept for route and controller with findQuery in ember.
I have api with data filtering. Data request is executed by
this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": filters})});
after that I show them in table view. The filter is updated by form views in a template.
My current solution:
Form views set controller parameters and a button call action from controller. Controller action loads parameter, executes findQuery and set('content',data).
In most cases I saw concept with a defining model: function() .. in the Route and setupController: function(controller, model) with controller.set('content',model). I like this "set" because 'content' is RecordArray (not PromiseArray) and I can easily use that for datatables and another JavaScript plugins. I think my solution isn't good.
I think your concept is correct, I have been using the following flow:
In your router:
App.Router.map(function() {
this.resource('search', { path: '/query/:filters' });
});
App.SearchRoute = Ember.Route.extend({
model: function(params) {
return this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": params.filters})});
});
In your html, just bind the action which will lead to the new Search Route,
something like below :
<button {{action "doSearch"}}>Search</button>
In your controller:
App.SearchController = Ember.ArrayController.extend({
...
actions: {
doSearch: function() {
var query = buildYourQueryObject();
this.transitionToRoute("search", query);
}
}
Upon clicking on the button, the app will transition into your search route, and "query" will be serialized and sent into the Route, and the Route.model() will attempt to be populated based on the serialized parameters provided.
Note: The code has been simplified, you might need to add more stuff in order to make it work

Index view not refreshing after receiving updated data from backend

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)

How to use "needs" with nested routes / controllers in emberjs RC 2

I have a very basic route setup that allows me to first show "all" records for some object. Then if the user selects a dropdown they can filter this down using a date.
I recently upgraded to RC2 and realized that "needs" has replaced or will soon replace controllerFor.
I'm curious how I can use "needs" in the below situation where I need the nested / inner route for "records.date" to change the content for the parent "records" route when a date is selected.
What is missing from below is that inside the App.RecordsDateRoute I need to change the content of the "records" controller to be a new filter (by date this time) and everything I seem to do just dumps the handlebars template and show nothing -even when I try to use something simple like
this.controllerFor("records").set('content', App.Record.find(new Date(model.loaded)))
from within the setupController method of the RecordsDateRoute
App.Router.map(function(match) {
return this.resource("records", { path: "/" }, function() {
return this.route("date", { path: "/:date_loaded" });
});
});
App.RecordsController = Ember.ArrayController.extend({
selected: 0,
dates: Ember.computed(function() {
return App.Date.find();
}).property()
});
App.RecordsIndexRoute = Ember.Route.extend({
model: function() {
this.controllerFor("records").set("selected", 0);
return App.Record.find();
}
});
App.RecordsDateRoute = Ember.Route.extend({
model: function(params) {
//the controllerFor below seems to be working great ... but what about needs?
this.controllerFor("records").set("selected", params.date_loaded);
return App.Date.create({ loaded: params.date_loaded });
}
});
With rc2, instances of other controllers can be retrieved via "controllers.controllerName", in you case it would be this.get('controllers.records').
The "needs" declaration makes the referencing controller sort of import the reference to the other controller; in your case, the date controller would be:
App.RecordsDateRoute = Ember.Route.extend({
needs: ['records'],
model: function(params) {
this.get("controllers.records").set("selected", params.date_loaded);
return App.Date.create({ loaded: params.date_loaded });
}
});
Regarding App.Record.find(new Date(model.loaded)), find() expects an id or an object whose keys and values will be used to filter the collection of models, but you're giving it a Javascript date.
Did you mean App.Record.find(new App.Date(model.loaded)), or maybe something like App.Record.find({ loaded: model.loaded }) /* assuming it's already a Date */?
There is also an initController(controller, model) method in the route called , maybe you could use that instead of "overloading" the model() method with too many responsibilities. http://emberjs.com/api/classes/Ember.Route.html#method_setupController
I recently upgraded to RC2 and realized that "needs" has replaced or will soon replace controllerFor.
To access another controller from route hooks you should continue to use controllerFor. Controller.needs is for communication between controllers, it replaces the now deprecated use of controllerFor method on controllers. AFAIK there is no plan to deprecate controllerFor on ember Routes.
I'm curious how I can use "needs" in the below situation where I need the nested / inner route for "records.date" to change the content for the parent "records" route when a date is selected.
For this use case it would be best to stick with controllerFor. It is possible to use needs this way, by specifying that App.RecordsDateController needs = ['records'] you could access the records controller via controller.get('controllers.records') from within your route's setupController hook.
What is missing from below is that inside the App.RecordsDateRoute I need to change the content of the "records" controller to be a new filter (by date this time) and everything I seem to do just dumps the handlebars template and show nothing -even when I try to use something simple like this.controllerFor("records").set('content', App.Record.find(new Date(model.loaded))) from within the setupController method of the RecordsDateRoute
App.RecordsDateRoute = Ember.Route.extend({
model: function(params) {
return App.Date.create({ loaded: params.date_loaded });
},
setupController: function(controller, model) {
var recordsController = this.controllerFor("records");
// Moved this from model hook, since here you are 'setting up a controller'
recordsController.set("selected", model.date_loaded);
// Set query based on current route's model
var query = { loaded: model.loaded };
recordsController.set("content", App.Record.find(query));
}
});