Access a specific index of Ember.ArrayProxy in Handlebars - ember.js

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.

Related

Add local controller property to store models

I want to add a simple 'selected' property to store objects, just localised to the particular route/controller.
So in my controller I'm loading the 'groups' from the store. For each 'group' I want to add in a 'selected' property. Tried a few different approaches but just can't get it working.
Advice on "the ember way" would be much appreciated.
If what you're trying to do is display something different for each group that isSelected. I'd take the following approach:
I'd create an array on your controller of selectedGroups, which can either be an array of group objects from the store or just simply an array of ids. As a group is selected/unselected, it can be added to/removed from the array. You can then use a computed function or pass selectedGroups into components to do whatever you need with selectedGroups.
So, if you had your groups template like so:
{{#each model as |group|}}
{{my-item-component item=group selectedItems=selectedGroups}}
{{/each}}
Then in your my-item-component.js, you would have a computed function like:
isSelected: computed('item', 'selectedItems.[]', function () {
// This assumes selectedItems contains item/group objects rather
// than ids. You will need to tweak a little for ids.
return (this.get('selectedItems').indexOf(this.get('item')) !== -1);
})

Binding to an array or object subscript

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.

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.

Pass array to linkTo helper

I want to create a component which needs to generate dynamics links. I tried passing the link data as an array, but this does not work.
var user1 = get("store").find("user", 1);
var data = {link: ["users.show", user1], title: "User1"};
{{#link-to data.link}}{{data.title}}{{/link-to}}
This should be equal to
{{#link-to "users.show" 1}}{{data.title}}{{/link-to}}
How to generate fully dynamic links from a variable?
You can specify an array as params argument into a link-to helper. Similar to nickiaconis' answer answer, but with just the default {{link-to}} helper:
{{#link-to params=data.link}}{{data.title}}{{/link-to}}
...will render something like:
User1
(tested with Ember 2.3.0)
Ember 1.13.x
The LinkComponent, which is what the link-to helper creates for you, is exposed as -link-to. I've created an example of its use here: http://emberjs.jsbin.com/rinukefuqe/2/edit?html,js,output
{{#-link-to params=(unbound link) hasBlock="true"}}
{{title}}
{{/-link-to}}
The params attribute is what the link-to helper normally bundles your positional parameters onto, although you must use the unbound helper here because the LinkComponent expects params to be an array rather than a value binding object. Additionally, the determination of use as block or inline component is not built into components yet, so you must pass hasBlock="true" unless you include the link text as the first parameter in your array.
Ember ≤ 1.12.x
Although it is not done already, you can manually expose the LinkView component, which is the equivalent of the new LinkComponent.
App.XLinkToComponent = Ember.LinkView.extend();
Then use it like:
{{#x-link-to params=link}}
{{title}}
{{/x-link-to}}
Using unbound and hasBlock="true" are not necessary as the internal logic of LinkView differs from LinkComponent.
I think that isn't possible to pass an array, but you can pass each argument directlly, like the following:
route
var user1 = this.store.find('user', 1);
var data = { data: { link: "users.show", model: user1, title: "User1" } };
return data;
template
{{#link-to data.link data.model.id}}{{data.title}}{{/link-to}}
I hope it helps

Reversing a content array

I'm storing a list of search terms in my ArrayController. I'd like the search terms to be displayed newest to oldest. By default Ember outputs them in order.
You can see my current implementation here: http://andymatthews.net/code/emberTweets/
And here's the pertinent code.
{{#each App.recentUsersArray.reverse}}
<li>
<a href="#" title="view again" {{action "searchAgain" target="App.recentUsersArray"}}>{{this}}</a>
</li>
{{/each}}
App.recentUsersArray = Em.ArrayController.create({
content: [],
reverse: function(){
return this.content.reverse();
}.property(),
});
You can see that I'm trying to reverse it using a property() method but it's not working. Am I doing something wrong?
You should always use get and set to access properties. Also if a computed property depends on other ones, you have to add these in the property declaration. The use of cacheable can be omitted in the next release of ember, see discussion. You can see a working example here.
reverse: function(){
return this.get('content').toArray().reverse();
}.property('content.#each').cacheable()
You could also use the unshiftObject method on an array and hereby circumvent creating a computed property, see http://jsfiddle.net/ez7bV/.