template knockoutjs knockout data component javascript mvvm knockout.js knockout-mapping-plugin knockout-2.0

javascript - knockoutjs - Knockout.js Haga que cada objeto anidado sea Observable



knockout components (6)

Al utilizar Knockout-Plugin podemos hacer que los elementos secundarios sean observables. Tenemos muchas opciones para administrar cómo queremos que nuestros datos sean observables.

Aquí hay un código de muestra:

var data = { people: [ { id: 1, age: 25, child : [ {id : 1,childname : "Alice"}, {id : 2,childname : "Wonderland"} ] }, {id: 2, age: 35} ], Address:[ { AddressID : 1, City : "NewYork", cities : [ { cityId : 1, cityName : "NewYork" }, { cityId :2, cityName : "California" } ] }, { AddressID : 2, City : "California", cities : [ { cityId :1, cityName : "NewYork" }, { cityId :2, cityName : "California" } ] } ], isSelected : true, dataID : 6 }; var mappingOptions = { people: { create: function(options) { console.log(options); return ko.mapping.fromJS(options.data, childmappingOptions); } }, Address: { create: function(options) { console.log(options); return ko.mapping.fromJS(options.data, childmappingOptions); } } }; var childmappingOptions = { child: { create: function(options) { return ko.mapping.fromJS(options.data, { observe: ["id","childname"]}); } }, cities :{ create: function(options) { return ko.mapping.fromJS(options.data, { observe: ["cityId","cityName"]}); } } }; var r = ko.mapping.fromJS(data, mappingOptions);

Adjunto un violín funcional: http://jsfiddle.net/wmqTx/5/

Estoy usando Knockout.js como una biblioteca de MVVM para vincular mis datos a algunas páginas. Actualmente estoy creando una biblioteca para realizar llamadas REST a un servicio web. Mi servicio web RESTful devuelve una estructura simple:

{ id : 1, details: { name: "Johnny", surname: "Boy" } }

Tengo un padre principal observable, myObject . Cuando lo hago

myObject(ko.mapping.fromJS(data))

los observables en myObject son:

  • id
  • name
  • surname

¿Cómo puedo hacer que los details (y teóricamente cualquier objeto en la estructura sea observable)? Necesito este comportamiento para poder establecer un observable calculado en los detalles y ser notado tan pronto como cambie alguno de los datos internos.

He configurado una función recursiva básica que debería hacer el truco. Por supuesto, myObject.details no se vuelve observable.

// Makes every object in the tree an observable. var makeAllObservables = function () { makeChildrenObservables(myObject); }; var makeChildrenObservables = function (object) { // Make the parent an observable if it''s not already if (!ko.isObservable(object)) { if ($.isArray(object)) object = ko.observableArray(object); else object = ko.observable(object); } // Loop through its children for (var child in object()) { makeChildrenObservables(object()[child]); } };

Estoy bastante seguro de que se trata de referencias incorrectas, pero ¿cómo puedo solucionar esto? Gracias.


Extenderé la respuesta de Paolo del Mundo (que creo que podría ser la mejor y la única solución en este momento) con mi solución de ejemplo.

Considera el objeto original de frapontillo :

{ id : 1, details: { name: "Johnny", surname: "Boy" } }

La propiedad de los details sí misma es un objeto y, como tal, NO PUEDE ser observable. Lo mismo se aplica a la propiedad del User en el siguiente ejemplo, que también es un objeto. Esos dos objetos no pueden ser observables, pero pueden ser sus propiedades LEAF .

Cada propiedad de hoja de su árbol / modelo de datos PUEDE SER OBSERVABLE. La manera más fácil de lograr eso es definir adecuadamente el modelo de mapeo antes de pasarlo al plugin de mapeo como parámetro.

Vea mi ejemplo a continuación.

EJEMPLO:

Digamos que necesitamos mostrar una página / vista html donde tenemos una lista de usuarios en una grilla. Al lado de la cuadrícula Usuarios se muestra un formulario para editar a un usuario seleccionado de la grilla.

PASO 1: DEFINIENDO LOS MODELOS

function UsersEdit() { this.User = new User(); // model for the selected user this.ShowUsersGrid = ko.observable(false); // defines the grid''s visibility (false by default) this.ShowEditForm = ko.observable(false); // defines the selected user form''s visibility (false by default) this.AllGroups = []; // NOT AN OBSERVABLE - when editing a user in the user''s form beside the grid a multiselect of all available GROUPS is shown (to place the user in one or multiple groups) this.AllRoles = []; // NOT AN OBSERVABLE - when editing a user in the user''s form beside the grid a multiselect of all available ROLES is shown (to assign the user one or multiple roles) } function User() { this.Id = ko.observable(); this.Name = ko.observable(); this.Surname = ko.observable(); this.Username = ko.observable(); this.GroupIds = ko.observableArray(); // the ids of the GROUPS that this user belongs to this.RoleIds = ko.observableArray(); // the ids of the ROLES that this user has }

PASO 2: MAPEO (PARA OBTENER OBSERVABLES ANIDADO)

Digamos que este es su modelo JSON sin procesar con datos que desea mapear y obtener un modelo KO con observables anidados.

var model = { User: { Id: 1, Name: "Johnny", Surname = "Boy", Username = "JohhnyBoy", GroupIds = [1, 3, 4], RoleIds = [1, 2, 5] } };

Ahora que todo esto está definido, puedes mapear:

var observableUserEditModel = ko.mapping.fromJS(model, new UsersEdit());

¡Y TU ESTAS LISTO! :)

El observableUserEditModel contendrá todos sus observables, incluso los anidados. Ahora, lo único que debe tener en cuenta para probar esto es enlazar el objeto observableUserEditModel con su HTML. Sugerencia: utilice el enlace with y compruebe la estructura de datos observableUserEditModel UserEditModel observableUserEditModel insertándolo en su vista HTML:

<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>


No creo que el knockout tenga una forma integrada de observar los cambios en los elementos secundarios. Si entiendo su pregunta, cuando alguien cambia el nombre, quiere que se note un cambio en los detalles como una entidad. ¿Puedes dar un ejemplo concreto de cómo usarías esto? ¿Utilizaría una suscripción a los detalles observables para realizar alguna acción?

La razón por la que su código no hace que los detalles sean observables es porque javascript pasa por valor, por lo que cambiar el valor del argumento ''objeto'' en su función no cambia el valor real que pasó, solo el valor del argumento dentro de su función.

Editar

Si los cambios se propagarán automáticamente a los padres, creo que esto debería hacer que todos los niños sean observables, pero su raíz que pasa la primera vez ya debería ser observable.

// object should already be observable var makeChildrenObservables = function (object) { if(!ko.isObservable(object)) return; // Loop through its children for (var child in object()) { if (!ko.isObservable(object()[child])) { object()[child] = ko.observable(object()[child]); } makeChildrenObservables(object()[child]); } };


Por lo que he experimentado, ko.mapping.fromJS no hace un observable fuera de un objeto.

Digamos que tienes este constructor de ViewModel:

var VM = function(payload) { ko.mapping.fromJS(payload, {}, this); }

y este objeto de datos:

var data1 = { name: ''Bob'', class: { name: ''CompSci 101'', room: 112 }

}

y usa data1 para crear VM1:

var VM1 = new VM(data1);

Entonces VM1.class no será un ko.observable, es un objeto simple de JavaScript.

Si luego crea otro modelo de vista usando un objeto de datos con un miembro de clase nulo, es decir:

var data2 = { name: ''Bob'', class: null } var VM2 = new VM(data2);

entonces VM2.class es un ko.observable.

Si luego ejecuta:

ko.mapping(data1, {}, VM2)

entonces VM2.class sigue siendo ko.observable.

Por lo tanto, si crea un ViewModel a partir de un objeto de datos semilla donde los miembros del objeto son nulos, y luego los popupla con un objeto de datos poblado, tendrá miembros observables de la clase.

Esto lleva a problemas, porque a veces los miembros del objeto son observables, y algunas veces no lo son. Los enlaces de formularios funcionarán con VM1 y no funcionarán con VM2. Sería bueno si ko.mapping.fromJS siempre hiciera que todo fuera ko.observable, ¿así que era consistente?


Tal vez en una versión futura podría haber una opción de configuración que haga que ko.mapping.fromJS cree siempre observables. Se podría habilitar para nuevos proyectos o después de actualizar los enlaces de un proyecto existente.

Lo que hago para evitar este problema es asegurar que las semillas modelo siempre tengan propiedades de miembro de Objetos pobladas, en cada nivel. De esta forma, todas las propiedades de los objetos se asignan como POJO (Objetos JavaScript simples), por lo que ViewModel no los inicializa como ko.observables. Evita el problema "a veces observables, a veces no".

Un saludo, Mike


Yo usaría el complemento de mapeo knockout .

var jsonData = { id : 1, details: { name: "Johnny", surname: "Boy" } } var yourMapping = { ''details'': { create: function(options) { return Details(options.data); } } } function Details(data) { ko.mapping.fromJS(data, {}, this); } function YourObjectName() { ko.mapping.fromJS(jsonData, yourMapping, this); }

Esto creará su jerarquía de objetos con todos los niños como observables.