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 for 30 * @returns {Boolean} <code>true</code> if the property exists for the object and isn't 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 an 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 The 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 a value is of type object and isn't 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 /** 102 * @description Checks whether a value is of type string and isn't null. 103 * @param {Object} value The string to check 104 * @returns {Boolean} <code>true</code> if the string or value is of type string; otherwise <code>false</code> 105 */ 106 isString: function(value) { 107 return value !== null && typeof value == "string"; 108 }, 109 110 /** 111 * @description Checks whether the value appears to be JSON. 112 * @param {String} value The JSON string to check 113 * @returns {Boolean} <code>true</code> if the string starts and stops with {} , otherwise <code>false</code> 114 */ 115 appearsJson: function (value) { 116 return (/^\{.*\}$/).test(value); 117 }, 118 119 120 // common functions 121 //----------------- 122 123 /** 124 * @description An empty or blank function. 125 */ 126 nop: function () { 127 /* no-op */ 128 }, 129 130 /** 131 * @description Runs the specified function. 132 * @param {Function} fn The function to run 133 */ 134 invoker: function (fn) { 135 if ($.isFunction(fn)) { 136 fn(); 137 } 138 }, 139 140 /** 141 * @description Returns the argument. 142 * @param {Object} obj The object to return, untouched. 143 * @returns {Object} The argument used for this function call 144 */ 145 identity: function (obj) { 146 return obj; 147 }, 148 149 // @todo consider additional tests for: null, boolean, string, nan, element, regexp... as needed 150 /** 151 * @description Calls a defined function for each element in an object. 152 * @param {Object} obj The object to loop through. 153 The object can be an array, an array like object, or a map of properties. 154 * @param {Function} it The callback function to run for each element 155 * @param {Object} [ctx] The context object to be used for the callback function. 156 Defaults to the original object if not provided. 157 */ 158 each: function (obj, it, ctx) { 159 if ($.isNil(obj)) { 160 return; 161 } 162 var nativ = aproto.forEach, i = 0, l, key; 163 l = obj.length; 164 ctx = ctx || obj; 165 // @todo: looks like native method will not break on return false; maybe throw breaker {} 166 if (nativ && nativ === obj.forEach) { 167 obj.forEach(it, ctx); 168 } 169 else if ($.isNumber(l)) { // obj is an array-like object 170 while (i < l) { 171 if (it.call(ctx, obj[i], i, obj) === false) { 172 return; 173 } 174 i += 1; 175 } 176 } 177 else { 178 for (key in obj) { 179 if ($.hasOwn(obj, key) && it.call(ctx, obj[key], key, obj) === false) { 180 return; 181 } 182 } 183 } 184 }, 185 186 /** 187 * @description Creates a new array with the results of calling the 188 function on each element in the object. 189 * @param {Object} obj The object to use 190 * @param {Function} it The callback function to run for each element 191 * @param {Object} [ctx] The context object to be used for the callback function. 192 Defaults to the original object if not provided. 193 * @returns {Array} The array that is created by calling the function on each 194 element in the object. 195 */ 196 map: function (obj, it, ctx) { 197 var results = [], nativ = aproto.map; 198 if ($.isNil(obj)) { 199 return results; 200 } 201 if (nativ && obj.map === nativ) { 202 return obj.map(it, ctx); 203 } 204 ctx = ctx || obj; 205 $.each(obj, function (value, i, list) { 206 results.push(it.call(ctx, value, i, list)); 207 }); 208 return results; 209 }, 210 211 /** 212 * @description Creates an array containing all the elements of the given object. 213 * @param {Object} obj The source object used to create the array 214 * @returns {Array} An array containing all the elements in the object. 215 */ 216 values: function (obj) { 217 return $.map(obj, $.identity); 218 }, 219 220 /** 221 * @description Creates a new array containing the selected elements of the given array. 222 * @param {Array} array The array to subset 223 * @param {Integer} [begin=0] The index that specifies where to start the selection 224 * @param {Integer} [end = array.length] The index that specifies where to end the selection 225 * @returns {Array} A new array that contains the selected elements. 226 */ 227 slice: function (array, begin, end) { 228 /* FF doesn't like undefined args for slice so ensure we call with args */ 229 return aproto.slice.call(array, $.isUndefined(begin) ? 0 : begin, $.isUndefined(end) ? array.length : end); 230 }, 231 232 /** 233 * @description Creates an array from an object. 234 * @param {Object} iterable The source object used to create the array. 235 * @returns {Array} The new array created from the object. 236 */ 237 toArray: function (iterable) { 238 if (!iterable) { 239 return []; 240 } 241 if (iterable.toArray) { 242 return iterable.toArray; 243 } 244 if ($.isArray(iterable)) { 245 return iterable; 246 } 247 if ($.isArguments(iterable)) { 248 return $.slice(iterable); 249 } 250 return $.values(iterable); 251 }, 252 253 /** 254 * @description Calculates the number of elements in an object. 255 * @param {Object} obj The object to size 256 * @returns {Integer} The number of elements in the object. 257 */ 258 size: function (obj) { 259 return $.toArray(obj).length; 260 }, 261 262 /** 263 * @description Returns the location of an element in an array. 264 * @param {Array} array The array to check 265 * @param {Object} item The item to search for within the array 266 * @returns {Integer} The index of the element within the array. 267 Returns -1 if the element isn't found. 268 */ 269 indexOf: function (array, item) { 270 var nativ = aproto.indexOf, i, l; 271 if (!array) { 272 return -1; 273 } 274 if (nativ && array.indexOf === nativ) { 275 return array.indexOf(item); 276 } 277 for (i = 0, l = array.length; i < l; i += 1) { 278 if (array[i] === item) { 279 return i; 280 } 281 } 282 return -1; 283 }, 284 285 /** 286 * @description Removes an element from an array. 287 * @param {Array} array The array to modify 288 * @param {Object} item The element to remove from the array 289 */ 290 remove: function (array, item) { 291 var i = $.indexOf(array, item); 292 if (i >= 0) { 293 array.splice(i, 1); 294 } 295 }, 296 297 /** 298 * @description Serializes an object into a string that can be used as a URL query string. 299 * @param {Object|Array} a The array or object to serialize 300 * @param {Boolean} [encode=false] Indicates that the string should be encoded 301 * @returns {String} A string representing the object as a URL query string. 302 */ 303 param: function (a, encode) { 304 var s = []; 305 306 encode = encode || false; 307 308 function add( key, value ) { 309 310 if ($.isNil(value)) {return;} 311 value = $.isFunction(value) ? value() : value; 312 if ($.isArray(value)) { 313 $.each( value, function(v, n) { 314 add( key, v ); 315 }); 316 } 317 else { 318 if (encode) { 319 s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); 320 } 321 else { 322 s[ s.length ] = key + "=" + value; 323 } 324 } 325 } 326 327 if ( $.isArray(a)) { 328 $.each( a, function(v, n) { 329 add( n, v ); 330 }); 331 } else { 332 for ( var p in a ) { 333 if ($.hasOwn(a, p)) { 334 add( p, a[p]); 335 } 336 } 337 } 338 return s.join("&").replace(/%20/g, "+"); 339 }, 340 341 /** 342 * @description Converts a query string into an object. 343 * Note: this doesn't handle multi-value parameters. For instance, 344 * passing in <code>?param=value1&​param=value2</code> will not return <code>['value1', 'value2']</code> 345 * 346 * @param {String} q ?param1=value1¶m2=value2 347 * @return {Object} {param1 : 'value1', param2 : 'value2'} 348 */ 349 objectify : function (q) { 350 var o = {}; 351 q.replace( 352 new RegExp("([^?=&]+)(=([^&]*))?", "g"), 353 function($0, $1, $2, $3) { o[$1] = $3; } 354 ); 355 return o; 356 }, 357 358 /** 359 * @description Strips out the URL to {scheme}://{host}:{port}. Removes any path and query string information. 360 * @param {String} url The URL to be modified 361 * @returns {String} The {scheme}://{host}:{port} portion of the URL. 362 */ 363 stripUrl : function(url) { 364 return ($.isNil(url)) ? null : url.replace( /([^:]+:\/\/[^\/\?#]+).*/, '$1'); 365 }, 366 367 /** 368 * @description Appends the query string to the end of the URL and removes any hash tag. 369 * @param {String} url The URL to be appended to 370 * @returns The URL with the query string appended. 371 */ 372 query : function(url, q) { 373 if ($.isNil(q)) { 374 return url; 375 } 376 // Strip any old hash tags 377 url = url.replace(/#.*$/, ''); 378 url += (/^\#/.test(q)) ? q : (/\?/.test( url ) ? "&" : "?") + q; 379 return url; 380 }, 381 382 383 // strings 384 //-------- 385 /** 386 * @description Adds the contents of two or more objects to 387 a destination object. 388 * @param {Object} dest The destination object to modify 389 * @param {Object} mixin1-n An unlimited number of objects to add to the destination object 390 * @returns {Object} The modified destination object 391 */ 392 extend: function (dest /*, mixin1, mixin2, ... */) { 393 $.each($.slice(arguments, 1), function (mixin, i) { 394 $.each(mixin, function (value, key) { 395 dest[key] = value; 396 }); 397 }); 398 return dest; 399 }, 400 401 /** 402 * @description Determines if a string ends with a particular suffix. 403 * @param {String} str The string to check 404 * @param {String} suffix The suffix to check for 405 * @returns {boolean} <code>true</code>, if the string ends with suffix; otherwise, <code>false</code>. 406 */ 407 endsWith: function (str, suffix) { 408 return str.indexOf(suffix, str.length - suffix.length) !== -1; 409 }, 410 411 capitalize: function(str) { 412 return str.charAt(0).toUpperCase() + str.slice(1); 413 }, 414 415 uncapitalize: function(str) { 416 return str.charAt(0).toLowerCase() + str.slice(1); 417 }, 418 419 // Events 420 //-------- 421 /** 422 * @description Validates the event name. 423 * @param {String} name Name of the event; can include the namespace (namespace.name). 424 * @param {String} res Reserved namespace name to allow against default 425 * @returns {int} error code, 0 if valid 426 */ 427 validEventName : function(name, res) { 428 var ns, parts = name.split(/\./), 429 regex = /^[$A-Z_][0-9A-Z_$]*$/i, 430 reserved = { 431 'sfdc':true, 'canvas':true, 432 'force':true, 'salesforce':true, 'chatter':true 433 }; 434 $.each($.isArray(res) ? res : [res], function (v) { 435 reserved[v] = false; 436 }); 437 if (parts.length > 2) { 438 return 1; 439 } 440 if (parts.length === 2) { 441 ns = parts[0].toLowerCase(); 442 if (reserved[ns]) { 443 return 2; 444 } 445 } 446 if (!regex.test(parts[0]) || !regex.test(parts[1])) { 447 return 3; 448 } 449 return 0; 450 }, 451 452 453 /** 454 * @name Sfdc.canvas.prototypeOf 455 * @function 456 * @description Returns the prototype of the specified object. 457 * @param {Object} obj The object for which to find the prototype 458 * @returns {Object} The object that is the prototype of the given object. 459 */ 460 prototypeOf: function (obj) { 461 var nativ = Object.getPrototypeOf, 462 proto = '__proto__'; 463 if ($.isFunction(nativ)) { 464 return nativ.call(Object, obj); 465 } 466 else { 467 if (typeof {}[proto] === 'object') { 468 return obj[proto]; 469 } 470 else { 471 return obj.constructor.prototype; 472 } 473 } 474 }, 475 476 /** 477 * @description Adds a module to the global.Sfdc.canvas object. 478 * @param {String} ns The namespace for the new module 479 * @decl {Object} The module to add. 480 * @returns {Object} The global.Sfdc.canvas object with a new module added. 481 */ 482 module: function(ns, decl) { 483 var parts = ns.split('.'), parent = global.Sfdc.canvas, i, length; 484 485 // strip redundant leading global 486 if (parts[1] === 'canvas') { 487 parts = parts.slice(2); 488 } 489 490 length = parts.length; 491 for (i = 0; i < length; i += 1) { 492 // create a property if it doesn't exist 493 if ($.isUndefined(parent[parts[i]])) { 494 parent[parts[i]] = {}; 495 } 496 parent = parent[parts[i]]; 497 } 498 499 if ($.isFunction(decl)) { 500 decl = decl(); 501 } 502 return $.extend(parent, decl); 503 }, 504 505 // dom 506 //---- 507 // Returns window.document element when invoked from a browser otherwise mocked document for 508 // testing. (Do not add JSDoc tags for this one) 509 document: function() { 510 return doc; 511 }, 512 /** 513 * @description Returns the DOM element with the given ID in the current document. 514 * @param {String} id The ID of the DOM element 515 * @returns {DOMElement} The DOM element with the given ID. Returns null if the element doesn't exist. 516 */ 517 byId: function (id) { 518 return doc.getElementById(id); 519 }, 520 /** 521 * @description Returns a set of DOM elements with the given class names in the current document. 522 * @param {String} class The class names to find in the DOM; multiple 523 classnames can be passed, separated by whitespace 524 * @returns {Array} Set of DOM elements that all have the given class name 525 */ 526 byClass: function (clazz) { 527 return doc.getElementsByClassName(clazz); 528 }, 529 /** 530 * @description Returns the value for the given attribute name on the given DOM element. 531 * @param {DOMElement} el The element on which to check the attribute. 532 * @param {String} name The name of the attribute for which to find a value. 533 * @returns {String} The given attribute's value. 534 */ 535 attr : function(el, name) { 536 var a = el.attributes, i; 537 for (i = 0; i < a.length; i += 1) { 538 if (name === a[i].name) { 539 return a[i].value; 540 } 541 } 542 }, 543 544 /** 545 * @description Registers a callback to be called after the DOM is ready. 546 * @param {Function} cb The callback function to be called 547 */ 548 onReady : function(cb) { 549 if ($.isFunction(cb)) { 550 readyHandlers.push(cb); 551 } 552 } 553 }, 554 555 readyHandlers = [], 556 557 ready = function () { 558 ready = $.nop; 559 $.each(readyHandlers, $.invoker); 560 readyHandlers = null; 561 }, 562 563 /** 564 * @description 565 * @param {Function} cb The function to run when ready. 566 */ 567 canvas = function (cb) { 568 if ($.isFunction(cb)) { 569 readyHandlers.push(cb); 570 } 571 }; 572 573 (function () { 574 var ael = 'addEventListener', 575 tryReady = function () { 576 if (doc && /loaded|complete/.test(doc.readyState)) { 577 ready(); 578 } 579 else if (readyHandlers) { 580 if (!$.isNil(global.setTimeout)) { 581 global.setTimeout(tryReady, 30); 582 } 583 } 584 }; 585 586 if (doc && doc[ael]) { 587 doc[ael]('DOMContentLoaded', ready, false); 588 } 589 590 tryReady(); 591 592 if (global[ael]) { 593 global[ael]('load', ready, false); 594 } 595 else if (global.attachEvent) { 596 global.attachEvent('onload', ready); 597 } 598 599 }()); 600 601 $.each($, function (fn, name) { 602 canvas[name] = fn; 603 }); 604 605 if (!global.Sfdc) { 606 global.Sfdc = {}; 607 } 608 609 global.Sfdc.canvas = canvas; 610 611 612 }(this)); 613