Используя подключаемый модуль сопоставления нокаутов ( 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);
}
Может ли плагин сопоставления рекурсивно отобразить эти данные в иерархию моих настраиваемых объектов ?
Примерно так ( Живая копия на js fiddle ):
CSS:
.left {
float: left;
}
.clear {
clear: both;
}
HTML:
<p>Current:
<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: </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
, позволяют вам вернуться обратно вверх по дереву.
Во время навигации по примеру родительская метка становится ссылкой, позволяющей подняться на один уровень вверх, а некоторые конечные узлы имеют кнопку «Назад», которая позволяет им перемещаться вверх по дереву на заданное количество уровней.
- РЕДАКТИРОВАТЬ -
Если у ваших конечных узлов нет дочернего массива, вы можете столкнуться с проблемой, когда вводятся дополнительные данные, которых нет в модели.
Если вам не нужны вложенные mappingOptions (создание объекта карты ko для каждого уровня узла), вы можете воспользоваться тем фактом, что параметры отображения ko для create дают вам доступ к родительскому объекту. Примерно так:
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;
По своему опыту я бы сказал, что у него не должно быть никаких проблем.
Я хотел бы использовать следующую строку -
var grahamModel = ko.mapping.fromJS(data);
Затем установить точку останова на следующей строке при просмотре сгенерированного объекта в вашем отладчике (лучше всего работает chrome или FF + Firebug). Таким образом, вы узнаете, сгенерирует ли ko.mapping модель представления, соответствующую вашим потребностям.
Обычно он генерирует объект, в котором только конечные точки (переменные со значениями) являются ko.observables. Любое другое время данных, которое вы можете использовать для навигации по данным, например ... children: [...
, отображается как обычные объекты javaScript.