Cómo manejar la inicialización y representación de subviews en Backbone.js?


Tengo tres formas diferentes de inicializar y renderizar una vista y sus subviews, y cada una de ellas tiene diferentes problemas. Tengo curiosidad por saber si hay una mejor manera de resolver todos los problemas:


Escenario uno:

Inicializar los hijos en la función inicializar del padre. De esta manera, no todo se atasca en el renderizado, por lo que hay menos bloqueo en el renderizado.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.render().appendTo(this.$('.container-placeholder');
}

Los problemas:

  • El mayor problema es que llamar a render en el padre por segunda vez eliminará todos los enlaces de eventos de childs. (Esto se debe a cómo funciona $.html() de jQuery.) Esto podría mitigarse llamando a this.child.delegateEvents().render().appendTo(this.$el); en su lugar, pero entonces el primero, y el caso más a menudo, está haciendo más trabajo innecesariamente.

  • Al añadir los hijos, se obliga a la función de renderizado a tener conocimiento de la estructura DOM de los padres para que pueda obtener el orden que desea. Lo que significa que cambiar una plantilla puede requerir actualizar un función de renderizado de la vista.


Escenario Dos:

Inicialice los hijos en el initialize() del padre todavía, pero en lugar de agregar, use setElement().delegateEvents() para establecer el hijo en un elemento en la plantilla padre.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}

Problemas:

  • Esto hace que el delegateEvents() sea necesario ahora, lo que es un ligero negativo sobre lo que solo es necesario en llamadas posteriores en el primer escenario.

Escenario Tres:

Inicializar los hijos en el método render() del padre en su lugar.

initialize : function () {

    //parent init stuff
},

render : function () {

    this.$el.html(this.template());

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}

Problemas:

  • Esto significa que la función render ahora tiene que estar atada con toda la lógica de inicialización también.

  • Si edito el estado de una de las vistas secundarias y luego llamo a render al padre, se creará un hijo completamente nuevo y se perderá todo su estado actual. Lo que también parece que podría ponerse peligroso para las fugas de memoria.


Realmente curioso de conseguir tu opinión sobre esto. ¿Qué escenario usarías? ¿o hay un cuarto mágico que resuelva todos estos problemas?

¿Alguna vez ha mantenido un registro de un estado renderizado para una vista? ¿Decir una bandera renderedBefore? Parece muy janky.

Author: ggozad, 2012-02-18

7 answers

Esta es una gran pregunta. Backbone es genial debido a la falta de suposiciones que hace, pero significa que tienes que (decidir cómo) implementar cosas como esta tú mismo. Después de mirar a través de mis propias cosas, encuentro que (tipo de) uso una mezcla de escenario 1 y escenario 2. No creo que exista un 4to escenario mágico porque, simplemente, todo lo que haces en el escenario 1 y 2 debe hacerse.

Creo que sería más fácil explicar cómo me gusta manejarlo con un ejemplo. Digamos que tengo esta página simple se divide en las vistas especificadas:

Desglose de Páginas

Digamos que el HTML es, después de ser renderizado, algo como esto: {[59]]}

<div id="parent">
    <div id="name">Person: Kevin Peel</div>
    <div id="info">
        First name: <span class="first_name">Kevin</span><br />
        Last name: <span class="last_name">Peel</span><br />
    </div>
    <div>Phone Numbers:</div>
    <div id="phone_numbers">
        <div>#1: 123-456-7890</div>
        <div>#2: 456-789-0123</div>
    </div>
</div>

Esperemos que sea bastante obvio cómo el HTML coincide con el diagrama.

El ParentView tiene 2 vistas secundarias, InfoView y PhoneListView, así como algunos divs adicionales, uno de los cuales, #name, debe establecerse en algún momento. PhoneListView tiene vistas secundarias propias, una matriz de entradas PhoneView.

Así que a su pregunta real. Yo me encargo inicialización y representación diferentes según el tipo de vista. Divido mis vistas en dos tipos, Parent vistas y Child vistas.

La diferencia entre ellos es simple, Parent las vistas tienen vistas secundarias mientras que Child las vistas no. Así que en mi ejemplo, ParentView y PhoneListView son vistas Parent, mientras que InfoView y las entradas PhoneView son vistas Child.

Como mencioné antes, la mayor diferencia entre estas dos categorías es cuando se les permite renderizar. En un mundo perfecto, quiero Parent vistas para renderizar una sola vez. Depende de sus vistas secundarias manejar cualquier re-renderizado cuando el(los) modelo (s) cambian. Child vistas, por otro lado, permito volver a renderizar en cualquier momento que lo necesiten, ya que no tienen ninguna otra vista que dependa de ellas.

En un poco más de detalle, para las vistas Parent me gusta que mis funciones initialize hagan algunas cosas:

  1. Inicializar mi propia vista
  2. Render my own view
  3. Cree e inicialice cualquier vista secundaria.
  4. Asignar cada vista secundaria un elemento dentro de mi vista (por ejemplo, el InfoView se asignaría #info).

El paso 1 se explica por sí mismo.

El paso 2, el renderizado, se realiza de manera que cualquier elemento en el que se basan las vistas secundarias ya exista antes de intentar asignarlos. Al hacer esto, sé que todos los hijos events se configurarán correctamente, y puedo volver a renderizar sus bloques tantas veces como quiera sin tener que volver a delegar nada. En realidad no render ninguna vista de niño aquí, permito ellos para hacer eso dentro de su propio initialization.

Los pasos 3 y 4 se manejan al mismo tiempo que paso el al crear la vista hija. Me gusta pasar un elemento aquí, ya que siento que el padre debe determinar dónde, en su propia opinión, se le permite al hijo poner su contenido.

Para el renderizado, trato de mantenerlo bastante simple para las vistas Parent. Quiero que la función render no haga nada más que renderizar la vista padre. Sin delegación de eventos, sin representación de las opiniones del niño, Nada. Solo un simple renderizado.

A veces esto no siempre funciona. Por ejemplo, en mi ejemplo anterior, el elemento #name tendrá que actualizarse cada vez que el nombre dentro del modelo cambie. Sin embargo, este bloque es parte de la plantilla ParentView y no es manejado por una vista Child dedicada, así que trabajo alrededor de eso. Crearé algún tipo de función subRender que solo reemplace el contenido del elemento #name, y no tenga que desechar todo el elemento #parent. Esto puede parecer un truco, pero realmente he encontrado que funciona mejor que tener que preocuparse por volver a representar todo el DOM y volver a unir elementos y cosas así. Si realmente quisiera hacerlo limpio, crearía una nueva vista Child (similar a la InfoView) que manejaría el bloque #name.

Ahora para las vistas Child, las vistas initialization son bastante similares a las vistas Parent, solo que sin la creación de otras vistas Child. Así que:

  1. Inicializar mi vista
  2. La configuración enlaza la escucha para cualquier cambio en el modelo que me importa
  3. Render my view {[81]]}

Child el renderizado de vistas también es muy simple, simplemente renderizar y establecer el contenido de mi el. Una vez más, nada de meterse con la delegación ni nada por el estilo.

Aquí hay un código de ejemplo de cómo puede ser mi ParentView:

var ParentView = Backbone.View.extend({
    el: "#parent",
    initialize: function() {
        // Step 1, (init) I want to know anytime the name changes
        this.model.bind("change:first_name", this.subRender, this);
        this.model.bind("change:last_name", this.subRender, this);

        // Step 2, render my own view
        this.render();

        // Step 3/4, create the children and assign elements
        this.infoView = new InfoView({el: "#info", model: this.model});
        this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
    },
    render: function() {
        // Render my template
        this.$el.html(this.template());

        // Render the name
        this.subRender();
    },
    subRender: function() {
        // Set our name block and only our name block
        $("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
    }
});

Puedes ver mi implementación de subRender aquí. Al tener cambios vinculados a subRender en lugar de render, no tengo que preocuparme por destruir y reconstruir todo el bloque.

Aquí está código de ejemplo para el bloque InfoView:

var InfoView = Backbone.View.extend({
    initialize: function() {
        // I want to re-render on changes
        this.model.bind("change", this.render, this);

        // Render
        this.render();
    },
    render: function() {
        // Just render my template
        this.$el.html(this.template());
    }
});

Los lazos son la parte importante aquí. Al vincularme a mi modelo, nunca tengo que preocuparme por llamar manualmente render. Si el modelo cambia, este bloque se re-renderizará a sí mismo sin afectar a ninguna otra vista.

El PhoneListView será similar al ParentView, solo necesitará un poco más de lógica en sus funciones initialization y render para manejar colecciones. La forma en que manejes la colección depende realmente de ti, pero al menos necesitarás para escuchar los eventos de la colección y decidir cómo desea renderizar (anexar/eliminar, o simplemente volver a renderizar todo el bloque). Personalmente, me gusta agregar nuevas vistas y eliminar las antiguas, no volver a renderizar toda la vista.

El PhoneView será casi idéntico al InfoView, solo escuchando los cambios de modelo que le importan.

Esperemos que esto haya ayudado un poco, por favor hágamelo saber si algo es confuso o no lo suficientemente detallado.

 258
Author: Kevin Peel,
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-01-31 16:44:31

No estoy seguro de si esto responde directamente a tu pregunta, pero creo que es relevante:

Http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

El contexto en el que configuré este artículo es diferente, por supuesto, pero creo que las dos soluciones que ofrezco, junto con los pros y los contras de cada una, deberían ayudarte a avanzar en la dirección correcta.

 6
Author: Derick Bailey,
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-02-19 03:59:06

Para mí no me parece la peor idea del mundo diferenciar entre la configuración inicial y las configuraciones posteriores de sus vistas a través de algún tipo de bandera. Para hacer esto limpio y fácil, la bandera debe agregarse a su propia vista que debe extender la Vista Backbone (Base).

Igual que Derick, no estoy completamente seguro de si esto responde directamente a su pregunta, pero creo que al menos vale la pena mencionarla en este contexto.

Véase también: Uso de un Eventbus en Backbone

 6
Author: Bruiser,
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:25:34

Kevin Peel da una gran respuesta-aquí está mi versión tl; dr:

initialize : function () {

    //parent init stuff

    this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!

    this.child = new Child();
},
 3
Author: Nate Barr,
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-07-03 18:06:08

Estoy tratando de evitar el acoplamiento entre puntos de vista como estos. Hay dos formas en las que suelo hacerlo:

Utilice un router

Básicamente, permite que su función de enrutador inicialice la vista padre e hijo. Por lo tanto, la vista no tiene conocimiento el uno del otro, pero el enrutador se encarga de todo.

Pasando el mismo el a ambas vistas

this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});

Ambos tienen conocimiento del mismo DOM, y puedes pedirlos como quieras.

 2
Author: sntran,
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-03-01 02:40:03

Lo que hago es darle a cada niño una identidad (que Backbone ya lo ha hecho por ti: cid)

Cuando el Contenedor hace la representación, usando el 'cid' y 'tagName' generan un marcador de posición para cada hijo, por lo que en la plantilla los hijos no tienen idea de dónde será puesto por el Contenedor.

<tagName id='cid'></tagName>

De lo que puede usar

Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element 
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();

No se necesita ningún marcador de posición especificado, y el contenedor solo genera el marcador de posición en lugar de la estructura DOM de los hijos. Cotainer y Los niños todavía están generando sus propios elementos DOM y solo una vez.

 2
Author: Villa,
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-07-17 02:51:29

Aquí hay una mezcla ligera para crear y renderizar subviews, que creo que aborda todos los problemas de este hilo:

Https://github.com/rotundasoftware/backbone.subviews

El enfoque adoptado por este plug es crear y renderizar subviews después de primero cuando se renderiza la vista padre. Luego, en los renderizados posteriores de la vista padre,$.separe los elementos subview, vuelva a renderizar el elemento padre, luego inserte los elementos subview en los lugares apropiados y re-renderizarlos. De esta manera, los objetos subviews se reutilizan en los renderizados posteriores y no hay necesidad de volver a delegar eventos.

Tenga en cuenta que el caso de una vista de colección (donde cada modelo de la colección está representado con una vista secundaria) es bastante diferente y merece su propia discusión / solución, creo. La mejor solución general que conozco para ese caso es el collectionView in Marionette.

EDITAR: Para el caso de la vista de colección, es posible que también desee revisar esto más implementación centrada en la interfaz de usuario, si necesita seleccionar modelos basados en clics y / o arrastrar y soltar para reordenar.

 1
Author: Brave Dave,
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-08-12 17:13:23