1 var _slice = Array.prototype.slice;
  2 
  3 /**
  4  * Takes id and returns an element with that id (if one exists in a document)
  5  * @method getById
  6  * @memberOf fabric.util
  7  * @param {String|HTMLElement} id
  8  * @return {HTMLElement|null}
  9  */
 10 function getById(id) {
 11   return typeof id === 'string' ? document.getElementById(id) : id;
 12 }
 13 
 14 /**
 15  * Converts an array-like object (e.g. arguments or NodeList) to an array
 16  * @method toArray
 17  * @memberOf fabric.util
 18  * @param {Object} arrayLike
 19  * @return {Array}
 20  */
 21 function toArray(arrayLike) {
 22   return _slice.call(arrayLike, 0);
 23 }
 24 
 25 try {
 26   var sliceCanConvertNodelists = toArray(document.childNodes) instanceof Array;
 27 }
 28 catch(err) { }
 29 
 30 if (!sliceCanConvertNodelists) {
 31   toArray = function(arrayLike) {
 32     var arr = new Array(arrayLike.length), i = arrayLike.length;
 33     while (i--) {
 34       arr[i] = arrayLike[i];
 35     }
 36     return arr;
 37   };
 38 }
 39 
 40 /**
 41  * Creates specified element with specified attributes
 42  * @method makeElement
 43  * @memberOf fabric.util
 44  * @param {String} tagName Type of an element to create
 45  * @param {Object} [attributes] Attributes to set on an element
 46  * @return {HTMLElement} Newly created element
 47  */
 48 function makeElement(tagName, attributes) {
 49   var el = document.createElement(tagName);
 50   for (var prop in attributes) {
 51     if (prop === 'class') {
 52       el.className = attributes[prop];
 53     }
 54     else if (prop === 'for') {
 55       el.htmlFor = attributes[prop];
 56     }
 57     else {
 58       el.setAttribute(prop, attributes[prop]);
 59     }
 60   }
 61   return el;
 62 }
 63 
 64 /**
 65  * Adds class to an element
 66  * @method addClass
 67  * @memberOf fabric.util
 68  * @param {HTMLElement} element Element to add class to
 69  * @param {String} className Class to add to an element
 70  */
 71 function addClass(element, className) {
 72   if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
 73     element.className += (element.className ? ' ' : '') + className;
 74   }  
 75 }
 76 
 77 /**
 78  * Wraps element with another element
 79  * @method wrapElement
 80  * @memberOf fabric.util
 81  * @param {HTMLElement} element Element to wrap
 82  * @param {HTMLElement|String} wrapper Element to wrap with
 83  * @param {Object} [attributes] Attributes to set on a wrapper
 84  * @return {HTMLElement} wrapper
 85  */
 86 function wrapElement(element, wrapper, attributes) {
 87   if (typeof wrapper === 'string') {
 88     wrapper = makeElement(wrapper, attributes);
 89   }
 90   if (element.parentNode) {
 91     element.parentNode.replaceChild(wrapper, element);
 92   }
 93   wrapper.appendChild(element);
 94   return wrapper;
 95 }
 96 
 97 /**
 98  * Returns offset for a given element
 99  * @method getElementOffset
100  * @function
101  * @memberOf fabric.util
102  * @param {HTMLElement} element Element to get offset for
103  * @return {Object} Object with "left" and "top" properties
104  */
105 function getElementOffset(element) {
106   // TODO (kangax): need to fix this method
107   var valueT = 0, valueL = 0;
108   do {
109     valueT += element.offsetTop  || 0;
110     valueL += element.offsetLeft || 0;
111     element = element.offsetParent;
112   } 
113   while (element);
114   return ({ left: valueL, top: valueT });
115 }
116 
117 (function () {
118   var style = document.documentElement.style;
119 
120   var selectProp = 'userSelect' in style
121     ? 'userSelect'
122     : 'MozUserSelect' in style 
123       ? 'MozUserSelect' 
124       : 'WebkitUserSelect' in style 
125         ? 'WebkitUserSelect' 
126         : 'KhtmlUserSelect' in style 
127           ? 'KhtmlUserSelect' 
128           : '';
129   
130   /**
131    * Makes element unselectable
132    * @method makeElementUnselectable
133    * @memberOf fabric.util
134    * @param {HTMLElement} element Element to make unselectable
135    * @return {HTMLElement} Element that was passed in
136    */
137   function makeElementUnselectable(element) {
138     if (typeof element.onselectstart !== 'undefined') {
139       element.onselectstart = fabric.util.falseFunction;
140     }
141     if (selectProp) {
142       element.style[selectProp] = 'none';
143     }
144     else if (typeof element.unselectable == 'string') {
145       element.unselectable = 'on';
146     }
147     return element;
148   }
149   
150   fabric.util.makeElementUnselectable = makeElementUnselectable;
151 })();
152 
153 (function() {
154   
155   /**
156    * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
157    * @method getScript
158    * @memberOf fabric.util
159    * @param {String} url URL of a script to load
160    * @param {Function} callback Callback to execute when script is finished loading
161    */
162   function getScript(url, callback) {
163   	var headEl = document.getElementsByTagName("head")[0],
164   	    scriptEl = document.createElement('script'), 
165   	    loading = true;
166 
167   	scriptEl.type = 'text/javascript';
168   	scriptEl.setAttribute('runat', 'server');
169   	
170   	/** @ignore */
171   	scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
172   	  if (loading) {
173   	    if (typeof this.readyState == 'string' && 
174   	        this.readyState !== 'loaded' && 
175   	        this.readyState !== 'complete') return;
176     	  loading = false;
177     		callback(e || window.event);
178     		scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
179     	}
180   	};
181   	scriptEl.src = url;
182   	headEl.appendChild(scriptEl);
183   	// causes issue in Opera
184   	// headEl.removeChild(scriptEl);
185   }
186   
187   function getScriptJaxer(url, callback) {
188     Jaxer.load(url);
189     callback();
190   }
191   
192   fabric.util.getScript = getScript;
193   
194   var Jaxer = global.Jaxer;
195   if (Jaxer && Jaxer.load) {
196     fabric.util.getScript = getScriptJaxer;
197   }
198 })();
199 
200 /**
201  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
202  * @method animate
203  * @memberOf fabric.util
204  * @param {Object} [options] Animation options
205  * @param {Function} [options.onChange] Callback; invoked on every value change
206  * @param {Function} [options.onComplete] Callback; invoked when value change is completed
207  * @param {Number} [options.startValue=0] Starting value
208  * @param {Number} [options.endValue=100] Ending value
209  * @param {Function} [options.easing] Easing function
210  * @param {Number} [options.duration=500] Duration of change
211  */
212 function animate(options) {
213   
214   options || (options = { });
215   
216   var start = +new Date(), 
217       duration = options.duration || 500,
218       finish = start + duration, time, pos,
219       onChange = options.onChange || function() { },
220       abort = options.abort || function() { return false; },
221       easing = options.easing || function(pos) { return (-Math.cos(pos * Math.PI) / 2) + 0.5; },
222       startValue = 'startValue' in options ? options.startValue : 0,
223       endValue = 'endValue' in options ? options.endValue : 100,
224       isReversed = startValue > endValue;
225   
226   options.onStart && options.onStart();
227 
228   var interval = setInterval(function() {
229     time = +new Date();
230     pos = time > finish ? 1 : (time - start) / duration;
231     onChange(isReversed 
232       ? (startValue - (startValue - endValue) * easing(pos)) 
233       : (startValue + (endValue - startValue) * easing(pos)));
234     if (time > finish || abort()) {
235       clearInterval(interval);
236       options.onComplete && options.onComplete();
237     }
238   }, 10);
239   
240   return interval;
241 }
242 
243 fabric.util.getById = getById;
244 fabric.util.toArray = toArray;
245 fabric.util.makeElement = makeElement;
246 fabric.util.addClass = addClass;
247 fabric.util.wrapElement = wrapElement;
248 fabric.util.getElementOffset = getElementOffset;
249 fabric.util.animate = animate;