Backbone.js - Best Practice for Implementing “Instant” Search

后端 未结 2 1539
隐瞒了意图╮
隐瞒了意图╮ 2021-01-31 06:14

Several places in my Backbone application I\'d like to have an instant search over a collection, but I\'m having a hard time coming up with the best way to implement it.

2条回答
  •  终归单人心
    2021-01-31 06:33

    I got a little bit carried away while playing with your question.

    First, I would create a dedicated collection to hold the filtered models and a "state model" to handle the search. For example,

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

    which would be instantiated as

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

    Then I would create separated views for the list and the input fields: easier to maintain and to move around

    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 allows to change the DOM in place, see Backbone, not "this.el" wrapping for details

    The instances would look like

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

    And a demo of the search at this stage http://jsfiddle.net/XxRD7/2/

    Finally, I would modify CollectionView to graft the row views in my render function, something like

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

    Another Fiddle http://jsfiddle.net/XxRD7/3/

提交回复
热议问题