Backbone.js pobiera i ustawia atrybut obiektu zagnieżdżonego

105

Mam proste pytanie dotyczące funkcji pobierania i ustawiania Backbone.js .

1) Za pomocą poniższego kodu, w jaki sposób mogę bezpośrednio „pobrać” lub „ustawić” obj1.myAttribute1?

Inne pytanie:

2) W modelu, oprócz obiektu defaults , gdzie mogę / powinienem zadeklarować inne atrybuty mojego modelu, tak aby można było uzyskać do nich dostęp za pośrednictwem metod get and set 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?
    }
});

Wiem, że potrafię:

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

ale czy to dobra praktyka?

fortuneRice
źródło
3
Chociaż nie jest to odpowiedź na pytanie: za każdym razem, gdy defaultsokreślasz obiekt (cokolwiek przekazanego przez odniesienie) w (w tym przypadku obj1), ten sam obiekt będzie współdzielony we wszystkich instancjach modelu. Obecna praktyka polega na definiowaniu defaultsjako funkcji, która zwraca obiekt, który ma być użyty jako domyślny. backbonejs.org/#Model-defaults (patrz notatka zapisana kursywą)
Jonathan F
1
@JonathanF Komentarze nie służą do odpowiedzi, więc deklaracja nigdy nie była potrzebna :)
TJ

Odpowiedzi:

144

Chociaż this.model.get("obj1").myAttribute1jest w porządku, jest to trochę problematyczne, ponieważ wtedy możesz ulec pokusie zrobienia tego samego rodzaju dla zestawu, tj.

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

Ale jeśli to zrobisz, nie uzyskasz korzyści z modeli Backbone myAttribute1, takich jak zdarzenia zmian lub walidacja.

Lepszym rozwiązaniem byłoby nigdy nie zagnieżdżać POJSO („zwykłych starych obiektów JavaScript”) w modelach, a zamiast tego zagnieżdżać niestandardowe klasy modeli. Więc wyglądałoby to mniej więcej tak:

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

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

Wtedy byłby kod dostępu

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

ale co ważniejsze, kod ustawień byłby

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

które wywołają odpowiednie zmiany i tym podobne. Przykład roboczy tutaj: http://jsfiddle.net/g3U7j/

Domenic
źródło
24
Do tej odpowiedzi dodałbym wskazówkę, że to rozwiązanie balansuje na tle powszechnych naruszeń Prawa Demeter. Rozważyłbym dodanie wygodnych metod, które ukrywają nawigację do zagnieżdżonego obiektu. Zasadniczo dzwoniący nie muszą znać wewnętrznej struktury modelu; w końcu może się to zmienić i dzwoniący nie powinni być mądrzejsi.
Bill Eisenhauer
7
Nie mogę sprawić, żeby to zadziałało dla mnie. Zgłasza błąd:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage
1
@ChristianNunciato, pagewil, Benno: Wygląda na to, że przegapiłeś sedno posta, którym jest zagnieżdżanie modeli Backbone w modelach Backbone. Nie zagnieżdżaj zwykłych obiektów w modelach Backbone. Przykład roboczy tutaj: jsfiddle.net/g3U7j
Domenic
1
Nie sprawdzałem kodu backbone.js, ale z mojego testu, jeśli masz zagnieżdżony model niestandardowy i zmienisz jego właściwość za pomocą set (), jego model nadrzędny sam nie uruchomi zdarzenia „change”; Musiałem sam odpalić to wydarzenie. Naprawdę powinienem po prostu sprawdzić kod, ale czy to też rozumiesz?
tom
2
@tom, to prawda. Backbone nie ma specjalnego przypadku, gdy właściwości modeli są instancjami Backbone.Model, a następnie zacznij robić magiczne wydarzenie.
Domenic
74

Stworzyłem w tym celu model szkieletowy - po prostu rozszerz Backbone.DeepModel zamiast Backbone.Model, a następnie możesz użyć ścieżek do pobrania / ustawienia atrybutów zagnieżdżonych modeli. Utrzymuje również zmiany.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
zło
źródło
1
Tak, jeśli spojrzysz na API , jest przykład//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed
Działa świetnie! Ale czy wiersz 2 w twoim przykładzie wymaga dwukropka zamiast przecinka?
mariachi,
16

Rozwiązanie Domenic będzie działać, jednak każdy nowy MyModel będzie wskazywał na tę samą instancję Obj. Aby tego uniknąć, MyModel powinien wyglądać następująco:

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

Zobacz odpowiedź c3rin @ https://stackoverflow.com/a/6364480/1072653, aby uzyskać pełne wyjaśnienie.

Zardzewiały
źródło
1
Dla przyszłych czytelników moja odpowiedź została zaktualizowana, aby uwzględnić najlepszą odpowiedź Rusty'ego.
Domenic
2
Pytający powinien oznaczyć to jako zaakceptowaną odpowiedź. Domenic's to świetny początek, ale to rozwiązuje problem.
Jon Raasch
5

Używam tego podejścia.

Jeśli masz taki model Backbone:

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

Możesz ustawić atrybut „ab” za pomocą:

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

Teraz twój model będzie miał takie atrybuty, jak:

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

z uruchomionym zdarzeniem „zmiana”.

Natthakit Susanthitanon
źródło
1
Jesteś tego pewien? to nie działa dla mnie. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Nie wywołuje to dla mnie żadnego zdarzenia zmiany :(
HungryCoder
1
Widzę, że działa, gdy klonuję atrybut jak _.clone(m.get('x')). dzięki
HungryCoder
Dzięki @HungryCoder, dla mnie też zadziałało po sklonowaniu. Backbone musi porównać obiekt, którym jesteś, settingz obiektem, w którym się znajdujesz gettingw określonym czasie. Więc jeśli nie sklonujesz dwóch obiektów, to dwa porównywane obiekty będą dokładnie takie same w ustalonym czasie.
Derek Dahmer
Pamiętaj, że obiekty są przekazywane przez referencje i są modyfikowalne, w przeciwieństwie do łańcuchów i liczb. Metody zestawu i konstruktora Backbone próbują sklonować dowolne odwołanie do obiektu przekazane jako argument. Wszelkie odwołania do innych obiektów we właściwościach tego obiektu nie są klonowane. Po ustawieniu i pobraniu referencja jest taka sama, co oznacza, że ​​można modyfikować model bez wywoływania zmiany.
niall.campbell
3

Jest jedno rozwiązanie, o którym nikt jeszcze nie pomyślał, które jest bardzo przydatne. Rzeczywiście nie możesz bezpośrednio ustawić zagnieżdżonych atrybutów, chyba że używasz biblioteki innej firmy, której prawdopodobnie nie chcesz. Możesz jednak zrobić klon oryginalnego słownika, ustawić tam zagnieżdżoną właściwość, a następnie ustawić cały słownik. Bułka z masłem.

//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
rower
źródło
2

Miałem ten sam problem @pagewil i @Benno z rozwiązaniem @ Domenic. Moją odpowiedzią było napisanie zamiast tego prostej podklasy Backbone.Model, która rozwiązuje problem.

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

To, co robi dla Ciebie NestedModel, to pozwala im działać (co dzieje się, gdy myModel jest ustawiany za pomocą danych JSON):

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

Łatwo byłoby wygenerować listę modeli automatycznie podczas inicjalizacji, ale to rozwiązanie było dla mnie wystarczająco dobre.

Cory Gagliardi
źródło
2

Rozwiązanie zaproponowane przez Domenic ma pewne wady. Powiedz, że chcesz słuchać zdarzenia „zmiana”. W takim przypadku metoda „initialize” nie zostanie uruchomiona, a niestandardowa wartość atrybutu zostanie zastąpiona obiektem json z serwera. W moim projekcie zmierzyłem się z tym problemem. Moje rozwiązanie zastępowania metody „ustawiania” Modelu:

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
źródło
0

Chociaż w niektórych przypadkach używanie modeli Backbone zamiast zagnieżdżonych atrybutów Object ma sens, jak wspomniał Domenic, w prostszych przypadkach można utworzyć funkcję ustawiającą w modelu:

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
źródło
0

Jeśli wchodzisz w interakcję z backendem, który wymaga obiektu ze strukturą zagnieżdżenia. Ale z kręgosłupem łatwiej jest pracować ze strukturą liniową.

backbone.linear może Ci pomóc.

murzyn
źródło