range.js 15 KB

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