J'ai le JSON suivant fourni par un serveur. Avec cela, je veux créer un modèle avec un modèle imbriqué. Je ne sais pas quel est le moyen d'y parvenir.
//json
[{
name : "example",
layout : {
x : 100,
y : 100,
}
}]
Je souhaite que ceux-ci soient convertis en deux modèles de réseau fédéré imbriqués présentant la structure suivante:
// structure
Image
Layout
...
Donc, je définis le modèle de disposition comme suit:
var Layout = Backbone.Model.extend({});
Mais laquelle des deux (le cas échéant) techniques ci-dessous dois-je utiliser pour définir le modèle Image? A ou B ci-dessous?
A
var Image = Backbone.Model.extend({
initialize: function() {
this.set({ 'layout' : new Layout(this.get('layout')) })
}
});
ou, B
var Image = Backbone.Model.extend({
initialize: function() {
this.layout = new Layout( this.get('layout') );
}
});
J'ai le même problème pendant que j'écris mon application Backbone. Avoir à traiter avec des modèles incorporés/imbriqués. J'ai fait quelques ajustements que je pensais être une solution assez élégante.
Oui, vous pouvez modifier la méthode d’analyse pour modifier les attributs dans l’objet, mais tout cela est en réalité un code assez difficile à entretenir IMO et qui ressemble davantage à un hack qu’à une solution.
Voici ce que je suggère pour votre exemple:
Définissez d’abord votre modèle de présentation comme suit.
var layoutModel = Backbone.Model.extend({});
Alors voici votre image Modèle:
var imageModel = Backbone.Model.extend({
model: {
layout: layoutModel,
},
parse: function(response){
for(var key in this.model)
{
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {parse:true});
}
return response;
}
});
Notez que je n'ai pas altéré le modèle lui-même, mais simplement renvoyé l'objet souhaité à partir de la méthode d'analyse.
Cela devrait garantir la structure du modèle imbriqué lorsque vous lisez à partir du serveur. Vous remarquerez à présent que la sauvegarde ou les paramètres ne sont pas gérés ici, car j'estime qu'il est logique de définir explicitement le modèle imbriqué à l'aide du modèle approprié.
Ainsi:
image.set({layout : new Layout({x: 100, y: 100})})
Notez également que vous appelez réellement la méthode d'analyse dans votre modèle imbriqué en appelant:
new embeddedClass(embeddedData, {parse:true});
Vous pouvez définir autant de modèles imbriqués dans le champ model
que vous le souhaitez.
Bien sûr, si vous voulez aller jusqu'à sauvegarder le modèle imbriqué dans sa propre table. Cela ne suffirait pas. Mais dans le cas de la lecture et de la sauvegarde de l'objet dans son ensemble, cette solution devrait suffire.
Je publie ce code comme exemple de la proposition de Peter Lyon de redéfinir l'analyse. J'avais la même question et cela fonctionnait pour moi (avec un backend Rails). Ce code est écrit en Coffeescript. J'ai explicitement expliqué certaines choses aux personnes qui ne le connaissaient pas.
class AppName.Collections.PostsCollection extends Backbone.Collection
model: AppName.Models.Post
url: '/posts'
...
# parse: redefined to allow for nested models
parse: (response) -> # function definition
# convert each comment attribute into a CommentsCollection
if _.isArray response
_.each response, (obj) ->
obj.comments = new AppName.Collections.CommentsCollection obj.comments
else
response.comments = new AppName.Collections.CommentsCollection response.comments
return response
parse: function(response) {
if (_.isArray(response)) {
return _.each(response, function(obj) {
return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
});
} else {
response.comments = new AppName.Collections.CommentsCollection(response.comments);
}
return response;
};
Utilisation Backbone.AssociatedModel
de associations dorsales :
var Layout = Backbone.AssociatedModel.extend({
defaults : {
x : 0,
y : 0
}
});
var Image = Backbone.AssociatedModel.extend({
relations : [
type: Backbone.One,
key : 'layout',
relatedModel : Layout
],
defaults : {
name : '',
layout : null
}
});
Je ne suis pas sûr que Backbone ait un moyen recommandé de le faire. L'objet Layout a-t-il son propre ID et est-il enregistré dans la base de données principale? Si c'est le cas, vous pouvez en faire son propre modèle, comme vous l'avez fait. Sinon, vous pouvez le laisser comme un document imbriqué, mais assurez-vous de le convertir correctement vers et depuis JSON dans les méthodes save
et parse
. Si vous finissez par adopter une telle approche, je pense que votre exemple A est plus cohérent avec le réseau principal puisque set
mettra correctement à jour attributes
, mais encore une fois, je ne suis pas sûr de ce que Backbone fait avec les modèles imbriqués par défaut. Il est probable que vous aurez besoin d'un code personnalisé pour gérer cela.
Je choisirais l'option B si vous voulez garder les choses simples.
Une autre bonne option serait d'utiliser Backbone-Relational . Vous voudriez juste définir quelque chose comme:
var Image = Backbone.Model.extend({
relations: [
{
type: Backbone.HasOne,
key: 'layout',
relatedModel: 'Layout'
}
]
});
J'utilise le plugin Backbone DeepModel pour les modèles et attributs imbriqués.
https://github.com/powmedia/backbone-deep-model
Vous pouvez vous lier pour changer les événements des niveaux n profonds. par exemple: model.on('change:example.nestedmodel.attribute', this.myFunction);
Version CoffeeScript de rycfung's Belle réponse:
class ImageModel extends Backbone.Model
model: {
layout: LayoutModel
}
parse: (response) =>
for propName,propModel of @model
response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )
return response
N'est-ce pas si gentil? ;)
J'ai eu le même problème et j'ai expérimenté le code dans réponse de rycfung , ce qui est une excellente suggestion.
Si, toutefois, vous ne voulez pas set
directement les modèles imbriqués, ou si vous ne voulez pas passer constamment {parse: true}
dans options
, une autre approche serait de redéfinir set
lui-même.
Dans Backbone 1.0.0 , set
est appelé dans constructor
, unset
, clear
, fetch
et save
.
Considérez ce qui suit super model, pour tous les modèles qui doivent imbriquer des modèles et/ou des collections.
/** Compound supermodel */
var CompoundModel = Backbone.Model.extend({
/** Override with: key = attribute, value = Model / Collection */
model: {},
/** Override default setter, to create nested models. */
set: function(key, val, options) {
var attrs, prev;
if (key == null) { return this; }
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
// Run validation.
if (options) { options.validate = true; }
else { options = { validate: true }; }
// For each `set` attribute, apply the respective nested model.
if (!options.unset) {
for (key in attrs) {
if (key in this.model) {
if (!(attrs[key] instanceof this.model[key])) {
attrs[key] = new this.model[key](attrs[key]);
}
}
}
}
Backbone.Model.prototype.set.call(this, attrs, options);
if (!(attrs = this.changedAttributes())) { return this; }
// Bind new nested models and unbind previous nested models.
for (key in attrs) {
if (key in this.model) {
if (prev = this.previous(key)) {
this._unsetModel(key, prev);
}
if (!options.unset) {
this._setModel(key, attrs[key]);
}
}
}
return this;
},
/** Callback for `set` nested models.
* Receives:
* (String) key: the key on which the model is `set`.
* (Object) model: the `set` nested model.
*/
_setModel: function (key, model) {},
/** Callback for `unset` nested models.
* Receives:
* (String) key: the key on which the model is `unset`.
* (Object) model: the `unset` nested model.
*/
_unsetModel: function (key, model) {}
});
Notez que model
, _setModel
et _unsetModel
sont laissés en blanc volontairement. À ce niveau d'abstraction, vous ne pouvez probablement pas définir d'actions raisonnables pour les rappels. Cependant, vous pouvez les remplacer dans les sous-modèles qui étendent CompoundModel
.
Ces rappels sont utiles, par exemple, pour lier des écouteurs et propager des événements change
.
var Layout = Backbone.Model.extend({});
var Image = CompoundModel.extend({
defaults: function () {
return {
name: "example",
layout: { x: 0, y: 0 }
};
},
/** We need to override this, to define the nested model. */
model: { layout: Layout },
initialize: function () {
_.bindAll(this, "_propagateChange");
},
/** Callback to propagate "change" events. */
_propagateChange: function () {
this.trigger("change:layout", this, this.get("layout"), null);
this.trigger("change", this, null);
},
/** We override this callback to bind the listener.
* This is called when a Layout is set.
*/
_setModel: function (key, model) {
if (key !== "layout") { return false; }
this.listenTo(model, "change", this._propagateChange);
},
/** We override this callback to unbind the listener.
* This is called when a Layout is unset, or overwritten.
*/
_unsetModel: function (key, model) {
if (key !== "layout") { return false; }
this.stopListening();
}
});
Avec cela, vous avez la création automatique de modèles imbriqués et la propagation d'événements. Un exemple d'utilisation est également fourni et testé:
function logStringified (obj) {
console.log(JSON.stringify(obj));
}
// Create an image with the default attributes.
// Note that a Layout model is created too,
// since we have a default value for "layout".
var img = new Image();
logStringified(img);
// Log the image everytime a "change" is fired.
img.on("change", logStringified);
// Creates the nested model with the given attributes.
img.set("layout", { x: 100, y: 100 });
// Writing on the layout propagates "change" to the image.
// This makes the image also fire a "change", because of `_propagateChange`.
img.get("layout").set("x", 50);
// You may also set model instances yourself.
img.set("layout", new Layout({ x: 100, y: 100 }));
Sortie:
{"name":"example","layout":{"x":0,"y":0}}
{"name":"example","layout":{"x":100,"y":100}}
{"name":"example","layout":{"x":50,"y":100}}
{"name":"example","layout":{"x":100,"y":100}}
Je me rends compte que je suis en retard pour cette soirée, mais nous avons récemment publié un plugin pour gérer exactement ce scénario. Cela s'appelle backbone-nestify .
Donc, votre modèle imbriqué reste inchangé:
var Layout = Backbone.Model.extend({...});
Ensuite, utilisez le plugin pour définir le modèle contenant (en utilisant nderscore.extend ):
var spec = {
layout: Layout
};
var Image = Backbone.Model.extend(_.extend({
// ...
}, nestify(spec));
Après cela, en supposant que vous avez un modèle m
qui est une instance de Image
et que vous avez défini le code JSON à partir de la question sur m
, vous pouvez effectuer les opérations suivantes:
m.get("layout"); //returns the nested instance of Layout
m.get("layout|x"); //returns 100
m.set("layout|x", 50);
m.get("layout|x"); //returns 50
Utiliser des formes de colonne vertébrale
Il prend en charge les formulaires imbriqués, les modèles et toJSON. TOUS INTÉGRÉS
var Address = Backbone.Model.extend({
schema: {
street: 'Text'
},
defaults: {
street: "Arteaga"
}
});
var User = Backbone.Model.extend({
schema: {
title: { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
name: 'Text',
email: { validators: ['required', 'email'] },
birthday: 'Date',
password: 'Password',
address: { type: 'NestedModel', model: Address },
notes: { type: 'List', itemType: 'Text' }
},
constructor: function(){
Backbone.Model.apply(this, arguments);
},
defaults: {
email: "[email protected]"
}
});
var user = new User();
user.set({address: {street: "my other street"}});
console.log(user.toJSON()["address"]["street"])
//=> my other street
var form = new Backbone.Form({
model: user
}).render();
$('body').append(form.el);
Si vous ne voulez pas ajouter encore un autre framework, vous pouvez envisager de créer une classe de base avec set
et toJSON
surchargés et l'utiliser comme ceci:
// Declaration
window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
nestedTypes: {
background: window.app.viewer.Model.Image,
images: window.app.viewer.Collection.MediaCollection
}
});
// Usage
var gallery = new window.app.viewer.Model.GallerySection({
background: { url: 'http://example.com/example.jpg' },
images: [
{ url: 'http://example.com/1.jpg' },
{ url: 'http://example.com/2.jpg' },
{ url: 'http://example.com/3.jpg' }
],
title: 'Wow'
}); // (fetch will work equally well)
console.log(gallery.get('background')); // window.app.viewer.Model.Image
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
console.log(gallery.get('title')); // plain string
Vous aurez besoin de BaseModel
de cette réponse (disponible, si vous le souhaitez, en tant que Gist ).
Nous avons également ce problème et un membre du personnel de l’équipe a implémenté un plugin nommé backbone-nested-attributs.
L'utilisation est très simple. Exemple:
var Tree = Backbone.Model.extend({
relations: [
{
key: 'fruits',
relatedModel: function () { return Fruit }
}
]
})
var Fruit = Backbone.Model.extend({
})
Avec cela, le modèle Tree peut accéder ensuite aux fruits:
tree.get('fruits')
Vous pouvez voir plus d'informations ici: