Backbone.js ottiene e imposta l'attributo dell'oggetto annidato


105

Ho una semplice domanda sulle funzioni get e set di Backbone.js .

1) Con il codice sottostante, come posso "ottenere" o "impostare" obj1.myAttribute1 direttamente?

Un'altra domanda:

2) Nel Modello, a parte l' oggetto defaults , dove posso / devo dichiarare gli altri attributi del mio modello, in modo tale che sia possibile accedervi tramite i metodi get e set di 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?
    }
});

So che posso fare:

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

ma è una buona pratica?


3
Sebbene non sia una risposta alla domanda: ogni volta che si specifica un oggetto (qualsiasi cosa passata per riferimento) in defaults(obj1 in questo caso), lo stesso oggetto verrà condiviso tra tutte le istanze del modello. La pratica corrente è quella di definire defaultsuna funzione che restituisce un oggetto da utilizzare come predefinito. backbonejs.org/#Model-defaults (vedere la nota in corsivo)
Jonathan F,

1
@JonathanF I commenti non sono pensati per le risposte, quindi non hai mai avuto bisogno della dichiarazione :)
TJ

Risposte:


144

Anche se this.model.get("obj1").myAttribute1va bene, è un po 'problematico perché potresti essere tentato di fare lo stesso tipo di cose per il set, ad es

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

Ma se lo fai, non otterrai i vantaggi dei modelli Backbone per myAttribute1, come eventi di modifica o convalida.

Una soluzione migliore sarebbe quella di non annidare mai POJSO ("semplici vecchi oggetti JavaScript") nei modelli, e invece di nidificare classi di modelli personalizzate. Quindi sarebbe simile a questo:

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

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

Quindi il codice di accesso sarebbe

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

ma ancora più importante sarebbe il codice di impostazione

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

che attiverà eventi di modifica appropriati e simili. Esempio di lavoro qui: http://jsfiddle.net/g3U7j/


24
A questa risposta, aggiungerei il consiglio che questa soluzione vacilla su diffuse violazioni della Legge di Demetra. Considererei l'aggiunta di metodi di convenienza che nascondono la navigazione all'oggetto annidato. Fondamentalmente, i chiamanti non hanno bisogno di conoscere la struttura interna del modello; dopotutto, potrebbe cambiare e chi chiama non dovrebbe essere più saggio.
Bill Eisenhauer

7
Non riesco a farlo funzionare per me. Uncaught TypeError: Object #<Object> has no method 'set'
Genera

1
@ChristianNunciato, pagewil, Benno: sembra che tu abbia perso il punto del post, ovvero annidare i modelli Backbone all'interno dei modelli Backbone. Non annidare oggetti semplici all'interno dei modelli Backbone. Esempio funzionante qui: jsfiddle.net/g3U7j
Domenic

1
Non ho ispezionato il codice backbone.js, ma dal mio test, se hai un modello personalizzato annidato e ne modifichi una proprietà con set (), il suo modello genitore non attiverà un evento 'change' stesso; Ho dovuto licenziare l'evento da solo. Dovrei davvero ispezionare il codice, ma anche questa è la tua comprensione?
tom

2
@tom che è corretto. Backbone non è un caso speciale per quando le proprietà dei modelli sono istanze di Backbone.Modele quindi iniziano a fare bolle di eventi magici.
Domenic

74

Ho creato backbone-deep-model per questo: estendi semplicemente Backbone.DeepModel invece di Backbone.Model e puoi quindi utilizzare i percorsi per ottenere / impostare gli attributi del modello nidificato. Mantiene anche gli eventi di cambiamento.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric

1
Sì, se guardi l' API c'è un esempio come//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed

Funziona alla grande! Ma la riga 2 nel tuo esempio richiede i due punti anziché una virgola?
mariachi

16

La soluzione di Domenic funzionerà, tuttavia ogni nuovo MyModel punterà alla stessa istanza di Obj. Per evitare ciò, MyModel dovrebbe essere simile a:

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

Vedi la risposta di c3rin @ https://stackoverflow.com/a/6364480/1072653 per una spiegazione completa.


1
Per i futuri lettori, la mia risposta è stata aggiornata per incorporare la migliore risposta di Rusty.
Domenic

2
Il richiedente dovrebbe contrassegnarlo come risposta accettata. Quello di Domenic è un ottimo inizio, ma questo risolve un problema.
Jon Raasch

5

Uso questo approccio.

Se hai un modello Backbone come questo:

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

Puoi impostare l'attributo "ab" con:

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

Ora il tuo modello avrà attributi come:

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

con l'evento "change" attivato.


1
Sei sicuro di questo? questo non funziona per me. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Questo non attiva alcun evento di modifica per me :(
HungryCoder

1
Vedo che funziona quando clono l'attributo come _.clone(m.get('x')). grazie
HungryCoder

Grazie @ HungryCoder ha funzionato anche per me quando è stato clonato. Backbone deve confrontare l'oggetto che sei settingcon l'oggetto che ti trovi gettingall'ora impostata. Quindi, se non cloni i due oggetti, i due oggetti confrontati sono esattamente gli stessi all'ora impostata.
Derek Dahmer

Ricorda che gli oggetti vengono passati per riferimento e sono mutabili, a differenza delle primitive stringa e numero. I metodi set e costruttore di Backbone tentano un clone superficiale di qualsiasi riferimento a un oggetto passato come argomento. Qualsiasi riferimento ad altri oggetti nelle proprietà di quell'oggetto non viene clonato. Quando lo imposti e lo recuperi, il riferimento è lo stesso, il che significa che puoi modificare il modello senza attivare una modifica.
niall.campbell

3

C'è una soluzione a cui nessuno ha ancora pensato che è molto da usare. In effetti, non puoi impostare direttamente gli attributi annidati, a meno che non utilizzi una libreria di terze parti che probabilmente non desideri. Tuttavia, quello che puoi fare è creare un clone del dizionario originale, impostare la proprietà annidata lì e quindi impostare l'intero dizionario. Pezzo di torta.

//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

2

Ho avuto lo stesso problema di @pagewil e @Benno con la soluzione di @ Domenic. La mia risposta è stata invece di scrivere una semplice sottoclasse di Backbone.Model che risolva il problema.

// 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}]
});

Ciò che NestedModel fa per te è consentire a questi di funzionare (che è ciò che accade quando myModel viene impostato tramite dati JSON):

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

Sarebbe facile generare automaticamente l'elenco dei modelli durante l'inizializzazione, ma questa soluzione è stata abbastanza buona per me.


2

La soluzione proposta da Domenic presenta alcuni inconvenienti. Supponi di voler ascoltare l'evento di "cambiamento". In tal caso, il metodo "inizializza" non verrà attivato e il valore personalizzato per l'attributo verrà sostituito con l'oggetto json dal server. Nel mio progetto ho affrontato questo problema. La mia soluzione per sovrascrivere il metodo "set" del modello:

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);
}, 

0

Mentre in alcuni casi l'uso di modelli Backbone invece di attributi Object annidati ha senso come ha detto Domenic, in casi più semplici potresti creare una funzione setter nel modello:

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

Se interagisci con il backend, che richiede un oggetto con struttura di annidamento. Ma con la spina dorsale più facile da lavorare con la struttura lineare.

backbone.linear può aiutarti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.