]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dijit/_editor/range.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dijit / _editor / range.js
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");
4
5 dijit.range={};
6
7 dijit.range.getIndex=function(/*DomNode*/node, /*DomNode*/parent){
8 //      dojo.profile.start("dijit.range.getIndex");
9         var ret=[], retR=[];
10         var stop = parent;
11         var onode = node;
12
13         var pnode, n;
14         while(node != stop){
15                 var i = 0;
16                 pnode = node.parentNode;
17                 while((n=pnode.childNodes[i++])){
18                         if(n===node){
19                                 --i;
20                                 break;
21                         }
22                 }
23                 if(i>=pnode.childNodes.length){
24                         dojo.debug("Error finding index of a node in dijit.range.getIndex");
25                 }
26                 ret.unshift(i);
27                 retR.unshift(i-pnode.childNodes.length);
28                 node = pnode;
29         }
30
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){
37                         ret[ret.length-1]--;
38                         n = n.previousSibling;
39                 }
40                 n = onode.nextSibling;
41                 while(n && n.nodeType==3){
42                         retR[retR.length-1]++;
43                         n = n.nextSibling;
44                 }
45         }
46 //      dojo.profile.end("dijit.range.getIndex");
47         return {o: ret, r:retR};
48 }
49
50 dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
51         if(!dojo.isArray(index) || index.length==0){
52                 return parent;
53         }
54         var node = parent;
55 //      if(!node)debugger
56         dojo.every(index, function(i){
57                 if(i>=0&&i< node.childNodes.length){
58                         node = node.childNodes[i];
59                 }else{
60                         node = null;
61                         console.debug('Error: can not find node with index',index,'under parent node',parent );
62                         return false; //terminate dojo.every
63                 }
64                 return true; //carry on the every loop
65         });
66
67         return node;
68 }
69
70 dijit.range.getCommonAncestor = function(n1,n2,root){
71         var getAncestors = function(n,root){
72                 var as=[];
73                 while(n){
74                         as.unshift(n);
75                         if(n!=root && n.tagName!='BODY'){
76                                 n = n.parentNode;
77                         }else{
78                                 break;
79                         }
80                 }
81                 return as;
82         };
83         var n1as = getAncestors(n1,root);
84         var n2as = getAncestors(n2,root);
85
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)
88         for(var i=1;i<m;i++){
89                 if(n1as[i]===n2as[i]){
90                         com = n1as[i]
91                 }else{
92                         break;
93                 }
94         }
95         return com;
96 }
97
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)){
103                         return node;
104                 }
105
106                 node = node.parentNode;
107         }
108         return null;
109 }
110
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)){
119                         block = node;
120                 }
121                 if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
122                         blockContainer = node;
123                 }
124
125                 node = node.parentNode;
126         }
127         return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
128 }
129
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;
136                 }
137         }
138         if(offsetAtBeginning){
139                 var cnode = node;
140                 atBeginning = true;
141                 while(cnode && cnode !== container){
142                         if(cnode.previousSibling){
143                                 atBeginning = false;
144                                 break;
145                         }
146                         cnode = cnode.parentNode;
147                 }
148         }
149         return atBeginning;
150 }
151
152 dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
153         var atEnd = false;
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){
157                         offsetAtEnd = true;
158                 }
159         }
160         if(offsetAtEnd){
161                 var cnode = node;
162                 atEnd = true;
163                 while(cnode && cnode !== container){
164                         if(cnode.nextSibling){
165                                 atEnd = false;
166                                 break;
167                         }
168                         cnode = cnode.parentNode;
169                 }
170         }
171         return atEnd;
172 }
173
174 dijit.range.adjacentNoneTextNode=function(startnode, next){
175         var node = startnode;
176         var len = (0-startnode.length) || 0;
177         var prop = next?'nextSibling':'previousSibling';
178         while(node){
179                 if(node.nodeType!=3){
180                         break;
181                 }
182                 len += node.length
183                 node = node[prop];
184         }
185         return [node,len];
186 }
187
188 dijit.range._w3c = Boolean(window['getSelection']);
189 dijit.range.create = function(){
190         if(dijit.range._w3c){
191                 return dojo.doc.createRange();
192         }else{//IE
193                 return new dijit.range.W3CRange;
194         }
195 }
196
197 dijit.range.getSelection = function(win, /*Boolean?*/ignoreUpdate){
198         if(dijit.range._w3c){
199                 return win.getSelection();
200         }else{//IE
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){
209                                 id=id+1;
210                         }
211                         id=String(id);
212                         dijit.range.ie.cachedSelection[id] = s;
213                 }else{
214                         s = dijit.range.ie.cachedSelection[id];
215                 }
216                 if(!ignoreUpdate){
217                         s._getCurrentSelection();
218                 }
219                 return s;
220         }
221 }
222
223 if(!dijit.range._w3c){
224         dijit.range.ie={
225                 cachedSelection: {},
226                 selection: function(win){
227                         this._ranges = [];
228                         this.addRange = function(r, /*boolean*/internal){
229                                 this._ranges.push(r);
230                                 if(!internal){
231                                         r._select();
232                                 }
233                                 this.rangeCount = this._ranges.length;
234                         };
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();
239 //                              }
240                                 this._ranges = [];
241                                 this.rangeCount = 0;
242                         };
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));
249                                 }else{
250                                         return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
251                                 }
252                         };
253                         this.getRangeAt = function(i){
254                                 return this._ranges[i];
255                         };
256                         this._getCurrentSelection = function(){
257                                 this.removeAllRanges();
258                                 var r=_initCurrentRange();
259                                 if(r){
260                                         this.addRange(r, true);
261                                 }
262                         };
263                 },
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]];
270                 },
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();
276
277                         var startnode, startOffset, lastNode;
278                         if(parentNode.childNodes.length>0){
279                                 dojo.every(parentNode.childNodes, function(node,i){
280                                         var calOffset;
281                                         if(node.nodeType != 3){
282                                                 atmrange.moveToElementText(node);
283
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;
289                                                                 calOffset = true;
290                                                         }else{
291                                                                 startnode = parentNode;
292                                                                 startOffset = i;
293                                                                 return false;
294                                                         }
295                                                 }else{
296                                                         if(i==parentNode.childNodes.length-1){
297                                                                 startnode = parentNode;
298                                                                 startOffset = parentNode.childNodes.length;
299                                                                 return false;
300                                                         }
301                                                 }
302                                         }else{
303                                                 if(i==parentNode.childNodes.length-1){//at the end of this node
304                                                         startnode = node;
305                                                         calOffset = true;
306                                                 }
307                                         }
308                 //                      try{
309                                                 if(calOffset && startnode){
310                                                         var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
311                                                         if(prevnode){
312                                                                 startnode = prevnode.nextSibling;
313                                                         }else{
314                                                                 startnode = parentNode.firstChild; //firstChild must be a text node
315                                                         }
316                                                         var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
317                                                         prevnode = prevnodeobj[0];
318                                                         var lenoffset = prevnodeobj[1];
319                                                         if(prevnode){
320                                                                 atmrange.moveToElementText(prevnode);
321                                                                 atmrange.collapse(false);
322                                                         }else{
323                                                                 atmrange.moveToElementText(parentNode);
324                                                         }
325                                                         atmrange.setEndPoint(cmpstr, range);
326                                                         startOffset = atmrange.text.length-lenoffset;
327
328                                                         return false;
329                                                 }
330                 //                      }catch(e){ debugger }
331                                         lastNode = node;
332                                         return true;
333                                 });
334                         }else{
335                                 startnode = parentNode;
336                                 startOffset = 0;
337                         }
338
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;
345                                         startOffset = 0;
346                                 }
347                         }
348                         return [startnode, startOffset];
349                 },
350                 setEndPoint: function(range, container, offset){
351                         //text node
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){
357                                         if(offset > 0){
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;
363                                                 len = 0;
364                                                 while(node && node.nodeType == 3){
365                                                         len += node.length;
366                                                         container = node; //pass through
367                                                         node = node.previousSibling;
368                                                 }
369                                                 if(node){
370                                                         atmrange.moveToElementText(node);
371                                                 }
372                                                 atmrange.collapse(false);
373                                                 offset = len; //pass through
374                                         }else{ //no childNodes
375                                                 atmrange.moveToElementText(container);
376                                                 atmrange.collapse(true);
377                                         }
378                                 }else{
379                                         if(offset > 0){
380                                                 node = container.childNodes[offset-1];
381                                                 if(node.nodeType==3){
382                                                         container = node;
383                                                         offset = node.length;
384                                                         //pass through
385                                                 }else{
386                                                         atmrange.moveToElementText(node);
387                                                         atmrange.collapse(false);
388                                                 }
389                                         }
390                                 }
391                         }
392                         if(container.nodeType==3){
393                                 var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
394                                 var prevnode = prevnodeobj[0];
395                                 len = prevnodeobj[1];
396                                 if(prevnode){
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'){
402                                                 len++;
403                                         }
404                                 }else{
405                                         atmrange.moveToElementText(container.parentNode);
406                                         atmrange.collapse(true);
407                                 }
408
409                                 offset += len;
410                                 if(offset>0){
411                                         if(atmrange.moveEnd('character',offset) != offset){
412                                                 alert('Error when moving!');
413                                         }
414                                         atmrange.collapse(false);
415                                 }
416                         }
417
418                         return atmrange;
419                 },
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];
424
425                         if(range.htmlText.length){
426                                 if(range.htmlText == range.text){ //in the same text node
427                                         endOffset = startOffset+range.text.length;
428                                 }else{
429                                         tmpary = dijit.range.ie.getEndPoint(range,true);
430                                         endContainter = tmpary[0], endOffset = tmpary[1];
431                                 }
432                         }
433                         return [[startContainter, startOffset],[endContainter, endOffset], range.parentElement()];
434                 },
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);
439                         if(!this.collapsed){
440                                 var endrange = dijit.range.ie.setEndPoint(range, endContainter, endOffset);
441                                 range.setEndPoint('EndToEnd', endrange);
442                         }
443
444                         return range;
445                 }
446         }
447
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]);
453                 }else{
454                         this.commonAncestorContainer = null;
455                         this.startContainer = null;
456                         this.startOffset = 0;
457                         this.endContainer = null;
458                         this.endOffset = 0;
459                         this.collapsed = true;
460                 }
461         },
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);
466                 }else{
467                         r.moveToElementText(node);
468                 }
469                 r.collapse(true);
470                 range.setEndPoint(end?'EndToEnd':'StartToStart',r);
471         },
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();
479                         }
480                         this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer, __internal_common);
481                 }else{
482                         this.commonAncestorContainer = this.startContainer;
483                 }
484                 this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
485         },
486         setStart: function(node, offset, __internal_common){
487                 offset=parseInt(offset);
488                 if(this.startContainer === node && this.startOffset == offset){
489                         return;
490                 }
491                 delete this._cachedBookmark;
492
493                 this.startContainer = node;
494                 this.startOffset = offset;
495                 if(!this.endContainer){
496                         this.setEnd(node, offset, __internal_common);
497                 }else{
498                         this._updateInternal(__internal_common);
499                 }
500         },
501         setEnd: function(node, offset, __internal_common){
502                 offset=parseInt(offset);
503                 if(this.endContainer === node && this.endOffset == offset){
504                         return;
505                 }
506                 delete this._cachedBookmark;
507
508                 this.endContainer = node;
509                 this.endOffset = offset;
510                 if(!this.startContainer){
511                         this.setStart(node, offset, __internal_common);
512                 }else{
513                         this._updateInternal(__internal_common);
514                 }
515         },
516         setStartAfter: function(node, offset){
517                 this._setPoint('setStart', node, offset, 1);
518         },
519         setStartBefore: function(node, offset){
520                 this._setPoint('setStart', node, offset, 0);
521         },
522         setEndAfter: function(node, offset){
523                 this._setPoint('setEnd', node, offset, 1);
524         },
525         setEndBefore: function(node, offset){
526                 this._setPoint('setEnd', node, offset, 0);
527         },
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);
531         },
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);
535                 return r;
536         },
537         getBookmark: function(body){
538                 this._getIERange();
539                 return this._cachedBookmark;
540         },
541         _select: function(){
542                 var r = this._getIERange();
543                 r.select();
544         },
545         deleteContents: function(){
546                 var r = this._getIERange();
547                 r.pasteHTML('');
548                 this.endContainer = this.startContainer;
549                 this.endOffset = this.startOffset;
550                 this.collapsed = true;
551         },
552         cloneRange: function(){
553                 var r = new dijit.range.W3CRange([[this.startContainer,this.startOffset],
554                         [this.endContainer,this.endOffset]]);
555                 r._body = this._body;
556                 return r;
557         },
558         detach: function(){
559                 this._body = null;
560                 this.commonAncestorContainer = null;
561                 this.startContainer = null;
562                 this.startOffset = 0;
563                 this.endContainer = null;
564                 this.endOffset = 0;
565                 this.collapsed = true;
566 }
567 });
568 } //if(!dijit.range._w3c)
569
570 }