1 (function (global) { 2 3 "use strict"; 4 5 if (global.Sfdc && global.Sfdc.canvas) { 6 return; 7 } 8 9 // cached references 10 //------------------ 11 12 var oproto = Object.prototype, 13 aproto = Array.prototype, 14 doc = global.document, 15 /** 16 * @class Canvas 17 * @exports $ as Sfdc.canvas 18 */ 19 // $ functions 20 // The canvas global object is made available in the global scope. The reveal to the global scope is done later. 21 $ = { 22 23 // type utilities 24 //--------------- 25 26 /** 27 * @description Checks whether an object contains an uninherited property. 28 * @param {Object} obj The object to check 29 * @param {String} prop The property name to check 30 * @returns {Boolean} <code>true</code> if the property exists for the object itself and is not inherited, otherwise <code>false</code> 31 */ 32 hasOwn: function (obj, prop) { 33 return oproto.hasOwnProperty.call(obj, prop); 34 }, 35 36 /** 37 * @description Checks whether an object is currently undefined. 38 * @param {Object} value The object to check 39 * @returns {Boolean} <code>true</code> if the object or value is of type undefined, otherwise <code>false</code> 40 */ 41 isUndefined: function (value) { 42 var undef; 43 return value === undef; 44 }, 45 46 /** 47 * @description Checks whether object is undefined, null, or an empty string. 48 * @param {Object} value The object to check 49 * @returns {Boolean} <code>true</code> if the object or value is of type undefined, otherwise <code>false</code> 50 */ 51 isNil: function (value) { 52 return $.isUndefined(value) || value === null || value === ""; 53 }, 54 55 /** 56 * @description Checks whether a value is a number. This function doesn't resolve strings to numbers. 57 * @param {Object} value Object to check 58 * @returns {Boolean} <code>true</code> if the object or value is a number, otherwise <code>false</code> 59 */ 60 isNumber: function (value) { 61 return !!(value === 0 || (value && value.toExponential && value.toFixed)); 62 }, 63 64 /** 65 * @description Checks whether an object is a function. 66 * @param {Object} value Object to check 67 * @returns {Boolean} <code>true</code> if the object or value is a function, otherwise <code>false</code> 68 */ 69 isFunction: function (value) { 70 return !!(value && value.constructor && value.call && value.apply); 71 }, 72 73 /** 74 * @description Checks whether an object is an array. 75 * @param {Object} value The object to check 76 * @function 77 * @returns {Boolean} <code>true</code> if the object or value is of type array, otherwise <code>false</code> 78 */ 79 isArray: Array.isArray || function (value) { 80 return oproto.toString.call(value) === '[object Array]'; 81 }, 82 83 /** 84 * @description Checks whether an object is the argument set for a function 85 * @param {Object} value The object to check 86 * @returns {Boolean} <code>true</code> if the object or value is the argument set for a function, otherwise <code>false</code> 87 */ 88 isArguments: function (value) { 89 return !!(value && $.hasOwn(value, 'callee')); 90 }, 91 92 /** 93 * @description Checks whether the value is of type 'object' and is not null. 94 * @param {Object} value The object to check 95 * @returns {Boolean} <code>true</code> if the object or value is of type Object, otherwise <code>false</code> 96 */ 97 isObject: function (value) { 98 return value !== null && typeof value === 'object'; 99 }, 100 101 // common functions 102 //----------------- 103 104 /** 105 * @description An empty or blank function. 106 */ 107 nop: function () { 108 /* no-op */ 109 }, 110 111 /** 112 * @description This function runs the function that is passed to it. 113 * @param {Function} fn The function to run 114 */ 115 invoker: function (fn) { 116 if ($.isFunction(fn)) { 117 fn(); 118 } 119 }, 120 121 /** 122 * @description This function always returns the argument. 123 * @param {Object} obj The object to return, untouched. 124 * @returns {Object} The argument used for this function call. 125 */ 126 identity: function (obj) { 127 return obj; 128 }, 129 130 // @todo consider additional tests for: null, boolean, string, nan, element, regexp... as needed 131 /** 132 * @description Calls a defined function for each element in an object 133 * @param {Object} obj The object to loop through. 134 It can be an array, an array like object or a map of properties 135 * @param {Function} it The callback function to run for each element. 136 * @param {Object} [ctx] The context object to be used for the callback function. 137 Defaults to the original object if not provided. 138 */ 139 each: function (obj, it, ctx) { 140 if ($.isNil(obj)) { 141 return; 142 } 143 var nativ = aproto.forEach, i = 0, l, key; 144 l = obj.length; 145 ctx = ctx || obj; 146 // @todo: looks like native method will not break on return false; maybe throw breaker {} 147 if (nativ && nativ === obj.forEach) { 148 obj.forEach(it, ctx); 149 } 150 else if ($.isNumber(l)) { // obj is an array-like object 151 while (i < l) { 152 if (it.call(ctx, obj[i], i, obj) === false) { 153 return; 154 } 155 i += 1; 156 } 157 } 158 else { 159 for (key in obj) { 160 if ($.hasOwn(obj, key) && it.call(ctx, obj[key], key, obj) === false) { 161 return; 162 } 163 } 164 } 165 }, 166 167 /** 168 * @description Creates a new array with the results of calling the 169 function on each element in the object. 170 * @param {Object} obj The object to use. 171 * @param {Function} it The callback function to run for each element. 172 * @param {Object} [ctx] The context object to be used for the callback function. 173 Defaults to the original object if not provided. 174 * @returns {Array} The array that results when calling the function on each 175 element in the object. 176 */ 177 map: function (obj, it, ctx) { 178 var results = [], nativ = aproto.map; 179 if ($.isNil(obj)) { 180 return results; 181 } 182 if (nativ && obj.map === nativ) { 183 return obj.map(it, ctx); 184 } 185 ctx = ctx || obj; 186 $.each(obj, function (value, i, list) { 187 results.push(it.call(ctx, value, i, list)); 188 }); 189 return results; 190 }, 191 192 /** 193 * @description Creates an array containing all the elements of the given object 194 * @param {Object} obj The object the use in creating the array 195 * @returns {Array} An array containing all the elements in the object. 196 */ 197 values: function (obj) { 198 return $.map(obj, $.identity); 199 }, 200 201 /** 202 * @description Creates a new array containing the selected elements of the given array. 203 * @param {Array} array The array to subset. 204 * @param {Integer} [begin=0] The index that specifies where to start the selection. 205 * @param {Integer} [end = array.length] The index that specifies where to end the selection. 206 * @returns {Array} A new array that contains the selected elements. 207 */ 208 slice: function (array, begin, end) { 209 /* FF doesn't like undefined args for slice so ensure we call with args */ 210 return aproto.slice.call(array, $.isUndefined(begin) ? 0 : begin, $.isUndefined(end) ? array.length : end); 211 }, 212 213 /** 214 * @description Creates an array from an object. 215 * @param {Object} iterable The object to use in creating the array. 216 * @returns {Array} The new array created from the object. 217 */ 218 toArray: function (iterable) { 219 if (!iterable) { 220 return []; 221 } 222 if (iterable.toArray) { 223 return iterable.toArray; 224 } 225 if ($.isArray(iterable)) { 226 return iterable; 227 } 228 if ($.isArguments(iterable)) { 229 return $.slice(iterable); 230 } 231 return $.values(iterable); 232 }, 233 234 /** 235 * @description Calculates the number of elements in an object 236 * @param {Object} obj The object to size. 237 * @returns {Integer} The number of elements in the object. 238 */ 239 size: function (obj) { 240 return $.toArray(obj).length; 241 }, 242 243 /** 244 * @description Calculates the location of an element in an array. 245 * @param {Array} array The array to check. 246 * @param {Object} item The item to search for within the array. 247 * @returns {Integer} The index of the element within the array. 248 Returns -1 if the element is not found. 249 */ 250 indexOf: function (array, item) { 251 var nativ = aproto.indexOf, i, l; 252 if (!array) { 253 return -1; 254 } 255 if (nativ && array.indexOf === nativ) { 256 return array.indexOf(item); 257 } 258 for (i = 0, l = array.length; i < l; i += 1) { 259 if (array[i] === item) { 260 return i; 261 } 262 } 263 return -1; 264 }, 265 266 /** 267 * @description Removes an element from an array. 268 * @param {Array} array The array to modify. 269 * @param {Object} item The element to remove from the array. 270 */ 271 remove: function (array, item) { 272 var i = $.indexOf(array, item); 273 if (i >= 0) { 274 array.splice(i, 1); 275 } 276 }, 277 278 /** 279 * @description Serializes an object into a string that can be used as a URL query string. 280 * @param {Object|Array} a The array or object to serialize. 281 * @param {Boolean} [encode=false] Indicates that the string should be encoded. 282 * @returns {String} A string representing the object as a URL query string. 283 */ 284 param: function (a, encode) { 285 var s = []; 286 287 encode = encode || false; 288 289 function add( key, value ) { 290 291 if ($.isNil(value)) {return;} 292 value = $.isFunction(value) ? value() : value; 293 if ($.isArray(value)) { 294 $.each( value, function(v, n) { 295 add( key, v ); 296 }); 297 } 298 else { 299 if (encode) { 300 s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); 301 } 302 else { 303 s[ s.length ] = key + "=" + value; 304 } 305 } 306 } 307 308 if ( $.isArray(a)) { 309 $.each( a, function(v, n) { 310 add( n, v ); 311 }); 312 } else { 313 for ( var p in a ) { 314 if ($.hasOwn(a, p)) { 315 add( p, a[p]); 316 } 317 } 318 } 319 return s.join("&").replace(/%20/g, "+"); 320 }, 321 322 /** 323 * @description Strip out the URL to just the {scheme}://{host}:{port} - remove any path and query string information. 324 * @param {String} url The url to be stripped 325 * @returns {String} just the {scheme}://{host}:{port} portion of the url. 326 */ 327 stripUrl : function(url) { 328 return ($.isNil(url)) ? null : url.replace( /([^:]+:\/\/[^\/\?#]+).*/, '$1'); 329 }, 330 331 /** 332 * @description append the query string to the end of the URL, and strip off any existing Hash tag 333 * @param {String} url The url to be appended to 334 * @returns uel with query string appended.. 335 */ 336 query : function(url, q) { 337 if ($.isNil(q)) { 338 return url; 339 } 340 // Strip any old hash tags 341 url = url.replace(/#.*$/, ''); 342 url += (/^\#/.test(q)) ? q : (/\?/.test( url ) ? "&" : "?") + q; 343 return url; 344 }, 345 346 347 // strings 348 //-------- 349 /** 350 * @description Adds the contents of 2 or more objets to 351 a destination object. 352 * @param {Object} dest The destination object to modify. 353 * @param {Object} mixin1-n An unlimited number of objects to add to the destination. 354 * @returns {Object} The modified destination object. 355 */ 356 extend: function (dest /*, mixin1, mixin2, ... */) { 357 $.each($.slice(arguments, 1), function (mixin, i) { 358 $.each(mixin, function (value, key) { 359 dest[key] = value; 360 }); 361 }); 362 return dest; 363 }, 364 365 /** 366 * @name Sfdc.canvas.prototypeOf 367 * @function 368 * @description Returns the prototype of the specified object 369 * @param {Object} obj The object for which to find the prototype. 370 * @returns {Object} The object that is the prototype of the given object. 371 */ 372 prototypeOf: function (obj) { 373 var nativ = Object.getPrototypeOf, 374 proto = '__proto__'; 375 if ($.isFunction(nativ)) { 376 return nativ.call(Object, obj); 377 } 378 else { 379 if (typeof {}[proto] === 'object') { 380 return obj[proto]; 381 } 382 else { 383 return obj.constructor.prototype; 384 } 385 } 386 }, 387 388 /** 389 * @description Adds a module to the global.Sfdc.canvas object 390 * @param {String} ns The namespace for the new module. 391 * @decl {Object} The module to add. 392 * @returns {Object} The global.Sfdc.canvas object with a new module added. 393 */ 394 module: function(ns, decl) { 395 var parts = ns.split('.'), parent = global.Sfdc.canvas, i, length; 396 397 // strip redundant leading global 398 if (parts[1] === 'canvas') { 399 parts = parts.slice(2); 400 } 401 402 length = parts.length; 403 for (i = 0; i < length; i += 1) { 404 // create a property if it doesn't exist 405 if ($.isUndefined(parent[parts[i]])) { 406 parent[parts[i]] = {}; 407 } 408 parent = parent[parts[i]]; 409 } 410 411 if ($.isFunction(decl)) { 412 decl = decl(); 413 } 414 return $.extend(parent, decl); 415 }, 416 417 // dom 418 //---- 419 /** 420 * @description Returns the DOM element in the current document with the given ID 421 * @param {String} id The id to find in the DOM 422 * @returns {DOMElement} The DOM element with the given ID, null if the element does not exist. 423 */ 424 byId: function (id) { 425 return doc.getElementById(id); 426 }, 427 /** 428 * @description Returns a set of DOM elements in the current document with the given class names 429 * @param {String} clazz The class names to find in the DOM. Multiple 430 classnames can be given, separated by whitespace 431 * @returns {Array} Set of DOM elements that all have the given class name 432 */ 433 byClass: function (clazz) { 434 return doc.getElementsByClassName(clazz); 435 }, 436 /** 437 * @description Returns the value for the given attribute name on the given DOM element. 438 * @param {DOMElement} el The element on which to check the attribute. 439 * @param {String} name The name of the attribute for which to find a value 440 * @returns {String} The given attribute's value. 441 */ 442 attr : function(el, name) { 443 var a = el.attributes, i; 444 for (i = 0; i < a.length; i += 1) { 445 if (name === a[i].name) { 446 return a[i].value; 447 } 448 } 449 }, 450 451 /** 452 * @description register a callback to be called after the DOM is ready. 453 * onReady. 454 * @param {Function} The callback function to be called. 455 */ 456 onReady : function(cb) { 457 if ($.isFunction(cb)) { 458 readyHandlers.push(cb); 459 } 460 } 461 462 }, 463 464 readyHandlers = [], 465 466 ready = function () { 467 ready = $.nop; 468 $.each(readyHandlers, $.invoker); 469 readyHandlers = null; 470 }, 471 472 /** 473 * @description 474 * @param {Function} cb The function to run when ready. 475 */ 476 canvas = function (cb) { 477 if ($.isFunction(cb)) { 478 readyHandlers.push(cb); 479 } 480 }; 481 482 (function () { 483 var ael = 'addEventListener', 484 tryReady = function () { 485 if (doc && /loaded|complete/.test(doc.readyState)) { 486 ready(); 487 } 488 else if (readyHandlers) { 489 if (!$.isNil(global.setTimeout)) { 490 global.setTimeout(tryReady, 30); 491 } 492 } 493 }; 494 495 if (doc && doc[ael]) { 496 doc[ael]('DOMContentLoaded', ready, false); 497 } 498 499 tryReady(); 500 501 if (global[ael]) { 502 global[ael]('load', ready, false); 503 } 504 else if (global.attachEvent) { 505 global.attachEvent('onload', ready); 506 } 507 508 }()); 509 510 $.each($, function (fn, name) { 511 canvas[name] = fn; 512 }); 513 514 if (!global.Sfdc) { 515 global.Sfdc = {}; 516 } 517 518 global.Sfdc.canvas = canvas; 519 if (!global.Sfdc.JSON) { 520 global.Sfdc.JSON = JSON; 521 } 522 523 524 }(this)); 525