utils tutorial knockout example mvvm mapping knockout.js knockout-mapping-plugin

mvvm - tutorial - Asignación de objetos profundamente jerárquicos a clases personalizadas utilizando el plugin de mapeo knockout



ko utils stringify json (4)

Usando el complemento de mapeo knockout ( http://knockoutjs.com/documentation/plugins-mapping.html ) ¿puedes mapear un objeto profundamente jerárquico?

Si tengo un objeto con múltiples niveles:

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

¿Cómo puedo asignarlo a mis clases personalizadas en 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); }

¿Puede el complemento de mapeo mapear recursivamente estos datos en una jerarquía de mis objetos personalizados?


Algo como esto ( 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);​

Esta muestra simplemente mapea un conjunto infinitamente anidado de datos JSON, y puedo decir que de hecho usé este código exacto en una aplicación que funciona muy bien.

Algunas de las funciones adicionales como

selectBackNode y selectParentNode

te permite volver a subir al árbol.

Mientras navega por el ejemplo, la etiqueta principal se convierte en un enlace para permitir subir un nivel, y algunos de los nodos hoja tienen un botón Atrás que les permite retroceder en el árbol en un número determinado de niveles.

--EDITAR--

Si los nodos de hoja no tienen una matriz secundaria, es posible que surja un problema cuando se introducen datos adicionales que no existen en el modelo.


Desde mi experiencia, yo diría que no debería tener ningún problema.

Usaría la siguiente línea -

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

A continuación, establezca un punto de interrupción en la línea siguiente al mirar el objeto generado en su depurador (Chrome o FF + Firebug funciona mejor). De esta forma sabrá si ko.mapping generará un modelo de vista que satisfaga sus necesidades.

Normalmente, genera un objeto donde solo los puntos finales (variables con valores) son ko.observables. Cualquiera de los otros tiempos de datos que puede usar para navegar a través de los datos, como ... children: [... se muestran como objetos javaScript normales.


Si no desea las opciones de asignación anidadas (creando un objeto de mapa ko para cada nivel de nodo), puede aprovechar el hecho de que las opciones de asignación de ko para crear le dan acceso al objeto primario. Algo como esto:

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);

De esa manera solo tienes 1 copia del mapa, en tu prototipo de clase (o podría ser cualquier función). Si también quiere que Folder.parent sea observable, puede hacer data.parent = parent; dentro de la función del mapa y no pasar como un parámetro al constructor de la carpeta, o hacer eso dentro del constructor de la carpeta en lugar de self.parent = parent;


Utilicé el enfoque en esta answer para crear una jerarquía de casillas de verificación donde los nodos con hijos son contraíbles y cuando marcas o desmarcas a los padres, sus descendientes se verifican / desactivan.

Ver modelo

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 (ver)

<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>