1 /** 2 * Copyright (c) 2014, salesforce.com, inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 * that the following conditions are met: 7 * 8 * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 * following disclaimer. 10 * 11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 * 14 * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 * promote products derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 * POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 /*jslint bitwise:false */ 28 (function (global) { 29 30 "use strict"; 31 32 if (global.Sfdc && global.Sfdc.canvas && global.Sfdc.canvas.module) { 33 return; 34 } 35 36 // Preserve any external modules created on canvas 37 // (This is the case with controller.js) 38 var extmodules = {}; 39 if (global.Sfdc && global.Sfdc.canvas) { 40 for (var key in global.Sfdc.canvas) { 41 if (global.Sfdc.canvas.hasOwnProperty(key)) { 42 extmodules[key] = global.Sfdc.canvas[key]; 43 } 44 } 45 } 46 47 // cached references 48 //------------------ 49 50 var oproto = Object.prototype, 51 aproto = Array.prototype, 52 doc = global.document, 53 keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 54 55 /** 56 * @class Canvas 57 * @exports $ as Sfdc.canvas 58 */ 59 // $ functions 60 // The Sfdc.canvas global object is made available in the global scope. The reveal to the global scope is done later. 61 $ = { 62 63 // type utilities 64 //--------------- 65 66 /** 67 * @description Checks whether an object contains an uninherited property. 68 * @param {Object} obj The object to check 69 * @param {String} prop The property name to check for 70 * @returns {Boolean} <code>true</code> if the property exists for the object and isn't inherited; otherwise <code>false</code> 71 */ 72 hasOwn: function (obj, prop) { 73 return oproto.hasOwnProperty.call(obj, prop); 74 }, 75 76 /** 77 * @description Checks whether an object is currently undefined. 78 * @param {Object} value The object to check 79 * @returns {Boolean} <code>true</code> if the object or value is of type undefined; otherwise <code>false</code> 80 */ 81 isUndefined: function (value) { 82 var undef; 83 return value === undef; 84 }, 85 86 /** 87 * @description Checks whether an object is undefined, null, or an empty string. 88 * @param {Object} value The object to check 89 * @returns {Boolean} <code>true</code> if the object or value is of type undefined; otherwise <code>false</code> 90 */ 91 isNil: function (value) { 92 return $.isUndefined(value) || value === null || value === ""; 93 }, 94 95 /** 96 * @description Checks whether a value is a number. This function doesn't resolve strings to numbers. 97 * @param {Object} value Object to check 98 * @returns {Boolean} <code>true</code> if the object or value is a number; otherwise <code>false</code> 99 */ 100 isNumber: function (value) { 101 return !!(value === 0 || (value && value.toExponential && value.toFixed)); 102 }, 103 104 /** 105 * @description Checks whether an object is a function. 106 * @param {Object} value The object to check 107 * @returns {Boolean} <code>true</code> if the object or value is a function; otherwise <code>false</code> 108 */ 109 isFunction: function (value) { 110 return !!(value && value.constructor && value.call && value.apply); 111 }, 112 113 /** 114 * @description Checks whether an object is an array. 115 * @param {Object} value The object to check 116 * @function 117 * @returns {Boolean} <code>true</code> if the object or value is of type array; otherwise <code>false</code> 118 */ 119 isArray: Array.isArray || function (value) { 120 return oproto.toString.call(value) === '[object Array]'; 121 }, 122 123 /** 124 * @description Checks whether an object is the argument set for a function. 125 * @param {Object} value The object to check 126 * @returns {Boolean} <code>true</code> if the object or value is the argument set for a function; otherwise <code>false</code> 127 */ 128 isArguments: function (value) { 129 return !!(value && $.hasOwn(value, 'callee')); 130 }, 131 132 /** 133 * @description Checks whether a value is of type object and isn't null. 134 * @param {Object} value The object to check 135 * @returns {Boolean} <code>true</code> if the object or value is of type object; otherwise <code>false</code> 136 */ 137 isObject: function (value) { 138 return value !== null && typeof value === 'object'; 139 }, 140 141 /** 142 * @description Checks whether a value is of type string and isn't null. 143 * @param {Object} value The string to check 144 * @returns {Boolean} <code>true</code> if the string or value is of type string; otherwise <code>false</code> 145 */ 146 isString: function(value) { 147 return value !== null && typeof value == "string"; 148 }, 149 150 /** 151 * @description Checks whether the value appears to be JSON. 152 * @param {String} value The JSON string to check 153 * @returns {Boolean} <code>true</code> if the string starts and stops with {} , otherwise <code>false</code> 154 */ 155 appearsJson: function (value) { 156 return (/^\{.*\}$/).test(value); 157 }, 158 159 // common functions 160 //----------------- 161 162 /** 163 * @description An empty or blank function. 164 */ 165 nop: function () { 166 /* no-op */ 167 }, 168 169 /** 170 * @description Runs the specified function. 171 * @param {Function} fn The function to run 172 */ 173 invoker: function (fn) { 174 if ($.isFunction(fn)) { 175 fn(); 176 } 177 }, 178 179 /** 180 * @description Returns the argument. 181 * @param {Object} obj The object to return, untouched. 182 * @returns {Object} The argument used for this function call 183 */ 184 identity: function (obj) { 185 return obj; 186 }, 187 188 // @todo consider additional tests for: null, boolean, string, nan, element, regexp... as needed 189 /** 190 * @description Calls a defined function for each element in an object. 191 * @param {Object} obj The object to loop through. 192 The object can be an array, an array like object, or a map of properties. 193 * @param {Function} it The callback function to run for each element 194 * @param {Object} [ctx] The context object to be used for the callback function. 195 Defaults to the original object if not provided. 196 */ 197 each: function (obj, it, ctx) { 198 if ($.isNil(obj)) { 199 return; 200 } 201 var nativ = aproto.forEach, i = 0, l, key; 202 l = obj.length; 203 ctx = ctx || obj; 204 // @todo: looks like native method will not break on return false; maybe throw breaker {} 205 if (nativ && nativ === obj.forEach) { 206 obj.forEach(it, ctx); 207 } 208 else if ($.isNumber(l)) { // obj is an array-like object 209 while (i < l) { 210 if (it.call(ctx, obj[i], i, obj) === false) { 211 return; 212 } 213 i += 1; 214 } 215 } 216 else { 217 for (key in obj) { 218 if ($.hasOwn(obj, key) && it.call(ctx, obj[key], key, obj) === false) { 219 return; 220 } 221 } 222 } 223 }, 224 225 /** 226 * @description Convenience method to prepend a method with a fully qualified url, if the 227 * method does not begin with http protocol. 228 * @param {String} orig The original url to check 229 * @param {String} newUrl The new url to use if it does not begin with http(s) protocol. 230 * @returns {String} orig if the url begins with http, or newUrl if it does not. 231 */ 232 startsWithHttp: function(orig, newUrl) { 233 return !$.isString(orig) ? orig : (orig.substring(0, 4) === "http") ? orig : newUrl; 234 }, 235 236 237 /** 238 * @description Creates a new array with the results of calling the 239 function on each element in the object. 240 * @param {Object} obj The object to use 241 * @param {Function} it The callback function to run for each element 242 * @param {Object} [ctx] The context object to be used for the callback function. 243 Defaults to the original object if not provided. 244 * @returns {Array} The array that is created by calling the function on each 245 element in the object. 246 */ 247 map: function (obj, it, ctx) { 248 var results = [], nativ = aproto.map; 249 if ($.isNil(obj)) { 250 return results; 251 } 252 if (nativ && obj.map === nativ) { 253 return obj.map(it, ctx); 254 } 255 ctx = ctx || obj; 256 $.each(obj, function (value, i, list) { 257 results.push(it.call(ctx, value, i, list)); 258 }); 259 return results; 260 }, 261 262 /** 263 * @description Creates an array containing all the elements of the given object. 264 * @param {Object} obj The source object used to create the array 265 * @returns {Array} An array containing all the elements in the object. 266 */ 267 values: function (obj) { 268 return $.map(obj, $.identity); 269 }, 270 271 /** 272 * @description Creates a new array containing the selected elements of the given array. 273 * @param {Array} array The array to subset 274 * @param {Integer} [begin=0] The index that specifies where to start the selection 275 * @param {Integer} [end = array.length] The index that specifies where to end the selection 276 * @returns {Array} A new array that contains the selected elements. 277 */ 278 slice: function (array, begin, end) { 279 /* FF doesn't like undefined args for slice so ensure we call with args */ 280 return aproto.slice.call(array, $.isUndefined(begin) ? 0 : begin, $.isUndefined(end) ? array.length : end); 281 }, 282 283 /** 284 * @description Creates an array from an object. 285 * @param {Object} iterable The source object used to create the array. 286 * @returns {Array} The new array created from the object. 287 */ 288 toArray: function (iterable) { 289 if (!iterable) { 290 return []; 291 } 292 if (iterable.toArray) { 293 return iterable.toArray; 294 } 295 if ($.isArray(iterable)) { 296 return iterable; 297 } 298 if ($.isArguments(iterable)) { 299 return $.slice(iterable); 300 } 301 return $.values(iterable); 302 }, 303 304 /** 305 * @description Calculates the number of elements in an object. 306 * @param {Object} obj The object to size 307 * @returns {Integer} The number of elements in the object. 308 */ 309 size: function (obj) { 310 return $.toArray(obj).length; 311 }, 312 313 /** 314 * @description Returns the location of an element in an array. 315 * @param {Array} array The array to check 316 * @param {Object} item The item to search for within the array 317 * @returns {Integer} The index of the element within the array. 318 Returns -1 if the element isn't found. 319 */ 320 indexOf: function (array, item) { 321 var nativ = aproto.indexOf, i, l; 322 if (!array) { 323 return -1; 324 } 325 if (nativ && array.indexOf === nativ) { 326 return array.indexOf(item); 327 } 328 for (i = 0, l = array.length; i < l; i += 1) { 329 if (array[i] === item) { 330 return i; 331 } 332 } 333 return -1; 334 }, 335 336 /** 337 * @description Returns true if the object is null, or the object has no 338 * enumerable properties/attributes. 339 * @param {Object} obj The object to check 340 * @returns {Boolean} <code>true</code> if the object or value is null, or is an object with 341 * no enumerable properties/attributes. 342 */ 343 isEmpty: function(obj){ 344 if (obj === null){ 345 return true; 346 } 347 if ($.isArray(obj) || $.isString(obj)){ 348 return obj.length === 0; 349 } 350 for (var key in obj){ 351 if ($.hasOwn(obj, key)){ 352 return false; 353 } 354 } 355 return true; 356 }, 357 358 /** 359 * @description Removes an element from an array. 360 * @param {Array} array The array to modify 361 * @param {Object} item The element to remove from the array 362 */ 363 remove: function (array, item) { 364 var i = $.indexOf(array, item); 365 if (i >= 0) { 366 array.splice(i, 1); 367 } 368 }, 369 370 /** 371 * @description Serializes an object into a string that can be used as a URL query string. 372 * @param {Object|Array} a The array or object to serialize 373 * @param {Boolean} [encode=false] Indicates that the string should be encoded 374 * @returns {String} A string representing the object as a URL query string. 375 */ 376 param: function (a, encode) { 377 var s = []; 378 379 encode = encode || false; 380 381 function add( key, value ) { 382 383 if ($.isNil(value)) {return;} 384 value = $.isFunction(value) ? value() : value; 385 if ($.isArray(value)) { 386 $.each( value, function(v, n) { 387 add( key, v ); 388 }); 389 } 390 else { 391 if (encode) { 392 s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); 393 } 394 else { 395 s[ s.length ] = key + "=" + value; 396 } 397 } 398 } 399 400 if ( $.isArray(a)) { 401 $.each( a, function(v, n) { 402 add( n, v ); 403 }); 404 } else { 405 for ( var p in a ) { 406 if ($.hasOwn(a, p)) { 407 add( p, a[p]); 408 } 409 } 410 } 411 return s.join("&").replace(/%20/g, "+"); 412 }, 413 414 /** 415 * @description Converts a query string into an object. 416 * @param {String} q param1=value1¶m1=value2¶m2=value2 417 * @return {Object} {param1 : ['value1', 'value2'], param2 : 'value2'} 418 */ 419 objectify : function (q) { 420 var arr, obj = {}, i, p, n, v, e; 421 if ($.isNil(q)) {return obj;} 422 if (q.substring(0, 1) == '?') { 423 q = q.substring(1); 424 } 425 arr = q.split('&'); 426 for (i = 0; i < arr.length; i += 1) { 427 p = arr[i].split('='); 428 n = p[0]; 429 v = p[1]; 430 e = obj[n]; 431 if (!$.isNil(e)) { 432 if ($.isArray(e)) { 433 e[e.length] = v; 434 } 435 else { 436 obj[n] = []; 437 obj[n][0] = e; 438 obj[n][1] = v; 439 } 440 } 441 else { 442 obj[n] = v; 443 } 444 } 445 return obj; 446 }, 447 448 /** 449 * @description Strips out the URL to {scheme}://{host}:{port}. Removes any path and query string information. 450 * @param {String} url The URL to be modified 451 * @returns {String} The {scheme}://{host}:{port} portion of the URL. 452 */ 453 stripUrl : function(url) { 454 return ($.isNil(url)) ? null : url.replace( /([^:]+:\/\/[^\/\?#]+).*/, '$1'); 455 }, 456 457 /** 458 * @description Appends the query string to the end of the URL and removes any hash tag. 459 * @param {String} url The URL to be appended to 460 * @returns The URL with the query string appended. 461 */ 462 query : function(url, q) { 463 if ($.isNil(q)) { 464 return url; 465 } 466 // Strip any old hash tags 467 url = url.replace(/#.*$/, ''); 468 url += (/^\#/.test(q)) ? q : (/\?/.test( url ) ? "&" : "?") + q; 469 return url; 470 }, 471 472 473 // strings 474 //-------- 475 /** 476 * @description Adds the contents of two or more objects to 477 a destination object. 478 * @param {Object} dest The destination object to modify 479 * @param {Object} mixin1-n An unlimited number of objects to add to the destination object 480 * @returns {Object} The modified destination object 481 */ 482 extend: function (dest /*, mixin1, mixin2, ... */) { 483 $.each($.slice(arguments, 1), function (mixin, i) { 484 $.each(mixin, function (value, key) { 485 dest[key] = value; 486 }); 487 }); 488 return dest; 489 }, 490 491 /** 492 * @description Determines if a string ends with a particular suffix. 493 * @param {String} str The string to check 494 * @param {String} suffix The suffix to check for 495 * @returns {boolean} <code>true</code>, if the string ends with suffix; otherwise, <code>false</code>. 496 */ 497 endsWith: function (str, suffix) { 498 return str.indexOf(suffix, str.length - suffix.length) !== -1; 499 }, 500 501 capitalize: function(str) { 502 return str.charAt(0).toUpperCase() + str.slice(1); 503 }, 504 505 uncapitalize: function(str) { 506 return str.charAt(0).toLowerCase() + str.slice(1); 507 }, 508 509 /** 510 * @description decode a base 64 string. 511 * @param {String} str - base64 encoded string 512 * @return decoded string 513 */ 514 decode : function(str) { 515 var output = [], chr1, chr2, chr3 = "", enc1, enc2, enc3, enc4 = "", i = 0; 516 str = str.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 517 518 do { 519 enc1 = keyStr.indexOf(str.charAt(i++)); 520 enc2 = keyStr.indexOf(str.charAt(i++)); 521 enc3 = keyStr.indexOf(str.charAt(i++)); 522 enc4 = keyStr.indexOf(str.charAt(i++)); 523 chr1 = (enc1 << 2) | (enc2 >> 4); 524 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 525 chr3 = ((enc3 & 3) << 6) | enc4; 526 output.push(String.fromCharCode(chr1)); 527 if (enc3 !== 64) { 528 output.push(String.fromCharCode(chr2)); 529 } 530 if (enc4 !== 64) { 531 output.push(String.fromCharCode(chr3)); 532 } 533 chr1 = chr2 = chr3 = ""; 534 enc1 = enc2 = enc3 = enc4 = ""; 535 } while (i < str.length); 536 return output.join(''); 537 }, 538 539 540 // Events 541 //-------- 542 /** 543 * @description Validates the event name. 544 * @param {String} name Name of the event; can include the namespace (namespace.name). 545 * @param {String} res Reserved namespace name to allow against default 546 * @returns {int} error code, 0 if valid 547 */ 548 validEventName : function(name, res) { 549 var ns, parts = name.split(/\./), 550 regex = /^[$A-Z_][0-9A-Z_$]*$/i, 551 reserved = { 552 'sfdc':true, 'canvas':true, 553 'force':true, 'salesforce':true, 'chatter':true 554 }; 555 $.each($.isArray(res) ? res : [res], function (v) { 556 reserved[v] = false; 557 }); 558 if (parts.length > 2) { 559 return 1; 560 } 561 if (parts.length === 2) { 562 ns = parts[0].toLowerCase(); 563 if (reserved[ns]) { 564 return 2; 565 } 566 } 567 if (!regex.test(parts[0]) || !regex.test(parts[1])) { 568 return 3; 569 } 570 return 0; 571 }, 572 573 574 /** 575 * @name Sfdc.canvas.prototypeOf 576 * @function 577 * @description Returns the prototype of the specified object. 578 * @param {Object} obj The object for which to find the prototype 579 * @returns {Object} The object that is the prototype of the given object. 580 */ 581 prototypeOf: function (obj) { 582 var nativ = Object.getPrototypeOf, 583 proto = '__proto__'; 584 if ($.isFunction(nativ)) { 585 return nativ.call(Object, obj); 586 } 587 else { 588 if (typeof {}[proto] === 'object') { 589 return obj[proto]; 590 } 591 else { 592 return obj.constructor.prototype; 593 } 594 } 595 }, 596 597 /** 598 * @description Adds a module to the global.Sfdc.canvas object. 599 * @param {String} ns The namespace for the new module 600 * @decl {Object} The module to add. 601 * @returns {Object} The global.Sfdc.canvas object with a new module added. 602 */ 603 module: function(ns, decl) { 604 var parts = ns.split('.'), parent = global.Sfdc.canvas, i, length; 605 606 // strip redundant leading global 607 if (parts[1] === 'canvas') { 608 parts = parts.slice(2); 609 } 610 611 length = parts.length; 612 for (i = 0; i < length; i += 1) { 613 // create a property if it doesn't exist 614 if ($.isUndefined(parent[parts[i]])) { 615 parent[parts[i]] = {}; 616 } 617 parent = parent[parts[i]]; 618 } 619 620 if ($.isFunction(decl)) { 621 decl = decl(); 622 } 623 return $.extend(parent, decl); 624 }, 625 626 // dom 627 //---- 628 // Returns window.document element when invoked from a browser otherwise mocked document for 629 // testing. (Do not add JSDoc tags for this one) 630 document: function() { 631 return doc; 632 }, 633 /** 634 * @description Returns the DOM element with the given ID in the current document. 635 * @param {String} id The ID of the DOM element 636 * @returns {DOMElement} The DOM element with the given ID. Returns null if the element doesn't exist. 637 */ 638 byId: function (id) { 639 return doc.getElementById(id); 640 }, 641 /** 642 * @description Returns a set of DOM elements with the given class names in the current document. 643 * @param {String} class The class names to find in the DOM; multiple 644 classnames can be passed, separated by whitespace 645 * @returns {Array} Set of DOM elements that all have the given class name 646 */ 647 byClass: function (clazz) { 648 return doc.getElementsByClassName(clazz); 649 }, 650 /** 651 * @description Returns the value for the given attribute name on the given DOM element. 652 * @param {DOMElement} el The element on which to check the attribute. 653 * @param {String} name The name of the attribute for which to find a value. 654 * @returns {String} The given attribute's value. 655 */ 656 attr : function(el, name) { 657 var a = el.attributes, i; 658 for (i = 0; i < a.length; i += 1) { 659 if (name === a[i].name) { 660 return a[i].value; 661 } 662 } 663 }, 664 665 /** 666 * @description Registers a callback to be called after the DOM is ready. 667 * @param {Function} cb The callback function to be called 668 */ 669 onReady : function(cb) { 670 if ($.isFunction(cb)) { 671 readyHandlers.push(cb); 672 } 673 }, 674 675 console : (function() { 676 677 var enabled = false; 678 679 // Prevent errors in browsers without console.log 680 if (window && !window.console) {window.console = {};} 681 if (window && !window.console.log) {window.console.log = function(){};} 682 if (window && !window.console.error) {window.console.error = function(){};} 683 684 function isSessionStorage() { 685 try { 686 return 'sessionStorage' in window && window.sessionStorage !== null; 687 } catch (e) { 688 return false; 689 } 690 } 691 692 /** 693 * @description Writes a message to the console. You may pass as many arguments as you'd like. 694 * The first argument to log may be a string containing printf-like string substitution patterns. 695 * Note: this function will be ignored for versions of IE that don't support console.log 696 * 697 * @public 698 * @name Sfdc.canvas.console#log 699 * @function 700 * @param {Object} arguments Objects(s) to pass to the logger 701 * @example 702 * // Log a simple string to the console if the logger is enbabled. 703 * Sfdc.canvas.console.log("Hello world"); 704 * 705 * @example 706 * // Log a formatted string to the console if the logger is enbabled. 707 * Sfdc.canvas.console.log("Hello %s", "world"); 708 * 709 * @example 710 * // Log an object to the console if the logger is enbabled. 711 * Sfdc.canvas.console.log({hello : "Hello", world : "World"}); 712 * 713 */ 714 function log() {} 715 716 /** 717 * @description Writes an error message to the console. You may pass as many arguments as you'd like. 718 * The first argument to log may be a string containing printf-like string substitution patterns. 719 * Note: this function will be ignored for versions of IE that don't support console.error 720 * 721 * @public 722 * @name Sfdc.canvas.console#error 723 * @function 724 * @param {Object} arguments Objects(s) to pass to the logger 725 726 * @example 727 * // Log a simple string to the console if the logger is enbabled. 728 * Sfdc.canvas.console.error("Something wrong"); 729 * 730 * @example 731 * // Log a formatted string to the console if the logger is enbabled. 732 * Sfdc.canvas.console.error("Bad Status %i", 404); 733 * 734 * @example 735 * // Log an object to the console if the logger is enbabled. 736 * Sfdc.canvas.console.error({text : "Not Found", status : 404}); 737 * 738 */ 739 function error() {} 740 741 function activate() { 742 if (Function.prototype.bind) { 743 log = Function.prototype.bind.call(console.log, console); 744 error = Function.prototype.bind.call(console.error, console); 745 } 746 else { 747 log = function() { 748 Function.prototype.apply.call(console.log, console, arguments); 749 }; 750 error = function() { 751 Function.prototype.apply.call(console.error, console, arguments); 752 }; 753 } 754 } 755 756 function deactivate() { 757 log = function() {}; 758 error = function() {}; 759 } 760 761 /** 762 * @description Enable logging. subsequent calls to log() or error() will be displayed on the javascript console. 763 * This command can be typed from the javascript console. 764 * 765 * @example 766 * // Enable logging 767 * Sfdc.canvas.console.enable(); 768 */ 769 function enable() { 770 enabled = true; 771 if (isSessionStorage()) {sessionStorage.setItem("canvas_console", "true");} 772 activate(); 773 } 774 775 /** 776 * @description Disable logging. Subsequent calls to log() or error() will be ignored. This command can be typed 777 * from the javascript console. 778 * 779 * @example 780 * // Disable logging 781 * Sfdc.canvas.console.disable(); 782 */ 783 function disable() { 784 enabled = false; 785 if (isSessionStorage()) {sessionStorage.setItem("canvas_console", "false");} 786 deactivate(); 787 } 788 789 // Survive page refresh, if enabled or disable previously honor it. 790 // This is only called once when the page is loaded 791 enabled = (isSessionStorage() && sessionStorage.getItem("canvas_console") === "true"); 792 if (enabled) {activate();} else {deactivate();} 793 794 return { 795 enable : enable, 796 disable : disable, 797 log : log, 798 error : error 799 }; 800 }()) 801 }, 802 803 readyHandlers = [], 804 805 /** 806 * @description 807 * @param {Function} cb The function to run when ready. 808 */ 809 canvas = function (cb) { 810 if ($.isFunction(cb)) { 811 readyHandlers.push(cb); 812 } 813 }; 814 815 /** 816 * Provide a consistent/performant DOMContentLoaded across all browsers 817 * Implementation was based off of the following tutorial 818 * http://javascript.info/tutorial/onload-ondomcontentloaded?fromEmail=1 819 */ 820 (function () { 821 822 var called = false, isFrame, fn; 823 824 function ready() { 825 if (called) {return;} 826 called = true; 827 ready = $.nop; 828 $.each(readyHandlers, $.invoker); 829 readyHandlers = []; 830 } 831 832 function tryScroll(){ 833 if (called) {return;} 834 try { 835 document.documentElement.doScroll("left"); 836 ready(); 837 } catch(e) { 838 setTimeout(tryScroll, 30); 839 } 840 } 841 842 if ( document.addEventListener ) { // native event 843 document.addEventListener( "DOMContentLoaded", ready, false ); 844 } else if ( document.attachEvent ) { // IE 845 846 try { 847 isFrame = self !== top; 848 } catch(e) {} 849 850 // IE, the document is not inside a frame 851 if ( document.documentElement.doScroll && !isFrame ) { 852 tryScroll(); 853 } 854 855 // IE, the document is inside a frame 856 document.attachEvent("onreadystatechange", function(){ 857 if ( document.readyState === "complete" ) { 858 ready(); 859 } 860 }); 861 } 862 863 // Old browsers 864 if (window.addEventListener) { 865 window.addEventListener('load', ready, false); 866 } else if (window.attachEvent) { 867 window.attachEvent('onload', ready); 868 } else { 869 fn = window.onload; // very old browser, copy old onload 870 window.onload = function() { // replace by new onload and call the old one 871 if (fn) {fn();} 872 ready(); 873 }; 874 } 875 }()); 876 877 $.each($, function (fn, name) { 878 canvas[name] = fn; 879 }); 880 881 // Add those external modules back in 882 $.each(extmodules, function (fn, name) { 883 canvas[name] = fn; 884 }); 885 886 887 (function () { 888 var method; 889 var noop = function () { }; 890 var methods = [ 891 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 892 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 893 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 894 'timeStamp', 'trace', 'warn' 895 ]; 896 var length = methods.length; 897 var console = (typeof window !== 'undefined' && window.console) ? window.console : {}; 898 899 while (length--) { 900 method = methods[length]; 901 902 // Only stub undefined methods. 903 if (!console[method]) { 904 console[method] = noop; 905 } 906 } 907 908 }()); 909 910 911 if (!global.Sfdc) { 912 global.Sfdc = {}; 913 } 914 915 global.Sfdc.canvas = canvas; 916 917 918 }(this)); 919