【问题标题】:Mapping deeply hierarchical objects to custom classes using knockout mapping plugin使用敲除映射插件将深度层次化的对象映射到自定义类
【发布时间】:2011-11-21 21:13:06
【问题描述】:

使用剔除映射插件 (http://knockoutjs.com/documentation/plugins-mapping.html) 可以映射深度层次的对象吗?

如果我有一个具有多个级别的对象:

var data = {
    name: 'Graham',
    children: [
        {
            name: 'Son of Graham',
            children: [
                {
                    name: 'Son of Son of Graham',
                    children: [
                        {
                            ... and on and on....
                        }
                    ]

                }
            ]
        }
    ]
}

如何在 javascript 中将其映射到我的自定义类:

var mapping = {
    !! your genius solution goes here !!

    !! need to create a myCustomPerson object for Graham which has a child myCustomerPerson object 
    !! containing "Son of Graham" and that child object contains a child myCustomerPerson 
    !! object containing "Son of Son of Graham" and on and on....

}

var grahamModel = ko.mapping.fromJS(data, mapping);

function myCustomPerson(name, children)
{
     this.Name = ko.observable(name);
     this.Children = ko.observableArray(children);
}

映射插件能否将此数据递归地映射到我的自定义对象的层次结构中?

【问题讨论】:

    标签: mvvm mapping knockout.js knockout-mapping-plugin


    【解决方案1】:

    类似这样的东西 (Live copy on js fiddle):

    CSS:

    .left {
        float: left;
    }
    
    .clear {
        clear: both;
    }​
    

    HTML:

    <p>Current:&nbsp;
        <a href="#" data-bind="visible: (stack.length > 0), text: selectedNode().name, click: selectParentNode"></a>
        <span data-bind="visible: (stack.length <= 0), text: selectedNode().name"></span>
    </p>
    <p class="left">Children:&nbsp;</p>
    <ul class="left" data-bind="template: {name: 'childList', foreach: selectedNode().children}"></ul>
    
    <script type="text/html" id="childList">
        <li data-bind="click: function(){nodeViewModel.selectChildNode($data)}">
            <a href="#">A${name}</a>
        </li>
    </script>
    
    <br /><br />
    <ul class="clear" data-bind="template: {name: 'backBtn'}"></ul>
    
    <script type="text/html" id="backBtn">
        <a href="#" data-bind="visible: $data.selectedNode().back, click: function() { nodeViewModel.selectBackNode($data.selectedNode().back) }">Back</a>
    </script>​
    

    JavaScript:

    var node = function(config, parent) {
        this.parent = parent;
        var _this = this;
    
        var mappingOptions = {
            children: {
                create: function(args) {
                    return new node(args.data, _this);
                }
            }
        };
    
        ko.mapping.fromJS(config, mappingOptions, this);
    };
    
    var myModel = {
        node: {
            name: "Root",
            children: [
                {
                name: "Child 1",
                back: 1,
                children: [
                    {
                    name: "Child 1_1",
                    back: 1,
                    children: [
                        {
                        name: "Child 1_1_1",
                        back: 4,
                        children: [
                            ]},
                    {
                        name: "Child 1_1_2",
                        back: 2,
                        children: [
                            ]},
                    {
                        name: "Child 1_1_3",
                        back: 1,
                        children: [
                            ]}
                        ]}
                ]},
            {
                name: "Child 2",
                back: 1,
                children: [
                    {
                    name: "Child 2_1",
                    back: 1,
                    children: [
                        ]},
                {
                    name: "Child 2_2",
                    back: 1,
                    children: [
                        ]}
                ]}
            ]
        }
    };
    
    var viewModel = {
    
        nodeData: new node(myModel.node, undefined),
    
        selectedNode: ko.observable(myModel.node),
    
        stack: [],
    
        selectBackNode: function(numBack) {
    
            if (this.stack.length >= numBack) {
                for (var i = 0; i < numBack - 1; i++) {
                    this.stack.pop();
                }
            }
            else {
                for (var i = 0; i < this.stack.length; i++) {
                    this.stack.pop();
                }
            }
    
            this.selectNode( this.stack.pop() );
        },
    
        selectParentNode: function() {
            if (this.stack.length > 0) {
                this.selectNode( this.stack.pop() );
            }
        },
    
        selectChildNode: function(node) {
            this.stack.push(this.selectedNode());
            this.selectNode(node);
        },
    
        selectNode: function(node) {
            this.selectedNode(node);
        }
    
    };
    
    window.nodeViewModel = viewModel;
    ko.applyBindings(viewModel);​
    

    这个示例只是映射了一组无限嵌套的 JSON 数据,我可以说在应用程序中实际使用这个确切的代码效果很好。

    一些额外的功能,如

    selectBackNode 和 selectParentNode

    允许您向上移动。

    在导航示例时,父标签成为允许上一级的链接,并且一些叶节点有一个后退按钮,允许它们在树上向后移动给定数量的级别。

    --编辑--

    如果您的叶节点没有子数组,您可能会遇到引入模型中不存在的其他数据的问题。

    【讨论】:

    • 像 jsFiddle 这样的链接是一个很好的辅助,但总是将相关代码和标记放在答案本身中。为什么:meta.stackexchange.com/questions/118392/…这次我已经为你做了。
    • @HJO5:您将如何更新它以允许在运行时插入子级?
    【解决方案2】:

    根据我的经验,我会说它不应该有任何问题。

    我会使用以下行 -

    var grahamModel = ko.mapping.fromJS(data);
    

    然后在下一行设置断点,查看调试器中生成的对象(chrome 或 FF+Firebug 效果最佳)。通过这种方式,您将知道 ko.mapping 是否会生成满足您需求的视图模型。

    通常,它会生成一个对象,其中只有端点(带值的变量)是 ko.observables。可用于在数据中导航的任何其他数据时间,例如 ... children: [...,都显示为普通的 JavaScript 对象。

    【讨论】:

      【解决方案3】:

      如果您不想要嵌套的 mappingOptions(为每个节点级别创建一个 ko 映射对象),您可以利用 create 的 ko 映射选项让您访问父对象这一事实。像这样的:

      function Folder(parent,data) {
          var self = this;
          self.parent = parent;
          ko.mapping.fromJS(data, self.map, self);
      }
      
      Folder.prototype.map = {
          'folders': {
              create: function(options) {
                  var folder = new Folder(options.parent,options.data);
                  return folder;
              }
          }
      }
      
      var data = { name:"root", folders: [ {name:"child", folders: [] } ] };
      var root = new Folder(null, data);
      

      这样,您的类原型(或可以是任何函数)中只有 1 个地图副本。如果您希望 Folder.parent 也成为可观察对象,您可以在 map 函数中执行 data.parent = parent; 而不是作为参数传递给您的文件夹构造函数,或者在文件夹构造函数中执行此操作而不是 self.parent = parent;

      【讨论】:

        【解决方案4】:

        我使用answer 中的方法创建了一个复选框层次结构,其中具有子节点的节点是可折叠的,并且当您选中/取消选中父节点时,其后代会被选中/取消选中。

        查看模型

        var Category = function(data, parent) {
            var self = this;
            self.name = data.name;
            self.id = data.id;
            self.parent = parent;
            self.categoryChecked = ko.observable(false);
            ko.mapping.fromJS(data, self.map, self);
        };
        
        // This will add a "map" to our category view model
        Category.prototype.map = {
            'sub_categories' : {
                create: function(options){
                    var category = new Category(options.data, options.parent);
                    category.parent.categoryChecked.subscribe(function(value){
                        category.categoryChecked(value);
                    });
                    return category;
                }
            }
        };  
        

        HTML(视图)

            <div data-role="panel" id="left-panel" data-position="left" data-position-fixed="false" data-theme="b">
                    <div data-role="collapsible-set" data-bind="template: {name: 'category_collapsible', foreach: sub_categories}" data-mini="true" id="categories" data-iscroll> </div>
                </div><!-- END left panel -->
        
                <script type="text/html" id="category_collapsible">
                    <div class="category_collapsible" data-mini="true" data-content-theme="b" data-inset="true" data-iconpos="right">
                        <h3>     
                            <input data-role="none" data-them="b" data-bind='checked: categoryChecked, jqmChecked: true, attr: {id: "category_checkbox_"+id}' class="chk_category" type="checkbox" />
                            <label data-bind='attr: {for: "category_checkbox_"+id}'><span data-bind="text: name"> </span></label>
                        </h3>
                        <ul data-role="listview" data-bind="template: {name: 'category_list', foreach: sub_categories}">
        
                        </ul>
                    </div>
                </script><!-- END category_collapsible template -->
        
                <script type="text/html" id="category_list">
                    <!-- ko if: sub_categories().length==0 -->
                        <li data-theme="c">
                            <input data-role="none" data-theme="c" data-bind='checked: categoryChecked, jqmChecked: true, attr: {id: "category_checkbox_"+id}' class="chk_category" type="checkbox"/>
                            <label data-corners="false" data-bind='attr: {for: "category_checkbox_"+id}'>
                                <span data-bind="text: name"> </span>
                            </label>        
                        </li>
                    <!-- /ko -->
                    <!-- ko if: sub_categories().length>0 -->
                        <li data-theme="c" data-bind="template: {name: 'category_collapsible', data: $data}"></li>
                    <!-- /ko -->
                </script>
        

        【讨论】:

          猜你喜欢
          • 2018-10-31
          • 1970-01-01
          • 2012-04-13
          • 2013-06-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-01
          • 1970-01-01
          相关资源
          最近更新 更多