This is a migrated thread and some comments may be shown as answers.

RadEditor forgets position when switching between Design tab and HTML tab

2 Answers 72 Views
Editor
This is a migrated thread and some comments may be shown as answers.
Dennis
Top achievements
Rank 2
Dennis asked on 27 Jan 2017, 04:12 PM

When you switch between the Design tab and the HTML tab in the RadEditor, it does not remember your position in the text/content.

It seems like this is a bug, but I am wondering if there is any way to tell the RadEditor to remember the position when switching between those two tabs.

If you have a large amount of content with many HTML tags it becomes quite difficult to find the exact position you were in on the other tab.

 

2 Answers, 1 is accepted

Sort by
0
Rumen
Telerik team
answered on 29 Jan 2017, 06:40 PM

Hello Dennis,

Thank you for your useful feature request.

The requested functionality is logged with low priority in our backlog due to its high complexity for implementation and not enough demand by the community. In regards to this, I cannot tell whether it will be implemented in the future and to provide a firm estimate when.

 

Best regards,
Rumen
Telerik by Progress
Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
Emin Sadigov
Top achievements
Rank 2
Iron
Iron
Iron
commented on 28 Nov 2024, 03:45 PM

Hello, Rumen.

 

Is any news about this functional? May be some trick to make it using custom javascript?

1
Rumen
Telerik team
answered on 29 Nov 2024, 11:52 AM

Hi Emin,

I created a JavaScript-based solution demonstrating how to maintain the caret position when switching between the Design and HTML tabs and vice versa in the RadEditor. The current solution is experimental and not finalized, so it may not handle all scenarios perfectly. However, it provides a solid base that you can experiment with and enhance further.

<telerik:RadEditor ID="RadEditor1" runat="server" OnClientModeChange="onModeChange" ContentFilters="None" ContentAreaMode="Div" OnClientLoad="OnClientLoad">
    <Content>line one <br /> line two <br /> line three <br /> line four <br /> line five</Content>
</telerik:RadEditor>

<script>
    var currentMode = null; // Keep track of the current mode
    var caretCharacterIndex = null; // Store the caret position
    var rawHtmlContent = ""; // Store raw HTML content for accurate mapping

    // Function to get the caret position as a character index
    function getCaretCharacterIndex(editor, mode) {
        if (mode === Telerik.Web.UI.EditModes.Design) {
            var contentArea = editor.get_contentAreaElement();
            var selection = window.getSelection();
            if (selection.rangeCount > 0) {
                var range = selection.getRangeAt(0);
                var preCaretRange = range.cloneRange();
                preCaretRange.selectNodeContents(contentArea);
                preCaretRange.setEnd(range.startContainer, range.startOffset);
                rawHtmlContent = editor.get_html(false); // Save raw HTML for mapping
                return preCaretRange.toString().replace(/\s+/g, " ").length; // Return the character count
            }
        } else if (mode === Telerik.Web.UI.EditModes.Html) {
            var textarea = editor.get_textArea();
            if (textarea) {
                rawHtmlContent = textarea.value; // Save the raw HTML
                var plainText = rawHtmlContent.replace(/<[^>]*>/g, "").replace(/\s+/g, " "); // Strip HTML tags
                return mapHtmlToText(rawHtmlContent, plainText, textarea.selectionStart); // Map HTML position to plain text
            }
        }
        return null;
    }

    // Function to set the caret position based on character index
    function setCaretCharacterIndex(editor, mode, charIndex) {
        if (charIndex === null) return;

        if (mode === Telerik.Web.UI.EditModes.Design) {
            var contentArea = editor.get_contentAreaElement();
            var selection = window.getSelection();
            var range = document.createRange();

            var currentIndex = 0;

            (function traverseNodes(node) {
                if (node.nodeType === 3) { // Text node
                    var textLength = node.nodeValue.length;
                    if (currentIndex + textLength >= charIndex) {
                        // Set the range position
                        range.setStart(node, charIndex - currentIndex);
                        range.collapse(true);
                        return true; // Stop traversal
                    }
                    currentIndex += textLength;
                } else if (node.nodeType === 1 && node.childNodes) { // Element node
                    for (var i = 0; i < node.childNodes.length; i++) {
                        if (traverseNodes(node.childNodes[i])) return true;
                    }
                }
                return false;
            })(contentArea);

            selection.removeAllRanges();
            selection.addRange(range); // Set the range
            contentArea.focus(); // Ensure the content area is focused
        } else if (mode === Telerik.Web.UI.EditModes.Html) {
            var textarea = editor.get_textArea();
            if (textarea && rawHtmlContent) {
                var plainText = rawHtmlContent.replace(/<[^>]*>/g, "").replace(/\s+/g, " "); // Strip HTML tags
                var positionInHtml = mapTextToHtml(rawHtmlContent, plainText, charIndex); // Map plain text to HTML
                textarea.selectionStart = positionInHtml;
                textarea.selectionEnd = positionInHtml;
                textarea.focus();
            }
        }
    }

    // Map plain text position to HTML position
    function mapTextToHtml(html, plainText, charIndex) {
        var textIndex = 0;
        var htmlIndex = 0;

        while (textIndex < charIndex && htmlIndex < html.length) {
            if (html[htmlIndex] === plainText[textIndex]) {
                textIndex++;
            }
            htmlIndex++;
        }

        return htmlIndex; // Return the corresponding position in HTML
    }

    // Map HTML position to plain text position
    function mapHtmlToText(html, plainText, htmlIndex) {
        var textIndex = 0;
        var htmlPosition = 0;

        while (htmlPosition < htmlIndex && htmlPosition < html.length) {
            if (html[htmlPosition] === plainText[textIndex]) {
                textIndex++;
            }
            htmlPosition++;
        }

        return textIndex; // Return the corresponding position in plain text
    }

    // Handle mode change
    function onModeChange(editor, args) {
        var newMode = editor.get_mode();

        // Save caret position in the previous mode
        if (currentMode) {
            caretCharacterIndex = getCaretCharacterIndex(editor, currentMode);
        }

        // Set caret position in the new mode
        setTimeout(function () {
            setCaretCharacterIndex(editor, newMode, caretCharacterIndex);
        }, 10); // Small delay to ensure rendering is complete

        // Update the current mode
        currentMode = newMode;
    }

    // Initialize the current mode when the editor loads
    function OnClientLoad(sender, args) {
        var editor = sender;
        currentMode = editor.get_mode(); // Set the initial mode
    }
</script>

Some tech notes: In Design mode, the content area is an editable <div> where the HTML is rendered into a DOM structure. In HTML mode, the content is presented inside a <textarea> within an <iframe>, where it appears as plain text, including raw HTML tags. This structural difference makes mapping caret positions between the two modes inherently challenging.

The caret position in Design mode must be calculated relative to text nodes in the DOM, while in HTML mode it is based on plain text positions within the <textarea>. This example uses mapping functions to handle this complexity.

The solution requires the content area to be set to Div mode, and content filters must be disabled. It may not handle all edge cases, such as nested tags or empty elements like <div></div>. Additionally, it doesn’t account for multi-word selections, whole-word selections, or selections that span across multiple lines. These limitations highlight the challenges of implementing a fully reliable solution.

This code is a starting point for experimentation. If you create an improved version that handles more complex scenarios, we’d be happy to reward you with Telerik points for your contribution. Let us know if you have any feedback or enhancements to share!

    Best Regards,
    Rumen
    Progress Telerik

    Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Telerik family, check out our getting started resources
    Tags
    Editor
    Asked by
    Dennis
    Top achievements
    Rank 2
    Answers by
    Rumen
    Telerik team
    Share this question
    or