22
33var Emitter = require ( './emitter' ) ,
44 utils = require ( './utils' ) ,
5-
65 // cache methods
76 typeOf = utils . typeOf ,
87 def = utils . defProtected ,
98 slice = [ ] . slice ,
10-
119 // types
1210 OBJECT = 'Object' ,
1311 ARRAY = 'Array' ,
14-
15- // Array mutation methods to wrap
16- methods = [ 'push' , 'pop' , 'shift' , 'unshift' , 'splice' , 'sort' , 'reverse' ] ,
17-
1812 // fix for IE + __proto__ problem
1913 // define methods as inenumerable if __proto__ is present,
2014 // otherwise enumerable so we can loop through and manually
2115 // attach to array instances
2216 hasProto = ( { } ) . __proto__ ,
23-
2417 // lazy load
2518 ViewModel
2619
20+ // Array Mutation Handlers & Augmentations ------------------------------------
21+
2722// The proxy prototype to replace the __proto__ of
2823// an observed array
2924var ArrayProxy = Object . create ( Array . prototype )
3025
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+
3142/**
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.
3346 */
34- methods . forEach ( function ( method ) {
47+ function watchMutation ( method ) {
3548 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- } )
4349
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 , {
5069 method : method ,
5170 args : args ,
5271 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 )
6377}
6478
79+ /**
80+ * Link new elements to an Array, so when they change
81+ * and emit events, the owner Array can be notified.
82+ */
6583function linkArrayElements ( arr , items ) {
6684 if ( items ) {
6785 var i = items . length , item
6886 while ( i -- ) {
6987 item = items [ i ]
70- if ( typeOf ( item ) === 'Object' ) {
88+ if ( isWatchable ( item ) ) {
7189 convert ( item )
72- watchObject ( item )
90+ watch ( item )
7391 if ( ! item . __ownerArrays__ ) {
7492 def ( item , '__ownerArrays__' , [ ] )
7593 }
@@ -79,14 +97,17 @@ function linkArrayElements (arr, items) {
7997 }
8098}
8199
100+ /**
101+ * Unlink removed elements from the ex-owner Array.
102+ */
82103function unlinkArrayElements ( arr , items ) {
83104 if ( items ) {
84105 var i = items . length , item
85106 while ( i -- ) {
86107 item = items [ i ]
87108 if ( typeOf ( item ) === 'Object' ) {
88109 var owners = item . __ownerArrays__
89- owners . splice ( owners . indexOf ( arr ) )
110+ if ( owners ) owners . splice ( owners . indexOf ( arr ) )
90111 }
91112 }
92113 }
@@ -142,10 +163,48 @@ function replaceElement (index, data) {
142163 }
143164}
144165
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+ }
149208
150209/**
151210 * Watch an Object, recursive.
@@ -161,11 +220,6 @@ function watchObject (obj) {
161220 * and add augmentations by intercepting the prototype chain
162221 */
163222function watchArray ( arr ) {
164- var emitter = arr . __emitter__
165- if ( ! emitter ) {
166- emitter = new Emitter ( )
167- def ( arr , '__emitter__' , emitter )
168- }
169223 if ( hasProto ) {
170224 arr . __proto__ = ArrayProxy
171225 } else {
@@ -223,15 +277,6 @@ function convertKey (obj, key) {
223277 }
224278}
225279
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-
235280/**
236281 * When a value that is already converted is
237282 * observed again by another observer, we can skip
@@ -303,22 +348,7 @@ function ensurePath (obj, key) {
303348 }
304349}
305350
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 -----------------------------------------------------------
322352
323353/**
324354 * Observe an object with a given path,
@@ -374,12 +404,7 @@ function observe (obj, rawPath, observer) {
374404 // emit set events for everything inside
375405 emitSet ( obj )
376406 } 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 )
383408 }
384409}
385410
@@ -404,6 +429,8 @@ function unobserve (obj, path, observer) {
404429 observer . proxies [ path ] = null
405430}
406431
432+ // Expose API -----------------------------------------------------------------
433+
407434var pub = module . exports = {
408435
409436 // whether to emit get events
@@ -413,7 +440,8 @@ var pub = module.exports = {
413440 observe : observe ,
414441 unobserve : unobserve ,
415442 ensurePath : ensurePath ,
416- convertKey : convertKey ,
417443 copyPaths : copyPaths ,
418- watchArray : watchArray
444+ watch : watch ,
445+ convert : convert ,
446+ convertKey : convertKey
419447}
0 commit comments