1 if(!dojo._hasResource["dijit._editor.range"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit._editor.range"] = true;
3 dojo.provide("dijit._editor.range");
7 dijit.range.getIndex=function(/*DomNode*/node, /*DomNode*/parent){
8 // dojo.profile.start("dijit.range.getIndex");
16 pnode = node.parentNode;
17 while((n=pnode.childNodes[i++])){
23 if(i>=pnode.childNodes.length){
24 dojo.debug("Error finding index of a node in dijit.range.getIndex");
27 retR.unshift(i-pnode.childNodes.length);
31 //normalized() can not be called so often to prevent
32 //invalidating selection/range, so we have to detect
33 //here that any text nodes in a row
34 if(ret.length>0 && onode.nodeType==3){
35 n = onode.previousSibling;
36 while(n && n.nodeType==3){
38 n = n.previousSibling;
40 n = onode.nextSibling;
41 while(n && n.nodeType==3){
42 retR[retR.length-1]++;
46 // dojo.profile.end("dijit.range.getIndex");
47 return {o: ret, r:retR};
50 dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
51 if(!dojo.isArray(index) || index.length==0){
56 dojo.every(index, function(i){
57 if(i>=0&&i< node.childNodes.length){
58 node = node.childNodes[i];
61 console.debug('Error: can not find node with index',index,'under parent node',parent );
62 return false; //terminate dojo.every
64 return true; //carry on the every loop
70 dijit.range.getCommonAncestor = function(n1,n2,root){
71 var getAncestors = function(n,root){
75 if(n!=root && n.tagName!='BODY'){
83 var n1as = getAncestors(n1,root);
84 var n2as = getAncestors(n2,root);
86 var m = Math.min(n1as.length,n2as.length);
87 var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
89 if(n1as[i]===n2as[i]){
98 dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
99 root = root || node.ownerDocument.body;
100 while(node && node !== root){
101 var name = node.nodeName.toUpperCase() ;
102 if(regex.test(name)){
106 node = node.parentNode;
111 dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
112 dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
113 root = root || node.ownerDocument.body;
114 regex = regex || dijit.range.BlockTagNames;
115 var block=null, blockContainer;
116 while(node && node !== root){
117 var name = node.nodeName.toUpperCase() ;
118 if(!block && regex.test(name)){
121 if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
122 blockContainer = node;
125 node = node.parentNode;
127 return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
130 dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
131 var atBeginning = false;
132 var offsetAtBeginning = (offset == 0);
133 if(!offsetAtBeginning && node.nodeType==3){ //if this is a text node, check whether the left part is all space
134 if(dojo.trim(node.nodeValue.substr(0,offset))==0){
135 offsetAtBeginning = true;
138 if(offsetAtBeginning){
141 while(cnode && cnode !== container){
142 if(cnode.previousSibling){
146 cnode = cnode.parentNode;
152 dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
154 var offsetAtEnd = (offset == (node.length || node.childNodes.length));
155 if(!offsetAtEnd && node.nodeType==3){ //if this is a text node, check whether the right part is all space
156 if(dojo.trim(node.nodeValue.substr(offset))==0){
163 while(cnode && cnode !== container){
164 if(cnode.nextSibling){
168 cnode = cnode.parentNode;
174 dijit.range.adjacentNoneTextNode=function(startnode, next){
175 var node = startnode;
176 var len = (0-startnode.length) || 0;
177 var prop = next?'nextSibling':'previousSibling';
179 if(node.nodeType!=3){
188 dijit.range._w3c = Boolean(window['getSelection']);
189 dijit.range.create = function(){
190 if(dijit.range._w3c){
191 return dojo.doc.createRange();
193 return new dijit.range.W3CRange;
197 dijit.range.getSelection = function(win, /*Boolean?*/ignoreUpdate){
198 if(dijit.range._w3c){
199 return win.getSelection();
201 var id=win.__W3CRange,s;
202 if(!id || !dijit.range.ie.cachedSelection[id]){
203 s = new dijit.range.ie.selection(win);
204 //use win as the key in an object is not reliable, which
205 //can leads to quite odd behaviors. thus we generate a
206 //string and use it as a key in the cache
207 id=(new Date).getTime();
208 while(id in dijit.range.ie.cachedSelection){
212 dijit.range.ie.cachedSelection[id] = s;
214 s = dijit.range.ie.cachedSelection[id];
217 s._getCurrentSelection();
223 if(!dijit.range._w3c){
226 selection: function(win){
228 this.addRange = function(r, /*boolean*/internal){
229 this._ranges.push(r);
233 this.rangeCount = this._ranges.length;
235 this.removeAllRanges = function(){
236 //don't detach, the range may be used later
237 // for(var i=0;i<this._ranges.length;i++){
238 // this._ranges[i].detach();
243 var _initCurrentRange = function(){
244 var r = win.document.selection.createRange();
245 var type=win.document.selection.type.toUpperCase();
246 if(type == "CONTROL"){
247 //TODO: multiple range selection(?)
248 return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
250 return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
253 this.getRangeAt = function(i){
254 return this._ranges[i];
256 this._getCurrentSelection = function(){
257 this.removeAllRanges();
258 var r=_initCurrentRange();
260 this.addRange(r, true);
264 decomposeControlRange: function(range){
265 var firstnode = range.item(0), lastnode = range.item(range.length-1)
266 var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
267 var startOffset = dijit.range.getIndex(firstnode, startContainer).o;
268 var endOffset = dijit.range.getIndex(lastnode, endContainer).o+1;
269 return [[startContainer, startOffset],[endContainer, endOffset]];
271 getEndPoint: function(range, end){
272 var atmrange = range.duplicate();
273 atmrange.collapse(!end);
274 var cmpstr = 'EndTo' + (end?'End':'Start');
275 var parentNode = atmrange.parentElement();
277 var startnode, startOffset, lastNode;
278 if(parentNode.childNodes.length>0){
279 dojo.every(parentNode.childNodes, function(node,i){
281 if(node.nodeType != 3){
282 atmrange.moveToElementText(node);
284 if(atmrange.compareEndPoints(cmpstr,range) > 0){
285 startnode = node.previousSibling;
286 if(lastNode && lastNode.nodeType == 3){
287 //where share we put the start? in the text node or after?
288 startnode = lastNode;
291 startnode = parentNode;
296 if(i==parentNode.childNodes.length-1){
297 startnode = parentNode;
298 startOffset = parentNode.childNodes.length;
303 if(i==parentNode.childNodes.length-1){//at the end of this node
309 if(calOffset && startnode){
310 var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
312 startnode = prevnode.nextSibling;
314 startnode = parentNode.firstChild; //firstChild must be a text node
316 var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
317 prevnode = prevnodeobj[0];
318 var lenoffset = prevnodeobj[1];
320 atmrange.moveToElementText(prevnode);
321 atmrange.collapse(false);
323 atmrange.moveToElementText(parentNode);
325 atmrange.setEndPoint(cmpstr, range);
326 startOffset = atmrange.text.length-lenoffset;
330 // }catch(e){ debugger }
335 startnode = parentNode;
339 //if at the end of startnode and we are dealing with start container, then
340 //move the startnode to nextSibling if it is a text node
341 //TODO: do this for end container?
342 if(!end && startnode.nodeType!=3 && startOffset == startnode.childNodes.length){
343 if(startnode.nextSibling && startnode.nextSibling.nodeType==3){
344 startnode = startnode.nextSibling;
348 return [startnode, startOffset];
350 setEndPoint: function(range, container, offset){
352 var atmrange = range.duplicate(), node, len;
353 if(container.nodeType!=3){ //normal node
354 atmrange.moveToElementText(container);
355 atmrange.collapse(true);
356 if(offset == container.childNodes.length){
358 //a simple atmrange.collapse(false); won't work here:
359 //although moveToElementText(node) is supposed to encompass the content of the node,
360 //but when collapse to end, it is in fact after the ending tag of node (collapse to start
361 //is after the begining tag of node as expected)
362 node = container.lastChild;
364 while(node && node.nodeType == 3){
366 container = node; //pass through
367 node = node.previousSibling;
370 atmrange.moveToElementText(node);
372 atmrange.collapse(false);
373 offset = len; //pass through
374 }else{ //no childNodes
375 atmrange.moveToElementText(container);
376 atmrange.collapse(true);
380 node = container.childNodes[offset-1];
381 if(node.nodeType==3){
383 offset = node.length;
386 atmrange.moveToElementText(node);
387 atmrange.collapse(false);
392 if(container.nodeType==3){
393 var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
394 var prevnode = prevnodeobj[0];
395 len = prevnodeobj[1];
397 atmrange.moveToElementText(prevnode);
398 atmrange.collapse(false);
399 //if contentEditable is not inherit, the above collapse won't make the end point
400 //in the correctly position: it always has a -1 offset, so compensate it
401 if(prevnode.contentEditable!='inherit'){
405 atmrange.moveToElementText(container.parentNode);
406 atmrange.collapse(true);
411 if(atmrange.moveEnd('character',offset) != offset){
412 alert('Error when moving!');
414 atmrange.collapse(false);
420 decomposeTextRange: function(range){
421 var tmpary = dijit.range.ie.getEndPoint(range);
422 var startContainter = tmpary[0], startOffset = tmpary[1];
423 var endContainter = tmpary[0], endOffset = tmpary[1];
425 if(range.htmlText.length){
426 if(range.htmlText == range.text){ //in the same text node
427 endOffset = startOffset+range.text.length;
429 tmpary = dijit.range.ie.getEndPoint(range,true);
430 endContainter = tmpary[0], endOffset = tmpary[1];
433 return [[startContainter, startOffset],[endContainter, endOffset], range.parentElement()];
435 setRange: function(range, startContainter,
436 startOffset, endContainter, endOffset, check){
437 var startrange = dijit.range.ie.setEndPoint(range, startContainter, startOffset);
438 range.setEndPoint('StartToStart', startrange);
440 var endrange = dijit.range.ie.setEndPoint(range, endContainter, endOffset);
441 range.setEndPoint('EndToEnd', endrange);
448 dojo.declare("dijit.range.W3CRange",null, {
449 constructor: function(){
450 if(arguments.length>0){
451 this.setStart(arguments[0][0][0],arguments[0][0][1]);
452 this.setEnd(arguments[0][1][0],arguments[0][1][1],arguments[0][2]);
454 this.commonAncestorContainer = null;
455 this.startContainer = null;
456 this.startOffset = 0;
457 this.endContainer = null;
459 this.collapsed = true;
462 _simpleSetEndPoint: function(node, range, end){
463 var r = (this._body||node.ownerDocument.body).createTextRange();
464 if(node.nodeType!=1){
465 r.moveToElementText(node.parentNode);
467 r.moveToElementText(node);
470 range.setEndPoint(end?'EndToEnd':'StartToStart',r);
472 _updateInternal: function(__internal_common){
473 if(this.startContainer !== this.endContainer){
474 if(!__internal_common){
475 var r = (this._body||this.startContainer.ownerDocument.body).createTextRange();
476 this._simpleSetEndPoint(this.startContainer,r);
477 this._simpleSetEndPoint(this.endContainer,r,true);
478 __internal_common = r.parentElement();
480 this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer, __internal_common);
482 this.commonAncestorContainer = this.startContainer;
484 this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
486 setStart: function(node, offset, __internal_common){
487 offset=parseInt(offset);
488 if(this.startContainer === node && this.startOffset == offset){
491 delete this._cachedBookmark;
493 this.startContainer = node;
494 this.startOffset = offset;
495 if(!this.endContainer){
496 this.setEnd(node, offset, __internal_common);
498 this._updateInternal(__internal_common);
501 setEnd: function(node, offset, __internal_common){
502 offset=parseInt(offset);
503 if(this.endContainer === node && this.endOffset == offset){
506 delete this._cachedBookmark;
508 this.endContainer = node;
509 this.endOffset = offset;
510 if(!this.startContainer){
511 this.setStart(node, offset, __internal_common);
513 this._updateInternal(__internal_common);
516 setStartAfter: function(node, offset){
517 this._setPoint('setStart', node, offset, 1);
519 setStartBefore: function(node, offset){
520 this._setPoint('setStart', node, offset, 0);
522 setEndAfter: function(node, offset){
523 this._setPoint('setEnd', node, offset, 1);
525 setEndBefore: function(node, offset){
526 this._setPoint('setEnd', node, offset, 0);
528 _setPoint: function(what, node, offset, ext){
529 var index = dijit.range.getIndex(node, node.parentNode).o;
530 this[what](node.parentNode, index.pop()+ext);
532 _getIERange: function(){
533 var r=(this._body||this.endContainer.ownerDocument.body).createTextRange();
534 dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset);
537 getBookmark: function(body){
539 return this._cachedBookmark;
542 var r = this._getIERange();
545 deleteContents: function(){
546 var r = this._getIERange();
548 this.endContainer = this.startContainer;
549 this.endOffset = this.startOffset;
550 this.collapsed = true;
552 cloneRange: function(){
553 var r = new dijit.range.W3CRange([[this.startContainer,this.startOffset],
554 [this.endContainer,this.endOffset]]);
555 r._body = this._body;
560 this.commonAncestorContainer = null;
561 this.startContainer = null;
562 this.startOffset = 0;
563 this.endContainer = null;
565 this.collapsed = true;
568 } //if(!dijit.range._w3c)