2009-03-20 6 views
14

Próbuję uzyskać wszystkie węzły DOM, które są w zasięgu obiektu, jaki jest najlepszy sposób to zrobić?Jak uzyskać węzły leżące wewnątrz zakresu z javascript?

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*/; 

Byłem myślał o sposób, w ciągu ostatnich kilku godzin i podszedł z tym:

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's 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; 
}; 

Jednak gdy węzeł końcowy jest rodzicem węzła początkowego zwraca na wszystko Strona. Jestem pewien, że przeoczyłem coś oczywistego? A może dzieje się to w zupełnie niewłaściwy sposób.

MDC/DOM/range

+2

'var c = getSelection() getRangeAt (0) .cloneContents (.); c.querySelectorAll ('*') ' – caub

Odpowiedz

11

getNextNode pominie żądany endNode rekurencyjnie jeśli jego węzeł nadrzędny.

wykonać warunkowego sprawdzanie przerwania wewnątrz getNextNode Zamiast:

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); 
}; 

a while:

while (startNode = getNextNode(startNode, false , endNode)); 
+0

Dzięki :) Może chcieć edytować drugi bit, ale tylko przechodząc w dwóch parametrach i brakuje nawiasu kończącego. – Annan

+3

nie działa dla zakresów obejmujących kilka akapitów :( – Thariama

9

Oto implementacja wymyśliłem aby rozwiązać ten problem:

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; 
} 
+0

co za świetny kawałek kodu. Podczas gdy payam jabbari poniżej użycie querySelectorAll jest zadowalający, podstawowym problemem z jego podejściem dla mnie jest to, że klonuje on węzły tj. Usuwa je z dom, podczas gdy twój nie ma i dzięki temu zapewnia bezpośrednią manipulację domem. Bardzo dziękuję za to. – Pancho

1

poniżej kod rozwiązać problem

<!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's Netanyahu pushes back against Obama diplomacy.</li> 
</ol> 
</div> 
</body> 
</html> 
1

zrobiłem 2 dodatkowe poprawki w oparciu o odpowiedzi MikeB do poprawy dokładności wybranych węzłów.

Szczególnie testuję to na wybranych wszystkich operacjach, innych niż wybór zakresu przez przeciągnięcie kursora wzdłuż tekstu obejmującego wiele elementów.

W Firefoksie, uderzając Select All (CMD + A) zwraca zakres gdzie jest startContainer & endContainer jest contenteditable div, różnica jest w startOffset & endOffset gdzie jest odpowiednio indeks pierwszego i ostatniego węzła potomnego.

W przeglądarce Chrome naciśnięcie opcji zaznacz wszystko (CMD + A) zwraca zakres, w którym to pole startContainer jest pierwszym węzłem podrzędnym elementu div z atrybutem content, a element endContainer jest ostatnim węzłem podrzędnym elementu div z atrybutem content.

Dodane przeze mnie modyfikacje działają w przypadku rozbieżności między tymi dwoma. Możesz zobaczyć komentarze w kodzie dla dodatkowego wyjaśnienia.

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's 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; 
}; 
0

Annon, świetna robota. Zmodyfikowałem oryginał plus uwzględniłem modyfikacje Stefana w poniższym tekście.

Ponadto usunąłem zależność od Range, która przekształca tę funkcję w ogólny algorytm przechodzenia między dwoma węzłami. Dodatkowo zawinąłem wszystko w jedną funkcję.

Myśli o innych rozwiązań:

  • nie interesuje powołując się na jQuery
  • Korzystanie cloneNode podnosi wyniki do fragmentu, który zapobiega wielu operacji może chcieć przeprowadzić podczas filtrowania.
  • Użycie querySelectAll na sklonowanym fragmencie jest niewygodne, ponieważ węzły początkowe lub końcowe mogą znajdować się w węźle zawijania, dlatego parser może nie mieć znacznika zamykającego?

przykład:

<div> 
    <p>A</p> 
    <div> 
     <p>B</p> 
     <div> 
      <p>C</p> 
     </div> 
    </div> 
</div> 

Załóżmy rozpocząć węzeł "A" ust, a węzeł końcowy jest "C" ust . Powstały klonowany fragment będzie:

<p>A</p> 
    <div> 
     <p>B</p> 
     <div> 
      <p>C</p> 

i brakuje nam tagów zamykających? co daje efektowną strukturę DOM?

W każdym razie oto funkcja, która zawiera opcję filtru, która powinna zwracać wartość PRAWDA lub FAŁSZ w celu uwzględnienia/wykluczenia z wyników.

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; 
}; 
0

bob. funkcja zwraca tylko kod startNode i endNode. węzły pośrednie nie są przekazywane do tablicy.

wydaje się, że pętla while zwraca null na getNextNode(), dlatego ten blok nigdy nie zostanie wykonany.

0

o to funkcja zamian układ podzakresów

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; 

}

Powiązane problemy