Knockout.js与bootstrap.multiselect.js的自定义绑定不更新所选值



我将Knockout与bootstrap-multiselect结合使用。我在一个HTML表中有三列,允许您指定搜索项、搜索操作符和搜索条件。搜索操作符列实际上包含三个不同的元素:一个多选择、一个单选择和一个输入元素;在任何给定时间,实际上只显示三个元素中的一个。显示哪个元素由搜索项列的值控制。

所有select元素初始化为bootstrap-multiselect。搜索条件列和搜索操作符列可以正常工作。然而,搜索项列似乎没有正确地绑定回引导多选隐藏的本机选择。在搜索项列中,选中的值旁边有一个单选按钮,但是文本将显示选择列表中的第一个值。如果我没有将搜索项列初始化为引导多选,那么对于搜索项列的本机选择,一切都按照预期工作。Knockout视图模型按照预期使用正确的值进行更新。

下面是搜索条件表的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>

这是我的视图模型,我新建了一个实例并传递给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
    }
}();

重述一下:如果我没有将search-item列连接为引导多选,那么一切都可以工作。如果我将它连接起来,列将显示列表中的第一个值。在searchDropDownList绑定处理程序中设置断点将始终显示通过valueAccessor获得的值是给定search-item元素列表中的第一项。

我很聪明,无法理解或解释searc-item专栏的这种行为,特别是因为其他两个专栏似乎没有问题。

您可以将'change'事件附加到下拉列表并设置ko对象。让我们假设'#example'是你的下拉列表:

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

相关内容

  • 没有找到相关文章

最新更新