Dynamiczne ustawianie id i className w widokach Backbone.js.

85

Jestem w trakcie uczenia się i korzystania z Backbone.js.

Mam model przedmiotu i odpowiadający mu widok przedmiotu. Każda instancja modelu ma atrybuty item_class i item_id, w których chcę, aby były odzwierciedlone jako atrybuty „id” i „class” odpowiedniego widoku. Jaki jest właściwy sposób, aby to osiągnąć?

Przykład:

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({       
});

Jak zaimplementować widok, aby elementy widoków przełożyły się na:

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

W większości przypadków, które widziałem, el widoku służy jako bezsensowny element opakowania, w którym trzeba ręcznie wpisać kod „semantyczny”.

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

Więc kiedy renderujemy, dostajemy

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

Ale wydaje się to marnotrawstwem - dlaczego zewnętrzny div? Chcę, aby el przetłumaczył bezpośrednio na wewnętrzny element DIV!

Nadav
źródło

Odpowiedzi:

133

Podsumowanie: dynamiczne ustawianie atrybutów widoku z danymi modelu

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"
  } )
} );
  • Ten przykład zakłada, że ​​pozwalasz Backbone wygenerować dla Ciebie element DOM.

  • attributesMetoda jest wywoływana po właściwości przekazany do konstruktora widok są ustawione (w tym przypadku model), co pozwala na dynamiczne ustawianie atrybutów z danymi modelu przed tworzy Backbone el.

  • W przeciwieństwie do niektórych innych odpowiedzi: nie koduje na stałe wartości atrybutów w klasie widoku, ustawia je dynamicznie na podstawie danych modelu; nie czeka aż render()do ustawienia wartości atr; nie ustawia wielokrotnie wartości atr w każdym wywołaniu do render(); nie ustawia niepotrzebnie ręcznie wartości atrybutów w elemencie DOM.

  • Zauważ, że jeśli ustawienie klasę podczas wywoływania Backbone.View.extendlub konstruktor widok (na przykład new Backbone.View), trzeba użyć nazwy własność dom, classNameale jeśli ustawienie go za pomocą attributesskrótu / metody (jak w tym przykładzie) trzeba użyć nazwy atrybutu class.

  • Od wersji Backbone 0.9.9:

    Podczas deklarowania View ... el, tagName, idi classNamemoże teraz być zdefiniowane jako funkcje, jeśli chcesz ich wartość należy określić w czasie wykonywania.

    Wspominam o tym na wypadek sytuacji, w której byłoby to przydatne jako alternatywa dla attributesmetody przedstawionej na ilustracji.

Korzystanie z istniejącego elementu

Jeśli używasz istniejącego elementu (np. Przekazując eldo konstruktora widoku) ...

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

... wtedy attributesnie zostanie zastosowany do elementu. Jeśli żądane atrybuty nie są jeszcze ustawione w elemencie lub nie chcesz powielać tych danych w klasie widoku i innej lokalizacji, możesz dodać initializemetodę do konstruktora widoku, która ma zastosowanie attributesdo el. Coś takiego (używając jQuery.attr):

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

Użycie el, renderowanie, unikanie opakowania

W większości przykładów, które widziałem, el widoku służy jako bezsensowny element opakowania, w którym trzeba ręcznie wpisać kod „semantyczny”.

Nie ma powodu, view.elaby być „bezsensownym elementem opakowania”. W rzeczywistości często powodowałoby to uszkodzenie struktury DOM. Jeśli <li>na przykład klasa widoku reprezentuje element, musi być renderowana jako <li>- renderowanie jej jako elementu <div>lub dowolnego innego elementu spowodowałoby uszkodzenie modelu zawartości. Będziesz prawdopodobnie chcesz skupić się na prawidłowej konfiguracji elementu widoku danych (za pomocą właściwości jak tagName, classNamei id), a następnie renderowania jej zawartości później.

Opcje dotyczące interakcji obiektów widoku Backbone z DOM są szeroko otwarte. Istnieją 2 podstawowe scenariusze początkowe:

  • Możesz dołączyć istniejący element DOM do widoku Backbone.

  • Możesz zezwolić Backbone na utworzenie nowego elementu, który jest odłączony od dokumentu, a następnie w jakiś sposób wstawić go do dokumentu.

Istnieje wiele sposobów generowania zawartości elementu (ustaw ciąg literału, jak w przykładzie; użyj biblioteki szablonów, takiej jak Mustache, Handlebars itp.). Sposób korzystania z elwłaściwości widoku zależy od tego, co robisz.

Istniejący element

Twój przykład renderowania sugeruje, że masz istniejący element, który przypisujesz do widoku, chociaż nie pokazujesz instancji widoków. Jeśli tak jest, a element jest już w dokumencie, możesz chcieć zrobić coś takiego (zaktualizuj zawartość el, ale nie zmieniaj elsamego siebie):

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

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

Wygenerowany element

Powiedzmy, że nie masz istniejącego elementu i pozwalasz, aby Backbone wygenerował jeden za Ciebie. Ty może chcesz zrobić coś takiego (ale to może lepiej do rzeczy architektonicznych tak, że widok nie jest odpowiedzialna za wiedząc o samej zewnątrz cokolwiek):

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

http://jsfiddle.net/vQMa2/

Szablony

W moim przypadku korzystam z szablonów np:

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

Szablon przedstawia cały widok. Innymi słowy, szablon nie będzie otaczał - div.playerbędzie to główny lub najbardziej zewnętrzny element mojego widoku.

Moja klasa gracza będzie wyglądać mniej więcej tak (z bardzo uproszczonym przykładem 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
źródło
Świetny sposób na nadpisanie właściwości atrybutów funkcją i ponowne zwrócenie obiektu!
Kel
2
@Kel tak, to dobry sposób na osiągnięcie czegoś dynamicznego, takiego jak to, o co chodzi w pytaniu, zapełnianie atrybutów danymi modelu, bez konieczności używania powtarzalnego kodu, w którym tworzone są widoki. Prawdopodobnie wiesz o tym, ale na wypadek, gdyby nie było to oczywiste, jest to funkcja Backbone, dzięki której możesz użyć funkcji, która zwraca hash jako wartość attributes, na przykład szereg innych właściwości Backbone, które mogą być dostarczone jako funkcja lub inna rodzaj wartości. W takich przypadkach Backbone sprawdza, czy wartość jest funkcją, wywołuje ją i używa wartości zwracanej.
JMM,
95

Twoim zdaniem po prostu zrób coś takiego

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
źródło
To jest prawidłowa odpowiedź na to pytanie i powinna zostać zaakceptowana
reach4thelasers
13
Ta odpowiedź nie przedstawia dynamicznego ustawiania atrybutów widoku na podstawie danych modelu, po prostu pokazuje alternatywną metodę zakodowania wartości atrybutów.
JMM
3
@JMM - jego przykładowy kod również nie korzysta z danych modelu. Ta odpowiedź działa w oparciu o jego przykładowy kod. Oczywiście dane modelu można zastąpić wartościami.
Clint
5
@Clint, nie liczyłbym na to, że będzie to oczywiste dla OP. „Jego przykładowy kod również nie korzysta z danych modelu”. - to dlatego, że nie wie jak i dlatego poprosił o pomoc. Wydaje mi się jasne, że pyta, jak ustawić atrybuty view.el za pomocą danych modelu i nie ma pojęcia, jak się do tego zabrać. Odpowiedź nie pokazuje nawet, jak to zrobić, i dlaczego miałbyś czekać do renderowania, aby to zrobić, lub robić to ponownie za każdym razem, gdy renderujesz? „Ta odpowiedź działa…” - jak to działa? Każdy utworzony w ten sposób widok miałby te same atrybuty. Pokazuje tylko, jak unikać opakowania.
JMM
OP zniknął od lutego '12. :( Oto kolejne +1 dla tej odpowiedzi.
Almo
27

Możesz ustawić właściwości classNamei idelement główny: 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");
   }       
});

EDYCJA Dołączony przykład ustawienia id na podstawie parametrów konstruktora

Jeśli widoki są zbudowane jak wspomniano:

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

Wtedy wartości można ustawić w ten sposób:

// ...
className: function(){
    return this.options.item_class;
},
id: function(){
    return this.options.item_id;
}
// ...
Jørgen
źródło
3
Nie czuję, że ta odpowiedź jest poprawna, ponieważ wtedy każdy ItemViewbędzie miał id: 'id1'. Należy to obliczyć w czasie realizacji w oparciu omodel.id .
fguillen
Możesz oczywiście ustawić identyfikator w dowolny sposób. Użyj funkcji, zmiennej lub czegokolwiek. Mój kod zawiera tylko przykład, wskazujący, jak ustawić wartość elementu głównego.
Jørgen
Dodałem przykład wyjaśniający, jak ustawić wartości dynamicznie na podstawie parametrów konstruktora.
Jørgen,
To jest poprawna odpowiedź. Używa poprawnie funkcji Backbone, aby rozwiązać problem.
Marc-Antoine Lemieux
6

Wiem, że to stare pytanie, ale dodane w celach informacyjnych. Wydaje się, że jest to łatwiejsze w nowych wersjach szkieletowych. W Backbone 1.1 właściwości id i className są oceniane w funkcji ensureElement(patrz źródło ) przy użyciu podkreślenia, _.resultco oznacza, że ​​jeśli funkcja jest classNamelub idjest, zostanie wywołana, w przeciwnym razie zostanie użyta jej wartość.

Możesz więc podać className bezpośrednio w konstruktorze, podać inny parametr, który byłby użyty w className, itp ... Wiele opcji

więc to powinno działać

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
źródło
Twój przykład jest nieprawidłowy, chciałbyśid: function() { return this.model.get('item_id'); })
Cobby
4

Inne przykłady nie pokazują, jak faktycznie pobrać dane z modelu. Aby dynamicznie dodać identyfikator i klasę z danych modelu:

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
źródło
Czy to jest „this.className” czy „this.class”?
Gabe Rainbow
2

Musisz usunąć tagName i zadeklarować el.

„tagName” oznacza, że ​​chcesz, aby szkielet utworzył element. Jeśli element już istnieje w DOM, możesz określić el, na przykład:

el: $('#emotions'),

i później:

render: function() { 
     $(this.el).append(this.model.toJSON());
}
jskulski
źródło
2

Spróbuj przypisać wartości w metodzie initialize, co spowoduje bezpośrednie dynamiczne przypisanie id i klasy do atrybutu div.

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
źródło
@Michel Pleasae przejrzyj tę dokumentację, backbonejs.org/#View-constructor
Hemanth
0

Oto minimalny sposób dynamicznej zmiany klasy elementu widoku za pośrednictwem modelu i aktualizowania go przy zmianach modelu.

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