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