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