Preparation Code Preparation HTML (this will be inserted in the <body>
of a valid HTML5 document in standards mode) (useful when testing DOM operations or including libraries)
Setup JS if (!Array .prototype .find ) {
Array .prototype .find = function (callback, thisArg ) {
'use strict' ;
var arr = this ,
arrLen = arr.length ,
i;
for (i = 0 ; i < arrLen; i += 1 ) {
if (callback.call (thisArg, arr[i], i, arr)) {
return arr[i];
}
}
return undefined ;
};
}
var gms = {};
gms.Observer = function ( object, property, callback, context, options ) {
if ( typeof object !== 'object' ) {
return ;
}
if ( ! ( this instanceof gms.Observer )) {
return new gms.Observer ( object, property, callback, context, options );
}
this .object = object;
this .property = property;
this .callback = callback;
this .context = context;
this .options = options || {};
this .sync = ( options ) ? options.sync : false ;
this .observeFunction = ( this .sync ) ? gms.Observe .propertySync : gms.Observe .property ;
if ( this .sync ) {
this .useNative = false ;
} else {
this .useNative = ( options && options.useNative !== undefined ) ? options.useNative : Object .observe !== undefined ;
}
var observer = this ._observerAlreadyAttached ();
if ( observer && observer.length > 0 ){
return observer;
}
if ( property.indexOf ( '.' ) > -1 ) {
return this ._observeChildren ( );
}
this ._observingArray = (
object[ property ] instanceof Array ||
object instanceof Array && property.indexOf ( '@each' ) === 0
);
this ._attachObservers ();
return this ._attachCallbackFunction ();
};
gms.Observer .prototype = {
_attachObservers : function ( ) {
if ( this ._observingArray ){
if ( ! ( this .property .indexOf ( '@each' === 0 ) && this .object instanceof Array ) ) {
this .object = this .object [ this .property ];
}
if ( this .useNative ) {
this ._addArrayObjectObserver ( this .object );
}
else {
this ._addArrayMethods ( this .object );
this ._addSetters ( );
}
} else {
if ( this .useNative ) {
this ._addObjectObserver ();
}
else {
this ._addSetters ( );
}
}
},
_observeArray : function ( object, property, eachProp ) {
var array = ( property === '@each' ) ? object : object[ property ],
observers = [];
var observer = this .observeFunction .call ( gms.Observe , object, property, this .callback , this .context , this .options );
observers.push ( observer );
if ( eachProp ) {
this ._addEachProperties ( array, eachProp );
array.__eachObservers__ [ eachProp ].push ( this ._createObserver () );
var eachObservers = [];
array.forEach ( function ( item ) {
eachObservers.push (
this .observeFunction .call ( gms.Observe , item, eachProp, this .callback , this .context , this .options )
);
}, this );
observers = observers.concat ( eachObservers );
}
return observers;
},
_addEachProperties : function ( array, eachProp ) {
if ( ! array.__eachObservers__ ) {
Object .defineProperty ( array, '__eachObservers__' , {
enumerable : true ,
writable : true ,
value : {}
} );
}
if ( ! array.__eachObservers__ [ eachProp ] ) {
Object .defineProperty ( array.__eachObservers__ , eachProp, {
enumerable : true ,
writable : true ,
value : []
} );
}
},
_attachCallbackFunction : function ( ) {
var __observersValue__ = ( this ._observingArray ) ? [] : {};
if ( this .object .__observers__ === undefined ) {
Object .defineProperty ( this .object , '__observers__' , {
enumerable : false ,
value : __observersValue__,
writable : true
} );
}
if ( !this ._observingArray && this .object .__observers__ [ this .property ] === undefined ) {
var observers = this .object .__observers__ ;
Object .defineProperty ( observers, this .property , {
enumerable : true ,
value : [],
writable : true
} );
}
var observer = this ._createObserver ();
if ( !this ._observingArray ){
this .object .__observers__ [ this .property ].push ( observer );
} else {
this .object .__observers__ .push ( observer );
}
return observer;
},
_observeChildren : function ( object, properties ) {
object = object || this .object ;
var parts = ( properties ) ? properties.split ( '.' ) : this .property .split ( '.' ),
property = parts.shift (),
observers = [], observer;
var observeLastProperty = this .options .observeLastProperty ;
if ( parts[ 0 ] === '@each' || property === '@each' ) {
observer = this ._addEachObservers ( object, property, parts );
}
else if ( property === '@any' ) {
return this ._addAnyObservers ( parts.join ( '.' ) );
}
else {
object[ property ] = object[ property ] || {};
observer = this .observeFunction .call ( gms.Observe , object, property, this .callback , this .context , this .options );
}
observers.push ( observer );
if ( parts.length > 1 ){
var childObservers = this ._observeChildren ( object[ property ], parts.join ('.' ) );
observers = observers.concat ( childObservers );
}
else {
if ( property !== '@any' ) {
object = object[ property ];
}
property = parts[ 0 ];
var optionsClone = ( this .options !== undefined ) ? goog.object .clone ( this .options ) : { };
if ( observeLastProperty ){
optionsClone.lastProperty = true ;
}
observer = this .observeFunction .call ( gms.Observe , object, parts.join ( '.' ), this .callback , this .context , optionsClone );
observers.push ( observer );
}
return observers;
},
_addEachObservers : function ( object, property, parts ) {
var eachObserver = ( parts[ 0 ] === '@each' ) ? parts.slice ( 1 ).join ( '.' ) : parts.join ( '.' );
object[ property ] = object[ property ] || [];
var observer = this ._observeArray ( object, property, eachObserver );
if ( parts[ 0 ] === '@each' ){
parts.splice ( 0 , 1 );
}
return observer;
},
_addAnyObservers : function ( property ) {
var observers = [];
if ( this .sync && ! this .options .ignoreAnySyncWarning ){
console .warn ( '----------------' );
console .log ( 'You are synchronously observing @any on an object.' );
console .log ( 'Your callback will fire when any property currently defined on the object changes' );
console .log ( 'But will not fire when new properties are added.' );
console .log ( 'To acheive both, you must observe @any asynchronously.' );
console .log ( 'To suppress this message pass ignoreAnySyncWarning: true as an Observer options argument' );
console .warn ( '----------------' );
}
for ( var prop in this .object ) {
var observer = this .observeFunction .call ( gms.Observe , this .object , prop, this .callback , this .context , this .options );
observers.push ( observer );
if ( property !== '' && property !== '@any' ) {
observer = this .observeFunction .call ( gms.Observe , this .object [ prop ], property, this .callback , this .context , this .options );
observers.push ( observer );
}
}
return observers;
},
_createObserver : function ( ) {
return {
callback : this .callback ,
context : this .context ,
sync : this .sync ,
object : this .object ,
property : this .property ,
nativeObserve : this .useNative ,
options : this .options
};
},
_createChange : function ( object, property, oldValue, args ) {
return {
type : 'update' ,
object : object,
name : property,
oldValue : oldValue,
args : args,
currentValue : object[ property ]
};
},
_createArrayChange : function ( array, item, type, oldValue, args ) {
var change = {
type : type,
object : array,
index : array.indexOf ( item ),
oldValue : oldValue,
currentValue : array[ array.indexOf ( item ) ],
args : args
};
if ( type === 'delete' ) {
change.oldValue = item;
}
return change;
},
_cloneNativeChange : function ( change, args ) {
return {
type : 'update' ,
object : change.object ,
name : change.name ,
oldValue : change.oldValue ,
args : args,
currentValue : change.object [ change.name ]
};
},
_cloneNativeArrayChange : function ( change, args ) {
return {
type : change.type ,
object : change.object ,
index : change.name ,
oldValue : change.oldValue ,
args : args,
currentValue : change.object [ change.name ]
};
},
_addObjectObserver : function ( ) {
var observedProperty = this .property ,
_this = this ;
Object .observe ( this .object , function ( changes ) {
changes = changes.filter ( function ( c ) {
return [ 'add' , 'update' , 'delete' ].indexOf ( c.type ) > -1 ;
});
_this._objectObserverCallback ( changes, observedProperty );
});
},
_addArrayObjectObserver : function ( array ) {
var _this = this ;
Object .observe ( array, function ( change ) {
_this._arrayObjectObserverCallback ( change );
});
},
_objectObserverCallback : function ( changed, observedProperty ) {
changed.forEach ( function ( change ) {
this ._forEachChangedObject ( change, observedProperty );
}, this );
},
_arrayObjectObserverCallback : function ( changed ) {
changed.forEach ( function ( change ) {
this ._forEachArrayChangedObject ( change );
}, this );
},
_forEachChangedObject : function ( change, observedProperty ) {
var property = observedProperty,
object = change.object ,
observers = object.__observers__ ;
if ( change.name === property || observedProperty === '@any' ) {
observers[ property ].forEach ( function ( observer ){
change = this ._cloneNativeChange ( change, observer.options );
gms.Observe ._onObservedPropChange ( observer, change );
}, this );
}
},
_forEachArrayChangedObject : function ( change ) {
var array = change.object ,
observers = array.__observers__ ;
if ( ! isNaN ( change.name ) ) {
this ._reattachEachObservers ( array, change.object [ change.name ] );
observers.forEach ( function ( observer ){
change = this ._cloneNativeArrayChange ( change, observer.options );
gms.Observe ._onObservedPropChange ( observer, change );
}, this );
}
},
_addSetters : function ( ){
var object = this .object ,
property = this .property ;
if ( property === '@any' ) {
return this ._addAnyObservers ( this .property );
}
if ( ! object.hasOwnProperty ( '__' + property + '__' ) ) {
this ._addPrivateProp ( object, property );
var setter = object.__lookupSetter__ ( property ),
_this = this ;
Object .defineProperty ( object, property, {
configurable : true ,
get : function ( ) {
return this [ '__' + property + '__' ];
},
set : function ( value ) {
var oldValue = this [ property ];
if ( value !== oldValue ) {
if ( setter ) {
setter ( value );
} else {
object[ '__' + property + '__' ] = value;
}
return _this._set .call ( _this, this , property, oldValue );
}
}
});
}
},
_set : function ( object, property, oldValue ) {
if ( object.__observers__ && object.__observers__ [ property ] ) {
object.__observers__ [ property ].forEach ( function ( observer ){
var changed = this ._createChange ( object, property, oldValue, observer.options );
gms.Observe ._onObservedPropChange ( observer, changed );
}, this );
}
},
_addArrayMethods : function ( array ) {
var _this = this ;
array.push = function ( ) {
var pushed = Array .prototype .push .apply ( this , arguments );
_this._onArrayUpdated ( this , 'add' , Array .prototype .slice .call ( arguments , [ this [ pushed ] ] ) );
return pushed;
};
array.splice = function ( ) {
var spliced = Array .prototype .splice .apply ( this , arguments );
if ( arguments [ 2 ] !== undefined ){
_this._onArrayUpdated ( this , 'add' , [ arguments [ 2 ] ] );
} else {
_this._onArrayUpdated ( this , 'delete' , spliced );
}
return spliced;
};
array.pop = function ( ) {
var popped = Array .prototype .pop .apply ( this , arguments );
_this._onArrayUpdated ( this , 'delete' , popped );
return popped;
};
array.sort = function ( ) {
var sorted = Array .prototype .sort .apply ( this , arguments );
_this._onArrayUpdated ( this , 'change' , sorted );
return sorted;
};
},
_onArrayUpdated : function ( array, type, changedItems ) {
if ( array['__observers__' ] ) {
if ( type === 'add' && array.__eachObservers__ ){
this ._reattachEachObservers ( array, changedItems );
}
for ( var i = 0 , l = array['__observers__' ].length ; i < l; i++ ) {
var observer = array['__observers__' ][ i ];
var changes = [];
if ( changedItems instanceof Array ) {
changedItems.forEach ( function ( item ) {
var change = this ._createArrayChange ( array, item, type, observer.options );
changes.push ( change );
}, this );
}
else {
changes.push ( this ._createArrayChange ( array, changedItems, type, observer.options ) );
}
gms.Observe ._onObservedPropChange ( observer, changes );
}
}
},
_reattachEachObservers : function ( array, items ) {
for ( var observedProp in array.__eachObservers__ ){
var observers = array.__eachObservers__ [ observedProp ];
observers.forEach ( function ( observer ){
if ( items instanceof Array ){
items.forEach ( function ( arrayItem ){
this .observeFunction .call ( gms.Observe , arrayItem, observedProp, observer.callback , observer.context , observer.args );
}, this );
}
else {
this .observeFunction .call ( gms.Observe , items, observedProp, observer.callback , observer.context , observer.args );
}
}, this );
}
},
_addPrivateProp : function ( object, property ) {
var getter = object.__lookupGetter__ ( property );
if ( getter ) {
Object .defineProperty ( object, '__' + property + '__' , {
enumerable : false ,
get : getter
});
}
else {
Object .defineProperty ( object, '__' + property + '__' , {
enumerable : false ,
value : object[ property ],
writable : true
});
}
},
_observerAlreadyAttached : function ( ) {
if ( this .object .__observers__ && this .object .__observers__ [ this .property ] ){
return this .object .__observers__ [ this .property ].filter ( function ( observer ) {
return this .callback === observer.callback && this .context === observer.context ;
}, this );
}
return false ;
}
};
gms.Observe = {
_queue : [],
property : function ( object, property, callback, context, options ) {
if ( typeof property !== 'string' ) {
if ( Array .isArray ( property ) ){
return this .properties .apply ( this , arguments );
}
throw 'You must pass a string to Observe.property' ;
}
return new gms.Observer ( object, property, callback, context, options );
},
propertySync : function ( object, property, callback, context, options ) {
options = options || {};
options.sync = true ;
return this .property ( object, property, callback, context, options );
},
properties : function ( object, properties, callback, context, options ) {
if ( ! ( properties instanceof Array ) ) {
throw 'You must pass an array to Observe.properties' ;
}
var observers = [];
properties.forEach ( function ( property ){
observers.push ( this .property ( object, property, callback, context, options ) );
}, this );
return observers;
},
propertiesSync : function ( object, properties, callback, context, options ) {
var observers = [];
properties.forEach ( function ( property ){
observers.push ( this .propertySync ( object, property, callback, context, options ) );
}, this );
return observers;
},
lastProperty : function ( object, property, callback, context, options ) {
options = options || {};
options.observeLastProperty = true ;
this .property ( object, property, callback, context, options );
},
lastPropertySync : function ( object, property, callback, context, options ) {
options = options || {};
options.observeLastProperty = true ;
this .propertySync ( object, property, callback, context, options );
},
stop : function ( observer ) {
if ( observer instanceof Array ) {
observer.forEach ( this ._removeObserver , this );
} else {
this ._removeObserver ( observer );
}
},
remove : function ( object, property, callback, context ) {
var observers = object.__observers__ ,
observersToRemove = observers[ property ].filter ( function ( o ) {
return o.callback === callback && o.context === context;
});
this .stop ( observersToRemove );
},
pause : function ( observer ) {
observer.paused = true ;
},
resume : function ( observer, fireLastChange ) {
observer.paused = false ;
if ( fireLastChange && observer.__lastChange__ ) {
this ._onObservedPropChange ( observer, observer.__lastChange__ );
}
delete observer.__lastChange__ ;
},
removeAllFromObject : function ( object ) {
for ( var prop in object.__observers__ ){
object.__observers__ [ prop ].forEach ( this ._removeObserver , this );
}
},
_pushObserverToQueue : function ( observer, changes ) {
var item = this ._queue .find ( function ( item ){
return item.callback === observer.callback && item.context === observer.context ;
} );
if ( item ) {
item.changes = item.changes .concat ( changes );
} else {
this ._queue .push ( {
callback : observer.callback ,
context : observer.context ,
changes : changes
});
}
window .clearTimeout ( this ._queueTimer );
this ._queueTimer = window .setTimeout ( this ._fireQueuedCallbacks .bind ( this ), 2 );
},
_fireQueuedCallbacks : function ( ) {
for ( var i = 0 , l = this ._queue .length ; i < l; i++ ) {
var item = this ._queue .shift ();
if ( item ) {
item.callback .call ( item.context , item.changes , item.options );
}
}
if ( this ._queue .length > 0 ) {
this ._fireQueuedCallbacks ();
}
},
_onObservedPropChange : function ( observer, change ) {
var changes = ( change instanceof Array ) ? change : [ change ];
this ._reattachOnChange ( changes );
if ( observer.paused ) {
observer.__lastChange__ = change;
return ;
}
if ( observer.options && observer.options .observeLastProperty && ! observer.options .lastProperty ) {
return ;
}
if ( observer.sync ){
observer.callback .call ( observer.context , changes, observer.options );
} else {
gms.Observe ._pushObserverToQueue ( observer, changes );
}
},
_reattachOnChange : function ( changes ) {
changes.forEach ( function ( change ){
this ._reattachChildObservers ( change.object , change.name , change.oldValue );
}, this );
},
_reattachChildObservers : function ( object, property, oldValue ) {
if ( typeof object !== 'object' || object === null ) {
return ;
}
if ( !oldValue || oldValue.__observers__ === undefined ) {
return ;
}
var observersToRemove = [];
for ( var observedProp in oldValue.__observers__ ) {
var observers = oldValue.__observers__ [ observedProp ];
observers.forEach ( function ( observer ) {
if ( observer.context === oldValue ) {
observer.context = object[ property ];
}
var observeFunction = ( observer.sync ) ? this .propertySync : this .property ;
observeFunction.call ( this , object[ property ], observedProp, observer.callback , observer.context );
observersToRemove.push ( observer );
}, this );
this ._reattachChildObservers ( object[ property ], observedProp, oldValue[ observedProp ] );
}
observersToRemove.forEach ( this ._removeObserver , this );
},
_removeObserver : function ( observer ) {
if ( ! observer ) {
return ;
}
var object = observer.object ,
property = observer.property ;
if ( ! object ) {
return ;
}
var observerIndex = object.__observers__ [ property ].findIndex ( function ( o ) {
return o === observer;
});
object.__observers__ [ property ].splice ( observerIndex, 1 );
var hasNoKeys = Object .keys ( object.__observers__ ).length === 0 ;
if ( hasNoKeys ) {
if ( observer.nativeObserve ) {
Object .unobserve ( observer.object , observer.callback );
}
}
},
};
var object = {
a : 1 ,
b : 2 ,
c : 3 ,
d : 4 ,
e : 5
};
var changes;
var callbackFunction = function ( changes ) {
changes = changes;
console .log ( changes );
debugger ;
deferred.resolve ();
}
Teardown JS