An Ember Multi-select Checkbox
Last week, I needed to create a series of multi-select checkboxes for a project that I’m working on. The client side framework is EmberJS. There were a few open-source options out there. Unfortunately, they use the existing Ember-CLI. Our project, being a bit older and out of date, cannot use Ember CLI.
But, rolling your own isn’t so difficult. Let’s start with what we’re trying to accomplish. We want to turn a list of checked items into an array of strings.
// Our model has a property called "permissions". This property is an
// array of strings.
App.IndexRoute = Ember.Router.extend({
model: function () {
return {
/* snip */
permissions: []
/* snip */
};
}
});
// Our controller has an array of available options. As usual, the `label`
// is displayed on screen. The `value` is the string that is stored on the
// model.
App.IndexController = Ember.Controller.extend({
adminOptions: [{
label: 'Can create users?',
value: 'create_users'
}, {
label: 'Can disable users?',
value: 'disable_users'
}, {
label: 'Can edit users?',
value: 'edit_users'
}],
// This is used to display the selected permissions in the UI. It is
// not required as part of the solution.
selectedAsString: Ember.computed('model.permissions.[]', function () {
return JSON.stringify(this.get('model.permissions'));
})
});
Our component is written in two parts. The first part is the checkbox element itself. The second is the actual component.
// Each available option becomes an instance of a "MultiSelectCheckbox" object.
var MultiSelectCheckbox = Ember.Object.extend({
label: 'label',
value: 'value',
isChecked: false,
changeValue: function () { },
onIsCheckedChanged: Ember.observer('isChecked', function () {
var fn = (this.get('isChecked') === true) ? 'pushObject' : 'removeObject';
this.get('changeValue').call(this, fn, this.get('value'));
})
});
App.MultiSelectCheckboxesComponent = Ember.Component.extend({
labelProperty: 'label',
valueProperty: 'value',
// The list of available options.
options: [],
// The collection of selected options. This should be a property on
// a model. It should be a simple array of strings.
selected: [],
checkboxes: Ember.computed('options', function () {
var _this = this;
var labelProperty = this.get('labelProperty');
var valueProperty = this.get('valueProperty');
var selected = this.get('selected');
return this.get('options').map(function (opt) {
var label = opt[labelProperty];
var value = opt[valueProperty];
var isChecked = selected.contains(value);
return MultiSelectCheckbox.create({
label: label,
value: value,
isChecked: isChecked,
changeValue: function (fn, value) {
_this.get('selected')[fn](value);
}
});
});
})
});
Here is our (very simple) component template.
{{#each checkboxes as |checkbox|}}
<p>
<label>
{{input type='checkbox' checked=checkbox.isChecked}}
{{checkbox.label}}
</label>
</p>
{{/each}}
Finally, to make use of our component, write the following in your template.
{{multi-select-checkboxes options=adminOptions selected=model.permissions}}
That’s really all it takes. A fully working example of this code is available at JSBin.