define("dijit/_editor/range", [ "dojo/_base/array", // array.every "dojo/_base/declare", // declare "dojo/_base/lang", // lang.isArray "dojo/_base/window", // win.doc TODO: remove in 2.0 "../main" // for exporting symbols to dijit, TODO: remove in 2.0 ], function(array, declare, lang, win, dijit){ // module: // dijit/_editor/range // summary: // W3C range API dijit.range={}; dijit.range.getIndex = function(/*DomNode*/node, /*DomNode*/parent){ // dojo.profile.start("dijit.range.getIndex"); var ret = [], retR = []; var onode = node; var pnode, n; while(node != parent){ var i = 0; pnode = node.parentNode; while((n = pnode.childNodes[i++])){ if(n === node){ --i; break; } } //if(i>=pnode.childNodes.length){ //dojo.debug("Error finding index of a node in dijit/range.getIndex()"); //} ret.unshift(i); retR.unshift(i - pnode.childNodes.length); node = pnode; } //normalized() can not be called so often to prevent //invalidating selection/range, so we have to detect //here that any text nodes in a row if(ret.length > 0 && onode.nodeType == 3){ n = onode.previousSibling; while(n && n.nodeType == 3){ ret[ret.length - 1]--; n = n.previousSibling; } n = onode.nextSibling; while(n && n.nodeType == 3){ retR[retR.length - 1]++; n = n.nextSibling; } } // dojo.profile.end("dijit/range.getIndex()"); return {o: ret, r:retR}; }; dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){ if(!lang.isArray(index) || index.length == 0){ return parent; } var node = parent; // if(!node)debugger array.every(index, function(i){ if(i >= 0 && i < node.childNodes.length){ node = node.childNodes[i]; }else{ node = null; //console.debug('Error: can not find node with index',index,'under parent node',parent ); return false; //terminate array.every } return true; //carry on the every loop }); return node; }; dijit.range.getCommonAncestor = function(n1, n2, root){ root = root || n1.ownerDocument.body; var getAncestors = function(n){ var as = []; while(n){ as.unshift(n); if(n !== root){ n = n.parentNode; }else{ break; } } return as; }; var n1as = getAncestors(n1); var n2as = getAncestors(n2); var m = Math.min(n1as.length, n2as.length); var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default) for(var i = 1; i < m; i++){ if(n1as[i] === n2as[i]){ com = n1as[i] }else{ break; } } return com; }; dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){ root = root || node.ownerDocument.body; while(node && node !== root){ var name = node.nodeName.toUpperCase(); if(regex.test(name)){ return node; } node = node.parentNode; } return null; }; dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/; dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){ root = root || node.ownerDocument.body; regex = regex || dijit.range.BlockTagNames; var block = null, blockContainer; while(node && node !== root){ var name = node.nodeName.toUpperCase(); if(!block && regex.test(name)){ block = node; } if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){ blockContainer = node; } node = node.parentNode; } return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body}; }; dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){ var atBeginning = false; var offsetAtBeginning = (offset == 0); if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0, offset))){ offsetAtBeginning = true; } } if(offsetAtBeginning){ var cnode = node; atBeginning = true; while(cnode && cnode !== container){ if(cnode.previousSibling){ atBeginning = false; break; } cnode = cnode.parentNode; } } return atBeginning; }; dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){ var atEnd = false; var offsetAtEnd = (offset == (node.length || node.childNodes.length)); if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){ offsetAtEnd = true; } } if(offsetAtEnd){ var cnode = node; atEnd = true; while(cnode && cnode !== container){ if(cnode.nextSibling){ atEnd = false; break; } cnode = cnode.parentNode; } } return atEnd; }; dijit.range.adjacentNoneTextNode = function(startnode, next){ var node = startnode; var len = (0 - startnode.length) || 0; var prop = next ? 'nextSibling' : 'previousSibling'; while(node){ if(node.nodeType != 3){ break; } len += node.length; node = node[prop]; } return [node,len]; }; dijit.range.create = function(/*Window?*/ win){ // TODO: for 2.0, replace optional window param w/mandatory window or document param win = win || window; if(win.getSelection){ return win.document.createRange(); }else{//IE return new dijit.range.W3CRange(); } }; dijit.range.getSelection = function(/*Window*/ window, /*Boolean?*/ ignoreUpdate){ if(window.getSelection){ return window.getSelection(); }else{//IE var s = new dijit.range.ie.selection(window); if(!ignoreUpdate){ s._getCurrentSelection(); } return s; } }; // TODO: convert to has() test? But remember IE9 issues with quirks vs. standards in main frame vs. iframe. if(!window.getSelection){ dijit.range.ie = { cachedSelection: {}, selection: function(window){ this._ranges = []; this.addRange = function(r, /*boolean*/ internal){ this._ranges.push(r); if(!internal){ r._select(); } this.rangeCount = this._ranges.length; }; this.removeAllRanges = function(){ //don't detach, the range may be used later // for(var i=0;i 0){ array.every(parentNode.childNodes, function(node, i){ var calOffset; if(node.nodeType != 3){ atmrange.moveToElementText(node); if(atmrange.compareEndPoints(cmpstr, range) > 0){ //startnode = node.previousSibling; if(lastNode && lastNode.nodeType == 3){ //where shall we put the start? in the text node or after? startnode = lastNode; calOffset = true; }else{ startnode = parentNode; startOffset = i; return false; } }else{ if(i == parentNode.childNodes.length - 1){ startnode = parentNode; startOffset = parentNode.childNodes.length; return false; } } }else{ if(i == parentNode.childNodes.length - 1){//at the end of this node startnode = node; calOffset = true; } } // try{ if(calOffset && startnode){ var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0]; if(prevnode){ startnode = prevnode.nextSibling; }else{ startnode = parentNode.firstChild; //firstChild must be a text node } var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode); prevnode = prevnodeobj[0]; var lenoffset = prevnodeobj[1]; if(prevnode){ atmrange.moveToElementText(prevnode); atmrange.collapse(false); }else{ atmrange.moveToElementText(parentNode); } atmrange.setEndPoint(cmpstr, range); startOffset = atmrange.text.length - lenoffset; return false; } // }catch(e){ debugger } lastNode = node; return true; }); }else{ startnode = parentNode; startOffset = 0; } //if at the end of startnode and we are dealing with start container, then //move the startnode to nextSibling if it is a text node //TODO: do this for end container? if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){ var nextnode = startnode.nextSibling; if(nextnode && nextnode.nodeType == 3){ startnode = nextnode; startOffset = 0; } } return [startnode, startOffset]; }, setEndPoint: function(range, container, offset){ //text node var atmrange = range.duplicate(), node, len; if(container.nodeType != 3){ //normal node if(offset > 0){ node = container.childNodes[offset - 1]; if(node){ if(node.nodeType == 3){ container = node; offset = node.length; //pass through }else{ if(node.nextSibling && node.nextSibling.nodeType == 3){ container = node.nextSibling; offset = 0; //pass through }else{ atmrange.moveToElementText(node.nextSibling ? node : container); var parent = node.parentNode; var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling); atmrange.collapse(false); parent.removeChild(tempNode); } } } }else{ atmrange.moveToElementText(container); atmrange.collapse(true); } } if(container.nodeType == 3){ var prevnodeobj = dijit.range.adjacentNoneTextNode(container); var prevnode = prevnodeobj[0]; len = prevnodeobj[1]; if(prevnode){ atmrange.moveToElementText(prevnode); atmrange.collapse(false); //if contentEditable is not inherit, the above collapse won't make the end point //in the correctly position: it always has a -1 offset, so compensate it if(prevnode.contentEditable != 'inherit'){ len++; } }else{ atmrange.moveToElementText(container.parentNode); atmrange.collapse(true); // Correct internal cursor position // http://bugs.dojotoolkit.org/ticket/15578 atmrange.move('character', 1); atmrange.move('character', -1); } offset += len; if(offset > 0){ if(atmrange.move('character', offset) != offset){ console.error('Error when moving!'); } } } return atmrange; }, decomposeTextRange: function(range){ var tmpary = dijit.range.ie.getEndPoint(range); var startContainer = tmpary[0], startOffset = tmpary[1]; var endContainer = tmpary[0], endOffset = tmpary[1]; if(range.htmlText.length){ if(range.htmlText == range.text){ //in the same text node endOffset = startOffset + range.text.length; }else{ tmpary = dijit.range.ie.getEndPoint(range, true); endContainer = tmpary[0],endOffset = tmpary[1]; // if(startContainer.tagName == "BODY"){ // startContainer = startContainer.firstChild; // } } } return [startContainer, startOffset, endContainer, endOffset]; }, setRange: function(range, startContainer, startOffset, endContainer, endOffset, collapsed){ var start = dijit.range.ie.setEndPoint(range, startContainer, startOffset); range.setEndPoint('StartToStart', start); if(!collapsed){ var end = dijit.range.ie.setEndPoint(range, endContainer, endOffset); } range.setEndPoint('EndToEnd', end || start); return range; } }; declare("dijit.range.W3CRange",null, { constructor: function(){ if(arguments.length>0){ this.setStart(arguments[0][0],arguments[0][1]); this.setEnd(arguments[0][2],arguments[0][3]); }else{ this.commonAncestorContainer = null; this.startContainer = null; this.startOffset = 0; this.endContainer = null; this.endOffset = 0; this.collapsed = true; } }, _updateInternal: function(){ if(this.startContainer !== this.endContainer){ this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer); }else{ this.commonAncestorContainer = this.startContainer; } this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset); }, setStart: function(node, offset){ offset=parseInt(offset); if(this.startContainer === node && this.startOffset == offset){ return; } delete this._cachedBookmark; this.startContainer = node; this.startOffset = offset; if(!this.endContainer){ this.setEnd(node, offset); }else{ this._updateInternal(); } }, setEnd: function(node, offset){ offset=parseInt(offset); if(this.endContainer === node && this.endOffset == offset){ return; } delete this._cachedBookmark; this.endContainer = node; this.endOffset = offset; if(!this.startContainer){ this.setStart(node, offset); }else{ this._updateInternal(); } }, setStartAfter: function(node, offset){ this._setPoint('setStart', node, offset, 1); }, setStartBefore: function(node, offset){ this._setPoint('setStart', node, offset, 0); }, setEndAfter: function(node, offset){ this._setPoint('setEnd', node, offset, 1); }, setEndBefore: function(node, offset){ this._setPoint('setEnd', node, offset, 0); }, _setPoint: function(what, node, offset, ext){ var index = dijit.range.getIndex(node, node.parentNode).o; this[what](node.parentNode, index.pop()+ext); }, _getIERange: function(){ var r = (this._body || this.endContainer.ownerDocument.body).createTextRange(); dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed); return r; }, getBookmark: function(){ this._getIERange(); return this._cachedBookmark; }, _select: function(){ var r = this._getIERange(); r.select(); }, deleteContents: function(){ var s = this.startContainer, r = this._getIERange(); if(s.nodeType === 3 && !this.startOffset){ //if the range starts at the beginning of a //text node, move it to before the textnode //to make sure the range is still valid //after deleteContents() finishes this.setStartBefore(s); } r.pasteHTML(''); this.endContainer = this.startContainer; this.endOffset = this.startOffset; this.collapsed = true; }, cloneRange: function(){ var r = new dijit.range.W3CRange([this.startContainer,this.startOffset, this.endContainer,this.endOffset]); r._body = this._body; return r; }, detach: function(){ this._body = null; this.commonAncestorContainer = null; this.startContainer = null; this.startOffset = 0; this.endContainer = null; this.endOffset = 0; this.collapsed = true; } }); } //if(!window.getSelection) return dijit.range; });