1 | var _ = require('underscore');
|
2 | var BBEvents = require('backbone-events-standalone');
|
3 | var ampExtend = require('ampersand-class-extend');
|
4 |
|
5 |
|
6 | var options = ['collection', 'el', 'viewOptions', 'view', 'filter', 'reverse'];
|
7 |
|
8 |
|
9 | function CollectionView(spec) {
|
10 | _.extend(this, _.pick(spec, options));
|
11 | this.views = [];
|
12 | this.listenTo(this.collection, 'add', this._addViewForModel);
|
13 | this.listenTo(this.collection, 'remove', this._removeViewForModel);
|
14 | this.listenTo(this.collection, 'move sort', this._renderAll);
|
15 | this.listenTo(this.collection, 'refresh reset', this._reset);
|
16 | }
|
17 |
|
18 | _.extend(CollectionView.prototype, BBEvents, {
|
19 |
|
20 | render: function () {
|
21 | this._renderAll();
|
22 | return this;
|
23 | },
|
24 | remove: function () {
|
25 | _.invoke(this.views, 'remove');
|
26 | this.stopListening();
|
27 | },
|
28 | _getViewByModel: function (model) {
|
29 | return _.find(this.views, function (view) {
|
30 | return model === view.model;
|
31 | });
|
32 | },
|
33 | _createViewForModel: function (model) {
|
34 | var view = new this.view(_({model: model, collection: this.collection}).extend(this.viewOptions));
|
35 | this.views.push(view);
|
36 | view.parent = this;
|
37 | view.renderedByParentView = true;
|
38 | view.render();
|
39 | },
|
40 | _getOrCreateByModel: function (model) {
|
41 | return this._getViewByModel(model) || this._createViewForModel(model);
|
42 | },
|
43 | _addViewForModel: function (model) {
|
44 | var view = this._getViewByModel(model);
|
45 | var matches = this.filter ? this.filter(model) : true;
|
46 | if (!matches) {
|
47 | return;
|
48 | }
|
49 | if (!view) {
|
50 | view = new this.view(_({model: model, collection: this.collection}).extend(this.viewOptions));
|
51 | this.views.push(view);
|
52 | view.parent = this;
|
53 | view.renderedByParentView = true;
|
54 | view.render({containerEl: this.el});
|
55 | }
|
56 | this._insertView(view);
|
57 | },
|
58 | _insertView: function (view) {
|
59 | if (!view.insertSelf) {
|
60 | if (this.reverse) {
|
61 | this.el.insertBefore(view.el, this.el.firstChild);
|
62 | } else {
|
63 | this.el.appendChild(view.el);
|
64 | }
|
65 | }
|
66 | },
|
67 | _removeViewForModel: function (model) {
|
68 | var view = this._getViewByModel(model);
|
69 | if (!view) {
|
70 | return;
|
71 | }
|
72 | var index = this.views.indexOf(view);
|
73 | if (index !== -1) {
|
74 |
|
75 |
|
76 | view = this.views.splice(index, 1)[0];
|
77 | this._removeView(view);
|
78 | }
|
79 | },
|
80 | _removeView: function (view) {
|
81 | if (view.animateRemove) {
|
82 | view.animateRemove();
|
83 | } else {
|
84 | view.remove();
|
85 | }
|
86 | },
|
87 | _renderAll: function () {
|
88 | this.collection.each(this._addViewForModel, this);
|
89 | },
|
90 | _reset: function () {
|
91 | var newViews = [];
|
92 |
|
93 | if (this.views.length) {
|
94 | newViews = this.collection.map(this._getOrCreateByModel);
|
95 | }
|
96 | var toRemove = _.difference(this.views, newViews);
|
97 | toRemove.forEach(this._removeView, this);
|
98 | this.views = newViews;
|
99 | this.views.forEach(this._insertView, this);
|
100 | }
|
101 | });
|
102 |
|
103 | CollectionView.prototype.extend = ampExtend;
|
104 |
|
105 | module.exports = CollectionView;
|