Backbone.js obtém e define o atributo do objeto aninhado


105

Tenho uma pergunta simples sobre as funções get e set do Backbone.js .

1) Com o código abaixo, como posso 'obter' ou 'definir' obj1.myAttribute1 diretamente?

Outra pergunta:

2) No Model, além do objeto defaults , onde posso / devo declarar os outros atributos do meu modelo, de forma que eles possam ser acessados ​​através dos métodos get e set do 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?
    }
});

Eu sei que posso fazer:

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

mas isso é uma boa prática?


3
Embora não seja uma resposta à pergunta: Sempre que especificar um objeto (qualquer coisa passada por referência) em defaults(obj1 neste caso), esse mesmo objeto será compartilhado por todas as instâncias do modelo. A prática atual é definir defaultscomo uma função que retorna um objeto para ser usado como padrão. backbonejs.org/#Model-defaults (veja a nota em itálico)
Jonathan F

1
@JonathanF Os comentários não são para respostas, então você nunca precisou da declaração :)
TJ

Respostas:


144

Embora this.model.get("obj1").myAttribute1esteja bem, é um pouco problemático porque então você pode ser tentado a fazer o mesmo tipo de coisa para o conjunto, ou seja,

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

Mas se você fizer isso, não obterá os benefícios dos modelos de Backbone myAttribute1, como eventos de mudança ou validação.

Uma solução melhor seria nunca aninhar POJSOs ("objetos JavaScript simples e antigos") em seus modelos e, em vez disso, aninhar classes de modelos personalizados. Portanto, seria algo assim:

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

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

Então, o código de acesso seria

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

mas o mais importante, o código de configuração seria

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

que irá disparar eventos de mudança apropriados e similares. Exemplo de trabalho aqui: http://jsfiddle.net/g3U7j/


24
A essa resposta, eu acrescentaria o aviso de que essa solução oscila em relação a violações generalizadas da Lei de Demeter. Eu consideraria adicionar métodos de conveniência que ocultam a navegação para o objeto aninhado. Basicamente, seus chamadores não precisam conhecer a estrutura interna do modelo; afinal, ele pode mudar e os chamadores não devem ter percebido.
Bill Eisenhauer

7
Não pode fazer isso funcionar para mim. Gera erro:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage

1
@ChristianNunciato, pagewil, Benno: Você parece ter perdido o ponto da postagem, que é aninhar modelos de Backbone dentro de modelos de Backbone. Não aninhe objetos simples dentro de modelos de Backbone. Exemplo de trabalho aqui: jsfiddle.net/g3U7j
Domenic

1
Eu não inspecionei o código backbone.js, mas a partir do meu teste, se você tiver um modelo personalizado aninhado e alterar uma propriedade dele com set (), seu modelo pai não disparará um evento 'alterar' em si; Eu tive que disparar o evento sozinho. Eu realmente deveria apenas inspecionar o código, mas esse é o seu entendimento também?
tomo

2
@tom isso está correto. O backbone não é um caso especial para quando as propriedades dos modelos são instâncias de Backbone.Modele, em seguida, começam a fazer bolhas de eventos mágicos.
Domenic

74

Criei um modelo profundo de backbone para isso - basta estender Backbone.DeepModel em vez de Backbone.Model e você pode usar caminhos para obter / definir atributos de modelo aninhados. Ele também mantém eventos de mudança.

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

1
Sim, parece, se você olhar a API, há um exemplo como//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed

Funciona bem! Mas a linha 2 em seu exemplo requer dois pontos em vez de uma vírgula?
mariachi de

16

A solução de Domenic funcionará, no entanto, cada novo MyModel apontará para a mesma instância de Obj. Para evitar isso, MyModel deve ser semelhante a:

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

Veja a resposta de c3rin @ https://stackoverflow.com/a/6364480/1072653 para uma explicação completa.


1
Para futuros leitores, minha resposta foi atualizada para incorporar o melhor da resposta de Rusty.
Domenic de

2
O solicitante deve sinalizar como a resposta aceita. Domenic's é um grande começo, mas isso resolve um problema com ele.
Jon Raasch

5

Eu uso essa abordagem.

Se você tiver um modelo de Backbone como este:

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

Você pode definir o atributo "ab" com:

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

Agora seu modelo terá atributos como:

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

com o evento "alterar" disparado.


1
Você tem certeza disso? isso não funciona para mim. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Isso não aciona nenhum evento de alteração para mim :(
HungryCoder

1
Vejo que funciona quando clono o atributo like _.clone(m.get('x')). obrigado
HungryCoder

Obrigado @HungryCoder funcionou para mim também quando clonado. O backbone deve comparar o objeto que você é settingcom o objeto que você é gettingno tempo definido. Portanto, se você não clonar os dois objetos, os dois objetos sendo comparados serão exatamente os mesmos no tempo definido.
Derek Dahmer

Lembre-se de que os objetos são passados ​​por referência e são mutáveis, ao contrário dos primitivos de string e número. Os métodos set e constructor do backbone tentam um clone superficial de qualquer referência de objeto passada como argumento. Quaisquer referências a outros objetos nas propriedades desse objeto não são clonadas. Ao defini-lo e recuperá-lo, a referência é a mesma, o que significa que você pode alterar o modelo sem acionar uma alteração.
niall.campbell

3

Existe uma solução em que ninguém pensou ainda que é muito útil. De fato, você não pode definir atributos aninhados diretamente, a menos que use uma biblioteca de terceiros que provavelmente não deseja. No entanto, o que você pode fazer é fazer um clone do dicionário original, definir a propriedade aninhada lá e definir todo o dicionário. Pedaco de bolo.

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

Tive o mesmo problema que @pagewil e @Benno tiveram com a solução de @Domenic. Minha resposta foi, em vez disso, escrever uma subclasse simples de Backbone.Model que corrige o 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}]
});

O que NestedModel faz por você é permitir que eles funcionem (que é o que acontece quando myModel é definido por meio de dados JSON):

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

Seria fácil gerar a lista de modelos automaticamente na inicialização, mas essa solução foi boa o suficiente para mim.


2

A solução proposta por Domenic tem alguns inconvenientes. Digamos que você queira ouvir o evento de 'mudança'. Nesse caso, o método 'inicializar' não será disparado e seu valor personalizado para o atributo será substituído pelo objeto json do servidor. No meu projeto, enfrentei esse problema. Minha solução para substituir o método 'definir' do modelo:

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

Embora em alguns casos o uso de modelos de backbone em vez de atributos de objeto aninhados faça sentido, como Domenic mencionou, em casos mais simples, você pode criar uma função setter no modelo:

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 você interagir com o back-end, o que requer objeto com estrutura de aninhamento. Mas com backbone mais fácil de trabalhar com estrutura linear.

backbone.linear pode ajudá-lo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.