2013-08-10 3 views
11

Несколько мест в моем приложении базовой линии. Я хотел бы иметь мгновенный поиск по коллекции, но мне сложно найти лучший способ реализовать Это.Backbone.js - Лучшая практика для реализации «мгновенного» поиска

Вот краткая реализация. http://jsfiddle.net/7YgeE/ Помните, что моя коллекция может содержать более 200 моделей.

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

И крошечный вид для каждой модели ...

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; 

Вопрос 1

На каждом KeyDown, я фильтр коллекции, опорожнить tbody, и сделать результаты, тем самым создавая новое представление для каждой модели. Я только что создал призрак, да? Было бы лучше правильно уничтожить каждую точку зрения? Или я должен попытаться управлять моими RowView ... создавая каждый из них только один раз и прокручивая их, чтобы отображать только результаты? Может быть, массив в моем CollectionView? После опорожнения tbody у RowViews все еще есть их el или это теперь нулевое значение и нужно повторно отобразить?

Вопрос 2, Выбор модели

Вы заметите, что я запуская пользовательское событие в моей RowView. Я хотел бы иметь подробный вид где-нибудь, чтобы обработать это событие и отобразить всю мою модель. Когда я просматриваю свой список, если моя выбранная модель остается в результатах поиска, я хочу сохранить это состояние и оставить его в моем подробном представлении. Как только это больше не будет в моих результатах, я очищу подробный вид. Поэтому мне, безусловно, нужно будет управлять массивом взглядов, не так ли? Я рассмотрел структуру с двойной связью, в которой каждая точка зрения указывает на ее модель, а каждая модель - на ее вид ... но если я буду реализовывать одномоментную фабрику на своих моделях в будущем, я не могу наложить это на модель. :/

Итак, каков наилучший способ управления этими видами?

ответ

19

я немного увлекся, играя с вашим вопросом.

Во-первых, я бы создал специальную коллекцию для хранения фильтрованных моделей и «модели состояния» для обработки поиска. Например,

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

, который должен быть создан в качестве

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

Тогда я хотел бы создать обособленные представления для списка и полей ввода: легче поддерживать и передвигаться

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 позволяет изменить DOM на месте, см. Backbone, not "this.el" wrapping для получения более подробной информации

экземпляры будут выглядеть

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

И демо поиска на данном этапе http://jsfiddle.net/XxRD7/2/

Наконец, я хотел бы изменить CollectionView прививать точку строки в моей визуализации функции, что-то вроде

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

Другая скрипка http://jsfiddle.net/XxRD7/3/

+0

Спасибо, это чрезвычайно полезно. Мне очень нравится, что вы сделали с фильтром. В моих ранних попытках у меня тоже был охват, но он был жестко закодирован, и я как-то пропустил функцию 'pick' в документах. Кроме того, никогда не знал о функции «throttle», также очень полезен. –

+0

Я все еще обнимаю то, как у вас есть вещи, и я не совсем продаюсь за использование 'setElement'. Мне кажется нелепо, что нужно переделывать события на каждом рендере. Я никогда не видел эту технологию прививки, где вы визуализируете элементы списка в CollectionView и трансформируете ItemViews ... Я не привык к тому, что ItemView не отвечает за рендеринг себя, и, с одной стороны, кажется, что это разделение проблем, которые должны произойти, но, с другой стороны, удивительно прямолинейны, так как всегда проще, чтобы шаблон повторялся по нашим коллекциям. –

+0

@savinger 'setElement' в основном по косметическим соображениям и для« самодостаточности »шаблонов, этот метод был бы более полезен, если бы вам пришлось перегруппировать строки, например. Этот ответ может помочь вам понять мою точку зрения http://stackoverflow.com/questions/12004534/backbonejs-rendering-problems/12006179#12006179 – nikoshr

4

Коллекция, связанная с вашим CollectionView, должна соответствовать тому, что вы рендерите, или вы столкнетесь с проблемами. Вам не нужно будет опорожнять труп вручную. Вы должны обновить коллекцию и прослушать события, выпущенные в коллекции CollectionView, и использовать их для обновления представления. В вашем методе поиска вы должны обновлять свою коллекцию, а не свой CollectionView. Это один из способов вы можете реализовать его в CollectionView ИНИЦИАЛИЗИРУЙТЕ метод:


initialize: function() { 
    //... 

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

И в методе поиска, вы можете просто сбросить свою коллекцию, и вид будет оказывать автоматически:


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

где filteredModels является массив моделей, соответствующих поисковому запросу. Обратите внимание, что как только вы сбросите свою коллекцию с фильтрованными моделями, вы потеряете доступ к другим моделям, которые первоначально были там до начала поиска. Вы должны иметь ссылку на коллекцию, содержащую все ваши модели, независимо от поиска. Эта «основная коллекция» не связана с вашим видом как таковой, но вы можете использовать фильтр в этой основной коллекции и обновлять коллекцию представления с помощью фильтрованных моделей.

Что касается вашего второго вопроса, вы не должны иметь ссылку на представление модели.Модель должна быть полностью независимой от представления - только представление должно ссылаться на модель.

Вашего addOne метод может быть переработан, как это для лучшей производительности (всегда используйте $ эш прикрепить подвиды):


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

Благодарим за отзыв. Первый вопрос ... В чем разница между 'this.listenTo (this.collection," reset ", this.render)' и 'this.collection.on (" reset ", this.render, this)'? –

+0

Второй вопрос. Мне нравится то, что вы сказали о мастер-коллекции и коллекции CollectionView ... но вы не обратились к subviews. Можно ли создавать новые 'RowView' с каждым 'addOne'? –

+2

@savinger Они по существу выполняют одно и то же - слушают события. Однако 'this.listenTo' связывает прослушивание с представлением, в то время как' this.collection.on' связывает прослушивание с коллекцией. Это не похоже на большую разницу, но имейте в виду, если вы используете 'this.collection.on', коллекция будет продолжать прослушиваться, даже если вы удалите свое представление, которое может вызвать утечку памяти и значительно замедлить ваше приложение. С другой стороны, если вы используете 'this.listenTo', он не будет прослушивать событие после удаления представления. – hesson

Смежные вопросы