n级深度复选框树行为

时间:2013-02-13 15:59:40

标签: javascript knockout.js

我有一个使用knockout实现的n级复选框树,其中父级chechbox的选择应该选择其子级(但不是相反)并且一旦提交数据,所选元素的id应转换为JSON被发送到服务器。

我无法弄清楚如何单向复选框关系,也无法弄清楚如何以这样的方式过滤我的最终json:

  1. 如果选择了父级(因为它也意味着所有子级都被选中),则不在json中发送其子ID

  2. 如果只选择了一个或几个孩子,如何不发送父ID。

  3. 列表项的模型是:

    marketingListsItem = function (data, parent) {
            var self = this;
            self.Name = ko.observable(data.Name);
            self.Selected = ko.observable(data.Selected);
            self.Parent = ko.observable(parent);
            self.Children = ko.observableArray([]);
            self.Id = ko.observable(data.Id);         
            self.DateAdded = ko.observable(new Date(data.DateAdded));
            self.DateModified = ko.observable(data.DateModified);
            if (data.Children) {
                ko.utils.arrayForEach(data.Children, function (child) {
                    self.Children.push(new marketingListsItem(child, this));
                }.bind(this));
            };
        }
    

    以下是视图模型部分:

    marketingListsViewModel = {
            marketingLists: mapping.fromJS([]),
            originatorConnectionName: ko.observable(''),
            selectedMarketingListIds: ko.observableArray([])
        },
        init = function (connectionId, connectionName) {
            marketingListsViewModel.originatorConnectionName(connectionName);
            marketingListsViewModel.getFieldMapping = function () {
                require(['mods/fieldmapping'], function (fieldmapping) {
                    fieldmapping.init(connectionId, connectionName);
                });
            };
            // Here I only managed to filter the parent level selections
            marketingListsViewModel.selectedLists = ko.computed(function () {
                return ko.utils.arrayFilter(marketingListsViewModel.marketingLists(), function (item) {
                    return item.Selected() == true;
                });
            });
            marketingListsViewModel.saveMarketingListChanges = function () {
                // Which I can filter my JSON to include them but the children are missing
                var latestMarketingListChanges = ko.toJSON(marketingListsViewModel.selectedLists, ["Id"]);
                console.log(latestMarketingListChanges);
                amplify.request("updateExistingMarketingLists", { cid: connectionId, ResponseEntity: { "id": connectionId, "selectedMarketListIds": latestMarketingListChanges } },
                    function (data) {
                        console.log(data);
                    });
            }
            amplify.request("getExistingMarketingLists", { cid: connectionId }, function (data) {
                showMarketingLists();
                mapping.fromJS(data.ResponseEntity, dataMappingOptions, marketingListsViewModel.marketingLists);
                ko.applyBindings(marketingListsViewModel, $('#marketingLists')[0]);
            });
        };
    

    最后这里是观点:

    <div id="marketingListsContainer">
            <ul data-bind="template: {name: 'itemTmpl' , foreach: marketingLists}"></ul>
            <script id="itemTmpl" type="text/html">
                <li>
                    <label><input type="checkbox" data-bind="checked: Selected" /><span data-bind='text: Name'></span></label>                    
                <ul data-bind="template: { name: 'itemTmpl', foreach: Children }" class="childList"></ul>
            </script>
        </div>
        <a class="s_button modalClose right" href="#"><span data-bind="click: saveMarketingListChanges">Save and close</span></a><br>
    

2 个答案:

答案 0 :(得分:2)

感谢您的回答。问题是,如果已经通过服务器中的数据检查了Parent,那么现在使用您的解决方案,那么它的子项检查值不会发生变异。

我已将以下内容添加到模型中以解决此问题:

self.sync = ko.computed(function () {
       return self.Selected.valueHasMutated();
});

对于那些稍后会发现这篇文章的人,可能想知道最终结果是什么:

    define('mods/marketinglists', ["knockout", "libs/knockout.mapping", "libs/knockout.validation", "datacontext", "mods/campaigner", "text!templates/marketinglists.html", "text!styles/marketinglists.css"],
function (ko, mapping, validation, datacontext, campaigner, html, css) {
    'use strict';
    var
        marketingListsItem = function (data, parent) {
            var self = this;
            self.Name = ko.observable(data.Name);
            self.Selected = ko.observable(data.Selected);
            self.Parent = ko.observable(parent);
            self.Children = ko.observableArray([]);
            self.Id = ko.observable(data.Id);
            self.DateAdded = ko.observable(new Date(data.DateAdded));
            self.DateModified = ko.observable(data.DateModified);
            // If node contains children define each one as a marketingListItem in itself
            // and bind it to the the model
            if (data.Children) {
                ko.utils.arrayForEach(data.Children, function (child) {
                    self.Children.push(new marketingListsItem(child, this));
                }.bind(this));
            };
            // Watch for value changes in parent and check children 
            // if the parent was checked
            self.Selected.subscribe(function (newValue) {
                if (newValue === true) {
                    for (var i = 0; i < self.Children().length; i++) {
                        self.Children()[i].Selected(true);
                    }
                }
                else {
                    if (self.Parent() != null) { self.Parent().Selected(false); }
                }
            });
            // Make sure subscribers have been notified when needed
            self.sync = ko.computed(function () {
                return self.Selected.valueHasMutated();
            });
        },
        dataMappingOptions = {
            key: function (data) {
                return data.Id;
            },
            create: function (options) {
                return new marketingListsItem(options.data, null);
            }
        },
        showMarketingLists = function () {
            campaigner.addStylesToHead(css);
            campaigner.addModalWindow(html, {
                windowSource: "inline",
                width: 700,
                height: '340'
            });
        },
        marketingListsViewModel = {},
        init = function (connectionId, connectionName) {
            // Define marketingLists as an observable array from JS object
            marketingListsViewModel.marketingLists = mapping.fromJS([]);
            marketingListsViewModel.originatorConnectionName = ko.observable('');
            // Set the name for the marketing list
            marketingListsViewModel.originatorConnectionName(connectionName);
            marketingListsViewModel.getFieldMapping = function () {
                require(['mods/fieldmapping'], function (fieldmapping) {
                    fieldmapping.init(connectionId, connectionName);
                });
            };
            marketingListsViewModel.selectedLists = ko.computed(function () {
                var selectedItems = [];
                ko.utils.arrayFilter(
                    marketingListsViewModel.marketingLists(),
                    function (item) {
                        // If a parent a selected its being collected
                        if (item.Selected() == true) selectedItems.push(item);
                        else {
                            // If a child is slected it is collected
                            ko.utils.arrayForEach(item.Children(), function (child) {
                                if (child.Selected()) selectedItems.push(child);
                                else {
                                    ko.utils.arrayForEach(child.Children(),
                                        // Finally if children's child is selected its collected
                                        function (childChildren) {
                                            if (childChildren.Selected())
                                                selectedItems.push(childChildren);
                                        });
                                }
                            })
                        }
                    });
                return selectedItems;
            });
            marketingListsViewModel.saveMarketingListChanges = function () {
                // Pick only the selected elements and parse only the Id
                var latestMarketingListChanges = ko.toJSON
                    (marketingListsViewModel.selectedLists,
                        ["Id"]);
                console.log(latestMarketingListChanges);
                // Send the latest marketing lists changes Ids to the server
                amplify.request("updateExistingMarketingLists",
                    {
                        cid: connectionId,
                        ResponseEntity:
                        {
                            "id": connectionId,
                            "selectedMarketListIds": latestMarketingListChanges
                        }
                    },
                    function (data) {
                        console.log(data);
                    });
            }
            amplify.request("getExistingMarketingLists", { cid: connectionId },
                function (data) {
                    showMarketingLists();
                    mapping.fromJS(
                        data.ResponseEntity,
                        dataMappingOptions,
                        marketingListsViewModel.marketingLists);

                    ko.applyBindings(marketingListsViewModel, $('#marketingLists')[0]);
                });
        };
    return {
        init: init,
        marketingListsViewModel: marketingListsViewModel,
        html: html,
        css: css
    }
});

使用此视图:

<div id="marketingListsContainer">
            <ul data-bind="template: {name: 'itemTmpl' , foreach: marketingLists}"></ul>
            <script id="itemTmpl" type="text/html">
                <li>
                    <!-- ko if: $data.Parent -->
                    (my parent is: <span data-bind="text: $data.Parent().Name"></span>) 
                    <!-- /ko -->
                    <label><input type="checkbox" data-bind="checked: Selected" /><span data-bind='text: Name'></span></label>                    
                <ul data-bind="template: { name: 'itemTmpl', foreach: Children }" class="childList"></ul>
            </script>
        </div>
        <a class="s_button modalClose right" href="#"><span data-bind="click: saveMarketingListChanges">Save and close</span></a><br>

答案 1 :(得分:1)

您可以向所选绑定添加订阅事件,并执行类似这样的操作,以便在检查父级时将所有子项标记为已选中。

self.Selected.subscribe( function ( newValue ) {
    if ( newValue === true ) { 
        for ( i=0; i<self.Children().length; i++) {
            self.Children()[i].Selected(true);
        }
    }
    else {
         if ( self.Parent() != null ) { 
             self.Parent().Selected(false);
         }
    }
} ) ;

然后在您的保存方法中,您将需要对列表进行一些自定义遍历逻辑

marketingListsViewModel.saveMarketingListChanges = function (
    var allSelected = ko.toJS( marketingListsViewModel.selectedLists );
    var ids = [];

    for ( i = 0; i < allSelected.length; i++ ) {
        ids.concat ( marketingListsViewModel.getIds( allSelected[i] ) );
    }

    var latestMarketingListChanges = ko.toJSON(ids);
    ...
);

marketingListsViewModel.getIds = function( item ) { 
     var ids = []

     if ( item.Selected === true ) { 
         ids.add( item.Id );
     }
     else if ( item.Children.length > 0 ) {
         for ( i = 0; i < item.Children.length; i++ ) {
             ids.concat( marketingListsViewModel.getIds( item.Children[i] )  )
         }
     }

     return ids;
}

现在请记住,我并不打算在这里写javascript,但你明白了。您可能需要编写数组连接函数或扩展jQuery或类似的东西。

希望有所帮助