2011-08-03 15 views
62

Mam API JEST REST, który zwraca listę "dzienników". Istnieje wiele rodzajów dzienników, które implementują różne, ale podobne zachowania. Wdrożenie po stronie serwera to na warstwie danych jest rodzajem pojedynczej tabeli dziedziczenia, więc każda reprezentacja JSON dzienniku zawiera jego „type”:Kolekcja Backbone.js z wieloma podklasami modelu

[ 
    {"type": "ULM", "name": "My uml logbook", ... , specific_uml_logbook_attr: ...}, 
    {"type": "Plane", "name": "My plane logbook", ... , specific_plane_logbook_attr: ...} 
] 

chciałbym powielać ten model serwera po stronie klienta , więc mam baza Logbook klasę i wiele klas dziennik Podtyp:

class Logbook extends Backbone.Model 

class UmlLogbook extends Logbook 

class PlaneLogbook extends Logbook 

... 

My Backbone.Collection jest zbiorem Logbook modeli, które mogę używać do kwerendy API JSON:

class LogbookCollection extends Backbone.Collection 
    model: Logbook 
    url: "/api/logbooks" 

Kiedy pobiera się kolekcję dzienników, czy istnieje sposób na przesłanie każdego Logbook do odpowiadającej mu klasy podrzędnej (na podstawie atrybutu "typ" JSON)?

Odpowiedz

80

Rzeczywiście.

Po wywołaniu polecenia "pobierz" w kolekcji przekazuje odpowiedź za pośrednictwem metody Backbone.Collection.parse przed dodaniem jej do kolekcji.

Domyślna implementacja „parse” właśnie przechodzi odpowiedź poprzez, jak to jest, ale można zastąpić go do powrotu listę modeli które mają być dodane do kolekcji:

class Logbooks extends Backbone.Collection 

    model: Logbook 

    url: 'api/logbooks' 

    parse: (resp, xhr) -> 
    _(resp).map (attrs) -> 
     switch attrs.type 
     when 'UML' then new UmlLogbook attrs 
     when 'Plane' then new PLaneLogbook attrs 

EDIT: Zaraz, idbentley dotarłem tam przede mną. jedyną różnicą jest to, że użył "każdego" i użyłem "mapy". Oba będą działać, ale inaczej.

Użycie "każdego" powoduje skuteczne zerwanie łańcucha, od którego zaczęło się wywołanie "pobieranie" (przez powrót "niezdefiniowane" - kolejne wywołanie "reset" (lub "dodaj") w związku z tym nic nie da) i wykonuje wszystkie operacje przetwarzania tam w funkcji parsowania.

Użycie "mapy" przekształca listę atrybutów w listę modeli i przekazuje ją do łańcucha już w ruchu.

Różne pociągnięcia.

Edycja ponownie: po prostu sobie sprawę, istnieje też inny sposób, aby to zrobić:

„model” atrybutu w kolekcji jest nie tylko więc zbiór wie, jak sprawić, by nowy model, czy to przekazywane atrybutów w „dodaj” , "utwórz" lub "zresetuj". Więc można zrobić coś takiego:

class Logbooks extends Backbone.Collection 

    model: (attrs, options) -> 
    switch attrs.type 
     when 'UML' then new UmlLogbook attrs, options 
     when 'Plane' then new PLaneLogbook attrs, options 
     # should probably add an 'else' here so there's a default if, 
     # say, no attrs are provided to a Logbooks.create call 

    url: 'api/logbooks' 

Zaletą jest to, że kolekcja będzie teraz wiedzą, jak „rzucać” właściwej podklasy Logbook dla operacji innych niż „sprowadzić”.

+0

Dziękuję za odpowiedź! Wiedziałem o istnieniu metody parsowania, ale nie wiedziałem, że wynik został przekazany bezpośrednio do metody resetowania ... Powinienem lepiej wykopać kod źródłowy! Thx again – Tricote

+1

Twoja edycja za pomocą metody 'model' jest znacznie lepsza niż przy użyciu' parsowania', ponieważ, jak słusznie zauważyłeś, działa na 'reset'. W ten sposób, czy nowe modele przychodzą poprzez 'pobieranie 'lub bootstrapowane w HTML, to nadal działa. Dzięki! – philoye

+2

Proponuję przesunąć w górę tę ostatnią część, aby ludzie mogli ją zobaczyć jeszcze więcej :) tylko myśl – corroded

11

Tak. Można zastąpić funkcję parse na zbiorach (Będę wykorzystanie javascript zamiast coffeescript, bo to, co wiem, ale mapowanie powinno być łatwe):

LogbookCollection = Backbone.Collection.extend({ 
    model: Logbook, 
    url: "/api/logbooks", 
    parse: function(response){ 
     var self = this; 
     _.each(response, function(logbook){ 
      switch(logbook.type){ 
      case "ULM": 
       self.add(new UmlLogBook(logbook); 
       break; 
      case "Plane": 
       ... 
      } 
     } 
    } 
}); 

Nadzieja to pomaga.

0

Może to źle użyć eval, ale jest to sposób znacznie bardziej rubinowy stylu (coffeescript):

parse: (resp)-> 
    _(resp).map (attrs) -> 
     eval("new App.Models.#{attrs.type}(attrs)") 

Więc nie trzeba pisać dużo przełącznik/przypadkach atrybut prostu ustawić typ w twoim JSON. Działa bardzo dobrze z szynami + cyterem lub innym rozwiązaniem dziedziczenia multitable. Możesz dodać nowych potomków bez dodawania ich do swoich spraw.

Możesz używać takich konstrukcji w innych miejscach, gdzie potrzebujesz dużo przełączników/obudów w zależności od klasy Twojego modelu.

+4

Tak naprawdę nie trzeba używać eval tutaj, nowe App.Models [attrs.type] (attrs) będzie działało dobrze. – Dmitry

+0

@dgutov, dzięki, piękne! –

3

jako głównego łańcucha 0.9.1, zacząłem się metodą opisaną w pull-żądanie esa-Matti suuronen za:

https://github.com/documentcloud/backbone/pull/1148

po zastosowaniu patcha, Twoja kolekcja będzie coś takiego:

LogbookCollection = Backbone.Collection.extend({ 

    model: Logbook, 

    createModel: function (attrs, options) { 
     if (attrs.type === "UML") { // i'am assuming ULM was a typo 
      return new UmlLogbook(attrs, options); 
     } else if (attrs.type === "Plane") { 
      return new Plane(attrs, options); 
     } else { 
      return new Logbook(attrs, options); 
      // or throw an error on an unrecognized type 
      // throw new Error("Bad type: " + attrs.type); 
     } 
    } 

}); 

wierzę, że to pasuje skoro używasz STI (wszystkie modele posiadają unikalne identyfikatory)

Powiązane problemy