/** * $Id$ * * @author Moxiecode * @copyright Copyright © 2004-2007, Moxiecode Systems AB, All rights reserved. */ /** * Constructs a Selection instance and binds it to the specificed TinyMCE editor control. * * @param {TinyMCE_Control} inst TinyMCE editor control instance. * @constructor * @member TinyMCE_Selection */ function TinyMCE_Selection(inst) { this.instance = inst; }; /**#@+ * @member TinyMCE_Selection */ TinyMCE_Selection.prototype = { /**#@+ * @method */ /** * Returns the selected HTML code. * * @return Selected HTML contents. * @type string */ getSelectedHTML : function() { var inst = this.instance, e, r = this.getRng(), h; if (!r) return null; e = document.createElement("body"); if (r.cloneContents) e.appendChild(r.cloneContents()); else if (typeof(r.item) != 'undefined' || typeof(r.htmlText) != 'undefined') e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText; else e.innerHTML = r.toString(); // Failed, use text for now h = tinyMCE._cleanupHTML(inst, inst.contentDocument, inst.settings, e, e, false, true, false); // When editing always use fonts internaly //if (tinyMCE.getParam("convert_fonts_to_spans")) // tinyMCE.convertSpansToFonts(inst.getDoc()); return h; }, /** * Returns the selected text. * * @return Selected text contents. * @type string */ getSelectedText : function() { var inst = this.instance, d, r, s, t; if (tinyMCE.isIE) { d = inst.getDoc(); if (d.selection.type == "Text") { r = d.selection.createRange(); t = r.text; } else t = ''; } else { s = this.getSel(); if (s && s.toString) t = s.toString(); else t = ''; } return t; }, /** * Returns a selection bookmark that can be restored later with moveToBookmark. * This acts much like the one MSIE has built in but this one is persistent if between DOM * tree rewritings. The simple mode enables a quicker and non persistent bookmark. * * @param {boolean} simple If this is set to true, the selection bookmark will not me dom persistent. * @return Selection bookmark that can be restored later with moveToBookmark. * @type TinyMCE_Bookmark */ getBookmark : function(simple) { var inst = this.instance, rng = this.getRng(), doc = inst.getDoc(), b = inst.getBody(); var trng, sx, sy, xx = -999999999, vp = inst.getViewPort(); var sp, le, s, e, nl, i, si, ei, w; sx = vp.left; sy = vp.top; if (simple) return {rng : rng, scrollX : sx, scrollY : sy}; if (tinyMCE.isRealIE) { if (rng.item) { e = rng.item(0); nl = b.getElementsByTagName(e.nodeName); for (i=0; i bookmark.index) { try { rng.addElement(nl[bookmark.index]); } catch (ex) { // Might be thrown if the node no longer exists } } } else { // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs try { // Incorrect bookmark if (bookmark.start < 0) return true; rng = inst.getSel().createRange(); rng.moveToElementText(inst.getBody()); rng.collapse(true); rng.moveStart('character', bookmark.start); rng.moveEnd('character', bookmark.length); } catch (ex) { return true; } } rng.select(); win.scrollTo(bookmark.scrollX, bookmark.scrollY); return true; } if (tinyMCE.isGecko || tinyMCE.isOpera) { if (!sel) return false; if (bookmark.rng) { sel.removeAllRanges(); sel.addRange(bookmark.rng); } if (bookmark.start != -1 && bookmark.end != -1) { try { sd = this._getTextPos(b, bookmark.start, bookmark.end); rng = doc.createRange(); rng.setStart(sd.startNode, sd.startOffset); rng.setEnd(sd.endNode, sd.endOffset); sel.removeAllRanges(); sel.addRange(rng); if (!tinyMCE.isOpera) win.focus(); } catch (ex) { // Ignore } } /* if (typeof(bookmark.index) != 'undefined') { tinyMCE.selectElements(b, 'IMG', function (n) { if (bookmark.index-- == 0) { // Select image in Gecko here } return false; }); } */ win.scrollTo(bookmark.scrollX, bookmark.scrollY); return true; } return false; }, /** * Returns the character start and end position of a start and end node. This is Gecko specific logic. * * @param {Element} r Root element to loop through. * @param {Node} sn Start node to get char position of. * @param {Node} en End node to get char position of. * @return Data container with start/end properties. * @type Object * @private */ _getPosText : function(r, sn, en) { var w = document.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; while ((n = w.nextNode()) != null) { if (n == sn) d.start = p; if (n == en) { d.end = p; return d; } p += n.nodeValue ? n.nodeValue.length : 0; } return null; }, /** * Returns the node and offset of start and end character positions. This is Gecko specific logic. * * @param {Element} r Root element to loop through. * @param {int} sp Start character position. * @param {Node} ep End character position. * @return Data container with startNode/startOffset/endNode/endOffset properties. * @type Object * @private */ _getTextPos : function(r, sp, ep) { var w = document.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; while ((n = w.nextNode()) != null) { p += n.nodeValue ? n.nodeValue.length : 0; if (p >= sp && !d.startNode) { d.startNode = n; d.startOffset = sp - (p - n.nodeValue.length); } if (p >= ep) { d.endNode = n; d.endOffset = ep - (p - n.nodeValue.length); return d; } } return null; }, /** * Selects the specified node. * * @param {HTMLNode} node Node object to move selection to. * @param {boolean} collapse True/false if it will be collasped. * @param {boolean} select_text_node True/false if the text contents should be selected or not. * @param {boolean} to_start True/false if the collapse should be to start or end of range. */ selectNode : function(node, collapse, select_text_node, to_start) { var inst = this.instance, sel, rng, nodes; if (!node) return; if (typeof(collapse) == "undefined") collapse = true; if (typeof(select_text_node) == "undefined") select_text_node = false; if (typeof(to_start) == "undefined") to_start = true; if (inst.settings.auto_resize) inst.resizeToContent(); if (tinyMCE.isRealIE) { rng = inst.getDoc().body.createTextRange(); try { rng.moveToElementText(node); if (collapse) rng.collapse(to_start); rng.select(); } catch (e) { // Throws illigal agrument in MSIE some times } } else { sel = this.getSel(); if (!sel) return; if (tinyMCE.isSafari) { sel.setBaseAndExtent(node, 0, node, node.innerText.length); if (collapse) { if (to_start) sel.collapseToStart(); else sel.collapseToEnd(); } this.scrollToNode(node); return; } rng = inst.getDoc().createRange(); if (select_text_node) { // Find first textnode in tree nodes = tinyMCE.getNodeTree(node, [], 3); if (nodes.length > 0) rng.selectNodeContents(nodes[0]); else rng.selectNodeContents(node); } else rng.selectNode(node); if (collapse) { // Special treatment of textnode collapse if (!to_start && node.nodeType == 3) { rng.setStart(node, node.nodeValue.length); rng.setEnd(node, node.nodeValue.length); } else rng.collapse(to_start); } sel.removeAllRanges(); sel.addRange(rng); } this.scrollToNode(node); // Set selected element tinyMCE.selectedElement = null; if (node.nodeType == 1) tinyMCE.selectedElement = node; }, /** * Scrolls to the specified node location. * * @param {HTMLNode} node Node to scroll to. */ scrollToNode : function(node) { var inst = this.instance, w = inst.getWin(), vp = inst.getViewPort(), pos = tinyMCE.getAbsPosition(node), cvp, p, cwin; // Only scroll if out of visible area if (pos.absLeft < vp.left || pos.absLeft > vp.left + vp.width || pos.absTop < vp.top || pos.absTop > vp.top + (vp.height-25)) w.scrollTo(pos.absLeft, pos.absTop - vp.height + 25); // Scroll container window if (inst.settings.auto_resize) { cwin = inst.getContainerWin(); cvp = tinyMCE.getViewPort(cwin); p = this.getAbsPosition(node); if (p.absLeft < cvp.left || p.absLeft > cvp.left + cvp.width || p.absTop < cvp.top || p.absTop > cvp.top + cvp.height) cwin.scrollTo(p.absLeft, p.absTop - cvp.height + 25); } }, /** * Returns a global absolute position calculating both the container iframe location and the position within. * * @param {HTMLNode} n node. */ getAbsPosition : function(n) { var pos = tinyMCE.getAbsPosition(n), ipos = tinyMCE.getAbsPosition(this.instance.iframeElement); return { absLeft : ipos.absLeft + pos.absLeft, absTop : ipos.absTop + pos.absTop }; }, /** * Returns the browsers selection instance. * * @return Browser selection instance. * @type DOMSelection */ getSel : function() { var inst = this.instance; if (tinyMCE.isRealIE) return inst.getDoc().selection; return inst.contentWindow.getSelection(); }, /** * Returns the browsers selections first range instance. * * @return Browsers selections first range instance. * @type DOMRange */ getRng : function() { var s = this.getSel(); if (s == null) return null; if (tinyMCE.isRealIE) return s.createRange(); if (tinyMCE.isSafari && !s.getRangeAt) return '' + window.getSelection(); if (s.rangeCount > 0) return s.getRangeAt(0); return null; }, /** * Returns true/false if the current selection is collapsed or not. * * @return {bool} true/false if the current selection is collapsed or not. */ isCollapsed : function() { var r = this.getRng(); if (r.item) return false; return r.boundingWidth == 0 || this.getSel().isCollapsed; }, /** * Collapses the current selection to start or end. * * @param {bool} b true - to start, false - to end. */ collapse : function(b) { var r = this.getRng(), s = this.getSel(); if (r.select) { r.collapse(b); r.select(); } else { if (b) s.collapseToStart(); else s.collapseToEnd(); } }, /** * Returns the currently selected/focused element. * * @return Currently selected element. * @type HTMLElement */ getFocusElement : function() { var inst = this.instance, doc, rng, sel, elm; if (tinyMCE.isRealIE) { doc = inst.getDoc(); rng = doc.selection.createRange(); // if (rng.collapse) // rng.collapse(true); elm = rng.item ? rng.item(0) : rng.parentElement(); } else { if (!tinyMCE.isSafari && inst.isHidden()) return inst.getBody(); sel = this.getSel(); rng = this.getRng(); if (!sel || !rng) return null; elm = rng.commonAncestorContainer; //elm = (sel && sel.anchorNode) ? sel.anchorNode : null; // Handle selection a image or other control like element such as anchors if (!rng.collapsed) { // Is selection small if (rng.startContainer == rng.endContainer) { if (rng.startOffset - rng.endOffset < 2) { if (rng.startContainer.hasChildNodes()) elm = rng.startContainer.childNodes[rng.startOffset]; } } } // Get the element parent of the node elm = tinyMCE.getParentElement(elm); //if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img") // elm = tinyMCE.selectedElement; } return elm; } /**#@-*/ };