ember.js Ember.Select multiple=true with preselected values - ember.js

I'm using a Multiselect view:
{{view Ember.Select
multiple="true"
contentBinding="App.filtersProductController"
selectionBinding="App.filtersController.products"
optionLabelPath="content.fullName"
optionValuePath="content.id"
isVisibleBinding="App.filtersController.productListBox"}}
Is it possible to preselect multiple values in the "select" box and to change the selected values programmatically? Background: I want to save different combinations of three "select" boxes settings as bookmarks. When loading a bookmark, I have to set the "select" boxes values.
Thank you

Yes. In your controller you have to create a property to keep the selected value or values when working with Ember.Select.
In the code below I'm setting the Greetings as the content of the select box, in the controller that lists those Greetings (check ApplicationRoute), I also have a property called selectedItems which I'm binding to the Select and I'm using a couple other properties to filter the values I want to pre-select (1 and 3) in case none of the items are already selected when the view loads.
This will render a multiple select box with the items which the id are either 1 or 3 marked as selected. You can see the source here: http://jsfiddle.net/schawaska/Y8P4m/
Handlebars:
<script type="text/x-handlebars">
<h1>Test</h1>
{{view Ember.Select
multiple="true"
selectionBinding="controller.selectedItems"
contentBinding="controller"
optionLabelPath="content.text"
optionValuePath="content.id"}}
</script>
JavaScript:
window.App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
App.Greeting = DS.Model.extend({
text: DS.attr('string'),
when: DS.attr('date'),
selected: false,
isSelected: function() {
return this.get('selected');
}.property('selected')
});
App.ApplicationController = Em.ArrayController.extend({
preselected: function() {
return this.get('content').filter(function(greeting) {
return greeting.get('id') == 1 ||
greeting.get('id') == 3;
});
}.property('content.#each'),
selectedItems: function() {
if(this.get('selected.length') <= 0) {
return this.get('preselected');
} else {
return this.get('selected');
}
}.property('selected', 'preselected'),
selected: function() {
return this.get('content').filter(function(greeting) {
return greeting.get('isSelected');
})
}.property('content.#each')
});
App.Greeting.FIXTURES = [
{id: 1, text: 'First', when: '3/4/2013 2:44:52 PM'},
{id: 2, text: 'Second', when: '3/4/2013 2:44:52 PM'},
{id: 3, text: 'Third', when: '3/4/2013 2:44:52 PM'},
{id: 4, text: 'Fourth', when: '3/4/2013 3:44:52 PM'}
];
App.ApplicationRoute = Em.Route.extend({
setupController: function(controller) {
controller.set('model', App.Greeting.find());
}
});

I have created a complete example with single and multi "select" elements. You can set defaults and change the selected value programmatically or by using the "select" GUI element. The controller code:
// class for single selects
App.SingleSelectFilterController = Ember.ArrayController.extend({
selection: null,
active: true,
update: function(id) {
this.set("selection", id);
},
getSelectedId: function() {
return this.get("selection");
}
});
// class for multi selects
App.MultiSelectFilterController = Ember.ArrayController.extend({
selection: null,
active: true,
update: function(selectionIds) {
// Workaround: Reinitializing "content". How to do it well?
var contentCopy = [];
for(i = 0; i < this.get("content").length; i++) {
contentCopy.push(this.get("content")[i]);
}
this.set("content", contentCopy);
this.set("selection", selectionIds);
},
selected: function() {
var me = this;
return this.get('content').filter(function(item) {
for(i = 0; i < me.get("selection").length; i++) {
if(me.get("selection")[i] === item.get('id')) { return true; }
}
return false;
});
}.property('content.#each'),
getSelectedIds: function() {
var ids = [];
for(i = 0; i < this.get("selected").length; i++) {
ids.push(this.get("selected")[i].get("id"));
}
return ids;
}
});
// create single and multi select controllers
App.metricController = App.SingleSelectFilterController.create();
App.metricController.set("content", App.filterData.get("metrics"));
App.metricController.set("selection", "views"); // set default value for single select element
App.platformController = App.MultiSelectFilterController.create();
App.platformController.set("content", App.filterData.get("platforms"));
App.platformController.set("selection", ["plat-black"]); // set default value for multi select element
And the complete example:
http://jsfiddle.net/7R7tb/2/
Thanks to MilkyWayJoe for his help!
Perhaps somebody knows how to fix the workaround (see the code comment above)?

Related

Computed #each property isn't updated

I'm using ember 2.1.0
I have 2 models - testGroup
export default DS.Model.extend({
tag: DS.attr('string'),
crvs: DS.hasMany('crv'),
skus: DS.hasMany('sku'),
isSKU: DS.attr('boolean', {defaultValue: true})
});
and crv.
import DS from 'ember-data';
export default DS.Model.extend({
tag: DS.attr('string'),
ctr: DS.attr('number', {defaultValue: 10}),
convs: DS.attr('number', {defaultValue: 10}),
testGroup: DS.belongsTo('testGroup')
});
And in component template I show all items from testGroup.crvs with ability to change ctr/convs.
{{#each model.crvs as |crv|}}
<tr>
<td>{{crv.tag}}</td>
<td>{{input value=crv.ctr}}</td>
<td>{{input value=crv.convs}}</td>
</tr>
{{/each}}
And at the end I want tot show total:
<div>Phases: {{total}}%</div>
For this issue I created a computed property in my component:
total: Ember.computed('model.crvs.#each.convs', function() {
var crvs = this.get('model').get('crvs');
if (crvs) {
var tmp = 0;
crvs.forEach(function(crv) {
tmp += parseFloat(crv.get('convs'));
});
return tmp;
} else {
return 0;
}
}),
It shows right value when page is rendered but when I change values in crvs collection the computed property isn't changed. I can see new value in ember console.
What's wrong with my property?
Update
I found strange behaviour. CRVs collection comes with test group from server:
{
'testGroup': {
'id': 12,
'tag': 'foo',
'crvs': [1, 2]
},
'crvs': [
{
id: 1,
tag: 'crv1234',
ctr: 10,
convs: 6,
testGroup: 12,
},
{
id: 2,
tag: 'crv1235',
ctr: 10,
convs: 7,
testGroup: 12,
}
]
}
And for this collection computed property doesn't work. But if I change to model.skus collection which is added by user in UI like this:
var sku = this.get('targetObject.store').createRecord('sku', {
catalogName: this.get('catalog').displayName,
testGroup: this.get('model'),
id: this.get('sku').sku,
value: this.get('skuValue')
});
this.get('model').get('skus').pushObject(sku);
Then computed property works fine for such item.
So it's very strange for me such behaviour.
I think I had the same problem. There is a bug with Ember 2.1/2.2 that breaks computed properties of relationships that use #each.
There is a workaround: use content.#each instead of #each
total: Ember.computed('model.crvs.content.#each.convs', function() {
var crvs = this.get('model').get('crvs');
if (crvs) {
var tmp = 0;
crvs.forEach(function(crv) {
tmp += parseFloat(crv.get('convs'));
});
return tmp;
} else {
return 0;
}
}),
reference answer by Pedro Rio: Model Computed Property not updating
try this:
Use [] instead of '#each'
total: Ember.computed('model.crvs.[].convs', function() {
var crvs = this.get('model').get('crvs');
if (crvs) {
var tmp = 0;
crvs.forEach(function(crv) {
tmp += parseFloat(crv.get('convs'));
});
return tmp;
} else {
return 0;
}
}),
You are counting the wrong property. There's only one convs in crvs but multiple crvs in model:
Ember.computed('model.#each.crvs.convs', function() {

Ember editable recursive nested components

I'm currently trying to build a component that will accept a model like this
"values": {
"value1": 234,
"valueOptions": {
"subOption1": 123,
"subOption2": 133,
"subOption3": 7432,
"valueOptions2": {
"subSubOption4": 821
}
}
}
with each object recursively creating a new component. So far I've created this branch and node components and its fine at receiving the data and displaying it but the problem I'm having is how I can edit and save the data. Each component has a different data set as it is passed down its own child object.
Js twiddle here : https://ember-twiddle.com/b7f8fa6b4c4336d40982
tree-branch component template:
{{#each children as |child|}}
{{child.name}}
{{tree-node node=child.value}}
{{/each}}
{{#each items as |item|}}
<li>{{input value=item.key}} : {{input value=item.value}} <button {{action 'save' item}}>Save</button></li>
{{/each}}
tree-branch component controller:
export default Ember.Component.extend({
tagName: 'li',
classNames: ['branch'],
items: function() {
var node = this.get('node')
var keys = Object.keys(node);
return keys.filter(function(key) {
return node[key].constructor !== Object
}).map(function(key){
return { key: key, value: node[key]};
})
}.property('node'),
children : function() {
var node = this.get('node');
var children = [];
var keys = Object.keys(node);
var branchObjectKeys = keys.filter(function(key) {
return node[key].constructor === Object
})
branchObjectKeys.forEach(function(keys) {
children.push(keys)
})
children = children.map(function(key) {
return {name:key, value: node[key]}
})
return children
}.property('node'),
actions: {
save: function(item) {
console.log(item.key, item.value);
}
}
});
tree-node component:
{{tree-branch node=node}}
Anyone who has any ideas of how I can get this working would be a major help, thanks!
Use:
save(item) {
let node = this.get('node');
if (!node || !node.hasOwnProperty(item.key)) {
return;
}
Ember.set(node, item.key, item.value);
}
See working demo.
I think this would be the perfect place to use the action helper:
In your controller define the action:
//controller
actions: {
save: function() {
this.get('tree').save();
}
}
and then pass it into your component:
{{tree-branch node=tree save=(action 'save')}}
You then pass this same action down into {{tree-branch}} and {{tree-node}} and trigger it like this:
this.attrs.save();
You can read more about actions in 2.0 here and here.

You modified *** twice in a single render

After upgrading to 1.13 I get this exception and I can't figure out what's the issue. I also couldn't find any helpful resource which tackles my issue.
It happens for properties I set in another computed property. But this property is definitely called only once.
I have created a jsbin example: http://emberjs.jsbin.com/roderameya/edit?html,js,console,output
UPDATE
As requested I post some code which is more close to my real implementation.
Ember.Controller.extend({
filter: '',
resultCount: {
total: 0,
matches: 0,
mismatches: 0
},
results: function() {
var items = this.get('model'),
matches = [],
resultCount = {};
// Apply search filter
matches = items.filter(function(item){
// Just a dummy filter function
return true;
});
// We need the total number matched by the filter string
resultCount.total = matches.length;
// The already matched items must be narrowed further down
matches = matches.filter(function(item) {
// Another filter function
return true;
});
resultCount.matches = matches.length;
resultCount.mismatches = resultCount.total - matches.length;
this.set('resultCount', resultCount);
return matches;
}.property('model', 'filter'),
});
Try to have your properties not set other properties, but rather depend on each other:
App.IndexController = Ember.Controller.extend({
count: function() {
return this.get("data.length") || 0;
}.property('data.length'),
data: [1,2,3]
});
Updated jsbin for you.
UPDATE
Basically, your resultCount is a temporary variable that we can get rid of, and the rest are just chaining computed properties together:
updated jsbin for advanced example
code:
// Index controller
App.IndexController = Ember.Controller.extend({
count: Ember.computed('filteredItems.length', function(){
return this.get('filteredItems.length');
}),
data: [
Ember.Object.create({ name: "jim", age: 15 }),
Ember.Object.create({ name: "jeff", age: 42 }),
Ember.Object.create({ name: "eric", age: 7 })
],
filter: RegExp(".*"),
ageFilter: -1,
mismatchCount: Ember.computed('filteredItems.length', 'secondPassFilteredItems.length', function() {
return this.get('filteredItems.length') - this.get('secondPassFilteredItems.length');
}),
filteredItems: Ember.computed('data', 'filter', function() {
var controller = this;
return this.get('data').filter(function(item) {
return item.get('name').match(controller.get("filter"));
});
}),
secondPassFilteredItems: Ember.computed('filteredItems', 'ageFilter', function() {
var controller = this;
var ageFilter = controller.get("ageFilter");
if (Ember.isEqual(ageFilter, -1)) {
return this.get('filteredItems');
} else {
return this.get('filteredItems').filter(function(item) {
// more filtering
return item.get("age") <= ageFilter;
});
}
}),
results: Ember.computed.alias('secondPassFilteredItems'),
actions: {
filterByJ: function() {
this.set('filter', new RegExp("j"));
},
filterByEric: function() {
this.set('filter', new RegExp("eric"));
},
filterAllNames: function() {
this.set('filter', new RegExp(".*"));
},
filterYoungins: function() {
this.set('ageFilter', 15);
},
filterAllAges: function() {
this.set('ageFilter', -1);
}
}
});
Template usage:
<script type="text/x-handlebars" data-template-name="index">
<p>Results found: {{count}}</p>
<p>Diff between first and second filter: {{mismatchCount}}</p>
<p>First Filter:
<button {{action 'filterAllNames'}}>all people</button>
<button {{action 'filterByJ'}}>People with J in name</button>
<button {{action 'filterByEric'}}>People named 'eric'</button>
</p>
<p> Second Filter:
<button {{action 'filterAllAges'}}>all ages</button>
<button {{action 'filterYoungins'}}>15 years old or younger</button>
</p>
<ul>
{{#each results as |item|}}
<li>{{item.name}} is {{item.age}} years old</li>
{{/each}}
</ul>
</script>

Delete item from ember-tables

I'm trying add a delete button with an ember action from a controller. For some reason Ember.Handlebars.compile('<button {{action "deletePerson"}}>Delete</button> returns a function and not the compiled string.
Here's a jsbin
Here's the relevant portion of code:
App.ApplicationController = Ember.Controller.extend({
columns: function() {
...
buttonColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 100,
headerCellName: 'Action',
getCellContent: function(row) {
var button = Ember.Handlebars.compile('<button {{action "deletePerson" this}}>Delete</button>');
return button; // returns 'function (context, options) { ...'
}
});
...
}.property()
...
After looking through the link from #fanta (http://addepar.github.io/#/ember-table/editable) and a lot of trial and error, I got it working.
Here's the working jsbin.
Here are some key points:
Instead of using getCellContent or contentPath in the ColumnDefinition, you need to use tableCellViewClass and to create a view that will handle your cell
Pass in this to the action on your button — and modify content off that. One gotcha is to edit content, you need to copy it using Ember.copy
Here's the relevant code:
App.ApplicationController = Ember.Controller.extend({
columns: function() {
...
buttonColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 100,
headerCellName: 'Action',
tableCellViewClass: 'App.PersonActionCell'
});
...
}.property(),
onContentDidChange: function(){
alert('content changed!');
}.observes('content.#each'),
...
});
App.PersonActionCell = Ember.Table.TableCell.extend({
template: Ember.Handlebars.compile('<button {{action "deletePerson" this target="view"}}>Delete</button>'),
actions: {
deletePerson: function(controller){
// Will NOT work without Ember.copy
var people = Ember.copy(controller.get('content'));
var row = this.get('row');
// For some reason people.indexOf(row) always returned -1
var idx = row.get('target').indexOf(row);
people.splice(idx, 1);
controller.set('content', people);
}
}
});

Ember.js count position of object in Ember Data Model for simple pagination

I am tryin to count the position of an object. That means which numeric position an item has. That cannot be the primary key because they are not persistent; a record can get deleted.
Asume we have this "model" (a simple array for now, I use Ember Data):
posts = [
{id: 4, number: 104, title: 'Post 4'},
{id: 2, number: 102, title: 'Post 2'},
{id: 3, number: 103, title: 'Post 3'},
];
So at to allow sorting in the postsController we do:
this.controllerFor('posts').set('sortProperties', ['id']); // or maybe just sorting on 'number'
this.controllerFor('posts').set('sortAscending', true);
In the template I want to show the current post and the total number of posts {{currentPostCount}} of {{totalPostCount}}
In postController I have the following computed properties:
App.PostController = Ember.ObjectController.extend({
posts: function () {
return this.store.all('posts');
}.property(),
currentCount: function () {
// if there is an id get the position of the record
// that will be the position count of the post record
var id = this.get('id');
if (id) {
console.log('this item has an id: ' + id);
var count = 0;
var currentCount;
// loop over posts to check at which position the current post is
this.get('posts').filter(function (item) {
count++;
if (item.id == id) {
console.log('yay id found! count is: ' + count)
currentCount = count;
}
});
return currentCount;
}
}.property('posts.#each'),
totalCount: function () {
var posts= this.get('posts');
return posts.get('length');
}.property('posts.#each')
});
Edit: add model:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
this.controllerFor('posts').set('model', this.store.find('post'));
// sort posts by id
this.controllerFor('posts').set('sortProperties', ['id']);
//(...)
}
});
App.PostsController = Ember.ArrayController.extend();
.
Update: full working simple pagination in Ember.js
In your template:
<div id="pagination">
<span>Post {{index}}/{{totalCount}}</span>
<a {{action previousPost this}} href="#">Previous</a>
<a {{action nextPost this}} href="#">Next</a>
</div>
The PostsController:
App.PostsController = Ember.ArrayController.extend({
sortProperties: ['id'], // I sort on ID, but you can sort on any property you want.
sortAscending: true,
assignIndex: function () {
this.map(function (item, index) {
Ember.set(item, 'index', index + 1)
})
}.observes('content.[]', 'firstObject', 'lastObject')
});
Actions in the PostController:
previousPost: function (post) {
var newIndex = (post.index - 1);
var previousPost = this.store.all('post').findBy('index', newIndex);
if (previousPost) {
this.transitionToRoute('post', previousPost);
}
},
nextPost: function (post) {
var newIndex = (post.index + 1);
var nextPost = this.store.all('post').findBy('index', newIndex);
if (nextPost) {
this.transitionToRoute('post', nextPost);
}
}
I have two computed properties in the PostController. However you can better use the following in your PostsController to do it the Ember way, like kingpin2k said. Then you can also omit the posts property.
posts: function () {
return this.store.all('posts');
}.property(),
// totalcount below the page for pagination
totalCount: function () {
var posts= this.get('posts');
return posts.get('length');
}.property('posts.#each'),
You can maintain an index on array content during sorting or removing objects.
In PostsController:
App.PostsController=Em.ArrayController.extend({
assignIndex:function(){
this.map(function(item,index){Em.set(item,'index',index+1)})
}.observes('content.[]','firstObject','lastObject'),
//other contents to follow...
In posts template the property index is available which is dynamically updated when adding or removing or sorting objects.
In Posts Template:
{{#each controller}}
<p>{{index}}. {{name}}</p>
{{/each}}