Knockout.js自定义绑定与bootstrap.multiselect.js没有更新选定的值

时间:2015-07-30 19:30:47

标签: javascript jquery twitter-bootstrap knockout.js

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

所有选择元素都初始化为bootstrap-multiselect。搜索条件列和搜索运算符列无任何问题。但是,搜索项列似乎没有正确绑定回本机选择哪个bootstrap-multiselect隐藏。搜索项目列将在您选择的值旁边选中单选按钮,但文本将显示选择列表中的第一个值。如果我没有将搜索项列初始化为bootstrap-multiselect,则所有内容都按预期使用search-item列的本机select。 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列连接为bootstrap-multiselect,那么一切正常。如果我将其连线,该列将显示列表中的第一个值。在searchDropDownList绑定处理程序中设置断点始终会将通过valueAccessor获取的值显示为给定search-item元素的列表中的第一个项目。

我的智慧已经结束,无法理解或解释searc-item列的这种行为,特别是因为其他两个列看起来没有问题。

1 个答案:

答案 0 :(得分:0)

You can attach a 'change' event to the drop down list and set the ko object. Let's suppose that '#example' is your drop down list:

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