I have an ember data app and I'm using createRecord to instantiate a model on a new records page. The problem is that this instantly creates the record in the store. So if someone navigates away from the the new record page the object is already persisted. There used to be a createModel method but it seems to have been removed. How is this handled now?
You can check if Model.isNew so you can see has it been persisted. For example, you can do following in Handlebars to display list of records from database and hide new non-persisted models when you, for example, navigate backwards from model/add route:
{{#each item in model}}
{{#unless item.isNew}}
{{item.name}}
{{/unless}}
{{/each}}
According to Ember API docs, DS.Store.createRecord method:
Creates a new record in the current store.
If you don't want to check if record isNew. You can have some properties for user input and call createRecord only if you are sure it can, and will, be persisted.
Alternatively, instead of checking for Model.isNew you can remove the record from the store once the user leaves the route using Route.resetController and Store.unloadRecord.
Route.resetController is meant for all controller clean up you have to do once the model changes or the user transitions away. IMHO this includes removing unsaved models from the store.
PostsNewRoute = Ember.Route.extend
model: (params) ->
#store.createRecord 'post'
resetController: (controller, isExiting, transition) ->
#store.unloadRecord controller.get('model') if isExiting
See http://emberjs.com/api/classes/Ember.Route.html#method_resetController and http://emberjs.com/api/data/classes/DS.Store.html#method_unloadRecord
Related
I have an app model and apps have an id and name.
this.resource("apps", function() {
this.route('show', { path: ':app_id' });
});
I'd like to make the show view show metrics about the app, but the query is pretty intense, so I don't want to include it in the call to the index view (let's say this is a table).
I'm not sure if this is possible with ember-data because the app would already be in the store with the simplified payload and not be re-requested for the show view to get the metrics.
Then my head went to making metrics a completely different model accessible from apps/1/metrics and then making it another model and everything.
But if I sideload the data, i have to provide ID references to the metrics for a particular app. And it's hasOne so there's not really IDs as there would be for a database backed model.
What's the best way to load in additional data about a model or expand the information supplied in the show view?
The backend is Rails and this is an ember-cli project.
The easiest way is to retrieve the data in the Route's afterModel handler:
var ShowRoute = Ember.Route.extend({
model: function(params) {
// Load the model and return it.
// This will only fire if the model isn't passed.
},
afterModel: function(model, transition) {
// Load the rest of the data based on the model and return it.
// This fires every time the route re-loads (wether passed in or via model method).
}
});
I'm learning Ember.js using a Ruby on Rails API server. I've got the routes, template, and model all setup and working - but the template is never re-rendered once the data has been loaded from the API server. I'm not getting any error messages, and I know the customer is being loaded from looking at the Ember inspector.
Customer list is supposed to be displayed after start
Customer list is being loaded correctly from the API server:
Router
// javacripts/router.js
App.Router.map(function() {
this.resource('customers', { path: "/" });
});
Customers Route
// javascripts/routes/customer_routes.js
App.CustomersRoute = Ember.Route.extend({
model: function() {
return this.store.find('customer');
},
renderTemplate: function() {
this.render('customers/index');
}
});
Customer Model
// javascripts/models/customer.js
App.Customer = DS.Model.extend({
name: DS.attr('string')
});
Customer Index Template
// javacripts/templates/customers/index.js.handlebars
<ul>
<li>start</li>
{{#each customers}}
<li>{{name}}</li>
{{/each}}
</ul>
Store
// javacripts/store.js
App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
namespace: 'api/v1'
});
Instead of
{{#each customers}}
It should read either
{{#each controller}}
{{name}}
{{/each}}
or
{{#each customer in controller}}
{{customer.name}}
{{/each}}
I have recently posted two screencasts. One showing how to get started with a new application, and one showing how to setup Grunt:
https://www.youtube.com/watch?v=T-s34EVSE_0
https://www.youtube.com/watch?v=kPaHt_F3VcU
You might also get some use out of a talk I gave earlier this year as well, which goes through developing a simple application during the talk, including Ember Data.
https://www.youtube.com/watch?v=KH5RreHtaaQ
Your customers/index template is referencing a "customers" collection that doesn't exist.
Your route's model hook is returning an array of records, which makes Ember generate an Ember.ArrayController with its model set to your array of customers. It doesn't have a property called "customers", so the {{#each customers}} doesn't have anything to iterate over. If you change it to just {{#each}} (because this in this scope references the controller, which is array-like) or {{#each model}} (to explicitly access the model array of the ArrayController), it should work correctly.
Also, your renderTemplate hook in the Route is the default behavior, so you can just delete it.
Incidentally, I'd recommend just using an Ember JSBin or something while you're starting out and learning the basics, so when you need to ask for help, you can just link to the bin, and people have live code they can work with to help you out, with a minimum of effort. That low barrier to entry makes a big difference to people who are doing free work for internet points.
Summary
What's a clean way of updating a select element in a child controller scope so that the selected item's ID matches an ID in a parent scope?
Details
I have two controllers:
OrderController
CustomerController
OrderController loads an Order and shows it on an order form.
CustomerController is the scope for a subform within the OrderController form. It shows a list of all customers, with the default customer being the one associated with the order. The user can edit the details of the selected customer or add a customer right from the subform.
Possible Solutions
I've thought of two so far, but neither seems very good.
Include a list of all customers in the JSON passed to the Order $resource. That won't work because the user needs a full separate controller to update customers on the subform.
Fire an event when the customers have loaded within CustomerController. OrderController handles that event, updating the select based on its order's customer_id property. That seems better but still hacky.
To communicate between two controllers you would usually broadcast/emit, e.g:
(Coffeescript)
CustomerController:
Api.Customer.get(
id: $scope.customer_id
).$promise.then (data) ->
$scope.$emit 'event:updateOptionId', id
OrderController:
$scope.$on 'event:updateOptionId', (event, id) ->
$scope.customer_id = id
I don't know the structure of your app but this is passing the new customer id to order controller based on an action in the customer controller. If you post some of your code you may get a more thorough answer.
I found that Angular does handle the update automatically. The problem was that my server was returning a one-item array for the order data. So the customer_id of the order property wasn't going into $scope.order.customer_id; it was going into $scope.order[0].customer_id. Correcting that on the back end solved the issue.
With the following queries exposed by my back end:
GET /api/foos
returns a list of all Foos, suitable for display within a master list
GET /api/foos/:foo_id
returns a single Foo, with more detailed information, suitable for display within a detail view
My front end displays a list of Foos on the left and when one of them is clicked, the outlet on the right (in the outlet) displays its detailed version.
{{#each foo in model}}
{{#link-to 'foo' foo}}{{foo.name}}{{/link-to}}
{{/each}}
{{outlet}}
I am using ember-model to store my models, and have implemented App.Foo.adapter's find and findAll hooks, such that when they are invoked, they hit the back end APIs described above correctly.
When my app hits the GET /api/foos (via findAll) first, and then subsequently when the user clicks on the Foo, and ember-model doesn't hit GET /api/foos/:foo_id because it doesn't invoke the find hook, because it realises that that particular model is already in the cache.
This is great, because why fetch something again, when we know that we already have it in memory.
However, in my app, this assumption is insufficient. I have to further check if I have got the full version of that Foo, e.g. !!aFoo.get('propertyOnlyInDetailedVersion'), and otherwise I would like to force Foo to fetch again.
How can I go about doing this - how do I make ember-model re-fetch an object that has already been fetched prior?
This used to be a known issue, but this got fixed recently.
Taken in verbatim from: https://github.com/ebryn/ember-model/pull/297 (ckung's comment)
Developers can define "transient: false" on their classes that extend
Ember.Model, this will skip the cache check.
The commit: https://github.com/ckung/ember-model/commit/dc46171d2121630e62a130d58dd6b709c6c00541
Update your ember-model to the relevant version and you can get this working.
--
Edit: Sorry, I thought the next block text made my last edit -- but alas it didn't.
So my idea now is to do something like CachedModel extends LiveModel extends Ember-Model. Then set transient to true and false respectively.
So this was what I ended up going with in the end:
App.FooDetailRoute = Ember.Route.extend({
model: function() {
var foo = this.modelFor('foo');
if (!foo.get('propertyOnlyInDetailedVersion')) {
//if foo does not have propertyOnlyInDetailedVersion property, we know that only the
//summarised foo has been last fetched.
//to force a re-fetch, we trick ember-model by setting isLoaded to false
//before triggering fetch once more
foo.set('isLoaded', false);
var refetchedFoo = App.Foo.find(parseInt(foo.get('id'), 10));
return refetchedFoo ;
}
return foo;
}
});
tl;dr = set model.isLoaded to false and call model.find again.
I have an "index" view and an accompanying "pagination" view. On initialization, the index view fetches the relevant collection. The initially fetched collection is limited to 100 models and contains the count of all values in the collection. The count is passed to the pagination view and it accordingly produces page numbers. After the 10th page (10 records/page) The next 100 models are fetched, and so on the pattern continues.
Keeping the aforementioned in mind, when I add one or more models to the collection the model count will need to be re-fetched from the server (so the pages can be re-calculated), even though I do:
#collection.add [new_model]
However, in the event of changing a value in a model, I simply want the collection to be re-rendered.
With the following initialization code, I'm able to have the collection re-rendered after a change. But in the event of a "add" nothing happens. How can I construct the view to re-fetch from the server the new collection and count?
Note: I'm using fetch(add: true)
initialize: ->
#collection = new MyApp.MyCollection()
#collection.on('add', #render, #)
#collection.on('change', #render, #)
#collection.fetch(add: true)
To clarify the events logic:
reset: will be triggered after a fetch from the server. A fetch drops all content of the collection and just inserts everything in the collection that comes from the server
add: will be triggered after a model has been added to the collection. ATTENTION: add will not be triggered by a fetch, unless you use the add:True option.
change: will be triggered after the content/field of a model has changed. ATTENTION: change will not be triggered by fetch nor by adding a model.
So the events do hardly overlap but all serve a specific use case. You have that in mind when binding the events to methods, especially the render method.