full object binding with ember select view - ember.js

So I'm building a component tied to a hasMany relation on an ember data model. I have a search modal that I'm popping up, and I just want to take the result of the search and use addObject to add the model to the relationship.
export default Ember.Controller.extend({
// relation is set when the controller is set up
actions: {
pushSelection(selection) {
this.get('relation').addObject(selection);
}
}
});
So If I do the most basic form of an ember select, this works fine:
{{view "select"
content=results
value=selection
class="form-control"}}
<button type="button" {{action pushSelection selection}}>Make Selection</button>
The above works, but the select is full of ember internal instance names and not at all acceptable.
If I set optionLabelPath, it immediately stops working and selection int he action is undefined. I have tried, I think, every possible combination of value, objectLabelPath, optionValuePath and selection.
//reference selection by ID
{{view "select"
content=results
value=selection.id
optionValuePath='content.id'
optionLabelPath='content.text'
class="form-control"}}
//try just binding to whatever object
{{view "select"
content=results
value=selection
optionValuePath='content.id'
optionLabelPath='content.text'
class="form-control"}}
//don't bind the value of the option to the id
{{view "select"
content=results
value=selection
optionLabelPath='content.text'
class="form-control"}}
//trying with the selection option instead
{{view "select"
content=results
selection=selection
optionValuePath='content.id'
optionLabelPath='content.text'
class="form-control"}}
I've done this sort of thing with the select2 addon before, which is nice, but this is for an addon and I don't really want to add that dependency if I can avoid id.

Related

How to set specific view in emberJS with itemcontroller

I have the following code:
{{#each categories.items itemController="item"}}
When I open the Ember inspector, it shows the view to be "virtual". I want to set the view to be "item" so that it follows the ember view I set out called itemView. I know we can set an itemController: is it possible to set an item view?
Yes it is possible using the optional 'itemViewClass' parameter.
{{#each categories.items itemController="item" itemViewClass="otherView"}}
Though I would recommend discontinuing using that and itemController as the latest Best Practice is to use a component within the each block.
So for your example:
// Ember 1.10+
{{#each categories.items as |item|}}
{{some-component item=item}}
{{/each}}
// Ember 1.9-
{{#each item in categories.items}}
{{some-component item=item}}
{{/each}}
Then you put your logic needed in the component object instead of the item controller.

Ember.checkbox nested in action doesn't change value

Template:
{{#each document in documents}}
<div class="col-md-6" {{action "selectDocument" document}}>{{view Ember.Checkbox checked=document.isSelected}} {{document.name}}</div>
{{/each}}
Controller:
App.IndexDocumentsController = Ember.ArrayController.extend({
actions: {
selectDocument: function(document){
document.set('isSelected', !document.get('isSelected'));
}
}
});
When I click on the div, the checkbox toggles 'checked' property. But when I click on the ckeckbox - nothing happens. What can be the reason?
UPDATED
Link to jsbin: http://emberjs.jsbin.com/nuvocumuteto/1/edit?html,css,js,output
The issue is that when you click on the checkbox 2 things happen.
the checkbox toggles the isActive property, then
the selectRow action is ran which again toggles the isActive property
So the isActive property ends up staying in the same state that it was.
In your case I would get rid of the action, wrap the checkbox in a <label> and set the label to display: block.
The template would look like
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li {{bind-attr class="item.isActive:active"}}><label>{{input type="checkbox" checked=item.isActive}}{{item.name}}</label></li>
{{/each}}
</ul>
</script>
and the css would look like
label {
display: block;
}
you would then be able to get rid of the selectRow action completely because clicking on the label will trigger the checkbox.
You can see a working bin here: http://emberjs.jsbin.com/nuvocumuteto/3/edit
I would argue that you are not following "The Ember Way" in two different ways here.
First, Ember.Checkbox is an internal Ember class (http://emberjs.com/api/classes/Ember.Checkbox.html). The recommended way to render a checkbox is to use the Handlebars input helpers (http://emberjs.com/guides/templates/input-helpers/#toc_checkboxes). This is just wrapping Ember.Checkbox anyway.
Second, if you want to update the value of isSelected, the "Ember Way" is to use two-way data bindings. Your code uses one-way data-binding when it reads document.isSelected and then tries to manually re-create the the data-binding in the other direction when the user clicks by manually writing a selectDocument action and calling it from an {{action}}.
Instead, simply bind the Ember Handlebars Input Helper directly to your value like this:
{{#each document in documents}}
<div class="col-md-6">{{input type="checkbox" checked=document.isSelected}} {{document.name}}</div>
{{/each}}

How can I filter Ember.Select content, instead of using {{each}} {{if}} handlebars filtering?

Using the select method with handlebars each and if helpers I can successfully display an array where only those with active = true.
<select>
{{#each content.users}}
{{#if active}}
<option value="">{{firstName}}</option>
{{/if}}
{{/each}}
</select>
vs
{{view Ember.Select class='btn btn-default dropdown-toggle' style='max-width: 200px'
content=content.users
optionValuePath='content.id'
optionLabelPath='content.firstName'
selectionBinding='someSelectionBinding'}}
The downside to the select way is I can't bind an action to the option value, and I lose out on some of the useful Ember Selection binding and label functionality/observers. Where the downside to the Ember.Select way is I can't set my content to only those users who have the active flag.
I'm assuming you're using a recent version of Ember. In your controller, assuming an Ember.ArrayController with content.users defined as you seem to have it, you'll want to create a new attribute, like activeUsers:
App.ThisController = Em.ArrayController.extend({
// Other stuff
activeUsers = Em.computed.filterBy('users', 'active', true)
})
This should give you an array of users where the active attribute is true, so you can set the content of your Ember.Select view to content.activeUsers.

How to set value for input field and have it update on submission to Ember.js action?

On the handlebars template, I'd like to pre-populate an input field with the value. That I can do by putting value=object.property. Then the user should update the value, and when they click the button activating the action, the value should submit to the Component.
The problem is that no value is getting submitted, not the pre-populated value, or the new value. When I console.log what is getting submitted to the component, the input from the text field is "undefined" and the input from the number field is "NaN".
This is my handlebars template:
{{input type="text" value=object.name valueBinding='newName'}}
{{view App.NumberField min='1' value=object.count valueBinding='newCount'}}
<button {{action updateObjectDetails object}}>Save</button>
The related component it is submitting to:
App.ObjectsDetailsComponent = Ember.Component.extend({
actions: {
updateObjectDetails: function(object){
object.set("name", this.get('newName'))
object.set("party_size", parseInt(this.get('newCount')))
object.save();
}
}
});
Is there a way to populate the input field with the correct value AND have it submit with the action?
Ah, got it. The thing is to not try to use the valueBindings, like you might when creating a new object, but to use the actual value, because the actual value is changing. So in the component, it's object.get('name'), not this.get('newName').
Therefore the handlebars should be like this:
{{input type="text" value=object.name}}
{{view App.NumberField min='1' value=object.count}}
<button {{action updateObjectDetails object}}>Save</button>
And the component like this:
App.ObjectsDetailsComponent = Ember.Component.extend({
actions: {
updateObjectDetails: function(object){
object.set("name", object.get('name'))
object.set("party_size", parseInt(object.get('count')))
object.save();
}
}
});

getting back reference to a specific model using Ember's Array Controller

I'm new to Ember and am finding some of their concepts a bit opaque. I have a app that manages inventory for a company. There is a screen that lists the entirety of their inventory and allows them to edit each inventory item. The text fields are disabled by default and I want to have an 'edit item' button that will set disabled / true to disabled / false. I have created the following which renders out correctly:
Inv.InventoryitemsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON("/arc/v1/api/inventory_items/" + params.location_id);
}
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" data-id=id}}>edit item</button>
<button {{action "saveInventoryItem" data-id=id}}>save item</button>
</div>
{{/each}}
</script>
So this renders in the UI fine but I am not sure how to access the specific model to change the text input from disabled/true to disabled/false. If I were just doing this as normal jQuery, I would add the id value of that specific model and place an id in the text input so that I could set the textfield. Based upon reading through docs, it seems like I would want a controller - would I want an ArrayController for this model instance or could Ember figure that out on its own?
I'm thinking I want to do something like the following but alerting the id give me undefined:
Inv.InventoryitemsController=Ember.ArrayController.extend({
isEditing: false,
actions: {
editInventoryItem: function(){
var model = this.get('model');
/*
^^^^
should this be a reference to that specific instance of a single model or the list of models provided by the InventoryitemsRoute
*/
alert('you want to edit this:' + model.id); // <-undefined
}
}
});
In the Ember docs, they use a playlist example (here: http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) like this:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
But this example is a bit confusing (for a couple of reasons) but in this particular case - how would I map their concept of playlist to me trying to edit a single inventory item?
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" this}}>edit item</button>
<button {{action "saveInventoryItem" this}}>save item</button>
</div>
{{/each}}
</script>
and
actions: {
editInventoryItem: function(object){
alert('you want to edit this:' + object.id);
}
}
Is what you need. But let me explain in a bit more detail:
First of all, terminology: Your "model" is the entire object tied to your controller. When you call this.get('model') on an action within an array controller, you will receive the entire model, in this case an array of inventory items.
The {{#each}} handlebars tag iterates through a selected array (by default it uses your entire model as the selected array). While within the {{#each}} block helper, you can reference the specific object you are currently on by saying this. You could also name the iteration object instead of relying on a this declaration by typing {{#each thing in model}}, within which each object would be referenced as thing.
Lastly, your actions are capable of taking inputs. You can declare these inputs simply by giving the variable name after the action name. Above, I demonstrated this with {{action "saveInventoryItem" this}} which will pass this to the action saveInventoryItem. You also need to add an input parameter to that action in order for it to be accepted.
Ok, that's because as you said, you're just starting with Ember. I would probably do this:
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled=headerEnabled}}</p>
<p>{{input type="text" value=detail disabled=detailEnabled}}</p>
<button {{action "editInventoryItem"}}>edit item</button>
<button {{action "saveInventoryItem"}}>save item</button>
</div>
{{/each}}
</script>
with this, you need to define a headerEnabled property in the InventoryitemController(Note that it is singular, not the one that contains all the items), and the same for detailEnabled, and the actions, you can define them also either in the same controller or in the route:
App.InventoryitemController = Ember.ObjectController.extend({
headerEnabled: false,
detailEnabled: false,
actions: {
editInventoryItem: function() {
this.set('headerEnabled', true);
this.set('detailEnabled', true);
}
}
});
that's just an example how you can access the data, in case the same property will enable both text fields, then you only need one, instead of the two that I put . In case the 'each' loop doesn't pick up the right controller, just specify itemController.