Ember.Select doesn't fire observable when value is changed - ember.js

I have a Ember.Select with valueBinding set to an item property in an array. This property is being "observed" by a function. That function nevers gets fired after a selection change, although the property's value has changed.
HandelBars:
{{#each tmpUploadedDocument in App.uploadedDocuments}}
{{view Ember.Select
valueBinding="tmpUploadedDocument.Category"
selectionBinding="tmpUploadedDocument.selectedItem"
contentBinding="App.documentCategories"
optionValuePath="content.value"
optionLabelPath="content.label"
prompt="Select Category..."
}}
{{/each}}
Javascript:
window.App = Ember.Application.create();
App.documentCategories = [];
App.documentCategories.push({label: 'A', value: '1'},{label: 'B', value: '2'});
App.uploadedDocuments = Ember.A();
var tmpDocument = Ember.Object.create({
Category: 0,
CategoryDidChange: function() {
console.log('Category changed to: ' + this.Category);
}.observes('Category')
});
App.uploadedDocuments.addObject(tmpDocument);
This jsbin shows it a bit clearer: JSBIN
What I want to accomplish in the observes function is to filter a second Ember.Select. Is this the right way? How can I make this jsbin work?
Thanks on any input.

The problem is that you’re trying to define an observer in Ember.Object.create when it’s normally done while defining a class with Ember.Object.extend. Either you should define a class like this:
var TmpDocument = Ember.Object.extend({
and then instantiate your tmpDocument instance from it, or you need to add the observer to the document after it’s created like I did in this working modification of your JS Bin:
tmpDocument.addObserver('Category', function() {
console.log('Category changed to: ' + this.Category);
});

Related

Set value properties into general view

Is possible to link data by handlebars to Ember.view ? Like Ember.Select with valueBindig.
My attempt:
{{#each models}}
<li>{{id}}</li>
{{/each}}
// -> 1,2,3 (this works fine)
{{view App.TimelineView valueBinding="models.id"}} //this doesn't work
App.TimelineView = Ember.View.extend({
tagName: 'div',
attributeBindings: ["value"],
value: null,
didInsertElement: function(){
console.log(this.value)
...
})
Console log: ---> null
but I need [1,2,3]
Supposing models is an array, models.id is accessing the id property of the array itself, not of each element. What you probably want is to map models to ids.
Define the following property on the controller which models is defined on:
modelIds: Ember.computed.mapBy('models', 'id')
And then use modelIds in the template instead of models.id.
Also, you should access properties this.get-style, not directly (in didInsertElement for example).
You are not accessing value correctly. The right way of doing that is as below. There were some more errors in there that I fixed.
App.TimelineView = Ember.View.extend({
tagName: 'div',
didInsertElement: function() {
console.log(this.get('value'));
},
)};
{{#each models}}
{{view App.TimelineView valueBinding="id"}}
{{/each}}
The context within the loop is this, which represents the current element of the iteration, so you want 'this.id' or simply 'id'.
Addition: If you want to get a list of all the ids at once as an array (as opposed to item-by-item), you can do the following:
App.MyController = Ember.ArrayController.extend({
allIds: function() {
var ids = this.get('content').getEach('id').reduce(function(accum, item) {
return accum.push(item);
}, []);
return ids;
}.property('content.#each.id');
});

Ember, how to copy value/label of selected item of Ember.Select to next TextField?

I have a simple construction Ember.Select holding some predefined strings and Ember.TextField next to it:
{{view Ember.Select
prompt="Choose..."
contentBinding="predefinedLabels"
optionValuePath="content.value"
optionLabelPath="content.label"
}}
{{view Ember.TextField valueBinding='view.newLabel' action='saveNewLabel'}}
I need to on select change get it's current value (if not prompt) and set it to the TextField and then reset the select to the prompt state - sounds trivial, but I have no idea how to do that :( I tried by using selectionBinding but problem is that these sets Select+ TextField are created dynamically. with common jQuery I would do it with common change listener added to the all select tags with some specified class, what is equivalent of the similar behavior in the Ember? (or what is proper way to do what I need?
You can still do just that with jQuery. Just put the relevant code (attaching change listeners) into the didInsertElement method of your current view.
See this working jsFiddle.
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
var self = this;
this.$('select').change(function() {
self.$('input').val(self.$('option:selected').text());
self.$("option:selected").removeAttr("selected");
});
}
});
Do I understand you correctly?
Is this the behaviour you are looking for: http://jsbin.com/aqaYEgo/3/edit
Basically:
App.ApplicationController = Ember.ObjectController.extend({
selectedLabel: null,
actions: {
saveNewLabel: function() {
console.log('Saving label: ' + this.get('selectedLabel.value'));
}
}
});
{{view Ember.Select
prompt="Choose..."
contentBinding="model"
optionValuePath="content.value"
optionLabelPath="content.label"
selectionBinding="selectedLabel"
}}
{{view Ember.TextField valueBinding="controller.selectedLabel.value" action='saveNewLabel'}}
Hope it helps.

Ember TextField valueBinding with dynamic property

I'm trying to write a generic view that handles custom fields in my app, but I'm having a hard time getting this to work. Here's the scenario - I have a fieldDef object which defines the custom fields, and a valueObject which has an array, customFields, which has the values. What I'm trying to do is something like this:
{{view Ember.TextField valueBinding="valueObject.customFields.[fieldDef.name]"}}
Obviously that doesn't work because it treats fieldDef.name as a literal. I've tried overriding the TextField class, but can't seem to get it to bind.
Any suggestions on how to accomplish this?
Thanks,
Scott
Ember can't bind to an array index, so you'll have to work around it. One solution is to limit yourself to a one-way binding, where your textfield updates the values hash. If you're planning to submit the form after the user presses a button, this should do the trick.
Define an array of field ids in your controller and a hash for their values to go in.
App.ApplicationController = Ember.Controller.extend({
fieldIds: ['name', 'email', 'whatever'],
fieldValues: {} // {name: 'user', email: 'user#...', ...}
});
Now extend Ember.TextField to update your values hash when a text field changes. You'll need to pass each instance a fieldId and a reference to the values hash from your controller.
App.TextField = Ember.TextField.extend({
fieldId: null,
values: null,
valueChange: function() {
var fieldId = this.get('fieldId');
var values = this.get('values');
if (values && fieldId) values[fieldId] = this.get('value');
}.observes('value')
});
The template is simple.
{{#each fieldId in fieldIds}}
<label>{{fieldId}}</label>
{{view App.TextField fieldIdBinding="fieldId" valuesBinding="fieldValues"}}
<br/>
{{/each}}
Here it is fleshed out in a jsfiddle.
#ahmacleod great answer man. Just in case anyone is interested it works great extending select too:
import Ember from 'ember';
export default Ember.Select.extend({
fieldId: null,
values: null,
valueChange: function() {
var fieldId = this.get('fieldId');
var values = this.get('values');
if (values && fieldId) values[fieldId] = this.get('value');
}.observes('value')
});
Call it as an normal component (components/dynamic-select.js)
{{#each id in fieldIds}}
{{dynamic-select content=fieldIds fieldIdBinding="header"
valuesBinding="fields"}}
{{/each}}
You can bind input values with dynamic keys(variables) of objects with help of mut helper now.
https://guides.emberjs.com/v2.6.0/templates/input-helpers/#toc_binding-dynamic-attribute
You can access it like this,
var Object = {};
var key = "firstName";
We can bind key in input helper like this,
{{input value=(mut (get Object key))}}
{{Object.firstName}} // Prints the dynamic value in textbox

What's the best idiom for creating an EmberJS view that can show all its child-views, or just one?

Say I have a model App.Page, an ArrayController of App.Page, and an App.PageView to render each App.Page.
I'm trying to figure out how to best implement App.MyPagesView so it works like so:
if App.showAllPages is true: I want MyPagesView to contain an App.PageView(s) for displaying each of the App.Page in App.pages
Else: I want MyPagesView only show one App.PageView, bound to App.pages.currentPage.
The most straightforward implementation that occurs to me is using a template like so:
// MyPagesViewApproach1
{{#unless App.showAllPages}}
{{view App.PageView pageBinding="pages.currentPage"}}
{{else}}
{{#each pages}}
{{view App.PageView pageBinding="this"}}
{{/each}}
{{/unless}}
But won't this create new views for the existing models every time the user toggles showAllPages on and off? Also, I get emberJS warnings about performance issues when I try to use this template.
The PageView(s) could be quite complex and expensive to render. I'd really like to create a PageView once for each Page, and just remove/hide the irrelevant PageViews from the DOM when they're not in use.
App = Ember.Application.create({
showAllPages: false,
pages: Ember.ArrayController.create({
content: []
currentPage: null
}),
ready: function () {
this.pages.pushObject(App.Page.create({title: 'Page One'});
this.pages.pushObject(App.Page.create({title: 'Some Other Page'});
this.pages.pushObject(App.Page.create({title: 'Grrreatest Page Evar'});
this.pagesController.set('currentPage',
this.pagesController.get('firstObject'));
}
});
App.Page = Ember.Object.extend({
title: null
// etc, etc...
});
App.PageView = Ember.View.extend({
templateName: 'page',
page: null // should be bound to an App.Page
});
App.MyPagesView_Approach1 = Ember.View.extend({
pagesBinding: 'Elicitation.pages'
// ???
});
App.MyPagesView_Approach2 = Ember.ContainerView.extend({
// ???
});
And my HTML:
<script type="text/x-handlebars" data-template-name="page">
The title of this page is {{ page.title }}
</script>
{{view App.MyPagesView }}
To recap, what's the proper EmberJS-y way to implement MyPagesView so it responds to App.showAllPages without re-creating all the views each time its toggled?
Should it be some sort of ContainerView? Or should I use the unless/else template shown at the top of the question? Or something entirely different? I feel like a really simple solution exists in EmberJS, but its elluding me.
Here's the best I've come up with, encapsulated as a re-usable View class called "CurrentCollectionView". I'm using CollectionView, and using view.set('isVisible') to hide/show appropriate child views. Basically use it like a CollectionView, but you can set currentContent to hide all but one element of content, or use showAllContent to override currentContent.
App.CurrentCollectionView = Ember.CollectionView.extend({
showAllContent: false,
currentContent: null,
currentContentChanged: function () {
console.log("Elicitation.PagesView.currentContentChanged()");
var showAllContent = this.get('showAllContent');
if (Ember.none(showAllContent) || !showAllContent) {
var contents = this.get('content');
var currentContent = this.get('currentContent');
this.get('childViews').forEach(function (view, i) {
var isVisible = contents.objectAt(i) == currentContent;
view.set('isVisible', isVisible);
});
} else {
this.get('childViews').forEach(function (view) {
view.set('isVisible', true);
});
}
}.observes('currentContent', 'showAllContent', 'childViews')
});
An example of using CurrentCollectionView to implement MyPagesView:
App.MyPagesView = App.CurrentCollectionView.extend({
itemViewClass: App.PageView,
contentBinding: 'App.pages',
currentContentBinding: 'App.pages.currentPage',
showAllContentBinding: 'App.showAllPages',
});
or as using it inline as a template:
{{view App.CurrentCollectionView itemViewClass="App.PageView" contentBinding="App.pages" currentContentBinding="App.pages.currentPage" showAllContentBinding="App.showAllPages"}}
Hope somebody else finds this useful and/or can improve on it (please!)

Binding value from textfield when pressing button

I have the following example (see below) to work with Ember.js, and everything works alright as far as I enter something in the textfield and press enter. But how can I have the same result when I press the button? How can I bind the value of the textfield when clicking the button? Do I have work with a view?
Thanks in advance!
<script type="text/x-handlebars">
{{view App.TextField}}
{{#view Ember.Button target="App.peopleController" action="addPerson"}}
Add Person
{{/view}}
<ul id='todo-list'>
{{#each App.peopleController }}
<li>{{name}}</li>
{{/each}}
</ul>
</script>
<script>
App = Em.Application.create();
App.peopleController = Em.ArrayController.create({
content: [{name: "Tom"}, {name: "Mike"}],
addPerson: function(name) {
this.unshiftObject(App.Person.create({name: name}));
}
});
App.Person = Em.Object.extend({
name: null
});
App.TextField = Em.TextField.extend({
insertNewline: function() {
App.peopleController.addPerson(this.get("value"));
this.set("value", "");
}
});
</script>
This is actually a little tricky to accomplish, but I've reworked your example in a jsFiddle: http://jsfiddle.net/ebryn/vdmrA/
I would advise against hardcoding references to controllers in your view subclasses. You can't reuse those view components elsewhere if you do that.
I think in your code for the Ember Button is just going to call the addPerson function without giving it the name parameter that it expects. You might have to write a separate view for that button in order to get the value of the input field to pass to the addPerson function.
App.MyButton = Em.Button.extend({
click: function(){
var value = App.TextField.get('value')
// etc
}