2009-03-20 14 views
14

Estoy tratando de obtener todos los nodos DOM que están dentro de un objeto rango, ¿cuál es la mejor manera de hacerlo?¿Cómo obtener nodos dentro de un rango 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*/; 

He estado pensando en una manera para que las últimas horas y se acercó con esto:

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

Sin embargo, cuando el nodo final es el padre del nodo de inicio se vuelve todo en la página. Estoy seguro de que estoy pasando por alto algo obvio? O tal vez hacerlo de una manera totalmente incorrecta.

MDC/DOM/range

+2

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

Respuesta

11

El getNextNode omitirá su nodo final deseada de forma recursiva si es un nodo padre.

realizar la comprobación de ruptura condicional dentro de la getNextNode lugar:

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

y en el estado, mientras que:

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

Gracias :) Puede que quiera editar el segundo bit, solo está pasando en dos parámetros y falta el corchete final. – Annan

+3

no funciona para rangos que abarcan varios párrafos :( – Thariama

9

Aquí es una implementación que se me ocurrió para resolver esto:

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

qué gran pieza de código. Mientras que el uso de querySelectorAll de debajo de payam jabbari es claro, el problema fundamental con su enfoque para mí es que clona los nodos, es decir, los elimina del dom, mientras que el tuyo no lo hace y, por lo tanto, proporciona una manipulación directa del dom. Muchas gracias por esto. – Pancho

1

continuación Código resolver su 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

Hice 2 correcciones adicionales en función de la respuesta de MikeB para mejorar la precisión de los nodos seleccionados.

Estoy probando esto especialmente al seleccionar todas las operaciones, aparte de la selección de rango realizada al arrastrar el cursor a lo largo del texto que abarca varios elementos.

En Firefox, golpeando Seleccionar todo (CMD + A) devuelve un rango donde es startContainer & endContainer es el div contenteditable, la diferencia está en el StartOffset & endOffset donde es, respectivamente, el índice del primer y el último nodo hijo.

En Chrome, al seleccionar seleccionar todo (CMD + A) se devuelve un rango donde está startContainer es el primer nodo hijo de la div contenteditable, y endContainer es el último nodo secundario de la div contenteditable.

Las modificaciones que he agregado resuelven las discrepancias entre las dos. Puede ver los comentarios en el código para una explicación adicional.

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, great work. Modifiqué el plus original que incluía las modificaciones de Stefan en lo siguiente.

Además, eliminé la dependencia de Rango, que convierte la función en un algoritmo genérico para caminar entre dos nodos. Además, he incluido todo en una sola función.

Reflexiones sobre otras soluciones:

  • no está interesado en depender de jQuery
  • Usando cloneNode levanta los resultados a un fragmento, lo que impide que muchas operaciones que se pueden Quiere lanzar durante el filtrado.
  • El uso de querySelectAll en un fragmento clonado es dudoso porque los nodos de inicio o fin pueden estar dentro de un nodo de envoltura, por lo tanto, es posible que el analizador no tenga la etiqueta de cierre.

Ejemplo:

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

Suponga empezar nodo es el párrafo "A", y el nodo final es el párrafo "C" . El fragmento clonado resultante sería:

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

y nos faltan etiquetas de cierre? dando como resultado una estructura funky DOM?

De todos modos, aquí está la función, que incluye una opción de filtro, que debe devolver VERDADERO o FALSO para incluir/excluir de los resultados.

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 función solo devuelve el startNode y endNode. los nodos intermedios no se presionan en la matriz.

parece que while loop devuelve null en getNextNode() por lo que ese bloque nunca se ejecuta.

0

aquí es función de retorno que serie de sub-intervalos

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; 

}

Cuestiones relacionadas