range.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit._editor.range"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit._editor.range"] = true;
  8. dojo.provide("dijit._editor.range");
  9. dijit.range={};
  10. dijit.range.getIndex=function(/*DomNode*/node, /*DomNode*/parent){
  11. // dojo.profile.start("dijit.range.getIndex");
  12. var ret=[], retR=[];
  13. var stop = parent;
  14. var onode = node;
  15. var pnode, n;
  16. while(node != stop){
  17. var i = 0;
  18. pnode = node.parentNode;
  19. while((n=pnode.childNodes[i++])){
  20. if(n === node){
  21. --i;
  22. break;
  23. }
  24. }
  25. //if(i>=pnode.childNodes.length){
  26. //dojo.debug("Error finding index of a node in dijit.range.getIndex");
  27. //}
  28. ret.unshift(i);
  29. retR.unshift(i-pnode.childNodes.length);
  30. node = pnode;
  31. }
  32. //normalized() can not be called so often to prevent
  33. //invalidating selection/range, so we have to detect
  34. //here that any text nodes in a row
  35. if(ret.length > 0 && onode.nodeType == 3){
  36. n = onode.previousSibling;
  37. while(n && n.nodeType == 3){
  38. ret[ret.length-1]--;
  39. n = n.previousSibling;
  40. }
  41. n = onode.nextSibling;
  42. while(n && n.nodeType == 3){
  43. retR[retR.length-1]++;
  44. n = n.nextSibling;
  45. }
  46. }
  47. // dojo.profile.end("dijit.range.getIndex");
  48. return {o: ret, r:retR};
  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. return node;
  67. }
  68. dijit.range.getCommonAncestor = function(n1,n2,root){
  69. root = root||n1.ownerDocument.body;
  70. var getAncestors = function(n){
  71. var as=[];
  72. while(n){
  73. as.unshift(n);
  74. if(n !== root){
  75. n = n.parentNode;
  76. }else{
  77. break;
  78. }
  79. }
  80. return as;
  81. };
  82. var n1as = getAncestors(n1);
  83. var n2as = getAncestors(n2);
  84. var m = Math.min(n1as.length,n2as.length);
  85. var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
  86. for(var i=1;i<m;i++){
  87. if(n1as[i] === n2as[i]){
  88. com = n1as[i]
  89. }else{
  90. break;
  91. }
  92. }
  93. return com;
  94. }
  95. dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
  96. root = root || node.ownerDocument.body;
  97. while(node && node !== root){
  98. var name = node.nodeName.toUpperCase() ;
  99. if(regex.test(name)){
  100. return node;
  101. }
  102. node = node.parentNode;
  103. }
  104. return null;
  105. }
  106. dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
  107. dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
  108. root = root || node.ownerDocument.body;
  109. regex = regex || dijit.range.BlockTagNames;
  110. var block=null, blockContainer;
  111. while(node && node !== root){
  112. var name = node.nodeName.toUpperCase() ;
  113. if(!block && regex.test(name)){
  114. block = node;
  115. }
  116. if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
  117. blockContainer = node;
  118. }
  119. node = node.parentNode;
  120. }
  121. return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
  122. }
  123. dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
  124. var atBeginning = false;
  125. var offsetAtBeginning = (offset == 0);
  126. if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space
  127. if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0,offset))){
  128. offsetAtBeginning = true;
  129. }
  130. }
  131. if(offsetAtBeginning){
  132. var cnode = node;
  133. atBeginning = true;
  134. while(cnode && cnode !== container){
  135. if(cnode.previousSibling){
  136. atBeginning = false;
  137. break;
  138. }
  139. cnode = cnode.parentNode;
  140. }
  141. }
  142. return atBeginning;
  143. }
  144. dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
  145. var atEnd = false;
  146. var offsetAtEnd = (offset == (node.length || node.childNodes.length));
  147. if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space
  148. if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){
  149. offsetAtEnd = true;
  150. }
  151. }
  152. if(offsetAtEnd){
  153. var cnode = node;
  154. atEnd = true;
  155. while(cnode && cnode !== container){
  156. if(cnode.nextSibling){
  157. atEnd = false;
  158. break;
  159. }
  160. cnode = cnode.parentNode;
  161. }
  162. }
  163. return atEnd;
  164. }
  165. dijit.range.adjacentNoneTextNode=function(startnode, next){
  166. var node = startnode;
  167. var len = (0-startnode.length) || 0;
  168. var prop = next?'nextSibling':'previousSibling';
  169. while(node){
  170. if(node.nodeType!=3){
  171. break;
  172. }
  173. len += node.length
  174. node = node[prop];
  175. }
  176. return [node,len];
  177. }
  178. dijit.range._w3c = Boolean(window['getSelection']);
  179. dijit.range.create = function(/*Window?*/win){
  180. if(dijit.range._w3c){
  181. return (win || dojo.global).document.createRange();
  182. }else{//IE
  183. return new dijit.range.W3CRange;
  184. }
  185. }
  186. dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){
  187. if(dijit.range._w3c){
  188. return win.getSelection();
  189. }else{//IE
  190. var s = new dijit.range.ie.selection(win);
  191. if(!ignoreUpdate){
  192. s._getCurrentSelection();
  193. }
  194. return s;
  195. }
  196. }
  197. if(!dijit.range._w3c){
  198. dijit.range.ie={
  199. cachedSelection: {},
  200. selection: function(win){
  201. this._ranges = [];
  202. this.addRange = function(r, /*boolean*/internal){
  203. this._ranges.push(r);
  204. if(!internal){
  205. r._select();
  206. }
  207. this.rangeCount = this._ranges.length;
  208. };
  209. this.removeAllRanges = function(){
  210. //don't detach, the range may be used later
  211. // for(var i=0;i<this._ranges.length;i++){
  212. // this._ranges[i].detach();
  213. // }
  214. this._ranges = [];
  215. this.rangeCount = 0;
  216. };
  217. var _initCurrentRange = function(){
  218. var r = win.document.selection.createRange();
  219. var type=win.document.selection.type.toUpperCase();
  220. if(type == "CONTROL"){
  221. //TODO: multiple range selection(?)
  222. return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
  223. }else{
  224. return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
  225. }
  226. };
  227. this.getRangeAt = function(i){
  228. return this._ranges[i];
  229. };
  230. this._getCurrentSelection = function(){
  231. this.removeAllRanges();
  232. var r=_initCurrentRange();
  233. if(r){
  234. this.addRange(r, true);
  235. }
  236. };
  237. },
  238. decomposeControlRange: function(range){
  239. var firstnode = range.item(0), lastnode = range.item(range.length-1);
  240. var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
  241. var startOffset = dijit.range.getIndex(firstnode, startContainer).o;
  242. var endOffset = dijit.range.getIndex(lastnode, endContainer).o+1;
  243. return [startContainer, startOffset,endContainer, endOffset];
  244. },
  245. getEndPoint: function(range, end){
  246. var atmrange = range.duplicate();
  247. atmrange.collapse(!end);
  248. var cmpstr = 'EndTo' + (end?'End':'Start');
  249. var parentNode = atmrange.parentElement();
  250. var startnode, startOffset, lastNode;
  251. if(parentNode.childNodes.length>0){
  252. dojo.every(parentNode.childNodes, function(node,i){
  253. var calOffset;
  254. if(node.nodeType != 3){
  255. atmrange.moveToElementText(node);
  256. if(atmrange.compareEndPoints(cmpstr,range) > 0){
  257. //startnode = node.previousSibling;
  258. if(lastNode && lastNode.nodeType == 3){
  259. //where shall we put the start? in the text node or after?
  260. startnode = lastNode;
  261. calOffset = true;
  262. }else{
  263. startnode = parentNode;
  264. startOffset = i;
  265. return false;
  266. }
  267. }else{
  268. if(i == parentNode.childNodes.length-1){
  269. startnode = parentNode;
  270. startOffset = parentNode.childNodes.length;
  271. return false;
  272. }
  273. }
  274. }else{
  275. if(i == parentNode.childNodes.length-1){//at the end of this node
  276. startnode = node;
  277. calOffset = true;
  278. }
  279. }
  280. // try{
  281. if(calOffset && startnode){
  282. var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
  283. if(prevnode){
  284. startnode = prevnode.nextSibling;
  285. }else{
  286. startnode = parentNode.firstChild; //firstChild must be a text node
  287. }
  288. var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
  289. prevnode = prevnodeobj[0];
  290. var lenoffset = prevnodeobj[1];
  291. if(prevnode){
  292. atmrange.moveToElementText(prevnode);
  293. atmrange.collapse(false);
  294. }else{
  295. atmrange.moveToElementText(parentNode);
  296. }
  297. atmrange.setEndPoint(cmpstr, range);
  298. startOffset = atmrange.text.length-lenoffset;
  299. return false;
  300. }
  301. // }catch(e){ debugger }
  302. lastNode = node;
  303. return true;
  304. });
  305. }else{
  306. startnode = parentNode;
  307. startOffset = 0;
  308. }
  309. //if at the end of startnode and we are dealing with start container, then
  310. //move the startnode to nextSibling if it is a text node
  311. //TODO: do this for end container?
  312. if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){
  313. var nextnode=startnode.nextSibling;
  314. if(nextnode && nextnode.nodeType == 3){
  315. startnode = nextnode;
  316. startOffset = 0;
  317. }
  318. }
  319. return [startnode, startOffset];
  320. },
  321. setEndPoint: function(range, container, offset){
  322. //text node
  323. var atmrange = range.duplicate(), node, len;
  324. if(container.nodeType!=3){ //normal node
  325. if(offset > 0){
  326. node = container.childNodes[offset-1];
  327. if(node){
  328. if(node.nodeType == 3){
  329. container = node;
  330. offset = node.length;
  331. //pass through
  332. }else{
  333. if(node.nextSibling && node.nextSibling.nodeType == 3){
  334. container=node.nextSibling;
  335. offset=0;
  336. //pass through
  337. }else{
  338. atmrange.moveToElementText(node.nextSibling?node:container);
  339. var parent = node.parentNode;
  340. var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling);
  341. atmrange.collapse(false);
  342. parent.removeChild(tempNode);
  343. }
  344. }
  345. }
  346. }else{
  347. atmrange.moveToElementText(container);
  348. atmrange.collapse(true);
  349. }
  350. }
  351. if(container.nodeType == 3){
  352. var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
  353. var prevnode = prevnodeobj[0];
  354. len = prevnodeobj[1];
  355. if(prevnode){
  356. atmrange.moveToElementText(prevnode);
  357. atmrange.collapse(false);
  358. //if contentEditable is not inherit, the above collapse won't make the end point
  359. //in the correctly position: it always has a -1 offset, so compensate it
  360. if(prevnode.contentEditable!='inherit'){
  361. len++;
  362. }
  363. }else{
  364. atmrange.moveToElementText(container.parentNode);
  365. atmrange.collapse(true);
  366. }
  367. offset += len;
  368. if(offset>0){
  369. if(atmrange.move('character',offset) != offset){
  370. console.error('Error when moving!');
  371. }
  372. }
  373. }
  374. return atmrange;
  375. },
  376. decomposeTextRange: function(range){
  377. var tmpary = dijit.range.ie.getEndPoint(range);
  378. var startContainer = tmpary[0], startOffset = tmpary[1];
  379. var endContainer = tmpary[0], endOffset = tmpary[1];
  380. if(range.htmlText.length){
  381. if(range.htmlText == range.text){ //in the same text node
  382. endOffset = startOffset+range.text.length;
  383. }else{
  384. tmpary = dijit.range.ie.getEndPoint(range,true);
  385. endContainer = tmpary[0], endOffset = tmpary[1];
  386. // if(startContainer.tagName == "BODY"){
  387. // startContainer = startContainer.firstChild;
  388. // }
  389. }
  390. }
  391. return [startContainer, startOffset, endContainer, endOffset];
  392. },
  393. setRange: function(range, startContainer,
  394. startOffset, endContainer, endOffset, collapsed){
  395. var start=dijit.range.ie.setEndPoint(range, startContainer, startOffset);
  396. range.setEndPoint('StartToStart',start);
  397. if(!collapsed){
  398. var end=dijit.range.ie.setEndPoint(range, endContainer, endOffset);
  399. }
  400. range.setEndPoint('EndToEnd',end || start);
  401. return range;
  402. }
  403. }
  404. dojo.declare("dijit.range.W3CRange",null, {
  405. constructor: function(){
  406. if(arguments.length>0){
  407. this.setStart(arguments[0][0],arguments[0][1]);
  408. this.setEnd(arguments[0][2],arguments[0][3]);
  409. }else{
  410. this.commonAncestorContainer = null;
  411. this.startContainer = null;
  412. this.startOffset = 0;
  413. this.endContainer = null;
  414. this.endOffset = 0;
  415. this.collapsed = true;
  416. }
  417. },
  418. _updateInternal: function(){
  419. if(this.startContainer !== this.endContainer){
  420. this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer);
  421. }else{
  422. this.commonAncestorContainer = this.startContainer;
  423. }
  424. this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
  425. },
  426. setStart: function(node, offset){
  427. offset=parseInt(offset);
  428. if(this.startContainer === node && this.startOffset == offset){
  429. return;
  430. }
  431. delete this._cachedBookmark;
  432. this.startContainer = node;
  433. this.startOffset = offset;
  434. if(!this.endContainer){
  435. this.setEnd(node, offset);
  436. }else{
  437. this._updateInternal();
  438. }
  439. },
  440. setEnd: function(node, offset){
  441. offset=parseInt(offset);
  442. if(this.endContainer === node && this.endOffset == offset){
  443. return;
  444. }
  445. delete this._cachedBookmark;
  446. this.endContainer = node;
  447. this.endOffset = offset;
  448. if(!this.startContainer){
  449. this.setStart(node, offset);
  450. }else{
  451. this._updateInternal();
  452. }
  453. },
  454. setStartAfter: function(node, offset){
  455. this._setPoint('setStart', node, offset, 1);
  456. },
  457. setStartBefore: function(node, offset){
  458. this._setPoint('setStart', node, offset, 0);
  459. },
  460. setEndAfter: function(node, offset){
  461. this._setPoint('setEnd', node, offset, 1);
  462. },
  463. setEndBefore: function(node, offset){
  464. this._setPoint('setEnd', node, offset, 0);
  465. },
  466. _setPoint: function(what, node, offset, ext){
  467. var index = dijit.range.getIndex(node, node.parentNode).o;
  468. this[what](node.parentNode, index.pop()+ext);
  469. },
  470. _getIERange: function(){
  471. var r = (this._body || this.endContainer.ownerDocument.body).createTextRange();
  472. dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed);
  473. return r;
  474. },
  475. getBookmark: function(body){
  476. this._getIERange();
  477. return this._cachedBookmark;
  478. },
  479. _select: function(){
  480. var r = this._getIERange();
  481. r.select();
  482. },
  483. deleteContents: function(){
  484. var r = this._getIERange();
  485. r.pasteHTML('');
  486. this.endContainer = this.startContainer;
  487. this.endOffset = this.startOffset;
  488. this.collapsed = true;
  489. },
  490. cloneRange: function(){
  491. var r = new dijit.range.W3CRange([this.startContainer,this.startOffset,
  492. this.endContainer,this.endOffset]);
  493. r._body = this._body;
  494. return r;
  495. },
  496. detach: function(){
  497. this._body = null;
  498. this.commonAncestorContainer = null;
  499. this.startContainer = null;
  500. this.startOffset = 0;
  501. this.endContainer = null;
  502. this.endOffset = 0;
  503. this.collapsed = true;
  504. }
  505. });
  506. } //if(!dijit.range._w3c)
  507. }