|
| 1 | +/*! |
| 2 | + * jQuery doTimeout: Like setTimeout, but better! - v1.0 - 3/3/2010 |
| 3 | + * http://benalman.com/projects/jquery-dotimeout-plugin/ |
| 4 | + * |
| 5 | + * Copyright (c) 2010 "Cowboy" Ben Alman |
| 6 | + * Dual licensed under the MIT and GPL licenses. |
| 7 | + * http://benalman.com/about/license/ |
| 8 | + */ |
| 9 | + |
| 10 | +// Script: jQuery doTimeout: Like setTimeout, but better! |
| 11 | +// |
| 12 | +// *Version: 1.0, Last updated: 3/3/2010* |
| 13 | +// |
| 14 | +// Project Home - http://benalman.com/projects/jquery-dotimeout-plugin/ |
| 15 | +// GitHub - http://github.com/cowboy/jquery-dotimeout/ |
| 16 | +// Source - http://github.com/cowboy/jquery-dotimeout/raw/master/jquery.ba-dotimeout.js |
| 17 | +// (Minified) - http://github.com/cowboy/jquery-dotimeout/raw/master/jquery.ba-dotimeout.min.js (1.0kb) |
| 18 | +// |
| 19 | +// About: License |
| 20 | +// |
| 21 | +// Copyright (c) 2010 "Cowboy" Ben Alman, |
| 22 | +// Dual licensed under the MIT and GPL licenses. |
| 23 | +// http://benalman.com/about/license/ |
| 24 | +// |
| 25 | +// About: Examples |
| 26 | +// |
| 27 | +// These working examples, complete with fully commented code, illustrate a few |
| 28 | +// ways in which this plugin can be used. |
| 29 | +// |
| 30 | +// Debouncing - http://benalman.com/code/projects/jquery-dotimeout/examples/debouncing/ |
| 31 | +// Delays, Polling - http://benalman.com/code/projects/jquery-dotimeout/examples/delay-poll/ |
| 32 | +// Hover Intent - http://benalman.com/code/projects/jquery-dotimeout/examples/hoverintent/ |
| 33 | +// |
| 34 | +// About: Support and Testing |
| 35 | +// |
| 36 | +// Information about what version or versions of jQuery this plugin has been |
| 37 | +// tested with, what browsers it has been tested in, and where the unit tests |
| 38 | +// reside (so you can test it yourself). |
| 39 | +// |
| 40 | +// jQuery Versions - 1.3.2, 1.4.2 |
| 41 | +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1. |
| 42 | +// Unit Tests - http://benalman.com/code/projects/jquery-dotimeout/unit/ |
| 43 | +// |
| 44 | +// About: Release History |
| 45 | +// |
| 46 | +// 1.0 - (3/3/2010) Callback can now be a string, in which case it will call |
| 47 | +// the appropriate $.method or $.fn.method, depending on where .doTimeout |
| 48 | +// was called. Callback must now return `true` (not just a truthy value) |
| 49 | +// to poll. |
| 50 | +// 0.4 - (7/15/2009) Made the "id" argument optional, some other minor tweaks |
| 51 | +// 0.3 - (6/25/2009) Initial release |
| 52 | + |
| 53 | +(function($){ |
| 54 | + '$:nomunge'; // Used by YUI compressor. |
| 55 | + |
| 56 | + var cache = {}, |
| 57 | + |
| 58 | + // Reused internal string. |
| 59 | + doTimeout = 'doTimeout', |
| 60 | + |
| 61 | + // A convenient shortcut. |
| 62 | + aps = Array.prototype.slice; |
| 63 | + |
| 64 | + // Method: jQuery.doTimeout |
| 65 | + // |
| 66 | + // Initialize, cancel, or force execution of a callback after a delay. |
| 67 | + // |
| 68 | + // If delay and callback are specified, a doTimeout is initialized. The |
| 69 | + // callback will execute, asynchronously, after the delay. If an id is |
| 70 | + // specified, this doTimeout will override and cancel any existing doTimeout |
| 71 | + // with the same id. Any additional arguments will be passed into callback |
| 72 | + // when it is executed. |
| 73 | + // |
| 74 | + // If the callback returns true, the doTimeout loop will execute again, after |
| 75 | + // the delay, creating a polling loop until the callback returns a non-true |
| 76 | + // value. |
| 77 | + // |
| 78 | + // Note that if an id is not passed as the first argument, this doTimeout will |
| 79 | + // NOT be able to be manually canceled or forced. (for debouncing, be sure to |
| 80 | + // specify an id). |
| 81 | + // |
| 82 | + // If id is specified, but delay and callback are not, the doTimeout will be |
| 83 | + // canceled without executing the callback. If force_mode is specified, the |
| 84 | + // callback will be executed, synchronously, but will only be allowed to |
| 85 | + // continue a polling loop if force_mode is true (provided the callback |
| 86 | + // returns true, of course). If force_mode is false, no polling loop will |
| 87 | + // continue, even if the callback returns true. |
| 88 | + // |
| 89 | + // Usage: |
| 90 | + // |
| 91 | + // > jQuery.doTimeout( [ id, ] delay, callback [, arg ... ] ); |
| 92 | + // > jQuery.doTimeout( id [, force_mode ] ); |
| 93 | + // |
| 94 | + // Arguments: |
| 95 | + // |
| 96 | + // id - (String) An optional unique identifier for this doTimeout. If id is |
| 97 | + // not specified, the doTimeout will NOT be able to be manually canceled or |
| 98 | + // forced. |
| 99 | + // delay - (Number) A zero-or-greater delay in milliseconds after which |
| 100 | + // callback will be executed. |
| 101 | + // callback - (Function) A function to be executed after delay milliseconds. |
| 102 | + // callback - (String) A jQuery method to be executed after delay |
| 103 | + // milliseconds. This method will only poll if it explicitly returns |
| 104 | + // true. |
| 105 | + // force_mode - (Boolean) If true, execute that id's doTimeout callback |
| 106 | + // immediately and synchronously, continuing any callback return-true |
| 107 | + // polling loop. If false, execute the callback immediately and |
| 108 | + // synchronously but do NOT continue a callback return-true polling loop. |
| 109 | + // If omitted, cancel that id's doTimeout. |
| 110 | + // |
| 111 | + // Returns: |
| 112 | + // |
| 113 | + // If force_mode is true, false or undefined and there is a |
| 114 | + // yet-to-be-executed callback to cancel, true is returned, but if no |
| 115 | + // callback remains to be executed, undefined is returned. |
| 116 | + |
| 117 | + $[doTimeout] = function() { |
| 118 | + return p_doTimeout.apply( window, [ 0 ].concat( aps.call( arguments ) ) ); |
| 119 | + }; |
| 120 | + |
| 121 | + // Method: jQuery.fn.doTimeout |
| 122 | + // |
| 123 | + // Initialize, cancel, or force execution of a callback after a delay. |
| 124 | + // Operates like <jQuery.doTimeout>, but the passed callback executes in the |
| 125 | + // context of the jQuery collection of elements, and the id is stored as data |
| 126 | + // on the first element in that collection. |
| 127 | + // |
| 128 | + // If delay and callback are specified, a doTimeout is initialized. The |
| 129 | + // callback will execute, asynchronously, after the delay. If an id is |
| 130 | + // specified, this doTimeout will override and cancel any existing doTimeout |
| 131 | + // with the same id. Any additional arguments will be passed into callback |
| 132 | + // when it is executed. |
| 133 | + // |
| 134 | + // If the callback returns true, the doTimeout loop will execute again, after |
| 135 | + // the delay, creating a polling loop until the callback returns a non-true |
| 136 | + // value. |
| 137 | + // |
| 138 | + // Note that if an id is not passed as the first argument, this doTimeout will |
| 139 | + // NOT be able to be manually canceled or forced (for debouncing, be sure to |
| 140 | + // specify an id). |
| 141 | + // |
| 142 | + // If id is specified, but delay and callback are not, the doTimeout will be |
| 143 | + // canceled without executing the callback. If force_mode is specified, the |
| 144 | + // callback will be executed, synchronously, but will only be allowed to |
| 145 | + // continue a polling loop if force_mode is true (provided the callback |
| 146 | + // returns true, of course). If force_mode is false, no polling loop will |
| 147 | + // continue, even if the callback returns true. |
| 148 | + // |
| 149 | + // Usage: |
| 150 | + // |
| 151 | + // > jQuery('selector').doTimeout( [ id, ] delay, callback [, arg ... ] ); |
| 152 | + // > jQuery('selector').doTimeout( id [, force_mode ] ); |
| 153 | + // |
| 154 | + // Arguments: |
| 155 | + // |
| 156 | + // id - (String) An optional unique identifier for this doTimeout, stored as |
| 157 | + // jQuery data on the element. If id is not specified, the doTimeout will |
| 158 | + // NOT be able to be manually canceled or forced. |
| 159 | + // delay - (Number) A zero-or-greater delay in milliseconds after which |
| 160 | + // callback will be executed. |
| 161 | + // callback - (Function) A function to be executed after delay milliseconds. |
| 162 | + // callback - (String) A jQuery.fn method to be executed after delay |
| 163 | + // milliseconds. This method will only poll if it explicitly returns |
| 164 | + // true (most jQuery.fn methods return a jQuery object, and not `true`, |
| 165 | + // which allows them to be chained and prevents polling). |
| 166 | + // force_mode - (Boolean) If true, execute that id's doTimeout callback |
| 167 | + // immediately and synchronously, continuing any callback return-true |
| 168 | + // polling loop. If false, execute the callback immediately and |
| 169 | + // synchronously but do NOT continue a callback return-true polling loop. |
| 170 | + // If omitted, cancel that id's doTimeout. |
| 171 | + // |
| 172 | + // Returns: |
| 173 | + // |
| 174 | + // When creating a <jQuery.fn.doTimeout>, the initial jQuery collection of |
| 175 | + // elements is returned. Otherwise, if force_mode is true, false or undefined |
| 176 | + // and there is a yet-to-be-executed callback to cancel, true is returned, |
| 177 | + // but if no callback remains to be executed, undefined is returned. |
| 178 | + |
| 179 | + $.fn[doTimeout] = function() { |
| 180 | + var args = aps.call( arguments ), |
| 181 | + result = p_doTimeout.apply( this, [ doTimeout + args[0] ].concat( args ) ); |
| 182 | + |
| 183 | + return typeof args[0] === 'number' || typeof args[1] === 'number' |
| 184 | + ? this |
| 185 | + : result; |
| 186 | + }; |
| 187 | + |
| 188 | + function p_doTimeout( jquery_data_key ) { |
| 189 | + var that = this, |
| 190 | + elem, |
| 191 | + data = {}, |
| 192 | + |
| 193 | + // Allows the plugin to call a string callback method. |
| 194 | + method_base = jquery_data_key ? $.fn : $, |
| 195 | + |
| 196 | + // Any additional arguments will be passed to the callback. |
| 197 | + args = arguments, |
| 198 | + slice_args = 4, |
| 199 | + |
| 200 | + id = args[1], |
| 201 | + delay = args[2], |
| 202 | + callback = args[3]; |
| 203 | + |
| 204 | + if ( typeof id !== 'string' ) { |
| 205 | + slice_args--; |
| 206 | + |
| 207 | + id = jquery_data_key = 0; |
| 208 | + delay = args[1]; |
| 209 | + callback = args[2]; |
| 210 | + } |
| 211 | + |
| 212 | + // If id is passed, store a data reference either as .data on the first |
| 213 | + // element in a jQuery collection, or in the internal cache. |
| 214 | + if ( jquery_data_key ) { // Note: key is 'doTimeout' + id |
| 215 | + |
| 216 | + // Get id-object from the first element's data, otherwise initialize it to {}. |
| 217 | + elem = that.eq(0); |
| 218 | + elem.data( jquery_data_key, data = elem.data( jquery_data_key ) || {} ); |
| 219 | + |
| 220 | + } else if ( id ) { |
| 221 | + // Get id-object from the cache, otherwise initialize it to {}. |
| 222 | + data = cache[ id ] || ( cache[ id ] = {} ); |
| 223 | + } |
| 224 | + |
| 225 | + // Clear any existing timeout for this id. |
| 226 | + data.id && clearTimeout( data.id ); |
| 227 | + delete data.id; |
| 228 | + |
| 229 | + // Clean up when necessary. |
| 230 | + function cleanup() { |
| 231 | + if ( jquery_data_key ) { |
| 232 | + elem.removeData( jquery_data_key ); |
| 233 | + } else if ( id ) { |
| 234 | + delete cache[ id ]; |
| 235 | + } |
| 236 | + }; |
| 237 | + |
| 238 | + // Yes, there actually is a setTimeout call in here! |
| 239 | + function actually_setTimeout() { |
| 240 | + data.id = setTimeout( function(){ data.fn(); }, delay ); |
| 241 | + }; |
| 242 | + |
| 243 | + if ( callback ) { |
| 244 | + // A callback (and delay) were specified. Store the callback reference for |
| 245 | + // possible later use, and then setTimeout. |
| 246 | + data.fn = function( no_polling_loop ) { |
| 247 | + |
| 248 | + // If the callback value is a string, it is assumed to be the name of a |
| 249 | + // method on $ or $.fn depending on where doTimeout was executed. |
| 250 | + if ( typeof callback === 'string' ) { |
| 251 | + callback = method_base[ callback ]; |
| 252 | + } |
| 253 | + |
| 254 | + callback.apply( that, aps.call( args, slice_args ) ) === true && !no_polling_loop |
| 255 | + |
| 256 | + // Since the callback returned true, and we're not specifically |
| 257 | + // canceling a polling loop, do it again! |
| 258 | + ? actually_setTimeout() |
| 259 | + |
| 260 | + // Otherwise, clean up and quit. |
| 261 | + : cleanup(); |
| 262 | + }; |
| 263 | + |
| 264 | + // Set that timeout! |
| 265 | + actually_setTimeout(); |
| 266 | + |
| 267 | + } else if ( data.fn ) { |
| 268 | + // No callback passed. If force_mode (delay) is true, execute the data.fn |
| 269 | + // callback immediately, continuing any callback return-true polling loop. |
| 270 | + // If force_mode is false, execute the data.fn callback immediately but do |
| 271 | + // NOT continue a callback return-true polling loop. If force_mode is |
| 272 | + // undefined, simply clean up. Since data.fn was still defined, whatever |
| 273 | + // was supposed to happen hadn't yet, so return true. |
| 274 | + delay === undefined ? cleanup() : data.fn( delay === false ); |
| 275 | + return true; |
| 276 | + |
| 277 | + } else { |
| 278 | + // Since no callback was passed, and data.fn isn't defined, it looks like |
| 279 | + // whatever was supposed to happen already did. Clean up and quit! |
| 280 | + cleanup(); |
| 281 | + } |
| 282 | + |
| 283 | + }; |
| 284 | + |
| 285 | +})(jQuery); |
0 commit comments