Setting a belongsTo attribute via an action on controller not working - ember.js

I have the following models, customer:
App.Customer = DS.Model.extend({
//Other Attributes
area: DS.belongsTo('area', {async: true})
});
and area model -
App.Area = DS.Model.extend({
name: DS.attr('string'),
zip_code: DS.attr('number')
});
And I use an Ember.Select view to show a dropdown of the area a customer is in -
{{view Ember.Select viewName="select_area" content=possible_areas optionValuePath="content.id" optionLabelPath="content.name" value=area.id prompt="No area selected" selectionBinding="selected_area"}}
and the controller which wires up everything together -
App.CustomerController = Ember.ObjectController.extend({
possible_areas: function() {
return this.store.find('area');
}.property(),
selected_area: null,
actions: {
save: function() {
var selected_area_id = this.get('selected_area.id');
console.log(selected_area_id);
var selected_area = this.store.find('area', selected_area_id);
var self = this;
selected_area.then(function(area) {
console.log(area.get('id'));
var updated_customer = self.get('model');
updated_customer.set('area', area );
console.log(new_customer.get('area.id'));
updated_customer.save()
.then(function(customer) {
//success
},
function() {
//failure
});
});
}
}
});
Now here is the weird thing. Upon calling the 'save' action the first time, the line updated_customer.set('area', area ); fails with the error
Uncaught Error: Assertion Failed: Cannot delegate set('id', ) to the 'content' property of object proxy <DS.PromiseObject:ember551>: its 'content' is undefined.
Upon calling 'save' action immediately after that, the save goes through without any error and area of the customer is updated successfully. Although the dropdown shows selected_area to be null.
How do I prevent the first save from erroring out?
I am using ember-data 1.0.0-beta.6.

Since you have the association defined in your Customer model, I would remove the selected_area property from the controller and use ember-data's associations instead. Bind to the "area" association in the Ember.Select by using the selectionBinding property.
{{view Ember.Select viewName="select_area"
content=possible_areas
optionValuePath="content.id"
optionLabelPath="content.name"
prompt="No area selected"
selectionBinding="area"}}
This way, the area attribute will change when the user interacts with the select menu.
This has the added benefit of cleaning up your save action since we're binding directly to the area association for the Customer.
App.CustomerController = Ember.ObjectController.extend({
possible_areas: function() {
return this.store.find('area');
},
actions: {
save: function() {
this.get('model').save().then(this.onDidCreate.bind(this), this.onDidFail.bind(this))
}
onDidCreate: function() {
// Fullfilled callback
}
onDidFail: function() {
// Rejected callback
}
}
});
However, the possible_areas property won't be populated when the template first renders since this.get('area') returns a promise. I would wait to render the select until the promise settles. You can do this in the routes model hook since it waits until promises settle to render the template.
App.CustomerRoute = Ember.Route.extend({
route: function(params) {
return Ember.RSVP.hash({
customer: this.store.find('customer', params.customer_id),
possible_areas: this.store.find('area')
});
},
// Since the route hook returns a hash of (hopefully) settled promises, we
// have to set the model property here, as well as the possible_areas property.
setupController: function(controller, model) {
controller.set('model', model.customer);
controller.set('possible_areas', model.possible_areas);
}
});

Related

Can't get property/model array in selection binding until too late

I have an Ember Select view in my Ember CLI project that is bound to a dynamic model (an array of trips) that is created in setupController. Once loaded up the selection needs to default to a particular one indicated by tripId.
The method called in selectionBinding (selectedTrip) should do this but it can't seem to get the tripArray from model.
I've called console.debug(this.get('model')) and I can see tripArray there filled with entries but console.debug(this.get('model.tripArray')) returns undefined. It's almost like an illusion. At this point tripId transforms into null and the starting position for the select view is the prompt.
It's only until all the components are loaded that the controller can access tripArray. What am I doing wrong?
Controller:
export default Ember.ObjectController.extend(Ember.Validations.Mixin,{
trips: function(){
return this.get('model.tripArray');
}.property('model.tripArray'),
selectedTrip: function() {
var tempTrips = this.get('trips');
var trip;
console.debug(this.get('model.tripArray'));
if(tempTrips){
for(var i = 0; i < tempTrips.length; i++){
if(tempTrips[i].id === this.get('model.tripId')){
trip = tempTrips[i];
return;
}
}
}
return trip;
}.property('trips')
}
Route:
export default Ember.Route.extend({
beforeModel: function(){
this.controllerFor('application').checkSuperLogin();
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('model', model);
$("#loading-spinner").modal("show");
$.getJSON("http://website.herokuapp.com/trips", function (data) {
console.log(data);
controller.set('model.tripArray', data);
}).done(function(){
$("#loading-spinner").modal("hide");
});
}
});
Select view:
{{view Ember.Select
contentBinding="controller.trips"
valueBinding="model.tripId"
optionValuePath="content.id"
optionLabelPath="content.locationName"
prompt="-"
classNames="form-control input-md"
selectionBinding="controller.selectedTrip"
}}

Observer in DS.Model for an attribute fires when even the attribute is just used in template

I've written an array controller with pagination function.
When I switch to another pages for the first time, there's no problem.
But if I reviist the page I visited before, and observer for an attribute that is used is template is triggered.
(in this case, published)
When I remove {{#unless published}}...{{/unless}} from template, the observer isn't triggered anymore when I revisit the page where I've already visited.
I don't think I've done weird thing on my controllers....
(When pagination button is clicked, it simply changes controllers's page)
(I've written observer for title in model class to test whether this issue is limited to published property, and observer for title also behaves like observer for published. So this issue doesn't seem to limited to published property )
I'm using
Ember : 1.7.1+pre.f095a455
Ember Data : 1.0.0-beta.9
Handlebars : 1.3.0
jQuery : 1.11.1
and I tried beta and canary version of ember, but this issue remains same.
Here is my Route
MuteAdmin.IndexRoute = Ember.Route.extend({
model: function(params, transition, queryParams) {
var search = params.search || '';
var page = params.page || 1;
return this.store.find(this.get('articleModelClassName'), {
search: search,
page: page
});
},
setupController: function(controller, model) {
controller.set('model', model);
var will_paginate_meta = model.get("meta.will_paginate");
controller.set('totalPages', will_paginate_meta.total_pages);
controller.set('previousPage', will_paginate_meta.previous_page);
controller.set('nextPage', will_paginate_meta.next_page);
}
});
and here is my controller
MuteAdmin.IndexController = Ember.ArrayController.extend(MuteAdmin.Modelable, {
queryParams: ['page', 'search'],
page: 1,
totalPages: null,
pageChanged: function() {
this.store.find(this.get('articleModelClassName'), {
search: this.get('search'),
page: this.get('page')
}).then(function(model) {
this.set('model', model);
var will_paginate_meta = model.get("meta.will_paginate");
this.set('totalPages', will_paginate_meta.total_pages);
this.set('previousPage', will_paginate_meta.previous_page);
this.set('nextPage', will_paginate_meta.next_page);
}.bind(this));
}.observes('page'),
actions: {
doSearch: function() {
this.store.find(this.get('articleModelClassName'), {
search: this.get('search'),
page: 1
}).then(function(model) {
this.set('model', model);
var will_paginate_meta = model.get("meta.will_paginate");
this.set('totalPages', will_paginate_meta.total_pages);
this.set('previousPage', will_paginate_meta.previous_page);
this.set('nextPage', will_paginate_meta.next_page);
this.set('page', will_paginate_meta.current_page);
}.bind(this));
}
}
});
and here is my template
{{#each controller}}
<tr>
<td>{{link-to title "edit" this}} {{#unless published}}<small class="text-muted">비공개</small>{{/unless}}</td>
<td>{{author.name}}</td>
<td>{{category.title}}</td>
<td>시간 지정</td>
<td>{{viewCount}}</td>
</tr>
{{/each}}
and here is my model which has observers
MuteAdmin.Article = DS.Model.extend({
title: DS.attr( 'string' ),
body: DS.attr( 'string' ),
category: DS.belongsTo('category'),
author: DS.belongsTo('user'),
viewCount: DS.attr('number'),
published: DS.attr('boolean', { defaultValue: true }),
publishScheduled: DS.attr('boolean', { defaultValue: false }),
publishScheduleTime: DS.attr('date'),
publishedChanged: function() {
if (this.get('published') == true) {
this.set('publishScheduled', false);
}
console.log('published changed! ' + this.toString());
}.observes('published'),
});
Never mind, I know what it is. Your making a call to the server for the records that already exist. The results are merging into the pre-existing records in the store causing the model to invalidate and observer to fire.
http://emberjs.jsbin.com/OxIDiVU/1043/edit

How to come up with the global path to an Ember controller's property when value binding to a view helper?

Let's say I have the following controllers
App.PersonController = Ember.ObjectController.extend();
App.PersonStuffController = Ember.Controller.extend({ somethingOnController: [] });
with router entries
this.resource('person', function() {
this.route('stuff');
});
and routes
App.PersonRoute = Ember.Route.extend({
model: function() {
this.store.createRecord('person', {});
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
App.PersonStuffRoute = Ember.Route.extend({
model: function() {
this.modelFor('person');
},
setupController: function(controller, model) {
controller.set('person', model);
this.store.find('thing').then(function(things) {
controller.set('things', things);
});
}
});
and models
App.Thing = DS.Model.extend({
name: DS.attr('string'), // Instances of this model will have name values that match a Person instance's property keys
personStuffPath: function() {
return 'person.' + this.get('name');
}.property('name')
});
App.Person = DS.Model.extend({
// A lot of attributes that match the names of App.Thing instance name property values
});
In my template for PersonStuff, I have code
{{#each thing in things}}
{{view Ember.Select content=somethingOnController value=thing.personStuffPath.value}}
{{/each}}
So what I'm expecting here is a bunch of input fields that are bound to PersonStuffController's person property's properties. Instead I get the error:
Uncaught Error: Assertion Failed: Path 'person.exampleThingName' must be global if no obj is given.
So I modified my Thing model to instead be:
App.Thing = DS.Model.extend({
name: DS.attr('string', { defaultValue: 'thingName' }),
personStuffPath: function() {
return 'App.PersonStuffController.person.' + this.get('name');
}.property('name')
});
and the error instead becomes:
Uncaught Error: Property set failed: object in path "App.PersonStuffController.person" could not be found or was destroyed.
This should be the right global path, right? Can I just not do things this way for a reason?
JSBins:
Relative path error: http://jsbin.com/lozayobe/1/edit?html,js,output
Global path error: http://jsbin.com/lozayobe/2/edit?html,js,output
App.FooController refers to the class, it's not the actual instance. In most cases Ember controllers and routes are singletons, so they aren't recreated (except item controllers). If you wanted to create a global way of accessing a controller you would need to get the controller then add it to some global namespace.
For example (I'm not necessarily recommending this, but it's good to understand how things work):
App.ApplicationController = Em.Controller.extend({
beforeModel: function(){
var fooInstance = this.controllerFor('foo');
App.FooControllerInstance = fooInstance;
}
});
Now App.FooControllerInstance has the instance of FooController where you can map dynamic computed properties.

EmberJS: Use AJAX data from parent resource in child route

Let's say that I want to create an ember app that displays a matrix and allows
you to click on a cell for more information. My routes look like this:
App.Router.map(function() {
this.resource("data", function() {
this.route("cell", {path: ':row/:col'});
});
});
App.DataRoute = Ember.Route.extend({
model: function(params) {
return App.DataModel.create({
/* information needed for AJAX query */
});
},
});
App.DataCellRoute = Ember.Route.extend({
model: function(params) {
return Ember.Object.create({
row: params.row,
col: params.col
});
},
serialize: function(model) {
return {row: model.row, col: model.col};
},
});
Furthermore, the matrix load is expensive (say, a slow AJAX request), so I
don't want to have to reload the data when I transition to the child route.
Also, assume that the data in the cells can't be serialized, so I can't just
pass the cell data in the URL.
Here's what the controllers looks like:
App.DataController = Ember.ArrayController.extend({
model: null,
content: Ember.A(),
loadData: function() {
self = this;
$.ajax({
/* ... */,
success: function(data) {
App.beginPropertyChanges();
self.clear();
data.forEach(function(row) {
self.pushObject(row);
});
App.endPropertyChanges();
},
}.observes('model'),
});
App.DataCellController = Ember.ObjectController.extend({
needs: 'data',
model: {row: 0, col: 0},
matrixBinding: 'controllers.data.content',
cell: function() {
var xy = this.get('model');
return this.get('matrix')[xy.row][xy.col];
}.property('matrix.#each', 'model'),
});
When you click on a cell, its view tells DataController to send an event that
transitons to data.cell with the appropriate row/column. I expected that
when I transition to the data.cell route, I should have access to
DataController's content. However, all I get is the default empty array.
What am I doing wrong?
EDIT:
To answer the question 'how is DataController.content set', I updated the
question to show a more accurate depicition of DataRoute and
DataController. Basically, DataController has a model that contains
information pertinent to the AJAX request. I have a function, loadData,
which observes the model and loads content.

Computed property in handlebar #if not updating

I am trying to use the following template:
<script type="text/x-handlebars" data-template-name="login">
{{#if logged_in}}
Logged in
{{else}}
Not logged in
{{/if}}
</script>
with the model:
App.Login = DS.Model.extend({
access_token: DS.attr('string'),
logged_in: function() {
return (this.get('access_token') != null);
}.property('access_token')
});
to display the user's logged-in state.
The access_token is being set via an async callback in the Route's setupController:
App.LoginRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('content', model);
// call async login method
window.setInterval(function test() {
model.set('access_token', 'MY_ACCESS_TOKEN');
console.log(model.get('access_token'));
}, 5000);
},
model: function() {
return App.Login.find();
}
});
The problem is logged_in never seems to change (even though the model.set line is executed and 'access_token' is updated). Am I doing something wrong or should I be filing a bug?
Full code: http://jsfiddle.net/Q8eHq/
You are setting the model to App.Login.find() which returns an enumerable, not a single object. One way to do it, is to set the model to a single object:
App.LoginRoute = Ember.Route.extend({
model: function() {
return App.Login.find(1);
}
});
Or if you are going to use a dynamic route (e.g. users/login/9):
App.LoginRoute = Ember.Route.extend({
model: function(params) {
return App.Login.find(params.id);
}
});