J'ai trois façons différentes d'initialiser et de restituer une vue et ses sous-vues, et chacune d'entre elles pose des problèmes différents. Je suis curieux de savoir s'il existe un meilleur moyen de résoudre tous les problèmes:
Initialisez les enfants dans la fonction d'initialisation du parent. De cette façon, tout ne reste pas bloqué dans le rendu, de sorte qu'il y ait moins de blocage sur le rendu.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.render().appendTo(this.$('.container-placeholder');
}
Les problèmes:
Le plus gros problème est que l'appel du rendu sur le parent pour une deuxième fois supprimera toutes les liaisons d'événement childs. (Ceci est dû au fonctionnement de $.html()
de jQuery.) Cela pourrait être atténué en appelant this.child.delegateEvents().render().appendTo(this.$el);
à la place, mais ensuite, le premier et le cas le plus souvent, vous faites plus de travail inutilement .
En ajoutant les enfants, vous forcez la fonction de rendu à connaître la structure DOM des parents afin d'obtenir l'ordre que vous souhaitez. Cela signifie que la modification d'un modèle peut nécessiter la mise à jour de la fonction de rendu d'une vue.
Initialisez les enfants dans la initialize()
du parent, mais au lieu de l'ajouter, utilisez setElement().delegateEvents()
pour définir l'enfant sur un élément du modèle de parent.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}
Problèmes:
delegateEvents()
nécessaire maintenant, ce qui est un léger inconvénient car elle n'est nécessaire que lors d'appels suivants dans le premier scénario.Initialisez les enfants dans la méthode render()
du parent à la place.
initialize : function () {
//parent init stuff
},
render : function () {
this.$el.html(this.template());
this.child = new Child();
this.child.appendTo($.('.container-placeholder').render();
}
Problèmes:
Cela signifie que la fonction de rendu doit maintenant être liée à toute la logique d'initialisation.
Si j'édite l'état de l'une des vues enfants et que j'appelle ensuite render sur le parent, un tout nouvel enfant sera créé et tout son état actuel sera perdu. Ce qui semble également être risqué pour les fuites de mémoire.
Vraiment curieux de connaître le point de vue de vos gars. Quel scénario utiliseriez-vous? ou y en a-t-il un quatrième qui résoudra tous ces problèmes?
Avez-vous déjà gardé la trace d'un état rendu pour une vue? Dites un drapeau renderedBefore
? Semble vraiment janky.
C'est une excellente question. Backbone est formidable en raison de l’absence d’hypothèses, mais cela signifie que vous devez (décider comment) mettre en œuvre de telles choses vous-même. Après avoir parcouru mes propres dossiers, je constate que j'utilise (en quelque sorte) un mélange de scénario 1 et de scénario 2. Je ne pense pas qu'un quatrième scénario magique existe, car, tout simplement, tout ce que vous faites dans les scénarios 1 et 2 doit être terminé.
Je pense qu'il serait plus facile d'expliquer comment j'aime gérer cela avec un exemple. Disons que cette page simple est divisée en vues spécifiées:
Supposons que le code HTML, après avoir été rendu, ressemble à ceci:
<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>
J'espère que la façon dont le code HTML correspond au diagramme est assez évidente.
Le ParentView
contient 2 vues enfants, InfoView
et PhoneListView
, ainsi que quelques divs supplémentaires, dont l'un, #name
, doit être réglé à un moment donné. PhoneListView
contient ses propres vues enfants, un tableau de PhoneView
entrées.
Passons à votre question actuelle. Je gère l'initialisation et le rendu différemment en fonction du type de vue. Je divise mes points de vue en deux types, Parent
vues et Child
vues.
La différence entre eux est simple: les vues Parent
conservent les vues enfants alors que les vues Child
ne le sont pas. Ainsi, dans mon exemple, ParentView
et PhoneListView
sont Parent
vues, tandis que InfoView
et les entrées PhoneView
sont Child
vues.
Comme je l'ai déjà mentionné, la plus grande différence entre ces deux catégories réside dans le moment où elles sont autorisées à effectuer le rendu. Dans un monde parfait, je veux que Parent
les vues ne soient rendues qu'une seule fois. Il appartient à leurs vues enfant de gérer tout rendu lors du changement de modèle. Child
vues, par contre, j’autorise à effectuer un nouveau rendu à chaque fois qu’ils en ont besoin, car ils ne disposent d’aucune autre vue.
Plus en détail, pour les vues Parent
, j'aime que mes fonctions initialize
fassent plusieurs choses:
InfoView
se verrait attribuer #info
).L'étape 1 est assez explicite.
L'étape 2, le rendu, est effectuée de sorte que tous les éléments sur lesquels reposent les vues enfants existent déjà avant que je ne les affecte. En faisant cela, je sais que tous les enfants events
seront correctement configurés et que je pourrai restituer leurs blocs autant de fois que je le voudrai sans me soucier de devoir déléguer à nouveau. En fait, je n'utilise render
aucun enfant vu ici, je les autorise à le faire dans leur propre initialization
.
Les étapes 3 et 4 sont en réalité traitées en même temps que je passe el
lors de la création de la vue enfant. J'aime passer un élément ici car j'estime que le parent devrait déterminer où, de son point de vue, l'enfant est autorisé à mettre son contenu.
Pour le rendu, j'essaie de garder les choses simples pour les vues Parent
. Je veux que la fonction render
ne fasse que rendre la vue parente. Pas de délégation d'événement, pas de rendu de vues enfants, rien. Juste un rendu simple.
Parfois, cela ne fonctionne pas toujours bien. Par exemple, dans mon exemple ci-dessus, le #name
L'élément devra être mis à jour chaque fois que le nom du modèle sera modifié. Cependant, ce bloc fait partie du modèle ParentView
et n'est pas géré par une vue dédiée Child
. Je contourne donc cela. Je vais créer une sorte de fonction subRender
qui seulement remplace le contenu du #name
élément, et ne pas avoir à jeter tout le #parent
élément. Cela peut sembler être un bidouillage, mais j’ai vraiment trouvé que cela fonctionnait mieux que de devoir redéfinir le rendu de l’ensemble du DOM et réattacher des éléments, etc. Si je voulais vraiment le rendre propre, je créerais une nouvelle vue Child
(semblable au InfoView
) qui gèrerait le #name
bloquer.
Maintenant, pour les vues Child
, la vue initialization
est assez similaire à la vue Parent
, sans la création d'aucune autre vue Child
. Alors:
Le rendu de la vue Child
est également très simple, il suffit de restituer et de définir le contenu de mon el
. Encore une fois, pas de problème avec la délégation ou quelque chose comme ça.
Voici un exemple de code de ce à quoi mon ParentView
pourrait ressembler:
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);
}
});
Vous pouvez voir mon implémentation de subRender
ici. En ayant des modifications liées à subRender
au lieu de render
, je n'ai pas à craindre de faire sauter et de reconstruire tout le bloc.
Voici un exemple de code pour le bloc 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());
}
});
Les liens sont la partie importante ici. En me liant à mon modèle, je n'ai jamais à m'inquiéter d'appeler manuellement moi-même render
. Si le modèle change, ce bloc se re-rendra sans affecter les autres vues.
Le PhoneListView
sera similaire au ParentView
, vous aurez juste besoin d'un peu plus de logique dans vos deux fonctions initialization
et render
pour gérer les collections. La façon dont vous gérez la collection dépend vraiment de vous, mais vous devez au moins écouter les événements de la collection et décider du mode de rendu (ajouter/supprimer ou simplement restituer le bloc en entier). Personnellement, j'aime bien ajouter de nouvelles vues et supprimer les anciennes, sans refaire le rendu de la vue.
Le PhoneView
sera presque identique au InfoView
, n'écoutant que les modifications de modèle dont il se soucie.
J'espère que cela a aidé un peu, s'il vous plaît laissez-moi savoir si quelque chose est déroutant ou pas assez détaillé.
Pour moi, il ne semble pas que la pire idée au monde soit de faire la différence entre la configuration initiale et la configuration suivante de vos vues via une sorte de drapeau. Pour que cela soit facile et net, le drapeau doit être ajouté à votre propre vue, ce qui devrait étendre la vue Backbone (Base).
Comme Derick, je ne suis pas tout à fait sûr que cela réponde directement à votre question, mais je pense que cela mérite au moins d'être mentionné dans ce contexte.
Je ne sais pas si cela répond directement à votre question, mais je pense que c'est pertinent:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
Le contexte dans lequel j’ai mis en place cet article est bien sûr différent, mais je pense que les deux solutions que j’offre, ainsi que les avantages et les inconvénients de chacune d’elles, devraient vous faire avancer dans la bonne direction.
Kevin Peel donne une excellente réponse - voici ma version de tl; dr:
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
Ce que je fais est de donner à chaque enfant une identité (ce que Backbone a déjà fait pour vous: cid)
Lorsque Container effectue le rendu, l'utilisation de 'cid' et de 'tagName' génère un espace réservé pour chaque enfant. Ainsi, dans le modèle, les enfants n'ont aucune idée de l'endroit où ils seront placés par le conteneur.
<tagName id='cid'></tagName>
que vous pouvez utiliser
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();
aucun espace réservé spécifié n'est requis et Container ne génère que l'espace réservé plutôt que la structure DOM des enfants. Cotainer et Children génèrent toujours leurs propres éléments DOM et une seule fois.
J'essaie d'éviter le couplage entre des vues comme celles-ci. Je fais habituellement de deux façons:
En gros, vous laissez la fonction de votre routeur initialiser la vue parent et enfant. Donc, la vue ne se connaît pas, mais le routeur gère tout.
this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});
Tous deux connaissent le même DOM et vous pouvez les commander quand vous le souhaitez.
Voici un mix léger pour la création et le rendu des sous-vues, qui, je pense, résout tous les problèmes de ce fil de discussion:
https://github.com/rotundasoftware/backbone.subviews
L'approche prise par ce plug est de créer et de rendre les sous-vues après le premier affichage de la vue parent. Ensuite, lors des rendus de la vue parent, $ .detachut les éléments de la sous-vue, restituez le rendu du parent, puis insérez les éléments de la sous-vue aux emplacements appropriés et restituez-les. De cette manière, les objets de sous-vues sont réutilisés lors de rendus ultérieurs, sans qu'il soit nécessaire de déléguer à nouveau des événements.
Notez que le cas d'une vue de collection (où chaque modèle de la collection est représenté avec une seule sous-vue) est très différent et mérite sa propre discussion/solution, je pense. La meilleure solution générale que je connaisse pour ce cas est le CollectionView in Marionette .
EDIT: Pour le cas de la vue de collection, vous pouvez également vouloir utiliser cette implémentation davantage axée sur l'interface utilisateur , si vous avez besoin de sélectionner des modèles basés sur des clics et/ou des glisser-déposer pour les réorganiser.