Сопоставление глубоко иерархических объектов с пользовательскими классами с помощью подключаемого модуля нокаутного сопоставления

Использование подключаемого модуля нокаут-мэппинга ( 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);
}

Может ли подключаемый модуль сопоставления рекурсивно сопоставить эти данные с иерархией моих пользовательских объектов?


person Mark Robinson    schedule 21.09.2011    source источник


Ответы (4)


Примерно так (живая копия на скрипте js):

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

позволяют вернуться вверх по дереву.

При навигации по примеру родительская метка становится ссылкой, позволяющей подняться на один уровень вверх, а некоторые конечные узлы имеют кнопку «Назад», которая позволяет им вернуться вверх по дереву на заданное количество уровней.

--РЕДАКТИРОВАТЬ--

Если ваши листовые узлы не имеют дочернего массива, вы можете столкнуться с проблемой, когда вводятся дополнительные данные, которых нет в модели.

person HJ05    schedule 23.09.2011
comment
Такие ссылки, как jsFiddle, являются хорошим дополнением, но всегда помещают соответствующий код и разметку в сам ответ. Почему: meta.stackexchange.com/questions/118392/ Я сделал это для вас в этом случае. - person T.J. Crowder; 20.03.2012
comment
@ HJO5: Как бы вы обновили это, чтобы разрешить вставку дочерних элементов во время выполнения? - person Phil; 14.07.2013

По своему опыту могу сказать, что проблем быть не должно.

Я бы использовал следующую строку -

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

Затем установите точку останова на следующей строке, чтобы посмотреть на сгенерированный объект в отладчике (лучше всего работает Chrome или FF+Firebug). Таким образом, вы будете знать, сгенерирует ли ko.mapping модель представления, соответствующую вашим потребностям.

Обычно он генерирует объект, в котором только конечные точки (переменные со значениями) являются ko.observables. Любое другое время данных, которое вы можете использовать для навигации по данным, например ... children: [..., отображается как обычные объекты JavaScript.

person photo_tom    schedule 22.09.2011

Если вам не нужны вложенные опции отображения (создание объекта карты ko для каждого уровня узла), вы можете воспользоваться тем фактом, что опции отображения 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 также был наблюдаемым, вы можете сделать data.parent = parent; внутри функции карты и не передавать в качестве параметра конструктору папки или сделать это внутри конструктора папки вместо self.parent = parent;

person eselk    schedule 09.04.2013

Я использовал подход в этом ответе, чтобы создать иерархию флажков, в которой узлы с дочерними элементами являются сворачиваемыми, и когда вы устанавливаете/снимаете флажок родитель, его потомки проверяются/не проверяются.

Посмотреть модель

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>
person hisa_py    schedule 02.08.2013