Backbone.js obtient et définit l'attribut d'objet imbriqué

105

J'ai une question simple sur les fonctions get et set de Backbone.js .

1) Avec le code ci-dessous, comment puis-je 'obtenir' ou 'définir' obj1.myAttribute1 directement?

Une autre question:

2) Dans le modèle, à part l' objet par défaut , où puis-je / dois-je déclarer les autres attributs de mon modèle, de sorte qu'ils soient accessibles via les méthodes get et set de Backbone?

var MyModel = Backbone.Model.extend({
    defaults: {
        obj1 : {
            "myAttribute1" : false,
            "myAttribute2" : true,
        }
    }
})

var MyView = Backbone.View.extend({
    myFunc: function(){
        console.log(this.model.get("obj1"));
        //returns the obj1 object
        //but how do I get obj1.myAttribute1 directly so that it returns false?
    }
});

Je sais que je peux faire:

this.model.get("obj1").myAttribute1;

mais est-ce une bonne pratique?

fortuneRiz
la source
3
Bien que ce ne soit pas une réponse à la question: chaque fois que vous spécifiez un objet (tout ce qui est passé par référence) dans defaults(obj1 dans ce cas), ce même objet sera partagé entre toutes les instances du modèle. La pratique actuelle consiste à définir defaultscomme une fonction qui renvoie un objet à utiliser par défaut. backbonejs.org/#Model-defaults (voir la note en italique)
Jonathan F
1
@JonathanF Les commentaires ne sont pas destinés aux réponses , vous n'avez donc jamais eu besoin de la déclaration :)
TJ

Réponses:

144

Bien que ce this.model.get("obj1").myAttribute1soit bien, c'est un peu problématique car alors vous pourriez être tenté de faire le même type de chose pour set, ie

this.model.get("obj1").myAttribute1 = true;

Mais si vous faites cela, vous n'obtiendrez pas les avantages des modèles Backbone myAttribute1, comme les événements de changement ou la validation.

Une meilleure solution serait de ne jamais imbriquer des POJSO («simples objets JavaScript anciens») dans vos modèles, mais plutôt d'imbriquer des classes de modèles personnalisées. Cela ressemblerait donc à ceci:

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.Model.extend({
    initialize: function () {
        this.set("obj1", new Obj());
    }
});

Ensuite, le code d'accès serait

var x = this.model.get("obj1").get("myAttribute1");

mais plus important encore, le code de réglage serait

this.model.get("obj1").set({ myAttribute1: true });

qui déclenchera des événements de changement appropriés et autres. Exemple de travail ici: http://jsfiddle.net/g3U7j/

Domenic
la source
24
À cette réponse, j'ajouterai l'avis que cette solution bascule sur les violations généralisées de la loi de Demeter. J'envisagerais d'ajouter des méthodes pratiques qui masquent la navigation vers l'objet imbriqué. Fondamentalement, vos appelants n'ont pas besoin de connaître la structure interne du modèle; après tout, cela peut changer et les appelants ne devraient pas être plus sages.
Bill Eisenhauer
7
Je ne peux pas faire fonctionner ça pour moi. Lance une erreur:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage
1
@ChristianNunciato, pagewil, Benno: Vous semblez avoir manqué le but du message, qui est d'imbriquer les modèles Backbone dans les modèles Backbone. N'imbriquez pas d'objets simples dans les modèles Backbone. Exemple de travail ici: jsfiddle.net/g3U7j
Domenic
1
Je n'ai pas inspecté le code de backbone.js, mais d'après mon test, si vous avez un modèle personnalisé imbriqué et que vous modifiez une propriété de celui-ci avec set (), son modèle parent ne déclenchera pas lui-même un événement 'change'; J'ai dû déclencher l'événement moi-même. Je devrais vraiment simplement inspecter le code, mais est-ce aussi votre compréhension?
tom
2
@tom c'est correct. Backbone n'a pas de cas particulier lorsque les propriétés des modèles sont des instances de Backbone.Model, puis commencez à faire des bulles d'événements magiques.
Domenic
74

J'ai créé backbone-deep-model pour cela - étendez simplement Backbone.DeepModel au lieu de Backbone.Model et vous pouvez ensuite utiliser des chemins pour obtenir / définir des attributs de modèle imbriqués. Il maintient également les événements de changement.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
maléfique
la source
1
Oui, si vous regardez l' API, il y a un exemple comme//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed
Fonctionne très bien! Mais la ligne 2 de votre exemple nécessite-t-elle un deux-points au lieu d'une virgule?
mariachi
16

La solution de Domenic fonctionnera cependant chaque nouveau MyModel pointera vers la même instance d'Obj. Pour éviter cela, MyModel devrait ressembler à:

var MyModel = Backbone.Model.extend({
  initialize: function() {
     myDefaults = {
       obj1: new Obj()
     } 
     this.set(myDefaults);
  }
});

Voir la réponse de c3rin @ https://stackoverflow.com/a/6364480/1072653 pour une explication complète.

Rouillé
la source
1
Pour les futurs lecteurs, ma réponse a été mise à jour pour intégrer le meilleur de la réponse de Rusty.
Domenic
2
Le demandeur doit marquer cela comme la réponse acceptée. Domenic est un bon début, mais cela résout un problème avec lui.
Jon Raasch
5

J'utilise cette approche.

Si vous avez un modèle Backbone comme celui-ci:

var nestedAttrModel = new Backbone.Model({
    a: {b: 1, c: 2}
});

Vous pouvez définir l'attribut "ab" avec:

var _a = _.omit(nestedAttrModel.get('a')); // from underscore.js
_a.b = 3;
nestedAttrModel.set('a', _a);

Maintenant, votre modèle aura des attributs comme:

{a: {b: 3, c: 2}}

avec l'événement "change" déclenché.

Natthakit Susanthitanon
la source
1
Es-tu sûr de ça? Cela ne fonctionne pas pour moi. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Cela ne déclenche aucun événement de changement pour moi :(
HungryCoder
1
Je vois que cela fonctionne lorsque je clone l'attribut comme _.clone(m.get('x')). merci
HungryCoder
Merci @HungryCoder cela a fonctionné pour moi aussi une fois cloné. Backbone doit comparer l'objet que vous êtes settingavec l'objet que vous êtes gettingà l'heure définie. Donc, si vous ne clonez pas les deux objets, les deux objets comparés sont exactement les mêmes à l'heure définie.
Derek Dahmer
N'oubliez pas que les objets sont passés par référence et sont mutables, contrairement aux primitives de chaîne et de nombre. Les méthodes set et constructeur de Backbone tentent un clone superficiel de toute référence d'objet passée en argument. Toutes les références à d'autres objets dans les propriétés de cet objet ne sont pas clonées. Lorsque vous le définissez et le récupérez, la référence est la même, ce qui signifie que vous pouvez faire muter le modèle sans déclencher de modification.
niall.campbell
3

Il y a une solution à laquelle personne n'a encore pensé et qui est très utile. En effet, vous ne pouvez pas définir directement les attributs imbriqués, sauf si vous utilisez une bibliothèque tierce dont vous ne voulez probablement pas. Cependant, ce que vous pouvez faire est de créer un clone du dictionnaire d'origine, d'y définir la propriété imbriquée et de définir l'ensemble de ce dictionnaire. Part de gâteau.

//How model.obj1 looks like
obj1: {
    myAttribute1: false,
    myAttribute2: true,
    anotherNestedDict: {
        myAttribute3: false
    }
}

//Make a clone of it
var cloneOfObject1 = JSON.parse(JSON.stringify(this.model.get('obj1')));

//Let's day we want to change myAttribute1 to false and myAttribute3 to true
cloneOfObject1.myAttribute2 = false;
cloneOfObject1.anotherNestedDict.myAttribute3 = true;

//And now we set the whole dictionary
this.model.set('obj1', cloneOfObject1);

//Job done, happy birthday
vélo
la source
2

J'ai eu le même problème que @pagewil et @Benno avaient avec la solution de @ Domenic. Ma réponse a été d'écrire à la place une simple sous-classe de Backbone.Model qui résout le problème.

// Special model implementation that allows you to easily nest Backbone models as properties.
Backbone.NestedModel = Backbone.Model.extend({
    // Define Backbone models that are present in properties
    // Expected Format:
    // [{key: 'courses', model: Course}]
    models: [],

    set: function(key, value, options) {
        var attrs, attr, val;

        if (_.isObject(key) || key == null) {
            attrs = key;
            options = value;
        } else {
            attrs = {};
            attrs[key] = value;
        }

        _.each(this.models, function(item){
            if (_.isObject(attrs[item.key])) {
                attrs[item.key] = new item.model(attrs[item.key]);
            }
        },this);

        return Backbone.Model.prototype.set.call(this, attrs, options);
    }
});

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.NestedModel.extend({
    defaults: {
        obj1: new Obj()
    },

    models: [{key: 'obj1', model: Obj}]
});

Ce que NestedModel fait pour vous, c'est leur permettre de fonctionner (ce qui se passe lorsque myModel est défini via des données JSON):

var myModel = new MyModel();
myModel.set({ obj1: { myAttribute1: 'abc', myAttribute2: 'xyz' } });
myModel.set('obj1', { myAttribute1: 123, myAttribute2: 456 });

Il serait facile de générer automatiquement la liste des modèles lors de l'initialisation, mais cette solution me suffisait.

Cory Gagliardi
la source
2

La solution proposée par Domenic présente quelques inconvénients. Supposons que vous souhaitiez écouter l'événement «changer». Dans ce cas, la méthode 'initialize' ne sera pas déclenchée et votre valeur personnalisée pour l'attribut sera remplacée par l'objet json du serveur. Dans mon projet, j'ai été confronté à ce problème. Ma solution pour remplacer la méthode 'set' de Model:

set: function(key, val, options) {
    if (typeof key === 'object') {
        var attrs = key;
        attrs.content = new module.BaseItem(attrs.content || {});
        attrs.children = new module.MenuItems(attrs.children || []);
    }

    return Backbone.Model.prototype.set.call(this, key, val, options);
}, 
yuliskov
la source
0

Bien que dans certains cas, l'utilisation de modèles Backbone au lieu d'attributs d'objets imbriqués ait du sens, comme l'a mentionné Domenic, dans des cas plus simples, vous pouvez créer une fonction de réglage dans le modèle:

var MyModel = Backbone.Model.extend({
    defaults: {
        obj1 : {
            "myAttribute1" : false,
            "myAttribute2" : true,
        }
    },
    setObj1Attribute: function(name, value) {
        var obj1 = this.get('obj1');
        obj1[name] = value;
        this.set('obj1', obj1);
    }
})
Ibrahim Muhammad
la source
0

Si vous interagissez avec le backend, ce qui nécessite un objet avec une structure d'imbrication. Mais avec backbone plus facile à travailler avec une structure linéaire.

backbone.linear peut vous aider.

sombre
la source