5

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');
    }
}
Magnus
  • 63
  • 6
  • I've had some of the same types of issues with DataTables and ended up writing my own. Aurelia's binding is amazing, but I haven't yet learned the way to fire an event when updates to the DOM have completed. That's where you want to direct your attention -- how to run the component's `rebuild` method at the correct moment. – LStarky Jan 23 '17 at 12:24

1 Answers1

1

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!

Fabio
  • 11,892
  • 1
  • 25
  • 41
  • Thank you for your suggestion. Sorry for not replying to you until now. That is very rude of me when you have had the trouble to help me. I was working on other parts of the project until now so forgot about this since it was kind of working.I thought bindign was two way by default= Did change the binding in the template on the select to be value.two-way. I will try your code which is cleaner. Doesn't work with selecting the selected values in code but will try to get that working. – Magnus Mar 06 '17 at 10:11