Last active
December 26, 2015 18:28
-
-
Save Rich-Harris/7194138 to your computer and use it in GitHub Desktop.
Utility for making observable objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Usage | |
// ===== | |
// | |
// Create an observable object: | |
// | |
// obj = makeObservable(); | |
// obj.set( 'foo', 'bar' ); | |
// | |
// observer = obj.observe( 'foo', function ( newFoo, oldFoo ) { | |
// alert( 'foo changed from ' + oldFoo + ' to ' + newFoo ); // alerts 'foo changed from undefined to bar' | |
// }); | |
// | |
// obj.set( 'foo', 'baz' ); // alerts 'foo changed from bar to baz' | |
// obj.set( 'foo', 'baz' ); // does nothing | |
// | |
// obj.get( 'foo' ); // 'baz' | |
// | |
// observer.cancel(); | |
// obj.set( 'foo', 'bop' ); // doesn't alert, we cancelled the observer | |
// | |
// obj.get( 'foo' ); // 'bop' | |
// | |
// Or take an existing object and make it observable (will overwrite any existing | |
// properties named `_attributes`, `_callbacks`, `set`, `get` or `observe`) | |
// | |
// makeObserveable( someObj ); | |
(function ( global ) { | |
'use strict'; | |
var isEqual, observable, makeObservable; | |
isEqual = function ( a, b ) { | |
// null is a special case as typeof null === 'object' | |
if ( a === null && b === null ) { | |
return true; | |
} | |
// if we're dealing with objects, return false as we don't know | |
// whether the contents have changed without doing a deep equality | |
// check. | |
if ( typeof a === 'object' || typeof b === 'object' ) { | |
return false; | |
} | |
return a === b; | |
}; | |
observable = { | |
set: function ( key, value ) { | |
var map, oldValue, callbacks, i, len; | |
if ( typeof key === 'object' ) { | |
map = key; | |
for ( key in map ) { | |
if ( map.hasOwnProperty( key ) ) { | |
this.set( key, map[ key ] ); | |
} | |
} | |
return; | |
} | |
oldValue = this.get( key ); | |
if ( !isEqual( value, oldValue ) ) { | |
this._attributes[ key ] = value; | |
// notify observers | |
if ( !( callbacks = this._callbacks[ key ] ) ) { | |
return; | |
} | |
if ( callbacks.active ) { | |
return; // prevent infinite loops | |
} | |
callbacks.active = true; | |
len = callbacks.length; | |
for ( i = 0; i < len; i += 1 ) { | |
callbacks[i]( value, oldValue ); | |
} | |
callbacks.active = false; | |
} | |
return this; | |
}, | |
get: function ( key ) { | |
return this._attributes[ key ]; | |
}, | |
observe: function ( key, callback, options ) { | |
var map, callbacks, observers, i; | |
if ( typeof key === 'object' ) { | |
map = key; | |
options = callback; | |
observers = []; | |
for ( key in map ) { | |
if ( map.hasOwnProperty( key ) ) { | |
observers[ observers.length ] = this.observe( key, map[ key ], options ); | |
} | |
} | |
return { | |
cancel: function () { | |
i = observers.length; | |
while ( i-- ) { | |
observers[i].cancel(); | |
} | |
} | |
}; | |
} | |
callbacks = this._callbacks[ key ] || ( this._callbacks[ key ] = [] ); | |
callbacks[ callbacks.length ] = callback; | |
if ( !options || options.init === false ) { | |
callback( this.get( key ) ); | |
} | |
return { | |
cancel: function () { | |
var index = callbacks.indexOf( callback ); | |
if ( index !== -1 ) { | |
callbacks.splice( index, 1 ); | |
} | |
} | |
}; | |
} | |
}; | |
makeObservable = function ( object ) { | |
object = object || {}; | |
object._attributes = {}; | |
object._callbacks = {}; | |
object.set = observable.set; | |
object.get = observable.get; | |
object.observe = observable.observe; | |
return object; | |
}; | |
// export as CommonJS module... | |
if ( typeof module !== 'undefined' && module.exports ) { | |
module.exports = makeObservable; | |
} | |
// ... or as AMD module... | |
else if ( typeof define !== 'undefined' && define.amd ) { | |
define( function () { return makeObservable; }); | |
} | |
// ... or as browser global | |
else { | |
global.makeObservable = makeObservable; | |
} | |
}( typeof window !== 'undefined' ? window : this )); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment