„Jak” zapisać całą kolekcję w Backbone.js - Backbone.sync lub jQuery.ajax?


81

Zdaję sobie sprawę, że można to zrobić i przyjrzałem się kilku miejscom (w tym: Najlepsza praktyka zapisywania całej kolekcji? ). Ale nadal nie wiem, „dokładnie jak” jest napisane w kodzie? (post wyjaśnia to po angielsku. Byłoby wspaniale mieć wyjaśnienie specyficzne dla javascript :)

Powiedzmy, że mam kolekcję modeli - same modele mogą mieć kolekcje zagnieżdżone. Zastąpiłem metodę toJSON () kolekcji nadrzędnej i otrzymuję prawidłowy obiekt JSON. Chcę „zapisać” całą kolekcję (odpowiadający jej kod JSON), ale szkielet nie wydaje się mieć wbudowanej takiej funkcjonalności.

var MyCollection = Backbone.Collection.extend({
model:MyModel,

//something to save?
save: function() {
   //what to write here?
 }

});

Wiem gdzieś musisz powiedzieć:

Backbone.sync = function(method, model, options){
/*
 * What goes in here?? If at all anything needs to be done?
 * Where to declare this in the program? And how is it called?
 */
}

Gdy „przeglądanie” zakończy się wraz z przetwarzaniem, jest ono odpowiedzialne za polecenie kolekcji, aby „zapisała się” na serwerze (zdolnym do obsługi zbiorczego żądania aktualizacji / tworzenia).

Pytania, które się pojawiają:

  1. Jak / co napisać w kodzie, aby „połączyć to wszystko razem”?
  2. Jaka jest „właściwa” lokalizacja wywołań zwrotnych i jak określić wywołanie zwrotne „powodzenie / błąd”? Mam na myśli składnię? Nie jestem pewien składni rejestracji callbacków w sieci szkieletowej ...

Jeśli jest to rzeczywiście trudne zadanie, czy możemy wywołać jQuery.ajax w widoku i przekazać this.successMethodlub this.errorMethodjako wywołanie zwrotne sukcesu / błędu? Czy to zadziała?

Muszę się zsynchronizować ze sposobem myślenia kręgosłupa - wiem, że na pewno brakuje mi czegoś podczas synchronizacji całych kolekcji.


Czy Twój kod po stronie serwera może potraktować to jako pojedyncze żądanie? Innymi słowy, cała kolekcja najwyższego poziomu, wszystkie modele i kolekcje zagnieżdżone jako pojedynczy pakiet JSON? A może musisz zapisywać każdy model osobno? Edycja: Ach, czytaj bliżej, serwer JEST w stanie zbiorczo aktualizować / tworzyć
Edward M Smith

@Edward: Tak! Wyraził to wyraźnie, ponieważ zwykle jest to kwestia niepokojąca, ale nie w tym przypadku :)
PhD

Jaka jest więc struktura danych, które serwer spodziewa się otrzymać?
Edward M Smith

@Edward: Czy struktura danych ma znaczenie? Formatowanie nie jest możliwe w komentarzu, ale wygląda to tak: [{postId: 1, labels: [{id: 1, name: "a"}, {id: 2, name: "b"}]}] W zasadzie każdy " postId ”może mieć zestaw / tablicę etykiet, które same są obiektami. Takich postów może być wiele ... Nie sądzę, że format danych ma cokolwiek wspólnego z danym problemem, chyba że czegoś mi brakuje
PhD

Odpowiedzi:


64

Moją natychmiastową myślą nie jest przesłonięcie metody przy zapisywaniu metody w Backbone.Collection, ale zawinięcie kolekcji w inny Backbone.Model i zastąpienie metody toJSON. Wtedy Backbone.js potraktuje model jako pojedynczy zasób i nie musisz hakować sposobu, w jaki backone myśli za dużo.

Zauważ, że Backbone.Collection ma metodę toJSON, więc większość pracy jest wykonywana za Ciebie. Wystarczy, że przekierujesz metodę toJSON swojego opakowania Backbone.Model do kolekcji Backbone.collection.

var MyCollectionWrapper = Backbone.Model.extend({
url: "/bulkupload",

//something to save?
toJSON: function() {
    return this.model.toJSON(); // where model is the collection class YOU defined above
 }

});

3
Przejrzałem kodu źródłowego i wydaje się, że kolekcje nie mają sposobu oszczędzania, więc nadrzędne nie powinno być problemem (jeśli miała metoda oszczędzania świata byłoby łatwiejsze LOT :)
PhD

+1 za pomysł na opakowanie - czysty i słodki. W ogóle o tym nie pomyślałem. Pomyślałem, że może będę musiał bezpośrednio wywołać Backboard.sync i po prostu przekazać kolekcję zamiast argumentu „model”. Czy musiałby podać adres URL modelu, aby działał, chociaż… jakieś uwagi? Ponieważ metoda synchronizacji wewnętrznie po prostu wywołuje getURL(model)argument modelu i nie wykonuje też żadnego rodzaju porównań ... wydaje się być zamierzona z założenia
doktorat,

3
Zgadzam się - bardzo elegancko. Jednak mam problem z tym rozwiązaniem: mój model wrappera jest zawsze nowy, więc kiedy save () jest to zawsze POST. Już odzyskałem dane, więc kiedy zapiszę (), powinno to być PUT. Przypuszczam, że mogę na stałe zakodować isNew () = false lub ustawić fałszywy identyfikator, ale nie wydaje się to eleganckim rozwiązaniem. Masz jakieś sugestie?
Scott Switzer

1
Naprawdę czysta odpowiedź, byłoby miło zobaczyć, jak utworzysz wystąpienie CollectionWrapper.
Anthony

Tak, to zadziałało również dla mnie i +1 za dobry wysiłek, ale jeśli chcę wysłać dwie kolekcje na serwer, jak mam się do tego zabrać?
CodeNotFound

25

Bardzo prosta ...

Backbone.Collection.prototype.save = function (options) {
    Backbone.sync("create", this, options);
};

... zapewni Twoim kolekcjom metodę zapisu. Pamiętaj, że to zawsze spowoduje wysłanie wszystkich modeli kolekcji na serwer, niezależnie od tego, co się zmieniło. opcje są zwykłymi opcjami jQuery ajax.


4
Wydaje się słuszne. Być może dodanie return Backbone.sync..jest bardziej Backbonish.
yves amsellem

Myślę - dla tego typu - aktualizacja byłaby lepsza niż tworzenie ... Przy okazji. możesz nadpisać toJSON modelu lub kolekcji, dzięki czemu możesz regulować, co wysłać do serwera ... (zwykle potrzebny jest tylko atrybut id) W Backbone.Relational możesz również ustawić, co dodać do formatu json.
inf3rno

1
Backbone.sync oczekuje „tworzenia”, patrz: backbonejs.org/docs/backbone.html#section-141
hacklikecrack

8

Skończyło się na metodzie podobnej do 'save' i wywołaniu w niej $ .ajax. Dało mi to większą kontrolę nad tym bez potrzeby dodawania klasy opakowującej, jak sugerował @brandgonesurfing (chociaż bardzo mi się podoba ten pomysł :) Jak wspomniałem, ponieważ już miałem metodę collection.toJSON () nadpisała wszystko, co zrobiłem, to jej używanie w wywołaniu Ajax ...

Mam nadzieję, że pomoże to komuś, kto się na to natknie ...


3
Użytkownik będzie lepiej nazywając Backbone.ajax (to pełnomocników do jQuery i tak, ale to sprawia, że bardziej linkujących)
developerbmw

5

To naprawdę zależy od tego, jaka jest umowa między klientem a serwerem. Oto uproszczony przykład CoffeeScript, w którym PUT na /parent/:parent_id/childrenz {"children":[{child1},{child2}]}zamieni dzieci rodzica na to, co jest w PUT i zwróci {"children":[{child1},{child2}]}:

class ChildElementCollection extends Backbone.Collection
  model: Backbone.Model
  initialize: ->
    @bind 'add', (model) -> model.set('parent_id', @parent.id)

  url: -> "#{@parent.url()}/children" # let's say that @parent.url() == '/parent/1'
  save: ->
    response = Backbone.sync('update', @, url: @url(), contentType: 'application/json', data: JSON.stringify(children: @toJSON()))
    response.done (models) => @reset models.children
    return response

To jest dość prosty przykład, możesz zrobić o wiele więcej ... to naprawdę zależy od stanu twoich danych podczas wykonywania funkcji save (), w jakim stanie muszą być, aby zostały wysłane na serwer i co daje serwer plecy.

Jeśli twój serwer jest w porządku z PUT wynoszącym [{child1},{child2], to linia Backbone.sync może zmienić się na response = Backbone.sync('update', @toJSON(), url: @url(), contentType: 'application/json').


Atrybut opcji „url” nie jest tu konieczny =)
Sergey Kamardin

5

Odpowiedź zależy od tego, co chcesz zrobić z kolekcją po stronie serwera.

Jeśli musisz wysłać dodatkowe dane z postem, możesz potrzebować modelu opakowującego lub modelu relacyjnego .

W przypadku modelu opakowania zawsze musisz napisać własną metodę parsowania :

var Occupants = Backbone.Collection.extend({
    model: Person
});

var House = Backbone.Model.extend({
    url: function (){
        return "/house/"+this.id;
    },
    parse: function(response){
        response.occupants = new Occupants(response.occupants)
        return response;
    }
});

Wydaje mi się, że modele relacyjne są lepsze, ponieważ można je łatwiej konfigurować i regulować za pomocąopcji includeInJSON , które atrybuty należy umieścić w pliku json wysyłanym do usługi odpoczynku.

var House = Backbone.RelationalModel.extend({
    url: function (){
        return "/house/"+this.id;
    },
    relations: [
        {
            type: Backbone.HasMany,
            key: 'occupants',
            relatedModel: Person,
            includeInJSON: ["id"],
            reverseRelation: {
                key: 'livesIn'
            }
        }
    ]
});

Jeśli nie wyślesz dodatkowych danych , możesz zsynchronizować samą kolekcję . W takim przypadku musisz dodać metodę save do swojej kolekcji (lub prototypu kolekcji):

var Occupants = Backbone.Collection.extend({
    url: "/concrete-house/occupants",
    model: Person,
    save: function (options) {
        this.sync("update", this, options);
    }
});

3

Zaskoczyło mnie również, że kolekcje Backbone nie mają wbudowanego zapisu. Oto, co umieściłem w mojej kolekcji kręgosłupa, aby to zrobić. Zdecydowanie nie chcę iterować każdego modelu w kolekcji i niezależnie zapisywać. Ponadto używam Backbone na zapleczu przy użyciu Node, więc zastępuję natywny, Backbone.syncaby zapisać do pliku płaskiego w moim małym projekcie - ale kod powinien być prawie taki sam:

    save: function(){                                                                                                                                                                                                                                                                                                                                                     
      Backbone.sync('save', this, {                                                                                                                                                                                                                                                                                                                                     
        success: function(){                                                                                                                                                                                                                                                                                                                                          
          console.log('users saved!');                                                                                                                                                                                                                                                                                                                              
        }                                                                                                                                                                                                                                                                                                                                                             
      });                                                                                                                                                                                                                                                                                                                                                               
    }

Ma to również sens, aby po prostu przekazać opcje, na przykładsave: function (options) { Backbone.sync('save', this, options); }
Matt Fletcher,

3

Stary wątek, który znam, skończyło się na tym:

Backbone.Collection.prototype.save = function (options) {
            // create a tmp collection, with the changed models, and the url
            var tmpCollection = new Backbone.Collection( this.changed() );
            tmpCollection.url = this.url;
            // sync
            Backbone.sync("create", tmpCollection, options);
        };
        Backbone.Collection.prototype.changed = function (options) {
            // return only the changed models.
            return this.models.filter( function(m){
                return m.hasChanged()
            });
        };
// and sync the diffs.
self.userCollection.save();

Dość naprzód :)


2

Oto prosty przykład:

var Books = Backbone.Collection.extend({
model: Book,
url: function() {
  return '/books/';
},
save: function(){
  Backbone.sync('create', this, {
    success: function() {
      console.log('Saved!');
    }
  });
 }
});

Gdy wywołasz metodę save () w swojej kolekcji, wyśle ​​ona żądanie metody PUT do zdefiniowanego adresu URL.


Zastanawiałem się, czy możesz pomóc rozwiązać mój problem z zapisywaniem kolekcji, w tej chwili jsfiddle.net/kyllle/f1h4cz7f/3 toJSON () aktualizuje każdy model, ale potem zapis nie wydaje się mieć żadnych danych?
styler

1

Spróbowałbym czegoś takiego:

var CollectionSync = function(method, model, [options]) {
    // do similar things to Backbone.sync
}

var MyCollection = Backbone.Collection.extend({
    sync: CollectionSync,
    model: MyModel,
    getChanged: function() {
        // return a list of models that have changed by checking hasChanged()
    },
    save: function(attributes, options) {
        // do similar things as Model.save
    }
});

( https://stackoverflow.com/a/11085198/137067 )


1

Zaakceptowana odpowiedź jest całkiem dobra, ale mogę pójść o krok dalej i podać kod, który zapewni odpalanie odpowiednich zdarzeń dla twoich słuchaczy, jednocześnie umożliwiając przekazanie opcji wywołań zwrotnych zdarzeń ajax:

save: function( options ) {
  var self = this;

  var success = options.success;
  var error = options.error;
  var complete = options.complete;

  options.success = function( response, status, xhr ) {
    self.trigger('sync', self, response, options);
    if (success) return success.apply(this, arguments);
  };

  options.error = function( response, status, xhr ) {
    self.trigger('error', self, response, options);
    if (error) return error.apply(this, arguments);
  };

  options.complete = function( response, status, xhr ) {
    if (complete) return complete.apply(this, arguments);
  }

  Backbone.sync('create', this, options);
}

0

Dla każdego, kto nadal korzysta z backbone.js w 2017 roku, zaakceptowana odpowiedź nie działa.

Spróbuj usunąć przesłonięcie toJSON () w modelu opakowania i wywołać toJSON w kolekcji podczas tworzenia wystąpienia opakowania modelu.

new ModelWrapper(Collection.toJSON());
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.