Замените innerHTML в contenteditable div
Мне нужно реализовать выделение для чисел (в будущем im добавить более сложные правила) в contenteditable div. Проблема заключается в том, когда im вставляют новый контент с заменой javascript, изменениями DOM и contenteditable div теряют фокус. Мне нужно сосредоточиться на div с кареткой на текущей позиции, поэтому пользователи могут просто вводить без каких-либо проблем, а моя функция - простое выделение номеров. Я пытаюсь понять, что Rangy библиотека - лучшее решение. У меня есть следующий код:
function formatText() {
var savedSel = rangy.saveSelection();
el = document.getElementById('pad');
el.innerHTML = el.innerHTML.replace(/(<([^>]+)>)/ig,"");
el.innerHTML = el.innerHTML.replace(/([0-9])/ig,"<font color='red'>$1</font>");
rangy.restoreSelection(savedSel);
}
<div contenteditable="true" id="pad" onkeyup="formatText();"></div>
Проблема заключается в том, что после завершения функции завершения работы возвращается в div, но карет всегда указывает на начало div, и я могу печатать где угодно, execept div begin. Кроме того, типы console.log следуют Rangy warning: Module SaveRestore: Marker element has been removed. Cannot restore selection.
Пожалуйста, помогите мне реализовать этот функционал. Im открыт для другого решения, а не только для библиотеки rangy. Спасибо!
http://jsfiddle.net/2rTA5/ Это jsfiddle, но он не работает должным образом (ничего не происходит, когда я вводил числа в свой div), не знаю, может быть, это мне (первый пост-код через jsfiddle) или ресурс не поддерживает контент.
UPD * Im прочитал аналогичные проблемы в stackoverflow, но решения не подходят для моего случая: (
Ответы
Ответ 1
Проблема в том, что модуль выбора сохранения/восстановления Rangy работает, вставляя невидимые элементы маркера в DOM, где находятся границы выбора, а затем ваш код удаляет все теги HTML, включая элементы маркера Rangy (как указывает сообщение об ошибке). У вас есть два варианта:
- Перейдите к решению обхода DOM для раскраски чисел, а не
innerHTML
. Это будет более надежным, но более привлекательным.
- Реализация альтернативного выбора и восстановления выбора на основе индекса. Это будет в целом хрупким, но в этом случае будет делать то, что вы хотите.
UPDATE
Я ударил сохраненный/восстановленный выбор на основе символа для Rangy (вариант 2 выше). Это немного грубо, но это делает работу по этому делу. Он работает путем перемещения текстовых узлов. Я могу добавить это в Ранги в той или иной форме. (ОБНОВЛЕНИЕ 5 июня 2012 года: Я теперь применил это более надежным способом для Rangy.)
jsFiddle: http://jsfiddle.net/2rTA5/2/
код:
function saveSelection(containerEl) {
var charIndex = 0, start = 0, end = 0, foundStart = false, stop = {};
var sel = rangy.getSelection(), range;
function traverseTextNodes(node, range) {
if (node.nodeType == 3) {
if (!foundStart && node == range.startContainer) {
start = charIndex + range.startOffset;
foundStart = true;
}
if (foundStart && node == range.endContainer) {
end = charIndex + range.endOffset;
throw stop;
}
charIndex += node.length;
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
traverseTextNodes(node.childNodes[i], range);
}
}
}
if (sel.rangeCount) {
try {
traverseTextNodes(containerEl, sel.getRangeAt(0));
} catch (ex) {
if (ex != stop) {
throw ex;
}
}
}
return {
start: start,
end: end
};
}
function restoreSelection(containerEl, savedSel) {
var charIndex = 0, range = rangy.createRange(), foundStart = false, stop = {};
range.collapseToPoint(containerEl, 0);
function traverseTextNodes(node) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
throw stop;
}
charIndex = nextCharIndex;
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
traverseTextNodes(node.childNodes[i]);
}
}
}
try {
traverseTextNodes(containerEl);
} catch (ex) {
if (ex == stop) {
rangy.getSelection().setSingleRange(range);
} else {
throw ex;
}
}
}
function formatText() {
var el = document.getElementById('pad');
var savedSel = saveSelection(el);
el.innerHTML = el.innerHTML.replace(/(<([^>]+)>)/ig,"");
el.innerHTML = el.innerHTML.replace(/([0-9])/ig,"<font color='red'>$1</font>");
// Restore the original selection
restoreSelection(el, savedSel);
}
Ответ 2
Я хотел бы поблагодарить Тима за ту функцию, которую он поделил с нами, это было очень важно для проекта, над которым я работаю. Я включил его функцию в небольшой плагин jQuery, доступ к которому можно получить здесь: https://jsfiddle.net/sh5tboL8/
$.fn.get_selection_start = function(){
var result = this.get(0).selectionStart;
if (typeof(result) == 'undefined') result = this.get_selection_range().selection_start;
return result;
}
$.fn.get_selection_end = function(){
var result = this.get(0).selectionEnd;
if (typeof(result) == 'undefined') result = this.get_selection_range().selection_end;
return result;
}
$.fn_get_selected_text = function(){
var value = this.get(0).value;
if (typeof(value) == 'undefined'){
var result = this.get_selection_range().selected_text;
}else{
var result = value.substring(this.selectionStart, this.selectionEnd);
}
return result;
}
$.fn.get_selection_range = function(){
var range = window.getSelection().getRangeAt(0);
var cloned_range = range.cloneRange();
cloned_range.selectNodeContents(this.get(0));
cloned_range.setEnd(range.startContainer, range.startOffset);
var selection_start = cloned_range.toString().length;
var selected_text = range.toString();
var selection_end = selection_start + selected_text.length;
var result = {
selection_start: selection_start,
selection_end: selection_end,
selected_text: selected_text
}
return result;
}
$.fn.set_selection = function(selection_start, selection_end){
var target_element = this.get(0);
selection_start = selection_start || 0;
if (typeof(target_element.selectionStart) == 'undefined'){
if (typeof(selection_end) == 'undefined') selection_end = target_element.innerHTML.length;
var character_index = 0;
var range = document.createRange();
range.setStart(target_element, 0);
range.collapse(true);
var node_stack = [target_element];
var node = null;
var start_found = false;
var stop = false;
while (!stop && (node = node_stack.pop())) {
if (node.nodeType == 3){
var next_character_index = character_index + node.length;
if (!start_found && selection_start >= character_index && selection_start <= next_character_index){
range.setStart(node, selection_start - character_index);
start_found = true;
}
if (start_found && selection_end >= character_index && selection_end <= next_character_index){
range.setEnd(node, selection_end - character_index);
stop = true;
}
character_index = next_character_index;
}else{
var child_counter = node.childNodes.length;
while (child_counter --){
node_stack.push(node.childNodes[child_counter]);
}
}
}
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}else{
if (typeof(selection_end) == 'undefined') selection_end = target_element.value.length;
target_element.focus();
target_element.selectionStart = selection_start;
target_element.selectionEnd = selection_end;
}
}
плагин делает только то, что мне нужно, чтобы сделать, получить выделенный текст и установить настраиваемый выбор текста. Он также работает с текстовыми полями и contentEditable div.