I'm writing a Chrome content script extension and I need to be able to target a specific element that, unfortunately, has no unique identifiers except its parent element.
I need to target the immediate first child element of parentElement. console.log(parentElement) reports both of the child elements/nodes perfectly, but the succeeding console logs (the ones that target the childNodes) always return an undefined value no matter what I do.
This is my code so far
(I have excluded the actual names to avoid confusion and extra, unnecessary explanation)
function injectCode() {
var parentElement = document.getElementsByClassName("uniqueClassName");
if (parentElement && parentElement.innerHTML != "") {
console.log(parentElement);
console.log(parentElement.firstElementChild);
console.log(parentElement.firstChild);
console.log(parentElement.childNodes);
console.log(parentElement.childNodes[0]);
console.log(parentElement.childNodes[1]);
} else {
setTimeout(injectCode, 250);
}
}
How do I select the first child element/node of parentElement?
Update:
parentElement.children[0] also has the same error as parentElement.childNodes[0].
Both these will give you the first child node:
console.log(parentElement.firstChild); // or
console.log(parentElement.childNodes[0]);
If you need the first child that is an element node then use:
console.log(parentElement.children[0]);
Edit
Ah, I see your problem now; parentElement is an array.
If you know that getElementsByClassName will only return one result, which it seems you do, you should use [0] to dearray (yes, I made that word up) the element:
var parentElement = document.getElementsByClassName("uniqueClassName")[0];
Related
This exercise will be to write a helper script that could theoretically be used on any web page to help identify the elements that contain text, simply by including your JavaScript file!
Define the script in a source file called highlightNodes.js.
This script should navigate every element in the DOM, and for each element in the body determine whether it is a element ( type 3) or not.
Now add to your script code to create a new child node for every non-text node encountered. This new node should take on the class " hoverNode" and innerHTML equal to the parent tag name. Define appropriate styles for that CSS class.
Now add listeners so that when you click on the newly created nodes, they will alert you to information about the tag name, so that when a node is clicked a pop- up alerts us to the details about that node including its ID and innerHTML.
The example picture that the teacher took it really poor quality, but I think you will still be able to see what's going on.
http://imgur.com/7XKs4U5
Here's the code I have so far: https://jsfiddle.net/vuku3qdu/2/
window.onload = function () {
var bodyNodes = document.getElementsByTagName("*");
for (var i = 0; i < bodyNodes.length; i++) {
if (bodyNodes[i].nodeType != 3) {
newChildNode = document.createElement("p");
newChildNode.className = "hoverNode";
newChildNode.innerHTML = bodyNodes[i].tagName;
bodyNodes[i].appendChild(newChildNode);
newChildNode.addEventListener("click", function () {
alert("Tag Name: " + newChildNode.tagName + " innerHtml: " + newChildNode.innerHTML);
});
}
i++;
}
};*
For some reason, this isn't working on Jfiddle correctly. It displays highlight nodes locally. My issue is that it isn't evaluating the nodeTypes properly. It is saying that they are all "1" for NodeType. I know this isn't correct and it's giving me too many of these highlight nodes. Also, it is skipping the "label" elements that are embedded within the Fieldset.
My last problem... I can't get the Highlight nodes to output the proper newChildNodes.innerHTML in the listener functions. It always gives me undefined.
I've logged all of the steps through the console and I know that it is evaluating the node types wrong, but I cannot figure out the correct command to do so.
Thanks Guys!
Given the following structure
<p>
<p>
<span>
<span class="a">
<p>
I want to turn it into
<p>
<span>
<span class="a">
Yes, the first block is invalid, we'll ignore that. It's just an example.
Basically, what I want to do is check if any child is necessary, and if not, remove it, keeping all of its children. So, all <p>'s are identical, straight elements, therefore only the top one is really doing anything (in my code, I realize that's not always the case). However, the spans, though identical in name, are not the same, as one has class="a" and the other has no class, thus they both stay.
The expansion of what I'm looking for would not just work if class name differs, but any of the properties that might make it actually different.
I was thinking that I could use node1.attributes === node2.attributes, but that doesn't work, even though node1 and node2 attributes have a length of zero. Furthermore, node1 === node2, node1.isEqualNode(node2), and node1.isSameNode(node2) all fail as well. And rightly so, as they're not the same node.
So how can I rightly check to see if the node is eligible for removal?
Another example, of when this would actually be useful
<p>
<b>
Some text
<u>that is
<b>bolded</b> <!-- << That <b> is totally useless, and should be removed. -->
</u>
</b>
</p>
No Jquery please.
This is what I ended up with:
it = doc.createNodeIterator(doc.firstChild, NodeFilter.SHOW_ALL, null, false);
node = it.nextNode();
while(node){
// Remove any identical children
// Get the node we can move around. We need to go to the parent first, or everything will be true
var tempNode = node.parentNode;
// While we haven't hit the top - This checks for identical children
while(tempNode && tempNode !== div && tempNode !== doc){
// If they're the same name, which isn't not on the noDel list, and they have the same number of attributes
if(node.nodeName === tempNode.nodeName && noDel.indexOf(node.nodeName) < 0 && node.attributes.length === tempNode.attributes.length){
// Set flag that is used to determine if we want to remove or not
var remove = true;
// Loop through all the attributes
for(var i = 0; i < node.attributes.length; ++i){
// If we find a mismatch, make the flag false and leave
if(node.attributes[i] !== tempNode.attributes[i]){
remove = false;
break;
}
}
// If we want to remove it
if(remove){
// Create a new fragment
var frag = doc.createDocumentFragment();
// Place all of nodes children into the fragment
while(node.firstChild){
frag.appendChild(node.firstChild);
}
// Replace the node with the fragment
node.parentNode.replaceChild(frag, node);
}
}
// Otherwise, look at the next parent
tempNode = tempNode.parentNode;
}
// Next node
node = it.nextNode();
}
Oh this would be an interesting interview question!
Regarding the solution- make a function to traverse the DOM any which way you like, which receives a predicate function as an argument. Then collect an array of items which pass the predicate, and do your 'fixing' afterwards.
In your case, the predicate is obviously some isChildNecessary function, which you will need to implement.
How would you plan on handling a second pass, and a third, etc? When do you stop? After removing some nodes, you may end up having another 'invalid' state.. Just something to think about.
Hello all you ridiculously genius people!
So I'm creating a basic word processor, and I need to have an option to clear formatting in a certain section. The place the user is typing is split up into several different divs, and whenever they hit enter it starts a new div. When they add a style option (Bold, italics, etc.) it ads the correct html tag.
Well, I want the user to be able to clear all formatting on text they highlight. To do this, I need to find the element that all of them share. so if it displayed "Here is my text" in a structure like this:
<div id="1">
<div>Here</div>
<div>is</div>
<div>some</div>
<div>text</div>
</div>
and the user highlighted all 4 words, I would need to computer to give me the div with the id "1" because that is what they all share in common.
I am completely stumped on this, and have absolutely no clue where to start, so I really don't have any code for you. I will be using window.getSelected(); to get the actual text, but I have no idea how to go about finding the divs around it.
Thanks for your help!
If you are using window.getSelection(), it returns a selection object which contains properties .anchorNode and .focusNode which are the nodes at the start and end of the selection. You can then use those nodes to walk up the parent chain to find any level of parent you want.
The closest common parent could be found by getting the immediate parent of the first object and then seeing if it is also a parent of the second object. If not, go up one parent higher until you eventually find a common parent.
function findCommonParentElement(startNode, endNode) {
// see if node has a particular parent
// by walking up the parent chain and comparing parents
function hasParent(node, parent) {
while (node && node !== parent && node !== document.body) {
node = node.parentNode;
}
return node === parent;
}
// for text nodes, get the containing element
// for elements, just return what was passed in
function getElement(node) {
while (node && node.nodeType !== 1) {
node = node.parentNode;
}
return node;
}
// go up the parent chain of endNode looking for a node
// that startNode has as a parent
while (endNode && !hasParent(startNode, endNode)) {
endNode = endNode.parentNode;
}
// return the containing element - so it won't return a textnode
return getElement(endNode);
}
// Usage:
var sel = window.getSelection();
var commonParent = findCommonParentElement(sel.anchorNode, sel.focusNode);
Working demo: http://jsfiddle.net/jfriend00/GV96w/
function countChars(elm) {
if (elm.nodeType == 3) { // TEXT_NODE
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
I tried passing this function a string like countChars("hello"); but that didn't work. What are examples of elements that I can pass?
It expects a reference to a DOM node. That could be a text node (nodeType == 3) or an element node (nodeType == 1) by the look of the function. For example:
countChars(document.getElementById("someId"));
The following HTML would cause the above call to return 5:
<span id="someId">Hello</span>
If the argument is a text node the function returns the number of characters that make up that node. If the argument is an element node the function recursively counts the number of characters that make up the descendant text nodes of the element.
The following HTML would cause the above call to return 10 (the child node is included):
<div id="someId">Outer<span>inner</span></div>
You can see the full list of node types on MDN.
It expects DOM nodes, like something you'd get from document.getElementById() or the list of child nodes on another DOM node.
What it does is find all the text nodes in the list of child nodes of an element and count up how many characters they contain. It does it recursively, so it will find all the text within a container, no matter how far separated in the element graph.
Also it should be noted that this code expects a node to be either a text node or else an element. There are other types of nodes, however, most notably comment nodes.
Looks like you should pass a dom element.
An element (node, actually, hence why it checks if nodeType is a text node), e.g.:
countChars(document.getElementById("myid"));
You can get nodeType over a DOM fragment, so... you must enter a DOM element as input
https://developer.mozilla.org/en/nodeType
The example from that link shows it
var node = document.documentElement.firstChild;
if(node.nodeType != Node.COMMENT_NODE)
alert("You should comment your code well!");
I am trying to replace a certain div element parent with another one newparent. I want to copy only some of parent's children and put them in newparent, and then replace the parent by newparent.
Here is a snippet of my code:
var sb_button = parent.firstChild;
var temp;
while(sb_button) {
console.log("loop: ");
console.log(sb_button.id);
temp = sb_button;
if(sb_button.id != curr_button.id && sb_button.id != prev_button.id) {
console.log("if");
newparent.appendChild(temp);
}
else if(sb_button.id == curr_button.id) {
console.log("elseif");
newparent.appendChild(temp);
newparent.appendChild(prev_button);
}
else {
console.log("else");
}
sb_button.parentNode = parent;
console.log(sb_button.id)
console.log(sb_button.parentNode.children);
sb_button = sb_button.nextSibling;
}
parent.parentNode.replaceChild(newparent,parent);
EDIT :
So when I do newparent.appendChild(temp) it modifies sb_button. What's the workaround for this?
I haven't run your code, but there's a few weird things, perhaps one of which may cause the issue or help clear up the code so the issue is more obvious.
the variable temp seems to be an alias for sb_button: you could remove the variable declaration and replace all references with temp
sb_button is a confusing name for an arbitrary child node
you're appending the node in sb_button to newparent within the if statement, but right after you're trying to set sb_button_.parentNode to parent - that's not possible since parentNode is readonly and it certainly doesn't make sense - you can't append the element to one element but have a different parent.
are you trying to copy or move nodes?
Edit: given that you want to copy nodes, I believe you're looking for cloneNode: make a copy of the node and append that copy, not the original node.
As a matter of clean design, when things get complicated, I'd avoid this kind of hard-to-reason-about while loop. Instead, simply make an array of the nodes, order those the way you want (you could even do this using sort to make it immediately obvious you're just rearranging things), and then make a function that takes a newparent and the array and appends copies of all elements in array order to newparent. Your example isn't that complex, but even here, I'd change the order of the if-clauses to have the "default" case in the final else. e.g.:
for(var child = parent.firstChild; child; child = child.nextSibling)
if(child.id == curr_button.id) { //insert prev_button after curr_button
newparent.appendChild(child.cloneNode(true));
newparent.appendChild(prev_button.cloneNode(true));
} else if(child.id != prev_button.id) {
newparent.appendChild(child.cloneNode(true));
}
parent.parentNode.replaceChild(newparent, parent);
The idea being to make it instantly obvious to the reader that all children are processed exactly once.