1 /** 2 * @namespace Core utilities and functions. 3 */ 4 var Jelo = window.Jelo || { 5 version : 1.21 6 }; 7 8 /** 9 * Can be used to create unique IDs or global counters. Every time this function is called, a number will be returned 10 * which is 1 greater than the number previously returned. 11 * 12 * @function 13 * @return {Number} A unique (to this function), autoincrementing positive integer. The first number returned is 1. 14 */ 15 Jelo.uID = function() { 16 var id = 1; // initial value 17 return function() { 18 return id++; 19 }; 20 }(); 21 22 /** 23 * A reusable no-operation function. Useful for placeholders. 24 */ 25 Jelo.emptyFn = function() {}; 26 27 /** 28 * Performs a function on each item in a collection. If the first argument is not an array or object, the function is 29 * called once with it. If the function ever returns false, execution halts immediately and the "failed" index is 30 * returned. Normally, Jelo.each will return null. 31 * 32 * @param {Array|NodeList|Object} collection The object over which to iterate. 33 * @param {Function} fn The function to execute for each item in the collection. Arguments passed to the function will 34 * be the item, its index, and the complete array. For example, myFunc(collection[index], index, collection) 35 * @param {Object} [scope] The scope ("this") in which to execute the function. By default, the scope will be the 36 * current item being iterated across. 37 */ 38 Jelo.each = function(a, f, s) { 39 var i, ai; 40 if (typeof a.length == "number") { 41 for (i = 0, l = a.length; i < l; i++) { 42 ai = a[i]; 43 if (typeof a[i] != 'undefined') { 44 if (f.call(s || ai, ai, i, a) === false) { 45 return i; 46 } 47 } 48 } 49 } else if (typeof a == "object") { 50 for (i in a) { 51 ai = a[i]; 52 if (a.hasOwnProperty(i) && typeof ai != 'undefined') { 53 if (f.call(s || ai, ai, i, a) === false) { 54 return i; 55 } 56 } 57 } 58 } 59 }; 60 61 /** 62 * Binds arguments to a function and optional scope. 63 * 64 * @param {Function} fn The function to execute when the delegate is called. 65 * @param {Object} [scope=window] The scope in which to execute the chosen function. 66 * @param {Mixed} [...] Additional parameters to pass to the function 67 * @return {Function} The new function reference. 68 */ 69 Jelo.delegate = function(fn, scope) { 70 var s = scope || window; 71 var a = [].slice.call(arguments, 2); 72 return function() { 73 fn.apply(s, a); 74 }; 75 }; 76 77 // Normalizes window.console to prevent debug errors when there is no console. 78 (function() { 79 if (!('console' in window) || !('firebug' in console)) { 80 var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", 81 "timeEnd", "count", "trace", "profile", "profileEnd"]; 82 window.console = {}; 83 for (var i = 0; i < names.length; ++i) { 84 // TODO: write to a hidden div, provide a method to toggle 85 // visibility 86 window.console[names[i]] = Jelo.emptyFn; 87 } 88 } 89 /** 90 * @namespace 91 * @name Console 92 * @memberOf Jelo 93 */ 94 Jelo.Console = { 95 firebug : window.console 96 }; // Google likes to eat the console :( 97 /** 98 * @function 99 * @param {Mixed} Information to log in the debug console. 100 */ 101 Jelo.Console.log = window.console.log; 102 /** 103 * @function 104 * @returns {Array} The current call stack. 105 */ 106 Jelo.Console.getStackTrace = function() { 107 var /* counter */i, /* length */len, 108 /* stack */s = [], 109 /* isStackPopulated */isPop = false, /* lines */l; 110 try { 111 hb.stone.javascript += 0; // raise an error 112 } catch (e) { 113 if (e.stack) { 114 // Firefox 115 l = e.stack.split("\n"); 116 for (i = 0, len = l.length; i < len; i++) { 117 if (l[i].match(/^\s*[a-z0-9\-_\$]+\(/i)) { 118 s.push(l[i]); 119 } 120 } 121 s.shift(); // remove call to getStackTrace 122 isPop = true; 123 } else if (window.opera && e.message) { // Opera 124 l = e.message.split("\n"); 125 for (i = 0, len = l.length; i < len; i++) { 126 if (l[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) { 127 var entry = l[i]; 128 if (l[i + 1]) { 129 entry += " at " + l[i + 1]; // file info 130 i++; 131 } 132 s.push(entry); 133 } 134 } 135 s.shift(); // remove call to getStackTrace 136 isPop = true; 137 } 138 } 139 if (!isPop) { 140 // IE and Safari 141 var curr = arguments.callee; 142 while ((curr = curr.caller)) { 143 var fn = curr.toString(); 144 var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous"; 145 s.push(fname); 146 } 147 } 148 return s; 149 150 }; 151 152 })(); 153 154 /* 155 * Normalizes setTimeout and setInterval behavior across all browsers. Typically, IE does not treat additional arguments 156 * correctly, and Firefox adds a "lateness" argument to the supplied function call. 157 */ 158 (function(f) { 159 /** 160 * @function 161 * @name setTimeout 162 * @param {Function} fn Method to invoke. 163 * @param {Number} ms Milliseconds to delay before invoking fn. 164 * @param {Mixed} [...] Additional arguments to be passed to fn when it is called. 165 * @returns {Number} Resource id, can be cancelled using clearTimeout(id) 166 */ 167 window.setTimeout = f(window.setTimeout); 168 /** 169 * @function 170 * @name setInterval 171 * @param {Function} fn Method to invoke. 172 * @param {Number} ms Milliseconds to delay between intervals 173 * @param {Mixed} [...] Additional arguments to be passed to fn when it is called. 174 * @returns {Number} Resource id, can be cancelled using clearInterval(id) 175 */ 176 window.setInterval = f(window.setInterval); 177 })(function(f) { 178 return function(c, t) { 179 var a = [].slice.call(arguments, 2); 180 return ((typeof c == 'function') 181 ? f(function() { 182 c.apply(this, a); 183 }, t) 184 : f.call(this, c, t)); 185 }; 186 }); 187 188 // Normalizes Internet Explorer's behavior to match modern browsers. 189 (function() { 190 if (typeof Array.prototype.indexOf == 'undefined') { 191 /** 192 * @memberOf Array 193 * @param {Mixed} x The item to attempt to find. 194 * @returns {Number} The item's index if found, -1 otherwise. 195 */ 196 Array.prototype.indexOf = function(k) { 197 var len = this.length; 198 for (var i = 0; i < len; i++) { 199 if (this[i] == k) { 200 return i; 201 } 202 } 203 return -1; 204 }; 205 Array.indexOf = Array.prototype.indexOf; 206 } 207 if (typeof Array.prototype.lastIndexOf == 'undefined') { 208 /** 209 * @memberOf Array 210 * @param {Mixed} x The item to attempt to find. 211 * @returns {Number} The index of the item's last occurrence if found, -1 otherwise. 212 */ 213 Array.prototype.lastIndexOf = function(k) { 214 var len = this.length; 215 for (var i = len - 1; i > -1; i--) { 216 if (this[i] == k) { 217 return i; 218 } 219 } 220 return -1; 221 }; 222 Array.lastIndexOf = Array.prototype.lastIndexOf; 223 } 224 if (typeof Array.prototype.find == 'undefined') { 225 /** 226 * @memberOf Array 227 * @param {Mixed} x The item to attempt to find, or a RegExp to match. 228 * @returns {Array|Boolean} An array of indeces at which the item was found, or at which the RegExp tested 229 * positive. Boolean false if no element matched. 230 */ 231 Array.prototype.find = function(k) { 232 var res = []; 233 var len = this.length; 234 for (var i = 0; i < len; i++) { 235 if ((k.test && k.test(this[i])) || k === this[i]) { 236 res.push(i); 237 } 238 } 239 return !!res.length && res; 240 }; 241 Array.find = Array.prototype.find; 242 } 243 if (typeof Array.prototype.shuffle == 'undefined') { 244 /** 245 * @memberOf Array 246 * @returns {Array} The array, randomized. 247 */ 248 Array.prototype.shuffle = function() { 249 for (var j, x, i = this.length; i; j = parseInt(Math.random() * i, 10), x = this[--i], this[i] = this[j], this[j] = x) {} 250 return this; 251 }; 252 Array.shuffle = Array.prototype.shuffle; 253 } 254 })(); 255 256 /** 257 * Allows you to check or uncheck a group of checkboxes by clicking and dragging across them (or their labels). 258 * 259 * @function 260 * @name dragCheckbox 261 * @param {HTMLElement} [root=document] The element within which to apply "drag toggle" functionality. 262 * @memberOf Jelo 263 */ 264 Jelo.dragCheckbox = function(root) { 265 root = root || document; 266 var dragging = false; 267 var current = false; 268 var undrag = function() { 269 dragging = false; 270 }; 271 Jelo.un(document, "mouseup", undrag); 272 Jelo.on(document, "mouseup", undrag); 273 var getTarget = function(element) { 274 switch (element.tagName.toLowerCase()) { 275 case 'input' : 276 return element; 277 case 'label' : 278 Jelo.css(element, '-moz-user-select', 'none'); 279 Jelo.css(element, '-webkit-user-select', 'ignore'); 280 element.onselectstart = function() { 281 return false; 282 }; 283 var el = element.getAttribute('for') || element.getAttribute('htmlFor'); 284 return $('input#' + el); 285 default : 286 return null; // invalid element 287 } 288 }; 289 Jelo.each($$('[type=checkbox]', root), function() { 290 var down = function() { 291 var box = getTarget(this); 292 if (box) { 293 dragging = true; 294 box.checked = !box.checked; 295 current = box.checked; 296 } 297 }; 298 var over = function() { 299 var box = getTarget(this); 300 if (box && dragging) { 301 box.checked = current; 302 } 303 }; 304 var click = function(target, event) { 305 var box = getTarget(this); 306 if (box) { 307 box.checked = current; 308 } 309 }; 310 Jelo.un(this, "mousedown", down); 311 Jelo.un(this, "mouseover", over); 312 Jelo.un(this, "click", click); 313 Jelo.on(this, "mousedown", down); 314 Jelo.on(this, "mouseover", over); 315 Jelo.on(this, "click", click); 316 var label = $('label[for=' + this.id + ']', root); 317 if (label) { 318 Jelo.un(label, "mousedown", down); 319 Jelo.un(label, "mouseover", over); 320 Jelo.un(label, "click", click); 321 Jelo.on(label, "mousedown", down); 322 Jelo.on(label, "mouseover", over); 323 Jelo.on(label, "click", click); 324 } 325 }); 326 }; 327