Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout.JS with bootstrap selectpicker

I'm attempting to use Bootstrap Selectpicker along with knockout.js. There is already a custom binding out there that works for the multiselect version of the selectpicker (seen here), but I need it to work with the single select version. I thought it was going to be as simple as changing the ko.observableArray to a ko.observable and removing the multiple attribute -- but this doesn't seem to be the case. Any ideas on how to get this working?

Fiddle with the binding and my updated code

like image 531
Samsquanch Avatar asked Feb 24 '14 20:02

Samsquanch


People also ask

Does knockout require jQuery?

KO itself doesn't depend on jQuery, but you can certainly use jQuery at the same time, and indeed that's often useful if you want things like animated transitions.

What is two way binding in knockout JS?

KO is able to create a two-way binding if you use value to link a form element to an Observable property, so that the changes between them are exchanged among them. If you refer a simple property on ViewModel, KO will set the form element's initial state to property value.

What are the types of binding supported by knockout JS?

Binding Values The binding value can be a single value, literal, a variable or can be a JavaScript expression.


3 Answers

EDIT See below for alternate solution

the problem is with your selectPicker.init function.

You need to call the options binding, not the value binding. the options.init sets the initial internal state, when this bypassed the options.update function will reset the value.

// regular select and observable so call the default value binding
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor);

change to

// regular select and observable so call the default value binding
ko.bindingHandlers.options.init(element, valueAccessor, allBindingsAccessor);

EDIT

Ok, I took you sample back to basics to use the standard options bindings to get the normal select working.

Then use the selectpicker binding solely to initialise and refresh. It will synchronize to the select on its own.

Prior to Knockout 3, the selectPicker update function would have been invoked if any of the bindings on the element caused an update ( like updating options, value or selectedOptions ). With Knockout 3 the bindings now fire independently ( good thing ), but you now need to use subscriptions to get notified when either the options or value/selectedOptions changes.

I think you will see this is now a lot simpler and there is no distinct between single and multiple select in your custom binding. This now works if either the teamItems or itemID observable is updated.

HTML

<!-- Multiple Select -->
<select data-bind="selectedOptions: teamIDs, 
                   options: teamItems, 
                   optionsText: 'text', 
                   optionsValue : 'id', 
                   selectPicker: {}" multiple="true"></select>

JAVASCRIPT

ko.bindingHandlers.selectPicker = {
  after: ['options'],   /* KO 3.0 feature to ensure binding execution order */
  init: function (element, valueAccessor, allBindingsAccessor) {
     var $element = $(element);
     $element.addClass('selectpicker').selectpicker();

     var doRefresh = function() {
         $element.selectpicker('refresh');
     },  subscriptions = [];

     // KO 3 requires subscriptions instead of relying on this binding's update
     // function firing when any other binding on the element is updated.

     // Add them to a subscription array so we can remove them when KO
     // tears down the element.  Otherwise you will have a resource leak.
     var addSubscription = function(bindingKey) {
         var targetObs = allBindingsAccessor.get(bindingKey);

         if ( targetObs && ko.isObservable(targetObs )) {
            subscriptions.push( targetObs.subscribe(doRefresh) );
         }
     };

     addSubscription('options');
     addSubscription('value');           // Single
     addSubscription('selectedOptions'); // Multiple

     ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
         while( subscriptions.length ) {
             subscriptions.pop().dispose();
         }
     } );
   },
   update: function (element, valueAccessor, allBindingsAccessor) {
   }
 };
like image 87
Robert Slaney Avatar answered Oct 18 '22 11:10

Robert Slaney


Knockout has excellent and robust support for computed bindings (for example item().children.length > foo().maxChildren) and the current answers fail to support these.

So as further improvement to RobertSlanley's answer, and based on Knockout's documentation, I have created the following binding.

Knockout will handle the observable subscriptions by itself, so long as we access them in the update method. Which means we can remove the subscription cruft and just concentrate on what we really want to do:

ko.bindingHandlers.selectPicker = {
    after: ['options', 'value', 'selectedOptions'],
    init: function (element, valueAccessor, allBindingsAccessor) {
        $(element).addClass('selectpicker').selectpicker();
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        /* KO 3.3 will track any bindings we depend on
           and call us when any of them changes */
        allBindingsAccessor.get('options');
        allBindingsAccessor.get('value');
        allBindingsAccessor.get('selectedOptions');

        $(element).selectpicker('refresh');
    }
};

Here's a demo fiddle

like image 36
M.Stramm Avatar answered Oct 18 '22 12:10

M.Stramm


Unfortunately I can't yet comment on above answer, but there is a little mistake in the HTML-part:

The selected values binding should be selectedOptions and not selectOptions.

HTML

<!-- Multiple Select -->
<select data-bind="selectedOptions: teamIDs, 
               options: teamItems, 
               optionsText: 'text', 
               optionsValue : 'id', 
               selectPicker: {}" multiple="true"></select>
like image 22
Erwin Haas Avatar answered Oct 18 '22 12:10

Erwin Haas