I m new to javascript and would be great if you could help me with this issue I m facing.
In the HTML code below, I m trying to highlight a particular word(say "message")by replacing the word by appending to it to make it bold.
<body>
<img src="http://some.web.site/image.jpg" title="abcd" />
This is a test message.
</body>
The String "This is a test message" is found directly in the body element(so there is no id, and hence no getElementById can be used to extract the text).
So I got the entire body element and extracted the text by using textContent.(this gave me the text and ignored the image that is above the text in the html.)
Then after highlighting the word I set the 'body's textContent back to the new String.
The problem is that now I m not able to preserve the image that was above the text, and the new body has only the value of the textContent in it, and the image is lost. This is the JS I used(but now I m replacing message with the word phone).
<script>
function myFunction(){
var x = document.getElementsByTagName('body')[0].textContent;
var v = x.replace("message","phone");
document.getElementsByTagName('body')[0].textContent = v;
}
</script>
Is there any other way to replace text that is placed directly under the body, which has other elements too?
In order to do something like this, you would need to loop through all text nodes in the document, search for the word, and wrap it.
Something like this:
function highlight(str,node) {
if( !node) node = document.body;
var c = node.children, l = c.length, i, p;
for( i=0; i<l; i++) {
if( c[i].nodeType == 1) highlight(str,c[i]);
if( c[i].nodeType == 3) {
if( (p=c[i].data.indexOf(str)) > -1) {
c[i].splitText(p);
c[i].nextSibling.deleteData(0,str.length);
c[i].parentNode.insertBefore(
document.createElement('b'),c[i].nextSibling
).appendChild(document.createTextNode(str));
}
}
}
}
That should do it. To use, just call highlight("message"), with whatever text you want. Note that this will be case-sensitive - if you need caseless matching, let me know and I'll edit to take that into account (although for the most part you could probably get away with highlight("message"); highlight("Message");)
Also, you can limit the search to a particular element. Let's say you have <div id="replace-me">, you can limit the search to that element like so:
highlight("message",document.getElementById('replace-me'));
(You can use any way to get the node, this is just the easiest)
The textContent property sets or returns the textual content of the specified node, and all its descendants.
If you set the textContent proerty, any child nodes are removed and replaced by a single Text node containing the specified string.
refer to here
Try
var children = document.body.childNodes;
for(var len = children.length, child=0; child<len; child++){
if (children[child].nodeType === 3){ // textnode
var contents = children[child].nodeValue;
children[child].nodeValue = contents.replace(/message/gi, 'phone');
}
}
Related
I'm trying to write a jQuery or pure Javascript function (preferring the more readable solution) that can count the length of a starting tag or ending tag in an HTML document.
For example,
<p>Hello.</p>
would return 3 and 4 for the starting and ending tag lengths. Adding attributes,
<span class="red">Warning!</span>
would return 18 and 7 for the starting and ending tag lengths. Finally,
<img src="foobar.png"/>
would return 23 and 0 (or -1) for the starting and ending tag lengths.
I'm looking for a canonical, guaranteed-to-work-according-to-spec solution, so I'm trying to use DOM methods rather than manual text manipulations. For example, I would like the solution to work even for weird cases like
<p>spaces infiltrating the ending tag</ p >
and
<img alt="unended singleton tags" src="foobar.png">
and such. That is, my hope is that as long as we use proper DOM methods, we should be able to find the number of characters between < and > no matter how weird things get, even
<div data-tag="<div>">HTML-like strings within attributes</div>
I have looked at the jQuery API (especially the Manipulation section, including DOM Insertion and General Attributes subsections), but I don't see anything that would help.
Currently the best idea I have, given an element node is
lengthOfEndTag = node.tagName.length + 3;
lengthOfStartTag = node.outerHTML.length
- node.innerHTML.length
- lengthOfEndTag;
but of course I don't want to make such an assumption for the end tag.
(Finally, I'm familiar with regular expressions—but trying to avoid them if at all possible.)
EDIT
#Pointy and #squint helped me understand that it's not possible to see </ p >, for example, because the HTML is discarded once the DOM is created. That's fine. The objective, adjusted, is to find the length of the start and end tags as would be rendered in outerHTML.
An alternate way to do this could be to use XMLSerializer's serializeToString on a clone copy of the node (with id set) to avoid having to parse innerHTML, then split over "><"
var tags = (function () {
var x = new XMLSerializer(); // scope this so it doesn't need to be remade
return function tags(elm) {
var s, a, id, n, o = {open: null, close: null}; // spell stuff with var
if (elm.nodeType !== 1) throw new TypeError('Expected HTMLElement');
n = elm.cloneNode(); // clone to get rid of innerHTML
id = elm.getAttribute('id'); // re-apply id for clone
if (id !== null) n.setAttribute('id', id); // if it was set
s = x.serializeToString(n); // serialise
a = s.split('><');
if (a.length > 1) { // has close tag
o.close = '<' + a.pop();
o.open = a.join('><') + '>'; // join "just in case"
}
else o.open = a[0]; // no close tag
return o;
}
}()); // self invoke to init
After running this, you can access .length of open and close properties
tags(document.body); // {open: "<body class="question-page">", close: "</body>"}
What if an attribute's value has >< in it? XMLSerializer escapes this to >< so it won't change the .split.
What about no close tag? close will be null.
This answer helped me understand what #Pointy and #squint were trying to say.
The following solution works for me:
$.fn.lengthOfStartTag = function () {
var node = this[0];
if (!node || node.nodeType != 1) {
$.error("Called $.fn.lengthOfStartTag on non-element node.");
}
if (!$(node).is(":empty")) {
return node.outerHTML.indexOf(node.innerHTML);
}
return node.outerHTML.length;
}
$.fn.lengthOfEndTag = function () {
var node = this[0];
if (!node || node.nodeType != 1) {
$.error("Called $.fn.lengthOfEndTag on non-element node.");
}
if (!$(node).is(":empty")) {
var indexOfInnerHTML = node.outerHTML.indexOf(node.innerHTML);
return node.outerHTML.length - (indexOfInnerHTML + node.innerHTML.length);
}
return -1;
}
Sample jsFiddle here.
So, I have some JavaScript code which I test with Greasemonkey locally. But I get this persistent error in the Firefox Error Console:
catChildNotes[y].setAttribute is not an function
Code:
var i = prompt("How many videos have you got?", "");
function remove_mp4()
{
titleElems=document.getElementsByName("title");
for(i=0; i<titleElems.length; i++)
{
titleInner=titleElems[i].innerHTML;
titleElems[i].innerHTML=titleInner.replace(titleInner.match(".mp4"), "");
}
}
for (var x = 0; x < i; i++)
{
document.getElementsByName("description")[x].value = "Visit me on my web-site :\
\
http://www.sample.com/";
document.getElementsByName("keywords")[x].value = prompt("Enter keywords : ","");
catChildNodes=document.getElementsByName("category")[x].childNodes;
catChildNodes[x + 1].removeAttribute("selected");
for(y=0; y<catChildNodes.length; y++)
{
if(catChildNodes[y].value="27")
{
catChildNodes[y].setAttribute("selected","");
}
}
}
remove_mp4();
This script should be run on Youtube upload page and do the following :
Remove ".mp4" from the title
Add default description
Add keywords (which are equal to the prompt value)
Change category to "Education"
Typically, when you get the child nodes of an element you will get other element nodes and text nodes. The former have a setAttribute method, the latter don't (simply because text nodes don't have any attributes). If you need only the element children and no the text nodes then you should use children property instead of childNodes.
There is at least one more bug in your code, this is not a comparison:
if (catChildNodes[y].value = "27")
This will assign the value 27 to catChildNodes[y].value. If you actually want to compare then you should use the comparison operator:
if (catChildNodes[y].value == "27")
imagine this html on a page
<div id="hpl_content_wrap">
<p class="foobar">this is one word and then another word comes in foobar and then more words and then foobar again.</p>
<p>this is a link with foobar in an attribute but only the foobar inside of the link should be replaced.</p>
</div>
using javascript, how to change all 'foobar' words to 'herpderp' without changing any inside of html tags?
ie. only plain text should be changed.
so the successful html changed will be
<div id="hpl_content_wrap">
<p class="foobar">this is one word and then another word comes in herpderp and then more words and then herpderp again.</p>
<p>this is a link with herpderp in an attribute but only the herpderp inside of the link should be replaced. </p>
</div>
Here is what you need to do...
Get a reference to a bunch of elements.
Recursively walk the children, replacing text in text nodes only.
Sorry for the delay, I was sidetracked before I could add the code.
var replaceText = function me(parentNode, find, replace) {
var children = parentNode.childNodes;
for (var i = 0, length = children.length; i < length; i++) {
if (children[i].nodeType == 1) {
me(children[i], find, replace);
} else if (children[i].nodeType == 3) {
children[i].data = children[i].data.replace(find, replace);
}
}
return parentNode;
}
replaceText(document.body, /foobar/g, "herpderp");
jsFiddle.
It's a simple matter of:
identifying all text nodes in the DOM tree,
then replacing all foobar strings in them.
Here's the full code:
// from: https://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery
var getTextNodesIn = function (el) {
return $(el).find(":not(iframe)").andSelf().contents().filter(function() {
return this.nodeType == 3;
});
};
var replaceAllText = function (pattern, replacement, root) {
var nodes = getTextNodesIn(root || $('body'))
var re = new RegExp(pattern, 'g')
nodes.each(function (i, e) {
if (e.textContent && e.textContent.indexOf(pattern) != -1) {
e.textContent = e.textContent.replace(re, replacement);
}
});
};
// replace all text nodes in document's body
replaceAllText('foobar', 'herpderp');
// replace all text nodes under element with ID 'someRootElement'
replaceAllText('foobar', 'herpderp', $('#someRootElement'));
Note that I do a precheck on foobar to avoid processing crazy long strings with a regexp. May or may not be a good idea.
If you do not want to use jQuery, but only pure JavaScript, follow the link in the code snippet ( How do I select text nodes with jQuery? ) where you'll also find a JS only version to fetch nodes. You'd then simply iterate over the returned elements in a similar fashion.
(HTML) Let's say I have within fixed-width containers. Some of these headings will be longer than one line. I would like isolate these lines and do separate things to each line. Is there a way, with JavaScript, to calculate where the heading has broken to the next line and to for example put a span around each line?
Hacky and dirty: render the same string in the same container char-by-char until it doesn't fit, then take this string and wrap it in a <span>, repeat.
$(function(){
$h = $('.fixed').find('h3');
$h.each(function(i,e){
var txt = $(e).text();
$th = $('<h3 />').prependTo($(e).parent());
var lh = $(e).text('X').height();
$(e).text('');
while (txt.length > 0) {
$th.text($th.text() + txt[0]);
txt = txt.slice(1);
if (($th.height() > lh) || (txt.length <= 0)) {
var shc = $th.text().split(' ');
var ph = shc.slice(0,-1).join(' ')+' ';
if (txt.length <= 0) { ph += shc.pop(); }
$('<span />').text(ph).appendTo($(e));
$th.text(shc.pop());
}
}
$th.remove();
})
});
There are some limitations in usage. For example, headings aren't expected to contain anything but plain text - any HTML tags will be stripped off.
And there is, of course, fiddle for this.
I've written a slightly hacky custom selector. This selector clones the object, then gives the clone the HTML A. It then compares heights of the original and the clone. If they are the same or the copy is bigger, the selector fails. If the copy is smaller than the original (i.e. we presume the line has wrapped), the selector passes:
$.expr[':'].multiLine = function(obj) {
var $this = $(obj),
copy = $this.clone().html('A').insertAfter($this)
ret;
ret = (copy.height() < $this.height());
copy.remove();
return ret;
};
You can call this like this:
$('h1:multiLine')
NB that you should not wrap h* elements in span tags, because inline elements (e.g. span) may not contain block-level elements (e.g. h1).
Edit Note also that this may not work if you have images in your header, or if you change the height of any text within the header. It's probably best not to rely on this kind of detection -- use it for enhancement only.
It's probably something really simple, but I'm just learning.
There's a page with 3 blockquote tags on it, and I'd need to get the innerHTML of the one containing a certain string. I don't know how to search/match a string and get the innerHTML of the tag containing the matched result.
Any help would be appreciated!
var searchString = 'The stuff in innerHTML';
var elements = document.getElementsByTagName('blockquote')
for (var i = 0; i < elements.length; i++) {
if (elements[i].innerHTML.indexOf(searchString) !== -1) {
alert('Match');
break;
}
}
:)
Btw there would be a much nicer method if you'd be using Prorotype JS (which is much better than jQuery btw):
var el = $$('blockquote').find(function(el) {
return el.innerHTML.indexOf('The string you are looking for.') !== -1;
});
You could of course also use regular expressions to find the string, which might be more useful (use el.match() for that).
If you need to search through every <blockquote> on the page, try this:
function findBlockquoteContainingHtml(matchString) {
var blockquoteElements = document.getElementsByTagName('BLOCKQUOTE');
var i;
for (i = 0; i < blockquoteElements.length; i++) {
if (blockquoteElements[i].innerHTML.indexOf(matchString) >= 0) {
return blockquoteElements[i].innerHTML;
}
}
return null;
}
Assign an id to the blockquote elements then you can get the innerHTML like this:
HTML:
<blockquote id="bq1">Foo</blockquote>
JS:
var quote1 = document.getElementById('bq1').innerHTML;
Be careful using innerHTML to search for text within a tag, as that may also search for text in attributes or tags as well.
You can find all blockquote elements using:
var elems = document.getElementsByTagName("blockquote")
You can then look through their innerHTML, but I would recommend instead looking through their textContent/innerText (sadly, this is not standardized across browser, it seems):
for (i in elems) {
var text = elems[i].textContent || elems[i].innerText;
if (text.match(/foo/)) {
alert(elems[i].innerHTML);
}
}