Сохранение изменений объектов диапазона после выбора в HTML
Есть ли способ сохранить изменения, такие как изменение фона текста HTML, охватывающего несколько тегов, чтобы при повторном загрузке внесенные изменения отражались на странице HTML.
EDIT: подробное объяснение.
Когда страница HTML загружается, текст выбирается и выделяется с использованием объекта диапазона и executeCommand:
document.execCommand("BackColor", false, 'yellow');
Изменения (выделение текста желтым цветом) остаются до перезагрузки страницы. Но когда страница перезагружается, этих изменений нет. Я хочу сохранить как-то эти изменения, например, в локальном DB sqlite, чтобы при перезагрузке/обновлении страницы появлялись изменения в HTML-странице.
Любая идея, как это сделать. Нужно ли мне сохранять смещение начала и конечное смещение диапазона, которое можно использовать для создания диапазона в следующий раз при загрузке страницы. Пожалуйста, дайте свои идеи.
Ответы
Ответ 1
Для каждого выбора вы можете сериализовать выбранный диапазон на смещения символов и снова десериализовать его при перезагрузке, используя что-то вроде этого:
Демо: http://jsfiddle.net/WeWy7/3/
код:
var saveSelection, restoreSelection;
if (window.getSelection && document.createRange) {
saveSelection = function(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
};
restoreSelection = function(containerEl, savedSel) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
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);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection) {
saveSelection = function(containerEl) {
var selectedTextRange = document.selection.createRange();
var preSelectionTextRange = document.body.createTextRange();
preSelectionTextRange.moveToElementText(containerEl);
preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
var start = preSelectionTextRange.text.length;
return {
start: start,
end: start + selectedTextRange.text.length
}
};
restoreSelection = function(containerEl, savedSel) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", savedSel.end);
textRange.moveStart("character", savedSel.start);
textRange.select();
};
}
Ответ 2
Не зная больше о контексте, трудно дать точный ответ, но да, это было бы возможно, но в большинстве случаев это будет довольно сложно. В зависимости от usecase есть несколько способов.
Cookies или локальное хранилище
Вы можете использовать какое-то хранилище на стороне клиента (файлы cookie, локальное хранилище или подобное) и сохранять информацию о том, какие элементы были изменены и как. Всякий раз, когда страница перезагружается, вы читаете это хранилище и применяете изменения. Как реализовать это будет зависеть от того, как эти изменения будут сделаны, и будет достаточно обширным, чтобы охватить один SO-ответ, которого я боюсь.
Хранилище на стороне сервера
Если вы знаете, кто каждый пользователь (у вас есть какая-то форма проверки подлинности), всякий раз, когда они меняют внешний вид чего-то (однако это сделано), вы делаете ajax-запрос на сервер и сохраняете эти изменения в базе данных. При каждой последующей загрузке страницы вам нужно будет проверить, какое использование делает запрос, выполнить поиск в своей базе данных, чтобы увидеть, внесли ли они какие-либо изменения, и в этом случае применить их соответственно.
Общим для решений для хранения данных на стороне клиента и на стороне сервера является то, что они будут достаточно обширными для реализации, я считаю.
Плагин для браузера
Еще один способ - использовать плагины, такие как Greasemonkey для Firefox, которые позволяют пользователю настраивать способ отображения веб-страницы. Эти настройки будут постоянными при загрузке страниц.
Ответ 3
Использование смещений символов не работает, если курсор находится в начале нового абзаца. Ниже приведен подход DOM node и подсчитывает все узлы в направлении смещения. Он также обрабатывает начало и конец в отдельности, чтобы убедиться, что выбор запоминает его точное положение. Вот обновленная версия, которую я использую в основном проекте (см. Функции в конце):
/*
Gets the offset of a node within another node. Text nodes are
counted a n where n is the length. Entering (or passing) an
element is one offset. Exiting is 0.
*/
var getNodeOffset = function(start, dest) {
var offset = 0;
var node = start;
var stack = [];
while (true) {
if (node === dest) {
return offset;
}
// Go into children
if (node.firstChild) {
// Going into first one doesn't count
if (node !== start)
offset += 1;
stack.push(node);
node = node.firstChild;
}
// If can go to next sibling
else if (stack.length > 0 && node.nextSibling) {
// If text, count length (plus 1)
if (node.nodeType === 3)
offset += node.nodeValue.length + 1;
else
offset += 1;
node = node.nextSibling;
}
else {
// If text, count length
if (node.nodeType === 3)
offset += node.nodeValue.length + 1;
else
offset += 1;
// No children or siblings, move up stack
while (true) {
if (stack.length <= 1)
return offset;
var next = stack.pop();
// Go to sibling
if (next.nextSibling) {
node = next.nextSibling;
break;
}
}
}
}
};
// Calculate the total offsets of a node
var calculateNodeOffset = function(node) {
var offset = 0;
// If text, count length
if (node.nodeType === 3)
offset += node.nodeValue.length + 1;
else
offset += 1;
if (node.childNodes) {
for (var i=0;i<node.childNodes.length;i++) {
offset += calculateNodeOffset(node.childNodes[i]);
}
}
return offset;
};
// Determine total offset length from returned offset from ranges
var totalOffsets = function(parentNode, offset) {
if (parentNode.nodeType == 3)
return offset;
if (parentNode.nodeType == 1) {
var total = 0;
// Get child nodes
for (var i=0;i<offset;i++) {
total += calculateNodeOffset(parentNode.childNodes[i]);
}
return total;
}
return 0;
};
var getNodeAndOffsetAt = function(start, offset) {
var node = start;
var stack = [];
while (true) {
// If arrived
if (offset <= 0)
return { node: node, offset: 0 };
// If will be within current text node
if (node.nodeType == 3 && (offset <= node.nodeValue.length))
return { node: node, offset: Math.min(offset, node.nodeValue.length) };
// Go into children (first one doesn't count)
if (node.firstChild) {
if (node !== start)
offset -= 1;
stack.push(node);
node = node.firstChild;
}
// If can go to next sibling
else if (stack.length > 0 && node.nextSibling) {
// If text, count length
if (node.nodeType === 3)
offset -= node.nodeValue.length + 1;
else
offset -= 1;
node = node.nextSibling;
}
else {
// No children or siblings, move up stack
while (true) {
if (stack.length <= 1) {
// No more options, use current node
if (node.nodeType == 3)
return { node: node, offset: Math.min(offset, node.nodeValue.length) };
else
return { node: node, offset: 0 };
}
var next = stack.pop();
// Go to sibling
if (next.nextSibling) {
// If text, count length
if (node.nodeType === 3)
offset -= node.nodeValue.length + 1;
else
offset -= 1;
node = next.nextSibling;
break;
}
}
}
}
};
exports.save = function(containerEl) {
// Get range
var selection = window.getSelection();
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
return {
start: getNodeOffset(containerEl, range.startContainer) + totalOffsets(range.startContainer, range.startOffset),
end: getNodeOffset(containerEl, range.endContainer) + totalOffsets(range.endContainer, range.endOffset)
};
}
else
return null;
};
exports.restore = function(containerEl, savedSel) {
if (!savedSel)
return;
var range = document.createRange();
var startNodeOffset, endNodeOffset;
startNodeOffset = getNodeAndOffsetAt(containerEl, savedSel.start);
endNodeOffset = getNodeAndOffsetAt(containerEl, savedSel.end);
range.setStart(startNodeOffset.node, startNodeOffset.offset);
range.setEnd(endNodeOffset.node, endNodeOffset.offset);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
};
Это работает только в современных браузерах (по крайней мере, IE 9+).