﻿/*
* jQuery Watch 1.2.0 -- http://plugins.jquery.com/project/watch
*
* Copyright (c) 2009 SolutionStream.com & Michael J. Ryan (http://tracker1.info/)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
* 
* ===============================================================================================
*
*	Provides an interface to objects matching the Mozilla JS (1.2) Object.prototype.watch 
*	method.  This uses a timer to determin changes, so rapidly changing items may miss 
*	some property changes.  This is a rough timer for some general use, not precision observer
*	patterns.
*
*	EXAMPLE:
*			function watchCallbackExample(propertyName, oldValue, newValue) {
*				alert("o." + propertyName + " changed from " + oldValue + " to " + newValue);
*			}
*			
*			//bind a watch event to a callback
*			jQuery(o).watch("someProperty", watchCallbackExample)
*				
*			//unbind a watch event from a callback
*			jQuery(o).unwatch("someProperty", watchCallbackExample);
*
*			//unbind all callbacks from a watched property
*			jQuery(o).unwatch("someProperty");
*
*			//unbind all callbacks for all watched properties
*			jQuery(o).unwatch();
*
* ===============================================================================================
*
*	1.0.0	Initial Release (watch only)
*	
*	1.2.0	Add unwatch methods
*			Remove internal use of Object.prototype.watch support unreliable in the latest FF3.5
*/
(function($) {
    //how often to rerun the genericWatchTimer below, increase this when debugging.
    var timerCycle = 100; //milliseconds (100 = 1/10 second)
    var watchedObjects = []; //objects to be watched
    var watchedCallbacks = []; //properties, prior values & callback methods
    var watchTimer = null;

    //jQuery extension method
    $.fn.watch = function(propertyName, callbackMethod) {
        for (var i = 0; i < this.length; i++) {
            var o = this[i];
            watchIt(o, propertyName, callbackMethod); //use timer based watch/check
        }
    }

    $.fn.unwatch = function(propertyName, callbackMethod) {
        for (var i = 0; i < this.length; i++) {
            var o = this[i];
            unwatchIt(o, propertyName, callbackMethod); //use timer based watch/check
        }
    }

    //add an object/property/callback item to the list.
    function watchIt(obj, propertyName, callbackMethod) {

        propertyName = String(propertyName); //force to string

        //object must exist, have a propertyName property and the callback must be a function
        if (!(obj && propertyName in obj && typeof callbackMethod == "function"))
            return; //nothing to do/add

        var item, props, prop, oldval, newval;
        for (var i = 0; i < watchedObjects.length; i++) {
            if (obj != watchedCallbacks[i]) continue;

            item = watchedObjects[i];
            props = watchedCallbacks[i];

            for (var j = 0; j < props.length; i++) {
                prop = props[j];
                if (prop.name != propertyName) continue;
                prop.callbacks.push(callbackMethod);
                return; //found prop match
            }

            //no prop match, insert one
            props.push({ "name": propertyName, oldValue: obj[propertyName], callbacks: [callbackMethod] })
            return;
        }

        //no object match found
        watchedObjects.push(obj);
        watchedCallbacks.push([{ "name": propertyName, oldValue: obj[propertyName], callbacks: [callbackMethod]}]);
    }

    //remove an object/property/callback item (nestable) from the list.
    //	null for propertyName and/or callbackMethod will be a wildcard match.
    function unwatchIt(obj, propertyName, callbackMethod) {
        var props, remprops, prop, pname;
        for (var i = 0; i < watchedObjects.length; i++) {
            if (obj == watchedObjects[i]) {
                props = watchedCallbacks[i];
                remprops = [];
                for (var j = 0; j < props.length; j++) {
                    if (!propertyName || propertyName == props[j].name) {
                        prop = props[j];
                        for (var k = 0; k < prop.callbacks.length; k++) {
                            var cb = prop.callbacks[k];
                            if (!callbackMethod || cb == callbackMethod) {
                                prop.callbacks.splice(k, 1);
                                k--;
                            }
                        }
                        if (!prop.callbacks.length)
                            remprops.push(prop.name);
                    }
                }

                //remove unneeded properties from the watch list
                remitems:
                while (remprops.length) {
                    pname = remprops.pop();
                    for (var j = 0; j < props.length; j++) {
                        if (props[j].name == pname) {
                            props.splice(j, 1);
                            continue remitems;
                        }
                    }
                }

                //remove unneeded objects from the watch list
                if (props.length == 0) {
                    watchedObjects.splice(i, 1);
                    watchedCallbacks.splice(i, 1);
                }

                return; //done with match
            }
        }
    }


    //watch handler for browsers without a watch
    function wind() { //wind the watch
        watchTimer = window.setTimeout(genericWatchTimer, timerCycle);
    }
    function genericWatchTimer() {
        /**************************************************
        NOTE: IF YOU ARE DEBUGGING, YOU MAY WANT TO 
        INCREASE THE timerCycle VARIABLE ABOVE
        **************************************************/
        var item, props, prop, oldval, newval;
        for (var i = 0; i < watchedObjects.length; i++) {
            item = watchedObjects[i];
            props = watchedCallbacks[i];
            for (var j = 0; j < props.length; j++) {
                prop = props[j];
                newval = item[prop.name];
                if (prop.oldValue != newval || typeof (prop.oldValue) != typeof (newval)) {
                    for (var k = 0; k < prop.callbacks.length; k++) {
                        prop.callbacks[k].call(item, prop.name, prop.oldValue, newval);
                        prop.oldValue = newval;
                    }
                }
            }
        }
        wind();
    }
    wind();


})(jQuery);

