Backbone.js ruft das Attribut für verschachtelte Objekte ab und legt es fest

105

Ich habe eine einfache Frage zu Backbone.js' get und set - Funktionen.

1) Wie kann ich mit dem folgenden Code obj1.myAttribute1 direkt 'abrufen' oder 'setzen'?

Eine andere Frage:

2) Wo kann / sollte ich im Modell neben dem Standardobjekt die anderen Attribute meines Modells deklarieren, damit auf sie über die get- und set-Methoden von Backbone zugegriffen werden kann?

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

Ich weiß, dass ich tun kann:

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

aber ist das eine gute Praxis?

FortuneRice
quelle
3
defaultsDies ist zwar keine Antwort auf die Frage: Wenn Sie in (obj1 in diesem Fall) ein Objekt angeben (alles, was als Referenz übergeben wird), wird dasselbe Objekt für alle Instanzen des Modells freigegeben. Die derzeitige Praxis besteht darin, defaultseine Funktion zu definieren , die ein Objekt zurückgibt, das als Standard verwendet werden soll. backbonejs.org/#Model-defaults (siehe den kursiven Hinweis)
Jonathan F
1
@ JonathanF Kommentare sind nicht für Antworten gedacht, daher brauchten Sie die Erklärung nie :)
TJ

Antworten:

144

Es this.model.get("obj1").myAttribute1ist zwar in Ordnung, aber ein bisschen problematisch, weil Sie dann möglicherweise versucht sind, das Gleiche für das Set zu tun, d. H.

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

Wenn Sie dies tun, erhalten Sie jedoch nicht die Vorteile von Backbone-Modellen myAttribute1wie Änderungsereignisse oder Validierung.

Eine bessere Lösung wäre, niemals POJSOs ("einfache alte JavaScript-Objekte") in Ihren Modellen zu verschachteln und stattdessen benutzerdefinierte Modellklassen zu verschachteln. Es würde also ungefähr so ​​aussehen:

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

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

Dann wäre der Zugangscode

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

aber was noch wichtiger ist, wäre der Einstellungscode

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

die geeignete Änderungsereignisse und dergleichen auslösen. Arbeitsbeispiel hier: http://jsfiddle.net/g3U7j/

Domenic
quelle
24
Zu dieser Antwort möchte ich den Hinweis hinzufügen, dass diese Lösung auf weit verbreiteten Verstößen gegen das Demeter-Gesetz beruht. Ich würde in Betracht ziehen, bequeme Methoden hinzuzufügen, die die Navigation zum verschachtelten Objekt verbergen. Grundsätzlich müssen Ihre Anrufer die interne Struktur des Modells nicht kennen. Immerhin kann es sich ändern und die Anrufer sollten nicht klüger sein.
Bill Eisenhauer
7
Ich kann das nicht für mich arbeiten lassen. Wirft Fehler:Uncaught TypeError: Object #<Object> has no method 'set'
Wilsonpage
1
@ChristianNunciato, pagewil, Benno: Sie scheinen den Punkt des Beitrags verpasst zu haben, der darin besteht, Backbone-Modelle in Backbone-Modellen zu verschachteln. Verschachteln Sie keine einfachen Objekte in Backbone-Modellen. Arbeitsbeispiel hier: jsfiddle.net/g3U7j
Domenic
1
Ich habe den Code von backbone.js nicht überprüft, aber wenn Sie nach meinem Test ein verschachteltes benutzerdefiniertes Modell haben und eine Eigenschaft davon mit set () ändern, löst das übergeordnete Modell selbst kein 'change'-Ereignis aus. Ich musste das Ereignis selbst abfeuern. Ich sollte wirklich nur den Code überprüfen, aber ist das auch Ihr Verständnis?
Tom
2
@ Tom das ist richtig. Das Backbone ist kein Sonderfall, wenn Eigenschaften von Modellen Instanzen von Backbone.Modelsind und dann mit dem Sprudeln magischer Ereignisse beginnen.
Domenic
74

Ich habe dafür ein Backbone-Deep-Modell erstellt - erweitern Sie einfach Backbone.DeepModel anstelle von Backbone.Model, und Sie können dann Pfade verwenden, um verschachtelte Modellattribute abzurufen / festzulegen. Es werden auch Änderungsereignisse verwaltet.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
böser Schmuck
quelle
1
Ja, wenn Sie sich die API ansehen, gibt es ein Beispiel wie//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed
Funktioniert super! Aber erfordert Zeile 2 in Ihrem Beispiel einen Doppelpunkt anstelle eines Kommas?
Mariachi
16

Die Lösung von Domenic wird funktionieren, jedoch wird jedes neue MyModel auf dieselbe Instanz von Obj verweisen. Um dies zu vermeiden, sollte MyModel folgendermaßen aussehen:

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

Eine vollständige Erklärung finden Sie in der Antwort von c3rin unter https://stackoverflow.com/a/6364480/1072653 .

rostig
quelle
1
Für zukünftige Leser wurde meine Antwort aktualisiert, um das Beste aus Rustys Antwort aufzunehmen.
Domenic
2
Der Fragesteller sollte dies als akzeptierte Antwort markieren. Domenic's ist ein guter Anfang, aber dies löst ein Problem damit.
Jon Raasch
5

Ich benutze diesen Ansatz.

Wenn Sie ein Backbone-Modell wie dieses haben:

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

Sie können das Attribut "ab" setzen mit:

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

Jetzt hat Ihr Modell Attribute wie:

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

mit dem Ereignis "change" ausgelöst.

Natthakit Susanthitanon
quelle
1
Bist du dir da sicher? das funktioniert bei mir nicht. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Dies löst für mich kein Änderungsereignis aus :(
HungryCoder
1
Ich sehe, dass es funktioniert, wenn ich das Attribut wie klone _.clone(m.get('x')). danke
HungryCoder
Danke @HungryCoder, es hat auch bei mir funktioniert, als es geklont wurde. Das Backbone muss das Objekt, das Sie sind, settingmit dem Objekt vergleichen, das Sie gettingzur festgelegten Zeit sind. Wenn Sie also die beiden Objekte nicht klonen, sind die beiden verglichenen Objekte zur festgelegten Zeit genau gleich.
Derek Dahmer
Denken Sie daran, dass Objekte als Referenz übergeben werden und im Gegensatz zu Zeichenfolgen- und Zahlenprimitiven veränderbar sind. Die Set- und Konstruktormethoden von Backbone versuchen einen flachen Klon einer als Argument übergebenen Objektreferenz. Verweise auf andere Objekte in den Eigenschaften dieses Objekts werden nicht geklont. Wenn Sie es festlegen und abrufen, ist die Referenz dieselbe. Dies bedeutet, dass Sie das Modell mutieren können, ohne eine Änderung auszulösen.
niall.campbell
3

Es gibt eine Lösung, an die noch niemand gedacht hat und die viel zu gebrauchen ist. Sie können verschachtelte Attribute tatsächlich nicht direkt festlegen, es sei denn, Sie verwenden eine Bibliothek eines Drittanbieters, die Sie wahrscheinlich nicht möchten. Sie können jedoch einen Klon des ursprünglichen Wörterbuchs erstellen, die verschachtelte Eigenschaft dort festlegen und dann das gesamte Wörterbuch festlegen. Stück Kuchen.

//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
Fahrrad
quelle
2

Ich hatte das gleiche Problem, das @pagewil und @Benno mit der Lösung von @ Domenic hatten. Meine Antwort war, stattdessen eine einfache Unterklasse von Backbone.Model zu schreiben, die das Problem behebt.

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

Was NestedModel für Sie tut, ist zuzulassen, dass diese funktionieren (was passiert, wenn myModel über JSON-Daten festgelegt wird):

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

Es wäre einfach, die Modellliste beim Initialisieren automatisch zu generieren, aber diese Lösung war gut genug für mich.

Cory Gagliardi
quelle
2

Die von Domenic vorgeschlagene Lösung weist einige Nachteile auf. Angenommen, Sie möchten das Ereignis "Ändern" anhören. In diesem Fall wird die Methode 'initialize' nicht ausgelöst und Ihr benutzerdefinierter Wert für das Attribut wird durch das json-Objekt vom Server ersetzt. In meinem Projekt war ich mit diesem Problem konfrontiert. Meine Lösung zum Überschreiben der 'set'-Methode des Modells:

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
quelle
0

Während in einigen Fällen die Verwendung von Backbone-Modellen anstelle von verschachtelten Objektattributen sinnvoll ist, wie in Domenic erwähnt, können Sie in einfacheren Fällen eine Setter-Funktion im Modell erstellen:

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
quelle
0

Wenn Sie mit dem Backend interagieren, für das ein Objekt mit Verschachtelungsstruktur erforderlich ist. Aber mit Backbone einfacher mit linearer Struktur zu arbeiten.

backbone.linear kann Ihnen helfen.

dunkel
quelle