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