2013-08-10 13 views
11

Kilka miejsc w mojej aplikacji szkieletowej Chciałbym mieć natychmiastowe przeszukanie kolekcji, ale trudno mi wymyślić najlepszy sposób implementacji to.Backbone.js - najlepsza praktyka w implementacji "natychmiastowego" wyszukiwania

Oto szybka implementacja. http://jsfiddle.net/7YgeE/ Należy pamiętać, że moja kolekcja może zawierać ponad 200 modeli.

var CollectionView = Backbone.View.extend({ 

    template: $('#template').html(), 

    initialize: function() { 

    this.collection = new Backbone.Collection([ 
     { first: 'John', last: 'Doe' }, 
     { first: 'Mary', last: 'Jane' }, 
     { first: 'Billy', last: 'Bob' }, 
     { first: 'Dexter', last: 'Morgan' }, 
     { first: 'Walter', last: 'White' }, 
     { first: 'Billy', last: 'Bobby' } 
    ]); 
    this.collection.on('add', this.addOne, this); 

    this.render(); 
    }, 

    events: { 
    'keyup .search': 'search', 
    }, 

    // Returns array subset of models that match search. 
    search: function(e) { 

    var search = this.$('.search').val().toLowerCase(); 

    this.$('tbody').empty(); // is this creating ghost views? 

    _.each(this.collection.filter(function(model) { 
     return _.some(
     model.values(), 
     function(value) { 
      return ~value.toLowerCase().indexOf(search); 
     }); 
    }), $.proxy(this.addOne, this)); 
    }, 

    addOne: function(model) { 

    var view = new RowView({ model: model }); 
    this.$('tbody').append(view.render().el); 
    }, 

    render: function() { 

    $('#insert').replaceWith(this.$el.html(this.template)); 
     this.collection.each(this.addOne, this); 
    } 
}); 

i mały widok dla każdego modelu ...

var RowView = Backbone.View.extend({ 

    tagName: 'tr', 

    events: { 
    'click': 'click' 
    }, 

    click: function() { 
    // Set element to active 
    this.$el.addClass('selected').siblings().removeClass('selected'); 

    // Some detail view will listen for this. 
    App.trigger('model:view', this.model); 
    }, 

    render: function() { 

    this.$el.html('<td>' + this.model.get('first') + '</td><td>' + this.model.get('last') + '</td>'); 
     return this; 
    } 
}); 

new CollectionView; 

Pytanie 1

Na każdym keydown, filtrować kolekcję, opróżnić tbody i uczynienia wyniki, tworząc w ten sposób nowy widok dla każdego modelu. Właśnie stworzyłem widoki duchów, tak? Czy najlepiej zniszczyć każdy widok? A może powinienem spróbować zarządzać moim RowView s ... tworząc każdy z nich tylko jeden raz i przechodząc przez nie, aby renderować tylko wyniki? Może tablica w moim CollectionView? Po opróżnieniu tbody, czy RowViews nadal będzie miał ich el, czy jest teraz pusty i wymaga ponownego renderowania?

Pytanie 2, Wybór modelu

Zauważysz Jestem wyzwalania zdarzenia niestandardowego w moim RowView. Chciałbym mieć gdzieś widok szczegółów, aby obsłużyć to zdarzenie i wyświetlić cały mój model. Gdy przeszukuję moją listę, jeśli wybrany przeze mnie model pozostanie w wynikach wyszukiwania, chcę zachować ten stan i pozostawić go w widoku szczegółów. Gdy nie będzie już w moich wynikach, opróżnię widok szczegółów. Więc na pewno będę musiał zarządzać szeregiem widoków, prawda? Rozważałem podwójnie połączoną strukturę, w której każdy widok wskazuje na jej model, a każdy model na jego widok ... ale jeśli mam w przyszłości zaimplementować singletonową fabrykę w moich modelach, nie mogę tego narzucić Model. :/

Jaki jest najlepszy sposób zarządzania tymi widokami?

Odpowiedz

19

mam trochę ponieść podczas zabawy ze swoim pytaniem.

Najpierw utworzyłbym dedykowaną kolekcję do przechowywania filtrowanych modeli i "modelu stanu" do obsługi wyszukiwania. Na przykład,

var Filter = Backbone.Model.extend({ 
    defaults: { 
     what: '', // the textual search 
     where: 'all' // I added a scope to the search 
    }, 
    initialize: function(opts) { 
     // the source collection 
     this.collection = opts.collection; 
     // the filtered models 
     this.filtered = new Backbone.Collection(opts.collection.models); 
     //listening to changes on the filter 
     this.on('change:what change:where', this.filter); 
    }, 

    //recalculate the state of the filtered list 
    filter: function() { 
     var what = this.get('what').trim(), 
      where = this.get('where'), 
      lookin = (where==='all') ? ['first', 'last'] : where, 
      models; 

     if (what==='') { 
      models = this.collection.models;    
     } else { 
      models = this.collection.filter(function(model) { 
       return _.some(_.values(model.pick(lookin)), function(value) { 
        return ~value.toLowerCase().indexOf(what); 
       }); 
      }); 
     } 

     // let's reset the filtered collection with the appropriate models 
     this.filtered.reset(models); 
    } 
}); 

które byłyby tworzone wystąpienia jako

var people = new Backbone.Collection([ 
    {first: 'John', last: 'Doe'}, 
    {first: 'Mary', last: 'Jane'}, 
    {first: 'Billy', last: 'Bob'}, 
    {first: 'Dexter', last: 'Morgan'}, 
    {first: 'Walter', last: 'White'}, 
    {first: 'Billy', last: 'Bobby'} 
]); 
var flt = new Filter({collection: people}); 

Następnie chciałbym tworzyć odseparowane widoki na listy i pola wejściowe: łatwiejszy w utrzymaniu i poruszać

var BaseView = Backbone.View.extend({ 
    render:function() { 
     var html, $oldel = this.$el, $newel; 

     html = this.html(); 
     $newel=$(html); 

     this.setElement($newel); 
     $oldel.replaceWith($newel); 

     return this; 
    } 
}); 
var CollectionView = BaseView.extend({ 
    initialize: function(opts) { 
     // I like to pass the templates in the options 
     this.template = opts.template; 
     // listen to the filtered collection and rerender 
     this.listenTo(this.collection, 'reset', this.render); 
    }, 
    html: function() { 
     return this.template({ 
      models: this.collection.toJSON() 
     }); 
    } 
}); 
var FormView = Backbone.View.extend({ 
    events: { 
     // throttled to limit the updates 
     'keyup input[name="what"]': _.throttle(function(e) { 
      this.model.set('what', e.currentTarget.value); 
     }, 200), 

     'click input[name="where"]': function(e) { 
      this.model.set('where', e.currentTarget.value); 
     } 
    } 
}); 

BaseView pozwala zmienić DOM na miejscu, zobacz Backbone, not "this.el" wrapping w celu uzyskania szczegółów:

Przypadki wyglądałby

var inputView = new FormView({ 
    el: 'form', 
    model: flt 
}); 
var listView = new CollectionView({ 
    template: _.template($('#template-list').html()), 
    collection: flt.filtered 
}); 
$('#content').append(listView.render().el); 

a demo poszukiwań na tym etapie http://jsfiddle.net/XxRD7/2/

koniec chciałbym zmodyfikować CollectionView szczepić widoki wiersza w mojej funkcji renderowania, coś

var ItemView = BaseView.extend({ 
    events: { 
     'click': function() { 
      console.log(this.model.get('first')); 
     } 
    } 
}); 

var CollectionView = BaseView.extend({ 
    initialize: function(opts) { 
     this.template = opts.template; 
     this.listenTo(this.collection, 'reset', this.render); 
    }, 
    html: function() { 
     var models = this.collection.map(function (model) { 
      return _.extend(model.toJSON(), { 
       cid: model.cid 
      }); 
     }); 
     return this.template({models: models}); 
    }, 
    render: function() { 
     BaseView.prototype.render.call(this); 

     var coll = this.collection; 
     this.$('[data-cid]').each(function(ix, el) { 
      new ItemView({ 
       el: el, 
       model: coll.get($(el).data('cid')) 
      }); 
     }); 

     return this; 
    } 
}); 

Kolejne skrzypce http://jsfiddle.net/XxRD7/3/

+0

Dziękuję, to jest niezwykle pomocne. Naprawdę podoba mi się to, co zrobiłeś z filtrem. W moich wczesnych próbach miałem również zakres, ale był mocno zakodowany i jakoś pominął funkcję "pick" w dokumentach. Ponadto, nigdy nie wiedział o funkcji "przepustnicy", również bardzo pomocne. –

+0

Nadal pakuję głowę wokół sposobu, w jaki renderowane są rzeczy, i nie jestem całkiem sprzedany przy użyciu 'setElement'. Wydaje mi się niecelowe powtarzanie zdarzeń na każdym renderowaniu. Nigdy nie widziałem tej techniki szczepienia, w której renderujesz elementy listy w CollectionView i graft w ItemViews ... Nie jestem przyzwyczajony do tego, że ItemView nie jest odpowiedzialny za renderowanie się, a z jednej strony wydaje się separacją Obawy, które nie powinny pojawić się, ale z drugiej strony są zaskakująco proste, ponieważ zawsze łatwiej jest zmienić szablon na nasze kolekcje. –

+0

@savinger 'setElement' jest przeważnie z powodów kosmetycznych i dla" samodzielności "szablonów, technika ta byłaby bardziej przydatna, gdybyś musiał na przykład powtórzyć wiersze. Ta odpowiedź może pomóc w zrozumieniu mojego punktu widzenia: http://stackoverflow.com/questions/12004534/backbonejs-rendering-problems/12006179#12006179 – nikoshr

4

Kolekcja powiązana z kolekcją CollectionView musi być spójna z tym, co renderujesz, lub napotkasz problemy. Nie powinieneś ręcznie opróżniać swojego tbody. Należy zaktualizować kolekcję i odsłuchać zdarzenia emitowane przez kolekcję w CollectionView i użyć jej do zaktualizowania widoku. W swojej metodzie wyszukiwania powinieneś zaktualizować swoją kolekcję, a nie swój CollectionView. Jest to jeden sposób można wdrożyć go w CollectionView zainicjować metoda:


initialize: function() { 
    //... 

    this.listenTo(this.collection, "reset", this.render); 
    this.listenTo(this.collection, "add", this.addOne); 
} 

A w swojej metody wyszukiwania, można po prostu zresetować swoją kolekcję i widok będzie świadczyć automatycznie:


search: function() { 
    this.collection.reset(filteredModels); 
} 

gdzie filteredModels jest tablica modeli pasujących do zapytania wyszukiwania. Zwróć uwagę, że po zresetowaniu kolekcji za pomocą modeli filtrowanych utracisz dostęp do innych modeli, które były pierwotnie dostępne przed wyszukiwaniem. Powinieneś mieć odniesienie do głównej kolekcji, która zawiera wszystkie twoje modele, niezależnie od wyszukiwania. Ta "kolekcja główna" nie jest powiązana z twoim widokiem per se, ale możesz użyć filtru tej kolekcji głównej i zaktualizować kolekcję widoku za pomocą filtrowanych modeli.

Jeśli chodzi o drugie pytanie, nie powinieneś mieć odniesienia do widoku z modelu.Model powinien być całkowicie niezależny od widoku - tylko widok powinien odwoływać się do modelu.

Twoja metoda addOne może być refactored tak dla lepszej wydajności (zawsze używać $ el dołączyć subviews):


var view = new RowView({ model: model }); 
this.$el.find('tbody').append(view.render().el); 
+0

Dzięki za odpowiedź. Pierwsze pytanie ... Jaka jest różnica między 'this.listenTo (this.collection," reset ", this.render)' i 'this.collection.on (" reset ", this.render, this)'? –

+0

Drugie pytanie. Podoba mi się to, co powiedziałeś o kolekcji głównej i kolekcji CollectionView ... ale nie dotyczyłeś subviews. Czy można utworzyć nowe 'RowView's z każdym' addOne'? –

+2

@savinger Zasadniczo osiągają to samo - słuchają wydarzeń. Jednakże 'this.listenTo' kojarzy słuchanie z widokiem, podczas gdy' this.collection.on' kojarzy słuchanie z kolekcją. Nie wydaje się to dużą różnicą, ale pamiętaj, że jeśli użyjesz 'this.collection.on', kolekcja będzie nadal nasłuchiwała, nawet jeśli usuniesz swój widok, co może spowodować wycieki pamięci i znacznie spowolnić działanie aplikacji. Z drugiej strony, jeśli użyjesz 'this.listenTo', to nie będzie słuchać zdarzenia po usunięciu widoku. – hesson

Powiązane problemy