-
Notifications
You must be signed in to change notification settings - Fork 35
/
ViewSequence.js
282 lines (253 loc) · 9.65 KB
/
ViewSequence.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
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Owner: [email protected]
* @license MPL 2.0
* @copyright Famous Industries, Inc. 2014
*/
define(function(require, exports, module) {
/**
* Helper object used to iterate through items sequentially. Used in
* views that deal with layout. A ViewSequence object conceptually points
* to a node in a linked list.
*
* @class ViewSequence
*
* @constructor
* @param {Object|Array} options Options object, or content array.
* @param {Number} [options.index] starting index.
* @param {Number} [options.array] Array of elements to populate the ViewSequence
* @param {Object} [options._] Optional backing store (internal
* @param {Boolean} [options.loop] Whether to wrap when accessing elements just past the end
* (or beginning) of the sequence.
*/
function ViewSequence(options) {
if (!options) options = [];
if (options instanceof Array) options = {array: options};
this._ = null;
this.index = options.index || 0;
if (options.array) this._ = new (this.constructor.Backing)(options.array);
else if (options._) this._ = options._;
if (this.index === this._.firstIndex) this._.firstNode = this;
if (this.index === this._.firstIndex + this._.array.length - 1) this._.lastNode = this;
if (options.loop !== undefined) this._.loop = options.loop;
this._previousNode = null;
this._nextNode = null;
}
// constructor for internal storage
ViewSequence.Backing = function Backing(array) {
this.array = array;
this.firstIndex = 0;
this.loop = false;
this.firstNode = null;
this.lastNode = null;
};
// Get value "i" slots away from the first index.
ViewSequence.Backing.prototype.getValue = function getValue(i) {
var _i = i - this.firstIndex;
if (_i < 0 || _i >= this.array.length) return null;
return this.array[_i];
};
// Set value "i" slots away from the first index.
ViewSequence.Backing.prototype.setValue = function setValue(i, value) {
this.array[i - this.firstIndex] = value;
};
// After splicing into the backing store, restore the indexes of each node correctly.
ViewSequence.Backing.prototype.reindex = function reindex(start, removeCount, insertCount) {
if (!this.array[0]) return;
var i = 0;
var index = this.firstIndex;
var indexShiftAmount = insertCount - removeCount;
var node = this.firstNode;
// find node to begin
while (index < start - 1) {
node = node.getNext();
index++;
}
// skip removed nodes
var spliceStartNode = node;
for (i = 0; i < removeCount; i++) {
node = node.getNext();
if (node) node._previousNode = spliceStartNode;
}
var spliceResumeNode = node ? node.getNext() : null;
// generate nodes for inserted items
spliceStartNode._nextNode = null;
node = spliceStartNode;
for (i = 0; i < insertCount; i++) node = node.getNext();
index += insertCount;
// resume the chain
if (node !== spliceResumeNode) {
node._nextNode = spliceResumeNode;
if (spliceResumeNode) spliceResumeNode._previousNode = node;
}
if (spliceResumeNode) {
node = spliceResumeNode;
index++;
while (node && index < this.array.length + this.firstIndex) {
if (node._nextNode) node.index += indexShiftAmount;
else node.index = index;
node = node.getNext();
index++;
}
}
};
/**
* Return ViewSequence node previous to this node in the list, respecting looping if applied.
*
* @method getPrevious
* @return {ViewSequence} previous node.
*/
ViewSequence.prototype.getPrevious = function getPrevious() {
if (this.index === this._.firstIndex) {
if (this._.loop) {
this._previousNode = this._.lastNode || new (this.constructor)({_: this._, index: this._.firstIndex + this._.array.length - 1});
this._previousNode._nextNode = this;
}
else {
this._previousNode = null;
}
}
else if (!this._previousNode) {
this._previousNode = new (this.constructor)({_: this._, index: this.index - 1});
this._previousNode._nextNode = this;
}
return this._previousNode;
};
/**
* Return ViewSequence node next after this node in the list, respecting looping if applied.
*
* @method getNext
* @return {ViewSequence} previous node.
*/
ViewSequence.prototype.getNext = function getNext() {
if (this.index === this._.firstIndex + this._.array.length - 1) {
if (this._.loop) {
this._nextNode = this._.firstNode || new (this.constructor)({_: this._, index: this._.firstIndex});
this._nextNode._previousNode = this;
}
else {
this._nextNode = null;
}
}
else if (!this._nextNode) {
this._nextNode = new (this.constructor)({_: this._, index: this.index + 1});
this._nextNode._previousNode = this;
}
return this._nextNode;
};
/**
* Return index of this ViewSequence node.
*
* @method getIndex
* @return {Number} index
*/
ViewSequence.prototype.getIndex = function getIndex() {
return this.index;
};
/**
* Return printable version of this ViewSequence node.
*
* @method toString
* @return {string} this index as a string
*/
ViewSequence.prototype.toString = function toString() {
return '' + this.index;
};
/**
* Add one or more objects to the beginning of the sequence.
*
* @method unshift
* @param {...Object} value arguments array of objects
*/
ViewSequence.prototype.unshift = function unshift(value) {
this._.array.unshift.apply(this._.array, arguments);
this._.firstIndex -= arguments.length;
};
/**
* Add one or more objects to the end of the sequence.
*
* @method push
* @param {...Object} value arguments array of objects
*/
ViewSequence.prototype.push = function push(value) {
this._.array.push.apply(this._.array, arguments);
};
/**
* Remove objects from the sequence
*
* @method splice
* @param {Number} index starting index for removal
* @param {Number} howMany how many elements to remove
* @param {...Object} value arguments array of objects
*/
ViewSequence.prototype.splice = function splice(index, howMany) {
var values = Array.prototype.slice.call(arguments, 2);
this._.array.splice.apply(this._.array, [index - this._.firstIndex, howMany].concat(values));
this._.reindex(index, howMany, values.length);
};
/**
* Exchange this element's sequence position with another's.
*
* @method swap
* @param {ViewSequence} other element to swap with.
*/
ViewSequence.prototype.swap = function swap(other) {
var otherValue = other.get();
var myValue = this.get();
this._.setValue(this.index, otherValue);
this._.setValue(other.index, myValue);
var myPrevious = this._previousNode;
var myNext = this._nextNode;
var myIndex = this.index;
var otherPrevious = other._previousNode;
var otherNext = other._nextNode;
var otherIndex = other.index;
this.index = otherIndex;
this._previousNode = (otherPrevious === this) ? other : otherPrevious;
if (this._previousNode) this._previousNode._nextNode = this;
this._nextNode = (otherNext === this) ? other : otherNext;
if (this._nextNode) this._nextNode._previousNode = this;
other.index = myIndex;
other._previousNode = (myPrevious === other) ? this : myPrevious;
if (other._previousNode) other._previousNode._nextNode = other;
other._nextNode = (myNext === other) ? this : myNext;
if (other._nextNode) other._nextNode._previousNode = other;
if (this.index === this._.firstIndex) this._.firstNode = this;
else if (this.index === this._.firstIndex + this._.array.length - 1) this._.lastNode = this;
if (other.index === this._.firstIndex) this._.firstNode = other;
else if (other.index === this._.firstIndex + this._.array.length - 1) this._.lastNode = other;
};
/**
* Return value of this ViewSequence node.
*
* @method get
* @return {Object} value of thiss
*/
ViewSequence.prototype.get = function get() {
return this._.getValue(this.index);
};
/**
* Call getSize() on the contained View.
*
* @method getSize
* @return {Array.Number} [width, height]
*/
ViewSequence.prototype.getSize = function getSize() {
var target = this.get();
return target ? target.getSize() : null;
};
/**
* Generate a render spec from the contents of this component.
* Specifically, this will render the value at the current index.
* @private
* @method render
* @return {number} Render spec for this component
*/
ViewSequence.prototype.render = function render() {
var target = this.get();
return target ? target.render.apply(target, arguments) : null;
};
module.exports = ViewSequence;
});