Knockout.js Hace que cada objeto anidado sea Observable


Estoy usando Knockout.js como biblioteca MVVM para enlazar mis datos a algunas páginas. Actualmente estoy construyendo una biblioteca para hacer 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 details (y teóricamente cualquier objeto en la estructura sea observable)? Necesito este comportamiento para que pueda establecer un observable computado en los detalles y ser notado tan pronto como cualquiera de los cambios de datos internos.

He configurado una función recursiva básica que debería hacer el truco. No, por supuesto, myObject.details no se convierte en un 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 resolver esto? Agradecer.

Author: frapontillo, 2012-05-11

6 answers

No creo que knockout tenga una forma incorporada de observar los cambios en los elementos secundarios. Si entiendo su pregunta, cuando alguien cambia el nombre, desea que se note un cambio en los detalles como entidad. ¿Puede dar un ejemplo concreto de cómo usaría esto? ¿Usarías 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 es un valor de paso por, por lo que cambia el valor del 'objeto' el argumento 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, esto debería hacer que todos los niños sean observables, creo, pero su raíz que pase 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]);
    }
};
 14
Author: Jason Goemaat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-17 17:09:08

Usaría el complemento knockout mapping.

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 hijos como observables.

 22
Author: Paolo del Mundo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-11 18:54:37

Por lo que he experimentado, ko.asignación.fromJS no hace observable un objeto.

Digamos que tienes este constructor 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);

Luego VM1.la clase no será un ko.observable, es un objeto javascript simple.

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

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

Luego VM2.la clase es a ko.observable.

Si luego ejecuta:

ko.mapping(data1, {}, VM2)

Luego VM2.la clase 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 null, y luego los pliega con un objeto de datos poblado, tendrá miembros de clase observables.

Esto conduce a problemas, porque a veces los miembros del objeto son observables, y a veces no lo son. Los enlaces de formulario funcionarán con VM1 y no con VM2. Sería bueno si ko.asignación.fromJS siempre hizo todo un ko.observable por lo que era consistente?

 3
Author: Beans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-27 03:23:40

Usando 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 ejemplo:

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

He adjuntado un violín de trabajo: http://jsfiddle.net/wmqTx/5 /

 3
Author: AR M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-21 13:31:20

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

Considere el objeto original de frapontillo :

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

La propiedad details en sí es un objeto y como tal NO PUEDE ser observable. Lo mismo ocurre con la propiedad User en el ejemplo siguiente, que también es un objeto. Esos dos objetos no pueden ser observables pero sus propiedades FOLIARES sí pueden be .

Cada propiedad hoja de su árbol / modelo de datos PUEDE SER OBSERVABLE. La forma más fácil de lograrlo es que defina correctamente el modelo de asignación antes de pasarlo al complemento de asignación 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 cuadrícula. Al lado de la cuadrícula de usuarios hay un formulario para editar un usuario seleccionado de la cuadrícula demostrar.

PASO 1: DEFINIR 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 ANIDADOS)

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 YA ESTÁ! :)

El observableUserEditModel albergará todos tus observables, incluso los anidados. Ahora la única cosa que necesita tener cuidado para probar esto es enlazar el objeto observableUserEditModel con su HTML. Sugerencia: use el enlace with y pruebe la estructura de datos observable observableUserEditModel insertando esto en su vista HTML:

<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
 2
Author: AlexRebula,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-04 13:59:42

Tal vez en una versión futura podría haber una opción de configuración que causa ko.asignación.fromJS para crear siempre observables. Podría habilitarse para nuevos proyectos o después de actualizar los enlaces de un proyecto existente.

Lo que hago para evitar este problema es asegurarme de que las semillas del modelo siempre tengan las propiedades de los miembros de los objetos pobladas, en todos los niveles. De esta manera, todas las propiedades de los objetos se mapean como POJOs (Objetos Javascript Antiguos simples), por lo que ViewModel no los inicializa como ko.observables. Evita el tema "a veces observables, a veces no".

Saludos cordiales, Mike

 1
Author: Beans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-28 03:32:42