2
2
3
3
var Emitter = require ( './emitter' ) ,
4
4
utils = require ( './utils' ) ,
5
-
6
5
// cache methods
7
6
typeOf = utils . typeOf ,
8
7
def = utils . defProtected ,
9
8
slice = [ ] . slice ,
10
-
11
9
// types
12
10
OBJECT = 'Object' ,
13
11
ARRAY = 'Array' ,
14
-
15
- // Array mutation methods to wrap
16
- methods = [ 'push' , 'pop' , 'shift' , 'unshift' , 'splice' , 'sort' , 'reverse' ] ,
17
-
18
12
// fix for IE + __proto__ problem
19
13
// define methods as inenumerable if __proto__ is present,
20
14
// otherwise enumerable so we can loop through and manually
21
15
// attach to array instances
22
16
hasProto = ( { } ) . __proto__ ,
23
-
24
17
// lazy load
25
18
ViewModel
26
19
20
+ // Array Mutation Handlers & Augmentations ------------------------------------
21
+
27
22
// The proxy prototype to replace the __proto__ of
28
23
// an observed array
29
24
var ArrayProxy = Object . create ( Array . prototype )
30
25
26
+ // intercept mutation methods
27
+ ; [
28
+ 'push' ,
29
+ 'pop' ,
30
+ 'shift' ,
31
+ 'unshift' ,
32
+ 'splice' ,
33
+ 'sort' ,
34
+ 'reverse'
35
+ ] . forEach ( watchMutation )
36
+
37
+ // Augment the ArrayProxy with convenience methods
38
+ def ( ArrayProxy , 'remove' , removeElement , ! hasProto )
39
+ def ( ArrayProxy , 'set' , replaceElement , ! hasProto )
40
+ def ( ArrayProxy , 'replace' , replaceElement , ! hasProto )
41
+
31
42
/**
32
- * Define mutation interceptors so we can emit the mutation info
43
+ * Intercep a mutation event so we can emit the mutation info.
44
+ * we also analyze what elements are added/removed and link/unlink
45
+ * them with the parent Array.
33
46
*/
34
- methods . forEach ( function ( method ) {
47
+ function watchMutation ( method ) {
35
48
def ( ArrayProxy , method , function ( ) {
36
- var mutation = applyMutation ( this , method , slice . call ( arguments ) )
37
- linkArrayElements ( this , mutation . inserted )
38
- unlinkArrayElements ( this , mutation . removed )
39
- this . __emitter__ . emit ( 'mutate' , null , this , mutation )
40
- return mutation . result
41
- } , ! hasProto )
42
- } )
43
49
44
- /**
45
- * Mutate the Array and extract mutation info
46
- */
47
- function applyMutation ( arr , method , args ) {
48
- var result = Array . prototype [ method ] . apply ( arr , args ) ,
49
- mutation = {
50
+ var args = slice . call ( arguments ) ,
51
+ result = Array . prototype [ method ] . apply ( this , args ) ,
52
+ inserted , removed
53
+
54
+ // determine new / removed elements
55
+ if ( method === 'push' || method === 'unshift' ) {
56
+ inserted = args
57
+ } else if ( method === 'pop' || method === 'shift' ) {
58
+ removed = [ result ]
59
+ } else if ( method === 'splice' ) {
60
+ inserted = args . slice ( 2 )
61
+ removed = result
62
+ }
63
+ // link & unlink
64
+ linkArrayElements ( this , inserted )
65
+ unlinkArrayElements ( this , removed )
66
+
67
+ // emit the mutation event
68
+ this . __emitter__ . emit ( 'mutate' , null , this , {
50
69
method : method ,
51
70
args : args ,
52
71
result : result
53
- }
54
- if ( method === 'push' || method === 'unshift' ) {
55
- mutation . inserted = args
56
- } else if ( method === 'pop' || method === 'shift' ) {
57
- mutation . removed = [ result ]
58
- } else if ( method === 'splice' ) {
59
- mutation . inserted = args . slice ( 2 )
60
- mutation . removed = result
61
- }
62
- return mutation
72
+ } )
73
+
74
+ return result
75
+
76
+ } , ! hasProto )
63
77
}
64
78
79
+ /**
80
+ * Link new elements to an Array, so when they change
81
+ * and emit events, the owner Array can be notified.
82
+ */
65
83
function linkArrayElements ( arr , items ) {
66
84
if ( items ) {
67
85
var i = items . length , item
68
86
while ( i -- ) {
69
87
item = items [ i ]
70
- if ( typeOf ( item ) === 'Object' ) {
88
+ if ( isWatchable ( item ) ) {
71
89
convert ( item )
72
- watchObject ( item )
90
+ watch ( item )
73
91
if ( ! item . __ownerArrays__ ) {
74
92
def ( item , '__ownerArrays__' , [ ] )
75
93
}
@@ -79,14 +97,17 @@ function linkArrayElements (arr, items) {
79
97
}
80
98
}
81
99
100
+ /**
101
+ * Unlink removed elements from the ex-owner Array.
102
+ */
82
103
function unlinkArrayElements ( arr , items ) {
83
104
if ( items ) {
84
105
var i = items . length , item
85
106
while ( i -- ) {
86
107
item = items [ i ]
87
108
if ( typeOf ( item ) === 'Object' ) {
88
109
var owners = item . __ownerArrays__
89
- owners . splice ( owners . indexOf ( arr ) )
110
+ if ( owners ) owners . splice ( owners . indexOf ( arr ) )
90
111
}
91
112
}
92
113
}
@@ -142,10 +163,48 @@ function replaceElement (index, data) {
142
163
}
143
164
}
144
165
145
- // Augment the ArrayProxy with convenience methods
146
- def ( ArrayProxy , 'remove' , removeElement , ! hasProto )
147
- def ( ArrayProxy , 'set' , replaceElement , ! hasProto )
148
- def ( ArrayProxy , 'replace' , replaceElement , ! hasProto )
166
+ // Watch Helpers --------------------------------------------------------------
167
+
168
+ /**
169
+ * Check if a value is watchable
170
+ */
171
+ function isWatchable ( obj ) {
172
+ ViewModel = ViewModel || require ( './viewmodel' )
173
+ var type = typeOf ( obj )
174
+ return ( type === OBJECT || type === ARRAY ) && ! ( obj instanceof ViewModel )
175
+ }
176
+
177
+ /**
178
+ * Convert an Object/Array to give it a change emitter.
179
+ */
180
+ function convert ( obj ) {
181
+ if ( obj . __emitter__ ) return false
182
+ var emitter = new Emitter ( )
183
+ def ( obj , '__emitter__' , emitter )
184
+ emitter . on ( 'set' , function ( ) {
185
+ var owners = obj . __ownerArrays__ , i
186
+ if ( owners ) {
187
+ i = owners . length
188
+ while ( i -- ) {
189
+ owners [ i ] . __emitter__ . emit ( 'set' , '' )
190
+ }
191
+ }
192
+ } )
193
+ emitter . values = utils . hash ( )
194
+ return true
195
+ }
196
+
197
+ /**
198
+ * Watch target based on its type
199
+ */
200
+ function watch ( obj ) {
201
+ var type = typeOf ( obj )
202
+ if ( type === OBJECT ) {
203
+ watchObject ( obj )
204
+ } else if ( type === ARRAY ) {
205
+ watchArray ( obj )
206
+ }
207
+ }
149
208
150
209
/**
151
210
* Watch an Object, recursive.
@@ -161,11 +220,6 @@ function watchObject (obj) {
161
220
* and add augmentations by intercepting the prototype chain
162
221
*/
163
222
function watchArray ( arr ) {
164
- var emitter = arr . __emitter__
165
- if ( ! emitter ) {
166
- emitter = new Emitter ( )
167
- def ( arr , '__emitter__' , emitter )
168
- }
169
223
if ( hasProto ) {
170
224
arr . __proto__ = ArrayProxy
171
225
} else {
@@ -223,15 +277,6 @@ function convertKey (obj, key) {
223
277
}
224
278
}
225
279
226
- /**
227
- * Check if a value is watchable
228
- */
229
- function isWatchable ( obj ) {
230
- ViewModel = ViewModel || require ( './viewmodel' )
231
- var type = typeOf ( obj )
232
- return ( type === OBJECT || type === ARRAY ) && ! ( obj instanceof ViewModel )
233
- }
234
-
235
280
/**
236
281
* When a value that is already converted is
237
282
* observed again by another observer, we can skip
@@ -303,22 +348,7 @@ function ensurePath (obj, key) {
303
348
}
304
349
}
305
350
306
- function convert ( obj ) {
307
- if ( obj . __emitter__ ) return false
308
- var emitter = new Emitter ( )
309
- def ( obj , '__emitter__' , emitter )
310
- emitter . on ( 'set' , function ( ) {
311
- var owners = obj . __ownerArrays__
312
- if ( owners ) {
313
- var i = owners . length
314
- while ( i -- ) {
315
- owners [ i ] . __emitter__ . emit ( 'set' , '' )
316
- }
317
- }
318
- } )
319
- emitter . values = utils . hash ( )
320
- return true
321
- }
351
+ // Main API Methods -----------------------------------------------------------
322
352
323
353
/**
324
354
* Observe an object with a given path,
@@ -374,12 +404,7 @@ function observe (obj, rawPath, observer) {
374
404
// emit set events for everything inside
375
405
emitSet ( obj )
376
406
} else {
377
- var type = typeOf ( obj )
378
- if ( type === OBJECT ) {
379
- watchObject ( obj )
380
- } else if ( type === ARRAY ) {
381
- watchArray ( obj )
382
- }
407
+ watch ( obj )
383
408
}
384
409
}
385
410
@@ -404,6 +429,8 @@ function unobserve (obj, path, observer) {
404
429
observer . proxies [ path ] = null
405
430
}
406
431
432
+ // Expose API -----------------------------------------------------------------
433
+
407
434
var pub = module . exports = {
408
435
409
436
// whether to emit get events
@@ -413,7 +440,8 @@ var pub = module.exports = {
413
440
observe : observe ,
414
441
unobserve : unobserve ,
415
442
ensurePath : ensurePath ,
416
- convertKey : convertKey ,
417
443
copyPaths : copyPaths ,
418
- watchArray : watchArray
444
+ watch : watch ,
445
+ convert : convert ,
446
+ convertKey : convertKey
419
447
}
0 commit comments