2009-03-20 6 views
14

J'essaie d'obtenir tous les nœuds DOM qui se trouvent dans un objet Range, quelle est la meilleure façon de le faire?Comment obtenir des nœuds se trouvant dans une plage avec 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*/; 

J'ai pensé à un moyen pour les dernières heures et est venu avec ceci:

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

Toutefois, lorsque le noeud final est le parent du nœud de départ, il retourne tout sur la page. Je suis sûr que je néglige quelque chose d'évident? Ou peut-être y va-t-il totalement dans le mauvais sens.

MDC/DOM/range

+2

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

Répondre

11

Le getNextNode sautera votre récursive si désiré nœud terminal est un nœud parent.

Effectuer le contrôle de rupture conditionnelle à l'intérieur de la getNextNode place:

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

et while:

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

Merci :) Vous voudrez peut-être modifier le second bit, mais il ne passe que deux paramètres et il manque le crochet de fin. – Annan

+3

ne fonctionne pas pour les plages couvrant plusieurs paragraphes :( – Thariama

9

est ici une implémentation je suis venu avec pour résoudre ce:

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

Alors que payam jabbari est ci-dessous l'utilisation de querySelectorAll est propre, le problème fondamental avec son approche pour moi est qu'il clone les nœuds, c'est-à-dire les supprime de la dom, alors que le vôtre ne le fait pas et donc fournit une manipulation directe dom Merci beaucoup pour cela. – Pancho

1

ci-dessous le code résoudre votre problème

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

J'ai fait 2 corrections supplémentaires en fonction de la réponse de MikeB pour améliorer la précision des noeuds sélectionnés.

Je suis particulièrement tester ceci sur toutes les opérations, autres que la sélection de gamme faite en faisant glisser le curseur le long du texte couvrant plusieurs éléments.

Dans Firefox, frappant tout sélectionner (CMD + A) retourne une plage où il est startContainer & endContainer est le contenteditable div, la différence est dans le startOffset & endOffset où il est respectivement l'indice du premier et le dernier nœud enfant. Dans Chrome, le fait de cliquer sur tout sélectionner (CMD + A) renvoie une plage où il est startContainer est le premier nœud enfant de la div contented, et endContainer est le dernier nœud enfant de la div contenteditable.

Les modifications que j'ai ajoutées fonctionnent autour des différences entre les deux. Vous pouvez voir les commentaires dans le code pour plus d'explications.

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, excellent travail. J'ai modifié l'original et ajouté les modifications de Stefan dans ce qui suit.

En outre, j'ai enlevé la confiance sur Range, qui convertit la fonction en un algorithme générique pour marcher entre deux noeuds. De plus, j'ai tout enveloppé dans une seule fonction.

Réflexions sur d'autres solutions:

  • Pas intéressé en se fondant sur jquery
  • En utilisant cloneNode ascenseurs les résultats à un fragment, ce qui empêche de nombreuses opérations on pourrait vouloir effectuer pendant le filtrage.
  • L'utilisation de querySelectAll sur un fragment cloné est bancale car les noeuds de début ou de fin peuvent se trouver dans un noeud d'encapsulation. Par conséquent, l'analyseur peut ne pas avoir la balise de fermeture.

Exemple:

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

supposer noeud départ est le point "A", et le noeud d'extrémité est le paragraphe "C". Le fragment cloné résultant serait:

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

et il nous manque des balises de fermeture? résultant de la structure DOM funky?

Quoi qu'il en soit, voici la fonction, qui inclut une option de filtre, qui devrait renvoyer TRUE ou FALSE pour inclure/exclure des résultats.

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 fonction ne renvoie que startNode et endNode. les nœuds intermédiaires ne sont pas poussés vers le tableau.

semble que la boucle while renvoie null sur getNextNode(), par conséquent ce bloc n'est jamais exécuté.

0

est fonction de retour ici réseau de sous-gammes

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; 

}

Questions connexes