Let's say I have a paragraph of text in a HTML file. Using TreeWalker I pushed every non-empty text node to an array:
function getTextNodes(elem) {
var textNodes = [];
var nextNode;
var treeWalker = document.createTreeWalker(elem, NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
return node.textContent !== "\n" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
}, false);
var counter = 1;
while (nextNode = treeWalker.nextNode()) {
textNodes.push(nextNode);
console.log("Pushed " + counter + " times.");
counter++;
}
return textNodes;
}
When I try to change the contents of the array, like replacing every element with the string "changed ", nothing happens in the browser window.
Is it somehow possible storing the nodes by reference, such that every time they are changed, the text in the browser changes, too?
EDIT:
function changeTextNodes(elem) {
for (var i = 0; i < elem.length; i++) {
elem[i] = "change ";
}
}
The code that changes the array elements.
EDIT2:
$(document).ready(function() {
changeTextNodes(getTextNodes(document.body));
});
Here's how the 2 functions are called.
Your code that attempts to change the text examines the array result from gathering up the nodes. That array consists of nodes, so if you want to modify the content you need to do that through the node API. Currently, your code just modifies the array contents, which will have no effect on the DOM; it's just a JavaScript array.
To change the content, you'd modify the nodeValue property:
for (var i = 0; i < elem.length; i++) {
elem[i].nodeValue = "change ";
}
Related
I'm looping through a js object with a nested for loop, stated below, it appends the first element correctly, but then throws the following error:
Can't set the property className of an undefined reference or empty reference. (not sure if exact error, translating from Dutch...)
function allVideos() {
var sql = "SELECT videos.VideoName, videos.VideoPath FROM videos";
var resultSet = db.query(sql, {json:true}); //returns: [{"VideoName":"timelapse aethon2","VideoPath":"videos\\Roermond Papier\\160424 Time laps Aethon2.avi"},{"VideoName":"timelapse aethon3","VideoPath":"videos\\Roermond Papier\\160424 Time laps Aethon2.avi"}]
var parsed = JSON.parse(resultSet);
var parsedlength = arrLenght(parsed);
//alert(resultSet);
for(var i = 0; i < parsedlength; i++) {
var obj = parsed[i];
//alert(i);
var videoElement = document.getElementById("allVideos");
for (var key in obj) {
if(obj.hasOwnProperty(key)) {
videoElement.appendChild(document.createElement('div'));
videoElement.children[i].id='allVid' + i;
videoElement.children[i].className='col-md-4 col-xs-12';
//alert(typeof key)
var card = document.getElementById('allVid' + i);
alert(i);
card.appendChild(document.createElement('div'));
card.children[i].className='card card-block';
card.children[i].innerHTML = "<h3 class='card-title'>" + obj['VideoName'] + "</h3><button class='btn btn-primary'>Selecteren</button>"
}
}
}
}
[EDIT] added screenshot of how it looks
Your code has some significant logic issues. You're using nested loops, but appending to an element assuming that the outer loop counter will let you index into that element's children to get the element you just appended. Later, you try to get that same element again using getElementById. Then, you append a new element to your newly-created element, but try to access that new element using children[i] on the one you just created — at that point, the card element will only have a single child, so as of the second outer loop, it will fail.
createElement returns the element to you, so there's no reason at all to try to access it via children[i] (either time) or getElementById.
See comments:
function allVideos() {
var sql = "SELECT videos.VideoName, videos.VideoPath FROM videos";
var resultSet = db.query(sql, {json:true});
var parsed = JSON.parse(resultSet);
var parsedlength = arrLenght(parsed);
for(var i = 0; i < parsedlength; i++) {
var obj = parsed[i];
//alert(i);
var videoElement = document.getElementById("allVideos");
for (var key in obj) {
if(obj.hasOwnProperty(key)) {
// Create the card, give it its id and class
var card = document.createElement('div');
card.id='allVid' + i;
card.className='col-md-4 col-xs-12';
// Create the div to put in the card, give it its class and content
var div = document.createElement('div');
card.appendChild(div);
div.className='card card-block';
div.innerHTML = "<h3 class='card-title'>" + obj['VideoName'] + "</h3><button class='btn btn-primary'>Selecteren</button>"
// Append the card
videoElement.appendChild(card);
}
}
}
}
Side note: arrLenght looks like a typo (it should be th, not ht), but moreover, there's no reason to use a function to get the length of an array; it's available via the array's length property: parsedLength = parsed.length.
Side note 2: You may find these ways of looping through arrays useful.
Your problem is the if within the nested for:
if(obj.hasOwnProperty(key)) { ...
The variable i is increased even if the property is not "owned" (when the if condition returns false), so next time that the condition is true, i is out of bounds.
I need some help with JavaScript.I am working with XML so i need on which i am implementing JavaScript, and i would like to do sorting of the elements node name. Please see the following figure which will make you clear what i am talking about. Please i want the code with JavaScript not Jquery.
Live Fiddle
UnSorted: Sorted:
bookstore bookstore
xbook abook
title author
author price
year title
price year
cbook cbook
gbook gbook
abook xbook
function generate(node) {
if (node.nodeType != 1) return "";
var html = "<li>" + node.nodeName;
var htmlForChildNodes = "";
for (var i = 0; i < node.childNodes.length; i++) {
htmlForChildNodes += generate(node.childNodes[i]);
}
if (htmlForChildNodes) {
html += "<ul>" + htmlForChildNodes + "</ul>";
}
html += "</li>";
return html;
}
Thank you
You should parse the xml document and put it into a javascript array of objects. Those are easily sortable.
So instead of calling generate(xmlDoc.documentElement) and have it return html straight from the xml you should add a parse(xmlDoc.documentElement) function that returns an array of arrays. After that change your generate() function to take an object with a children property that is an array instead of the original xml dom.
Parse should look something like this:
function parse(node) {
if (node.nodeType != 1) return null;
var result = {name: node.nodeName};
var children = [];
for (var i = 0; i < node.childNodes.length; i++) {
var child = parse(node.childNodes[i]);
if (child)
{
children.push(child);
}
}
result.children = children;
return result;
}
Now you can sort the arrays with children by creating your own comparator function.
After sorting you call the (changed) generate() function that will iterate over the objects/arrays to produce the html.
Here is a fiddle. I've left the changes of the generate() function to you. (Use your browser console to inspect the generated data structure)
I want to make a word bold in given paragraph. Here is a javascript code.
var hlWord = "idm";
var nregex = new RegExp(hlWord,"gi");
var div = document.getElementById("SR").innerHTML;
var rword = div.replace(nregex,"<b>"+hlWord+"</b>");
document.getElementById("SR").innerHTML = rword;
Here is a HTML code.
<div id="SR">
Download here free idm.
click here to download
</div>
This is work well and make all idm bold but here is a problem that it also change
url to like this
click here to download
This is not a valid url.This is the problem that this code make the url damaged.
Please tell me how can I avoid this.
Thanks...
You can iterate through all the text nodes with the methods in this thread, change them and replace them with new bold ones.
var hlWord = "idm";
var nregex = new RegExp(hlWord,"gi");
var sr = document.getElementById('SR');
function escape_html(html) {
return html.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}
(function findTextNodes(current) {
// make a shadow copy of the child nodes.
var current_children = Array.prototype.slice.call(current.childNodes);
for(var i = 0; i < current_children.length; i++) {
var child = current.childNodes[i];
// text node
if(child.nodeType == 3) {
var value = escape_html(child.nodeValue);
var html = value.replace(nregex, '<b>' + hlWord + '</b>');
if (html != value) {
var node = document.createElement('div');
node.innerHTML = html;
// make a shadow copy of the child nodes.
var childNodes = Array.prototype.slice.call(node.childNodes);
// replace the plain text node with the bold segments
for (var j = 0; j < childNodes.length; j++) {
var c = childNodes[j];
current.insertBefore(c, child);
}
current.removeChild(child);
}
}
else {
findTextNodes(child);
}
}
})(sr);
Check the code example at jsFiddle.
UPDATE:
Passerby pointed out that innerHTML should be used carefully. Escape text nodeValue before processing.
After some try-and-fail, I made a working demo that may be more complicated than you might have think:
http://jsfiddle.net/4VKNk/
var cache=[];
var reg=/idm/gi;
var id=function(ID){return document.getElementById(ID);}
function walkElement(ele){
if(ele.childNodes.length>0){
for(var i=0;i<ele.childNodes.length;i++){
walkElement(ele.childNodes[i]);
}
}else if(ele.nodeType==3){//text node
if(reg.test(ele.nodeValue)){
cache.push(ele);
}
}
}
id("test").onclick=function(){
cache=[];
walkElement(id("SR"));
while(cache.length>0){
var ele=cache.shift();
var val=ele.nodeValue;
var pnt=ele.parentNode;
var nextSibling=ele.nextSibling;
var i=0;
var r,tmp;
pnt.removeChild(ele);
while(r=reg.exec(val)){
tmp=document.createTextNode(val.substring(i,r.index));
if(nextSibling){
pnt.insertBefore(tmp,nextSibling);
tmp=document.createElement("strong");
tmp.appendChild(document.createTextNode("idm"));
pnt.insertBefore(tmp,nextSibling);
}else{
pnt.appendChild(tmp);
tmp=document.createElement("strong");
tmp.appendChild(document.createTextNode("idm"));
pnt.appendChild(tmp);
}
i=reg.lastIndex;
}
if(i<val.length-1){
tmp=document.createTextNode(val.substring(i,val.length));
if(nextSibling){
pnt.insertBefore(tmp,nextSibling);
}else{
pnt.appendChild(tmp);
}
}
}
};
I took the approach of DOM manipulation.
Explanation:
Walk through the whole DOM tree under target element, and cache all TEXT_NODE (nodeType==3);
Use RegExp.exec() method to get the index of each match;
While you find a match, add back the text that come before it, and then add a highlight element (<strong>) that contains the match; continue this step;
If we still have text left, add it back.
The reason I need to cache the TEXT_NODEs first, is that if we directly modify it in walkElement, it will change childNodes.length of its parent, and break the process.
From below HTML code I want to get all the text except that in 'text_exposed_hide' span elements.
Initially I tried to get the text from span with no class names.
But this method misses the text which is not within any span but just in div.
How can I get the required text. I need this code in pure javascript
<div id="id_4f1664f84649d2c59795040" class="text_exposed_root">
9jfasiklfsa
<span>CT:PFOUXHAOfuAI07mvPC/</span>
<span>NAg==$1ZUlmHC15dwJX8JNEzKxNDGGT</span>
dwL/L1ubjTndn89JL+M6z
<span class="text_exposed_hide">...</span>
<span class="text_exposed_show">
<span>MDmclkBPI/</span>
<span>s4B7R9hJyU9bE7zT10xkJ8vxIpo0quQ</span>
55
</span>
<span class="text_exposed_hide">
<span class="text_exposed_link">
<a onclick="CSS.addClass($("id_4f1664f84649d2c59795040"), "text_exposed");">See More</a>
</span>
</span>
</div
Edit :
I tried removing nodes with class name 'text_exposed_hidden' and then getting text from remaining nodes. Below is the code. But its not working
Control is not entering for loop. Even visibleDiv.removeChild(textExposedHideNodes[0]) is not working. I am running this in Chrome Browser 16.0
//msg is the parent node for the div
visibleDiv = msg.getElementsByClassName("text_exposed_root");
textExposedHideNodes = visibleDiv.getElementsByClassName("text_exposed_hide");
for(var n = 0;n < textExposedHideNodes.legth ; n++ ) {
console.log("Removing");
msg.removeChild(textExposedHideNodes[n]);
}
return visibleDiv.innerText;
This code will collect all text from text nodes who don't have a parent with the class="text_exposed_hide" and put the results in an array.
It does this non-destructively without removing anything:
function getTextFromChildren(parent, skipClass, results) {
var children = parent.childNodes, item;
var re = new RegExp("\\b" + skipClass + "\\b");
for (var i = 0, len = children.length; i < len; i++) {
item = children[i];
// if text node, collect its text
if (item.nodeType == 3) {
results.push(item.nodeValue);
} else if (!item.className || !item.className.match(re)) {
// if it doesn't have a className or it doesn't match
// what we're skipping, then recurse on it to collect from it's children
getTextFromChildren(item, skipClass, results);
}
}
}
var visibleDiv = document.getElementsByClassName("text_exposed_root");
var text = [];
getTextFromChildren(visibleDiv[0], "text_exposed_hide", text);
alert(text);
If you want all the text in one string, you can concatenate it together with:
text = text.join("");
You can see it work here: http://jsfiddle.net/jfriend00/VynKJ/
Here's how it works:
Create an array to put the results in
Find the root that we're going to start with
Call getTextFromChildren() on that root
Get the children objects of that root
Loop through the children
If we find a text node, collect its text into the results array
If we find an element node that either doesn't have a className or who's className doesn't match the one we're ignoring, then call getTextFromChildren() recursively with that element as the new root to gather all text from within that element
Is this what you're looking for?
/*Get Required Text
Desc: Return an array of the text contents of a container identified by the id param
Params:
id = Container DOM object id
*/
function getRequiredText(id)
{
/*Get container, declare child var and return array*/
var el = document.getElementById(id),
child,
rtn = Array();
/*Iterate through childNodes*/
for(var i = 0; i < el.childNodes.length; i++){
/*Define child*/
child = el.childNodes[i]
/*If node isn't #text and doesn't have hidden class*/
if(child.nodeName !="#text" && child.className != "text_exposed_hide")
rtn.push(child.textContent);
}
/*Return results*/
return rtn;
}
This will go through all childNodes, including nested childNodes and place all text into an array. if you want to exclude nested children replace the "if" statement with.
if(child.nodeName !="#text" && child.className != "text_exposed_hide" && child.parentNode == el)
Instead of removing the node, why not set its innertext/html to empty string:
//msg is the parent node for the div
visibleDiv = msg.getElementsByClassName("text_exposed_root");
textExposedHideNodes = visibleDiv.getElementsByClassName("text_exposed_hide");
for(var i = 0;i < textExposedHideNodes.legth ; i++ ) {
//store to temp for later use
textExposedHideNodes[i].txt = textExposedHideNodes[i].innerHTML;
textExposedHideNodes[i].innerHTML = '';
}
return visibleDiv.innerText;
I want to add an element to a textNode. For example: I have a function that search for a string within element's textNode. When I find it, I want to replace with a HTML element. Is there some standard for that?
Thank you.
You can't just replace the string, you'll have to replace the entire TextNode element, since TextNode elements can't contain child elements in the DOM.
So, when you find your text node, generate your replacement element, then replace the text node with a function similar to:
function ReplaceNode(textNode, eNode) {
var pNode = textNode.parentNode;
pNode.replaceChild(textNode, eNode);
}
For what it appears you want to do, you will have to break apart the current Text Node into two new Text Nodes and a new HTML element. Here's some sample code to point you hopefully in the right direction:
function DecorateText(str) {
var e = document.createElement("span");
e.style.color = "#ff0000";
e.appendChild(document.createTextNode(str));
return e;
}
function SearchAndReplaceElement(elem) {
for(var i = elem.childNodes.length; i--;) {
var childNode = elem.childNodes[i];
if(childNode.nodeType == 3) { // 3 => a Text Node
var strSrc = childNode.nodeValue; // for Text Nodes, the nodeValue property contains the text
var strSearch = "Special String";
var pos = strSrc.indexOf(strSearch);
if(pos >= 0) {
var fragment = document.createDocumentFragment();
if(pos > 0)
fragment.appendChild(document.createTextNode(strSrc.substr(0, pos)));
fragment.appendChild(DecorateText(strSearch));
if((pos + strSearch.length + 1) < strSrc.length)
fragment.appendChild(document.createTextNode(strSrc.substr(pos + strSearch.length + 1)));
elem.replaceChild(fragment, childNode);
}
}
}
}
Maybe jQuery would have made this easier, but it's good to understand why all of this stuff works the way it does.