Select2 allowclear and knockout.js

2019-05-28 00:07发布

I use select2 and knockoutJs with this simple binding:

ko.bindingHandlers.select2 = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var options = ko.toJS(valueAccessor()) || {};
        setTimeout(function () {
            $(element).select2(options);
        }, 0);
    }
};

Markup:

<select class="select2" style="width:100%" data-bind="optionsCaption: '',options: $root.items,optionsText: 'description',optionsValue: 'id', value: $root.selectedItem,select2: { placeholder: 'Select an item...',allowClear: true }"></select>

it works! Now I enabled the allowClear option in Select2 to clear dropdown to a placeholder value like Select an item....

If I click the x icon dropdown properly sets the placeholder but knockout don't update observable binded value!

I think I've to change custombinding adding something like this:

setTimeout(function () {
    $(element).select2(options).on("select2-removed", function (e) {
       ko.bindingHandlers.value.update(element, function () { return ''; });
    });
...

but it won't work!

1条回答
再贱就再见
2楼-- · 2019-05-28 00:28

There are couple of issues.

1) the update in bindinghandler is to update DOM based on value change, you should never define an update callback with the ability to mutate your model.

The right way is, when define a new bindinghandler, use init callback to hook up all change events with the model, the update callback is merely a DOM drawing routine.

If your init provided DOM drawing (such as provided by select2), you don't need to define an update callback.

Hence the line ko.bindingHandlers.value.update(element, function () { return ''; }); only redraw the DOM, it doesn't do what you want.

2) the select2 binding you created has some holes.

  • first, value binding doesn't know the existence of select2 binding, that's where you struggled.
  • second, your select2 binding has to wait for other binding (the options binding) to finish DOM creation, what's why you use setTimeout. But ko provided a way to define sequence of the bindings, just look the source code of ko value binding, it's defined as 'after': ['options', 'foreach']
  • third, your select2 doesn't respond to outside change. For instance, if you have another UI to update $root.selectedItem (a normal select list), the change raised by that UI would not sync back to your select2.

The solution

Define select2 binding based on existing value binding (just found out it's not needed), and hook up all change events.

  • we don't need "select2-removed" event, it's all about "change" event.
  • select2 provided all drawing, we don't need update callback.
  • use a flag shouldIgnore to break the loop between value subscriber and select2 change event handler.

http://jsfiddle.net/huocp/8N3zX/6/ http://jsfiddle.net/huocp/8N3zX/9/

ko.bindingHandlers.valueSelect2 = {
    'after': ['options'],
    'init': function(element, valueAccessor, allBindings) {
        // kind of extend value binding
        // ko.bindingHandlers.value.init(element, valueAccessor, allBindings);

        var options = allBindings.get('select2Options') || {};
        $(element).select2(options);

        var value = valueAccessor();
        // init val
        $(element).val(ko.unwrap(value)).trigger("change");

        var changeListener;
        if (ko.isObservable(value)) {
            var shouldIgnore = false;
            changeListener = value.subscribe(function(newVal) {
                if (! shouldIgnore) {
                  shouldIgnore = true;
                  // this is just select2 syntax
                  $(element).val(newVal).trigger("change");
                  shouldIgnore = false;
                }
            });

            // this demo only works on single select.
            $(element).on("change", function(e) {
                if (! shouldIgnore) {
                    shouldIgnore = true;
                    if (e.val == '') {
                        // select2 use empty string for unselected value
                        // it could cause problem when you really want '' as a valid option
                        value(undefined);
                    } else {
                        value(e.val);
                    }
                    shouldIgnore = false;
                }
            });
        }

        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            if (changeListener) changeListener.dispose();
            $(element).select2("destory");
        });
    }
};
查看更多
登录 后发表回答