2009-03-20 3 views
14

Sto cercando di ottenere tutti i nodi DOM che si trovano all'interno di un oggetto intervallo, qual è il modo migliore per farlo?Come ottenere i nodi che si trovano all'interno di un intervallo con 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*/; 

Ho pensato a un modo per le ultime ore e arrivato fino a questo:

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

Tuttavia quando il nodo finale è il genitore del nodo di partenza si ritorna tutto su la pagina. Sono sicuro che sto trascurando qualcosa di ovvio? O forse se ne sta andando nel modo sbagliato.

MDC/DOM/range

+2

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

risposta

11

Il getNextNode salterà la tua endNode desiderato ricorsivamente se un nodo padre.

eseguire il controllo di interruzione condizionale all'interno della getNextNode invece:

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

e in while:

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

Grazie :) Potrebbe voler modificare il secondo bit, ma sta passando solo in due parametri e manca la parentesi finale. – Annan

+3

non funziona per intervalli che si estendono su diversi paragrafi :( – Thariama

9

Ecco un'implementazione mi è venuta per risolvere questo:

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

che gran parte del codice. Mentre payam jabbari di seguito l'uso di querySelectorAll è pulito, il problema fondamentale con il suo approccio per me è che clona i nodi cioè li rimuove dal dom, mentre il tuo non lo fa e quindi fornisce una dom manipulation diretta. Grazie mille per questo. – Pancho

1

sottostante Codice risolvere il problema

<!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

ho fatto 2 ulteriori correzioni in base alla risposta del mikeb per migliorare la precisione dei nodi selezionati.

In particolare, sto verificando questo in selezionare tutte le operazioni, oltre alla selezione dell'intervallo effettuata trascinando il cursore lungo il testo che si estende su più elementi.

In Firefox, colpendo Seleziona tutto (CMD + A) restituisce un intervallo in cui è startContainer & endContainer è il div contenteditable, la differenza è nel & endOffset startOffset dove è, rispettivamente, l'indice del primo e l'ultimo nodo figlio.

In Chrome, premendo select all (CMD + A) restituisce un intervallo in cui startContainer è il primo nodo figlio del div contenteditable e endContainer è l'ultimo nodo figlio del div contenteditable.

Le modifiche che ho aggiunto aggirano le discrepanze tra i due. Puoi vedere i commenti nel codice per ulteriori spiegazioni.

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, ottimo lavoro. Ho modificato il plus originale incluso le modifiche di Stefan nel seguito.

Inoltre, ho rimosso la dipendenza da Range, che converte la funzione in un algoritmo generico per camminare tra due nodi. Inoltre, ho avvolto tutto in un'unica funzione.

pensieri su altre soluzioni:

  • Non sono interessato al basandosi su jquery
  • Uso cloneNode solleva i risultati in un frammento, che impedisce molte operazioni si potrebbe desiderare di effettuare durante la filtrazione.
  • L'uso di querySelectAll su un frammento clonato è imprevedibile perché i nodi di inizio o fine potrebbero trovarsi all'interno di un nodo di wrapping, quindi il parser potrebbe non avere il tag di chiusura?

Esempio:

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

assumere avviare nodo è il punto "A", ed il nodo finale è la "C" paragrafo . Il frammento clonato risultante sarebbe:

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

e ci manca tag di chiusura? con conseguente struttura DOM funky?

In ogni caso, ecco la funzione, che include un'opzione di filtro, che dovrebbe restituire VERO o FALSO per includere/escludere dai risultati.

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. la funzione restituisce solo startNode e endNode. i nodi in mezzo non vengono spinti all'array.

sembra che il ciclo while restituisca null su getNextNode(), quindi quel blocco non viene mai eseguito.

0

ecco funzione di ritorno si array di sottointervalli

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; 

}