I am new to javascript but understand jQuery. I am trying to use this code to convert www. and http in p tags to working links.
Here is the code I am using, the problem is that I do not fully understand how the code works, could anybody please explain?
<script>
var re = /(http:\/\/[^ ]+)/g;
function createLinks(els) {
$(els).contents().each(function () {
if (this.nodeType === 1 && this.nodeName !== 'script') {
createLinks(this);
} else if (this.nodeType === 3 && this.data.match(re)) {
var markup = this.data.replace(re, '$1');
$(this).replaceWith(markup);
}
});
}
createLinks(document.body);
</script>
First, you set regular expression template for matching text which starts from "http://"
Second, you create recursive function which traverse whole html document.
nodeType == 1 means that current element is html tag (i.e. a, p, div etc)
nodeType == 2 means that element is Attribute
nodeType == 3 means that element is text node
So when you found html tag, you're searching inside it,
when you found text node, you are checking via regular expression, if this text starts from "http://", if so you change and replce this text to yourmatchedurl
in the end you call your function to start from body as a root
ok, here goes...
//create a regular expression to format the link
var re = /(http:\/\/[^ ]+)/g;
//this is the create links function which gets called below, "els" is the elements passed to the function (document.body)
function createLinks(els) {
//for each of the elements in the body
$(els).contents().each(function () {
//check if its an element type but not a script
if (this.nodeType === 1 && this.nodeName !== 'script') {
//call the create links function and send in this object
createLinks(this);
//if its not an element but is a text node and the format matches the regular expression
} else if (this.nodeType === 3 && this.data.match(re)) {
//create the markup
var markup = this.data.replace(re, '$1');
//finally, replace this link with the marked up link
$(this).replaceWith(markup);
}
});
}
//call the create links function
createLinks(document.body);
I hope the commented code helps you understand.
Related
I'm working on a DOM traversal type of script and I'm almost finished with it. However, there is one problem that I've encountered and for the life of me, I can't figure out what to do to fix it. Pardon my ineptitude, as I'm brand new to JS/JQuery and I'm still learning the ropes.
Basically, I'm using Javascript/JQuery to create an "outline", representing the structure of an HTML page, and appending the "outline" to the bottom of the webpage. For example, if the HTML is this...
<html>
<head>
</head>
<body>
<h1>Hello World</h1>
<script src=”http://code.jquery.com/jquery-2.1.0.min.js” type=”text/javascript”>
</script>
<script src=”outline.js” type=”text/javascript”></script>
</body>
</html>
Then the output should be an unordered list like this:
html
head
body
h1
text(Hello World)
script src(”http://code.jquery.com/jquery-2.1.0.min.js”) type(”text/javascript”)
script src(”outline.js”) type(”text/javascript”)
Here's what I've got so far:
var items=[];
$(document).ready(function(){
$("<ul id = 'list'></ul>").appendTo("body");
traverse(document, function (node) {
if(node.nodeName.indexOf("#") <= -1){
items.push("<ul>"+"<li>"+node.nodeName.toLowerCase());
}
else {
var x = "text("+node.nodeValue+")";
if(node.nodeValue == null) {
items.push("<li> document");
}
else if(/[a-z0-9]/i.test(node.nodeValue) && node.nodeValue != null) {
items.push("<ul><li>"+ x +"</ul>");
}
else {
items.push("</ul>");
}
}
});
$('#list').append(items.join(''));
});
function traverse(node, func) {
func(node);
node = node.firstChild;
while (node) {
traverse(node, func);
node = node.nextSibling;
}
}
It works almost perfectly, except it seems to read a carriage return as a text node. For example, if there's
<head><title>
it reads that properly, adding head as an unordered list element, and then creating a new "unordered list" for title, which is nested inside the header. HOWEVER, if it's
<head>
<title>
It makes the new unordered list and its element, "head", but then jumps to the else statement that does items.push(</ul>) . How do I get it to ignore the carriage return? I tried testing to see if the nodeValue was equal to the carriage return, \r, but that didn't seem to do the trick.
I'm having a bit of a hard time understanding exactly which text nodes you want to skip. If you just want to skip a text node that is only whitespace, you can do that like this:
var onlyWhitespaceRegex = /^\s*$/;
traverse(document, function (node) {
if (node.nodeType === 3 && onlyWhitespaceRegex.test(node.nodeValue) {
// skip text nodes that contain only whitespace
return;
}
else if (node.nodeName.indexOf("#") <= -1){
items.push("<ul>"+"<li>"+node.nodeName.toLowerCase());
} else ...
Or, maybe you just want to trim any multiple leading or trailing whitespaces off a text node before displaying it since it may not display in HTML.
var trimWhitespaceRegex = /^\s+|\s+$/g;
traverse(document, function (node) {
if(node.nodeName.indexOf("#") <= -1){
items.push("<ul>"+"<li>"+node.nodeName.toLowerCase());
} else {
var text = node.nodeValue;
if (node.nodeType === 3) {
text = text.replace(trimWhitespaceRegex, " ");
}
var x = "text("+text+")";
if(node.nodeValue == null) {
items.push("<li> document");
} ....
A further description of exactly what you're trying to achieve in the output for various forms of different text nodes would help us better understand your requirements.
I'm trying to replace all occurences of '$' (dollar sign) in a web page with another string.
The problem is that there may be some <script> tags that may contain the '$' (jQuery code) that I don't want to change.
For example:
document.body.innerHTML = document.body.innerHTML.replace(/\$/g, 'xxx'); seems to work, but also replaces '$' from any <script>$('...')...</script> parts.
Is this achievable?
Thank you in advance.
EDIT: I cannot modify the way the page is generated or change all other js parts - neither use some server-side logic. I can only add some custom js code
You can filter out the script tags
[].slice.call(document.body.children).forEach(function(element) {
if ( element.tagName.toLowerCase() != 'script' ) {
element.innerHTML = element.innerHTML.replace(/\$/g, 'xxx');
}
});
FIDDLE
This is not recursive, which means it only works for script tags directly under the body tag, not script tags that are nested deeper
function textNodes(main) {
var arr = [];
var loop = function(main) {
do {
if(main.hasChildNodes() && (["STYLE","SCRIPT"].indexOf(main.nodeName)==-1)){
loop(main.firstChild);
} else if(main.nodeType === 3) {
arr.push(main)
}
}
while (main = main.nextSibling);
}
loop(main);
return arr;
}
textNodes(document.body).forEach(function(a){a.textContent=a.textContent.replace(/\$/g,'€')});
Based on this DOM walking example
Say a web page has a string such as "I am a simple string" that I want to find. How would I go about this using JQuery?
jQuery has the contains method. Here's a snippet for you:
<script type="text/javascript">
$(function() {
var foundin = $('*:contains("I am a simple string")');
});
</script>
The selector above selects any element that contains the target string. The foundin will be a jQuery object that contains any matched element. See the API information at: https://api.jquery.com/contains-selector/
One thing to note with the '*' wildcard is that you'll get all elements, including your html an body elements, which you probably don't want. That's why most of the examples at jQuery and other places use $('div:contains("I am a simple string")')
Normally jQuery selectors do not search within the "text nodes" in the DOM. However if you use the .contents() function, text nodes will be included, then you can use the nodeType property to filter only the text nodes, and the nodeValue property to search the text string.
$('*', 'body')
.andSelf()
.contents()
.filter(function(){
return this.nodeType === 3;
})
.filter(function(){
// Only match when contains 'simple string' anywhere in the text
return this.nodeValue.indexOf('simple string') != -1;
})
.each(function(){
// Do something with this.nodeValue
});
This will select just the leaf elements that contain "I am a simple string".
$('*:contains("I am a simple string")').each(function(){
if($(this).children().length < 1)
$(this).css("border","solid 2px red") });
Paste the following into the address bar to test it.
javascript: $('*:contains("I am a simple string")').each(function(){ if($(this).children().length < 1) $(this).css("border","solid 2px red") }); return false;
If you want to grab just "I am a simple string". First wrap the text in an element like so.
$('*:contains("I am a simple string")').each(function(){
if($(this).children().length < 1)
$(this).html(
$(this).text().replace(
/"I am a simple string"/
,'<span containsStringImLookingFor="true">"I am a simple string"</span>'
)
)
});
and then do this.
$('*[containsStringImLookingFor]').css("border","solid 2px red");
If you just want the node closest to the text you're searching for, you could use this:
$('*:contains("my text"):last');
This will even work if your HTML looks like this:
<p> blah blah <strong>my <em>text</em></strong></p>
Using the above selector will find the <strong> tag, since that's the last tag which contains that entire string.
Take a look at highlight (jQuery plugin).
Just adding to Tony Miller's answer as this got me 90% towards what I was looking for but still didn't work. Adding .length > 0; to the end of his code got my script working.
$(function() {
var foundin = $('*:contains("I am a simple string")').length > 0;
});
this function should work. basically does a recursive lookup till we get a distinct list of leaf nodes.
function distinctNodes(search, element) {
var d, e, ef;
e = [];
ef = [];
if (element) {
d = $(":contains(\""+ search + "\"):not(script)", element);
}
else {
d = $(":contains(\""+ search + "\"):not(script)");
}
if (d.length == 1) {
e.push(d[0]);
}
else {
d.each(function () {
var i, r = distinctNodes(search, this);
if (r.length === 0) {
e.push(this);
}
else {
for (i = 0; i < r.length; ++i) {
e.push(r[i]);
}
}
});
}
$.each(e, function () {
for (var i = 0; i < ef.length; ++i) {
if (this === ef[i]) return;
}
ef.push(this);
});
return ef;
}
I am using ligatures.js to replace text within my site with the ligature of some character combinations. For instance, the 'fi' in 'five'.
Here is my example: http://jsfiddle.net/vinmassaro/GquVy/
When you run it, you can select the output text and see that the 'fi' in 'five' has become one character as intended. If you copy the link address and paste it, you will see that the href portion has been replaced as well:
/news/here-is-a-url-with-%EF%AC%81ve-ligature
This is unintended and breaks the link. How can I make the replacement on JUST the text of the link but not the href portion? I've tried using .text() and .not() with no luck. Thanks in advance.
I think you can solve it using the appropiate jQuery selectors
$('h3 a, h3:not(:has(a))')
.ligature('ffi', 'ffi')
.ligature('ffl', 'ffl')
.ligature('ff', 'ff')
.ligature('fi', 'fi')
.ligature('fl', 'fl');
See http://jsfiddle.net/GquVy/7/
You are applying the function to the whole heading's innerHTML, which includes the anchor's href attribute. This should work for your fiddle example:
$('h1 a, h2 a, h3 a, h4 a').ligature( //...
However, it will only work on links inside the headings, and I'm not sure that's what you're looking for. If you want something that works for any contents inside a certain element (with any level of tag nesting), then you'll need a recursive approach. Here is an idea, which is basically plain JavaScript since jQuery does not provide a way to target DOM text nodes:
$.fn.ligature = function(str, lig) {
return this.each(function() {
recursiveLigatures(this, lig);
});
function recursiveLigatures(el, lig) {
if(el.childNodes.length) {
for(var i=0, len=el.childNodes.length; i<len; i++) {
if(el.childNodes[i].childNodes.length > 0) {
recursiveLigatures(el.childNodes[i], lig);
} else {
el.childNodes[i].nodeValue = htmlDecode(el.childNodes[i].nodeValue.replace(new RegExp(str, 'g'), lig));
}
}
} else {
el.nodeValue = htmlDecode(el.nodeValue.replace(new RegExp(str, 'g'), lig));
}
}
// http://stackoverflow.com/a/1912522/825789
function htmlDecode(input){
var e = document.createElement('div');
e.innerHTML = input;
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
};
// call this from the document.ready handler
$(function(){
$('h3').ligature('ffi', 'ffi')
.ligature('ffl', 'ffl')
.ligature('ff', 'ff')
.ligature('fi', 'fi')
.ligature('fl', 'fl');
});
That should work on contents like this:
<h3>
mixed ffi content
<span>this is another tag ffi <span>(and this is nested ffi</span></span>
Here is a ffi ligature
</h3>
http://jsfiddle.net/JjLZR/
This is my jquery script that replace string to new string:
$("*").contents().each(function() {
if(this.nodeType == 3)
this.nodeValue = this.nodeValue.replace("1.(800).123.1234", "new");
});
working example : http://jsfiddle.net/webdesignerart/eKRGT/
but i want to add before and after to string html element like new
when i do this :
this.nodeValue = this.nodeValue.replace("1.(800).123.1234", "<b>new</b>");
The Result comes:
<b>new</b>
I want output this: new
i want to allow html tags during replacement.
is jquery .append work with this.
You are replacing the contents of a TextNode element which is always just text. To make the text bold, you will need to create another element, b which wraps around the TextNode. One approach is to use the wrap() from jQuery:
$("*").contents().each(function() {
var me = this;
if(this.nodeType == 3)
this.nodeValue = this.nodeValue.replace("1.(800).123.1234", function(a){
$(me).wrap('<b />');
return "new";
});
});
example: http://jsfiddle.net/niklasvh/eLkZp/
This should work:
$("*").contents().each(function() {
var me = this;
if(this.nodeType == 3
&& this.nodeValue.indexOf("1.(800).123.1234")>-1){
$(this).replaceWith(this.nodeValue.replace("1.(800).123.1234", "<b>new</b>"));
}
});
http://jsfiddle.net/eLkZp/20/
Basically replace the text node rather than just the text within it.
You should really consider if there's some way to narrow down that original filter though. Parsing your entire page is generally a bad idea.