1 /** 2 * @fileOverview The jQuery Chrono plugin 3 * Copyright (c) 2011 Arthur Klepchukov 4 * Licensed under the BSD license (BSD_LICENSE.txt) 5 * 6 * @author <a href="mailto:first-name.last-name@gmail.com">Arthur Klepchukov</a> 7 * @version 1.2 8 */ 9 10 /*global jQuery, $ */ 11 var jQueryChrono; 12 13 /** 14 * @namespace Main namespace 15 */ 16 jQueryChrono = (function() { 17 /** 18 * Syntactic sugar for setTimeout. 19 * <pre> 20 * setTimeout(function() { ... }, 300000); // becomes: 21 * $.after(5, "minutes", function() { ... }); 22 * 23 * // other valid calls: 24 * $.after(100, function() { ... }); // 100 milliseconds 25 * $.after("9.7", function() { ... }); // 9.7 milliseconds 26 * $.after("50sec", function() { ... }); // 50 seconds 27 * $.after("33", "hours", function() { ... }); // 33 hours 28 * $.after("minute", function() { ... }); // 1 minute 29 * $.after("1 hour, 2 minutes, 15 seconds", function() { ... }); // 1:02:15 hours 30 * $.after("1min, 15 s", function() { ... }); // 1:15 minutes 31 * </pre> 32 * Valid time units include: 33 * <strong>millisecond, second, minute, hour, & day</strong><br /> 34 * along with all their common abbreviations and pluralizations.<br /> 35 * (See full list of valid time units: {@link jQueryChrono-valid_units}) 36 * @name jQuery.after 37 */ 38 function after() { 39 var timer = jQueryChrono.create_timer.apply(this, arguments); 40 return setTimeout(timer.callback, timer.when); 41 } 42 43 /** 44 * Syntactic sugar for setTimeout. 45 * <pre> 46 * setInterval(function() { ... }, 300000); // becomes: 47 * $.every(5, "minutes", function() { ... }); 48 * </pre> 49 * Supports the same syntax and arguments as {@link jQuery.after} 50 * @name jQuery.every 51 */ 52 function every() { 53 var timer = jQueryChrono.create_timer.apply(this, arguments); 54 return setInterval(timer.callback, timer.when); 55 } 56 57 /** 58 * Reasonable defaults (delay: 4, units: ms), based on how Mozilla works with timers: 59 * https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting 60 * @constant 61 */ 62 var defaults = { 63 delay: 4, 64 units: "milliseconds" 65 }, 66 // each supported unit, in milliseconds 67 ms = 1, 68 sec = ms * 1000, 69 min = sec * 60, 70 hr = min * 60, 71 day = hr * 24; 72 73 /** 74 * The supported units of time:<br /> 75 * millisecond, milliseconds, ms,<br /> 76 * second, seconds, sec, secs, s,<br /> 77 * minute, minutes, min, mins, m,<br /> 78 * hour, hours, hr, hrs, h,<br /> 79 * day, days, d 80 * @constant 81 */ 82 var valid_units = { 83 "millisecond" : ms, 84 "milliseconds": ms, 85 "ms" : ms, 86 87 "second" : sec, 88 "seconds" : sec, 89 "sec" : sec, 90 "secs" : sec, 91 "s" : sec, 92 93 "minute" : min, 94 "minutes" : min, 95 "min" : min, 96 "mins" : min, 97 "m" : min, 98 99 "hour" : hr, 100 "hours" : hr, 101 "hr" : hr, 102 "hrs" : hr, 103 "h" : hr, 104 105 "day" : day, 106 "days" : day, 107 "d" : day 108 }; 109 110 /** 111 * Trim string. Copied from jQuery. 112 * @param {String} text to be trimmed 113 * @returns {String} string without leading or trailing spaces 114 */ 115 var trim; 116 if (typeof(jQuery) !== 'undefined') { 117 trim = jQuery.trim; 118 } else { 119 trim = String.prototype.trim2 ? 120 function(text) { 121 return text == null ? "" : 122 String.prototype.trim.call( text ); 123 } : function(text) { 124 return text == null ? "" : 125 text.toString().replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""); 126 } 127 } 128 129 /** 130 * Parses a numerical delay from the given arguments. 131 * 132 * @param {Object} parsed The arguments parsed so far 133 * @param {arguments} args The original arguments from the caller 134 * (e.g. {@link jQueryChrono.create_timer}) 135 * @throws Exception if the delay is not a number 136 * @returns {Object} The parsed parameter updated with the parsed delay 137 */ 138 function parse_delay(parsed, args) { 139 if (typeof args[0] === "string") { 140 parsed.delay = parseFloat(args[0], 10); 141 if (isNaN(parsed.delay)) { 142 parsed.delay = (valid_units[args[0]] > ms) ? 1 : defaults.delay; 143 } 144 } else { 145 parsed.delay = args[0]; 146 } 147 148 if (typeof parsed.delay !== "number" || isNaN(parsed.delay)) { 149 throw "$.after and $.every - Require a numerical delay as the 1st argument"; 150 } 151 152 return parsed; 153 } 154 155 /** 156 * Parses a units string from the given arguments. 157 * 158 * @param {Object} parsed The arguments parsed so far 159 * @param {arguments} args The original arguments from the caller 160 * (e.g. {@link jQueryChrono.create_timer}) 161 * @throws Exception if the units are not a key of {@link jQueryChrono-valid_units} 162 * @returns {Object} The parsed parameter updated with the parsed units 163 */ 164 function parse_units(parsed, args) { 165 if (typeof args[0] === "string" && parsed.delay !== null) { 166 parsed.units = trim(args[0].replace(parsed.delay, "")) || null; // "9.7sec" || "9.7" 167 } 168 if (typeof args[1] === "string") { 169 parsed.units = args[1]; 170 } 171 if (parsed.units === null && args.length === 2) { // no units specified 172 parsed.units = defaults.units; 173 } 174 175 if (typeof valid_units[parsed.units] !== "number") { 176 throw "$.after and $.every - Require a valid unit of time as the 2nd argument"; 177 } 178 179 return parsed; 180 } 181 182 /** 183 * Parses a callback function from the given arguments. 184 * 185 * @param {Object} parsed The arguments parsed so far 186 * @param {arguments} args The original arguments from the caller 187 * (e.g. {@link jQueryChrono.create_timer}) 188 * @throws Exception if the callback is not a function 189 * @returns {Object} The parsed parameter updated with the parsed callback 190 */ 191 function parse_callback(parsed, args) { 192 parsed.callback = args[args.length - 1]; 193 194 if (typeof(parsed.callback) != 'function') { 195 throw "$.after and $.every - Require a callback as the last argument"; 196 } 197 198 return parsed; 199 } 200 201 /** 202 * Parses a string sequence of delay with unit arguments. 203 * 204 * @param {Object} parsed The arguments parsed so far 205 * @param {arguments} args The original arguments from the caller 206 * (e.g. {@link jQueryChrono.create_timer}) 207 * @throws Exception if the sequence contains blanks, invalid delays, or invalid units 208 * @returns {Object} The parsed parameter updated with the parsed delay 209 * and units, each set to the minimum unit in the sequence 210 * @example "1 minute, 15 seconds" // parsed = { delay: 75, units: "seconds" } 211 */ 212 function parse_sequence(parsed, args) { 213 var commaArgs, name, minInterval = '', timer, timers = []; 214 215 // if the first arg is a string, try splitting it on commas 216 commaArgs = (typeof args[0] === 'string') ? args[0].split(',') : []; 217 218 // create a timer for each sequence element 219 for (name in commaArgs) { 220 if (! /\d\s?\w+/.test(commaArgs[name])) { 221 throw "$.after and $.every - Invalid delays with units sequence: " + 222 commaArgs.join(','); 223 } 224 timer = create_timer.call(this, commaArgs[name], parsed.callback); 225 226 // keep track of the minimum interval so we can convert the whole set to this in the next loop 227 if (minInterval === '' || valid_units[timer.units] <= valid_units[minInterval]) { 228 minInterval = timer.units; 229 } 230 timers[name] = timer; 231 } 232 parsed.units = minInterval; 233 234 // convert each timer to the lowest interval, then add those units to parsed.delay 235 for (name in timers) { 236 parsed.delay += timers[name].delay * (valid_units[timers[name].units] / valid_units[minInterval]); 237 } 238 return parsed; 239 } 240 241 /** 242 * Accepts more human-readable arguments for creating JavaScript timers and 243 * converts them to values that can be inspected and passed along to 244 * setTimeout or setInterval.<br /> 245 * If the time when the timer should run is negative or faster than 246 * the default ({@link jQueryChrono-defaults}), 247 * it uses the default delay and default units. 248 * 249 * @param {Number|String} delay|delay+units 250 * Combined with units, represents when a timer should run.<br /> 251 * Units can be specified as part of this argument as a suffix of the string and 252 * must represent a valid unit of time ({@link jQueryChrono-valid_units}). 253 * @param {String} [units] 254 * Combined with the delay, represents when a timer should run. 255 * If present, must be a valid unit of time ({@link jQueryChrono-valid_units}). 256 * @param {Function} callback 257 * Represents the code to be executed when the timer is ready. 258 * 259 * @returns {Object} An object with a valid "delay", a valid "units" string, 260 * a time, in milliseconds, of "when" the timer should run, and 261 * a "callback" that the timer should execute when it's ready. 262 * @static 263 */ 264 function create_timer() { 265 var parsed = { 266 delay : null, 267 units : null, 268 when : null, 269 callback : null 270 }; 271 272 if (arguments.length < 2 || arguments.length > 3) { 273 throw "$.after and $.every - Accept only 2 or 3 arguments"; 274 } 275 276 parsed = parse_callback(parsed, arguments); 277 if (typeof arguments[0] === 'string' && arguments[0].search(',') > -1) { 278 parsed = parse_sequence(parsed, arguments); 279 } else { 280 parsed = parse_delay(parsed, arguments); 281 parsed = parse_units(parsed, arguments); 282 } 283 284 // Reset to defaults, if necessary 285 if (parsed.delay < defaults.delay && parsed.units === defaults.units) { 286 parsed.delay = defaults.delay; 287 } 288 if (parsed.delay < 0) { 289 parsed.delay = defaults.delay; 290 parsed.units = defaults.units; 291 } 292 293 parsed.when = parsed.delay * valid_units[parsed.units]; 294 295 return parsed; 296 } 297 298 /** @scope jQueryChrono */ 299 return { 300 every : every, 301 after : after, 302 defaults : defaults, 303 valid_units : valid_units, 304 create_timer : function() { 305 return create_timer.apply(this, arguments); 306 } 307 }; 308 }()); 309 310 /** 311 * The extended jQuery library 312 * @name jQuery 313 * @class the extended jQuery library 314 * @exports $ as jQuery 315 */ 316 if (typeof(jQuery) !== 'undefined') { 317 jQuery.extend({ 318 after : jQueryChrono.after, 319 every : jQueryChrono.every 320 }); 321 } 322