/*!
 * doTimeout - v0.3 - 6/25/2009
 * http://benalman.com/projects/jquery-dotimeout-plugin/
 * 
 * Copyright (c) 2009 "Cowboy" Ben Alman
 * Licensed under the MIT license
 * http://benalman.com/about/license/
 */

// Script: doTimeout
//
// Version: 0.3, Date: 6/25/2009
// 
// Tested with jQuery 1.3.2 in Internet Explorer 6-8, Firefox 3, Safari 3-4,
// Chrome, Opera 9.
// 
// Home       - http://benalman.com/projects/jquery-dotimeout-plugin/
// Source     - http://benalman.com/code/javascript/jquery/jquery.ba-do-timeout.js
// (Minified) - http://benalman.com/code/javascript/jquery/jquery.ba-do-timeout.min.js (0.7kb)
// Examples   - http://benalman.com/code/test/jquery-do-timeout/
// 
// 
// About: License
// 
// Copyright (c) 2009 "Cowboy" Ben Alman
// 
// Licensed under the MIT license
// 
// http://benalman.com/about/license/
// 
// Topic: What this plugin does
// 
// It's not uncommon to need to delay execution of something, but then continue
// to delay that execution until the triggering event or events has stopped for
// a certain amount of time. This plugin makes doing that easy. Just think of 
// setTimeout, but with additional management options.

(function($){
  '$:nomunge'; // Used by YUI compressor.
  
  var cache = {},
    
    // Reused internal string.
    doTimeout = 'doTimeout',
    
    // A convenient shortcut.
    aps = Array.prototype.slice;
  
  // Method: jQuery.doTimeout
  // 
  // Initialize, cancel, or force execution of a callback after a delay. For any
  // given id:
  // 
  // * If delay and callback are passed, doTimeout is initialized, overriding
  //   any existing doTimeout. The callback will execute after the delay.
  // * If true is passed, that id's callback is executed immediately and
  //   synchronously, returning its value. If the callback has already executed,
  //   undefined is returned.
  // * If nothing is passed and the callback hasn't yet executed, the doTimeout
  //   is cleared, returning true. If the callback has already executed,
  //   undefined is returned.
  // 
  // Usage:
  // 
  //  jQuery.doTimeout( id, delay, callback [, arg ... ] );                  - -
  //  jQuery.doTimeout( id, true );                                          - -
  //  jQuery.doTimeout( id );                                                - -
  // 
  // Arguments:
  // 
  //  id - (String) The unique identifier for this doTimeout.
  //  delay - (Number) A zero-or-greater delay in milliseconds after which
  //    callback will be executed. 
  //  callback - (Function) A function to be executed after delay milliseconds.
  // 
  // Note: any additional arguments will be passed into callback when it is
  // executed.
  // 
  // Returns:
  // 
  //  Depends on the arguments passed, read above.
  
  $[doTimeout] = function() {
    return p_doTimeout.apply( window, [ 0 ].concat( aps.call( arguments ) ) );
  };
  
  // Method: jQuery.fn.doTimeout
  // 
  // Initialize, cancel, or force execution of a callback after a delay.
  // Operates like <jQuery.doTimeout>, but the passed callback executes in the
  // context of the jQuery collection of elements, and the id is stored as data
  // on the first element in that collection. For any given id:
  // 
  // * If delay and callback are passed, doTimeout is initialized, overriding
  //   any existing doTimeout. The callback will execute after the delay. The
  //   initial jQuery collection of elements is returned.
  // * If true is passed, that id's callback is executed immediately and
  //   synchronously, returning its value. If the callback has already executed,
  //   undefined is returned.
  // * If nothing is passed and the callback hasn't yet executed, the doTimeout
  //   is cleared, returning true. If the callback has already executed,
  //   undefined is returned.
  // 
  // Usage:
  // 
  //  jQuery('selector').doTimeout( id, delay, callback [, arg ... ] );      - -
  //  jQuery('selector').doTimeout( id, true );                              - -
  //  jQuery('selector').doTimeout( id );                                    - -
  // 
  // Arguments:
  // 
  //  id - (String) The unique identifier for this doTimeout. Because this is
  //    stored as jQuery data on the element
  //  delay - (Number) A zero-or-greater delay in milliseconds after which
  //    callback will be executed. 
  //  callback - (Function) A function to be executed after delay milliseconds.
  // 
  // Note: any additional arguments will be passed into callback when it is
  // executed.
  // 
  // Returns:
  // 
  //  Depends on the arguments passed, read above.
  
  $.fn[doTimeout] = function() {
    var args = aps.call( arguments ),
      result = p_doTimeout.apply( this, [ doTimeout + args[0] ].concat( args ) );
    
    return args[2] ? this : result;
  };
  
  function p_doTimeout( jquery_data_key, id, delay, callback ) {
    var that = this,
      elem,
      data,
      
      // Pass any additional arguments to the callback.
      args = arguments;
    
    if ( jquery_data_key ) { // Note: key is 'doTimeout' + id
      // Get id-object from the first element's data, otherwise initialize it to {}.
      elem = that.eq(0);
      elem.data( jquery_data_key, data = elem.data( jquery_data_key ) || {} );
      
    } else {
      // Get id-object from the cache, otherwise initialize it to {}.
      data = cache[ id ] || ( cache[ id ] = {} );
    }
    
    // Clear any existing timeout for this id.
    data.id && clearTimeout( data.id );
    
    // Clean up when necessary.
    function cleanup() {
      if ( jquery_data_key ) {
        elem.removeData( jquery_data_key );
      } else {
        delete cache[ id ];
      }
      return true;
    };
    
    if ( callback ) {
      // A callback (and delay) were specified. Store the callback reference for
      // possible later use, and then setTimeout.
      data.fn = function(){
        cleanup();
        return callback.apply( that, aps.call( args, 4 ) );
      };
      
      // Yes, there actually is a setTimeout call in here, somewhere!
      data.id = setTimeout( data.fn, delay );
      
    } else if ( data.fn ) {
      // No callback passed. If "delay" is true, execute the stored callback
      // immediately and return its value. If "delay" is false or undefined,
      // clean up and return true.
      return delay ? data.fn() : cleanup();
      
    } else {
      // Since no callback was passed, and data.fn isn't defined, it looks like
      // whatever was supposed to happen already did. Clean up and quit!
      cleanup();
    }
    
  };
  
})(jQuery);

