How to select this text using pure javascript, no jquery? - javascript

I need to select some text using javascript (no jquery), and Im not sure how to do it. I need to select the text ('Hello World') in myClass element before the first child element. Here are two examples, sometimes it will be an <img> tag as the first element and sometimes it will be a <br> tag. I also need to select the text between the <br> tags.
<p class="myClass" style="padding: 5px;">
Hello World
<img src="http://xyz.com/image.gif">
<br>
30-12-2011 19:45
<br>
Testing
<br>
</p>
<p class="myClass" style="padding: 5px;">
Hello World
<br>
30-12-2011 19:45
<br>
Testing
<br>
</p>
Edit note: I also need to select the other text nodes in myClass. And note that sometimes there will be an <img> as one of the child elements and sometimes there won't.
So I would like to end up with
var a = 'Hello World'
var b = '30-12-2011 19:45'
var c = 'Testing'
Anyone able to do this?

Not really sure what you want. If you want to get all the text, then use:
var text = element.innerText || element.textContent;
If you want to get the text in several pieces, then you have to iterate over all child nodes and extract the text nodes:
var texts = [],
children = element.childNodes;
for(var i = 0, len = children.length; i < len; i++) {
var node = children[i];
if(node.nodeType === 3) {
var text = node.nodeValue.replace(/^\s+|\s+$/g, '');
if(text.length > 0) {
texts.push(text);
}
}
}
This would not concatenate consecutive text nodes though, which can occur if you inserted new text nodes or split text nodes with JavaScript. In this case, you could do this:
var texts = [''],
children = element.childNodes,
new_bucket = false,
bucket = 0;
for(var i = 0, len = children.length; i < len; i++) {
var node = children[i];
if(node.nodeType === 1 && new_bucket) {
new_bucket = false;
texts[++bucket] = '';
}
else if(node.nodeType === 3) {
var text = node.nodeValue.replace(/^\s+|\s+$/g, '');
if(text.length > 0) {
new_bucket = true;
texts[bucket] += text
}
}
}

var elem = document.getElementsByClassname("myClass")[0],
txtNode = elem.childNodes[0],
txtNodeValue = txtNode.nodeValue;
console.log(txtNodeValue);
Example.
We select the element by its class ([0] just tells it to select the first element in the matched set) and then look for its first child node. In your case this would be the text node, so all we have to do from there is get the nodeValue.

This will get the first element with the class myClass and the first text node of that element:
document.getElementsByClassName("myClass")[0].childNodes[0].nodeValue
Here is a great article explaining.
You will most likely want to trim the string as well to remove white-space. You can add the following to give you trim functionality:
if(typeof String.prototype.trim !== 'function') {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
}
}
...and use it as such:
var trimmedString = document.getElementsByClassName("myClass")[0].childNodes[0].nodeValue.trim();

http://jsfiddle.net/kgdnR/1/
var abc = [].filter.call((document.getElementsByClassName("myClass")[0].childNodes), function(node) {
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length) {
return true;
}
return false;
}).map(function(node) {
return node.nodeValue.trim();
});
var a = abc[0],
b = abc[1],
c = abc[2];
console.log(a, b, c);
Can be made work in IE but if you wanted that you might as well use jQuery.

I dont know how regular your example is but you can use the document object to interact with the DOM.
select the myClass elements
var elements = document.getElementsByClassName('myClass');
then take the first index , its childNodes and the first index of that which is a text node
var nodes = elements[0].childNodes;
for (var i = 0; i < nodes.length; i++)
{
var node = nodes[i],
text;
// loop over the nodes and see if its as textnode. Then trim and skip empty strings
if(node.nodeType === 3) {
text = node.textContent.trim();
if(text) {
alert(text);
}
}
}

first of all, you can use
document.getElementsByClassName('test')
to select the elements using the class name.
select the element you want (it returns an array), and then use
nodeValue
which will return the text.

With using XPath expression:
var contextNode = document.body;
var node, textNodes = [];
var search = document.evaluate('.//text()', contextNode, null, XPathResult.ANY_TYPE);
while(node = search.iterateNext()) textNodes.push(node);

Selecting text as asked by the OP:
var my_class = document.getElementsByClassName('myClass')[0];
window.getSelection().selectAllChildren(my_class);

Related

Make a text Highlight using javascript

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.

how to get first text node while bypassing <b> and <i>?

I want to get the first text node from a string but it may contain few tags like <b>,<i> and <span>. I have tried it like this but it gives only login whereas it should give login<b>user</b> account
var s = $.trim('login<b>user</b> account<tbody> <tr> <td class="translated">Lorem ipsum dummy text</td></tr><tr><td class="translated">This is a new paragraph</td></tr><tr><td class="translated"><b>Email</b></td></tr><tr><td><i>This is yet another text</i></td> </tr></tbody>');
if( $(s).find('*').andSelf().not('b,i').length > 1 ) {
if( s.substring( 0, s.indexOf('<') ) != '') {
alert(s.substring(0, s.indexOf('<')));
} else {
alert($(s).find('*:not(:empty)').first().text());
}
}
check it on jsfiddle
Note:
This string will be dynamic, so write generic answer not specific to this text only.
More Information :
#Jeremy J Starcher! I just want to get the first non-empty text node of iframe being clicked. This node will include <b> or <i> and whatever is in between them like this:
hi my <b>bold</b> text is here // note the bold tags as it is
If only one element is clicked then its text is thrown but if there are more than then one elements selected then it must get the very first text node among all the nodes.
Kind of a brute method but you can take out all the tags you're expecting to occur in the first node and read until the start of the next tag like so:
text = text.replace("<b>"," ");
text = text.replace("</b>"," ");
text = text.replace("<i>"," ");
text = text.replace("</i>"," ");
text = text.replace("<span>"," ");
text = text.replace("</span>"," ");
text = text.substr(0, text.indexOf("<"));
I didn't fully follow the question, but if you are trying to extract the text from a DOM element, this may help:
var getText = function (el) {
var ret;
var txt = [],
i = 0;
if (!el) {
ret = "";
} else if (el.nodeType === 3) {
// No problem if it's a text node
ret = el.nodeValue;
} else {
// If there is more to it, then let's gather it all.
while (el.childNodes[i]) {
txt[txt.length] = getText(el.childNodes[i]);
i++;
}
// return the array as a string
ret = txt.join("");
}
return ret;
};

getting text from selective nodes in javascript

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;

JavaScript Node.replaceChild() doesn't count new child's innerHtml

While creating a Firefox addon, I've run into a weird problem.
I have an array of nodes, returned by some iterator. Iterator returns only nodes, containing Node.TEXT_NODE as one or more of it's children. The script runs on page load.
I have to find some text in that nodes by regexp and surround it with a SPAN tag.
//beginning skipped
var node = nodeList[i];
var node_html = node.innerHTML;
var node_content = node.textContent;
if(node_content.length > 1){
var new_str = "<SPAN class='bar'>" + foo + "</SPAN>";
var regexp = new RegExp( foo , 'g' );
node_html = node_html.replace(regexp, new_str);
node.innerHTML = node_html;
}
Basic version looked like this, and it worked except one issue - node.innerHTML could contain attributes, event handlers, that could also contain foo, that should not be surrounded with <span> tags.
So I decided to make replacements in text nodes only. But text nodes can't contain a HTML tag, so I had to wrap them with <div>. Like this:
var node = nodeList[i];
for(var j=0; j<node.childNodes.length; j++){
var child = node.childNodes[j];
var child_content = child.textContent;
if(child.nodeType == Node.TEXT_NODE && child_content.length >1){
var newChild = document.createElement('div');
// var newTextNode = document.createTextNode(child_content);
// newChild.appendChild(newTextNode);
var new_html = child_content;
var new_str = "<SPAN class='bar'>" + foo + "</SPAN>";
var regexp = new RegExp( foo , 'g' );
new_html = new_html.replace(regexp, new_str);
newChild.innerHTML = new_html;
alert(newChild.innerHTML);
node.replaceChild(newChild, child);
}
}
In this case, alert(newChild.innerHTML); shows right html. But after the page is rendered, all <div>s created are empty! I'm puzzled.
If I uncomment this code:
// var newTextNode = document.createTextNode(child_content);
// newChild.appendChild(newTextNode);
alert also shows things right, and <div>s are filled with text (textNode adding works ok) , but again without <span>s. And another funny thing is that I can't highlight that new <div>s' content with a mouse in browser.
Looks like it doesn't take new innerHTML into account, and I can't understand why.
Do I do something wrong? (I certainly do, but what? Or, is that a FF bug/feature?)
Since you are in Firefox you can use fun stuff like TreeWalker and Range. You may even be able to get rid of the code that gives you the initial array of nodes.
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
var range = document.createRange();
var wrapper = document.createElement('span');
wrapper.className = "wrapper";
var node;
var re = /^wrap me$/;
while (node = walker.nextNode()) {
if (re.test(node.textContent)) {
range.selectNode(node);
range.surroundContents(wrapper.cloneNode(true));
}
}
JSBin
You could tweak this so only part of the text node is wrapped by setting the range differently and TreeWalker can be filtered more.
Range / TreeWalker
That code is really odd; why are those three lines outside of the if statement?
I think it should look something like this:
var node = nodeList[i];
for(var j=0; j<node.childNodes.length; j++){
var child = node.childNodes[j];
var child_content = child.textContent;
if(child.nodeType == Node.TEXT_NODE && child_content.length >1){
var newChild = document.createElement('div');
newChild.innerHTML = '<span class="bar">' + child_content + '</span>';
node.replaceChild(newChild, child);
}
Now I can't figure out what was going on with that regex and the replacement stuff; it makes no sense to me in the code you've posted.

JavaScript: Add elements in textNode

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.

Categories

Resources