Javascript replace text with image exclude textbox input - javascript

How do I get it to not replace the text on textbox inputs? It doesn't do it while typing, but if you go to edit a document or input field that contains the replaced information it will and makes it impossible to edit.
I've tried to do it but it just turns the entire script off. xD
<script type='text/javascript'>
document.body.innerHTML = document.body.innerHTML.replace(/E0/g, '<img src="http://s31.postimg.org/5riz5vn0b/Zero.png" title="E"/>');
document.body.innerHTML = document.body.innerHTML.replace(/E10/g, '<img src="http://s31.postimg.org/6wm82qo8r/ten.png" title="E"/>');
document.body.innerHTML = document.body.innerHTML.replace(/E20/g, '<img src="http://s31.postimg.org/4j4abq9tn/twenty.png"title="E"/>');
document.body.innerHTML = document.body.innerHTML.replace(/E30/g, '<img src="http://s31.postimg.org/mzyp2jprv/thirty.png"title="E"/>');
document.body.innerHTML = document.body.innerHTML.replace(/E40/g, '<img src="http://s31.postimg.org/j4vazz6m3/forty.png"title="E"/>');
</script>
Here is an image of the problem I see on the web page:

You can use document.querySelectorAll() with selector string "body *" to select all elements except <body>; for loop; exclude <input> elements from single .replace() call by filtering INPUT .tagName property at if condition
<div>E0 E10 E20 E30 E40</div>
<input type="text" value="E40" />
<script type='text/javascript'>
var elems = document.querySelectorAll("body *");
var url = "http://s31.postimg.org/";
for (var i = 0; i < elems.length; i++) {
if (elems[i].tagName !== "INPUT") {
elems[i].innerHTML = elems[i].innerHTML
.replace(/(E0)|(E10)|(E20)|(E30)|(E40)/g
, function(match, p1, p2, p3, p4, p5) {
if (p1) return "<img src=" + url
+ "5riz5vn0b/Zero.png title=E/>";
if (p2) return "<img src=" + url
+ "6wm82qo8r/ten.png title=E/>";
if (p3) return "<img src=" + url
+ "4j4abq9tn/twenty.png title=E/>";
if (p4) return "<img src=" + url
+ "mzyp2jprv/thirty.png title=E/>";
if (p5) return "<img src=" + url
+ "j4vazz6m3/forty.png title=E/>";
})
}
}
</script>

An alternative method might be to search the document for text nodes which contain E0, E10, E20, E30, E40, skipping those which are child nodes of TEXTAREA and INPUT elements, and replace each text node that remains with an element containing text and images.
This should avoid a potential problem of replacing innerHTML of container elements which have elements inside them that need to be left unchanged. While the following code runs in IE and Firefox it has not been tested it in the full context of your application.
function textToImages() // within document.body
{
var checkAny = /E0|E10|E20|E30|E40/g;
function filter( node)
{ checkAny.lastIndex=0;
if(checkAny.test(node.nodeValue))
{ switch(node.parentNode.tagName)
{ case "INPUT":
case "TEXTAREA":
return NodeFilter.FILTER_SKIP;
}
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
}
filter.acceptNode = filter; // cross browser compatability
var scan = document.createNodeIterator(
document.body,
NodeFilter.SHOW_TEXT,
filter,
false
);
var node, replaceList=[];
document.body.normalize();
while(node = scan.nextNode())
replaceList.push(node);
if( replaceList.length)
replaceList.forEach( replaceTextNode);
function replaceTextNode( node)
{ var html = node.nodeValue;
var span = document.createElement("span");
html=html.replace(/E0/g, '<img src="http://s31.postimg.org/5riz5vn0b/Zero.png" title="E"/>');
html=html.replace(/E10/g, '<img src="http://s31.postimg.org/6wm82qo8r/ten.png" title="E"/>');
html=html.replace(/E20/g, '<img src="http://s31.postimg.org/4j4abq9tn/twenty.png" title="E"/>');
html=html.replace(/E30/g, '<img src="http://s31.postimg.org/mzyp2jprv/thirty.png" title="E"/>');
html=html.replace(/E40/g, '<img src="http://s31.postimg.org/j4vazz6m3/forty.png"title="E"/>');
span.innerHTML = html;
node.parentNode.replaceChild(span, node);
}
}
A NodeIterator is used to scan text nodes in the DOM, filtering for those which contain text to be replaced which are not child nodes of TEXTAREA or INPUT elements, and create a static list of nodes to be processed.
I had trouble with document.createNodeIteratorin IE which requires at least 4 parameters and requires its 3rd parameter to be a function. If you encounter problems in other browsers I would suggest writing custom DOM traversal and filtering code to avoid using NodeIterator altogether.
Text nodes which passed through the filter are replaced by a SPAN element containing zero or more text nodes and one or more image elements. The node replacement function basically uses the code posted in the question. While it should be possible to replace a single text node with one or more text and image nodes, without the use of a container, it would require additional coding to create and manipulate DOM elements.

Related

extract div as HTML including CSS classes

I am trying to create a general function that will extract a div content (with nested elements) and save it locally in an HTML file.
Basically I get the div innerHTML, wrap it in html/head/body tags and then save it:
function div2html() {
var inner=document.getElementById("div2save").innerHTML;
var html="<html><head></head><body>"+inner+"</body></html>";
saveTextAsFile("div2html.html", html);
}
See a working version here: jsfiddle
However I am not sure how to handle classes. As you can see the class in the sample (bigbold) is not embedded in the new HTML. I need some way to get all the classes used in the div and then add them (or the computed styles ?) to the html I generate .. is this possible ? is there any other way around it ?
Try including style element .outerHTML within saved html
function div2html() {
var inner=document.getElementById("div2save").innerHTML;
var style = document.getElementsByTagName("style")[0].outerHTML;
var html="<html><head>"+style+"</head><body>"+inner+"</body></html>";
saveTextAsFile("div2html.html", html);
}
jsfiddle http://jsfiddle.net/fb6s763w/1/
Alternatively, using window.getComputedStyle() to select only css of #div2save child node
function div2html() {
var inner = document.getElementById("div2save");
var style = window.getComputedStyle(inner.children[0]).cssText;
var html = "<html><head><style>"
+ "." + inner.children[0].className
+ "{" + style + "}"
+ "</style></head><body>"
+ inner.innerHTML + "</body></html>";
saveTextAsFile("div2html.html", html);
}
jsfiddle http://jsfiddle.net/fb6s763w/2/
Looks like this might be able to help you out:
https://github.com/Automattic/juice
If the CSS of the page is not big, a simple solution is to include it all in the saved html as suggested by guest271314 above with
var style = document.getElementsByTagName("style")[0].outerHTML;
see jsfiddle
A more comprehensive solution extracts the classes from the div and then adds only the rules of those classes to the div (Using code from How do you read CSS rule values with JavaScript?)
function div2html(divId) {
var html = document.getElementById(divId).innerHTML;
// get all css classes in html
var cssClasses = [];
var classRegexp = /class=['"](.*?)['"]/g;
var m;
while ((m = classRegexp.exec(html))) cssClasses = cssClasses.concat(cssClasses, m[1].split(" "));
// filter non unique or empty cssClasses
cssClasses = cssClasses.filter(function (item, pos, self) {
return item && self.indexOf(item) == pos;
});
// get html of classes
var cssHtml = '';
for (var i = 0; i < cssClasses.length; i++) cssHtml += getRule('.' + cssClasses[i]);
// assemble html
var html = "<html><head><style>" + cssHtml + "</style></head><body>" + html + "</body></html>";
console.log(html);
saveTextAsFile("div2html.html", html);
}
see jsfiddle

Write text over div without using .text()

I've got again a rather simple question, that I couldn't find an answer to.
I was using sofar the Jquery function .text() to write text on mouseenter on a dynamically created div. I came to realise that this only worked on my Iceweasel, but not in Chrome for instance. Instead ot .text() everywhere people advised of using the .val(), but I can't seem to figure out exactly how to use it in my implementation, since the divs had no previous text value.
Please find below a simple code, with .text() to understnad the question.
(function(){
for (var i = 0; i < 3; i++) {
var span = document.createElement("span");
span.innerHTML = "<img width=\"" + data.size[i][0] + "\" height=\"" + data.size[i][1] + "\" id=\"" + i + "\">";
span.style.position = "absolute";
span.style.left = data.coords[i][0] + "px";
span.style.top = data.coords[i][1] + "px";
document.body.appendChild(span);
}
}());
for (var i=0; i<3; i++) {
$('#' + i).mouseenter(function() {
$(this).text("text");
});
$('#' + i).mouseleave(function() {
$(this).text("")
});
}
http://jsfiddle.net/ckpx6esj/1/
I hope someone can give me an idea, of how to apply .val() or use something else entirely to make this work for chrome also.
Best Regards and Thanks in advance!
The problem is that you put text in an image tag!
<img>Some text</img>
This is invalid HTML, see this answer.
If you want text over an image, I suggest using a div with background: url(...) instead.
Updated fiddle.
The cleverest I could think to don't screw up your for loop is appending a <p> tag containing your text and removing it on mouseleave:
for (var i=0; i<3; i++){
$('#' + i).on("mouseenter",function() {
$(this).parent().append("<p>text</p>");
});
$('#' + i).on("mouseleave",function() {
$(this).parent().find("p").remove();
});
}
Fiddle:
http://jsfiddle.net/ckpx6esj/2/
Besides, text was not working because you are listening to the image (<img>) instead of the span. Images has no .text() prototype, hence you should access its parent() (which is a <span> in that case) if you want to use the .text() prototype, but using .text() on the parent will remove the image, hence the idea of appending the text and removing it later.
According to specification, val() function is to set value attribute and it only matters for input fields on your page. text() function is to change content of your element.
The .val() method is primarily used to get the values of form elements
such as input, select and textarea.
So you should use text() function in your code.
Also according to your code you change text property of <img> element. This is not good. You should change text of your <span>. So just move your id to span element.
If you want the jQuery equivalent of Javascript's native innerHtml, go for $(this).html('text');.
Take a look at these functions:
http://api.jquery.com/html/
$(this).html('text');
http://api.jquery.com/append/
$(this).append('text'); // Note that this appends instead of replaces
http://api.jquery.com/val/
$(this).val('text');
Or if you're feeling adventurous:
http://api.jquery.com/appendto/
$('text').appendTo($(this)); // Performance penalty for creating an object out of 'text'
First I will use class instead id, it will save using the second loop,
also if you want to have also text and also image you can do it but it will be littel complicated I would recommand add some child element to the span that will contain the text, I didnt do it just for the challenge
http://jsfiddle.net/ckpx6esj/5/
simple plugin to change the text without changing the html elements
$.fn.selectorText = function(text) {
var str = '';
this.contents().each(function() {
if (this.nodeType === 3) {
if(typeof(text) === 'string'){
this.textContent = text;
return false;
}else{
str += this.textContent || this.innerText || '';
}
}
});
return str;
};
var thisData = [{
'coords' : [[100,100], [300, 300], [200, 200]],
'size' : [[30, 30], [30, 30], [30, 30]]
}];
var data = thisData[0];
(function(){
for (var i = 0; i < 3; i ++){
var span = document.createElement("span");
span.setAttribute('class','spanImage');
span.style.position = "absolute";
span.style.left = data.coords[i][0] + "px";
span.style.top = data.coords[i][1] + "px";
span.innerHTML = "\n<img width=\"" + data.size[i][0] + "\" height=\"" + data.size[i][1] + "\" id=\"" + i + "\">";
document.body.appendChild(span);
}
$('.spanImage')
.on( 'mouseenter', function() {
$(this).selectorText('text');
})
.on( 'mouseleave', function() {
$(this).selectorText('');
});
}());

Highlighting and formatting code snippets using JavaScript

I'm looking for a way to highlight and format code snippets passed as string for a live style guide. I'm playing around with highlighjs and prettify. They are really helpful and easy for highlighting, but I can't seem to figure out a way to format or whether they can actually do that or not.
By formatting, I mean tabs and newlines to make code legible. I need to pass code as a string to automate the output of dust template I'm using for the style guide.
That is, I want to pass:
"<table><tr><td class="title">Name</td><td class="title">Category</td><td class="title">Results</td></tr></table>"
And get something like:
<table>
<tr>
<td class="title">Name</td>
<td class="title">Category</td>
<td class="title">Results</td>
</tr>
</table>
Any ideas on how to accomplish this?
Thanks!
You could parse this as HTML into a DOM and than traverse every element writing it out and indenting it with every iteration.
This code will do the job. Feel free to use it and surely to improve it. It's version 0.0.0.1.
var htmlString = '<table><tr><td class="title">Name</td><td class="title">Category</td><td class="title">Results</td></tr></table>';
//create a containing element to parse the DOM.
var documentDOM = document.createElement("div");
//append the html to the DOM element.
documentDOM.insertAdjacentHTML('afterbegin', htmlString);
//create a special HTML element, this shows html as normal text.
var documentDOMConsole = document.createElement("xmp");
documentDOMConsole.style.display = "block";
//append the code display block.
document.body.appendChild(documentDOMConsole);
function indentor(multiplier)
{
//indentor handles the indenting. The multiplier adds \t (tab) to the string per multiplication.
var indentor = "";
for (var i = 0; i < multiplier; ++i)
{
indentor += "\t";
}
return indentor;
}
function recursiveWalker(element, indent)
{
//recursiveWalker walks through the called DOM recursively.
var elementLength = element.children.length; //get the length of the children in the parent element.
//iterate over all children.
for (var i = 0; i < elementLength; ++i)
{
var indenting = indentor(indent); //set indenting for this iteration. Starts with 1.
var elements = element.children[i].outerHTML.match(/<[^>]*>/g); //retrieve the various tags in the outerHTML.
var elementTag = elements[0]; //this will be opening tag of this element including all attributes.
var elementEndTag = elements[elements.length-1]; //get the last tag.
//write the opening tag with proper indenting to the console. end with new line \n
documentDOMConsole.innerHTML += indenting + elementTag + "\n";
//get the innerText of the top element, not the childs using the function getElementText
var elementText = getElementText(element.children[i]);
//if the texts length is greater than 0 put the text on the page, else skip.
if (elementText.length > 0)
{
//indent the text one more tab, end with new line.
documentDOMConsole.innerHTML += (indenting + indentor(1) ) + elementText+ "\n";
}
if (element.children[i].children.length > 0)
{
//when the element has children call function recursiveWalker.
recursiveWalker(element.children[i], (indent+1));
}
//if the start tag matches the end tag, write the end tag to the console.
if ("<"+element.children[i].nodeName.toLowerCase()+">" == elementEndTag.replace(/\//, ""))
{
documentDOMConsole.innerHTML += indenting + elementEndTag + "\n";
}
}
}
function getElementText(el)
{
child = el.firstChild,
texts = [];
while (child) {
if (child.nodeType == 3) {
texts.push(child.data);
}
child = child.nextSibling;
}
return texts.join("");
}
recursiveWalker(documentDOM, 1);
http://jsfiddle.net/f2L82m8h/

How come this JQuery code returns nothing?

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script>
var newvalue = '<img class="youtube_replace youtube_canvas" data-code="Wn-_MyJV37E" src="http://img.youtube.com/vi/Wn-_MyJV37E/0.jpg" />';
var abc = $('<div>' + newvalue + '</div>').find('*').each(function() {
}).html();
alert(abc);
</script>
I want abc to equal "newvalue". But in my current code, abc is empty. Why?
This is what I truly want to do, but for example purposes, I left this blank above:
var whitelist = ['a','div','img', 'span'];
var abc = $('<div>' + newvalue + '</div>').find('*').each(function() {
if($.inArray(this.nodeName.toLowerCase(), whitelist)==-1) {
$(this).remove();
}
}).html(); //abc is now sanitized!!!
Breaking it down:
var abc = $('<div>' + newvalue + '</div>') //Creates a div with an img element inside it
.find('*') //retrieves the img element
.each(function() {}) //iterates over the jQuery set (only one img element)
.html(); //returns the HTML serialization of the img element
//which has no contents, so it is an empty string
You could call .parent().html(), which would retrieve the contents of the div you created.
In your second example, you would want .end().html() which would pop the internal jQuery stack and get you back to the top-most div.
The issue here is that when you do:
var abc = $('<div>' + newvalue + '</div>').find('*')
your jQuery object holds the img element, not the div element. So when you're calling .html(), you're getting the inner HTML of the image - which of course doesn't exist.
var abc = $('<div>' + newvalue + '</div>')
.find('*')
.each(function() {
// stuff
})
.parent().html();
(but #Dennis got there first :). )
Do it like that (if you insist on getting HTML of element you just generated):
var abc = $('<div>' + newvalue + '</div>').html();
The problem is just incorrect mixing of different jQuery functions and callbacks.
EDIT:
The problem you have is that with find('*') you retrieve all the <img> tags (actually: one <img> tag) within <div>, but <img> tags have no HTML inside them (they have no other tags inside).
If you shorten your code to this:
var abc = $('<div>' + newvalue + '</div>').find('*').each(function() {
/* your JS code here */
}).parent().html();
you will actually receive HTML of the whole <img> tag.

JavaScript RegExp match text ignoring HTML

Is it possible to match "the dog is really really fat" in "The <strong>dog</strong> is really <em>really</em> fat!" and add "<span class="highlight">WHAT WAS MATCHED</span>" around it?
I don't mean this specifically, but generally be able to search text ignoring HTML, keeping it in the end result, and just add the span above around it all?
EDIT:
Considering the HTML tag overlapping problem, would it be possible to match a phrase and just add the span around each of the matched words? The problem here is that I don't want the word "dog" matched when it's not in the searched context, in this case, "the dog is really really fat."
Update:
Here is a working fiddle that does what you want. However, you will need to update the htmlTagRegEx to handle matching on any HTML tag, as this just performs a simple match and will not handle all the cases.
http://jsfiddle.net/briguy37/JyL4J/
Also, below is the code. Basically, it takes out the html elements one by one, then does a replace in the text to add the highlight span around the matched selection, and then pushes back in the html elements one by one. It's ugly, but it's the easiest way I could think of to get it to work...
function highlightInElement(elementId, text){
var elementHtml = document.getElementById(elementId).innerHTML;
var tags = [];
var tagLocations= [];
var htmlTagRegEx = /<{1}\/{0,1}\w+>{1}/;
//Strip the tags from the elementHtml and keep track of them
var htmlTag;
while(htmlTag = elementHtml.match(htmlTagRegEx)){
tagLocations[tagLocations.length] = elementHtml.search(htmlTagRegEx);
tags[tags.length] = htmlTag;
elementHtml = elementHtml.replace(htmlTag, '');
}
//Search for the text in the stripped html
var textLocation = elementHtml.search(text);
if(textLocation){
//Add the highlight
var highlightHTMLStart = '<span class="highlight">';
var highlightHTMLEnd = '</span>';
elementHtml = elementHtml.replace(text, highlightHTMLStart + text + highlightHTMLEnd);
//plug back in the HTML tags
var textEndLocation = textLocation + text.length;
for(i=tagLocations.length-1; i>=0; i--){
var location = tagLocations[i];
if(location > textEndLocation){
location += highlightHTMLStart.length + highlightHTMLEnd.length;
} else if(location > textLocation){
location += highlightHTMLStart.length;
}
elementHtml = elementHtml.substring(0,location) + tags[i] + elementHtml.substring(location);
}
}
//Update the innerHTML of the element
document.getElementById(elementId).innerHTML = elementHtml;
}
Naah... just use the good old RegExp ;)
var htmlString = "The <strong>dog</strong> is really <em>really</em> fat!";
var regexp = /<\/?\w+((\s+\w+(\s*=\s*(?:\".*?"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/gi;
var result = '<span class="highlight">' + htmlString.replace(regexp, '') + '</span>';
A simpler way with JQuery would be.
originalHtml = $("#div").html();
newHtml = originalHtml.replace(new RegExp(keyword + "(?![^<>]*>)", "g"), function(e){
return "<span class='highlight'>" + e + "</span>";
});
$("#div").html(newHtml);
This works just fine for me.
Here is a working regex example to exclude matches inside html tags as well as javascripts:
http://refiddle.com/lwy6
Use this regex in a replace() script.
/(a)(?!([^<])*?>)(?!<script[^>]*?>)(?![^<]*?<\/script>|$)/gi
this.keywords.forEach(keyword => {
el.innerHTML = el.innerHTML.replace(
RegExp(keyword + '(?![^<>]*>)', 'ig'),
matched => `<span class=highlight>${matched}</span>`
)
})
You can use string replace with this expression </?\w*> and you'll get your string
If you use jQuery, you can use the text property on the element containing the text you're searching for. Given this markup:
<p id="the-text">
The <strong>dog</strong> is really <em>really</em> fat!
</p>
This would yield "The dog is really really fat!":
$('#the-text').text();
You could do your regex search on that text instead of trying to do so in the markup.
Without jQuery, I'm unsure of an easy way to extract and concatenate the text nodes from all child elements.

Categories