Binding to an array or object subscript - ember.js

I'm wanting to do something that I suspect is quite easy but for some reason can't figure out how to get my head around it:
{{#each item in myArray}}
{{ui-input value=storeMe[#index]}}
{{/each}}
In the above case I'd be iterating through a numeric array of things and I want to store values of a UI component using the same numeric index.
Similarly it would be nice to be able to do the following:
{{#each item in myArrayOfObjects}}
{{ui-input value=storeMe[item.id]}}
{{/each}}
Where the storage device -- storeMe -- is a dictionary whose keys are determined by the id property of each item in array of objects.
In my particular use-case, I am asking the user to input a strategy for measuring body fat. I use a select box for that:
<div class="clearfix downer">
{{x-selectize
options=measurementStrategies
labelField="name"
placeholder="measurement strategy"
valueObject=measurementPoints
}}
</div>
By binding to the "valueObject" above I get back a simple array of measurement points which are relevant for the user chosen strategy (e.g., chest, thigh, lower back, etc.). I then iterate through these measurement points and want to have a value stored for each one:
<div class="downer">
{{#each point in measurementPoints}}
<div class="clearfix">
{{ui-number-input value=model.measurements[point]}}
</div>
{{/each}}
This doesn't work, of course, because apparently I can't bind to an offset property (aka, measurements[point]).
In many cases this type of problem doesn't matter because if I want to manipulate the structure I'm iterating over then the each loop provides the indirection. The problem comes when the storage property is hanging off of a different base than that which you are iterating over. So in my case, if I were actually manipulating point or a property hanging off of point this would be easy because point is an offset of measurementPoints but in my case I'm iterating measurementPoints and saving values to model.measurements.

That’s too much logic for Handlebars. You could create a computed property instead that pairs each item with each value in storeMe, so you can access them as pairs.
You could probably accomplish this with a custom helper, but it seems like a mess to me.
Here’s a rudimentary example, lacking detail on your problem domain:
storeMeItemsByIndex: function() {
var storeMe = this.get('storeMe');
return this.get('myArray').map(function(item, index) {
return storeMe[index];
});
}.property('myArray', 'storeMe'),
storeMeItemsById: function() {
var storeMe = this.get('storeMe');
return this.get('myArray').map(function(item) {
return storeMe[item.id];
});
}
Those would fit your example template code. However, you wouldn’t have access to both the value from storeMe and the item at the same time. If you want that, you could just construct object pairs:
storeMeItemsByIndex: function() {
var storeMe = this.get('storeMe');
return this.get('myArray').map(function(item, index) {
return {item: item, value: storeMe[index]};
});
}.property('myArray', 'storeMe'),
Or something like that.
With your more specific example, you could do something like this:
measurementPointsWithValues: function() {
var measurements = this.get('model.measurements');
return this.get('measurementPoints').map(function(point, index) {
return {point: point, value: measurements.itemAt(index)};
};
}
Then you’d use it in your template like this:
{{#each pointAndValue in measurementPointsWithValues}}
{{! some use of pointAndValue.point, probably}}
{{ui-number-input value=pointAndValue.value}}
{{/each}}
It’s clunky, but it works. I don’t know the details of your object model, but you may benefit from some intermediate objects.

Related

Access a specific index of Ember.ArrayProxy in Handlebars

I have this code in my handlebars template:
{{#each maps as |map|}}
{{map.title}}
{{/each}}
Which gives me the title of every map.
But if I try to access a specific index, like this:
<p>{{maps.[0].title}}</p>
Or
<p>{{maps.Get.[0].title}}</p>
It gives me nothing (but it compiles).
the 'maps' variable is of type:
Ember.ArrayProxy
So how can I access the map located at index 0?
I see three good options:
Use the firstObject property:
<p>{{maps.firstObject}}</p>
Use the get helper:
<p>{{get maps '0'}}</p>
Use a computed property in your controller:
firstMap: Ember.computed('maps.[]', function() {
return this.get('maps')[0];
})
Notice that the second two allow you to choose any item in the array, not just the first.

Get the value of another select control in Ember

My situation is I have a table where the user can add suppliers, and edit any existing ones (so there are potentially multiple records). Three of the fields (Type, Name, Version) come from a lookup object returned by the API (which is one table in the backend database).
Before clicking 'edit'
In edit mode
The thing is, I need these select elements to be "chained" (or cascading), but since they're populated from the same object, it's more like the selection of "Type" will filter the options available for "Name" and likewise selecting Name will further restrict the options available for Version.
However, since this is just one record being edited, these selects are in an {{#each supplier in suppliers}} block to generate the rows, and show selects if that record's isEditing property is true, so the value or selection is the per-record value, e.g. supplier.type and not a single property on the whole controller.
I've tried to come up with multiple ways to do this, but so far haven't found a solution to cascading dropdowns with multiple records since that means the value of any one select is dependent on the record.
I think I could get the option filtering to work if I knew how to reference the value of say the Type dropdown from within the controller, but then again it's conceivable that two records could be in edit mode at once, so modifying any property on the controller to populate the selects would affect the others too, and that's not good. I just really wanted to figure this out so I didn't have to pop up a modal dialog to edit the record.
You should use components to handle each row seperately.
Let's say that you have something like this:
{{#each suppliers as |supplier|}}
// .. a lot of if's, selects and others
{{/each}}
If you find yourself using {{#each}} helper and your block passed to that helper is more than one line, than it's a good sign you probably need a component there.
If you create a component named, let's say, SupplierRow you could make it as follow:
module export Ember.Component({
editing: Ember.computed.alias('model.isEditing'),
types: Ember.computed('passedTypes', function() {
// .. return types array for that exact supplier
}),
names: Ember.computed('passedNames', 'model.type', function() {
// .. return names array for that exact supplier based on possibleNames and model.type
}),
versions: Ember.computed('passedVersions', 'model.type', 'model.name', function() {
// .. return versions array for that exact supplier based on possibleVersions and model.type and model.name
}),
actions: {
saveClicked() {
this.sendAction('save', this.get('model'));
}
}
});
The template would basically look similiary to what you have currently in your {{#each}} helper. It would be rendered something like this:
{{#each suppliers as |supplier|}}
{{supplier-row model=supplier possibleTypes=types possibleNames=names possibleVersions=versions save="save"}}
{{/each}}
Seems like you are using an old version of Ember wich allows context switching in {{#each}} helpers. Assuming that, you can set itemController for each iteration and handle selectable values for each row separately:
{{#each suppliers itemController="supplierController"}}
// this == supplierController, this.model == supplier
{{/each}}
So inside the supplierController you can calculate select content for each single supplier. You can also access main controller from item controller by this.parentController property.

Input type='checkbox' two way binding doesn't work?

Just started learning ember.js, and currently working on the TODO MVC from their guide. I'm currently at this step: http://emberjs.com/guides/getting-started/show-when-all-todos-are-complete/
I noticed if I hook the "checked" property to a computed property, the two-way binding doesn't work as I would've expected. That computed property won't update the value if I check/uncheck the checkbox manually.
Here is the simplified example (as if their examples aren't simple enough):
Here is the handlebar code. I only added '{{allAreDone}}' element just to be able to see the value real time:
//...
<section id="main">
{{outlet}}
{{input type="checkbox" id="toggle-all" checked=allAreDone}}
{{allAreDone}}
</section>
//...
Here is the js snippet for the controller, but I simplified it such that it's not based on another property:
// ...
allAreDone: function(key, value) {
return false
}.property()
// ...
Now, if I check the box (i.e. property 'checked' = true), 'allAreDone' will still show false. The result will be different if the input type is text. Two way binding checkbox will also work if it's linked to a non-computed property such as:
// ...
allAreDone: false
//will return false if I uncheked the checkbox directly and vice versa
// ...
I just want to confirm my understanding of the behavior is correct. And why would it be different from type 'text'.
Thanks!
Your computed property definition is read-only. There’s no way to set the value, only get it. Adapting the example from the guides to your situation:
allAreDone: function(key, value) {
// setter
if (arguments.length > 1) {
this.set('_allAreDone', value);
}
// getter
return this.get('_allAreDone');
}.property('_allAreDone'),
_allAreDone: false
This uses an internal property to store the actual value, but something more problem-specific surely applies.

Binding several attributes to the same element

I am trying to do something like this:
<a href="#/{{unbound goto}}" {{bind-attr class=":menu-entry-text :nowrap active:selected-menu-entry-text"}} {{bind-attr id="active:active-nav:inactive-nav"}} {{bind-attr data-goto="goto"}}>
But only the first attribute (class) gets set: id and data-goto are not defined. Is it possible to set several attributes with bind-attr in the same element? How?
Yes you can bind several attributes at once by either using multiple bind-attr helpers like in your example, or just putting all the attributes in a single bind-attr helper. You have an issue in your example though, which is why things aren't working as expected.
The "value if true" form that you're using for the class attribute cannot be applied to other types of attributes. All other attributes must be bound to a dynamic property on the controller. For example, if you had a controller that looked like this:
App.MyController = Ember.ObjectController.extend({
myId: function() {
if (this.get("active") === true) {
"active-nav"
} else {
"inactive-nav"
}
}.property("active")
});
Then you would bind that like so:
<a href="#/{{unbound goto}}" {{bind-attr id="myID"}}>
Just a side note on that example, it's probably a code smell if you have a dynamic ID for an HTML element. IDs are supposed to uniquely identify a single element and I wouldn't expect them to change. It seems like a class would be a better fit for this use case.

Ember.js: Dynamic width based on model attribute value

First-time Ember user here. In the app, my model objects are each represented by a rectangular-shaped <div> element. The width of each div is determined by its model's size property. The catch is that the possible values for Model.size are 1-10, not simply pixel values. The div's width is then calculated based on the size. For example, a size of 1 might equal a width of 100px, and a size of 2 would equal 200px, and so on. Thus, these CSS width values need to be calculated and bound to the template. Being new to Ember, I don't yet know where this logic should live. Helper? Controller? Because it's really just presentation logic, it doesn't seem right to have it in the model.
<script type="text/x-handlebars" id="things">
{{#each model}}
<div>
{{description}}
</div>
{{/each}}
</script>
Also, will binding it to the template allow the calculated width to be updated automatically in the template whenever the Model.size value is changed (say, from 1 to 3, thus the div would grow wider)?
While it is a good idea to keep presentation and logic separate, sometimes they need to be mixed. I've certainly had use cases. I used this helper when I had a similar issue. Assuming you had this property in your model:
divWidth: function() {
return this.get('size') * 100;
}.property('size')
You could use this template (which is bound to the property value):
<script type="text/x-handlebars" id="things">
{{#each model}}
<div {{bindStyle width="divWidth" width-unit="px"}}>
{{description}}
</div>
{{/each}}
</script>
I don't think this is the right way to do this, but you should be able to do it this way. You can add a function to your controller (or, I believe your model as well) that listens to the size property for changes, and then calls a jQuery function.
i.e.
changeSize: function() {
return $('#things').css( 'width', this.get('size') * 100);
}.property('size')
Also, you could create a computed property in your model the calculates the size for you:
divSize: function() {
return this.get('size') * 100;
}.property('size')
And you'd reference the divSize in changeSize function, which would be useful if your conversion was more complicated than just multiplying by 100. The helper in the other answer looks useful and more Ember-esque, but here's another way it could be done.