range.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. if (!dojo._hasResource["dijit._editor.range"]) { // _hasResource checks added
  2. // by build. Do not use
  3. // _hasResource directly in
  4. // your code.
  5. dojo._hasResource["dijit._editor.range"] = true;
  6. dojo.provide("dijit._editor.range");
  7. dijit.range = {};
  8. dijit.range.getIndex = function(/* DomNode */node, /* DomNode */parent) {
  9. // dojo.profile.start("dijit.range.getIndex");
  10. var ret = [], retR = [];
  11. var stop = parent;
  12. var onode = node;
  13. while (node != stop) {
  14. var i = 0;
  15. var pnode = node.parentNode, n;
  16. while (n = pnode.childNodes[i++]) {
  17. if (n === node) {
  18. --i;
  19. break;
  20. }
  21. }
  22. if (i >= pnode.childNodes.length) {
  23. dojo
  24. .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. // normalized() can not be called so often to prevent
  31. // invalidating selection/range, so we have to detect
  32. // here that any text nodes in a row
  33. if (ret.length > 0 && onode.nodeType == 3) {
  34. var n = onode.previousSibling;
  35. while (n && n.nodeType == 3) {
  36. ret[ret.length - 1]--;
  37. n = n.previousSibling;
  38. }
  39. n = onode.nextSibling;
  40. while (n && n.nodeType == 3) {
  41. retR[retR.length - 1]++;
  42. n = n.nextSibling;
  43. }
  44. }
  45. // dojo.profile.end("dijit.range.getIndex");
  46. return {
  47. o : ret,
  48. r : retR
  49. };
  50. }
  51. dijit.range.getNode = function(/* Array */index, /* DomNode */parent) {
  52. if (!dojo.isArray(index) || index.length == 0) {
  53. return parent;
  54. }
  55. var node = parent;
  56. // if(!node)debugger
  57. dojo.every(index, function(i) {
  58. if (i >= 0 && i < node.childNodes.length) {
  59. node = node.childNodes[i];
  60. } else {
  61. node = null;
  62. console.debug('Error: can not find node with index',
  63. index, 'under parent node', parent);
  64. return false; // terminate dojo.every
  65. }
  66. return true; // carry on the every loop
  67. });
  68. return node;
  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. var m = Math.min(n1as.length, n2as.length);
  86. var com = n1as[0]; // at least, one element should be in the array: the
  87. // 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? */
  98. 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. node = node.parentNode;
  106. }
  107. return null;
  108. }
  109. dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
  110. dijit.range.getBlockAncestor = function(/* DomNode */node, /* RegEx? */regex, /* DomNode? */
  111. root) {
  112. root = root || node.ownerDocument.body;
  113. regex = regex || dijit.range.BlockTagNames;
  114. var block = null, blockContainer;
  115. while (node && node !== root) {
  116. var name = node.nodeName.toUpperCase();
  117. if (!block && regex.test(name)) {
  118. block = node;
  119. }
  120. if (!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)) {
  121. blockContainer = node;
  122. }
  123. node = node.parentNode;
  124. }
  125. return {
  126. blockNode : block,
  127. blockContainer : blockContainer || node.ownerDocument.body
  128. };
  129. }
  130. dijit.range.atBeginningOfContainer = function(/* DomNode */container, /* DomNode */
  131. node, /* Int */offset) {
  132. var atBeginning = false;
  133. var offsetAtBeginning = (offset == 0);
  134. if (!offsetAtBeginning && node.nodeType == 3) { // if this is a text
  135. // node, check whether
  136. // the left part is all
  137. // space
  138. if (dojo.trim(node.nodeValue.substr(0, offset)) == 0) {
  139. offsetAtBeginning = true;
  140. }
  141. }
  142. if (offsetAtBeginning) {
  143. var cnode = node;
  144. atBeginning = true;
  145. while (cnode && cnode !== container) {
  146. if (cnode.previousSibling) {
  147. atBeginning = false;
  148. break;
  149. }
  150. cnode = cnode.parentNode;
  151. }
  152. }
  153. return atBeginning;
  154. }
  155. dijit.range.atEndOfContainer = function(/* DomNode */container, /* DomNode */
  156. node, /* Int */offset) {
  157. var atEnd = false;
  158. var offsetAtEnd = (offset == (node.length || node.childNodes.length));
  159. if (!offsetAtEnd && node.nodeType == 3) { // if this is a text node,
  160. // check whether the right
  161. // part is all space
  162. if (dojo.trim(node.nodeValue.substr(offset)) == 0) {
  163. offsetAtEnd = true;
  164. }
  165. }
  166. if (offsetAtEnd) {
  167. var cnode = node;
  168. atEnd = true;
  169. while (cnode && cnode !== container) {
  170. if (cnode.nextSibling) {
  171. atEnd = false;
  172. break;
  173. }
  174. cnode = cnode.parentNode;
  175. }
  176. }
  177. return atEnd;
  178. }
  179. dijit.range.adjacentNoneTextNode = function(startnode, next) {
  180. var node = startnode;
  181. var len = (0 - startnode.length) || 0;
  182. var prop = next ? 'nextSibling' : 'previousSibling';
  183. while (node) {
  184. if (node.nodeType != 3) {
  185. break;
  186. }
  187. len += node.length
  188. node = node[prop];
  189. }
  190. return [node, len];
  191. }
  192. dijit.range._w3c = Boolean(window['getSelection']);
  193. dijit.range.create = function() {
  194. if (dijit.range._w3c) {
  195. return document.createRange();
  196. } else {// IE
  197. return new dijit.range.W3CRange;
  198. }
  199. }
  200. dijit.range.getSelection = function(win, /* Boolean? */ignoreUpdate) {
  201. if (dijit.range._w3c) {
  202. return win.getSelection();
  203. } else {// IE
  204. var id = win.__W3CRange;
  205. if (!id || !dijit.range.ie.cachedSelection[id]) {
  206. var s = new dijit.range.ie.selection(win);
  207. // use win as the key in an object is not reliable, which
  208. // can leads to quite odd behaviors. thus we generate a
  209. // string and use it as a key in the cache
  210. id = (new Date).getTime();
  211. while (id in dijit.range.ie.cachedSelection) {
  212. id = id + 1;
  213. }
  214. id = String(id);
  215. dijit.range.ie.cachedSelection[id] = s;
  216. } else {
  217. var s = dijit.range.ie.cachedSelection[id];
  218. }
  219. if (!ignoreUpdate) {
  220. s._getCurrentSelection();
  221. }
  222. return s;
  223. }
  224. }
  225. if (!dijit.range._w3c) {
  226. dijit.range.ie = {
  227. cachedSelection : {},
  228. selection : function(win) {
  229. this._ranges = [];
  230. this.addRange = function(r, /* boolean */internal) {
  231. this._ranges.push(r);
  232. if (!internal) {
  233. r._select();
  234. }
  235. this.rangeCount = this._ranges.length;
  236. };
  237. this.removeAllRanges = function() {
  238. // don't detach, the range may be used later
  239. // for(var i=0;i<this._ranges.length;i++){
  240. // this._ranges[i].detach();
  241. // }
  242. this._ranges = [];
  243. this.rangeCount = 0;
  244. };
  245. var _initCurrentRange = function() {
  246. var r = win.document.selection.createRange();
  247. var type = win.document.selection.type.toUpperCase();
  248. if (type == "CONTROL") {
  249. // TODO: multiple range selection(?)
  250. return new dijit.range.W3CRange(dijit.range.ie
  251. .decomposeControlRange(r));
  252. } else {
  253. return new dijit.range.W3CRange(dijit.range.ie
  254. .decomposeTextRange(r));
  255. }
  256. };
  257. this.getRangeAt = function(i) {
  258. return this._ranges[i];
  259. };
  260. this._getCurrentSelection = function() {
  261. this.removeAllRanges();
  262. var r = _initCurrentRange();
  263. if (r) {
  264. this.addRange(r, true);
  265. }
  266. };
  267. },
  268. decomposeControlRange : function(range) {
  269. var firstnode = range.item(0), lastnode = range
  270. .item(range.length - 1)
  271. var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
  272. var startOffset = dijit.range.getIndex(firstnode,
  273. startContainer).o;
  274. var endOffset = dijit.range.getIndex(lastnode, endContainer).o
  275. + 1;
  276. return [[startContainer, startOffset],
  277. [endContainer, endOffset]];
  278. },
  279. getEndPoint : function(range, end) {
  280. var atmrange = range.duplicate();
  281. atmrange.collapse(!end);
  282. var cmpstr = 'EndTo' + (end ? 'End' : 'Start');
  283. var parentNode = atmrange.parentElement();
  284. var startnode, startOffset, lastNode;
  285. if (parentNode.childNodes.length > 0) {
  286. dojo.every(parentNode.childNodes, function(node, i) {
  287. var calOffset;
  288. if (node.nodeType != 3) {
  289. atmrange.moveToElementText(node);
  290. if (atmrange
  291. .compareEndPoints(cmpstr, range) > 0) {
  292. startnode = node.previousSibling;
  293. if (lastNode && lastNode.nodeType == 3) {
  294. // where share we put the start? in
  295. // the text node or after?
  296. startnode = lastNode;
  297. calOffset = true;
  298. } else {
  299. startnode = parentNode;
  300. startOffset = i;
  301. return false;
  302. }
  303. } else {
  304. if (i == parentNode.childNodes.length
  305. - 1) {
  306. startnode = parentNode;
  307. startOffset = parentNode.childNodes.length;
  308. return false;
  309. }
  310. }
  311. } else {
  312. if (i == parentNode.childNodes.length - 1) {// at
  313. // the
  314. // end
  315. // of
  316. // this
  317. // node
  318. startnode = node;
  319. calOffset = true;
  320. }
  321. }
  322. // try{
  323. if (calOffset && startnode) {
  324. var prevnode = dijit.range
  325. .adjacentNoneTextNode(startnode)[0];
  326. if (prevnode) {
  327. startnode = prevnode.nextSibling;
  328. } else {
  329. startnode = parentNode.firstChild; // firstChild
  330. // must
  331. // be a
  332. // text
  333. // node
  334. }
  335. var prevnodeobj = dijit.range
  336. .adjacentNoneTextNode(startnode);
  337. prevnode = prevnodeobj[0];
  338. var lenoffset = prevnodeobj[1];
  339. if (prevnode) {
  340. atmrange.moveToElementText(prevnode);
  341. atmrange.collapse(false);
  342. } else {
  343. atmrange.moveToElementText(parentNode);
  344. }
  345. atmrange.setEndPoint(cmpstr, range);
  346. startOffset = atmrange.text.length
  347. - lenoffset;
  348. return false;
  349. }
  350. // }catch(e){ debugger }
  351. lastNode = node;
  352. return true;
  353. });
  354. } else {
  355. startnode = parentNode;
  356. startOffset = 0;
  357. }
  358. // if at the end of startnode and we are dealing with start
  359. // container, then
  360. // move the startnode to nextSibling if it is a text node
  361. // TODO: do this for end container?
  362. if (!end && startnode.nodeType != 3
  363. && startOffset == startnode.childNodes.length) {
  364. if (startnode.nextSibling
  365. && startnode.nextSibling.nodeType == 3) {
  366. startnode = startnode.nextSibling;
  367. startOffset = 0;
  368. }
  369. }
  370. return [startnode, startOffset];
  371. },
  372. setEndPoint : function(range, container, offset) {
  373. // text node
  374. var atmrange = range.duplicate();
  375. if (container.nodeType != 3) { // normal node
  376. atmrange.moveToElementText(container);
  377. atmrange.collapse(true);
  378. if (offset == container.childNodes.length) {
  379. if (offset > 0) {
  380. // a simple atmrange.collapse(false); won't work
  381. // here:
  382. // although moveToElementText(node) is supposed to
  383. // encompass the content of the node,
  384. // but when collapse to end, it is in fact after the
  385. // ending tag of node (collapse to start
  386. // is after the begining tag of node as expected)
  387. var node = container.lastChild;
  388. var len = 0;
  389. while (node && node.nodeType == 3) {
  390. len += node.length;
  391. container = node; // pass through
  392. node = node.previousSibling;
  393. }
  394. if (node) {
  395. atmrange.moveToElementText(node);
  396. }
  397. atmrange.collapse(false);
  398. offset = len; // pass through
  399. } else { // no childNodes
  400. atmrange.moveToElementText(container);
  401. atmrange.collapse(true);
  402. }
  403. } else {
  404. if (offset > 0) {
  405. var node = container.childNodes[offset - 1];
  406. if (node.nodeType == 3) {
  407. container = node;
  408. offset = node.length;
  409. // pass through
  410. } else {
  411. atmrange.moveToElementText(node);
  412. atmrange.collapse(false);
  413. }
  414. }
  415. }
  416. }
  417. if (container.nodeType == 3) {
  418. var prevnodeobj = dijit.range
  419. .adjacentNoneTextNode(container);
  420. var prevnode = prevnodeobj[0], len = prevnodeobj[1];
  421. if (prevnode) {
  422. atmrange.moveToElementText(prevnode);
  423. atmrange.collapse(false);
  424. // if contentEditable is not inherit, the above collapse
  425. // won't make the end point
  426. // in the correctly position: it always has a -1 offset,
  427. // so compensate it
  428. if (prevnode.contentEditable != 'inherit') {
  429. len++;
  430. }
  431. } else {
  432. atmrange.moveToElementText(container.parentNode);
  433. atmrange.collapse(true);
  434. }
  435. offset += len;
  436. if (offset > 0) {
  437. if (atmrange.moveEnd('character', offset) != offset) {
  438. alert('Error when moving!');
  439. }
  440. atmrange.collapse(false);
  441. }
  442. }
  443. return atmrange;
  444. },
  445. decomposeTextRange : function(range) {
  446. var tmpary = dijit.range.ie.getEndPoint(range);
  447. var startContainter = tmpary[0], startOffset = tmpary[1];
  448. var endContainter = tmpary[0], endOffset = tmpary[1];
  449. if (range.htmlText.length) {
  450. if (range.htmlText == range.text) { // in the same text node
  451. endOffset = startOffset + range.text.length;
  452. } else {
  453. tmpary = dijit.range.ie.getEndPoint(range, true);
  454. endContainter = tmpary[0], endOffset = tmpary[1];
  455. }
  456. }
  457. return [[startContainter, startOffset],
  458. [endContainter, endOffset], range.parentElement()];
  459. },
  460. setRange : function(range, startContainter, startOffset,
  461. endContainter, endOffset, check) {
  462. var startrange = dijit.range.ie.setEndPoint(range,
  463. startContainter, startOffset);
  464. range.setEndPoint('StartToStart', startrange);
  465. if (!this.collapsed) {
  466. var endrange = dijit.range.ie.setEndPoint(range,
  467. endContainter, endOffset);
  468. range.setEndPoint('EndToEnd', endrange);
  469. }
  470. return range;
  471. }
  472. }
  473. dojo.declare("dijit.range.W3CRange", null, {
  474. constructor : function() {
  475. if (arguments.length > 0) {
  476. this.setStart(arguments[0][0][0], arguments[0][0][1]);
  477. this.setEnd(arguments[0][1][0], arguments[0][1][1],
  478. arguments[0][2]);
  479. } else {
  480. this.commonAncestorContainer = null;
  481. this.startContainer = null;
  482. this.startOffset = 0;
  483. this.endContainer = null;
  484. this.endOffset = 0;
  485. this.collapsed = true;
  486. }
  487. },
  488. _simpleSetEndPoint : function(node, range, end) {
  489. var r = (this._body || node.ownerDocument.body)
  490. .createTextRange();
  491. if (node.nodeType != 1) {
  492. r.moveToElementText(node.parentNode);
  493. } else {
  494. r.moveToElementText(node);
  495. }
  496. r.collapse(true);
  497. range.setEndPoint(end ? 'EndToEnd' : 'StartToStart', r);
  498. },
  499. _updateInternal : function(__internal_common) {
  500. if (this.startContainer !== this.endContainer) {
  501. if (!__internal_common) {
  502. var r = (this._body || this.startContainer.ownerDocument.body)
  503. .createTextRange();
  504. this._simpleSetEndPoint(this.startContainer, r);
  505. this._simpleSetEndPoint(this.endContainer, r, true);
  506. __internal_common = r.parentElement();
  507. }
  508. this.commonAncestorContainer = dijit.range
  509. .getCommonAncestor(this.startContainer,
  510. this.endContainer, __internal_common);
  511. } else {
  512. this.commonAncestorContainer = this.startContainer;
  513. }
  514. this.collapsed = (this.startContainer === this.endContainer)
  515. && (this.startOffset == this.endOffset);
  516. },
  517. setStart : function(node, offset, __internal_common) {
  518. if (this.startContainer === node && this.startOffset == offset) {
  519. return;
  520. }
  521. delete this._cachedBookmark;
  522. this.startContainer = node;
  523. this.startOffset = offset;
  524. if (!this.endContainer) {
  525. this.setEnd(node, offset, __internal_common);
  526. } else {
  527. this._updateInternal(__internal_common);
  528. }
  529. },
  530. setEnd : function(node, offset, __internal_common) {
  531. if (this.endContainer === node && this.endOffset == offset) {
  532. return;
  533. }
  534. delete this._cachedBookmark;
  535. this.endContainer = node;
  536. this.endOffset = offset;
  537. if (!this.startContainer) {
  538. this.setStart(node, offset, __internal_common);
  539. } else {
  540. this._updateInternal(__internal_common);
  541. }
  542. },
  543. setStartAfter : function(node, offset) {
  544. this._setPoint('setStart', node, offset, 1);
  545. },
  546. setStartBefore : function(node, offset) {
  547. this._setPoint('setStart', node, offset, 0);
  548. },
  549. setEndAfter : function(node, offset) {
  550. this._setPoint('setEnd', node, offset, 1);
  551. },
  552. setEndBefore : function(node, offset) {
  553. this._setPoint('setEnd', node, offset, 0);
  554. },
  555. _setPoint : function(what, node, offset, ext) {
  556. var index = dijit.range.getIndex(node, node.parentNode).o;
  557. this[what](node.parentNode, index.pop() + ext);
  558. },
  559. _getIERange : function() {
  560. var r = (this._body || this.endContainer.ownerDocument.body)
  561. .createTextRange();
  562. dijit.range.ie.setRange(r, this.startContainer,
  563. this.startOffset, this.endContainer, this.endOffset);
  564. return r;
  565. },
  566. getBookmark : function(body) {
  567. this._getIERange();
  568. return this._cachedBookmark;
  569. },
  570. _select : function() {
  571. var r = this._getIERange();
  572. r.select();
  573. },
  574. deleteContents : function() {
  575. var r = this._getIERange();
  576. r.pasteHTML('');
  577. this.endContainer = this.startContainer;
  578. this.endOffset = this.startOffset;
  579. this.collapsed = true;
  580. },
  581. cloneRange : function() {
  582. var r = new dijit.range.W3CRange([
  583. [this.startContainer, this.startOffset],
  584. [this.endContainer, this.endOffset]]);
  585. r._body = this._body;
  586. return r;
  587. },
  588. detach : function() {
  589. this._body = null;
  590. this.commonAncestorContainer = null;
  591. this.startContainer = null;
  592. this.startOffset = 0;
  593. this.endContainer = null;
  594. this.endOffset = 0;
  595. this.collapsed = true;
  596. }
  597. });
  598. } // if(!dijit.range._w3c)
  599. }