¿"Cómo" guardar una colección completa en Backbone.js - Backbone.sync o jQuery.ajax?

81

Soy consciente de que se puede hacer y he examinado bastantes lugares (que incluyen: ¿Las mejores prácticas para guardar una colección completa? ). Pero todavía no tengo claro "exactamente cómo" está escrito en código. (La publicación lo explica en inglés. Sería genial tener una explicación específica de JavaScript :)

Digamos que tengo una colección de modelos; los modelos en sí pueden tener colecciones anidadas. He anulado el método toJSON () de la colección principal y obtengo un objeto JSON válido. Deseo "guardar" la colección completa (JSON correspondiente), pero la red troncal no parece estar incorporada con esa funcionalidad.

var MyCollection = Backbone.Collection.extend({
model:MyModel,

//something to save?
save: function() {
   //what to write here?
 }

});

Sé que en algún lugar tienes que decir:

Backbone.sync = function(method, model, options){
/*
 * What goes in here?? If at all anything needs to be done?
 * Where to declare this in the program? And how is it called?
 */
}

Una vez que la 'vista' ha terminado con el procesamiento, es responsable de decirle a la colección que se "guarde" en el servidor (capaz de manejar una solicitud de actualización / creación masiva).

Preguntas que surgen:

  1. ¿Cómo / qué escribir en el código para "conectarlo todo"?
  2. ¿Cuál es la ubicación 'correcta' de las devoluciones de llamada y cómo especificar una devolución de llamada de "éxito / error"? Quiero decir sintácticamente? No tengo claro la sintaxis de registrar devoluciones de llamada en la red troncal ...

Si de hecho es un trabajo complicado, ¿podemos llamar a jQuery.ajax dentro de una vista y pasar el this.successMethodo this.errorMethodcomo devoluciones de llamada de éxito / error? ¿Funcionará?

Necesito sincronizarme con la forma de pensar de Backbone. Sé que definitivamente me estoy perdiendo algo, la sincronización de colecciones enteras.

Doctor
fuente
¿Puede su código del lado del servidor tomar esto como una sola solicitud? En otras palabras, ¿toda la colección de nivel superior, todos los modelos y colecciones anidadas como un solo paquete JSON? ¿O necesita guardar cada modelo individualmente? Editar: Ah, lea más de cerca, el servidor ES capaz de "actualización / creación masiva"
Edward M Smith
@Edward: ¡Sí! Lo había hecho explícito ya que generalmente es un punto de preocupación, pero no en este caso :)
PhD
Entonces, ¿cuál es la estructura de los datos que el servidor espera recibir?
Edward M Smith
@Edward: ¿Importa la estructura de los datos? El formato no es posible en un comentario, pero es así: [{postId: 1, tags: [{id: 1, name: "a"}, {id: 2, name: "b"}]}] Básicamente cada " postId "puede tener un conjunto / matriz de etiquetas que en sí mismas son objetos. Puede haber muchas publicaciones de este tipo ... No creo que el formato de datos tenga nada que ver con el problema en cuestión, a menos que me falte algo
PhD
Véase también stackoverflow.com/questions/7975316/…
philfreo

Respuestas:

64

Mi pensamiento inmediato no es anular el método en el método de guardar en Backbone.Collection, sino envolver la colección en otro Backbone.Model y anular el método toJSON en eso. Entonces Backbone.js tratará el modelo como un solo recurso y no tendrá que piratear la forma en que Backone piensa demasiado.

Tenga en cuenta que Backbone.Collection tiene un método toJSON, por lo que la mayor parte de su trabajo está hecho por usted. Solo tiene que enviar el método toJSON de su contenedor Backbone.Model a Backbone.collection.

var MyCollectionWrapper = Backbone.Model.extend({
url: "/bulkupload",

//something to save?
toJSON: function() {
    return this.model.toJSON(); // where model is the collection class YOU defined above
 }

});
bradgonesurf
fuente
3
Revisé el código fuente y parece que las colecciones no tienen un método de guardado, por lo que la anulación no debería ser un problema (si tuviera un método de guardado, el mundo sería MUCHO más fácil :)
PhD
+1 para la idea del envoltorio: limpio y dulce. No pensé en eso en absoluto. Estaba pensando que tal vez tendría que llamar a Backboard.sync directamente y simplemente pasar la colección en lugar del argumento "modelo". Sin embargo, necesitaría especificar una URL para que el modelo funcione ... ¿alguna idea? Dado que el método de sincronización internamente solo llama getURL(model)al argumento del modelo y tampoco realiza ningún tipo de comparaciones ... parece ser intencional por diseño
PhD
3
De acuerdo, muy elegante. Sin embargo, tengo un problema con esta solución: mi modelo de contenedor es siempre nuevo, por lo tanto, cuando guardo () siempre es un POST. Ya recuperé mis datos, así que cuando guarde () debería ser un PUT. Supongo que puedo codificar de forma rígida isNew () = false, o establecer una identificación falsa, pero esto no parece una solución elegante. ¿Tienes alguna sugerencia?
Scott Switzer
1
Respuesta realmente limpia, sería bueno ver cómo instanciaría CollectionWrapper.
Anthony
Sí, también funcionó para mí y +1 por el buen esfuerzo, pero si quiero enviar dos colecciones al servidor, ¿cómo lo hago?
CodeNotFound
25

Un muy simple ...

Backbone.Collection.prototype.save = function (options) {
    Backbone.sync("create", this, options);
};

... le dará a sus colecciones un método de guardado. Tenga en cuenta que esto siempre publicará todos los modelos de la colección en el servidor, independientemente de lo que haya cambiado. las opciones son opciones normales de jQuery ajax.

hacklikecrack
fuente
4
Parece correcto. Puede que la adición return Backbone.sync..sea ​​más Backbonish.
yves amsellem
Creo que, para el tipo, actualizar sería mejor que crear ... Por cierto. puede anular el toJSON del modelo o la colección, por lo que puede regular qué enviar al servidor ... (generalmente solo el atributo id necesario) En Backbone.Relational también puede establecer qué agregar al formato json.
inf3rno
1
Backbone.sync espera "crear", consulte backbonejs.org/docs/backbone.html#section-141
hacklikecrack
8

Terminé teniendo un método similar a 'guardar' y llamé $ .ajax dentro de él. Me dio más control sobre él sin la necesidad de agregar una clase contenedora como sugirió @brandgonesurfing (aunque me encanta la idea :) Como se mencionó, ya que ya tenía el método collection.toJSON () anulado, todo lo que hice fue usarlo en la llamada ajax ...

Espero que esto ayude a alguien que se tropiece con él ...

Doctor
fuente
3
Sería mejor que llamaras a Backbone.ajax (de todos modos se utiliza como proxy de jQuery, pero lo hace más fácil de mantener)
developerbmw
5

Esto realmente depende de cuál sea el contrato entre el cliente y el servidor. He aquí un ejemplo simplificado CoffeeScript donde un PUT a /parent/:parent_id/childrenla {"children":[{child1},{child2}]}reemplazará hijos de un padre con lo que hay en el PUT y retorno {"children":[{child1},{child2}]}:

class ChildElementCollection extends Backbone.Collection
  model: Backbone.Model
  initialize: ->
    @bind 'add', (model) -> model.set('parent_id', @parent.id)

  url: -> "#{@parent.url()}/children" # let's say that @parent.url() == '/parent/1'
  save: ->
    response = Backbone.sync('update', @, url: @url(), contentType: 'application/json', data: JSON.stringify(children: @toJSON()))
    response.done (models) => @reset models.children
    return response

Este es un ejemplo bastante simple, puede hacer mucho más ... realmente depende de en qué estado se encuentran sus datos cuando se ejecuta save (), en qué estado deben estar para enviarlos al servidor y lo que da el servidor espalda.

Si su servidor está bien con un PUT de [{child1},{child2], entonces su línea Backbone.sync podría cambiar a response = Backbone.sync('update', @toJSON(), url: @url(), contentType: 'application/json').

carpeliam
fuente
El atributo de las opciones "url" no es necesario aquí =)
Sergey Kamardin
5

La respuesta depende de lo que quiera hacer con la colección en el lado del servidor.

Si tiene que enviar datos adicionales con la publicación, es posible que necesite un modelo contenedor o un modelo relacional .

Con el modelo de contenedor , siempre debe escribir su propio método de análisis :

var Occupants = Backbone.Collection.extend({
    model: Person
});

var House = Backbone.Model.extend({
    url: function (){
        return "/house/"+this.id;
    },
    parse: function(response){
        response.occupants = new Occupants(response.occupants)
        return response;
    }
});

Creo que los modelos relacionales son mejores, porque puedes configurarlos más fácilmente y puedes regular con laopción includeInJSON qué atributos poner en el json que envías a tu servicio de descanso.

var House = Backbone.RelationalModel.extend({
    url: function (){
        return "/house/"+this.id;
    },
    relations: [
        {
            type: Backbone.HasMany,
            key: 'occupants',
            relatedModel: Person,
            includeInJSON: ["id"],
            reverseRelation: {
                key: 'livesIn'
            }
        }
    ]
});

Si no envía datos adicionales , puede sincronizar la colección . Debe agregar un método de guardado a su colección (o al prototipo de colección) en ese caso:

var Occupants = Backbone.Collection.extend({
    url: "/concrete-house/occupants",
    model: Person,
    save: function (options) {
        this.sync("update", this, options);
    }
});
inf3rno
fuente
3

También me sorprendió que las colecciones de Backbone no tengan un guardado integrado. Esto es lo que puse en mi colección de columna vertebral para hacerlo. Definitivamente no quiero iterar a través de cada modelo de la colección y guardar de forma independiente. Además, estoy usando Backbone en el backend usando Node, así que estoy anulando el nativo Backbone.syncpara guardarlo en un archivo plano en mi pequeño proyecto, pero el código debería ser prácticamente el mismo:

    save: function(){                                                                                                                                                                                                                                                                                                                                                     
      Backbone.sync('save', this, {                                                                                                                                                                                                                                                                                                                                     
        success: function(){                                                                                                                                                                                                                                                                                                                                          
          console.log('users saved!');                                                                                                                                                                                                                                                                                                                              
        }                                                                                                                                                                                                                                                                                                                                                             
      });                                                                                                                                                                                                                                                                                                                                                               
    }
Mauvis Ledford
fuente
También tiene sentido pasar opciones, comosave: function (options) { Backbone.sync('save', this, options); }
Matt Fletcher
3

Viejo hilo que sé, lo que terminé haciendo es lo siguiente:

Backbone.Collection.prototype.save = function (options) {
            // create a tmp collection, with the changed models, and the url
            var tmpCollection = new Backbone.Collection( this.changed() );
            tmpCollection.url = this.url;
            // sync
            Backbone.sync("create", tmpCollection, options);
        };
        Backbone.Collection.prototype.changed = function (options) {
            // return only the changed models.
            return this.models.filter( function(m){
                return m.hasChanged()
            });
        };
// and sync the diffs.
self.userCollection.save();

Bastante estresante hacia adelante :)

Rene Weteling
fuente
2

He aquí un ejemplo sencillo:

var Books = Backbone.Collection.extend({
model: Book,
url: function() {
  return '/books/';
},
save: function(){
  Backbone.sync('create', this, {
    success: function() {
      console.log('Saved!');
    }
  });
 }
});

Cuando llame al método save () en su colección, enviará una solicitud de método PUT a la URL definida.

Gaurav Gupta
fuente
Me preguntaba si podría ayudarme a resolver el problema de guardado de mi colección, ahora mismo jsfiddle.net/kyllle/f1h4cz7f/3 toJSON () actualiza cada modelo pero luego el guardado no parece tener ningún dato.
Styler
1

Probaría algo como:

var CollectionSync = function(method, model, [options]) {
    // do similar things to Backbone.sync
}

var MyCollection = Backbone.Collection.extend({
    sync: CollectionSync,
    model: MyModel,
    getChanged: function() {
        // return a list of models that have changed by checking hasChanged()
    },
    save: function(attributes, options) {
        // do similar things as Model.save
    }
});

( https://stackoverflow.com/a/11085198/137067 )

philfreo
fuente
1

La respuesta aceptada es bastante buena, pero puedo ir un paso más allá y darle un código que garantizará que se activen los eventos adecuados para sus oyentes y al mismo tiempo le permitirá pasar las devoluciones de llamada de eventos ajax de la opción:

save: function( options ) {
  var self = this;

  var success = options.success;
  var error = options.error;
  var complete = options.complete;

  options.success = function( response, status, xhr ) {
    self.trigger('sync', self, response, options);
    if (success) return success.apply(this, arguments);
  };

  options.error = function( response, status, xhr ) {
    self.trigger('error', self, response, options);
    if (error) return error.apply(this, arguments);
  };

  options.complete = function( response, status, xhr ) {
    if (complete) return complete.apply(this, arguments);
  }

  Backbone.sync('create', this, options);
}
Acelerador
fuente
0

Para cualquiera que todavía esté usando backbone.js en 2017, la respuesta aceptada no funciona.

Intente eliminar la anulación de toJSON () en el modelo de contenedor y llame a toJSON en la colección cuando cree una instancia del contenedor de modelo.

new ModelWrapper(Collection.toJSON());
ceros y unos
fuente