Search code examples
asp.net-mvcknockout.jsbootstrap-duallistbox

bootstrap Dual Listbox knockout binding


I am using bootstrap dual listbox pluging in my ASP.NET MVC project.
I am also using Knockout in the project. I am trying to create bindingHandler for the this plugin to make it working smoothly with knockout.

here is what I tried

Binding Handler

ko.bindingHandlers.dualList = {
    init: function (element, valueAccessor) {
        $(element).bootstrapDualListbox({
            selectorMinimalHeight: 160
        });

        $(element).on('change', function () {
            $(element).bootstrapDualListbox('refresh', true);
        });

        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).bootstrapDualListbox('destroy');
        });
    },
    update: function (element, valueAccessor) {
        $(element).bootstrapDualListbox('refresh', true);
    }
}

HTML

<select class="form-control" data-bind="foreach:{data: EditedElement().Operations, as : 'op'} , dualList: EditedElement().Operations" multiple>
    <option data-bind="value: op.OperationID(), text: op.Name(), selected: op.IsSelected()"></option>
</select>

View Model

function SelectOperationVM(operationId, isSelected, name) {
    var self = this;

    self.OperationID = ko.observable(operationId);
    self.IsSelected = ko.observable(isSelected);
    self.Name = ko.observable(name);

    self.copy = function () {
        return new SelectOperationVM(self.OperationID(), self.IsSelected(), self.Name());
    }
}

My problem is that I can not make sync between the viewModel observableArray, and the plugin. In other words, I want the changes in the plugin (the user removed some options or added some options) to be reflected on the view model property, and vice verse


Solution

  • to sync, you need to pass multiple observables to custom binding

    so your html should be like

    <select class="form-control" data-bind="foreach: { data: Operations, as: 'op'}, dualList: { options: Operations, selected: Selected }" multiple>
        <option data-bind="value: op.OperationID(), text: op.Name(), selected: op.IsSelected()"></option>
    </select>
    

    and custom binding be like

    ko.bindingHandlers.dualList = {
      init: function(element, valueAccessor) {
        var data = ko.utils.unwrapObservable(valueAccessor());
        $(element).bootstrapDualListbox({
          selectorMinimalHeight: 160
        });
    
        $(element).on('change', function() {
          // save selected to an observable  
          data.selected($(element).val());;
        });
    
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
          $(element).bootstrapDualListbox('destroy');
        });
      },
      update: function(element, valueAccessor) {
        // to view if there is an update (without this "update" will not fire)
        var options = ko.utils.unwrapObservable(valueAccessor()).options();
        $(element).bootstrapDualListbox('refresh', true);
      }
    }
    

    also i have created a dirty working jsfiddle