13

在我的 Backbone 应用程序中的几个地方,我想对集合进行即时搜索,但我很难想出实现它的最佳方法。

这是一个快速实现。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

在每次按下按键时,我都会过滤集合、清空tbody并渲染结果,从而为每个模型创建一个新视图。我刚刚创建了幽灵视图,是吗?最好适当地销毁每个视图吗?还是我应该尝试管理我RowView的...只创建一次,然后循环遍历它们以仅呈现结果?CollectionView也许我的数组?清空 后tbody,是否RowViews仍然有它们的el或现在为 null 并需要重新渲染?

问题2,模型选择

你会注意到我在我的RowView. 我想在某处有一个详细视图来处理该事件并显示我的整个模型。当我搜索我的列表时,如果我选择的模型保留在搜索结果中,我想保持该状态并让它保留在我的详细视图中。一旦它不再出现在我的结果中,我将清空详细视图。所以我当然需要管理一系列视图,对吧?我考虑过一个双重链接的结构,其中每个视图都指向它的模型,每个模型都指向它的视图......但是如果我将来要在我的模型上实现一个单例工厂,我不能将它强加于模型。:/

那么管理这些视图的最佳方法是什么?

4

2 回答 2

20

在玩你的问题时,我有点得意忘形。

首先,我将创建一个专用集合来保存过滤后的模型和一个“状态模型”来处理搜索。例如,

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,而不是“this.el”包装了解详细信息

实例看起来像

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/

于 2013-08-10T15:58:27.243 回答
4

与您的 CollectionView 关联的 Collection 必须与您正在呈现的内容一致,否则您会遇到问题。您不必手动清空 tbody。您应该更新集合,并在 CollectionView 中侦听集合发出的事件并使用它来更新视图。在您的搜索方法中,您应该只更新您的 Collection 而不是您的 CollectionView。这是您可以在 CollectionView 初始化方法中实现它的一种方式:


initialize: function() {
  //...

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

在您的搜索方法中,您只需重置您的集合,视图将自动呈现:


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

wherefilteredModels是与搜索查询匹配的模型数组。请注意,一旦您使用过滤模型重置您的集合,您将无法访问在搜索之前最初存在的其他模型。无论搜索如何,您都应该引用包含所有模型的主集合。此“主集合”与您的视图本身无关,但您可以在此主集合上使用过滤器并使用过滤后的模型更新视图的集合。

至于您的第二个问题,您不应该参考模型中的视图。模型应该完全独立于视图——只有视图应该引用模型。

您的addOne方法可以像这样重构以获得更好的性能(始终使用 $el 附加子视图):


var view = new RowView({ model: model });
this.$el.find('tbody').append(view.render().el);
于 2013-08-10T01:00:05.723 回答