Cannot replace nodes after using TreeWalker - javascript

I would like to replace the current text nodes with span nodes.
I have made this JS code but it does not work:
var EditorElement_Content;
var ContentElement_TreeWalker;
//
ContentElement_TreeWalker = document.createTreeWalker(EditorElement_Content, NodeFilter.SHOW_TEXT);
while (ContentElement_TreeWalker.nextNode()) {
var ReplacementNode = document.createElement("span");
ReplacementNode.appendChild(ContentElement_TreeWalker.currentNode);
ContentElement_TreeWalker.currentNode.parentNode.replaceChild(ReplacementNode, ContentElement_TreeWalker.currentNode);
}
Also, the Web explorer console returns this: HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy
What am I doing wrong and why?
Thanks in advance.

Related

DOMParser parseFromString removing nodes only when iterating resulting body childNodes

I am writing an electronjs app. I want to parse a string to DOM nodes and try to use DOMParser parseFromString for that. Here is the code:
let str = '<div id="customerList" style="display: none;"><ul></ul></div><script type="text/javascript" src="../js/customerList.js"></script>';
let nodes = new DOMParser().parseFromString(str, 'text/html').body.childNodes;
console.log(nodes);
This returns a NodeList with 2 elements, the expected div and scriptl, in it. If I add the following part in the code, the first element, the div, disappears from the NodeList:
let str = '<div id="customerList" style="display: none;"><ul></ul></div><script type="text/javascript" src="../js/customerList.js"></script>';
let nodes = new DOMParser().parseFromString(str, 'text/html').body.childNodes;
console.log(nodes);
for (let node of nodes) {
contentDiv.appendChild(node);
}
The for loop is after the console.log and somehow alters the behavior of the code before. I can't seem to figure out, why the code behaves like it does though...Since I want to provide information about the first element in an ipcRenderer call, this is actually quite frustrating at the moment. Why does the code behave like it does?
Node.appendChild() moves a node to the new destination. That's why it disappears from your node list.
You can clone the node to avoid that like so:
let str = '<div id="customerList" style="display: none;"><ul></ul></div><script type="text/javascript" src="../js/customerList.js"></script>';
let nodes = new DOMParser().parseFromString(str, 'text/html').body.childNodes;
console.log(nodes);
for (let node of nodes) {
contentDiv.appendChild(node.cloneNode());
}
This will append clones of all(!) nodes from the list and keep your nodes list as is.
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild

Piping createElement and appendChild in JavaScript

Can someone please explain to me, why
var Node = document.createElement("testing");
var Parent = document.createElement("testingOne")
Parent.appendChild(document.createElement("hi"));
Node.appendChild(Parent);
produces a different result from
var Node = document.createElement("testing");
var Parent = document.createElement("testingOne")
.appendChild(document.createElement("hi"));
Node.appendChild(Parent);
In the second snippet the element testingOne doesn't even get included. Why does the piping do this?
Your first example will result in
<testing><testingone><hi></hi></testingone></testing>
Parent will contain the testingOne and the hi element will be appended to it.
While the second example will result in
<testing><hi></hi></testing>
Because Parent will contain the hi element, which is returned by the appendChild method.

Reverse the order of elements added to DOM with JavaScript

I am making a game in JavaScript, and need an event log. If i attack, it tells me if i hit or i miss. Here is my code:
function eventlogshow (text){
var para = document.createElement("p");
var node = document.createTextNode(text);
para.appendChild(node);
var element = document.getElementById("eventlog");
element.appendChild(para);
}
It lists the most recent event on the bottom, with the oldest on top. How do i reverse that?
I would like it to show the most recent event on the top.
Prepend the child element instead. Since there is no prependChild() function, you need to "insert it before the first child":
function eventlogshow (text){
var para = document.createElement("p");
var node = document.createTextNode(text);
para.appendChild(node);
var element = document.getElementById("eventlog");
element.insertBefore(para, element.firstChild);
}
A similar question has been asked here: How to set DOM element as first child?.
Read more about Node.firstChild and Node.insertBefore()
appendChild adds a node as a last child. You want to insert before the first node:
element.insertBefore(para, element.childNodes[0]);

Calculating text selection offsets in nest elements in Javascript

The Problem
I am trying to figure out the offset of a selection from a particular node with javascript.
Say I have the following HTML
<p>Hi there. This <strong>is blowing my mind</strong> with difficulty.</p>
If I select from blowing to difficulty, it gives me the offset from the #text node inside of the <strong>. I need the string offset from the <p>'s innerHTML and the length of the selection. In this case, the offset would be 26 and the length would be 40.
My first thought was to do something with string offsets, etc. but you could easily have something like
<p> Hi there. This <strong>is awesome</strong>. For real. It <strong>is awesome</strong>.</p>
which would break that method because there are identical nodes. I also need the option to throw out nodes. Say I have something like this
<p>Hi there. This <strong>is blowing my mind</strong> with difficulty.</p>
I want to throw out an elements with rel="inserted" when I do the calculation. I still want 26 and 40 as the result.
What I'm looking for
The solution needs to be recursive. If there was a <span> with a <strong> in it, it would still need to traverse to the <p>.
The solution needs to remove the length of any element with rel="inserted". The contents are important, but the tags themselves are not. All other tags are important. I'd strongly prefer not to remove any elements from the DOM when I do all of this.
I am using document.getSelection() to get the selection object. This solution only has to work in WebKit. jQuery is an option, but I'd prefer to it without it if possible.
Any ideas would be greatly appreciated.
I have no control over the HTML I doing all of this on.
I think I solved my issue. I ended not calculating the offset like I originally planned. I am storing the "path" from the chunk (aka <p>). Here is the code:
function isChunk(node) {
if (node == undefined || node == null) {
return false;
}
return node.nodeName == "P";
}
function pathToChunk(node) {
var components = new Array();
// While the last component isn't a chunk
var found = false;
while (found == false) {
var childNodes = node.parentNode.childNodes;
var children = new Array(childNodes.length);
for (var i = 0; i < childNodes.length; i++) {
children[i] = childNodes[i];
}
components.unshift(children.indexOf(node));
if (isChunk(node.parentNode) == true) {
found = true
} else {
node = node.parentNode;
}
}
return components.join("/");
}
function nodeAtPathFromChunk(chunk, path) {
var components = path.split("/");
var node = chunk;
for (i in components) {
var component = components[i];
node = node.childNodes[component];
}
return node;
}
With all of that, you can do something like this:
var p = document.getElementsByTagName('p')[0];
var piece = nodeAtPathFromChunk(p, "1/0"); // returns desired node
var path = pathToChunk(piece); // returns "1/0"
Now I just need to expand all of that to support the beginning and the end of a selection. This is a great building block though.
What does this offset actually mean? An offset within the innerHTML of an element is going to be extremely fragile: any insertion of a new node or change to an attribute of an element preceding the point in the document the offset represents is going to make that offset invalid.
I strongly recommend using the browser's built-in support for this in the form of DOM Range. You can get hold of a range representing the current selection as follows:
var range = window.getSelection().getRangeAt(0);
If you're going to be manipulating the DOM based on this offset that you want, you're best off doing so using nodes instead of string representations of those nodes.
you can use the following java script code:
var text = window.getSelection();
var start = text.anchorOffset;
alert(start);
var end = text.focusOffset - text.anchorOffset;
alert(end);
Just check if your selected element is a paragraph, and if not use something like Prototype's Element.up() method to select the first paragraph parent.
For example:
if(selected_element.nodeName != 'P') {
parent_paragraph = $(selected_element).up('p');
}
Then just find the difference between the parent_paragraph's text offset and your selected_element's text offset.
Maybe you could use the jQuery selectors to ignore the rel="inserted"?
$('a[rel!=inserted]').doSomething();
http://api.jquery.com/attribute-not-equal-selector/
What code are you using now to select from blowing to difficulty?

"touch" a DOM element

Is there a handy way to "touch" a DOM element? I'd like to remove the element and insert it again at the same position. Something like this:
element.parentNode.removeChild(element).appendChild(element);
except that appendChild inserts the element as the last sibling.
Use insertBefore instead of appendChild.
var other = element.nextSibling;
if ( other ) {
other.parentNode.removeChild(element);
other.parentNode.insertBefore(element,other);
} else {
other = element.parentNode;
other.removeChild(element);
other.appendChild(element);
}
This creates a dummy text node to be used as a marker and replaces it with the node. Later when the node is to be re-inserted, replace it with the dummy node so the position is preserved.
Node.replaceChild
var dummy = document.createTextNode('');
var parent = element.parentNode;
parent.replaceChild(dummy, element); // replace with empty text node
parent.replaceChild(element, dummy); // swap out empty text node for original
Yes but it would be better to use the DOM cloneNode(true) as it would retain all of the child nodes and properties:
// Copy the node.
var theOldChild = document.getElementById("theParent").childNodes[blah]
var theNewChild = theOldChild.cloneNode(true);
// Find the next Sibling
var nextSib = theOldChild.nextSibling();
// Remove the old Node
theOldChild.parentNode.removeChild(theOldChild)
// Append where it was.
nextSib.parentNode.inserertBefore(theNewChild, nextSib);
That is the way that I would do it as you can hold onto the variable "theNewChild" 100% as it was and insert it back into the document at any time.

Categories