13

バックボーン アプリケーションのいくつかの場所で、コレクションをすばやく検索したいと考えていますが、それを実装する最善の方法を見つけるのに苦労しています。

これが簡単な実装です。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の s を管理しようとする必要があります... それぞれを 1 回だけ作成し、それらをループして結果のみをレンダリングする必要がありますか? 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);
        }
    }
});

BaseViewDOM をその場で変更できます。詳細については、"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 でリッスンし、それを使用してビューを更新する必要があります。検索方法では、CollectionView ではなく Collection のみを更新する必要があります。これは、CollectionView の初期化メソッドで実装できる 1 つの方法です。


initialize: function() {
  //...

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

検索メソッドでは、コレクションをリセットするだけで、ビューが自動的にレンダリングされます。


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

検索クエリにfilteredModels一致するモデルの配列です。フィルタリングされたモデルでコレクションをリセットすると、検索前に最初にあった他のモデルにアクセスできなくなることに注意してください。検索に関係なく、すべてのモデルを含むマスター コレクションへの参照が必要です。この「マスター コレクション」は、ビュー自体には関連付けられていませんが、このマスター コレクションでフィルターを使用して、フィルター処理されたモデルでビューのコレクションを更新できます。

2 番目の質問については、モデルからのビューへの参照は必要ありません。モデルはビューから完全に独立している必要があります。ビューのみがモデルを参照する必要があります。

パフォーマンスを向上させるために、メソッドを次のようにリファクタリングaddOneできます (常に $el を使用してサブビューをアタッチします)。


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