Ember.Select works sometimes but assertion fails - ember.js

In my ember.js app, I have a 'user' model that has a "hasMany" relationship to 'group'. A user can be a member of zero or more groups. To allow the user to select the groups, I am using the built-in Ember.Select view.
If I load my users via the route /users, I can see the user and the groups to which that user is assigned. If I go to the edit route (/users/1/edit), I use Ember.Select to show the universe of all groups, along with the selection of that user's "selected" groups. Unfortunately, when I transition via the /users route, none of the groups are selected. If I refresh the page on the edit route, I see the groups correctly selected as I expect.
Another thing to note is that I don't see any errors when transitioning from /users to /users/1/edit (with no selected groups). However, when I refresh directly from the /users/1/edit route, the selection works correctly, but I see the following in the console (I am including a bit of the stack):
Assertion failed: The content property of DS.PromiseArray should be set before modifying it ember.js:394
(anonymous function) ember.js:394
Ember.assert ember.js:53
Ember.ArrayProxy.Ember.Object.extend._replace ember.js:16284
Ember.ArrayProxy.Ember.Object.extend.replace ember.js:16291
Ember.EnumerableUtils.replace ember.js:1829
Ember.Select.Ember.View.extend._changeMultiple ember.js:27933
Ember.Select.Ember.View.extend._change ember.js:27859
Ember.Select.Ember.View.extend._triggerChange ember.js:27902
sendEvent ember.js:2334
Any pointers would be helpful!
user_model.js:
Usermanagement.User = DS.Model.extend({
authenticateExternally: DS.attr(),
email: DS.attr(),
enabled: DS.attr(),
firstName: DS.attr(),
lastName: DS.attr(),
password: DS.attr(),
systemExternalAuthenticationEnabled: DS.attr(),
selectedGroups: DS.hasMany('group', {
async: true
}),
username: DS.attr(),
meta: DS.attr(),
fullName: function() {
return '%# %#'.fmt(this.get('firstName'), this.get('lastName'));
}.property('firstName', 'lastName'),
});
user_edit_template.hbs: (snippet)
<div class="field form-group">
<div class="fieldLabel">Groups</div>
{{view Ember.Select
multiple="true"
class="form-control"
selectionBinding="selectedGroups"
contentBinding="controllers.groups.allGroups"
optionLabelPath="content.name"
optionValuePath="content.id"}}
</div>
groups_controller.js:
Usermanagement.GroupsController = Ember.ArrayController.extend({
allGroups: function() {
return this.store.find('group');
}.property()
});
EDIT: Forgot to mention, Ember v1.0.0, Ember-data v1.0.0-beta3

The error is complaining about the select mucking with your model (user.selectedGroups) before it's finished loading.
The reason none are selected is probably because they are probably different objects. You might iterate over each item in the selected items and the allGroups options and check out the ember guid on it, if they are different items then that's why it's not showing them as selected.
Just out of curiosity, can you try setting the controller in the application route's setupController?
App.ApplicationRoute = Em.Route.extend({
setupController: function(controller, model){
this._super(controller, model);
this.controllerFor('groups').set('model', this.store.find('group'));
}
});
{{view Ember.Select
multiple="true"
class="form-control"
selectionBinding="selectedGroups"
contentBinding="controllers.groups.model" //instead of allGroups
optionLabelPath="content.name"
optionValuePath="content.id"}}

Related

How to preselect a value on an Ember.select view

I'm trying to preselect a value on an Ember.select view. My select works fine, but I don't know how to preselect a value dynamically - or isn't it possible at the moment with Ember 1.8-beta and Ember Data Beta-11?
This is my select:
{{view select
class="uk-width-1-1"
content=services
optionLabelPath="content.name"
optionValuePath="content.id"
prompt="Service"
selectionBinding="selectedService"
}}
It works fine when I try to get the current active value with this.get('selectedService'), but when I try to set a specific customer in my controller (e.g. to pre-fill an edit form), nothing happens:
var service = timetracking.get('service');
this.set('selectedService', service);
These are my models:
App.Timetracking = DS.Model.extend({
duration: DS.attr('number'),
day: DS.attr('date'),
notice: DS.attr('string'),
project: DS.belongsTo('project', {async: true}),
service: DS.belongsTo('service', {async: true}),
user: DS.belongsTo('user', {async: true})
});
App.Service = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
timetrackings: DS.hasMany('timetracking', {async: true}),
archived: DS.attr('boolean')
});
so you set the value of the select box, by setting "selectedService" to the instance that you want to have selected
http://emberjs.jsbin.com/vuhefa
you seem to be doing the same thing
var service = timetracking.get('service');
this.set('selectedService', service);
but since service is an async relationship it will return a promise and therefore not be the correct actual model try this insted
var context = this;
var service = timetracking.get('service').then(function(service){
context.set('selectedService', service);
});
its a rule that async relationships return promises and embedded relationships return objects
In your template can simply use service from your controller/model as follows:
{{view select
class="uk-width-1-1"
selection=service
content=services
optionLabelPath="content.name"
optionValuePath="content.id"
prompt="Service"
}}
The selection option specifies the property to use for populating the initial selection. It will use that object along with optionLabelPath and optionValuePath to configure the <select> element in the DOM.
When an option is chosen by the user, that same selection property will be updated, ie service on your controller/model.
IMPORTANT: Since you are using an async belongsTo for the service relation on your model, you need to help the select view to use the content of the promise proxy representing the relationship.
Do this by using an alias on your controller:
App.TimetrackingController = Ember.ObjectController.extend({
service: Ember.computed.alias('content.service.content')
});
There is an more on this over on the Ember issue.

Ember JS Deep Linking

I have an Ember JS 1.5.1 app with ember-data 1.0.8 beta. There are TWO simple compiled templates the relevant parts are:
index
<div class="container-fluid">
<div class="col-md-2 sidebar">
<ul class="nav nav-sidebar">
{{#each model}}
<li>
{{#link-to 'activities' this}}{{name}}{{/link-to}}
</li>
{{/each}}
</ul>
</div>
<div class="col-md-10 col-md-offset-2">
{{outlet}}
</div>
</div>
activities
<div>
<ul>
{{#each model.activities}}
<div class="row">
<p>activity {{id}} is {{name}}</p>
</div>
{{/each}}
</ul>
</div>
The application is also simple, reduced to a few bits of fixture data and some route functions:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter;
App.Router.map( function(){
this.resource('index', {path: '/'}, function(){
this.resource('activities', { path:':name'}, function(){
this.resource('activity');
});
});
});
App.IndexRoute = Ember.Route.extend({
model: function(){
return this.store.find('role');
}
});
App.ActivitiesRoute = Ember.Route.extend({
model: function(params){
var roles = this.modelFor('index');
return roles.findBy('name', params.name).get('activites');
}
});
App.Role = DS.Model.extend({
name: DS.attr('string'),
activities: DS.hasMany('activity', {async:true} )
});
App.Activity = DS.Model.extend({
name: DS.attr('string')
});
App.Role.FIXTURES = [{
id: 1,
name: 'Management',
activities: [1]
},{
id: 2,
name: 'Analysis',
activities: [1,2]
},{
id: 3,
name: 'Development',
activities: [2]
}]
App.Activity.FIXTURES = [{
id: 1,
name: 'talking'
},{
id: 2,
name: 'doing'
}];
What I get when I navigate to localhost is a simple list of the three roles on the left hand side of the screen and nothing on the right hand side. (as expected)
When I then select a link (such as 'Analysis') the outlet on the right hand side fills with the expected list of two activity names "talking" and "doing".
LHS list RHS pane
========== ========
Management talking
Analysis doing
Development
So far so good.
I noticed that when I hovered over the 'Analysis' link the browser shows the url below as expected
localhost:/#/Analysis
However when I cut and paste this url into the browser address bar directly I only get the left hand side list of links and nothing in the main window. The list of "talking" and "doing" does no appear. There are no errors shown in the browser and ember does not raise and exceptions.
How do you get this simple nested route to refresh all the contents when you directly deep link rather than having to navigate from the root all the time?
When you use link-to and pass it the model, it will skip the model hook supplying the model from the link-to to the route. If you refresh the page, it will hit each route down the tree until it's fetched the models for each resource/route necessary to fulfill the request. So if we look at your routes one at a time it will do this:
Hit the application route, fetch its model if it exists (application route is the root of every Ember app).
Hit your index route, where it will return App.Role.find()
Hit your activites route, where it will return App.Activity.find()
Number 3 is where you real issue lies. Regardless of whether or not that part of the url says Analysis, Management, or Development you will already return App.Activity.find(). You've defined the dynamic slug :name, ember will parse the appropriate part of the url, and pass that part is as an object, in the case of Analysis Ember will pass in { name: 'Analysis' } to your model hook. You will want to take advantage of this, to return the correct model.
App.ActivitiesRoute = Ember.Route.extend({
model: function(params){
var roles = this.modelFor('index');
return roles.findBy('name', params.name);
}
});
Additionally you are using a fairly old version of Ember Data. Here's a small example of how Ember Data should be used with newer versions: http://emberjs.jsbin.com/OxIDiVU/617/edit
As you can see, you no longer declare the store. Additionally you may run into trouble with what would be considered async properties, and might want to read https://github.com/emberjs/data/blob/master/TRANSITION.md

choosing a value from select

using ember.js 1.0 and ember-data 1.0 beta2
I have a model (state) with the following properties
state: DS.attr('string'),
stateName: DS.attr('string'),
and a model (customer) with the following properties
name: DS.attr('string'),
stateID: DS.attr('string'),
state: DS.belongsTo("state")
I want to be able to edit the customer and choose the state from a drop-down (that has the stateID + name showing : eg "FL - Florida" and when selected, to store the state.stateID into the customer.stateID property
this is the first time I've tried something like this , and am slightly confused about the process.
In my customer route I've set up the following:
setupController: function(controller, model) {
this._super(controller, model);
this.controllerFor('state').set('content', this.store.find('state'));
}
and my select is this:
{{view Ember.Select
contentBinding="controllers.state.content"
optionValuePath="content.stateName"
optionLabelPath="content.stateName"
valueBinding="content.stateID"
selectionBinding="content.stateID"
prompt="Select a state"
}}
now I'm confused about where to go from here.
thanks
update
changed the view to say
{{view Ember.Select
contentBinding="controllers.state.content"
optionValuePath="content.stateID"
optionLabelPath="content.stateName"
valueBinding="customer.stateID"
}}
and I still don't get the stateid property to change . I've also tried
selectionBinding="customer"
to no avail.
update #2
I suspect that my problem may be linked to the property name. I changed the customer.stateID property to be customer.foobar and changed the select to read
{{view Ember.Select
contentBinding="controllers.state.content"
optionValuePath="content.stateName"
optionLabelPath="content.stateName"
valueBinding="foobar"
class="form-control"
}}
and now customer.foobar is updated with the value from the select.
Is there a problem with a property called stateID on customer ? I have a state model and state controller etc so is there a conflict ?
after all that - the problem was in the models themselves. The state model does not have a stateID field, it's state.state ...
My heartfelt apologies to all that wasted their time on this. Such a stupid error.
Okay, maybe not the best solution, but it works well:
App.ItemModalController = Ember.ObjectController.extend({
content: [],
availableCategories: function() {
return this.store.find('category');
}.property(),
//...
});
And the select:
{{view Ember.Select
contentBinding="availableCategories"
valueBinding="categorySelected"
optionLabelPath="content.title"
optionValuePath="content.id"
prompt="Please select a category"
class="form-control"
}}

Ember.js What is the difference between the setupController and declaring a <Name>Controller

I see many confusing examples in Ember.js official tutorials.
One example which I really don't like is:
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('title', "Hello world!");
}
});
App.ApplicationController = Ember.Controller.extend({
appName: 'My First Example'
});
Now as I understand it I could have written it like that instead:
App.ApplicationController = Ember.Controller.extend({
appName: 'My First Example',
title: 'Hello world!'
});
And removing the setupController from route.
What is the purpose/benefit of using setupController?
setupController is primarily for setting up some controller context dynamically. In your example, if the title is always gonna be "Hello world!" it's fine to set it in class declaration.
By default, setupController will set the model property of controller to the value returned from model hook of the route.
You could also you it to, for example, set the model of another controller, or set some initial controller state that depends on the model.
For example, suppose you have the following:
// Model
App.Post = DS.Model.extend({
title: DS.attr('string'),
text: DS.attr('string'),
autoEdit: DS.attr('string')
});
// Controller
App.PostController = Ember.ObjectController.extend({
isEditing: null,
toggleEdit: function() { this.toggleProperty('isEditing'); }
});
Template:
<a href="#" {{action 'toggleEdit'}}>Toggle edit mode</a>
{{#if isEditing}}
{{input type="text" value=title placeholder="Title"}}
{{textarea type="text" value=text placeholder="Text"}}
{{else}}
<h1>{{title}}<h1>
<article>{{text}}</article>
{{/if}}
And then, you decide that it would be nice to turn editing mode on by default for posts with autoEdit equal to true. You'll probably want to do that in the route (since the controller, when instantiated, knows nothing about the model):
App.PostRoute = Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
if (model.get('autoEdit')) {
controller.set('isEditing', true);
}
}
});
So basically, it's for "initializing" the controller (setting model and default state).

duplicated data when navigating without reloading page using ember-data

I'm using ember.js 1.0.0-pre4, ember-data revision 11.
I have the following model:
App.DbProcess = DS.Model.extend({
pid: DS.attr('number'),
backendStart: DS.attr('string'),
transactionStart: DS.attr('string'),
queryStart: DS.attr('string'),
stateChange: DS.attr('string'),
waiting: DS.attr('boolean'),
state: DS.attr('string'),
query: DS.attr('string')
})
With the following route:
App.HomeDbProcessesRoute = Ember.Route.extend({
model: function() {
return App.DbProcess.find();
}
})
I then have a template which uses {{#each controller}}{{/each}} to render all the processes retrieved. However if I navigate to other pages (without reloading the page) and returning back to the processes page, the processes will be retrieved again and the duplicates are rendered on page.
EDIT: I also tried this, but it didn't work:
DS.RESTAdapter.map('App.DbProcess', {
primaryKey: 'pid'
})
I had the same issue now and here is my little hot-fix:
{{#if id}}
<div>
{{title}}
</div>
{{/if}}
In the template I render item from store only if it has id set (only those are coming from databse). But You propably solved it already!
(using revision 12)
Turns out you can do something like this to customize the primary key globally
App.Adapter = DS.RESTAdapter.extend({
url: document.location.protocol+'//url-api.com',
serializer: DS.RESTSerializer.extend({
primaryKey: function(type) {
// If the type is `BlogPost`, this will return
// `blog_post_id`.
var typeString = (''+type).split(".")[1].underscore();
return typeString + "_id";
}
})
})