javascript - ¿Cómo manejar la inicialización y la representación de subvistas en Backbone.js?
(7)
A mí no me parece la peor idea del mundo para diferenciar entre la configuración inicial y las configuraciones subsiguientes 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 debería extender la Vista de Backbone (Base).
Al igual que Derick, no estoy completamente seguro de si esto responde directamente a tu pregunta, pero creo que al menos vale la pena mencionarlo en este contexto.
Ver también: Uso de un Eventbus en Backbone.
Tengo tres formas diferentes de inicializar y renderizar una vista y sus subvistas, y cada una de ellas tiene problemas diferentes. Tengo curiosidad por saber si hay una mejor manera de resolver todos los problemas:
Escenario uno:
Inicialice los hijos en la función de inicialización del padre. De esta manera, no todo se bloquea en el procesamiento, de modo que hay menos bloqueo en el procesamiento.
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 al render en el padre por segunda vez eliminará todos los enlaces de eventos secundarios. (Esto se debe a cómo funciona jQuery
$.html()
. Esto se puede mitigar llamando athis.child.delegateEvents().render().appendTo(this.$el);
en cambio, pero luego el primer caso, y el más a menudo, está haciendo más trabajo innecesariamente.Al agregar los elementos secundarios, obliga a la función de representación a tener conocimiento de la estructura DOM de los padres para que obtenga el orden que desea. Lo que significa que cambiar una plantilla puede requerir actualizar la función de renderizado de una vista.
Escenario dos:
Inicialice los elementos secundarios en la initialize()
los padres initialize()
, pero en lugar de agregarlos, use setElement().delegateEvents()
para configurar el elemento secundario en un elemento de la plantilla de los padres.
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
delegateEvents()
sea necesario ahora, lo cual es un poco negativo, ya que solo es necesario en las llamadas subsiguientes en el primer escenario.
Escenario Tres:
Inicialice los hijos en el método render()
los padres 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 de renderización ahora también debe estar vinculada con toda la lógica de inicialización.
Si edito el estado de una de las vistas secundarias y luego hago una llamada a la representación en el elemento primario, se creará un elemento secundario completamente nuevo y se perderá todo su estado actual. Lo que también parece que podría ser peligroso para las fugas de memoria.
Realmente curioso para obtener la opinión de sus chicos sobre esto. ¿Qué escenario utilizarías? ¿O hay un cuarto mágico que resuelve todos estos problemas?
¿Alguna vez has hecho un seguimiento de un estado renderizado para una vista? ¿Diga una bandera renderedBefore
? Parece realmente janky
Aquí hay una combinación ligera para crear y representar subvistas, que creo que aborda todos los problemas en este hilo:
https://github.com/rotundasoftware/backbone.subviews
El enfoque adoptado por este complemento es crear y generar subvistas después de la primera vez que se renderiza la vista principal. Luego, en representaciones posteriores de la vista primaria, $ .detachar los elementos de subvista, volver a renderizar el padre, luego insertar los elementos de subvista en los lugares apropiados y volver a renderizarlos. De esta manera, los objetos de subvistas se reutilizan en representaciones posteriores y no es necesario volver a delegar eventos.
Tenga en cuenta que el caso de una vista de colección (donde cada modelo de la colección se representa con una subvista) 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 en Marionette .
EDITAR: Para el caso de la vista de colección, es posible que también desee revisar esta implementación más centrada en la interfaz de usuario , si necesita la selección de modelos basados en clics y / o arrastrando y soltando para reordenar.
Esta es una gran pregunta. La red troncal es excelente debido a la falta de suposiciones que hace, pero significa que tienes que (decidir cómo) implementar cosas como esta. Después de revisar mis propias cosas, encuentro que (de alguna manera) uso una combinación de escenario 1 y escenario 2. No creo que exista un cuarto escenario mágico porque, simplemente, todo lo que haces en el escenario 1 y 2 debe ser hecho.
Creo que sería más fácil explicar cómo me gusta manejarlo con un ejemplo. Digamos que tengo esta página simple dividida en las vistas especificadas:
Digamos que el HTML es, después de ser renderizado, algo como esto:
<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.
ParentView
tiene 2 vistas secundarias, InfoView
y PhoneListView
, así como algunos divs adicionales, uno de los cuales, #name
, debe configurarse en algún momento. PhoneListView
tiene vistas secundarias propias, una serie de entradas de PhoneView
.
Así que a su pregunta real. Manejo la inicialización y la representación de forma diferente según el tipo de vista. Divido mis vistas en dos tipos, vistas de Parent
y vistas de Child
.
La diferencia entre ellos es simple, las vistas de los Parent
mantienen las vistas de los Child
mientras que las vistas de los Child
no. Así que en mi ejemplo, ParentView
y PhoneListView
son vistas principales, mientras que las entradas InfoView
y PhoneView
son vistas PhoneView
.
Como mencioné anteriormente, la mayor diferencia entre estas dos categorías es cuando se les permite renderizar. En un mundo perfecto, quiero que las vistas de los Parent
se reproduzcan una sola vez. Depende de sus vistas secundarias manejar cualquier representación cuando los modelos cambien. Child
vistas de Child
, por otro lado, permiten volver a renderizar en cualquier momento que lo necesiten, ya que no tienen otras opiniones que confíen en ellos.
En un poco más de detalle, para las vistas de Parent
, me gusta que mis funciones de initialize
hagan algunas cosas:
- Inicializar mi propia vista
- Renderizar mi propia vista
- Crea e inicializa las vistas de niños.
- Asignar a cada vista secundaria un elemento dentro de mi vista (por ejemplo, a
InfoView
se le asignaría#info
).
El paso 1 es bastante autoexplicativo.
El paso 2, el renderizado, se realiza para que los elementos en los que se basan las vistas secundarias ya existan antes de que intente asignarlos. Al hacer esto, sé que todos los events
secundarios se configurarán correctamente, y puedo volver a representar sus bloques tantas veces como quiera sin preocuparme por tener que volver a delegar nada. En realidad no render
las vistas de los niños aquí, les permito que lo hagan dentro de su propia initialization
.
Los pasos 3 y 4 en realidad se manejan al mismo tiempo que paso el
mientras creaba la vista secundaria. Me gusta pasar un elemento aquí, ya que creo que el padre debería determinar en qué punto de vista se le permite al niño poner su contenido.
Para renderizar, trato de ser bastante simple para las vistas de los Parent
. Quiero que la función de render
no haga nada más que representar la vista principal. Sin delegación de eventos, sin representación de vistas de niños, nada. Sólo un simple render.
Aunque a veces esto no siempre funciona. Por ejemplo, en mi ejemplo anterior, el elemento #name
deberá actualizarse cada vez que cambie el nombre dentro del modelo. Sin embargo, este bloque forma parte de la plantilla de ParentView
y no se maneja con una vista ParentView
dedicada, así que trabajo en eso. subRender
algún tipo de función subRender
que solo reemplaza el contenido del elemento #name
, y no tengo que #parent
todo el elemento #parent
. Esto puede parecer un truco, pero realmente he encontrado que funciona mejor que tener que preocuparme por volver a renderizar todo el DOM y volver a unir elementos y demás. Si realmente quisiera #name
, crearía una nueva vista #name
(similar a InfoView
) que manejaría el bloque #name
.
Ahora, para las vistas Child
, la initialization
es bastante similar a las vistas principales, sin la creación de más vistas Child
. Asi que:
- Inicializar mi vista
- La configuración se enlaza a escuchar cualquier cambio en el modelo que me importa
- Render mi vista
Child
visualización de la vista Child
también es muy simple, simplemente renderice y configure el contenido de mi el
. De nuevo, no te metas con la delegación ni nada de eso.
Aquí hay un código de ejemplo de cómo puede verse 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
lugar de render
, no tengo que preocuparme por la voladura y la reconstrucción de todo el bloque.
Aquí hay un 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());
}
});
Las ataduras son la parte importante aquí. Al enlazar a mi modelo, nunca tengo que preocuparme por llamar manualmente el render
. Si el modelo cambia, este bloque se volverá a generar sin afectar a ninguna otra vista.
PhoneListView
será similar a ParentView
, solo necesitará un poco más de lógica en sus funciones de initialization
y render
para manejar las colecciones. La forma en que maneje la colección depende realmente de usted, pero al menos deberá escuchar los eventos de la colección y decidir cómo desea procesar (agregar / eliminar, o simplemente volver a representar todo el bloque). Personalmente, me gusta añadir 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 interesan.
Esperemos que esto haya ayudado un poco, por favor hágame saber si algo es confuso o no lo suficientemente detallado.
Estoy tratando de evitar el acoplamiento entre puntos de vista como estos. Hay dos formas en las que suelo hacer:
Usar un enrutador
Básicamente, dejas que la función de tu enrutador inicialice la vista principal y secundaria. Así que la vista no se conoce, 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 puede ordenarlos de la manera que desee.
Kevin Peel da una gran respuesta, aquí está mi versión de tl; dr:
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
Lo que hago es darle a cada niño una identidad (que Backbone ya ha hecho eso por ti: cid)
Cuando Container hace el renderizado, el uso de ''cid'' y ''tagName'' generan un marcador de posición para cada niño, por lo que en la plantilla los niños no tienen idea de dónde lo colocará el Container.
<tagName id=''cid''></tagName>
de lo que puedes 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 un marcador de posición específico, y Container solo genera el marcador de posición en lugar de la estructura DOM de los niños. Cotainer y Children siguen generando elementos DOM propios y solo una vez.
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 instalé este artículo es diferente, por supuesto, pero creo que las dos soluciones que ofrezco, junto con las ventajas y desventajas de cada uno, deberían hacer que te muevas en la dirección correcta.