使用敲除映射插件将深度层次化的对象映射到自定义类

Posted

技术标签:

【中文标题】使用敲除映射插件将深度层次化的对象映射到自定义类【英文标题】: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);

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

【问题讨论】:

【参考方案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>

【讨论】:

以上是关于使用敲除映射插件将深度层次化的对象映射到自定义类的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 RKMappingOperation 将本地 NSDictionary 映射到自定义对象?

将嵌套对象作为道具映射到自定义组件

Knockout JS映射插件没有初始数据/空表单

Ninja 框架端点在尝试将 JSON 映射到自定义对象时抛出 500 错误

实体框架 - 您能否将导入的存储过程的结果类型映射到自定义实体类型?

学习笔记 - MapStruct 映射工具