4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
11 define("tinymce/dom/Range", [
23 START_OFFSET = 'startOffset',
24 START_CONTAINER = 'startContainer',
25 END_CONTAINER = 'endContainer',
26 END_OFFSET = 'endOffset',
27 extend = Tools.extend,
28 nodeIndex = dom.nodeIndex;
30 function createDocumentFragment() {
31 return doc.createDocumentFragment();
34 function setStart(n, o) {
35 _setEndPoint(TRUE, n, o);
38 function setEnd(n, o) {
39 _setEndPoint(FALSE, n, o);
42 function setStartBefore(n) {
43 setStart(n.parentNode, nodeIndex(n));
46 function setStartAfter(n) {
47 setStart(n.parentNode, nodeIndex(n) + 1);
50 function setEndBefore(n) {
51 setEnd(n.parentNode, nodeIndex(n));
54 function setEndAfter(n) {
55 setEnd(n.parentNode, nodeIndex(n) + 1);
58 function collapse(ts) {
60 t[END_CONTAINER] = t[START_CONTAINER];
61 t[END_OFFSET] = t[START_OFFSET];
63 t[START_CONTAINER] = t[END_CONTAINER];
64 t[START_OFFSET] = t[END_OFFSET];
70 function selectNode(n) {
75 function selectNodeContents(n) {
77 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
80 function compareBoundaryPoints(h, r) {
81 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
82 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
84 // Check START_TO_START
86 return _compareBoundaryPoints(sc, so, rsc, rso);
91 return _compareBoundaryPoints(ec, eo, rsc, rso);
96 return _compareBoundaryPoints(ec, eo, rec, reo);
101 return _compareBoundaryPoints(sc, so, rec, reo);
105 function deleteContents() {
109 function extractContents() {
110 return _traverse(EXTRACT);
113 function cloneContents() {
114 return _traverse(CLONE);
117 function insertNode(n) {
118 var startContainer = this[START_CONTAINER],
119 startOffset = this[START_OFFSET], nn, o;
121 // Node is TEXT_NODE or CDATA
122 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
124 // At the start of text
125 startContainer.parentNode.insertBefore(n, startContainer);
126 } else if (startOffset >= startContainer.nodeValue.length) {
127 // At the end of text
128 dom.insertAfter(n, startContainer);
130 // Middle, need to split
131 nn = startContainer.splitText(startOffset);
132 startContainer.parentNode.insertBefore(n, nn);
135 // Insert element node
136 if (startContainer.childNodes.length > 0) {
137 o = startContainer.childNodes[startOffset];
141 startContainer.insertBefore(n, o);
143 startContainer.appendChild(n);
148 function surroundContents(n) {
149 var f = t.extractContents();
156 function cloneRange() {
157 return extend(new Range(dom), {
158 startContainer: t[START_CONTAINER],
159 startOffset: t[START_OFFSET],
160 endContainer: t[END_CONTAINER],
161 endOffset: t[END_OFFSET],
162 collapsed: t.collapsed,
163 commonAncestorContainer: t.commonAncestorContainer
169 function _getSelectedNode(container, offset) {
172 if (container.nodeType == 3 /* TEXT_NODE */) {
180 child = container.firstChild;
181 while (child && offset > 0) {
183 child = child.nextSibling;
193 function _isCollapsed() {
194 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
197 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
198 var c, offsetC, n, cmnRoot, childA, childB;
200 // In the first case the boundary-points have the same container. A is before B
201 // if its offset is less than the offset of B, A is equal to B if its offset is
202 // equal to the offset of B, and A is after B if its offset is greater than the
204 if (containerA == containerB) {
205 if (offsetA == offsetB) {
209 if (offsetA < offsetB) {
216 // In the second case a child node C of the container of A is an ancestor
217 // container of B. In this case, A is before B if the offset of A is less than or
218 // equal to the index of the child node C and A is after B otherwise.
220 while (c && c.parentNode != containerA) {
226 n = containerA.firstChild;
228 while (n != c && offsetC < offsetA) {
233 if (offsetA <= offsetC) {
240 // In the third case a child node C of the container of B is an ancestor container
241 // of A. In this case, A is before B if the index of the child node C is less than
242 // the offset of B and A is after B otherwise.
244 while (c && c.parentNode != containerB) {
250 n = containerB.firstChild;
252 while (n != c && offsetC < offsetB) {
257 if (offsetC < offsetB) {
264 // In the fourth case, none of three other cases hold: the containers of A and B
265 // are siblings or descendants of sibling nodes. In this case, A is before B if
266 // the container of A is before the container of B in a pre-order traversal of the
267 // Ranges' context tree and A is after B otherwise.
268 cmnRoot = dom.findCommonAncestor(containerA, containerB);
271 while (childA && childA.parentNode != cmnRoot) {
272 childA = childA.parentNode;
280 while (childB && childB.parentNode != cmnRoot) {
281 childB = childB.parentNode;
288 if (childA == childB) {
292 n = cmnRoot.firstChild;
306 function _setEndPoint(st, n, o) {
310 t[START_CONTAINER] = n;
313 t[END_CONTAINER] = n;
317 // If one boundary-point of a Range is set to have a root container
318 // other than the current one for the Range, the Range is collapsed to
319 // the new position. This enforces the restriction that both boundary-
320 // points of a Range must have the same root container.
321 ec = t[END_CONTAINER];
322 while (ec.parentNode) {
326 sc = t[START_CONTAINER];
327 while (sc.parentNode) {
332 // The start position of a Range is guaranteed to never be after the
333 // end position. To enforce this restriction, if the start is set to
334 // be at a position after the end, the Range is collapsed to that
336 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) {
343 t.collapsed = _isCollapsed();
344 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
347 function _traverse(how) {
348 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
350 if (t[START_CONTAINER] == t[END_CONTAINER]) {
351 return _traverseSameContainer(how);
354 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
355 if (p == t[START_CONTAINER]) {
356 return _traverseCommonStartContainer(c, how);
362 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
363 if (p == t[END_CONTAINER]) {
364 return _traverseCommonEndContainer(c, how);
367 ++startContainerDepth;
370 depthDiff = startContainerDepth - endContainerDepth;
372 startNode = t[START_CONTAINER];
373 while (depthDiff > 0) {
374 startNode = startNode.parentNode;
378 endNode = t[END_CONTAINER];
379 while (depthDiff < 0) {
380 endNode = endNode.parentNode;
384 // ascend the ancestor hierarchy until we have a common parent.
385 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
390 return _traverseCommonAncestors(startNode, endNode, how);
393 function _traverseSameContainer(how) {
394 var frag, s, sub, n, cnt, sibling, xferNode, start, len;
397 frag = createDocumentFragment();
400 // If selection is empty, just return the fragment
401 if (t[START_OFFSET] == t[END_OFFSET]) {
405 // Text node needs special case handling
406 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
408 s = t[START_CONTAINER].nodeValue;
409 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
411 // set the original text node to its new value
413 n = t[START_CONTAINER];
414 start = t[START_OFFSET];
415 len = t[END_OFFSET] - t[START_OFFSET];
417 if (start === 0 && len >= n.nodeValue.length - 1) {
418 n.parentNode.removeChild(n);
420 n.deleteData(start, len);
423 // Nothing is partially selected, so collapse to start point
431 if (sub.length > 0) {
432 frag.appendChild(doc.createTextNode(sub));
438 // Copy nodes between the start/end offsets.
439 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
440 cnt = t[END_OFFSET] - t[START_OFFSET];
442 while (n && cnt > 0) {
443 sibling = n.nextSibling;
444 xferNode = _traverseFullySelected(n, how);
447 frag.appendChild(xferNode);
454 // Nothing is partially selected, so collapse to start point
462 function _traverseCommonStartContainer(endAncestor, how) {
463 var frag, n, endIdx, cnt, sibling, xferNode;
466 frag = createDocumentFragment();
469 n = _traverseRightBoundary(endAncestor, how);
475 endIdx = nodeIndex(endAncestor);
476 cnt = endIdx - t[START_OFFSET];
479 // Collapse to just before the endAncestor, which
480 // is partially selected.
482 t.setEndBefore(endAncestor);
489 n = endAncestor.previousSibling;
491 sibling = n.previousSibling;
492 xferNode = _traverseFullySelected(n, how);
495 frag.insertBefore(xferNode, frag.firstChild);
502 // Collapse to just before the endAncestor, which
503 // is partially selected.
505 t.setEndBefore(endAncestor);
512 function _traverseCommonEndContainer(startAncestor, how) {
513 var frag, startIdx, n, cnt, sibling, xferNode;
516 frag = createDocumentFragment();
519 n = _traverseLeftBoundary(startAncestor, how);
524 startIdx = nodeIndex(startAncestor);
525 ++startIdx; // Because we already traversed it
527 cnt = t[END_OFFSET] - startIdx;
528 n = startAncestor.nextSibling;
529 while (n && cnt > 0) {
530 sibling = n.nextSibling;
531 xferNode = _traverseFullySelected(n, how);
534 frag.appendChild(xferNode);
542 t.setStartAfter(startAncestor);
549 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
550 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
553 frag = createDocumentFragment();
556 n = _traverseLeftBoundary(startAncestor, how);
561 commonParent = startAncestor.parentNode;
562 startOffset = nodeIndex(startAncestor);
563 endOffset = nodeIndex(endAncestor);
566 cnt = endOffset - startOffset;
567 sibling = startAncestor.nextSibling;
570 nextSibling = sibling.nextSibling;
571 n = _traverseFullySelected(sibling, how);
577 sibling = nextSibling;
581 n = _traverseRightBoundary(endAncestor, how);
588 t.setStartAfter(startAncestor);
595 function _traverseRightBoundary(root, how) {
596 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent;
597 var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
600 return _traverseNode(next, isFullySelected, FALSE, how);
603 parent = next.parentNode;
604 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
608 prevSibling = next.previousSibling;
609 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
612 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
615 isFullySelected = TRUE;
619 if (parent == root) {
623 next = parent.previousSibling;
624 parent = parent.parentNode;
626 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
629 clonedGrandParent.appendChild(clonedParent);
632 clonedParent = clonedGrandParent;
636 function _traverseLeftBoundary(root, how) {
637 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER];
638 var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
641 return _traverseNode(next, isFullySelected, TRUE, how);
644 parent = next.parentNode;
645 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
649 nextSibling = next.nextSibling;
650 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
653 clonedParent.appendChild(clonedChild);
656 isFullySelected = TRUE;
660 if (parent == root) {
664 next = parent.nextSibling;
665 parent = parent.parentNode;
667 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
670 clonedGrandParent.appendChild(clonedParent);
673 clonedParent = clonedGrandParent;
677 function _traverseNode(n, isFullySelected, isLeft, how) {
678 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
680 if (isFullySelected) {
681 return _traverseFullySelected(n, how);
684 if (n.nodeType == 3 /* TEXT_NODE */) {
685 txtValue = n.nodeValue;
688 offset = t[START_OFFSET];
689 newNodeValue = txtValue.substring(offset);
690 oldNodeValue = txtValue.substring(0, offset);
692 offset = t[END_OFFSET];
693 newNodeValue = txtValue.substring(0, offset);
694 oldNodeValue = txtValue.substring(offset);
698 n.nodeValue = oldNodeValue;
705 newNode = dom.clone(n, FALSE);
706 newNode.nodeValue = newNodeValue;
715 return dom.clone(n, FALSE);
718 function _traverseFullySelected(n, how) {
720 return how == CLONE ? dom.clone(n, TRUE) : n;
723 n.parentNode.removeChild(n);
726 function toStringIE() {
727 return dom.create('body', null, cloneContents()).outerText;
737 commonAncestorContainer: doc,
748 setStartBefore: setStartBefore,
749 setStartAfter: setStartAfter,
750 setEndBefore: setEndBefore,
751 setEndAfter: setEndAfter,
753 selectNode: selectNode,
754 selectNodeContents: selectNodeContents,
755 compareBoundaryPoints: compareBoundaryPoints,
756 deleteContents: deleteContents,
757 extractContents: extractContents,
758 cloneContents: cloneContents,
759 insertNode: insertNode,
760 surroundContents: surroundContents,
761 cloneRange: cloneRange,
762 toStringIE: toStringIE
768 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
769 Range.prototype.toString = function() {
770 return this.toStringIE();