-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy patheach.js
205 lines (167 loc) · 5.85 KB
/
each.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
// 'each' binding
module.exports = function(el, val) {
var self = this;
// get the reactive constructor from the current reactive instance
// TODO(shtylman) port over adapter and bindings from instance?
var Reactive = self.reactive.constructor;
var val = val.split(/ +/);
el.removeAttribute('each');
var name = val[0];
var prop = val[0];
if (val.length > 1) {
name = val[0];
prop = val[2];
}
var parent = el.parentNode;
// use text node to hold where end of list should be
var placeholder = document.createTextNode('');
parent.insertBefore(placeholder, el);
parent.removeChild(el);
// the reactive views we created for our array
// one per array item
// the length of this MUST always match the length of the 'arr'
// and mutates with 'arr'
var views = [];
function childView(el, model) {
return Reactive(el, model, {
delegate: self.view,
adapter: self.reactive.opt.adapter,
bindings: self.reactive.bindings
});
}
var array;
// bind entire new array
function change(arr) {
// remove any old bindings/views
views.forEach(function(view) {
view.destroy();
});
views = [];
// remove any old array observers
if (array) {
unpatchArray(array);
}
patchArray(arr);
array = arr;
// handle initial array
var fragment = document.createDocumentFragment();
arr.forEach(function(obj) {
var clone = el.cloneNode(true);
var view = childView(clone, obj);
views.push(view);
fragment.appendChild(clone);
});
parent.insertBefore(fragment, placeholder);
}
function unpatchArray(arr) {
delete arr.splice;
delete arr.push;
delete arr.unshift;
}
function patchArray(arr) {
// splice will replace the current arr.splice function
// so that we can intercept modifications
var old_splice = arr.splice;
// idx -> index to start operation
// how many -> elements to remove
// ... elements to insert
// return removed elements
var splice = function(idx, how_many) {
var args = Array.prototype.slice.apply(arguments);
// new items to insert if any
var new_items = args.slice(2);
var place = placeholder;
if (idx < views.length) {
place = views[idx].el;
}
// make views for these items
var new_views = new_items.map(function(item) {
var clone = el.cloneNode(true);
return childView(clone, item);
});
var splice_args = [idx, how_many].concat(new_views);
var removed = views.splice.apply(views, splice_args);
var frag = document.createDocumentFragment();
// insert into appropriate place
// first removed item is where to insert
new_views.forEach(function(view) {
frag.appendChild(view.el);
});
// insert before a specific location
// the location is defined by the element at idx
parent.insertBefore(frag, place);
// remove after since we may need the element for 'placement'
// of the new document fragment
removed.forEach(function(view) {
view.destroy();
});
var ret = old_splice.apply(arr, args);
// set the length property of the array
// so that any listeners can pick up on it
self.reactive.set(prop + '.length', arr.length);
return ret;
};
/// existing methods can be implemented via splice
var push = function(el1, el2) {
var args = Array.prototype.slice.apply(arguments);
var len = arr.length;
var splice_args = [len, 0].concat(args)
splice.apply(arr, splice_args);
return arr.length;
};
var unshift = function(el1, el2) {
var args = Array.prototype.slice.apply(arguments);
var len = arr.length;
var splice_args = [0, 0].concat(args)
splice.apply(arr, splice_args);
return arr.length;
};
var pop = function() {
if (!arr.length) {
return void 0;
}
var element = arr[arr.length-1];
splice.apply(arr,[arr.length-1,1]);
return element;
};
var shift = function() {
if (!arr.length) {
return void 0;
}
var element = arr[0];
splice.apply(arr,[0,1]);
return element;
};
var sort = function () {
var ret = Array.prototype.sort.apply(arr,arguments);
var arr2 = [0,arr.length].concat(arr);
splice.apply(arr,arr2);
return ret;
};
var reverse = function() {
var ret = Array.prototype.reverse.apply(arr);
var arr2 = [0,arr.length].concat(arr);
splice.apply(arr,arr2);
return ret;
};
// use defineProperty to avoid making ownProperty fields
function set_prop(prop, fn) {
Object.defineProperty(arr, prop, {
enumerable: false,
writable: true,
configurable: true,
value: fn
});
}
set_prop('splice', splice);
set_prop('push', push);
set_prop('unshift', unshift);
set_prop('pop', pop);
set_prop('shift', shift);
set_prop('sort', sort);
set_prop('reverse', reverse);
}
change(self.reactive.get(prop) || []);
self.skip = true;
self.reactive.sub(prop, change);
};