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
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
Hello, Rumen.
Is any news about this functional? May be some trick to make it using custom javascript?
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