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