Absoluten Index einer Node finden
dgog
- javascript
Hallo,
ich versuche gerade über JavaScript den absoluten Index für ein Node-Objekt innerhalb des DOM-Baumes herauszufinden, schweitere jedoch schon am Durchlaufen des DOM-Baumes.
Der eingesetzte Quelltext sieht so aus:
function getDomChilds(parent) {
for(i = 0; i < parent.childNodes.length; i++) {
getDomChilds(parent.childNodes[i]);
}
}
Ich benötige den absoluten Index, um die Selektion nach dem Neu laden der Seite wiederherstellen zu können, weil sich getSelection().getRangeAt(0).anchorNode natürlich auf das aktuelle Dokument beziehen. Das ganze wird von außern über eine native Anwendung gesteuert (Undo/Redo Stack) und soll dafür verwendet werden, um bei einem Undo bzw. Redo die alte Selektion wiederherstellen zu können. Das ermitteln der Selektion oder die Wiederherstellung ist kein Problem, sobald ich den Index für die Node und die Node für einen Index finden kann.
Ich habe auch schon versucht einfach document.getElementsByTagName("*") einfach zu durchlaufen. Das Problem scheint zu sein, dass die Nodes geclont werden und getSelection().getRangeAt(0).anchorNode im gesamten Array deshalb nicht vorkommt.
Ich entwickle ausschließlich für Chrome, Browserweichen oder so sind deshalb nicht Notwendug. Bei der Ausführung des o.g. Quelltextes hängt sich leider der Browser komplett auf. Ich habe auch versucht etwas später in den DOM-Baum einzusteigen und document.getElementsByTagName("body")[0] als "parent" für den Funktionsaufrufes anstelle von "document" zu verwenden.
Jemand eine Idee?
Hallo!
Warum machst Du es Dir so schwer? document.body.getElementsByTagName('*') sollte alle Nodes in Reihe enthalten.
Gruß, LX
Hallo!
Warum machst Du es Dir so schwer? document.body.getElementsByTagName('*') sollte alle Nodes in Reihe enthalten.
Gruß, LX
Ja, stimmt auch. Allerdings klappt hier der Vergleich der node mit keinem Eintrag der Liste. Deshalb bin ich davon ausgegangen, dass das Ergebnis ein "Clon" ist. Leider funktioniert die andere Methode auch mit der Fehlerbehebung nicht richtig (siehe meine andere Antwort).
Hi,
Ja, stimmt auch. Allerdings klappt hier der Vergleich der node mit keinem Eintrag der Liste.
Wieso, klar klappt der Vergleich:
<!DOCTYPE html>
<html lang="de">
<head>
<script type="text/javascript">
function getDomChilds(node) {
var nodes = document.getElementById("main").getElementsByTagName("*");
for(var i = 0; n = nodes[i]; i++) {
if(n === node) {
return i;
}
}
return "nix";
}
function init() {
alert(getDomChilds( document.getElementById("p2") ));
}
</script>
</head>
<body onload="init()">
<div id="main">
<div id="d1"><p id="p1">foo</p></div>
<div id="d2"><p id="p2">bar</p></div>
<div id="d3"><p id="p3">batz</p></div>
</div>
<p id="out"></p>
</body>
</html>
Gruesse, Joachim
function init() {
alert(getDomChilds( document.getElementById("p2") ));
}
Du ermittelst den gesuchten Knoten anders als ich. Bei mir funktioniert es über windows.getSelection().anchorNode nicht. Ich suche ja den Knoten der Selektion. Aber wie gesagt, ich mache sicher noch was falsch. Getestet im aktuellsten Chrome (genauer: Chromium), andere Browser sind in meinem Fall irrelevant.
Hi,
for(i = 0; i < parent.childNodes.length; i++) {
^^^^^^^^^^^^^^^^
schlecht. i global definiert überschreibt sich und produziert eine Endlosschleife. So gehts - vorausgesetzt es stört Dich nicht, dass auch Text-Nodes - und zwar je nach Browser unterschiedlich - berücksichtigt werden:
<!DOCTYPE html>
<html lang="de">
<head>
<script type="text/javascript">
function getDomChilds(parent, cnt) {
var child;
for(var i=0; child = parent.childNodes[i]; i++) {
cnt ++;
var e = document.createTextNode(cnt + ". " + (parent.childNodes[i].id || parent.childNodes[i]));
var br = document.createElement("br");
document.getElementById("out").appendChild(e);
document.getElementById("out").appendChild(br);
if(child.hasChildNodes()) {
getDomChilds(child, cnt);
}
}
}
function init() {
getDomChilds( document.getElementById("main"), 0 );
}
</script>
</head>
<body onload="init()">
<div id="main">
<div id="d1"><p id="p1">foo</p></div>
<div id="d2"><p id="p2">bar</p></div>
<div id="d3"><p id="p3">batz</p></div>
</div>
<p id="out"></p>
</body>
</html>
Gruesse, Joachim
Hallo,
okay, wieder was gelernt. Ich wusste nicht, dass "var" die Variable lokal definiert und ohne var Global anlegt. Erklärt natürlich das Problem.
Hier ist nun mein aktualisierter Quelltext:
var nodeIndex;
function getChildNodeIndex(parent, node) {
nodeIndex++;
for(var i = 0; i < parent.childNodes.length; i++)
if(parent.childNodes[i] == node || getDomChilds(parent.childNodes[i], node))
return true;
return false;
}
function getNodeIndex(node) {
nodeIndex = -1;
if(getChildNodeIndex(document, node))
return nodeIndex;
else
return -1;
}
var selection = window.getSelection();
if(selection.getRangeAt && selection.rangeCount > 0)
var range = selection.getRangeAt(0);
else
var range = null;
var anchorNode = getNodeIndex(selection.anchorNode);
var anchorOffset = selection.anchorOffset;
var focusNode = getNodeIndex(selection.focusNode);
var focusOffset = selection.focusOffset;
Stürzt zumindest nicht mehr ab, allerdings klappt der Vergleich
parent.childNodes[i] == node
nicht. Es gibt im gesamten DOM leider keinen Treffer. Es muss doch irgendwie möglich sein, die anchorNode aus der Selektion irgendwie wieder zu finden.
Die Umgebung ist Chrome in einer von mir festgelegten Version. Wichtig ist halt nur, dass der nodeIndex für das selbe Element im selben HTML Dokument wieder den selben Index ergibt. Der Undo-Stack hat für jeden Undo Schritt halt die Daten + Selektion.
Vielleicht mache ich bei meinem Vergleich ja noch etwas falsch, eine Idee dazu?
if(parent.childNodes[i] == node || getDomChilds(parent.childNodes[i], node))
Du rufst hier die falsche Funktion rekursiv auf.
Davon abgesehen funktioniert der Code bei mir und liefert anscheinend korrekte Index-Werte.
Mathias
Du rufst hier die falsche Funktion rekursiv auf.
Hallo inwiefern die falsche Funktion? Wenn die erste Bedingung nicht greift, muss rekursiv weitergesucht werden. Innerhalb der Funktion müsste ich vor "nodeIndex++" allerdings eigentlich noch prüfen, ob childNodes vorhanden sind. Wobei die Korrektheit des Indexes garnicht so wichtig ist, solange zwei Aufrufe der Funktion den selben eindeutigen Wert liefern.
Davon abgesehen funktioniert der Code bei mir und liefert anscheinend korrekte Index-Werte.
Hast du das in Chrome getestet? Ich könnte mir vorstellen, dass die Browser hier unterschiedlich arbeiten.
Würd mich freuen, wenn du den Code an der falschen Stelle mal irgendwie korrigieren könntest. Ich seh den Fehler irgendwie nicht. :(
inwiefern die falsche Funktion?
var nodeIndex;
function getChildNodeIndex(parent, node) {
nodeIndex++;
for(var i = 0; i < parent.childNodes.length; i++)
if(parent.childNodes[i] == node || getDomChilds(parent.childNodes[i], node))
return true;
return false;
}
Du musst hier getChildNodeIndex aufrufen, nicht getDomChilds. Da hast du wohl zwei Beispiele vermischt.
Mein (überarbeiteter) Testcode:
<!DOCTYPE html>
<html>
<head>
<script>
[code lang=javascript]function doit () {
function getNodeIndex (needle) {
var nodeIndex = -1;
walk(document.body);
return nodeIndex;
function walk (parent) {
nodeIndex++;
console.debug(nodeIndex, parent.nodeType == 1 ? parent.nodeName : parent.nodeValue);
var childNodes = parent.childNodes;
for (var i = 0, l = childNodes.length; i < l; i++) {
var node = childNodes[i];
if (needle == node || walk(node)) {
console.debug('found needle');
return true;
}
}
}
}
var selection = window.getSelection();
var anchorNodeIndex = getNodeIndex(selection.anchorNode);
var focusNodeIndex = getNodeIndex(selection.focusNode);
alert(anchorNodeIndex + ' bis ' + focusNodeIndex);
}
</script>
</head>
<body>
<p>aaaaa</p>
<p>bbbbb</p>
<p>ccccc</p>
<p>ddddd</p>
<p>eeeee</p>
<input type="button" value="do it" onclick="doit()">
</body>
</html>[/code]
Markieren und Button klicken. Funktioniert hervorragend in Chrome 12.
Wobei der nodeIndex bei dieser Umsetzung immer für alle Kinder gilt, d.h. wenn ich innerhalb des aaaaa-Textknoten markiere, so bekomme ich für Anfang und Ende der Auswahl den nodeIndex des p-Elternelements.
Mathias
Hallo,
Du musst hier getChildNodeIndex aufrufen, nicht getDomChilds. Da hast du wohl zwei Beispiele vermischt.
na da klatsch ich mir mal an den Kopf. :/ Habe den Code heute zig mal hin und her geändert, das hab ich tatsächlich übersehen. Ahhh. Nun funktioniert es natürlich wunderbar.
Wobei der nodeIndex bei dieser Umsetzung immer für alle Kinder gilt, d.h. wenn ich innerhalb des aaaaa-Textknoten markiere, so bekomme ich für Anfang und Ende der Auswahl den nodeIndex des p-Elternelements.
Ja, das ist aber kein Problem. Der ermittelte Index bleibt während der Laufzeit im Speicher und das ganze passt immer exakt zum Quelltext, für den er ermittelt wurde. Ich kann auf der Basis nun einfach eine getNodeForIndex funktion bauen, die mir dann den Knoten nach dem laden des Quelltextes liefert. Da muss ich dann nur noch die Selektion wieder herstellen.
Vielen Dank für die Hilfe. :)
@@Joachim:
nuqneH
<html lang="de">
▲▲
Bist du sicher? ;-)
<div id="d1"><p id="p1">foo</p></div>
<div id="d2"><p id="p2">bar</p></div>
<div id="d3"><p id="p3">batz</p></div>
Qapla'
Hi,
<html lang="de">
▲▲
Bist du sicher? ;-)
wenn Du mich schon so fragst nicht mehr ;-)
Gruesse, Joachim
Ist es irgendwie möglich die Rückwärtsselektion wiederherzustellen? Das ganze muss nur in der aktuellsten Chrome Version laufen, andere Browser sind irrelevant, weil ich volle Kontrolle über den verwendeten Browser habe.
Mach einfach eine Vorwärtsauswahl daraus. Du mit compareDocumentPosition kannst feststellen, wenn die Auswahl umgekehrt ist – dann tauscht du einfach Anfang und Ende aus.
Siehe beispielsweise meine Selection-Scripte:
https://github.com/molily/selectionmenu/blob/master/selectionmenu.js#L193
Auch wenn ich da mit startContainer/endContainer der DOM Range arbeite, mit anchorNode und focusNode ist es natürlich dasselbe (die WHATWG hat DOM Ranges nach dem Not-invented-here-Prinzip noch einmal in abgebildet – aktuell ist es aber nicht mehr in der HTML5-Spezifikation).
Mathias