lordvlad
12/5/2013 - 4:23 PM

Observable.js

(function(e){function t(e){return function(t){return t.__event!==e}}function n(e){return function(t){return e!==t}}function r(e,t){return e.filter(function(e){return t.indexOf(e)===-1})}function i(e,t){var n;return function(){clearTimeout(n);n=setTimeout(t,e)}}function s(e,r){function s(e){return arguments.length?f(e):a()}function a(){var t;if(u&&(t=u.__detected))t.push(s);l("get");return e}function f(t){var n=e;e=t;l("change",{oldValue:n,newValue:t});return s}function l(e,t){i.filter(function(t){return t.__event===e}).forEach(function(e){e.call(null,t)});return s}function c(e,t){t.__event=e;i.push(t);l("addListener",t);return s}function h(e){i=i.filter(e.match?t(e):n(e));l("removeListener",e);return s}e=e||null;if(e&&e.call)return new u(e,r);if(e&&e.push)return new o(e,r);var i=[];s.get=a;s.set=f;s.emit=s.trigger=l;s.on=s.addEventListener=c;s.off=s.removeEventListener=h;s.listeners=function(){return i};return s}function o(e,i){function o(e){return arguments.length?f(e):a()}function a(){var t;if(u&&(t=u.__detected))t.push(o);l("get");return e}function f(t){if(!t.push)throw new Error("Cannot replace array with some other variable type");var n=e;e=t;l("change",{oldValue:n,newValue:t,added:t,deleted:n});return o}function l(e,t){s.filter(function(t){return t.__event===e}).forEach(function(e){e.call(null,t)});return o}function c(e,t){t.__event=e;s.push(t);l("addListener",t);return o}function h(e){s=s.filter(e.match?t(e):n(e));l("removeListener",e);return o}function p(){var t=[].slice.call(e),n=e.pop();l("change",{oldValue:t,newValue:e,deleted:n});return n}function d(){var t=[].slice.call(e),n=e.shift();l("change",{oldValue:t,newValue:e,deleted:n});return n}function v(){var t=[].slice.call(e),n=[].slice.call(arguments);[].push.apply(e,n);l("change",{oldValue:t,newValue:e,added:n});return o}function m(){var t=[].slice.call(e),n=[].slice.call(arguments);[].unshift.apply(e,n);l("change",{oldValue:t,newValue:e,added:n});return o}function g(){var t=[].slice.call(e),n=[].slice.call(arguments);[].splice.apply(e,n);l("change",{oldValue:t,newValue:e,added:r(e,t),deleted:r(t,e)});return o}function y(){var t=[].slice.call(e),n=[].slice.call(arguments);e=[].sort.apply(e,n);l("change",{oldValue:t,newValue:e});return o}function b(){var t=[].slice.call(e),n=[].slice.call(arguments);e=[].reverse.apply(e,n);l("change",{oldValue:t,newValue:e});return o}function w(){var t=[].slice.call(e),n=[].slice.call(arguments);e=[].filter.apply(e,n);l("change",{oldValue:t,newValue:e,deleted:r(t,e)});return o}e=e||[];var s=[];o.get=a;o.set=f;o.emit=o.trigger=l;o.on=o.addEventListener=c;o.off=o.removeEventListener=h;o.pop=p;o.shift=d;o.push=v;o.unshift=m;o.filter=w;o.listeners=function(){return s};return o}function u(e,r){function f(){return c()}function l(){var t=a;a=e();h("change",{oldValue:t,newValue:a});return f}function c(){var e;if(u&&(e=u.__detected))e.push(f);h("get");return a}function h(e,t){o.filter(function(t){return t.__event===e}).forEach(function(e){e.call(null,t)});return f}function p(e,t){t.__event=e;o.push(t);h("addListener",t);return f}function d(e){o=o.filter(e.match?t(e):n(e));h("removeListener",e);return f}function v(){s.forEach(function(e){e.on("change",r.debounce?i(r.debounce,l):l)})}function m(){s.forEach(function(e){e.off(l)})}if(!e||!e.call)throw new Error("Computed needs a computing function!");var s=[],o=[],a;f.get=c;f.emit=f.trigger=h;f.on=f.addEventListener=p;f.off=f.removeEventListener=d;f.update=l;f.destroy=m;f.listeners=function(){return o};u.__detected=[];l();s=a.__dependencies=u.__detected;delete u.__detected;v();return f}e.Computed=u;e.Observable=s;e.ObservableArray=o;e.O=function(e,t){return new s(e,t)}})(window)
(function(x){

    function filEvent( e )  { return function(l){ return l.__event !== e; } }
    function filFunct( f )  { return function(l){ return f !== l; } }
    function diff(a,b)      { return a.filter(function(n){return b.indexOf(n) === -1; }); }
    function debounce( t,f ){ var x; return function(){ clearTimeout(x); x = setTimeout(f,t); }}


    function Observable( val, o ){

        val = val || null;

        if ( val && val.call ) return new Computed( val, o );
        if ( val && val.push ) return new ObservableArray( val, o );

        var listeners = [];

        function obs( v )      { return arguments.length ? set( v ) : get(); }

        function get()         { var d; if (Computed && (d = Computed.__detected) ) d.push(obs); emit('get'); return val; }
        function set( v )      { var o = val; val = v; emit('change', {oldValue : o, newValue : v}); return obs; }
        function emit( e, d )  { listeners.filter(function(l){ return l.__event===e;}).forEach(function(l){ l.call( null, d );}); return obs; }
        function on( e, l )    { l.__event = e; listeners.push( l ); emit('addListener', l ); return obs; }
        function off( e )      { listeners = listeners.filter(e.match ? filEvent(e) : filFunct(e)); emit('removeListener', e ); return obs; }

        obs.get  = get;
        obs.set  = set;
        obs.emit = obs.trigger = emit;
        obs.on   = obs.addEventListener = on;
        obs.off  = obs.removeEventListener = off;
        obs.isObservable = obs.isSimpleObservable = true;

        return obs;
    }
    function ObservableArray( arr, o ){

        arr = arr || [];

        var listeners = [];


        function obs( v )      { return arguments.length ? set( v ) : get(); }

        function get()         { var d; if (Computed && (d = Computed.__detected) ) d.push(obs); emit('get'); return arr; }
        function set( v )      { if ( !v.push ) throw new Error( "Cannot replace array with some other variable type" );
                                 var o = arr; arr = v; emit('change',{oldValue:o,newValue:v,added:v,deleted:o}); return obs;
                               }
        function emit( e, d )  { listeners.filter(function(l){ return l.__event===e;}).forEach(function(l){ l.call( null, d );}); return obs; }
        function on( e, l )    { l.__event = e; listeners.push( l ); emit('addListener', l ); return obs; }
        function off( e )      { listeners = listeners.filter(e.match ? filEvent(e) : filFunct(e)); emit('removeListener', e ); return obs; }

        function pop()         { var o = [].slice.call(arr), a = arr.pop(); emit('change',{oldValue:o,newValue:arr,deleted:[a]}); return a; }
        function shift()       { var o = [].slice.call(arr), a = arr.shift(); emit('change', {oldValue:o,newValue:arr,deleted:[a]}); return a; }

        function push()        { var o = [].slice.call(arr), a = [].slice.call(arguments); [].push.apply(arr,a); emit('change',{oldValue:o,newValue:arr,added:a});return obs; }
        function unshift()     { var o = [].slice.call(arr), a = [].slice.call(arguments); [].unshift.apply(arr,a); emit('change',{oldValue:o,newValue:arr,added:a}); return obs; }
        function splice()      { var o = [].slice.call(arr), a = [].slice.call(arguments); [].splice.apply(arr,a);
                                 emit('change',{oldValue:o,newValue:arr,added:diff(arr,o),deleted:diff(o,arr)}); return obs; }
        function sort()        { var o = [].slice.call(arr), a = [].slice.call(arguments); arr = [].sort.apply(arr,a); emit('change', {oldValue:o, newValue:arr}); return obs; }
        function reverse()     { var o = [].slice.call(arr), a = [].slice.call(arguments); arr = [].reverse.apply(arr,a); emit('change', {oldValue:o, newValue:arr}); return obs; }
        function filter()      { var o = [].slice.call(arr), a = [].slice.call(arguments); arr = [].filter.apply(arr,a);
                                 emit('change', {oldValue:o, newValue:arr,deleted:diff(o,arr)}); return obs; }


        obs.get     = get;
        obs.set     = set;
        obs.emit    = obs.trigger = emit;
        obs.on      = obs.addEventListener = on;
        obs.off     = obs.removeEventListener = off;
        obs.pop     = pop;
        obs.shift   = shift;
        obs.push    = push;
        obs.unshift = unshift;
        obs.filter  = filter;
        obs.isObservable = obs.isObservableArray = true;

        return obs;

    }


    function Computed( fn, o ){
        if (!fn || !fn.call) throw new Error( "Computed needs a computing function!" );

        var deps = [], listeners = [], val, o = o || {};

        function obs()        { return get(); }

        function update()     { var o = val; val = fn(); emit('change', {oldValue: o, newValue: val}); return obs; }
        function get()        { var d; if (Computed && (d = Computed.__detected) ) d.push(obs); emit('get'); return val; }
        function emit( e, d ) { listeners.filter(function(l){ return l.__event===e;}).forEach(function(l){ l.call( null, d );}); return obs; }
        function on( e, l )   { l.__event = e; listeners.push( l ); emit('addListener', l ); return obs; }
        function off( e )     { listeners = listeners.filter(e.match ? filEvent(e) : filFunct(e)); emit('removeListener', e ); return obs; }
        function attach()     { deps.forEach(function(obs){ obs.on('change', o.debounce ? debounce(o.debounce,update) : update); }); }
        function detach()     { deps.forEach(function(obs){ obs.off(update); }); }

        obs.get     = get;                                         // add functions
        obs.emit    = obs.trigger = emit;
        obs.on      = obs.addEventListener = on;
        obs.off     = obs.removeEventListener = off;
        obs.update  = update;
        obs.destroy = detach;
        obs.isObservable = obs.isComputedObservable = true;

        Computed.__detected = [];                                 // prepare dependency detection
        update();                                                 // start dependency detection
        deps = Computed.__detected;          // evaluate dependency detection
        delete Computed.__detected;                               // clean up dependency detection

        attach(); // set up listeners

        return obs;
    }


    x.Computed = Computed;
    x.Observable = Observable;
    x.ObservableArray = ObservableArray;
    x.O = function(v,o){ return new Observable(v,o); };

}(window));