Как получить узлы, лежащие внутри диапазона с помощью javascript?
Я пытаюсь получить все узлы DOM, которые находятся внутри объекта диапазона, что лучший способ сделать это?
var selection = window.getSelection(); //what the user has selected
var range = selection.getRangeAt(0); //the first range of the selection
var startNode = range.startContainer;
var endNode = range.endContainer;
var allNodes = /*insert magic*/;
Я думал о пути в течение последних нескольких часов и придумал это:
var getNextNode = function(node, skipChildren){
//if there are child nodes and we didn't come from a child node
if (node.firstChild && !skipChildren) {
return node.firstChild;
}
if (!node.parentNode){
return null;
}
return node.nextSibling
|| getNextNode(node.parentNode, true);
};
var getNodesInRange = function(range){
var startNode = range.startContainer.childNodes[range.startOffset]
|| range.startContainer;//it a text node
var endNode = range.endContainer.childNodes[range.endOffset]
|| range.endContainer;
if (startNode == endNode && startNode.childNodes.length === 0) {
return [startNode];
};
var nodes = [];
do {
nodes.push(startNode);
}
while ((startNode = getNextNode(startNode))
&& (startNode != endNode));
return nodes;
};
Однако, когда конец node является родителем начала node, он возвращает все на странице. Я уверен, что я пропускаю что-то очевидное? Или, может быть, это происходит совершенно неправильно.
MDC/DOM/range
Ответы
Ответ 1
GetNextNode будет пропускать ваш желаемый endNode рекурсивно, если его родительский node.
Выполните проверку условного разрыва внутри getNextNode:
var getNextNode = function(node, skipChildren, endNode){
//if there are child nodes and we didn't come from a child node
if (endNode == node) {
return null;
}
if (node.firstChild && !skipChildren) {
return node.firstChild;
}
if (!node.parentNode){
return null;
}
return node.nextSibling
|| getNextNode(node.parentNode, true, endNode);
};
и в выражении while:
while (startNode = getNextNode(startNode, false , endNode));
Ответ 2
В этой реализации я решил решить эту проблему:
function getNextNode(node)
{
if (node.firstChild)
return node.firstChild;
while (node)
{
if (node.nextSibling)
return node.nextSibling;
node = node.parentNode;
}
}
function getNodesInRange(range)
{
var start = range.startContainer;
var end = range.endContainer;
var commonAncestor = range.commonAncestorContainer;
var nodes = [];
var node;
// walk parent nodes from start to common ancestor
for (node = start.parentNode; node; node = node.parentNode)
{
nodes.push(node);
if (node == commonAncestor)
break;
}
nodes.reverse();
// walk children and siblings from start until end is found
for (node = start; node; node = getNextNode(node))
{
nodes.push(node);
if (node == end)
break;
}
return nodes;
}
Ответ 3
библиотека Rangy имеет функцию Range.getNodes([Array nodeTypes[, Function filter]])
,
Ответ 4
ниже код решает вашу проблему
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>payam jabbari</title>
<script src="http://code.jquery.com/jquery-2.0.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function(){
var startNode = $('p.first').contents().get(0);
var endNode = $('span.second').contents().get(0);
var range = document.createRange();
range.setStart(startNode, 0);
range.setEnd(endNode, 5);
var selection = document.getSelection();
selection.addRange(range);
// below code return all nodes in selection range. this code work in all browser
var nodes = range.cloneContents().querySelectorAll("*");
for(var i=0;i<nodes.length;i++)
{
alert(nodes[i].innerHTML);
}
});
</script>
</head>
<body>
<div>
<p class="first">Even a week ago, the idea of a Russian military intervention in Ukraine seemed far-fetched if not totally alarmist. But the arrival of Russian troops in Crimea over the weekend has shown that he is not averse to reckless adventures, even ones that offer little gain. In the coming days and weeks</p>
<ol>
<li>China says military will respond to provocations.</li>
<li >This Man Has Served 20 <span class="second"> Years—and May Die—in </span> Prison for Marijuana.</li>
<li>At White House, Israel Netanyahu pushes back against Obama diplomacy.</li>
</ol>
</div>
</body>
</html>
Ответ 5
Я сделал 2 дополнительных исправления на основе ответа MikeB, чтобы повысить точность выбранных узлов.
Я особенно тестирую это при выборе всех операций, кроме выбора диапазона, сделанного перетаскиванием курсора вдоль текста, охватывающего несколько элементов.
В Firefox, нажав выбрать все (CMD + A), возвращается диапазон, в котором startContainer и endContainer являются contenteditable div, разница в startOffset и endOffset, где он соответствует индексу первого и последнего дочерних node.
В Chrome, нажав кнопку "Выбрать все" (CMD + A), возвращается диапазон, в котором startContainer является первым дочерним node контента, доступным для контента, и endContainer является последним дочерним элементом node содержащего контент div.
Модификации, которые я добавил, работают вокруг несоответствий между ними. Вы можете увидеть комментарии в коде для дополнительного объяснения.
function getNextNode(node) {
if (node.firstChild)
return node.firstChild;
while (node) {
if (node.nextSibling) return node.nextSibling;
node = node.parentNode;
}
}
function getNodesInRange(range) {
// MOD #1
// When the startContainer/endContainer is an element, its
// startOffset/endOffset basically points to the nth child node
// where the range starts/ends.
var start = range.startContainer.childNodes[range.startOffset] || range.startContainer;
var end = range.endContainer.childNodes[range.endOffset] || range.endContainer;
var commonAncestor = range.commonAncestorContainer;
var nodes = [];
var node;
// walk parent nodes from start to common ancestor
for (node = start.parentNode; node; node = node.parentNode)
{
nodes.push(node);
if (node == commonAncestor)
break;
}
nodes.reverse();
// walk children and siblings from start until end is found
for (node = start; node; node = getNextNode(node))
{
// MOD #2
// getNextNode might go outside of the range
// For a quick fix, I'm using jQuery closest to determine
// when it goes out of range and exit the loop.
if (!$(node.parentNode).closest(commonAncestor)[0]) break;
nodes.push(node);
if (node == end)
break;
}
return nodes;
};
Ответ 6
Андон, отличная работа. Я изменил первоначальный плюс включил модификации Stefan в следующем.
Кроме того, я удалил зависимость от Range, которая преобразует функцию в общий алгоритм для перехода между двумя узлами. Кроме того, я включил все в одну функцию.
Мысли о других решениях:
- Не интересно полагаться на jquery
- Использование cloneNode поднимет результаты на фрагмент, который предотвращает многие операции, которые можно было бы провести во время фильтрации.
- Использование querySelectAll для клонированного фрагмента является неустойчивым, поскольку начальный или конечный узлы могут находиться внутри оболочки node, поэтому анализатор может не иметь закрывающий тег?
Пример:
<div>
<p>A</p>
<div>
<p>B</p>
<div>
<p>C</p>
</div>
</div>
</div>
Предположим, что начало node является абзацем "A", а конец node является абзацем "C"
, Полученный клонированный фрагмент будет:
<p>A</p>
<div>
<p>B</p>
<div>
<p>C</p>
и нам не хватает закрывающих тегов? что привело к созданию фанковой структуры DOM?
В любом случае, здесь функция, которая включает параметр фильтра, который должен возвращать TRUE или FALSE для включения/исключения из результатов.
var getNodesBetween = function(startNode, endNode, includeStartAndEnd, filter){
if (startNode == endNode && startNode.childNodes.length === 0) {
return [startNode];
};
var getNextNode = function(node, finalNode, skipChildren){
//if there are child nodes and we didn't come from a child node
if (finalNode == node) {
return null;
}
if (node.firstChild && !skipChildren) {
return node.firstChild;
}
if (!node.parentNode){
return null;
}
return node.nextSibling || getNextNode(node.parentNode, endNode, true);
};
var nodes = [];
if(includeStartAndEnd){
nodes.push(startNode);
}
while ((startNode = getNextNode(startNode, endNode)) && (startNode != endNode)){
if(filter){
if(filter(startNode)){
nodes.push(startNode);
}
} else {
nodes.push(startNode);
}
}
if(includeStartAndEnd){
nodes.push(endNode);
}
return nodes;
};
Ответ 7
боб. функция возвращает только startNode и endNode. узлы между ними не попадают в массив.
Кажется, while цикл возвращает null на getNextNode(), поэтому блок никогда не будет выполнен.
Ответ 8
здесь функция возвращает массив поддиапазонов
function getSafeRanges(range) {
var doc = document;
var commonAncestorContainer = range.commonAncestorContainer;
var startContainer = range.startContainer;
var endContainer = range.endContainer;
var startArray = new Array(0),
startRange = new Array(0);
var endArray = new Array(0),
endRange = new Array(0);
// @@@@@ If start container and end container is same
if (startContainer == endContainer) {
return [range];
} else {
for (var i = startContainer; i != commonAncestorContainer; i = i.parentNode) {
startArray.push(i);
}
for (var i = endContainer; i != commonAncestorContainer; i = i.parentNode) {
endArray.push(i);
}
}
if (0 < startArray.length) {
for (var i = 0; i < startArray.length; i++) {
if (i) {
var node = startArray[i - 1];
while ((node = node.nextSibling) != null) {
startRange = startRange.concat(getRangeOfChildNodes(node));
}
} else {
var xs = doc.createRange();
var s = startArray[i];
var offset = range.startOffset;
var ea = (startArray[i].nodeType == Node.TEXT_NODE) ? startArray[i] : startArray[i].lastChild;
xs.setStart(s, offset);
xs.setEndAfter(ea);
startRange.push(xs);
}
}
}
if (0 < endArray.length) {
for (var i = 0; i < endArray.length; i++) {
if (i) {
var node = endArray[i - 1];
while ((node = node.previousSibling) != null) {
endRange = endRange.concat(getRangeOfChildNodes(node));
}
} else {
var xe = doc.createRange();
var sb = (endArray[i].nodeType == Node.TEXT_NODE) ? endArray[i] : endArray[i].firstChild;
var end = endArray[i];
var offset = range.endOffset;
xe.setStartBefore(sb);
xe.setEnd(end, offset);
endRange.unshift(xe);
}
}
}
var topStartNode = startArray[startArray.length - 1];
var topEndNode = endArray[endArray.length - 1];
var middleRange = getRangeOfMiddleElements(topStartNode, topEndNode);
startRange = startRange.concat(middleRange);
response = startRange.concat(endRange);
return response;
}