Modelos anidados en Backbone.js, cómo acercarse


Tengo el siguiente JSON proporcionado desde un servidor. Con esto, quiero crear un modelo con un modelo anidado. No estoy seguro de cuál es el camino para lograrlo.

//json
[{
    name : "example",
    layout : {
        x : 100,
        y : 100,
    }
}]

Quiero que estos se conviertan en dos modelos troncales anidados con la siguiente estructura:

// structure
Image
    Layout
...

Así que defino el modelo de diseño de la siguiente manera:

var Layout = Backbone.Model.extend({});

Pero, ¿cuál de las dos técnicas (si las hay) a continuación debo usar para definir el modelo de imagen? A o B ¿abajo?

A

var Image = Backbone.Model.extend({
    initialize: function() {
        this.set({ 'layout' : new Layout(this.get('layout')) })
    }
});

o, B

var Image = Backbone.Model.extend({
    initialize: function() {
        this.layout = new Layout( this.get('layout') );
    }
});
Author: dvhh, 2011-06-30

12 answers

Tengo el mismo problema mientras escribo mi aplicación Backbone. Tener que lidiar con modelos incrustados/anidados. Hice algunos ajustes que pensé que era una solución bastante elegante.

Sí, puede modificar el método parse para cambiar los atributos a en el objeto, pero todo eso es en realidad un código IMO bastante inalcanzable, y se siente más como un truco que como una solución.

Esto es lo que sugiero para su ejemplo:

Primero defina su Modelo de Diseño como tan.

var layoutModel = Backbone.Model.extend({});

Entonces aquí está su modelo de imagen:

var imageModel = Backbone.Model.extend({

    model: {
        layout: layoutModel,
    },

    parse: function(response){
        for(var key in this.model)
        {
            var embeddedClass = this.model[key];
            var embeddedData = response[key];
            response[key] = new embeddedClass(embeddedData, {parse:true});
        }
        return response;
    }
});

Observe que no he manipulado el modelo en sí, sino que simplemente devuelvo el objeto deseado del método de análisis.

Esto debería asegurar la estructura del modelo anidado cuando esté leyendo desde el servidor. Ahora, se dará cuenta de que el ahorro o la configuración no se maneja aquí porque siento que tiene sentido para que usted establezca el modelo anidado explícitamente utilizando el modelo adecuado.

Como entonces:

image.set({layout : new Layout({x: 100, y: 100})})

También tenga en cuenta que en realidad está invocando el método parse en su modelo anidado llamando a:

new embeddedClass(embeddedData, {parse:true});

Puede definir tantos modelos anidados en el campo model como necesite.

Por supuesto, si desea ir tan lejos como guardar el modelo anidado en su propia tabla. Esto no sería suficiente. Pero en el caso de leer y guardar el objeto como un todo, esta solución debería ser suficiente.

 98
Author: rycfung,
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-08 14:39:04

Estoy publicando este código como un ejemplo de la sugerencia de Peter Lyon de redefinir el análisis. Tenía la misma pregunta y esto funcionó para mí (con un backend de Rails). Este código está escrito en Coffeescript. Hice algunas cosas explícitas para personas que no estaban familiarizadas con él.

class AppName.Collections.PostsCollection extends Backbone.Collection
  model: AppName.Models.Post

  url: '/posts'

  ...

  # parse: redefined to allow for nested models
  parse: (response) ->  # function definition
     # convert each comment attribute into a CommentsCollection
    if _.isArray response
      _.each response, (obj) ->
        obj.comments = new AppName.Collections.CommentsCollection obj.comments
    else
      response.comments = new AppName.Collections.CommentsCollection response.comments

    return response

O, en JS

parse: function(response) {
  if (_.isArray(response)) {
    return _.each(response, function(obj) {
      return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
    });
  } else {
    response.comments = new AppName.Collections.CommentsCollection(response.comments);
  }
  return response;
};
 16
Author: Eric Hu,
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-08-29 15:19:16

No estoy seguro de que Backbone tenga una forma recomendada de hacer esto. ¿El objeto de presentación tiene su propio ID y registro en la base de datos back-end? Si es así usted puede hacer su propio Modelo como usted tiene. Si no, puede dejarlo como un documento anidado, solo asegúrese de convertirlo a y desde JSON correctamente en los métodos save y parse. Si terminas tomando un enfoque como este, creo que tu ejemplo A es más consistente con backbone ya que set se actualizará correctamente attributes, pero de nuevo, no estoy seguro de lo que Backbone hace con los modelos anidados por defecto. Es probable que necesites algún código personalizado para manejar esto.

 11
Author: Peter Lyons,
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
2011-06-30 14:35:50

Use Backbone.AssociatedModel de Backbone-associations :

    var Layout = Backbone.AssociatedModel.extend({
        defaults : {
            x : 0,
            y : 0
        }
    });
    var Image = Backbone.AssociatedModel.extend({
        relations : [
            type: Backbone.One,
            key : 'layout',
            relatedModel : Layout          
        ],
        defaults : {
            name : '',
            layout : null
        }
    });
 11
Author: Jaynti Kanani,
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-09-23 04:08:23

Yo iría con la opción B si quieres mantener las cosas simples.

Otra buena opción sería usar Backbone-Relational. Simplemente definirías algo como:

var Image = Backbone.Model.extend({
    relations: [
        {
            type: Backbone.HasOne,
            key: 'layout',
            relatedModel: 'Layout'
        }
    ]
});
 8
Author: philfreo,
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
2013-05-07 06:35:13

Uso el complemento Backbone DeepModel para modelos y atributos anidados.

Https://github.com/powmedia/backbone-deep-model

Puede enlazar para cambiar eventos y niveles profundos. por ejemplo: model.on('change:example.nestedmodel.attribute', this.myFunction);

 6
Author: Mark,
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-09-23 04:17:01

Versión CoffeeScript de la respuesta hermosa de rycfung:

class ImageModel extends Backbone.Model
  model: {
      layout: LayoutModel
  }

  parse: (response) =>
    for propName,propModel of @model
      response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )

    return response

¿No es eso dulce? ;)

 5
Author: Dan Fox,
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
2013-09-09 07:12:14

Tuve el mismo problema y he estado experimentando con el código en la respuesta de rycfung, que es una gran sugerencia.
Si, sin embargo, no desea set los modelos anidados directamente, o no desea constantemente pass {parse: true} en el options, otro enfoque sería redefinir set en sí.

{[19] {} En[25]}columna vertebral 1.0.0, set se llama en constructor, unset, clear, fetch y save.

Considere el siguiente super modelo , para todos los modelos que necesidad de anidar modelos y / o colecciones.

/** Compound supermodel */
var CompoundModel = Backbone.Model.extend({
    /** Override with: key = attribute, value = Model / Collection */
    model: {},

    /** Override default setter, to create nested models. */
    set: function(key, val, options) {
        var attrs, prev;
        if (key == null) { return this; }

        // Handle both `"key", value` and `{key: value}` -style arguments.
        if (typeof key === 'object') {
            attrs = key;
            options = val;
        } else {
            (attrs = {})[key] = val;
        }

        // Run validation.
        if (options) { options.validate = true; }
        else { options = { validate: true }; }

        // For each `set` attribute, apply the respective nested model.
        if (!options.unset) {
            for (key in attrs) {
                if (key in this.model) {
                    if (!(attrs[key] instanceof this.model[key])) {
                        attrs[key] = new this.model[key](attrs[key]);
                    }
                }
            }
        }

        Backbone.Model.prototype.set.call(this, attrs, options);

        if (!(attrs = this.changedAttributes())) { return this; }

        // Bind new nested models and unbind previous nested models.
        for (key in attrs) {
            if (key in this.model) {
                if (prev = this.previous(key)) {
                    this._unsetModel(key, prev);
                }
                if (!options.unset) {
                    this._setModel(key, attrs[key]);
                }
            }
        }
        return this;
    },

    /** Callback for `set` nested models.
     *  Receives:
     *      (String) key: the key on which the model is `set`.
     *      (Object) model: the `set` nested model.
     */
    _setModel: function (key, model) {},

    /** Callback for `unset` nested models.
     *  Receives:
     *      (String) key: the key on which the model is `unset`.
     *      (Object) model: the `unset` nested model.
     */
    _unsetModel: function (key, model) {}
});

Observe que model, _setModel y _unsetModel se dejan en blanco a propósito. En este nivel de abstracción, probablemente no pueda definir ninguna acción razonable para las devoluciones de llamada. Sin embargo, es posible que desee sobrescribirlos en los submodelos que se extienden CompoundModel.
Esas devoluciones de llamada son útiles, por ejemplo, para enlazar oyentes y propagar eventos change.


Ejemplo:

var Layout = Backbone.Model.extend({});

var Image = CompoundModel.extend({
    defaults: function () {
        return {
            name: "example",
            layout: { x: 0, y: 0 }
        };
    },

    /** We need to override this, to define the nested model. */
    model: { layout: Layout },

    initialize: function () {
        _.bindAll(this, "_propagateChange");
    },

    /** Callback to propagate "change" events. */
    _propagateChange: function () {
        this.trigger("change:layout", this, this.get("layout"), null);
        this.trigger("change", this, null);
    },

    /** We override this callback to bind the listener.
     *  This is called when a Layout is set.
     */
    _setModel: function (key, model) {
        if (key !== "layout") { return false; }
        this.listenTo(model, "change", this._propagateChange);
    },

    /** We override this callback to unbind the listener.
     *  This is called when a Layout is unset, or overwritten.
     */
    _unsetModel: function (key, model) {
        if (key !== "layout") { return false; }
        this.stopListening();
    }
});

Con esto, tiene la creación automática del modelo anidado y propagación de eventos. El uso de la muestra también se proporciona y se prueba:

function logStringified (obj) {
    console.log(JSON.stringify(obj));
}

// Create an image with the default attributes.
// Note that a Layout model is created too,
// since we have a default value for "layout".
var img = new Image();
logStringified(img);

// Log the image everytime a "change" is fired.
img.on("change", logStringified);

// Creates the nested model with the given attributes.
img.set("layout", { x: 100, y: 100 });

// Writing on the layout propagates "change" to the image.
// This makes the image also fire a "change", because of `_propagateChange`.
img.get("layout").set("x", 50);

// You may also set model instances yourself.
img.set("layout", new Layout({ x: 100, y: 100 }));

Salida:

{"name":"example","layout":{"x":0,"y":0}}
{"name":"example","layout":{"x":100,"y":100}}
{"name":"example","layout":{"x":50,"y":100}}
{"name":"example","layout":{"x":100,"y":100}}
 2
Author: afsantos,
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
2017-05-23 12:02:22

Me doy cuenta de que llego tarde a esta fiesta, pero recientemente lanzamos un plugin para lidiar exactamente con este escenario. Se llama backbone-nestify.

Así que su modelo anidado permanece sin cambios:

var Layout = Backbone.Model.extend({...});

Luego use el plugin al definir el modelo contenedor (usando Subrayado.extender):

var spec = {
    layout: Layout
};
var Image = Backbone.Model.extend(_.extend({
    // ...
}, nestify(spec));

Después de eso, suponiendo que tiene un modelo m que es una instancia de Image, y ha establecido el JSON de la pregunta en m, puede hacer:

m.get("layout");    //returns the nested instance of Layout
m.get("layout|x");  //returns 100
m.set("layout|x", 50);
m.get("layout|x");  //returns 50
 2
Author: Scott Bale,
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-24 23:59:35

Use backbone-forms

Soporta formas anidadas, modelos y toJSON. TODOS ANIDADOS

var Address = Backbone.Model.extend({
    schema: {
    street:  'Text'
    },

    defaults: {
    street: "Arteaga"
    }

});


var User = Backbone.Model.extend({
    schema: {
    title:      { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
    name:       'Text',
    email:      { validators: ['required', 'email'] },
    birthday:   'Date',
    password:   'Password',
    address:    { type: 'NestedModel', model: Address },
    notes:      { type: 'List', itemType: 'Text' }
    },

    constructor: function(){
    Backbone.Model.apply(this, arguments);
    },

    defaults: {
    email: "[email protected]"
    }
});

var user = new User();

user.set({address: {street: "my other street"}});

console.log(user.toJSON()["address"]["street"])
//=> my other street

var form = new Backbone.Form({
    model: user
}).render();

$('body').append(form.el);
 2
Author: David Rz Ayala,
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-03-06 08:13:24

Si no desea agregar otro framework, podría considerar crear una clase base con set y toJSON reemplazados y usarlo de la siguiente manera:

// Declaration

window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
  nestedTypes: {
    background: window.app.viewer.Model.Image,
    images: window.app.viewer.Collection.MediaCollection
  }
});

// Usage

var gallery = new window.app.viewer.Model.GallerySection({
    background: { url: 'http://example.com/example.jpg' },
    images: [
        { url: 'http://example.com/1.jpg' },
        { url: 'http://example.com/2.jpg' },
        { url: 'http://example.com/3.jpg' }
    ],
    title: 'Wow'
}); // (fetch will work equally well)

console.log(gallery.get('background')); // window.app.viewer.Model.Image
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
console.log(gallery.get('title')); // plain string

Necesitarás BaseModel de esta respuesta (disponible, si te apetece, como una síntesis).

 1
Author: Dan Abramov,
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
2017-05-23 12:02:22

También tenemos este problema y un trabajador de equipo ha implementado un plugin llamado backbone-nested-attributes.

El uso es muy simple. Ejemplo:

var Tree = Backbone.Model.extend({
  relations: [
    {
      key: 'fruits',
      relatedModel: function () { return Fruit }
    }
  ]
})

var Fruit = Backbone.Model.extend({
})

Con esto, el modelo de Árbol puede acceder a las frutas:

tree.get('fruits')

Puede ver más información aquí:

Https://github.com/dtmtec/backbone-nested-attributes

 1
Author: Gustavo Kloh,
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-03-20 17:52:12