1 define("dijit/_editor/range", [
2 "dojo/_base/array", // array.every
3 "dojo/_base/declare", // declare
4 "dojo/_base/lang", // lang.isArray
5 "dojo/_base/window", // win.global
6 ".." // for exporting symbols to dijit, TODO: remove in 2.0
7 ], function(array, declare, lang, win, dijit){
10 // dijit/_editor/range
17 dijit.range.getIndex = function(/*DomNode*/node, /*DomNode*/parent){
18 // dojo.profile.start("dijit.range.getIndex");
19 var ret = [], retR = [];
23 while(node != parent){
25 pnode = node.parentNode;
26 while((n = pnode.childNodes[i++])){
32 //if(i>=pnode.childNodes.length){
33 //dojo.debug("Error finding index of a node in dijit.range.getIndex");
36 retR.unshift(i - pnode.childNodes.length);
40 //normalized() can not be called so often to prevent
41 //invalidating selection/range, so we have to detect
42 //here that any text nodes in a row
43 if(ret.length > 0 && onode.nodeType == 3){
44 n = onode.previousSibling;
45 while(n && n.nodeType == 3){
46 ret[ret.length - 1]--;
47 n = n.previousSibling;
49 n = onode.nextSibling;
50 while(n && n.nodeType == 3){
51 retR[retR.length - 1]++;
55 // dojo.profile.end("dijit.range.getIndex");
56 return {o: ret, r:retR};
59 dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
60 if(!lang.isArray(index) || index.length == 0){
65 array.every(index, function(i){
66 if(i >= 0 && i < node.childNodes.length){
67 node = node.childNodes[i];
70 //console.debug('Error: can not find node with index',index,'under parent node',parent );
71 return false; //terminate array.every
73 return true; //carry on the every loop
79 dijit.range.getCommonAncestor = function(n1, n2, root){
80 root = root || n1.ownerDocument.body;
81 var getAncestors = function(n){
93 var n1as = getAncestors(n1);
94 var n2as = getAncestors(n2);
96 var m = Math.min(n1as.length, n2as.length);
97 var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
98 for(var i = 1; i < m; i++){
99 if(n1as[i] === n2as[i]){
108 dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
109 root = root || node.ownerDocument.body;
110 while(node && node !== root){
111 var name = node.nodeName.toUpperCase();
112 if(regex.test(name)){
116 node = node.parentNode;
121 dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
122 dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
123 root = root || node.ownerDocument.body;
124 regex = regex || dijit.range.BlockTagNames;
125 var block = null, blockContainer;
126 while(node && node !== root){
127 var name = node.nodeName.toUpperCase();
128 if(!block && regex.test(name)){
131 if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
132 blockContainer = node;
135 node = node.parentNode;
137 return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
140 dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
141 var atBeginning = false;
142 var offsetAtBeginning = (offset == 0);
143 if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space
144 if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0, offset))){
145 offsetAtBeginning = true;
148 if(offsetAtBeginning){
151 while(cnode && cnode !== container){
152 if(cnode.previousSibling){
156 cnode = cnode.parentNode;
162 dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
164 var offsetAtEnd = (offset == (node.length || node.childNodes.length));
165 if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space
166 if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){
173 while(cnode && cnode !== container){
174 if(cnode.nextSibling){
178 cnode = cnode.parentNode;
184 dijit.range.adjacentNoneTextNode = function(startnode, next){
185 var node = startnode;
186 var len = (0 - startnode.length) || 0;
187 var prop = next ? 'nextSibling' : 'previousSibling';
189 if(node.nodeType != 3){
198 dijit.range._w3c = Boolean(window['getSelection']);
199 dijit.range.create = function(/*Window?*/window){
200 if(dijit.range._w3c){
201 return (window || win.global).document.createRange();
203 return new dijit.range.W3CRange;
207 dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){
208 if(dijit.range._w3c){
209 return win.getSelection();
211 var s = new dijit.range.ie.selection(win);
213 s._getCurrentSelection();
219 if(!dijit.range._w3c){
222 selection: function(win){
224 this.addRange = function(r, /*boolean*/internal){
225 this._ranges.push(r);
229 this.rangeCount = this._ranges.length;
231 this.removeAllRanges = function(){
232 //don't detach, the range may be used later
233 // for(var i=0;i<this._ranges.length;i++){
234 // this._ranges[i].detach();
239 var _initCurrentRange = function(){
240 var r = win.document.selection.createRange();
241 var type = win.document.selection.type.toUpperCase();
242 if(type == "CONTROL"){
243 //TODO: multiple range selection(?)
244 return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
246 return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
249 this.getRangeAt = function(i){
250 return this._ranges[i];
252 this._getCurrentSelection = function(){
253 this.removeAllRanges();
254 var r = _initCurrentRange();
256 this.addRange(r, true);
257 this.isCollapsed = r.collapsed;
259 this.isCollapsed = true;
263 decomposeControlRange: function(range){
264 var firstnode = range.item(0), lastnode = range.item(range.length - 1);
265 var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
266 var startOffset = dijit.range.getIndex(firstnode, startContainer).o[0];
267 var endOffset = dijit.range.getIndex(lastnode, endContainer).o[0] + 1;
268 return [startContainer, startOffset,endContainer, endOffset];
270 getEndPoint: function(range, end){
271 var atmrange = range.duplicate();
272 atmrange.collapse(!end);
273 var cmpstr = 'EndTo' + (end ? 'End' : 'Start');
274 var parentNode = atmrange.parentElement();
276 var startnode, startOffset, lastNode;
277 if(parentNode.childNodes.length > 0){
278 array.every(parentNode.childNodes, function(node, i){
280 if(node.nodeType != 3){
281 atmrange.moveToElementText(node);
283 if(atmrange.compareEndPoints(cmpstr, range) > 0){
284 //startnode = node.previousSibling;
285 if(lastNode && lastNode.nodeType == 3){
286 //where shall we put the start? in the text node or after?
287 startnode = lastNode;
290 startnode = parentNode;
295 if(i == parentNode.childNodes.length - 1){
296 startnode = parentNode;
297 startOffset = parentNode.childNodes.length;
302 if(i == parentNode.childNodes.length - 1){//at the end of this node
308 if(calOffset && startnode){
309 var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
311 startnode = prevnode.nextSibling;
313 startnode = parentNode.firstChild; //firstChild must be a text node
315 var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
316 prevnode = prevnodeobj[0];
317 var lenoffset = prevnodeobj[1];
319 atmrange.moveToElementText(prevnode);
320 atmrange.collapse(false);
322 atmrange.moveToElementText(parentNode);
324 atmrange.setEndPoint(cmpstr, range);
325 startOffset = atmrange.text.length - lenoffset;
329 // }catch(e){ debugger }
334 startnode = parentNode;
338 //if at the end of startnode and we are dealing with start container, then
339 //move the startnode to nextSibling if it is a text node
340 //TODO: do this for end container?
341 if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){
342 var nextnode = startnode.nextSibling;
343 if(nextnode && nextnode.nodeType == 3){
344 startnode = nextnode;
348 return [startnode, startOffset];
350 setEndPoint: function(range, container, offset){
352 var atmrange = range.duplicate(), node, len;
353 if(container.nodeType != 3){ //normal node
355 node = container.childNodes[offset - 1];
357 if(node.nodeType == 3){
359 offset = node.length;
362 if(node.nextSibling && node.nextSibling.nodeType == 3){
363 container = node.nextSibling;
367 atmrange.moveToElementText(node.nextSibling ? node : container);
368 var parent = node.parentNode;
369 var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling);
370 atmrange.collapse(false);
371 parent.removeChild(tempNode);
376 atmrange.moveToElementText(container);
377 atmrange.collapse(true);
380 if(container.nodeType == 3){
381 var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
382 var prevnode = prevnodeobj[0];
383 len = prevnodeobj[1];
385 atmrange.moveToElementText(prevnode);
386 atmrange.collapse(false);
387 //if contentEditable is not inherit, the above collapse won't make the end point
388 //in the correctly position: it always has a -1 offset, so compensate it
389 if(prevnode.contentEditable != 'inherit'){
393 atmrange.moveToElementText(container.parentNode);
394 atmrange.collapse(true);
399 if(atmrange.move('character', offset) != offset){
400 console.error('Error when moving!');
407 decomposeTextRange: function(range){
408 var tmpary = dijit.range.ie.getEndPoint(range);
409 var startContainer = tmpary[0], startOffset = tmpary[1];
410 var endContainer = tmpary[0], endOffset = tmpary[1];
412 if(range.htmlText.length){
413 if(range.htmlText == range.text){ //in the same text node
414 endOffset = startOffset + range.text.length;
416 tmpary = dijit.range.ie.getEndPoint(range, true);
417 endContainer = tmpary[0],endOffset = tmpary[1];
418 // if(startContainer.tagName == "BODY"){
419 // startContainer = startContainer.firstChild;
423 return [startContainer, startOffset, endContainer, endOffset];
425 setRange: function(range, startContainer, startOffset, endContainer, endOffset, collapsed){
426 var start = dijit.range.ie.setEndPoint(range, startContainer, startOffset);
428 range.setEndPoint('StartToStart', start);
430 var end = dijit.range.ie.setEndPoint(range, endContainer, endOffset);
432 range.setEndPoint('EndToEnd', end || start);
438 declare("dijit.range.W3CRange",null, {
439 constructor: function(){
440 if(arguments.length>0){
441 this.setStart(arguments[0][0],arguments[0][1]);
442 this.setEnd(arguments[0][2],arguments[0][3]);
444 this.commonAncestorContainer = null;
445 this.startContainer = null;
446 this.startOffset = 0;
447 this.endContainer = null;
449 this.collapsed = true;
452 _updateInternal: function(){
453 if(this.startContainer !== this.endContainer){
454 this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer);
456 this.commonAncestorContainer = this.startContainer;
458 this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
460 setStart: function(node, offset){
461 offset=parseInt(offset);
462 if(this.startContainer === node && this.startOffset == offset){
465 delete this._cachedBookmark;
467 this.startContainer = node;
468 this.startOffset = offset;
469 if(!this.endContainer){
470 this.setEnd(node, offset);
472 this._updateInternal();
475 setEnd: function(node, offset){
476 offset=parseInt(offset);
477 if(this.endContainer === node && this.endOffset == offset){
480 delete this._cachedBookmark;
482 this.endContainer = node;
483 this.endOffset = offset;
484 if(!this.startContainer){
485 this.setStart(node, offset);
487 this._updateInternal();
490 setStartAfter: function(node, offset){
491 this._setPoint('setStart', node, offset, 1);
493 setStartBefore: function(node, offset){
494 this._setPoint('setStart', node, offset, 0);
496 setEndAfter: function(node, offset){
497 this._setPoint('setEnd', node, offset, 1);
499 setEndBefore: function(node, offset){
500 this._setPoint('setEnd', node, offset, 0);
502 _setPoint: function(what, node, offset, ext){
503 var index = dijit.range.getIndex(node, node.parentNode).o;
504 this[what](node.parentNode, index.pop()+ext);
506 _getIERange: function(){
507 var r = (this._body || this.endContainer.ownerDocument.body).createTextRange();
508 dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed);
511 getBookmark: function(){
513 return this._cachedBookmark;
516 var r = this._getIERange();
519 deleteContents: function(){
520 var s = this.startContainer, r = this._getIERange();
521 if(s.nodeType === 3 && !this.startOffset){
522 //if the range starts at the beginning of a
523 //text node, move it to before the textnode
524 //to make sure the range is still valid
525 //after deleteContents() finishes
526 this.setStartBefore(s);
529 this.endContainer = this.startContainer;
530 this.endOffset = this.startOffset;
531 this.collapsed = true;
533 cloneRange: function(){
534 var r = new dijit.range.W3CRange([this.startContainer,this.startOffset,
535 this.endContainer,this.endOffset]);
536 r._body = this._body;
541 this.commonAncestorContainer = null;
542 this.startContainer = null;
543 this.startOffset = 0;
544 this.endContainer = null;
546 this.collapsed = true;
549 } //if(!dijit.range._w3c)