How to search through every part of HTML - javascript

I'm trying to ignore any links in a part of HTML, and get anything that does not have a link to do my function.
What I have so far is:
$(document).ready(function() {
// search through paragraphs
$("p").each(function() {
// if there is not a link
if (!$(this).find('a').hasClass('external-link')) {
// do my function
}
})
})
My problem I am having, is that if there is a link in a line, but also something I want to capture in the same line it does not work, as it ignores the entire line.
Here is link to a working JSFiddle, which hopefully will let you see what I am trying to do.
Thank you in advance
Edit:
I may have worded the question slightly confusingly.
An example of what I am trying to achieve is:
<p>Link to ignore: news
Link to create: news </p>
My code would search through the <p> tags for "news", and then create a link to the website. However, I do not want to create a link on top of an existing link. My current code, would ignore everything within the <p> tags, because there is a link there already.

Here's one way to ignore the anchors so you don't create new anchors inside existing anchors.
This targets the textNodes only
$(document).ready(function () {
$("p").contents().each(function(_, node) {
if ( node.nodeType && node.nodeType === 3 ) {
var regex = /(news)/g;
var value = node.nodeValue.replace(regex, '$$&');
if (value.match(regex)) {
var wrap = document.createElement('span');
wrap.innerHTML = value
node.parentNode.insertBefore(wrap, node);
node.parentNode.removeChild(node);
}
}
});
});
FIDDLE
To keep the dollarsign, you have to do $$ as the dollarsign has special meaning in a regular expression.

I took a different approach and extended jQuery's function prototype -
$.fn.extend({
replace: function (options) {
var defaults = {
search: ''
};
options = $.extend(defaults, options);
return this.each(function () {
var string = $(this).html();
//var regex = /(search)/g;
var regex = /(^|\s)news/;
//var regex = new RegExp("(^|\s)" + options.search);
console.log(regex);
var replace_text = string.replace(regex, '$&');
$(this).html(replace_text);
});
}
});
$('p').replace({search: 'news'});
EXAMPLE
$('p').replace();
Changing the regex slightly to account for a space (instead of a greater than bracket) at the beginning of 'news' allows a single neat call to the extended function. Also updated to make the function more useful, allowing the user to pass arguments to the function. Still not perfect - a work in progress.

You could do this by looking at the childnodes of each p and grabbing the ones that do not have a class of external-link:
var otherText = [];
$("p").each(function(){
console.log(this.childNodes);
var kids = this.childNodes;
for(var i = 0; i < kids.length; i++)
{
if(!($(kids[i]).hasClass("external-link")))
{
otherText.push(kids[i]); //or do what you want with the node here
}
}
});
console.log("other Text", otherText);
jsFiddle

Related

How do I replace all instances of prices (beginning with "US$") in html page using jquery?

Say I have the following HTML:
<div class="L-shaped-icon-container">
<span class="L-shaped-icon">Something is US$50.25 and another thing is US$10.99.</span>
</div>
What I'd like to do is replace all instances of US$XX.xx with GBP£YY.yy on the live page using jquery.
The value of GBP would be determined by my own currency conversion ratio.
So I'm assuming what I'd first need to do is use a regular expression to get all instances of the prices which would be anything beginning with USD$ and ending after .xx? Prices will always have cents displayed.
Then I'm stuck what would be the best way to accomplish the next part.
Should I wrap these instances in a span tag with a class, then use jquery.each() function to loop through each and replace the contents with a jquery(this).html("GBP£YY.yy")?
Any help setting me on the right path would be greatly appreciated. Thanks guys.
base method for text replacements:
var textWalker = function (node, callback) {
var nodes = [node];
while (nodes.length > 0) {
node = nodes.shift();
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType === child.TEXT_NODE)
callback(child);
else
nodes.push(child);
}
}
};
stuff you need to do:
var USDinGBP = 0.640573954;
textWalker(document, function (n) {
n.nodeValue = n.nodeValue.replace(/(USD?\$(\d+(\.\d+)?))/g, function($0, $1, $2){
return "GBP£" + (parseFloat($2) * USDinGBP).toFixed(2);
})
})
you can fire that on ANY site. it will even replace titles etc.
so.. to tell you about the benefits of not using jquery for this:
jquery will process and wrap every single element in a browser compatible way.
using a native javascript solution would speed up this process alot.
using native textnodes also is benefitial since it will not break event handlers for child elements.
you should also consider using fastdom.
it does not matter if you are using jquery or native js. after writing to elements the dom has to do certain tasks before it can be read again. in the end you will loose some time for each edited element.
to give you a fastdom example:
var textWalker = function (node, callback) {
var nodes = [node];
while (nodes.length > 0) {
node = nodes.shift();
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType === child.TEXT_NODE)
callback(child);
else
nodes.push(child);
}
}
};
var textReplace = function (node, regex, callback) {
textWalker(node, function (n) {
fastdom.read(function () {
var text = n.nodeValue;
if (!regex.test(text)) {
return;
}
text = text.replace(regex, callback);
fastdom.write(function () {
n.nodeValue = text;
});
});
});
};
// put this function call into your onload function:
var USDinGBP = 0.640573954;
textReplace(document, /(USD?\$(\d+(\.\d+)?))/g, function($0, $1, $2){
return "GBP£" + (parseFloat($2) * USDinGBP).toFixed(2);
});
this will basically do the job in an instant.
if you want to go even further you could add this to jquery as following:
jQuery.fn.textReplace = function (regex, callback) {
this.each(function () {
textReplace(this, regex, callback);
});
};
and call it like that:
var USDinGBP = 0.640573954;
jQuery(".L-shaped-icon").textReplace(/(USD?\$(\d+(\.\d+)?))/g, function($0, $1, $2){
return "GBP£" + (parseFloat($2) * USDinGBP).toFixed(2);
});
If all of these values are directly in the span, if not you can give them a unique class and use it to iterate over them, you can use the following
You first get the numeric part of the string in a variable
convert the currency store it in other variable.
replace US$ with GBP
replace numeric part of the string with converted value
jQuery:
("span").each(function() {
var currencyVal=$(this).text().match(/\d/);
var convertedVal=currencyVal * 100; // just for example.
$(this).text($(this).text().replace(/^US$/,'GBP£'));
$(this).text($(this).text().replace(/\d/,convertedVal));
});
I hope this will helps you. Here is working Fiddle
HTML:
<div class="L-shaped-icon-container">
<span class="L-shaped-icon">Something is US$50.25 and another thing is US$10.99.</span>
<span class="L-shaped-icon">Something is BR$20.25 and another thing is US$10.99.</span>
<span class="L-shaped-icon">Something is US$10.25 and another thing is US$10.99.</span>
<span class="L-shaped-icon">Something is US$50.20 and another thing is GR$10.99.</span>
</div>
<button class="btnUpdate">Update</button>
JavaScript Code:
function UpdateCurrency(){
var UStoGB=10;
$('span').each(function(e){
var matchedText = $(this).text().match(/US\$\S+/g);
var updatedText = $(this).text();
if(matchedText){
for(var i=0;i<= matchedText.length;i++){
if(matchedText[i]){
var currentValue=matchedText[i].replace('US$','');
if(!currentValue) currentValue=0;
var newCurrency = ( parseFloat(currentValue) * UStoGB);
updatedText= updatedText.replace(matchedText[i],'GBP£'+newCurrency);
}
}
}
$(this).text(updatedText);
});
return false;
}

Difference in js native .value vs jQuery .val()

I have a quick question about the difference in JavaScript's native element.value vs jQuery's $(element).val();
I have created a BB editor in AngularJS and this is where my question came from.
Here is the code for my directive:
bbApp.directive('bbEdit', function() {
return {
restrict: 'A',
templateUrl: 'tpl/editor.html',
link: function(scope) {
scope.tagType = '';
var el = document.querySelector('#bbeditor');
scope.wrap = function(type) {
scope.tagType = type;
var str = el.value.toString();
var selection = str.slice(el.selectionStart, el.selectionEnd);
if( scope.noWrapTags.indexOf(scope.tagType) == -1 && selection == "" ) {
alert('Please select text to format');
return false;
}
if( scope.allowedTags.indexOf(scope.tagType) == -1 ) {
alert('Sorry, that formatting option is not available');
return false;
}
var strArr = str.split("");
if( scope.noWrapTags.indexOf(scope.tagType) != -1 ) {
strArr.splice(el.selectionStart,selection.length, "["+ scope.tagType +"]");
} else {
strArr.splice(el.selectionStart,selection.length, "["+ scope.tagType +"]"+selection+"[/"+ scope.tagType +"]");
}
el.value = strArr.join("");
}
}
}});
I am accessing the element like so: var el = document.querySelector('#bbeditor');
then getting the value: var str = el.value.toString();
But when I attempt to do this using jQuery's .val() it's not working properly.
The way it is currently written, the app will wrap whatever highlighted text in the appropriate custom bb tag.
But when I access the value of the textarea like this:
var el = angular.element(element).find('textarea');
var str = el.val().toString();
I get the text value but my string manipulation simply wraps the entire value of the textarea with the bb tag as opposed to wrapping just the text highlighted by the user.
Is it even worth the hassle to use jQuery for this? Is my use of document.QuerySelector() okay?
I was just wanting to use what features are available in Angular, and obviously in my directive I can access element. But the jQuery .val() is not working the same as native .value.
Any explanation/advice would be appreciated. I am new to Angular.
My app is working, but I am just wondering if there is a different way I should be doing this.
document.querySelector returns the first element that matches. jQuery's .find method returns an array of matching elements.
Besides that, .value isn't returning the selected text, you are using var selection = str.slice(el.selectionStart, el.selectionEnd) to get the selected text.
If your code works in javascript, I'd leave it as such. There isn't a compelling reason to convert it to jQuery (IMHO). You won't find anything that makes things overwhelmingly easier -- in this case.

Using JavaScript & jQuery in a single function (Nodes & Stuff)

I am currently learning jQuery. I know that jQuery is a custom library for JavaScript.
I am doing some learning examples in a book that is only using JavaScript, and to further my learning experience, I am trying to make use of jQuery for anything that might be more efficient.
So, I have this code:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
if (document.getElementById('currentWord').childNodes.length > 0) {
$('#currentWord p').append(letter);
} else {
var p = document.createElement('p');
var txt = document.createTextNode(letter);
p.appendChild(txt);
$('#currentWord').append(p);
}
}
Question #1:
If I change document.getElementById('currentWord').childNodes.length to $('#currentWord').childNodes.length it doesn't work. I thought the jQuery selector was the same thing as the JS document.getElementById as that it brought me back the DOM element. If that was the case, it'd make sense to be able to use the .childNodes.length functions on it; but it doesn't work. I guess it's not the same thing?
Question #2:
The code is textbook code. I have added all the jQuery that there is in it. My jQuery knowlede is limited, is there a more efficient way to execute the function?
The function's purpose:
This function is supposed to create a p element and fill it with a Text Node if it's the first time it's run. If the p element has already been created, it simply appends characters into it.
This is a word generating game, so you click on a letter and it gets added to a 'currentWord' div. The tile's letter is embedded in the 3rd css class, hence the attr splitting.
Thanks!
document.getElementById('currentWord')
returns a DOM object whereas $('#currentWord') returns a DOM object wrapped inside a jQuery object.
To get the plain DOM object you can do
$('#currentWord').get(0)
So
$('#currentWord').get(0).childNodes.length
should work.
Question #1:
jQuery returns a jQuery object. To return it to a regular javascript object use $(object)[0] and you can then treat it as a plain javascript (or DOM) object.
Question #2:
The efficiency looks good to me. Although you might want to use spans instead of p elements.
I guess one thing you could do (even though yours looks to run very fast) is cache the dom element:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
var currentWord = document.getElementById('currentWord');
if (currentWord.childNodes.length > 0) {
$(currentWord).find('p').append(letter);
} else {
var p = document.createElement('p');
p.innerHTML = letter;
currentWord.appendChild(p);
}
}
Calls to the jQuery() function ($()) return a jQuery object containing the matching elements, not the elements themselves.
Calling $('#some-id') will, then, return a jQuery object that contains the element that would be selected by doing document.getElementById('some-id'). In order to access that element directly, you can get it out of that jQuery object, using either the .get() function or an array index syntax: $('#some-id')[0] (it's 0-indexed).
I think you can replace all of this with a call to the text function.
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
var currentWordP = $('#currentWord p');
if (currentWordP.size() > 0) {
currentWordP.text(currentWordP.text() + letter);
} else {
$('#currentWord').append("<p>" + letter + "</p>");
}
}
1: Use $.get(0) or $[0] to get the DOM element. e.x. $('#currentWord')[0].childNodes.length.
2: Try this:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
if ($('#currentWord p').length > 0) {
$('#currentWord p').append(letter);
} else {
$('#currentWord').append(
$('<p />', { text: letter })
);
}
}
Question #1:
document.getElementById returns DOM object. more
childNodes.length is property of Node object which is returned by document.getElementById.
jQuery selector returns jQuery object more. You can get DOM object from jQuery object using .get
$('#IDselector').get(0) = document.getElementById('IDselector')
Question #2:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
if ($('currentWord p').length > 0) {
$('#currentWord p').append(letter);
} else {
var p = $('<p />').text(letter);
$('#currentWord').append(p);
}
}

how to replace all matching plain text strings in string using javascript (but not tags or attributes)?

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.

Splitting node content in JavaScript DOM

I have a scenario where I need to split a node up to a given ancestor, e.g.
<strong>hi there, how <em>are <span>you</span> doing</em> today?</strong>
needs to be split into:
<strong>hi there, how <em>are <span>y</span></em></strong>
and
<strong><em><span>ou</span> doing</em> today?</strong>
How would I go about doing this?
Here is a solution that will work for modern browsers using Range. Something similar could be done for IE < 9 using TextRange, but I use Linux so I don't have easy access to those browsers. I wasn't sure what you wanted the function to do, return the nodes or just do a replace inline. I just took a guess and did the replace inline.
function splitNode(node, offset, limit) {
var parent = limit.parentNode;
var parentOffset = getNodeIndex(parent, limit);
var doc = node.ownerDocument;
var leftRange = doc.createRange();
leftRange.setStart(parent, parentOffset);
leftRange.setEnd(node, offset);
var left = leftRange.extractContents();
parent.insertBefore(left, limit);
}
function getNodeIndex(parent, node) {
var index = parent.childNodes.length;
while (index--) {
if (node === parent.childNodes[index]) {
break;
}
}
return index;
}
Demo: jsbin
It expects a TextNode for node, although it will work with an Element; the offset will just function differently based on the behavior of Range.setStart
See the method Text.splitText.
Not sure if this helps you, but this is what I came up with...
Pass the function an element and a node tag name string you wish to move up to.
<strong>hi there, how <em>are <span id="span">you</span> doing</em> today?</strong>
<script type="text/javascript">
function findParentNode(element,tagName){
tagName = tagName.toUpperCase();
var parentNode = element.parentNode;
if (parentNode.tagName == tagName){
//Erase data up to and including the node name we passed
console.log('Removing node: '+parentNode.tagName+' DATA: '+parentNode.firstChild.data);
parentNode.firstChild.data = '';
return parentNode;
}
else{
console.log('Removing node: '+parentNode.tagName+' DATA: '+parentNode.firstChild.data);
//Erase the first child's data (the first text node and leave the other nodes intact)
parentNode.firstChild.data = '';
//Move up chain of parents to find the tag we want. Return the results so we can do things with it after
return findParentNode(parentNode, tagName)
}
}
var ourNode = document.getElementById("span");
alert(findParentNode(ourNode,'strong').innerHTML);
</script>

Categories

Resources