简体   繁体   English

使用bootstrap.multiselect.js进行Knockout.js自定义绑定不会更新所选值

[英]Knockout.js custom binding with bootstrap.multiselect.js not updating selected value

I am using Knockout in combination with bootstrap-multiselect . 我将Knockout与bootstrap-multiselect结合使用。 I have three columns in an HTML table which allow you to specify a search item, search operator and search criteria. HTML表中有三列,可用于指定搜索项,搜索运算符和搜索条件。 The search operator column actually holds three different elements: one multi-select, one single select and an input element; 搜索运算符列实际上包含三个不同的元素:一个多选,一个单选和一个输入元素; only one out of the three elements is actually displayed at any given time. 在任何给定时间实际上仅显示三个元素中的一个。 Which element is displayed is controlled by the value of the search item column. 显示哪个元素由搜索项目列的值控制。

All select elements are initialized as bootstrap-multiselect. 所有选择元素均初始化为bootstrap-multiselect。 The search criteria column and the search operator column work without issue. 搜索条件列和搜索运算符列可以正常工作。 The search item column however does not seem to be properly binding back to the native select which bootstrap-multiselect hides. 但是,搜索项列似乎没有正确地绑定回引导多选隐藏的本机选择。 The search item column will have the radio button selected next to the value you picked, however the text will display the first value from the select list. 搜索项目列将在您选择的值旁边选中单选按钮,但是文本将显示选择列表中的第一个值。 If I do not initialize the search item column as a bootstrap-multiselect, everything works as expected with the native select for the search-item column. 如果我没有将搜索项列初始化为bootstrap-multiselect,那么一切都将按预期方式与搜索项列的本机选择一起使用。 The Knockout viewmodel is updated with the correct value as expected. Knockout视图模型将按预期的正确值更新。

Below is my HTML declaration for the search criteria table: 以下是我对搜索条件表的HTML声明:

<table id="search-criteria" class="table table-condensed hidden" data-bind="css: {hidden:SpecifiedCriteria().length == 0}">
    <thead>
    <tr>
        <th class ="search-item">
            Item
        </th>
        <th class="search-operator">
            Operator
        </th>
        <th class="search-criteria">
            Criteria
        </th>
    </tr>
    </thead>
    <tbody data-bind="foreach:SpecifiedCriteria">
    <tr>
        <td class="search-item">
            <select class="search-item" data-bind="searchDropDownList:SelectedItem, foreach: $root.CriteriaItems, value:SelectedItem">
                <optgroup data-bind="attr: {label: label}, foreach: children">
                    <option data-bind="text: DisplayName, option: $data"></option>
                </optgroup>
            </select>
        </td>
        <td class="search-operator">
            <select class="search-operator" data-bind="searchDropDownList:MatchCompareOperator, options:AvailableCompareOperators(), optionsValue: 'Value', optionsText: 'Key', value:MatchCompareOperator"></select>
        </td>
        <td class="search-criteria">
            <input type="text" class="search-criteria" data-bind="value:MatchValue" />
            <select class="search-criteria" data-bind="searchDropDownList:MatchValueListSelectedItemIds, options:MatchValueList, optionsValue:'Id', optionsText:'Description', selectedOptions:MatchValueListSelectedItemIds"></select>
            <select class="search-criteria" multiple="multiple" data-bind="searchDropDownList:MatchValueListSelectedItemIds, options:MatchValueList, optionsValue:'Id', optionsText:'Description', selectedOptions:MatchValueListSelectedItemIds"></select>
        </td>
    </tr>
    </tbody>
</table>

This is my view model, of which I new up an instance and pass into ko.applyBindings(): 这是我的视图模型,其中的一个是我的实例,然后传递给ko.applyBindings():

searchCriteria = function () {

    var comparisonOperators = Array(
        { "Key": "Starts With", "Value": 0 },
        { "Key": "Greater Than", "Value": 1 },
        { "Key": "Less Than", "Value": 2 },
        { "Key": "Greater Or Equal To", "Value": 3 },
        { "Key": "Less or Equal To", "Value": 4 },
        { "Key": "Not Equal To", "Value": 5 },
        { "Key": "Equal To", "Value": 6 },
        { "Key": "Contains", "Value": 7 },
        { "Key": "Does Not Contain", "Value": 8 });

    var criteriaDataType = {
        Alpha: 0,
        AlphaNumeric: 1,
        Integer: 2,
        Date: 3,
        DateTime: 4,
        Boolean: 5
    };

    //available styles constants
    var matchStyle = {
        InputBox: 0,
        DropDownList: 1,
        MultiSelectDropDownList: 2,
        DatePicker: 3
    };

    //available operator constants
    var matchCompareOperator = {
        StartsWith: 0,
        GreaterThan: 1,
        LessThan: 2,
        GreaterOrEqualTo: 3,
        LessOrEqualTo: 4,
        NotEqualTo: 5,
        EqualTo: 6,
        Contains: 7,
        DoesNotContain: 8
    };

    //determine which if the operator is eligible for the matchStyle
    function filterOperators(operator, specifiedMatchStyle, specifiedCriteriaDataType) {
        var eligible = false;

        switch (specifiedMatchStyle) {
            case matchStyle.MultiSelectDropDownList:
            case matchStyle.DropDownList:
                eligible = (operator.Value === matchCompareOperator.EqualTo || operator.Value === matchCompareOperator.NotEqualTo);
                break;
            case matchStyle.DatePicker:
                eligible = (operator.Value !== matchCompareOperator.StartsWith &&
                                   operator.Value !== matchCompareOperator.Contains &&
                                   operator.Value !== matchCompareOperator.DoesNotContain);
                break;
            case matchStyle.InputBox:
                if (specifiedCriteriaDataType !== criteriaDataType.Integer &&
                    specifiedCriteriaDataType !== criteriaDataType.Date &&
                    specifiedCriteriaDataType !== criteriaDataType.DateTime) {

                    if (operator.Value !== matchCompareOperator.GreaterThan &&
                        operator.Value !== matchCompareOperator.GreaterOrEqualTo &&
                        operator.Value !== matchCompareOperator.LessThan &&
                        operator.Value !== matchCompareOperator.LessOrEqualTo) {

                        eligible = true;
                    }

                } else {
                    if (operator.Value !== matchCompareOperator.StartsWith)
                        eligible = true;
                }

                break;
        }

        return eligible;
    }

    var searchCriteriaModel = function (criteriaItems, postUrl) {

        var modelObject = this;

        //setup the model elements
        modelObject.SpecifiedCriteria = ko.mapping.fromJS([]);
        modelObject.ComparisonOperators = comparisonOperators;

        var resultsDataTable = $("#search-results").dataTable();

        //create the criteria group items
        modelObject.CriteriaItems = [];

        var group = function (label, children) {
            this.label = ko.observable(label);
            this.children = ko.observableArray(children);
        }

        var groups = ko.utils.arrayMap(criteriaItems, function (item) { return item.DisplayGroupName; });
        groups = ko.utils.arrayGetDistinctValues(groups).sort();

        //filter the criteria items array based on the group and build the list of grouped items
        ko.utils.arrayForEach(groups, function (groupItemDisplayGroupName) {
            modelObject.CriteriaItems.push(
                new group(groupItemDisplayGroupName,
                    ko.utils.arrayFilter(criteriaItems, function (item) {
                        return (item.DisplayGroupName === groupItemDisplayGroupName);
                    })
                )
            );
        });

        var criteriaItem = function () {
            var itemObject = this;

            //setup the properties we are binding observables on
            itemObject.SelectedItem = ko.observable();
            itemObject.MatchValueColumnName = ko.observable();
            itemObject.MatchCompareOperator = ko.observable();
            itemObject.MatchValue = ko.observable();
            itemObject.DisplayName = ko.observable();
            itemObject.MatchStyle = ko.observable();
            itemObject.MatchValueList = ko.observableArray();
            itemObject.MatchValueListSelectedItemIds = ko.observableArray();
            itemObject.AvailableCompareOperators = ko.observableArray();

            //a computed which contains just the list of our selected items
            itemObject.MatchValueListSelectedItems = ko.computed(function () {
                return ko.utils.arrayFilter(itemObject.MatchValueList(), function (matchValueListItem) {
                    return ko.utils.arrayFirst(itemObject.MatchValueListSelectedItemIds(), function (selectedItemId) {
                        return (matchValueListItem.Id === selectedItemId);
                    });
                });
            });

            itemObject.SelectedItem.subscribe(function (newValue) {
                if (newValue === undefined || newValue === null)
                    return;

                //copy over all of the other attributes from the selected item to our item
                itemObject.DataIdColumnName = newValue.DataIdColumnName;
                itemObject.DataIdColumnValue = newValue.DataIdColumnValue;
                itemObject.DisplayGroupName = newValue.DisplayGroupName;
                itemObject.DisplayName(newValue.DisplayName);

                itemObject.MatchValueDataType = newValue.MatchValueDataType;
                itemObject.MatchValue(newValue.MatchValue);
                itemObject.MatchStyle(newValue.MatchStyle);
                itemObject.MatchValueColumnName = newValue.MatchValueColumnName;

                //initialize the compareoperator list
                itemObject.AvailableCompareOperators.removeAll();
                ko.utils.arrayPushAll(itemObject.AvailableCompareOperators,
                    ko.utils.arrayFilter(modelObject.ComparisonOperators, function (operator) { return filterOperators(operator, newValue.MatchStyle, newValue.MatchValueDataType) }));

                itemObject.MatchCompareOperator(newValue.MatchCompareOperator);

                //initialize the value list
                itemObject.MatchValueList.removeAll();
                if (newValue.MatchValueList !== null)
                    ko.utils.arrayPushAll(itemObject.MatchValueList, newValue.MatchValueList);

                itemObject.MatchValueListDisplayMember = newValue.MatchValueListDisplayMember;
                itemObject.MatchValueListSelectedItemIds.removeAll();
            });

        };

        //add a new item
        modelObject.addCriteriaItem = function () {
            var item = new criteriaItem();
            modelObject.SpecifiedCriteria.push(item);
        };

        //clear all items
        modelObject.clearCriteriaItems = function () {
            modelObject.SpecifiedCriteria.removeAll();
            $("#item-criteria-results").addClass("hidden");
            resultsDataTable.fnClearTable();
            modelObject.addCriteriaItem();
        };

        //seed with an empty row
        modelObject.addCriteriaItem();

        ko.bindingHandlers.option = {
            update: function (element, valueAccessor) {
                var value = ko.utils.unwrapObservable(valueAccessor());
                ko.selectExtensions.writeValue(element, value);
            }
        };

        //set up the ko search-multiselect binding for use with bootstrap multiselect
        ko.bindingHandlers.searchDropDownList = {
            update: function (element, valueAccessor, allBindings, viewMode, bindingContext) {

                if (element.nodeName.toLowerCase() !== "select")
                    return;

                //get the value and wire up the subscription
                var value = ko.utils.unwrapObservable(valueAccessor());
                if (value === null || value === undefined)
                    return;

                //cache the element so we don't have to look it up every time
                var triggerElement = $(element);
                var row = $(triggerElement).parents("tr");

                //initialize the multi-select list
                var searchItem = null;
                var operator = $(row).find("select.search-operator");

                //initialize the multi-selects if this is our first time through
                if (!triggerElement.hasClass("initialized")) {
                    if (triggerElement.hasClass("search-item")) {
                        //make sure we deselect any item
                        //triggerElement.prop("selectedIndex", -1);

                        triggerElement.multiselect({
                            buttonWidth: "100%",
                            maxHeight: 310,
                            enableFiltering: true,
                            enableCaseInsensitiveFiltering: true
                        });

                    } else {
                        triggerElement.multiselect({ buttonWidth: "100%" });
                    }

                    //flag the element as initialized
                    triggerElement.addClass("initialized");

                    //toggle off the operator and all of the criteria entry boxes
                    operator.siblings(".btn-group").addClass("hidden");
                    $(row).find("select.search-criteria").siblings(".btn-group").addClass("hidden");
                    $(row).find("input.search-criteria").addClass("hidden");
                    return;
                }

                //toggle the display of the operator and the search criteria
                if (triggerElement.hasClass("search-item")) {

                    if (operator.hasClass("initialized")) {
                        operator.multiselect("rebuild");
                        $(operator).siblings(".btn-group").first().removeClass("hidden");
                    }

                    //toggle off all of the criteria entry boxes
                    $(row).find("select.search-criteria").siblings(".btn-group").addClass("hidden");
                    $(row).find("input.search-criteria").addClass("hidden");

                    var searchCriteria = null;
                    if (value.MatchStyle === matchStyle.MultiSelectDropDownList)
                        searchCriteria = $(row).find("select[multiple='multiple'].search-criteria");
                    else if (value.MatchStyle === matchStyle.DropDownList)
                        searchCriteria = $(row).find("select:not([multiple]).search-criteria");
                    else
                        searchCriteria = $(row).find("input[type=text].search-criteria");

                    if (searchCriteria != null) {
                        if (searchCriteria.hasClass("initialized")) {
                            //we are a select
                            searchCriteria.multiselect("rebuild");
                            searchCriteria.next(".btn-group").removeClass("hidden");
                        } else {
                            searchCriteria.removeClass("hidden"); //we are an inputbox
                        }
                    }
                }
            }

        };
    }

    return {
        criteriaDataType: criteriaDataType,
        matchStyle: matchStyle,
        matchCompareOperator: matchCompareOperator,
        SearchCriteriaModel: searchCriteriaModel
    }
}();

Just to recap: if I do not wire up the search-item column as a bootstrap-multiselect, everything works. 回顾一下:如果我不将search-item列连接为bootstrap-multiselect,则一切正常。 If I do wire it up, the column will display the first value in the list. 如果我将其连接起来,该列将在列表中显示第一个值。 Setting a breakpoint in the searchDropDownList binding handler will always show the value gotten via the valueAccessor to be the first item in the list for the given search-item element. searchDropDownList绑定处理程序中设置断点将始终显示通过valueAccessor获得的值成为给定search-item元素列表中的第一项。

I am wits end and cannot understand or explain this behavior for the searc-item column, especially since the other two columns seeming work without issue. 我机智的结束了,无法理解或解释searc-item列的这种行为,尤其是因为其他两列似乎没有问题。

You can attach a 'change' event to the drop down list and set the ko object. 您可以将'change'事件附加到下拉列表并设置ko对象。 Let's suppose that '#example' is your drop down list: 假设“ #example”是您的下拉列表:

  var $example = $('#example');
  $example.multiselect();
  $example.on('change', function() {
      //set your ko object with the selected value
      itemObject.SelectedItem($example.val());
  });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM