javascript BackboneJS problemas de representación




jquery node.js (3)

Representación parcial de vistas

Para minimizar la representación completa de su jerarquía DOM, puede configurar nodos especiales en su DOM que reflejarán las actualizaciones en una propiedad determinada.

Usemos esta plantilla de subrayado simple, una lista de nombres:

<ul>
  <% _(children).each(function(model) { %>
    <li>
        <span class='model-<%= model.cid %>-name'><%= model.name %></span> :
        <span class='model-<%= model.cid %>-name'><%= model.name %></span>
    </li>
  <% }); %>
</ul>

Observe el model-<%= model.cid %>-name clase model-<%= model.cid %>-name , este será nuestro punto de inyección.

Luego podemos definir una vista base (o modificar Backbone.View) para llenar estos nodos con los valores apropiados cuando se actualicen:

var V = Backbone.View.extend({
    initialize: function () {
        // bind all changes to the models in the collection
        this.collection.on('change', this.autoupdate, this);
    },

    // grab the changes and fill any zone set to receive the values
    autoupdate: function (model) {
        var _this = this,
            changes = model.changedAttributes(),
            attrs = _.keys(changes);

        _.each(attrs, function (attr) {
            _this.$('.model-' + model.cid + '-' + attr).html(model.get(attr));
        });
    },

    // render the complete template
    // should only happen when there really is a dramatic change to the view
    render: function () {
        var data, html;

        // build the data to render the template
        // this.collection.toJSON() with the cid added, in fact
        data = this.collection.map(function (model) {
            return _.extend(model.toJSON(), {cid: model.cid});
        });

        html = template({children: data});
        this.$el.html(html);

        return this;
    }
});

El código podría variar un poco para acomodar un modelo en lugar de una colección. Un violín para jugar con http://jsfiddle.net/nikoshr/cfcDX/

Limitando las manipulaciones DOM

Delegar el renderizado a las subvistas puede ser costoso, sus fragmentos de HTML deben insertarse en el DOM del padre. Echa un vistazo a esta prueba jsperf comparando diferentes métodos de renderizado

La esencia de esto es que generar la estructura HTML completa y luego aplicar vistas es mucho más rápido que crear vistas y subvistas y luego poner en cascada el renderizado. Por ejemplo,

<script id="tpl-table" type="text/template">
    <table>
        <thead>
            <tr>
                <th>Row</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
        <% _(children).each(function(model) { %>
            <tr id='<%= model.cid %>'>
                <td><%= model.row %></td>
                <td><%= model.name %></td>
            </tr>
        <% }); %>
        </tbody>
     </table>
</script>
var ItemView = Backbone.View.extend({
});

var ListView = Backbone.View.extend({
    render: function () {
        var data, html, $table, template = this.options.template;

        data = this.collection.map(function (model) {
            return _.extend(model.toJSON(), {
                cid: model.cid
            });
        });

        html = this.options.template({
            children: data
        });

        $table = $(html);

        this.collection.each(function (model) {
            var subview = new ItemView({
                el: $table.find("#" + model.cid),
                model: model
            });
        });

        this.$el.empty();
        this.$el.append($table);

        return this;
    }
});


var view = new ListView({
    template: _.template($('#tpl-table').html()),
    collection: new Backbone.Collection(data)
});

http://jsfiddle.net/nikoshr/UeefE/

Tenga en cuenta que jsperf muestra que la plantilla se puede dividir en subplanos sin demasiada penalización, lo que le permitiría proporcionar una representación parcial de las filas.

En una nota relacionada, no trabaje en nodos conectados al DOM, esto causará reflujos innecesarios. Cree un DOM nuevo o separe el nodo antes de manipularlo.

Aplastando zombies

Derick Bailey escribió un excelente artículo sobre el tema de la erradicación de puntos de vista de zombies

Básicamente, debe recordar que cuando descarta una vista, debe desvincular a todos los oyentes y realizar cualquier limpieza adicional, como destruir las instancias del complemento jQuery. Lo que uso es una combinación de métodos similares a los que Derick usa en Backbone.Marionette :

var BaseView = Backbone.View.extend({

    initialize: function () {
        // list of subviews
        this.views = [];
    },

    // handle the subviews
    // override to destroy jQuery plugin instances
    unstage: function () {
        if (!this.views) {
            return;
        }

        var i, l = this.views.length;

        for (i = 0; i < l; i = i + 1) {
            this.views[i].destroy();
        }
        this.views = [];
    },

    // override to setup jQuery plugin instances
    stage: function () {
    },

    // destroy the view
    destroy: function () {
        this.unstage();
        this.remove();
        this.off();

        if (this.collection) {
            this.collection.off(null, null, this);
        }
        if (this.model) {
            this.model.off(null, null, this);
        }
    }
});

La actualización de mi ejemplo anterior para dar a las filas un comportamiento arrastrable se vería así:

var ItemView = BaseView.extend({
    stage: function () {
        this.$el.draggable({
            revert: "invalid",
            helper: "clone"
        });
    },

    unstage: function () {
        this.$el.draggable('destroy');
        BaseView.prototype.unstage.call(this);
    }
});

var ListView = BaseView.extend({

    render: function () {
       //same as before

        this.unstage();
        this.collection.each(function (model) {
            var subview = new ItemView({
                el: $table.find("#" + model.cid),
                model: model
            });
            subview.stage();
            this.views.push(subview);
        }, this);
        this.stage();

        this.$el.empty();
        this.$el.append($table);

        return this;
    }
});

http://jsfiddle.net/nikoshr/yL7g6/

La destrucción de la vista raíz recorrerá la jerarquía de vistas y realizará las limpiezas necesarias.

NB: lo siento por el código JS, no estoy lo suficientemente familiarizado con Coffeescript para proporcionar fragmentos precisos.

Durante los últimos seis meses he estado trabajando con Backbone. Los primeros dos meses fueron por ahí, aprendiendo y averiguando cómo quiero estructurar mi código al respecto. Los siguientes 4 meses estaban consumiendo una aplicación de ajuste de producción. No me malinterpreten, Backbone me ha salvado del lío de miles de líneas del código del cliente que era el estándar anterior, pero me permitió hacer cosas más grandiosas en menos tiempo, abriendo una nueva pila de problemas. Para todas las preguntas que planteo aquí hay soluciones simples que se sienten como hacks o simplemente se sienten mal . Prometo una recompensa de 300 puntos por una solución increíble. Aquí va:

  1. Cargando : para nuestro caso de uso (un panel de administración), la sincronización pesimista es mala. Para algunas cosas, necesito validar cosas en el servidor antes de aceptarlas. Comenzamos antes de que el evento 'sync' se fusionara en Backbone,

y usamos este pequeño código para imitar el evento de carga:

window.old_sync = Backbone.sync

# Add a loading event to backbone.sync
Backbone.sync = (method, model, options) ->
  old_sync(method, model, options)
  model.trigger("loading")

Estupendo. Funciona como se esperaba, pero no se siente correcto. Vinculamos este evento a todas las vistas relevantes y mostramos un ícono de carga hasta que recibamos un evento de éxito o error de ese modelo. ¿Hay una manera mejor y más sana de hacer esto?

Ahora para los duros:

  1. Demasiadas cosas se rinden demasiado : digamos que nuestra aplicación tiene pestañas. Cada pestaña controla una colección. En el lado izquierdo obtienes la colección. Hace clic en un modelo para comenzar a editarlo en el centro. Cambia su nombre y presiona la pestaña para ir al siguiente elemento del formulario. Ahora, su aplicación es un "algo en tiempo real algo" que nota la diferencia, ejecuta validaciones y sincroniza automáticamente el cambio al servidor, ¡sin necesidad de guardar el botón! Genial, pero el H2 al comienzo del formulario tiene el mismo nombre que en la entrada, necesita actualizarlo. Ah, y necesita actualizar el nombre en la lista al costado. OH, ¡y la lista se ordena por nombres!

Aquí hay otro ejemplo: desea crear un nuevo elemento en la colección. Presiona el botón "nuevo" y comienza a completar el formulario. ¿Inmediatamente agregas el artículo a la colección? Pero, ¿qué sucede si decidiste descartarlo? O si guarda la colección completa en otra pestaña? Y, hay una carga de archivo: debe guardar y sincronizar el modelo antes de poder comenzar a cargar el archivo (para que pueda adjuntar el archivo al modelo). Entonces todo comienza a renderizarse en temblores: guarda el modelo y la lista y el formulario vuelve a mostrarse: está sincronizado ahora, por lo que obtiene un nuevo botón de eliminar y se muestra en la lista, pero ahora la carga del archivo terminó de cargarse, por lo que todo comienza a renderizar nuevamente

Agregue subvistas a la mezcla y todo comienza a parecerse a una película de Fellini.

  1. Son subvistas todo el camino hacia abajo . Aquí hay un buen artículo sobre estas cosas . No pude, por amor a todo lo que es sagrado, encontrar una forma correcta de adjuntar los plugins jQuery o los eventos DOM a cualquier vista que tenga subvistas. El infierno sigue rápidamente. La información sobre herramientas detecta un render que tarda mucho tiempo y comienza a flipar, las subvistas se vuelven zombis o no responden. Este es el principal punto de dolor, ya que aquí están los errores reales, pero todavía no tengo una solución completa.

  2. Parpadeo : la representación es rápida. De hecho, es tan rápido que mi pantalla parece que tuvo un ataque. A veces son las imágenes las que tienen que volver a cargarse (¡con otra llamada al servidor!), Por lo que el html se minimiza y luego se maximiza de nuevo abruptamente; un ancho css + altura para ese elemento lo arreglará. a veces podemos resolver esto con un fadeIn y un fadeOut, que son un dolor de cabeza para escribir, ya que a veces estamos reutilizando una vista y, a veces, creándola de nuevo.

TL; DR - Tengo problemas con las vistas y las subvistas en Backbone. Se procesa demasiadas veces, parpadea cuando se renderiza, las subvistas separan mis eventos DOM y se comen mis cerebros.

¡Gracias!

Más detalles: BackboneJS con Ruby on Rails Gem. Plantillas que usan plantillas UnderscoreJS.


Ok, en orden ... :)

  1. Cargando...

En caso de que quiera validar los datos que se almacenan en el servidor, buenas prácticas hágalo en el lado del servidor. Si la validación en el servidor no tiene éxito, el servidor debe enviar un código HTTP no 200, por lo tanto, guarde el método de Backbone.Model provocará un error.

De otro lado, para la columna vertebral de datos de validación se ha implementado el método de validate . Creo que esa es la elección correcta para implementarlo y usarlo. Pero tenga en cuenta que validar se invoca antes de establecer y guardar, y si validate devuelve un error, establecer y guardar no continuará y los atributos del modelo no se modificarán. Las validaciones fallidas activan un evento de "error".

Otra forma, cuando llamamos al conjunto silencioso (con {silencioso: verdadero} param), debemos llamar al método isValid manualmente para validar los datos.

  1. Demasiadas cosas se rinden demasiado ...

Tienes que separar tus Vistas bajo su lógica. Las buenas prácticas para la recolección son vistas separadas para cada modelo. En este caso, podría representar cada elemento de forma independiente. Y aún más: al inicializar la vista de contenedor para la recopilación, puede vincular cualquier evento de cada modelo de la colección a la vista adecuada, y se procesarán automáticamente.

Genial, pero el H2 al comienzo del formulario tiene el mismo nombre que en la entrada, necesita actualizarlo. Ah, y necesita actualizar el nombre en la lista al costado.

puede usar JQuery on método para implementar la devolución de llamada que envía el valor para mostrar. Ejemplo:

//Container view
init: function() {
    this.collection = new Backbone.Collection({
        url: 'http://mybestpage.com/collection'
    });
    this.collection.bind('change', this.render, this);
    this.collection.fetch();
},
render: function() {

    _.each(this.collection.models, function(model) {
         var newView = new myItemView({
              model: model,
              name: 'view' + model.id
         });
         this.$('#my-collection').append(newView.render().$el);
         view.on('viewEdit', this.displayValue);
    }, this);
},
...
displayValue: function(value) {
    //method 1
    this.displayView.setText(value); //we can create little inner view before, 
                                     //for text displaying. Сonvenient at times.
    this.displayView.render();
    //method 2
    $(this.el).find('#display').html(value);
}

//View from collection
myItemView = Backbone.View.extend({
events: {
    'click #edit': 'edit'
},
init: function(options) {
    this.name = options.name;
},
...
edit: function() {
    this.trigger('viewEdit', this.name, this);
}

OH, ¡y la lista se ordena por nombres!

Puede usar el método de sort para colecciones troncales. Pero (!) Calling sort desencadena el evento "reset" de la colección. Pase {silent: true} para evitar esto. Cómo

Aquí hay otro ejemplo: Desea crear un nuevo elemento en la colección ...

Cuando presionamos un botón "Nuevo" necesitamos crear un nuevo modelo, pero solo cuando el método .save () desencadenará el éxito, debemos llevar este modelo a la colección. En otro caso, deberíamos mostrar un mensaje de error. Por supuesto, no tenemos motivos para agregar un nuevo modelo a nuestra colección hasta que haya sido validado y guardado en el servidor.

  1. Son subvistas hasta el fondo ... las subvistas se vuelven zombis o no responden.

cuando usted (o cualquier modelo) llama al método de renderizado, todos los elementos dentro de él se recrearán. Entonces, en caso de que tenga subvistas, debe llamar a subView.delegateEvents(subView.events); para todas las subvistas; Probablemente este método es un pequeño truco, pero funciona.

  1. Parpadeo..

Usar miniaturas para imágenes grandes y medianas minimizará el parpadeo en muchos casos. De otra manera, podría separar la representación de la vista de imágenes y otro contenido.

Ejemplo:

var smartView = Backbone.View.extend({
  initialize: function(){
    this.model.on( "imageUpdate", this.imageUpdate, this );
    this.model.on( "contentUpdate", this.contentUpdate, this );
  },

  render: function(){
    this.$el.html(this.template(this.model.toJSON()));
  },

  imageUpdate: function(){
    this.$el.find('#image').attr('src', this.model.get('imageUrl'));
  },
  contentUpdate: function(){
    this.$el.find('#content').html(this.model.get('content'));
  }
})

Espero que esto ayude a cualquiera. Perdón por los errores de gramática, si hay alguno :)


Cargando...

Soy un gran fanático de la carga ansiosa. Todas las llamadas a mi servidor son respuestas JSON, por lo que no es un gran problema hacerlas más a menudo que no. Por lo general, actualizo una colección cada vez que la necesita una vista.

Mi forma favorita de carga ansiosa es usar Backbone-relational . Si organizo mi aplicación de forma jerárquica Considera esto:

Organization model
|--> Event model
|--> News model
   |--> Comment model

Entonces, cuando un usuario está viendo una organization , puedo cargar los events y news esa organización. Y cuando un usuario está viendo un artículo de news , ansío cargar los comments ese artículo.

Backbone-relational proporciona una excelente interfaz para consultar registros relacionados desde el servidor.

Demasiadas cosas se rinden demasiado ...

Backbone-relational ayuda aquí! Backbone-relational proporciona una tienda de discos global que resulta ser muy útil. De esta manera, puede pasar identificaciones y recuperar el mismo modelo en otro lugar. Si lo actualiza en un lugar, está disponible en otro.

a_model_instance = Model.findOrCreate({id: 1})

Otra herramienta aquí es Backbone.ModelBinder . Backbone.ModelBinder te permite crear tus plantillas y olvidarte de adjuntarlas para ver los cambios. Entonces, en su ejemplo de recopilar información y mostrarla en el encabezado, simplemente dígale a Backbone.ModelBinder que mire AMBOS de esos elementos, y en el change entrada, su modelo se actualizará y en el change modelo su vista se actualizará, de modo que ahora el encabezado será actualizado.

Son subvistas todo el camino ... las subvistas se vuelven zombis o no responden ...

Me gusta mucho Backbone.Marionette . Se encarga de una gran parte de la limpieza y agrega una onShow llamada de onShow que puede ser útil cuando se eliminan temporalmente las vistas del DOM.

Esto también ayuda a facilitar la conexión de complementos jQuery. El método onShow se invoca después de que la vista se represente y se agregue al DOM para que el código del complemento jQuery pueda funcionar correctamente.

También proporciona algunas plantillas de visualización geniales como CollectionView que hace un gran trabajo al administrar una colección y sus subvistas.

Parpadeo

Lamentablemente, no tengo mucha experiencia con esto, pero podrías probar precargar las imágenes también. Prestarlos en una vista oculta y luego traerlos hacia adelante.





coffeescript