4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
21 START_OFFSET = 'startOffset',
22 START_CONTAINER = 'startContainer',
23 END_CONTAINER = 'endContainer',
24 END_OFFSET = 'endOffset',
25 extend = tinymce.extend,
26 nodeIndex = dom.nodeIndex;
35 commonAncestorContainer : doc,
46 setStartBefore : setStartBefore,
47 setStartAfter : setStartAfter,
48 setEndBefore : setEndBefore,
49 setEndAfter : setEndAfter,
51 selectNode : selectNode,
52 selectNodeContents : selectNodeContents,
53 compareBoundaryPoints : compareBoundaryPoints,
54 deleteContents : deleteContents,
55 extractContents : extractContents,
56 cloneContents : cloneContents,
57 insertNode : insertNode,
58 surroundContents : surroundContents,
59 cloneRange : cloneRange
62 function setStart(n, o) {
63 _setEndPoint(TRUE, n, o);
66 function setEnd(n, o) {
67 _setEndPoint(FALSE, n, o);
70 function setStartBefore(n) {
71 setStart(n.parentNode, nodeIndex(n));
74 function setStartAfter(n) {
75 setStart(n.parentNode, nodeIndex(n) + 1);
78 function setEndBefore(n) {
79 setEnd(n.parentNode, nodeIndex(n));
82 function setEndAfter(n) {
83 setEnd(n.parentNode, nodeIndex(n) + 1);
86 function collapse(ts) {
88 t[END_CONTAINER] = t[START_CONTAINER];
89 t[END_OFFSET] = t[START_OFFSET];
91 t[START_CONTAINER] = t[END_CONTAINER];
92 t[START_OFFSET] = t[END_OFFSET];
98 function selectNode(n) {
103 function selectNodeContents(n) {
105 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
108 function compareBoundaryPoints(h, r) {
109 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
110 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
112 // Check START_TO_START
114 return _compareBoundaryPoints(sc, so, rsc, rso);
116 // Check START_TO_END
118 return _compareBoundaryPoints(ec, eo, rsc, rso);
122 return _compareBoundaryPoints(ec, eo, rec, reo);
124 // Check END_TO_START
126 return _compareBoundaryPoints(sc, so, rec, reo);
129 function deleteContents() {
133 function extractContents() {
134 return _traverse(EXTRACT);
137 function cloneContents() {
138 return _traverse(CLONE);
141 function insertNode(n) {
142 var startContainer = this[START_CONTAINER],
143 startOffset = this[START_OFFSET], nn, o;
145 // Node is TEXT_NODE or CDATA
146 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
148 // At the start of text
149 startContainer.parentNode.insertBefore(n, startContainer);
150 } else if (startOffset >= startContainer.nodeValue.length) {
151 // At the end of text
152 dom.insertAfter(n, startContainer);
154 // Middle, need to split
155 nn = startContainer.splitText(startOffset);
156 startContainer.parentNode.insertBefore(n, nn);
159 // Insert element node
160 if (startContainer.childNodes.length > 0)
161 o = startContainer.childNodes[startOffset];
164 startContainer.insertBefore(n, o);
166 startContainer.appendChild(n);
170 function surroundContents(n) {
171 var f = t.extractContents();
178 function cloneRange() {
179 return extend(new Range(dom), {
180 startContainer : t[START_CONTAINER],
181 startOffset : t[START_OFFSET],
182 endContainer : t[END_CONTAINER],
183 endOffset : t[END_OFFSET],
184 collapsed : t.collapsed,
185 commonAncestorContainer : t.commonAncestorContainer
191 function _getSelectedNode(container, offset) {
194 if (container.nodeType == 3 /* TEXT_NODE */)
200 child = container.firstChild;
201 while (child && offset > 0) {
203 child = child.nextSibling;
212 function _isCollapsed() {
213 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
216 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
217 var c, offsetC, n, cmnRoot, childA, childB;
219 // In the first case the boundary-points have the same container. A is before B
220 // if its offset is less than the offset of B, A is equal to B if its offset is
221 // equal to the offset of B, and A is after B if its offset is greater than the
223 if (containerA == containerB) {
224 if (offsetA == offsetB)
227 if (offsetA < offsetB)
233 // In the second case a child node C of the container of A is an ancestor
234 // container of B. In this case, A is before B if the offset of A is less than or
235 // equal to the index of the child node C and A is after B otherwise.
237 while (c && c.parentNode != containerA)
242 n = containerA.firstChild;
244 while (n != c && offsetC < offsetA) {
249 if (offsetA <= offsetC)
255 // In the third case a child node C of the container of B is an ancestor container
256 // of A. In this case, A is before B if the index of the child node C is less than
257 // the offset of B and A is after B otherwise.
259 while (c && c.parentNode != containerB) {
265 n = containerB.firstChild;
267 while (n != c && offsetC < offsetB) {
272 if (offsetC < offsetB)
278 // In the fourth case, none of three other cases hold: the containers of A and B
279 // are siblings or descendants of sibling nodes. In this case, A is before B if
280 // the container of A is before the container of B in a pre-order traversal of the
281 // Ranges' context tree and A is after B otherwise.
282 cmnRoot = dom.findCommonAncestor(containerA, containerB);
285 while (childA && childA.parentNode != cmnRoot)
286 childA = childA.parentNode;
292 while (childB && childB.parentNode != cmnRoot)
293 childB = childB.parentNode;
298 if (childA == childB)
301 n = cmnRoot.firstChild;
313 function _setEndPoint(st, n, o) {
317 t[START_CONTAINER] = n;
320 t[END_CONTAINER] = n;
324 // If one boundary-point of a Range is set to have a root container
325 // other than the current one for the Range, the Range is collapsed to
326 // the new position. This enforces the restriction that both boundary-
327 // points of a Range must have the same root container.
328 ec = t[END_CONTAINER];
329 while (ec.parentNode)
332 sc = t[START_CONTAINER];
333 while (sc.parentNode)
337 // The start position of a Range is guaranteed to never be after the
338 // end position. To enforce this restriction, if the start is set to
339 // be at a position after the end, the Range is collapsed to that
341 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
346 t.collapsed = _isCollapsed();
347 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
350 function _traverse(how) {
351 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
353 if (t[START_CONTAINER] == t[END_CONTAINER])
354 return _traverseSameContainer(how);
356 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
357 if (p == t[START_CONTAINER])
358 return _traverseCommonStartContainer(c, how);
363 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
364 if (p == t[END_CONTAINER])
365 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;
397 frag = doc.createDocumentFragment();
399 // If selection is empty, just return the fragment
400 if (t[START_OFFSET] == t[END_OFFSET])
403 // Text node needs special case handling
404 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
406 s = t[START_CONTAINER].nodeValue;
407 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
409 // set the original text node to its new value
411 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
413 // Nothing is partially selected, so collapse to start point
420 frag.appendChild(doc.createTextNode(sub));
424 // Copy nodes between the start/end offsets.
425 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
426 cnt = t[END_OFFSET] - t[START_OFFSET];
429 sibling = n.nextSibling;
430 xferNode = _traverseFullySelected(n, how);
433 frag.appendChild( xferNode );
439 // Nothing is partially selected, so collapse to start point
446 function _traverseCommonStartContainer(endAncestor, how) {
447 var frag, n, endIdx, cnt, sibling, xferNode;
450 frag = doc.createDocumentFragment();
452 n = _traverseRightBoundary(endAncestor, how);
457 endIdx = nodeIndex(endAncestor);
458 cnt = endIdx - t[START_OFFSET];
461 // Collapse to just before the endAncestor, which
462 // is partially selected.
464 t.setEndBefore(endAncestor);
471 n = endAncestor.previousSibling;
473 sibling = n.previousSibling;
474 xferNode = _traverseFullySelected(n, how);
477 frag.insertBefore(xferNode, frag.firstChild);
483 // Collapse to just before the endAncestor, which
484 // is partially selected.
486 t.setEndBefore(endAncestor);
493 function _traverseCommonEndContainer(startAncestor, how) {
494 var frag, startIdx, n, cnt, sibling, xferNode;
497 frag = doc.createDocumentFragment();
499 n = _traverseLeftBoundary(startAncestor, how);
503 startIdx = nodeIndex(startAncestor);
504 ++startIdx; // Because we already traversed it
506 cnt = t[END_OFFSET] - startIdx;
507 n = startAncestor.nextSibling;
509 sibling = n.nextSibling;
510 xferNode = _traverseFullySelected(n, how);
513 frag.appendChild(xferNode);
520 t.setStartAfter(startAncestor);
527 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
528 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
531 frag = doc.createDocumentFragment();
533 n = _traverseLeftBoundary(startAncestor, how);
537 commonParent = startAncestor.parentNode;
538 startOffset = nodeIndex(startAncestor);
539 endOffset = nodeIndex(endAncestor);
542 cnt = endOffset - startOffset;
543 sibling = startAncestor.nextSibling;
546 nextSibling = sibling.nextSibling;
547 n = _traverseFullySelected(sibling, how);
552 sibling = nextSibling;
556 n = _traverseRightBoundary(endAncestor, how);
562 t.setStartAfter(startAncestor);
569 function _traverseRightBoundary(root, how) {
570 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
573 return _traverseNode(next, isFullySelected, FALSE, how);
575 parent = next.parentNode;
576 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
580 prevSibling = next.previousSibling;
581 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
584 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
586 isFullySelected = TRUE;
593 next = parent.previousSibling;
594 parent = parent.parentNode;
596 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
599 clonedGrandParent.appendChild(clonedParent);
601 clonedParent = clonedGrandParent;
605 function _traverseLeftBoundary(root, how) {
606 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
609 return _traverseNode(next, isFullySelected, TRUE, how);
611 parent = next.parentNode;
612 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
616 nextSibling = next.nextSibling;
617 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
620 clonedParent.appendChild(clonedChild);
622 isFullySelected = TRUE;
629 next = parent.nextSibling;
630 parent = parent.parentNode;
632 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
635 clonedGrandParent.appendChild(clonedParent);
637 clonedParent = clonedGrandParent;
641 function _traverseNode(n, isFullySelected, isLeft, how) {
642 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
645 return _traverseFullySelected(n, how);
647 if (n.nodeType == 3 /* TEXT_NODE */) {
648 txtValue = n.nodeValue;
651 offset = t[START_OFFSET];
652 newNodeValue = txtValue.substring(offset);
653 oldNodeValue = txtValue.substring(0, offset);
655 offset = t[END_OFFSET];
656 newNodeValue = txtValue.substring(0, offset);
657 oldNodeValue = txtValue.substring(offset);
661 n.nodeValue = oldNodeValue;
666 newNode = n.cloneNode(FALSE);
667 newNode.nodeValue = newNodeValue;
675 return n.cloneNode(FALSE);
678 function _traverseFullySelected(n, how) {
680 return how == CLONE ? n.cloneNode(TRUE) : n;
682 n.parentNode.removeChild(n);