/
array_controller.js
291 lines (232 loc) · 7.84 KB
/
array_controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/**
@module ember
@submodule ember-runtime
*/
import Ember from 'ember-metal/core';
import { get } from 'ember-metal/property_get';
import {
forEach,
replace
} from 'ember-metal/enumerable_utils';
import ArrayProxy from 'ember-runtime/system/array_proxy';
import SortableMixin from 'ember-runtime/mixins/sortable';
import ControllerMixin from 'ember-runtime/mixins/controller';
import { computed } from 'ember-metal/computed';
import EmberError from 'ember-metal/error';
import EmberArray from 'ember-runtime/mixins/array';
/**
`Ember.ArrayController` provides a way for you to publish a collection of
objects so that you can easily bind to the collection from a Handlebars
`#each` helper, an `Ember.CollectionView`, or other controllers.
The advantage of using an `ArrayController` is that you only have to set up
your view bindings once; to change what's displayed, simply swap out the
`model` property on the controller.
For example, imagine you wanted to display a list of items fetched via an XHR
request. Create an `Ember.ArrayController` and set its `model` property:
```javascript
MyApp.listController = Ember.ArrayController.create();
$.get('people.json', function(data) {
MyApp.listController.set('model', data);
});
```
Then, create a view that binds to your new controller:
```handlebars
{{#each person in MyApp.listController}}
{{person.firstName}} {{person.lastName}}
{{/each}}
```
Although you are binding to the controller, the behavior of this controller
is to pass through any methods or properties to the underlying array. This
capability comes from `Ember.ArrayProxy`, which this class inherits from.
Sometimes you want to display computed properties within the body of an
`#each` helper that depend on the underlying items in `model`, but are not
present on those items. To do this, set `itemController` to the name of a
controller (probably an `ObjectController`) that will wrap each individual item.
For example:
```handlebars
{{#each post in controller}}
<li>{{post.title}} ({{post.titleLength}} characters)</li>
{{/each}}
```
```javascript
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
```
In some cases it is helpful to return a different `itemController` depending
on the particular item. Subclasses can do this by overriding
`lookupItemController`.
For example:
```javascript
App.MyArrayController = Ember.ArrayController.extend({
lookupItemController: function( object ) {
if (object.get('isSpecial')) {
return "special"; // use App.SpecialController
} else {
return "regular"; // use App.RegularController
}
}
});
```
The itemController instances will have a `parentController` property set to
the `ArrayController` instance.
@class ArrayController
@namespace Ember
@extends Ember.ArrayProxy
@uses Ember.SortableMixin
@uses Ember.ControllerMixin
*/
export default ArrayProxy.extend(ControllerMixin, SortableMixin, {
/**
A string containing the controller name used to wrap items.
For example:
```javascript
App.MyArrayController = Ember.ArrayController.extend({
itemController: 'myItem' // use App.MyItemController
});
```
@property itemController
@type String
@default null
*/
itemController: null,
/**
Return the name of the controller to wrap items, or `null` if items should
be returned directly. The default implementation simply returns the
`itemController` property, but subclasses can override this method to return
different controllers for different objects.
For example:
```javascript
App.MyArrayController = Ember.ArrayController.extend({
lookupItemController: function( object ) {
if (object.get('isSpecial')) {
return "special"; // use App.SpecialController
} else {
return "regular"; // use App.RegularController
}
}
});
```
@method lookupItemController
@param {Object} object
@return {String}
*/
lookupItemController(object) {
return get(this, 'itemController');
},
objectAtContent(idx) {
var length = get(this, 'length');
var arrangedContent = get(this, 'arrangedContent');
var object = arrangedContent && arrangedContent.objectAt(idx);
var controllerClass;
if (idx >= 0 && idx < length) {
controllerClass = this.lookupItemController(object);
if (controllerClass) {
return this.controllerAt(idx, object, controllerClass);
}
}
// When `controllerClass` is falsy, we have not opted in to using item
// controllers, so return the object directly.
// When the index is out of range, we want to return the "out of range"
// value, whatever that might be. Rather than make assumptions
// (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
return object;
},
arrangedContentDidChange() {
this._super(...arguments);
this._resetSubControllers();
},
arrayContentDidChange(idx, removedCnt, addedCnt) {
var subControllers = this._subControllers;
if (subControllers.length) {
var subControllersToRemove = subControllers.slice(idx, idx + removedCnt);
forEach(subControllersToRemove, function(subController) {
if (subController) {
subController.destroy();
}
});
replace(subControllers, idx, removedCnt, new Array(addedCnt));
}
// The shadow array of subcontrollers must be updated before we trigger
// observers, otherwise observers will get the wrong subcontainer when
// calling `objectAt`
this._super(idx, removedCnt, addedCnt);
},
init() {
this._super(...arguments);
this._subControllers = [];
},
model: computed({
get(key) {
return Ember.A();
},
set(key, value) {
Ember.assert(
'ArrayController expects `model` to implement the Ember.Array mixin. ' +
'This can often be fixed by wrapping your model with `Ember.A()`.',
EmberArray.detect(value) || !value
);
return value;
}
}),
/**
* Flag to mark as being "virtual". Used to keep this instance
* from participating in the parentController hierarchy.
*
* @private
* @property _isVirtual
* @type Boolean
*/
_isVirtual: false,
controllerAt(idx, object, controllerClass) {
var container = get(this, 'container');
var subControllers = this._subControllers;
var fullName, subController, parentController;
if (subControllers.length > idx) {
subController = subControllers[idx];
if (subController) {
return subController;
}
}
if (this._isVirtual) {
parentController = get(this, 'parentController');
} else {
parentController = this;
}
fullName = 'controller:' + controllerClass;
if (!container._registry.has(fullName)) {
throw new EmberError('Could not resolve itemController: "' + controllerClass + '"');
}
subController = container.lookupFactory(fullName).create({
target: parentController,
parentController: parentController,
model: object
});
subControllers[idx] = subController;
return subController;
},
_subControllers: null,
_resetSubControllers() {
var controller;
var subControllers = this._subControllers;
if (subControllers.length) {
for (var i = 0, length = subControllers.length; length > i; i++) {
controller = subControllers[i];
if (controller) {
controller.destroy();
}
}
subControllers.length = 0;
}
},
willDestroy() {
this._resetSubControllers();
this._super(...arguments);
}
});