I'm working on a simple browser plug in to replace 1 word with another but with the addition of an anchor for a small tooltip/pop up. I have this actually working already but my problem is, if the word to be replaced is within an anchor already, then my </a> closes the already open <a>.
So I need some help with how to only replace a word, as long as its not within an open anchor tag. A follow up to this is to also make sure the target word isn't in an <input> or other inappropriate tags.
My code is:
var regex = new RegExp("\\b" + nativeWord + "\\b", "igm");
var body = $('body');
body.each(function() {
$(this).html(
$(this).html().replace(regex, function() {
counter++;
if(counter > wordSwapLimit) {
return nativeWord;
} else {
return "<a class=\"tooltip\" href=\"#\">" + studyWord + "<span class=\"classic\">" + nativeWord + "</span></a>";
}
})
);
});
I'm suspecting that I might need to write a more complex RegExp but this is my first outing with it so any suggestions would be greatly appreciated!
Update
So I have a test page I work with to see how my code works.
This is part of the original HTML from the page:
<p id="changeArea"> I love chicken but I hate beef. </p>
But with the code shown above and swapping 'chicken' for 'kip', this gets changed to:
<p id="changeArea"> I kip<span class="classic">chicken</span>.com">love <a class="tooltip" href="#">kip<span class="classic">chicken</span></a> but I hate beef. </p>
If I have no anchors around what I am swapping, then it works perfectly and I get a nice rollover tooltip.
Thanks again for your help!
Update 2
As requested, here are 'unprocessed' examples that my plugin might come across when swapping 'chicken' for 'kip':
<p id="changeArea"> I love chicken but I hate beef. </p>
<p id="changeArea2"> Chickenpie? pie-chicken. </p>
What I'm hoping for is the following:
<p id="changeArea"> I love chicken but I hate beef. </p>
<p id="changeArea2"> Chickenpie? pie-<a class="tooltip" href="#">kip<span class="classic">chicken</span></a>. </p>
As you can see in the first line, the html code is left alone, as is text for the word, because it is within a URL and so would break it if my tooltip anchor was put in.
The second line ignores 'chickenpie' because I only want whole word swapping, as per my existing regexp. 'pie-chicken' does get swapped though. This line is processed correctly with existing code.
I'm literally just looking to add additional rules to my existing code that say 'dont replace code and dont replace if within an anchor open tag.
Since you're in JavaScript, you already have access to the DOM. Why would you try using Regexes???
Just iterate through text nodes, ignoring existing links:
(function(elem) {
var recurse = arguments.callee, nodes = elem.childNodes, l = nodes.length, i, t,
getLink = function() {
var a = document.createElement('a'), s = document.createElement('span');
a.href = "#";
a.className = "tooltip";
a.appendChild(document.createTextNode(studyWord));
s.className = "classic";
s.appendChild(document.createTextNode(nativeWord));
a.appendChild(s);
return a;
};
for( i=0; i<l; i++) {
switch(nodes[i].nodeType) {
case 1:
// element - recurse, but not if already a link
if( nodes[i].nodeName != "A") recurse(nodes[i]);
break;
case 3:
// text node, look for keyword
t = nodes[i].nodeValue.search(new RegExp("\\b"+nativeWord+"\\b","i"));
if( t > -1) {
// found it! now to hack around...
t = nodes[i].splitText(t);
t.splitText(nativeWord.length);
t.parentNode.replaceChild(t,getLink());
}
break;
}
}
})(document.body);
Please note: This approach uses Vanilla JS. It is up to you to ensure it is run after the page is loaded. This may be done either by:
Adding the defer attribute to the script, if it is external
Putting the script right before the </body> (or at least after all the content you wish to affect)
Wrapping it in a window.onload handler, or even $(function() {...}) since you're already using jQuery.
Related
I searched through a bunch of related questions that help with replacing site innerHTML using JavaScript, but most reply on targetting the ID or Class of the text. However, my can be either inside a span or td tag, possibly elsewhere. I finally was able to gather a few resources to make the following code work:
$("body").children().each(function() {
$(this).html($(this).html().replace(/\$/g,"%"));
});
The problem with the above code is that I randomly see some code artifacts or other issues on the loaded page. I think it has something to do with there being multiple "$" part of the website code and the above script is converting it to %, hence breaking things.using JavaScript or Jquery
Is there any way to modify the code (JavaScript/jQuery) so that it does not affect code elements and only replaces the visible text (i.e. >Here<)?
Thanks!
---Edit---
It looks like the reason I'm getting a conflict with some other code is that of this error "Uncaught TypeError: Cannot read property 'innerText' of undefined". So I'm guessing there are some elements that don't have innerText (even though they don't meet the regex criteria) and it breaks other inline script code.
Is there anything I can add or modify the code with to not try the .replace if it doesn't meet the regex expression or to not replace if it's undefined?
Wholesale regex modifications to the DOM are a little dangerous; it's best to limit your work to only the DOM nodes you're certain you need to check. In this case, you want text nodes only (the visible parts of the document.)
This answer gives a convenient way to select all text nodes contained within a given element. Then you can iterate through that list and replace nodes based on your regex, without having to worry about accidentally modifying the surrounding HTML tags or attributes:
var getTextNodesIn = function(el) {
return $(el)
.find(":not(iframe, script)") // skip <script> and <iframe> tags
.andSelf()
.contents()
.filter(function() {
return this.nodeType == 3; // text nodes only
}
);
};
getTextNodesIn($('#foo')).each(function() {
var txt = $(this).text().trim(); // trimming surrounding whitespace
txt = txt.replace(/^\$\d$/g,"%"); // your regex
$(this).replaceWith(txt);
})
console.log($('#foo').html()); // tags and attributes were not changed
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="foo"> Some sample data, including bits that a naive regex would trip up on:
foo<span data-attr="$1">bar<i>$1</i>$12</span><div>baz</div>
<p>$2</p>
$3
<div>bat</div>$0
<!-- $1 -->
<script>
// embedded script tag:
console.log("<b>$1</b>"); // won't be replaced
</script>
</div>
I did it solved it slightly differently and test each value against regex before attempting to replace it:
var regEx = new RegExp(/^\$\d$/);
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++){
var allElementsText = allElements[i].innerText;
var regExTest = regEx.test(allElementsText);
if (regExTest=== true) {
console.log(el[i]);
var newText = allElementsText.replace(regEx, '%');
allElements[i].innerText=newText;
}
}
Does anyone see any potential issues with this?
One issue I found is that it does not work if part of the page refreshes after the page has loaded. Is there any way to have it re-run the script when new content is generated on page?
Few days ago I had to write code quickly and added the bad code below.
<td class="note_box_con" onclick="getElementsByTagName('a')[0].click();">
After then, when I tried to use Text editor plugin written by Javascript, I found
Text editor plugin and the function of DOM collide into each other
Now I know what was the problem and solve it. But I cannot understand what kind of risk getElementsByTagName('a')[0].click(); has.
In my incomplete view, that code is just addEventlistener function().....
what kind of risk onclick="getElementsByTagName('a')[0].click();" has?
In my understanding, its a bad practice to do it this way. I would rather suggest you to use to fetch using classname or id.
If you are using some resource that adds anchor tags to your page, this will break your logic.
Following is a simulation:
var count = 0;
function addAnchor(){
var div = document.getElementById("content");
var str = "<a href='#'>" + count++ + "</a>";
div.innerHTML = str + div.innerHTML;
}
document.getElementsByTagName("a")[0].addEventListener("click", function(){
console.log(this.innerHTML);
return false;
})
<div id="content">
Test
</div>
<button onclick="addAnchor()">Add Link</button>
<a onclick="document.getElementsByTagName('a')[0].click()"> test </a>
Also, if there is a change in DOM structure, your code will not work properly. Best use a proper selector that uniquely identifies the element.
Im trying to remove some invalid tags/html with jquery, but I think because its invalid the selector cant find it. IE
<p>Here1 is Here is lots of text in page <a href="" class="tooltipster">REMOVE THIS more content</a> and its cool</p>
$("a").find("a").remove();
Nothing happens, however if I change the second a (invalid one) to a span the selector can find and remove it? Thanks
======================================================
Update, ive taken m69s regex and added it to jquery (elements not in DOM so I cant use getelementbyID)
$(.container p).each(function (index) {
var cont = $(this).html().match(/^(.*?<A[^>]+>.*?)<A[^>]+>.*?(<\/A>.*)$/i);
if (cont) $(this).html(cont[1] + cont[2]);
});
This removes the illegal A element, but it doesn't completely restore the legal A element, because the browser removes the second closing A tag. As a result, some text that was inside the link may now come after it. (If you want to avoid the link being empty, you could move the next word into it.)
var elem = document.getElementsByTagName("P");
for (i = 0; i < elem.length; i++) {
var cont = elem[i].innerHTML.match(/^(.*?<A[^>]+>.*?)<A[^>]+>.*?(<\/A>.*)$/i);
if (cont) elem[i].innerHTML = cont[1] + cont[2];
}
<p>A simple text paragraph.</p>
<p>A paragraph with a legal <a href="">ILLEGAL LINK link</a> and some more text.</p>
<p>A paragraph with a legal link and some more text.</p>
Save your file as xhtml, the browser should find the un-closed tags for you. I don't think there is a way that this could be done in javascript.
I want to modify the text in a html file using javascript in an android webview.
Essentially, I want to do what android Linkify does to text, but I don't want to do it with java code, because I feel like that might delay the webview rendering the html (if I parse the text before sending it to the webview).
So, for example a piece of html like this:
<html>
<body>
google.com <!--these two shouldn't be linked-->
akhilcherian#gmail.com <!--these two shouldn't be linked-->
<p>www.google.com</p> <!--this should be linked-->
<p>102-232-2312 2032-122-332 </p><!-- should be linked as numbers-->
</body>
</html>
Should become this:
<html>
<body>
google.com
akhilcherian#gmail.com
<p>www.google.com</p>
<p>102-232-2312 <a href="tel:2032-122-332>2032-122-332</a> </p>
</body>
</html>
I already have the regexes to convert numbers and email ids to links, and they're working well enough. What I want to ensure is that I don't link anything that's already within tags. I've removed anchor tags, so they're not an issue, but I also need to avoid linking things like this:
<div width="1000"> <!-- Don't want this '1000' to be linked (but I do want other 4 digit numbers to be)-->
So for example if my regex for links is:
var replacePattern1 = /((https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/gim
How do I make sure that it's not within < and >? (Answers using javascript would be appreciated, but if you feel like this is a stupid way of doing it, please let me know about alternatives).
If you're answering with javascript, this question can essentially be shortened to:
How do I write a regex in javascript to search for patterns which are not surrounded by '<' '>' tags
So if you use JS than mean is client side, your DOM page have free access of all objects of your page coef events.
May be in this step you dont need to use a regex just using DOM.
jquery lib can easy update DOM object.
in your step you want only tag.
So i suggest :
//using jquery
$("p").each(function(){
console.log($(this))
});
//js
var paras = document.getElementsByTagName("p");
for(p in paras){
console.log(paras[p])
}
As i tell you the deal is manipulate the DOM so example with you step dunno if exactly what you try to get :
var paras = document.getElementsByTagName("p");
var hrefs = [];
//what you want to replace in the loop of p
var json_urls = {"links":["http://", "tel:"]};
for(p in paras){
//copy of text content of your p
var text_cp = paras[p].textContent;
//delete the p[i] content
paras[p].textContent = "";
//create element dom a
hrefs[p] = document.createElement("a");
//i add attribute id with some affectation unique
hrefs[p].id = "_" + p;
//add attribute href to a with some affectation replace + content
hrefs[p].href = json_urls.links[p] + text_cp;
hrefs[p].textContent = text_cp;
paras[p].appendChild(hrefs[p]);
}
So this is the functionality I need to clean up:
I need to create a function where viewers can click on any word in a sentence, and it will be highlighted. However, I need to make it so only one word is highlighted at a time. So for example, if you click the word 'you' and then you change your mind and click the word 'eagle', then the word 'you' will be deselected.
There's already existing code for it, but we're working on a very tight deadline and if we make edits and adjustments using this extremely long, extremely difficult-to-navigate code, then we will use up so many precious weeks producing a single 5-minute interactivity.
This is what it looks like:
And this is a snippet of the JS (so you can glimpse the problem):
(the HTML and JS codes are available upon request)
So instead of this long hell that we might need to put ourselves through, I was thinking something like passing each sentence through an array so each individual word would already be assigned to a name. So then we would be able to call each array via a for loop to print it out on the page, and use an arrayName[i] to call individual words for highlighting. And then maybe an if-else statement so only the selected word is highlighted.
I've been trying to push the actual HTML elements through the arrays, like, only get the <p> for every <div id="sentence1"> or something, but it doesn't seem to be possible... If it is, please tell me how it is done, or if it is not, I will still appreciate any other alternative for this.
I cannot for the life of me figure out all the hard-coding on my own, as I only know so much about JavaScript right now, but am very, very willing to learn! Any help with this would be deeply appreciated, as we're working on a very tight deadline.
Thank you so much in advance! Please, any help, or any suggestions would do!
EDIT
This is the code for our checkAns() function. It increments var correct when a correct answer is highlighted every time checkAns() runs. It is also responsible for marking specific numbers wrong or right.
function checkAns(){
document.getElementById('alertMsg').style.visibility = "hidden";
if(Ans1B == "selected"){
correct++
document.getElementById('marksymbol1').className = "smile";
}
else
{
document.getElementById('marksymbol1').className = "sad";
}
if(Ans2A == "selected"){
correct++
document.getElementById('marksymbol2').className = "smile";
}
else
{
document.getElementById('marksymbol2').className = "sad";
}
if(Ans3A == "selected"){
correct++
document.getElementById('marksymbol3').className = "smile";
}
else
{
document.getElementById('marksymbol3').className = "sad";
}
if(Ans4A == "selected"){
correct++
document.getElementById('marksymbol4').className = "smile";
}
else
{
document.getElementById('marksymbol4').className = "sad";
}
if(Ans5A == "selected"){
correct++
document.getElementById('marksymbol5').className = "smile";
}
else
{
document.getElementById('marksymbol5').className = "sad";
}
As per #DrewGoldsBerry's answer, with some example code. Here's a working fiddle of the code below: http://jsfiddle.net/E3D6T/1/
Set up your HTML with a class to indicate which lines should have the highlight functionality.
<p class="highlight">Each word will be wrapped in a span.</p>
<p class="highlight">A second paragraph here.</p>
In your JS, split the p elements into words wrapped in span tags, which can then be bound to a click function:
// wrap words in spans
$('p.highlight').each(function () {
var $this = $(this);
$this.html($this.text().replace(/\b(\w+)\b/g, "<span>$1</span>"));
});
// bind to each span
$('p.highlight span').click(function () {
$(this).parent().find('span').css('background-color', '');
$(this).css('background-color', '#ffff66');
});
edit:
http://jsfiddle.net/jorgthuijls/E3D6T/16/
I added all the answer checking to the click function itself. Should be fairly straight forward.
Sorry to add to the noise... my answer is very similar to Jorgs and Roberts, and it also checks for valid answers.
JS Fiddle is here:
http://jsfiddle.net/M7faZ/3/
The checkAns function uses the ID of the sentence element, to map the answer object to the selectedAnswer object.
The HTML has carefully chosen ID and classnames:
<ul class='sentences'>
<li class='sentence' id='ans1'>Can you draw an eagle?</li>
<li class='sentence' id='ans2'>She is good in painting.</li>
</ul>
<div id='mark-symbol-ans1'></div>
<div id='mark-symbol-ans2'></div>
And the JS has a map of answers.
// get the list
var $sentences = $('.sentence'),
answers = {
ans1: 'you',
ans2: 'She'
},
selectedAnswers = {};
function checkAns() {
var correct;
for (var i in answers) {
correct = selectedAnswers[i] === answers[i]
$('#mark-symbol-' + i).toggleClass('smile', correct);
$('#mark-symbol-' + i).toggleClass('sad', !correct);
}
}
If you care about people cheating, this part should be done on the server so it's not exposed to the client.
This could maybe work if the amount of content is limited else you might experience some lag onload.
Start out by gathering all the elements that need this ability into an array. Split each sentence using " " as the variable into another temporary array. Step through temp array adding a span tag around each word with a new class. Then return the new string with span tags to the corresponding element. This can be down with 2 for loops. Last bind both hover and onclick to each new tag by using another for loop. Sorry no code, I'm watching tosh lol, but if you make a jsfiddle I will write it for you.
Like I said this shouldn't be done with a lot of sentences at one time. You could always stagger sentences if you have multiple sections.
Hope this helps explain how I would do it. Good luck let me know what happens!
A similar solution to Jorg's, with some variations. I've done the wrapping with split() and join() instead of regex. I also tend to put my jQuery chains on separate lines for visual clarity.
<html>
<head>
<script type="text/javascript" src="./jquery.min.js"></script>
<script type="text/javascript">
$( document ).ready( function() {
var p = $( "p", "#myDiv" );
$.each( p, function( i, obj ){
$( obj )
.attr( "class", "highlightable" );
obj.innerHTML = "<span>" + obj.innerHTML.split(" ").join( "</span> <span>" ) + "</span>";
});
$( ".highlightable span", "#myDiv" )
.on( "click", function(){
$( "span", $( this ).parent() )
.css( "background-color", "");
$( this )
.css( "background-color", "#ffff45" );
});
});
</script>
</head>
<body>
<div id="myDiv">
<p>Here is a sentence.</p>
<p>Here is another sentence.</p>
<p>Here is yet another sentence.</p>
</div>
</body>
</html>