I am trying to get bootstrap-multiselect to work with Aurelia. have got it working more or less but not sure it is the best solution or if I might run into trouble.
Bootstrap-multiselect is a jquery plugin that turns a normal select (multi) into a drop down with checkboxes (http://davidstutz.github.io/bootstrap-multiselect/)
My first problem is to get it working with dynamically created options. I solved that by using the plugins "rebuild" feature when my array of options (created as a bindable property) changes. However the options of the original select hhas then not yet been created so I use setTimeout to delay rebuilding so Aurelia have rebuilt the select. Feels like a "dirty" solution and I know to little about the Aurelia lifecyle to be sure it will always work.
Second problem is that value for component will not be updated, however the change method will fire. I solved this by firing off a change event (found an example for some other plugin that do the same). Works fine, value wiill be updated but the change method will fire twice. Not a big problem but might be a problem if a change does some time consuming work (like getting data from a database etc).
Any suggestions to improve code ?
<template>
<select value.bind="value" multiple="multiple">
<option repeat.for="option of options"Value.bind="option.value">${option.label}</option>
</select>
</template>
import {customElement, bindable, inject} from 'aurelia-framework';
import 'jquery';
import 'bootstrap';
import 'davidstutz/bootstrap-multiselect';
@inject(Element)
export class MultiSelect {
@bindable value: any;
@bindable options: {};
@bindable config: {};
constructor(private element) {
this.element = element;
}
optionsChanged(newVal: any, oldVal: any) {
setTimeout(this.rebuild, 0);
}
attached() {
var selElement = $(this.element).find('select');
selElement.multiselect(
{
includeSelectAllOption: true,
selectAllText: "(All)",
selectAllNumber: false,
numberDisplayed: 1,
buttonWidth: "100%"
})
.on('change', (event) => {
if (event.originalEvent) { return; }
var notice = new Event('change', { bubbles: true });
selElement[0].dispatchEvent(notice);
});
}
detached() {
$(this.element).find('select').multiselect('destroy');
}
rebuild = () => {
$(this.element).find('select').multiselect('rebuild');
}
}
Your first problem could be solved by pushing the $(this.element).find('select').multiselect('rebuild');
onto the microTaskQueue, inside the optionsChanged()
handler. In this way, Aurelia will fire this event after rendering the new options.
Your second problem is not actually a problem. What is happening is that @bindable
properties are one-way by default. You should declare the value
property as two-way. Then, you should update the value
inside the multiselect.change
event.
Finally, your custom element should be something like this:
import {inject, bindable, bindingMode, TaskQueue} from 'aurelia-framework';
@inject(TaskQueue)
export class MultiselectCustomElement {
@bindable options;
@bindable({ defaultBindingMode: bindingMode.twoWay }) value = [];
constructor(taskQueue) {
this.taskQueue = taskQueue;
}
attached() {
$(this.select).multiselect({
onChange: (option, checked) => {
if (checked) {
this.value.push(option[0].value);
} else {
let index = this.value.indexOf(option[0].value);
this.value.splice(index, 1);
}
}
});
}
optionsChanged(newValue, oldValue) {
if (oldValue) {
this.taskQueue.queueTask(() => {
this.value = [];
$(this.select).multiselect('rebuild');
});
}
}
}
Running example: https://gist.run/?id=60d7435dc1aa66809e4dce68329f4dab
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With