web-dev-qa-db-fra.com

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

J'ai une question simple à propos des fonctions get et set de Backbone.js.

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

Une autre question:

2) Dans le modèle, mis à part l'objet defaults, 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? 

105
fortuneRice

Bien que this.model.get("obj1").myAttribute1 soit correct, c'est un peu problématique car vous pourriez alors être tenté de faire le même type de chose pour set, c'est-à-dire.

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

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

Une meilleure solution consisterait à ne jamais imbriquer des POJSO ("anciens objets JavaScript simples") dans vos modèles, mais à imbriquer des classes de modèle personnalisées. Cela ressemblerait à quelque chose comme ça:

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, etc. Exemple de travail ici: http://jsfiddle.net/g3U7j/

144
Domenic

J'ai créé backbone-deep-model pour cela - il suffit d'étendre Backbone.DeepModel au lieu de Backbone.Model et vous pouvez ensuite utiliser des chemins pour obtenir/définir les 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
74
evilcelery

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

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.

16
Rusty

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 "a.b" avec:

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

Maintenant, votre modèle aura des attributs tels que:

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

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

Il y a une solution à laquelle personne n'a encore pensé qui est beaucoup à utiliser. 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, vous pouvez créer un clone du dictionnaire d'origine, y définir la propriété imbriquée et définir l'ensemble du 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
3
bicycle

J'ai eu le même problème avec @pagewil et @Benno avec la solution de @ Domenic. Ma réponse a été d'écrire plutôt 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 (c’est ce qui se produit 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 convenait assez bien.

2
Cory Gagliardi

La solution proposée par Domenic présente certains inconvénients. Supposons que vous vouliez écouter l'événement 'changer'. Dans ce cas, la méthode 'initialize' ne sera pas déclenchée et votre valeur personnalisée pour attribut sera remplacée par l'objet json du serveur. Dans mon projet, j'ai fait face à 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);
}, 
2
yuliskov

Si vous interagissez avec le backend, ce qui nécessite un objet avec une structure imbriquée .

backbone.linear peut vous aider.

0
darky

Bien que, dans certains cas, l'utilisation de modèles Backbone au lieu d'attributs d'objet imbriqués ait un sens, comme mentionné par Domenic, dans des cas plus simples, vous pouvez créer une fonction de définition 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);
    }
})
0
Ibrahim Muhammad