如何过滤/搜索嵌套的主干集合?

时间:2014-09-09 13:46:07

标签: javascript backbone.js filter

我的Backbone应用程序中有一个树形视图,我使用嵌套的集合和模型:

集合

define(function(require) {

    var Backbone      = require('backbone')
      , UserListModel = require('app/models/userList');

    return Backbone.Collection.extend({
        model: UserListModel,
        url: '/api/lists',
    });
});

型号

define(function(require) {

    var Backbone = require('backbone');

    return Backbone.Model.extend({

        constructor: function(data, opts) {
            opts = _.extend({}, opts, {parse: true});

            var UserLists  = require('app/collections/userLists');
            this.children  = new UserLists();

            Backbone.Model.call(this, data, opts);
        },

        parse: function(data) {
            if (_.isArray(data.children))
                this.children.set(data.children);
            return _.omit(data, 'chilren');
        }

    });
});

视图的一部分 :(完整视图:http://laravel.io/bin/O9oYX

var UserListTreeItemView = Backbone.View.extend({

    render: function() {
        var data = this.model.toJSON();
            data.hasChildren = !!this.model.get('isFolder');

        this.$el.html(this.template(data));

        if( this.model.get('isFolder') ) {
            var list = new UserListTreeView({
                collection: this.model.children
            });
            this.$el.append(list.render().el);
        }

        return this;
    }

});

我使用两个视图将我的集合渲染为树视图。我想在我的树视图中添加搜索功能,我无法弄清楚如何。它应该能够在所有模型及其嵌套模型上搜索name属性。

有什么想法吗?

2 个答案:

答案 0 :(得分:1)

如果您已经在集合中拥有所需的模型,只需在集合本身上使用继承的Underscore方法filter()即可。但它会返回一个模型数组,而不是Backbone Collection。

http://underscorejs.org/#filter

假设按属性name进行过滤:

var nameToSearch = "whatever";
var itemsByName = this.model.children.filter(function(item){
  return item.get("name").indexOf(nameToSearch) >=0;
}

我要做的是隔离您的getData方法以涵盖这两种情况:过滤/关闭。

您没有指定如何搜索,但我猜您周围有文本输入,并且您想要使用该值。那只会搜索顶级项目吗?深度搜索会更复杂一些,涉及每个父项在其子项上查找名称。对于您将在每个文件夹中搜索文件的简单情况,请将搜索过滤器保留在父视图状态中。为此,我通常使用普通的香草Backbone模型,只是为了利用事件。

var MySearchView = Backbone.View.extend({
  initialize: function(options){
     //I like the idea of having a ViewModel to keep state
     this.viewState = new Backbone.Model({
       searchQuery: ""
     });
     //whenever the search query is changed, re-render
     this.listenTo(this.viewState, "change:searchQuery", this.render);
  },
  events: {
    "click .js-search-button": "doSearch"
  },
  doSearch: function(e){
    e.preventDefault();
    var query = this.$(".js-search-input").val();
    this.viewState.set("seachQuery", query);
  },  
  render: function(){
    var data = this.model.toJSON();
        data.hasChildren = !!this.model.get('isFolder');

    this.$el.html(this.template(data));

    if( this.model.get('isFolder') ) {
        //be careful with this, you're not removing your child views ever
        if(this._listView) {
           this._listView.remove();
        }
        this._listView = new UserListTreeView({
            collection: this.model.children,
            **searchQuery: this.viewState.get("searchQuery")**
        });
        this.$el.append(this._listView.render().el);
    }

    return this;
  }
});

现在在UserListTreeView中,将模板的数据输入抽象为考虑搜索查询的方法:

var UserListTreeView = Backbone.View.extend({
  initialize: function(options){
    this.searchQuery = options.searchQuery || "";
  },
  ...
  getData: function(){
     //filter your collection if needed
     var query = this.searchQuery;
     if(query !== ""){
       return this.collection.filter(function(file){
         return file.get("name").indexOf(query) >= 0;
     }
     else {
       return this.collection.toJSON();
     }
  },
  render: function() {
    var items = this.getData(),
        template = this.template(items);

    this.$el.empty().append(template);
    return this;
  }
});

Voilá,相同的视图将呈现完整集合或其项目名称中包含searchQuery的过滤版本。您可以通过更改filter调用中的比较来调整搜索方法:您可以执行RegExp,仅搜索以(indexOf(searchQuery)== 0)开头的文件,依此类推。

比预期更长时间,希望它有所帮助。另一种选择是在集合本身中实现它,你可以覆盖它的toJSON()方法来返回它上面的全部或一些项目。如果你发现自己编写了另一个需要filterint的视图,那么创建一个SearchableCollection并从那里继承它们可能是个更好的主意。保持干燥。 :)

作为旁注:您应该看看MarionetteJS或构建您自己的专业视图(收集等),以避免一遍又一遍地输入相同内容。

答案 1 :(得分:0)

我不确定我是否完全了解您的应用,但以下是我之前做过类似事情的方式:

在你的模型中添加:

matches: function(search) {
    // a very simple and basic implementation
    return this.get('name').indexOf(search) != -1;
}

并在UserListTreeView的{​​{1}}:

中使用它
render

非常简单但有效。这实际上是转移这个想法的最基本版本。您可以通过将其扩展到其他模型和集合,检查某些边缘情况并通过简单优化来提高其性能来改进此方法。