1 /**
  2  * ecma5schims.js - Shim for ECMA5 compatibility
  3  * (http://en.wikipedia.org/wiki/Shim_%28computing%29)
  4  *
  5  * A shim library that implements common functions that are missing on some
  6  * environments in order to complete ECMA5 compatibility across all major
  7  * browsers.
  8  *
  9  * TODO: This code needs to be refactored so as to conform to Aloha coding
 10  *       standards.  It is also severly lacking in documentation.  Please take
 11  *       note of: https://github.com/alohaeditor/Aloha-Editor/wiki/Commit-Checklist .
 12  */
 13 
 14 define([], function () {
 15 	'use strict';
 16 	var $_;
 17 
 18 	var shims = {
 19 		// Function bind
 20 		bind: function (owner) {
 21 			var obj = this.obj || this;
 22 			var native_method = Function.prototype.bind;
 23 			var args = Array.prototype.slice.call(arguments, 1);
 24 
 25 			if (native_method) {
 26 				return native_method.apply(obj, arguments);
 27 			}
 28 			return function () {
 29 				return obj.apply(owner, arguments.length === 0 ? args : args.concat(Array.prototype.slice.call(arguments)));
 30 			};
 31 		},
 32 
 33 		// String trim
 34 		trim: function () {
 35 			var obj = this.obj || this;
 36 			var native_method = String.prototype.trim;
 37 
 38 			if (native_method) {
 39 				return native_method.call(obj);
 40 			}
 41 			return obj.replace(/^\s+/, '').replace(/\s+$/, '');
 42 		},
 43 
 44 		// Array methods 
 45 		// i optional
 46 		indexOf: function (find, i) {
 47 			var obj = this.obj || this;
 48 			var native_method = Array.prototype.indexOf;
 49 
 50 			if (native_method) {
 51 				return native_method.call(obj, find, i);
 52 			}
 53 			if (i === undefined) {
 54 				i = 0;
 55 			}
 56 			if (i < 0) {
 57 				i += obj.length;
 58 			}
 59 			if (i < 0) {
 60 				i = 0;
 61 			}
 62 			var n;
 63 			for (n = obj.length; i < n; i++) {
 64 				if (undefined !== obj[i] && obj[i] === find) {
 65 					return i;
 66 				}
 67 			}
 68 			return -1;
 69 		},
 70 
 71 		// that optional
 72 		forEach: function (action, that) {
 73 			var obj = this.obj || this;
 74 			var native_method = Array.prototype.forEach;
 75 
 76 			if (native_method) {
 77 				return native_method.call(obj, action, that);
 78 			}
 79 			var i, n;
 80 			for (i = 0, n = obj.length; i < n; i++) {
 81 				if (undefined !== obj[i]) {
 82 					action.call(that, obj[i], i, obj);
 83 				}
 84 			}
 85  86 		},
 87 
 88 		// that optional
 89 		// chain optional
 90 		map: function (mapper, that, chain) {
 91 			var obj = this.obj || this;
 92 			var native_method = Array.prototype.map;
 93 			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
 94 			var result = [];
 95 
 96 			if (native_method) {
 97 				result = native_method.call(obj, mapper, that);
 98 			} else {
 99 				var other = [];
100 				var i, n;
101 				for (i = 0, n = obj.length; i < n; i++) {
102 					if (undefined !== obj[i]) {
103 						other[i] = mapper.call(that, obj[i], i, obj);
104 					}
105 				}
106 				result = other;
107 			}
108 
109 			return returnWrapper ? $_(result) : result;
110 		},
111 
112 		// that optional
113 		// chain optional
114 		filter: function (filterFunc, that, chain) {
115 			var obj = this.obj || this;
116 			var native_method = Array.prototype.filter;
117 			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
118 			var result = [];
119 
120 			if (native_method) {
121 				result = native_method.call(obj, filterFunc, that);
122 			} else {
123 				var other = [],
124 				    v,
125 				    i,
126 				    n;
127 				for (i = 0, n = obj.length; i < n; i++) {
128 					if (undefined !== obj[i] && filterFunc.call(that, v = obj[i], i, obj)) {
129 						other.push(v);
130 					}
131 				}
132 				result = other;
133 			}
134 
135 			return returnWrapper ? $_(result) : result;
136 		},
137 
138 		// that optional
139 		every: function (tester, that) {
140 			var obj = this.obj || this;
141 			var native_method = Array.prototype.every;
142 
143 			if (native_method) {
144 				return native_method.call(obj, tester, that);
145 			}
146 			var i, n;
147 			for (i = 0, n = obj.length; i < n; i++) {
148 				if (undefined !== obj[i] && !tester.call(that, obj[i], i, obj)) {
149 					return false;
150 				}
151 			}
152 			return true;
153 		},
154 
155 		// that optional
156 		some: function (tester, that) {
157 			var obj = this.obj || this;
158 			var native_method = Array.prototype.some;
159 
160 			if (native_method) {
161 				return native_method.call(obj, tester, that);
162 			}
163 			var i, n;
164 			for (i = 0, n = obj.length; i < n; i++) {
165 				if (undefined !== obj[i] && tester.call(that, obj[i], i, obj)) {
166 					return true;
167 				}
168 			}
169 			return false;
170 		},
171 
172 		// Since IE7 doesn't support 'hasAttribute' method on nodes
173 		// TODO: raise an exception if the object is not an node
174 		hasAttribute: function (attr) {
175 			var obj = this.obj || this;
176 			var native_method = obj.hasAttribute;
177 
178 			if (native_method) {
179 				return obj.hasAttribute(attr);
180 			}
181 			return !!obj.getAttribute(attr);
182 		}
183 
184 	};
185 
186 	$_ = function (obj) {
187 		var Wrapper = function () {};
188 		Wrapper.prototype = shims;
189 
190 		var wrapper_instance = new Wrapper();
191 		wrapper_instance.obj = obj;
192 		return wrapper_instance;
193 	};
194 
195 	var shim;
196 	for (shim in shims) {
197 		if (shims.hasOwnProperty(shim)) {
198 			$_[shim] = shims[shim];
199 		}
200 	}
201 
202 
203 	// Node constants
204 	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
205 	if (typeof window.Node != 'undefined') {
206 		$_.Node = window.Node;
207 	} else {
208 		$_.Node = {
209 			'ELEMENT_NODE': 1,
210 			'ATTRIBUTE_NODE': 2,
211 			'TEXT_NODE': 3,
212 			'CDATA_SECTION_NODE': 4,
213 			'ENTITY_REFERENCE_NODE': 5,
214 			'ENTITY_NODE': 6,
215 			'PROCESSING_INSTRUCTION_NODE': 7,
216 			'COMMENT_NODE': 8,
217 			'DOCUMENT_NODE': 9,
218 			'DOCUMENT_TYPE_NODE': 10,
219 			'DOCUMENT_FRAGMENT_NODE': 11,
220 			'NOTATION_NODE': 12,
221 			//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
222 			'DOCUMENT_POSITION_DISCONNECTED': 0x01,
223 			//The second node precedes the reference node.
224 			'DOCUMENT_POSITION_PRECEDING': 0x02,
225 			//The node follows the reference node.
226 			'DOCUMENT_POSITION_FOLLOWING': 0x04,
227 			//The node contains the reference node. A node which contains is always preceding, too.
228 			'DOCUMENT_POSITION_CONTAINS': 0x08,
229 			//The node is contained by the reference node. A node which is contained is always following, too.
230 			'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
231 			//The determination of preceding versus following is implementation-specific.
232 			'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
233 		};
234 	}
235 
236 	//node.ownerDocument gives the document object, which isn't the right info for a disconnect
237 	function getRootParent(node) {
238 		var parent = null;
239 
240 		if (node) {
241 			do {
242 				parent = node;
243 			} while (null != (node = node.parentNode));
244 		}
245 
246 		return parent;
247 	}
248 
249 	//Compare Position - MIT Licensed, John Resig; http://ejohn.org/blog/comparing-document-position/
250 	//Already checked for equality and disconnect
251 	function comparePosition(node1, node2) {
252 		return (node1.contains(node2) && 16) + (node2.contains(node1) && 8) + (node1.sourceIndex >= 0 && node2.sourceIndex >= 0 ? (node1.sourceIndex < node2.sourceIndex && 4) + (node1.sourceIndex > node2.sourceIndex && 2) : 1);
253 	}
254 
255 	//get a node with a sourceIndex to use
256 	function getUseNode(node) {
257 		//if the node already has a sourceIndex, use that node
258 		if (null != node.sourceIndex) {
259 			return node;
260 		}
261 		//otherwise, insert a comment (which has a sourceIndex but minimal DOM impact) before the node and use that
262 		return node.parentNode.insertBefore(document.createComment(""), node);
263 	}
264 
265 	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
266 	// FIXME: Check if the DOMNode prototype can be set.
267 	$_.compareDocumentPosition = function (node1, node2) {
268 
269 		if (document.documentElement.compareDocumentPosition) {
270 			return node1.compareDocumentPosition(node2);
271 		}
272 
273 		if (!document.documentElement.contains) {
274 			throw 'neither compareDocumentPosition nor contains is supported by this browser.';
275 		}
276 
277 		if (node1 == node2) {
278 			return 0;
279 		}
280 
281 		//if they don't have the same parent, there's a disconnect
282 		if (getRootParent(node1) != getRootParent(node2)) {
283 			return 1;
284 		}
285 
286 		//use this if both nodes have a sourceIndex (text nodes don't)
287 		if (null != node1.sourceIndex && null != node2.sourceIndex) {
288 			return comparePosition(node1, node2);
289 		}
290 
291 		//document will definitely contain the other node
292 		if (node1 == document) {
293 			return 20;
294 		}
295 		if (node2 == document) {
296 			return 10;
297 		}
298 
299 		//get sourceIndexes to use for both nodes
300 		var useNode1 = getUseNode(node1),
301 			useNode2 = getUseNode(node2);
302 
303 		//call this function again to get the result
304 		var result = comparePosition(useNode1, useNode2);
305 
306 		//clean up if needed
307 		if (node1 != useNode1) {
308 			useNode1.parentNode.removeChild(useNode1);
309 		}
310 		if (node2 != useNode2) {
311 			useNode2.parentNode.removeChild(useNode2);
312 		}
313 		return result;
314 	};
315 
316 	$_.getComputedStyle = function (node, style) {
317 		if (window.getComputedStyle) {
318 			return window.getComputedStyle(node, style);
319 		}
320 		if (node.currentStyle) {
321 			return node.currentStyle;
322 		}
323 		return null;
324 	};
325 
326 	return $_;
327 });
328