Definir id e className dinamicamente nas visualizações Backbone.js

85

Estou em processo de aprendizagem e uso do Backbone.js.

Eu tenho um modelo de item e uma visão de item correspondente. Cada instância de modelo tem atributos item_class e item_id, que desejo que sejam refletidos como os atributos 'id' e 'class' da visualização correspondente. Qual é a maneira correta de conseguir isso?

Exemplo:

var ItemModel = Backbone.Model.extend({      
});

var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});

var ItemView = Backbone.View.extend({       
});

Como devo implementar a visualização de modo que os el's de visualizações sejam traduzidos para:

<div id="id1" class="nice"></div>
<div id="id2" class="sad"> </div>

Na maioria dos exemplos que vi, o el do modo de exibição serve como um elemento de invólucro sem sentido dentro do qual é necessário escrever manualmente o código "semântico".

var ItemView = Backbone.View.extend({
   tagName:  "div",   // I know it's the default...

   render: function() {
     $(this.el).html("<div id="id1" class="nice"> Some stuff </div>");
   }       
});

Então, quando renderizado, obtém-se

<div> <!-- el wrapper -->
    <div id="id1" class="nice"> Some stuff </div>
</div>

Mas isso parece um desperdício - por que o div externo? Eu quero que o el se traduza diretamente no div interno!

Nadav
fonte

Respostas:

133

Resumo: definir atributos de visualização dinamicamente com dados do modelo

http://jsfiddle.net/5wd0ma8b/

// View class with `attributes` method
var View = Backbone.View.extend( {
  attributes : function () {
    // Return model data
    return {
      class : this.model.get( 'item_class' ),
      id : this.model.get( 'item_id' )
    };
  }
  // attributes
} );

// Pass model to view constructor
var item = new View( {
  model : new Backbone.Model( {
    item_class : "nice",
    item_id : "id1"
  } )
} );
  • Este exemplo assume que você está permitindo que o Backbone gere um elemento DOM para você.

  • O attributesmétodo é chamado depois que as propriedades passadas para o construtor de visão são definidas (neste caso, model), permitindo que você defina dinamicamente os atributos com os dados do modelo antes da criação do Backbone el.

  • Em contraste com algumas das outras respostas: não codifica valores de atributo na classe de visualização, os define dinamicamente a partir dos dados do modelo; não espera até render()definir os atributos; não define atributos repetidamente em cada chamada para render(); não define manualmente os atributos desnecessários no elemento DOM.

  • Observe que se definir a classe ao chamar Backbone.View.extendou um construtor de visualização (por exemplo new Backbone.View), você deve usar o nome da propriedade DOM className, mas se defini-lo por meio do attributesmétodo hash / (como neste exemplo), você deve usar o nome do atributo class,.

  • A partir do Backbone 0.9.9:

    Ao declarar a View ... el, tagName, ide classNameagora podem ser definidas como funções, se você quiser seus valores a ser determinado em tempo de execução.

    Menciono isso no caso de haver uma situação em que isso seja útil como alternativa ao uso de um attributesmétodo conforme ilustrado.

Usando um elemento existente

Se você estiver usando um elemento existente (por exemplo, passando elpara o construtor de visualização) ...

var item = new View( { el : some_el } );

... então attributesnão será aplicado ao elemento. Se os atributos desejados ainda não estiverem configurados no elemento, ou você não quiser duplicar os dados em sua classe de visão e em outro local, você pode querer adicionar um initializemétodo ao seu construtor de visão que se aplique attributesa el. Algo assim (usando jQuery.attr):

View.prototype.initialize = function ( options ) {
  this.$el.attr( _.result( this, 'attributes' ) );
};

Uso de el , renderização, evitando o invólucro

Na maioria dos exemplos que vi, o el do modo de exibição serve como um elemento de invólucro sem sentido dentro do qual é necessário escrever manualmente o código "semântico".

Não há razão view.elpara ser "um elemento de invólucro sem sentido". Na verdade, isso freqüentemente quebrava a estrutura do DOM. Se uma classe de visão representa um <li>elemento, por exemplo, ela precisa ser renderizada como um<li> - renderizá-la como um <div>ou qualquer outro elemento quebraria o modelo de conteúdo. É provável que você deseja focar corretamente configurar elemento do seu ponto de vista (usando propriedades como tagName, className, e id) e depois de proferir a sua conteúdo posteriormente.

As opções de como fazer com que seus objetos de visualização do Backbone interajam com o DOM estão totalmente abertas. Existem 2 cenários iniciais básicos:

  • Você pode anexar um elemento DOM existente a uma visualização do Backbone.

  • Você pode permitir que o Backbone crie um novo elemento desconectado do documento e, em seguida, insira-o de alguma forma no documento.

Existem várias maneiras de gerar o conteúdo para o elemento (definir uma string literal, como no seu exemplo; usar uma biblioteca de modelos como Mustache, Handlebars, etc.). Como você deve usar oel propriedade da visualização depende do que você está fazendo.

Elemento existente

Seu exemplo de renderização sugere que você tem um elemento existente que está atribuindo à visualização, embora você não mostre a instanciação das visualizações. Se for esse o caso, e o elemento já estiver no documento, você pode querer fazer algo assim (atualizar o conteúdo el, mas não se alterar el):

render : function () {
  this.$el.html( "Some stuff" );
}

http://jsfiddle.net/vQMa2/1/

Elemento gerado

Digamos que você não tenha um elemento existente e permita que o Backbone gere um para você. Você pode querer fazer algo assim (mas provavelmente é melhor arquitetar as coisas para que sua visão não seja responsável por saber nada sobre si mesma):

render : function () {
  this.$el.html( "Some stuff" );
  $( "#some-container" ).append( this.el );
}

http://jsfiddle.net/vQMa2/

Modelos

No meu caso, estou usando modelos, por exemplo:

<div class="player" id="{{id}}">
<input name="name" value="{{name}}" />
<input name="score" value="{{score}}" />
</div>
<!-- .player -->

O modelo representa a visão completa. Em outras palavras, não haverá wrapper em torno do modelo -div.player será a raiz ou o elemento mais externo da minha visão.

Minha classe de jogador será parecida com isto (com um exemplo muito simplificado de render()):

Backbone.View.extend( {
  tagName : 'div',
  className : 'player',

  attributes : function () {
    return {
      id : "player-" + this.model.cid
    };
  },
  // attributes

  render : function {
    var rendered_template = $( ... );

    // Note that since the top level element in my template (and therefore
    // in `rendered_template`) represents the same element as `this.el`, I'm
    // extracting the content of `rendered_template`'s top level element and
    // replacing the content of `this.el` with that.
    this.$el.empty().append( rendered_template.children() );
  }      
} );
JMM
fonte
Maneira incrível de sobrescrever a propriedade de atributos com uma função e retornar um objeto novamente!
Kel de
2
@Kel sim, é uma boa maneira de realizar algo dinâmico como o que a pergunta pede, populando os atributos com dados de modelo, sem ter que usar código repetitivo onde as visualizações são instanciadas. Você provavelmente sabe disso, mas caso não seja óbvio, é um recurso do Backbone que você pode usar uma função que retorna um hash como o valor de attributes, como várias outras propriedades do Backbone que podem ser fornecidas como uma função ou alguma outra tipo de valor. Nesses casos, o Backbone verifica se o valor é uma função, a chama e usa o valor de retorno.
JMM
95

Na sua opinião, faça algo assim

var ItemView = Backbone.View.extend({
   tagName:  "div",   // I know it's the default...

   render: function() {
     $(this.el).attr('id', 'id1').addClass('nice').html('Some Stuff'); 
   }       
});
Dan Brooke
fonte
Esta é a resposta correta para esta questão e deve ser aceita
reach4thelasers
13
Esta resposta não demonstra a configuração dinâmica dos atributos de visualização com base nos dados do modelo, apenas mostra um método alternativo de codificação permanente dos valores de atributo.
JMM
3
@JMM - Seu código de amostra também não está usando os dados do modelo. Essa resposta funciona com base em seu código de amostra. Obviamente, os dados do modelo podem ser substituídos pelos valores.
Clint
5
@Clint, eu não contaria que isso fosse óbvio para o OP. "Seu código de amostra também não está usando os dados do modelo." - isso porque ele não sabe como e, portanto, pediu ajuda. Parece claro para mim que ele está perguntando como definir atributos de view.el usando dados de modelo e não tem ideia de como fazer isso. A resposta nem mostra como fazer isso, e por que você esperaria até a renderização para fazer de qualquer maneira ou faria de novo toda vez que renderizar? "Esta resposta funciona ..." - como funciona? Cada visualização criada assim teria os mesmos atributos. A única coisa que mostra é como evitar o invólucro.
JMM
O OP foi encerrado desde fevereiro de 2012. :( Aqui está outro +1 para esta resposta.
Almo
27

Você pode definir as propriedades classNamee idno elemento raiz: http://documentcloud.github.com/backbone/#View-extend

var ItemView = Backbone.View.extend({
   tagName:  "div",   // I know it's the default...
   className : 'nice',
   id : 'id1',
   render: function() {
     $(this.el).html("Some stuff");
   }       
});

EDIT Incluir exemplo de configuração de id com base nos parâmetros do construtor

Se as vistas forem construídas conforme mencionado:

var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});

Então, os valores podem ser definidos desta forma:

// ...
className: function(){
    return this.options.item_class;
},
id: function(){
    return this.options.item_id;
}
// ...
Jørgen
fonte
3
Não creio que esta resposta esteja correta porque então todos ItemViewterão id: 'id1'. Isso deve ser calculado em tempo de execução com base no model.id.
fguillen
Você pode definir o ID da maneira que quiser. Use uma função, variável ou qualquer coisa. Meu código inclui apenas um exemplo, apontando como definir o valor no elemento raiz.
Jørgen,
Adicionei um exemplo que esclarece como definir os valores dinamicamente com base nos parâmetros do construtor.
Jørgen
Essa é a resposta correta. Ele usa recursos de Backbone corretamente para resolver o problema.
Marc-Antoine Lemieux
6

Eu sei que é uma pergunta antiga, mas adicionada para referência. Isso parece ser mais fácil em novas versões de backbone. No Backbone 1.1 as propriedades id e className são avaliadas na função ensureElement(veja da fonte ) usando o _.resultsignificado de sublinhado se classNameou idfor uma função, ela será chamada, caso contrário, seu valor será usado.

Então você poderia dar className diretamente no construtor, dar outro parâmetro que seria usado no className, etc ... Muitas opções

então isso deve funcionar

var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});

var ItemView = Backbone.View.extend({       
  id: function() { return this.model.get('item_id'); },
  className: function() { return this.model.get('item_class'); }
});
Marcus
fonte
Seu exemplo não é válido, você gostariaid: function() { return this.model.get('item_id'); })
Cobby
4

Os outros exemplos não mostram como realmente obter os dados do modelo. Para adicionar dinamicamente o id e a classe dos dados do modelo:

var ItemView = Backbone.View.extend({
   tagName:  "div",

   render: function() {
     this.id = this.model.get('item_id');
     this.class = this.model.get('item_class');
     $(this.el).attr('id',this.id).addClass(this.class).html('Some Stuff'); 
   }       
});
diskodave
fonte
É 'this.className' ou 'this.class'?
Gabe Rainbow
2

Você precisa remover tagName e declarar um el.

'tagName' significa que você deseja que o backbone crie um elemento. Se o elemento já existe no DOM, você pode especificar um el como:

el: $('#emotions'),

e depois:

render: function() { 
     $(this.el).append(this.model.toJSON());
}
Jskulski
fonte
2

Tente atribuir os valores no método de inicialização, isso atribuirá diretamente id e classe ao atributo div dinamicamente.

var ItemView = Backbone.View.extend( {
    tagName : "div",   
    id      : '',
    class   : '',

    initialize : function( options ) {
        if ( ! _.isUndefined( options ) ) {
            this.id = options.item_id;
            this.class= options.item_class;
        }
    },

    render : function() {
        $( this.el ).html( this.template( "stuff goes here" ) ); 
    }
} );
Hemanth
fonte
@Michel Pleasae analise esta documentação, backbonejs.org/#View-constructor
Hemanth
0

Aqui está uma maneira mínima de alterar a classe do elemento da visualização dinamicamente por meio de um modelo e atualizá-lo nas alterações do modelo.

var VMenuTabItem = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click': 'onClick'
    },
    initialize: function(options) {

        // auto render on change of the class. 
        // Useful if parent view changes this model (e.g. via a collection)
        this.listenTo(this.model, 'change:active', this.render);

    },
    render: function() {

        // toggle a class only if the attribute is set.
        this.$el.toggleClass('active', Boolean(this.model.get('active')));
        this.$el.toggleClass('empty', Boolean(this.model.get('empty')));

        return this;
    },
    onClicked: function(e) {
        if (!this.model.get('empty')) {

            // optional: notify our parents of the click
            this.model.trigger('tab:click', this.model);

            // then update the model, which triggers a render.
            this.model.set({ active: true });
        }
    }
});
Emile Bergeron
fonte