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!
Related
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];
I have an HTML document, and I would like to remove some of the tags from it dynamically using Javascript, based on whether the tags are within the current selection or not. However, I do not want to update the actual document on the page, I want to make a copy of the whole page's HTML and edit that copy. The problem is that the Range object I get from selection.getRangeAt(0) still points to the original document, as far as I can see.
I've managed to get editing the original document in place with this code:
var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
var allWithinRangeOfParent = node.getElementsByTagName("*");
for (var i=0, el; el = allWithinRangeParent[i]; i++) {
// The second parameter says to include the element
// even if it's not fully selected
if (selection.containsNode(el, true) ) {
el.remove();
}
}
But what I want to do is to somehow perform the same operation with removing elements, but remove them from a copy of the original HTML. I've made the copy like this: var fullDocument = $('html').clone(); How could I accomplish this?
Either dynamically add a class or data attribute to all your elements on load before you clone so that you have a point of reference then grab the class or data attribute on the common ancestor and remove it from the clone. I can give an example if you like? Along these lines - http://jsfiddle.net/9s9hpc2v/ isn't properly working exactly right but you get the gist.
$('*').each(function(i){
$(this).attr('data-uniqueId', i);
});
var theclone = $('#foo').clone();
function laa(){
var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
if(node.getElementsByTagName){
var allWithinRangeOfParent = $(node).find('*');
console.log(allWithinRangeOfParent, $(allWithinRangeOfParent).attr('data-uniqueId'));
$.each(allWithinRangeOfParent, function(){
theclone.find('[data-uniqueId="'+$(this).attr('data-uniqueId')+'"]').remove();
});
console.log(theclone.html());
}
}
$('button').click(laa);
I'm using a web service to query a ticketing interface and pull back all support tickets that are open at any point in time, displaying them in a nice neat table within an iframe as part of our web application. My application is written in PL/SQL however I use this to construct my HTML pages and the content that is displayed within my iframe. My main issue is that because the tickets are made up of Email content, there is a lot of useless rubbish (mostly tags) and irrelevant text that is included at the top of the Email content which causes my interface to look trashy.
Within my code I am wrapping the main areas of content that I require in a table with an ID of "ticketsTable" and would like to remove any elements or content within the iframe only that occurs before this tickets table.
Usually I would use jQuery for this task:
$("#earliercontent").nextUntil("#ticketsTable").andSelf().remove();
however, our system is built using ExtJS so we wish to avoid any potential conflict the jQuery library could cause with this environment. I therefore need a way to process and loop through the contents of the iframe, removing all elements and text that occur before the ticketTable id therefore eradicating all of the unnecessary content.
I've already tried this:
var last = null;
var curr = $('#page1');
while (curr.attr('id') != 'ticketsTable') {
if (last != null) {
last.remove();
}
last = curr;
curr = curr.next();
}
if (last != null) {
last.remove();
}
Source: jQuery remove all elements until id='whatever' found
however, as the Email content is variable, there is no specific ID tag I can specify for the script to start at. Therefore, is there an alternative method I can use (without a library, just pure JavaScript) to remove all elements and content before the ticketsTable?
I must stress that the contents of the ticketsTable must remain intact. It is only the content that occur before this within the body that I wish to remove.
http://jsfiddle.net/yJrb7/
var parent = document.getElementById("parent");
var stopID = "stop";
var length = parent.childNodes.length, j = 0;
for(var i=0; i < length; i++){
if(parent.childNodes[j].id != stopID){
parent.removeChild(parent.childNodes[j]);
}
else{
j++;
}
}
When you remove a child node, the array is shifted, so if for example you need to remove two first nodes, then you would call removeChild for index 0 two times.
I've noticed that when I change the ID or name of a form element via JavaScript, the jQuery events that were tied to it are no longer there. I've tried this with Firefox 17 and IE 10. Is this by design? And if so, is there any way to prevent it?
UPDATE: Please check http://jsfiddle.net/qHH7P/2/ for an example.
I'm adding the button to remove the row via jQuery. When I remove the first row, I give the elements in the second row a new name and ID. Then the remove button for that remaining row doesn't fire the event anymore. I need to rename the elements because ASP.NET MVC expects a certain naming convention for the collection of objects when binding. That's why I need to rename them with a "0" instead of "1". I'm doing my rename with
var regexpattern = new RegExp("WorkspaceQuestionSets\\[.+\\]", "g");
$(this).html($(this).html().replace(regexpattern, "WorkspaceQuestionSets[" + index + "]"));
var regexpattern = new RegExp("WorkspaceQuestionSets_.+__", "g");
$(this).html($(this).html().replace(regexpattern, "WorkspaceQuestionSets_" + index + "__"));
I just realized that I'm not even renaming the buttons. So it makes even less sense that the event is gone. But if I comment out the code to rename the elements, the event remains.
You are rewriting the HTML. .html() returns a string, which you then modify and set again. The browser will parse that HTML string and create new DOM elements from it. In this process, your destroying DOM elements and consequently loose the event handlers you bound to them before.
I just realized that I'm not even renaming the buttons. So it makes even less sense that the event is gone.
You are destroying and recreating every single element that is inside this (each row I assume), no matter whether you modified its HTML representation or not.
You have to possibilites to solve this:
Use event delegation: Instead of binding the event handlers directly to the elements, bind them to an ancestor, which will always exist. Read more about event delegation in the .on *[docs] documentation, section Direct and delegated events.
Don't rewrite the HTML. Select the elements whose name attribute you want to modify and modify it. For example:
var name_exp1 = /WorkspaceQuestionSets\[.+\]/g;
var name_exp2 = /WorkspaceQuestionSets_.+__/g;
sourceEle.closest(".table").find(".row.set").each(function(index) {
// Edit the name attribute of all `select` and `input` elements
$(this).find('select, input').prop('name', function(i, name) {
if (name_exp1.test(name)) {
return name.replace(
name_exp1,
"WorkspaceQuestionSets[" + index + "]"
);
}
else if (name_exp2.test(name)) {
return name.replace(
name_exp1,
"WorkspaceQuestionSets_" + index + "__"
);
}
return name;
});
});
In JavaScript, forget the HTML and work with the DOM.
This does not happen to me with plain JavaScript in Chrome 24.
Here's what I typed in the JS console:
document.write("<button id='bb'/>")
//undefined
var bb=document.getElementById('bb')
//undefined
bb
//<button id="bb"></button>
bb.addEventListener('click',function(){alert('hi');});
//undefined
bb
//<button id="bb"></button>
bb.id
//"bb"
bb.id="qq"
//"qq"
bb
//<button id="qq"></button>
document.getElementById("bb")
//null
document.getElementById("qq")
//<button id="qq"></button>
And importantly clicking the button made the event trigger both before and after the assignment to bb.id.
Also in Firefox 17 I didn't quite see that happening with plain JavaScript. I was on the page: http://start.ubuntu.com/12.04/Google/?sourceid=hp
And in the console here's what I issued:
[19:29:41.261] var bb=document.getElementById('sbtn')
[19:29:41.267] undefined
[19:29:45.501] bb.addEventListener('click',function(){alert('hi')});
[19:29:45.507] undefined
[19:29:48.292] bb.id
[19:29:48.298] "sbtn"
[19:29:59.613] bb.id="sbttttttn"
[19:29:59.619] "sbttttttn"
The alert still occurred after the id was changed.
This must be specific to either IE, which I cannot try, or the way jQuery sets up events or handles ID changes (perhaps invisibly destroying the object?).
I'm trying to figure out how to, in raw javascript (no jQuery, etc.), find an element with specific text and modify that text.
My first incarnation of the solution... is less than adequate. What I did was basically:
var x = document.body.innerHTML;
x.replace(/regular-expression/,"text");
document.body.innerHTML = x;
Naively I thought I succeeded with flying colors, especially since it was so simple. So then I added an image to my example and thought I could check every 5 seconds (because this string may enter the DOM dynamically)... and the image flickered every 5 seconds.
Oops.
So, there has to be a correct way to do this. A way that specifically singles out a specific DOM element and updates the text portion of that DOM element.
Now, there's always "recursively search through the children till you find the deepest child with the string" approach, which I want to avoid. And even then, I'm skeptical about "changing the innerHTML to something different" being the correct way to update a DOM element.
So, what's the correct way to search through the DOM for a string? And what's the correct way to update a DOM element's text?
Now, there's always "recursively search through the children till you find the deepest child with the string" approach, which I want to avoid.
I want to search for an element in an unordered random list. Now, there's a "go through all the elements till you find what you're looking for approach", which I want to avoid.
Old-timer magno tape, record, listen, meditate.
Btw, see: Find and replace text with JavaScript on James Padolsey's github
(also hig blog articles explaining it)
Edit: Changed querySelectorAll to getElementsByTagName from RobG's suggestion.
You can use the getElementsByTagName function to grab all of the tags on the page. From there, you can check their children and see if they have any Text Nodes as children. If they do, you'd then look at their text and see if it matches what you need. Here is an example that will print out the text of every Text Node in your document with the console object:
var elms = document.getElementsByTagName("*"),
len = elms.length;
for(var ii = 0; ii < len; ii++) {
var myChildred = elms[ii].childNodes;
len2 = myChildred.length;
for (var jj = 0; jj < len2; jj++) {
if(myChildred[jj].nodeType === 3) {
console.log(myChildred[jj].nodeValue);
// example on update a text node's value
myChildred[jj].nodeValue = myChildred[jj].nodeValue.replace(/test/,"123");
}
}
}
To update a DOM element's text, simple update the nodeValue property of the Text Node.
Don't use innerHTML with a regular expression, it will almost certainly fail for non-trivial content. Also, there are still differences in how browsers generate it from the live DOM. Replacing the innerHTML will also remove any event listeners added as element properties (i.e. like element.onclick = fn).
It is best if you can have the string enclosed in an element with an attribute or property you can search on (id, class, etc.) but failing that, a search of text nodes is the best approach.
Edit
Attempting a general purpose text selection function for an HTML document may result in a very complex algorithm since the string could be part of a complex structure, e.g.:
<h1>Some <span class="foo"><em>s</em>pecial</span> heading</h1>
Searching for the string "special heading" is tricky as it is split over 2 elements. Wrapping it another element (say for highlighting) is also not trivial since the resulting DOM structure must be valid. For example, the text matching "some special" in the above could be wrapped in a span but not a div.
Any such function must be accompanied by documentation stating its limitations and most appropriate use.
Forget regular expressions.
Iterate over each text node (and doing it recursively will be the most elegant) and modify the text nodes if the text is found. If just looking for a string, you can use indexOf().
x.replace(/regular-expression/,"text");
will return a value so
var y = x.replace(/regular-expression/,"text");
now you can assign new value.
document.body.innerHTML = y;
Bu you want to think about this, you dont't want to get the whole body just to change one small piece of code, why not get the content of a div or any element and so on
example:
<p id='paragraph'>
... some text here ...
</p>
now you can use javascript
var para = document.getElementById('paragraph').innerHTML;
var newPara = para.replace(/regex/,'new content');
para.innerHTML = newPara;
This should be the simplest way.