I'm making a simple step-by-step wizard for my website which asked viewers questions about their custom order. I've been using JavaScript to replace the content of each "page" with the document.getElementById('element-id').innerHTML command; however, it seems really slow and awkward to add entire divs as a string. For example, some of the code looks something like this:
function loadNextStep() {
document.getElementById('content').innerHTML = 'This is some content.<br>It seems like I need to write everything in one line to make the command work properly.<br><input type="date" id="date-picker" value=""></input>'
}
I'd love to be able to write some multi-line html code, and say "replace everything with this new html."
Is there a faster way of doing the same thing?
Thank you again!
I don't think getElementById or querySelector will make any difference, since the heavier stuff is done when you add a bunch of html elements as a string despite the fact that innerHTML can be vulnerable to cross site scripting if the output of that string has user input commands in it.
But if you still want to do this way you can do by using `` backticks to add as many lines as you'd like.
However, the way I would do is to create those elements on a different function and then output them to your loadNextStep function, then adding to your #content element using the appendChild method.
Here's a quick example of I would do:
function loadNextStep() {
var content = document.getElementById('content');
var step = step1();
step.forEach( stepContent => {
content.appendChild( stepContent );
})
}
function step1() {
var someContent = document.createElement('span');
someContent.innerText = `This is some content. It seems like I need to write everything in one line to make the command work properly.
Yes, but if you use backticks you can have multiple lines.`;
var input = document.createElement('input');
input.type = 'date';
input.id = 'date-picker';
return [ someContent, input ]
}
loadNextStep();
<div id="content">
</div>
Related
I get some markdown text from a database. I am using Showdown.js to transform this markdown into HTML:
var showdown = new Showdown.converter();
var str = showdown.makeHtml(myDatabaseString);
When there is code embedded within the markdown, Showdown.js will wrap it nicely into <pre><code> tags, so str may look something like this:
<p>Some text bla</p><pre><code>Some code</pre></code><p>Text again</p>
Now I want make things prettier by syntax highlighting the code pieces using SyntaxHighlighter. It's important that in the end I get a string back that contains everything it had before, plus the additional HTML for formatting.
My approach to this was the JQuery's ability to manipulate DOM:
$(str).each(function() {
// or can I select only pre within $(str) directly?
// everything I tried so far to do that failed miserably
if($(this).is('pre')) {
var code = $('code', $(this)).text();
// brush is my SyntaxHighlighter brush that I created earlier
$('code', $(this)).text(brush.getHtml(code));
// when I console.log($('code', $(this))) now, everything worked
// out perfectly
}
});
// but now I lost all my changes for some reason :-(
return str; // I need to return this as a string again
Am I on the right way? How can I keep the changes I make within my .each loop?
That's because you are creating a jQuery object that has nothing to do with str variable, you should use the created jQuery object. In fact you are modifying the elements of the created jQuery object and returning the original/unchanged str variable. I'd suggest:
// Creating a wrapper element
// and setting it's content by using str variable
var $wrapper = $('<div/>').html(str);
// Modifying descendant pre elements
$wrapper.find('pre').each(function(){
// implementing the logic
});
// Getting modified HTML content of the created wrapper element
str = $wrapper.html();
I am using Javascript(with Mootools) to dynamically build a large page using HTML "template" elements, copying the same template many times to populate the page. Within each template I use string keywords that need to be replaced to create the unique IDs. I'm having serious performance issues however in that it takes multiple seconds to perform all these replacements, especially in IE. The code looks like this:
var fieldTemplate = $$('.fieldTemplate')[0];
var fieldTr = fieldTemplate.clone(true, true);
fieldTr.removeClass('fieldTemplate');
replaceIdsHelper(fieldTr, ':FIELD_NODE_ID:', fieldNodeId);
parentTable.grab(fieldTr);
replaceIdsHelper() is the problem method according to IE9's profiler. I've tried two implementations of this method:
// Retrieve the entire HTML body of the element, replace the string and set the HTML back.
var html = rootElem.get('html').replace(new RegExp(replaceStr, 'g'), id);
rootElem.set('html', html);
and
// Load the child elements and replace just their IDs selectively
rootElem.getElements('*').each(function(elem) {
var elemId = elem.get('id');
if (elemId != null) elemId = elemId.replace(replaceStr, id);
elem.set('id', elemId)
});
However, both of these approaches are extremely slow given how many times this method gets called(about 200...). Everything else runs fine, it's only replacing these IDs which seems to be a major performance bottleneck. Does anyone know if there's a way to do this efficiently, or a reason it might be running so slow? The elements start hidden and aren't grabbed by the DOM until after they're created so there's no redrawing happening.
By the way, the reason I'm building the page this way is to keep the code clean, since we need to be able to create new elements dynamically after loading as well. Doing this from the server side would make things much more complicated.
I'm not 100% sure, but it sounds to me that the problem is with the indexing of the dom tree.
First of all, do you must use ids or can you manage with classes? since you say that the replacement of the id is the main issue.
Also, why do you clone part of the dom tree instead of just inserting a new html?
You can use the substitute method of String (when using MooTools), like so:
var template = '<div id="{ID}" class="{CLASSES}">{CONTENT}</div>';
template.substitute({ID: "id1", CLASSES: "c1 c2", CONTENT: "this is the content" });
you can read more about it here http://mootools.net/docs/core/Types/String#String:substitute
Then, just take that string and put it as html inside a container, let's say:
$("container_id").set("html", template);
I think that it might improve the efficiency since it does not clone and then index it again, but I can't be sure. give it a go and see what happens.
there are some things you can do to optimise it - and what #nizan tomer said is very good, the pseudo templating is a good pattern.
First of all.
var fieldTemplate = $$('.fieldTemplate')[0];
var fieldTr = fieldTemplate.clone(true, true);
you should do this as:
var templateHTML = somenode.getElement(".fieldTemplate").get("html"); // no need to clone it.
the template itself should/can be like suggested, eg:
<td id="{id}">{something}</td>
only read it once, no need to clone it for every item - instead, use the new Element constructor and just set the innerHTML - notice it lacks the <tr> </tr>.
if you have an object with data, eg:
var rows = [{
id: "row1",
something: "hello"
}, {
id: "row2",
something: "there"
}];
Array.each(function(obj, index) {
var newel = new Element("tr", {
html: templateHTML.substitute(obj)
});
// defer the inject so it's non-blocking of the UI thread:
newel.inject.delay(10, newel, parentTable);
// if you need to know when done, use a counter + index
// in a function and fire a ready.
});
alternatively, use document fragments:
Element.implement({
docFragment: function(){
return document.createDocumentFragment();
}
});
(function() {
var fragment = Element.docFragment();
Array.each(function(obj) {
fragment.appendChild(new Element("tr", {
html: templateHTML.substitute(obj)
}));
});
// inject all in one go, single dom access
parentTable.appendChild(fragment);
})();
I did a jsperf test on both of these methods:
http://jsperf.com/inject-vs-fragment-in-mootools
surprising win by chrome by a HUGE margin vs firefox and ie9. also surprising, in firefox individual injects are faster than fragments. perhaps the bottleneck is that it's TRs in a table, which has always been dodgy.
For templating: you can also look at using something like mustache or underscore.js templates.
I understand so far that in Jquery, with html() function, we can convert HTML into text, for example,
$("#myDiv").html(result);
converts "result" (which is the html code) into normal text and display it in myDiv.
Now, my question is, is there a way I can simply convert the html and put it into a variable?
for example:
var temp;
temp = html(result);
something like this, of course this does not work, but how can I put the converted into a variable without write it to the screen? Since I'm checking the converted in a loop, thought it's quite and waste of resource if keep writing it to the screen for every single loop.
Edit:
Sorry for the confusion, for example, if result is " <p>abc</p> " then $(#mydiv).html(result) makes mydiv display "abc", which "converts" html into normal text by removing the <p> tags. So how can I put "abc" into a variable without doing something like var temp=$(#mydiv).text()?
Here is no-jQuery solution:
function htmlToText(html) {
var temp = document.createElement('div');
temp.innerHTML = html;
return temp.textContent; // Or return temp.innerText if you need to return only visible text. It's slower.
}
Works great in IE ≥9.
No, the html method doesn't turn HTML code into text, it turns HTML code into DOM elements. The browser will parse the HTML code and create elements from it.
You don't have to put the HTML code into the page to have it parsed into elements, you can do that in an independent element:
var d = $('<div>').html(result);
Now you have a jQuery object that contains a div element that has the elements from the parsed HTML code as children. Or:
var d = $(result);
Now you have a jQuery object that contains the elements from the parsed HTML code.
You could simply strip all HTML tags:
var text = html.replace(/(<([^>]+)>)/g, "");
Why not use .text()
$("#myDiv").html($(result).text());
you can try:
var tmp = $("<div>").attr("style","display:none");
var html_text = tmp.html(result).text();
tmp.remove();
But the way with modifying string with regular expression is simpler, because it doesn't use DOM traversal.
You may replace html to text string with regexp like in answer of user Crozin.
P.S.
Also you may like the way when <br> is replacing with newline-symbols:
var text = html.replace(/<\s*br[^>]?>/,'\n')
.replace(/(<([^>]+)>)/g, "");
var temp = $(your_selector).html();
the variable temp is a string containing the HTML
$("#myDiv").html(result); is not formatting text into html code. You can use .html() to do a couple of things.
if you say $("#myDiv").html(); where you are not passing in parameters to the `html()' function then you are "GETTING" the html that is currently in that div element.
so you could say,
var whatsInThisDiv = $("#myDiv").html();
console.log(whatsInThisDiv); //will print whatever is nested inside of <div id="myDiv"></div>
if you pass in a parameter with your .html() call you will be setting the html to what is stored inside the variable or string you pass. For instance
var htmlToReplaceCurrent = '<div id="childOfmyDiv">Hi! Im a child.</div>';
$("#myDiv").html(htmlToReplaceCurrent);
That will leave your dom looking like this...
<div id="myDiv">
<div id="childOfmyDiv">Hi! Im a child.</div>
</div>
Easiest, safe solution - use Dom Parser
For more advanced usage - I suggest you try Dompurify
It's cross-browser (and supports Node js). only 19kb gziped
Here is a fiddle I've created that converts HTML to text
const dirty = "Hello <script>in script<\/script> <b>world</b><p> Many other <br/>tags are stripped</p>";
const config = { ALLOWED_TAGS: [''], KEEP_CONTENT: true, USE_PROFILES: { html: true } };
// Clean HTML string and write into the div
const clean = DOMPurify.sanitize(dirty, config);
document.getElementById('sanitized').innerText = clean;
Input: Hello <script>in script<\/script> <b>world</b><p> Many other <br/>tags are stripped</p>
Output: Hello world Many other tags are stripped
Using the dom has several disadvantages. The one not mentioned in the other answers: Media will be loaded, causing network traffic.
I recommend using a regular expression to remove the tags after replacing certain tags like br, p, ol, ul, and headers into \n newlines.
If i had a string:
hey user, what are you doing?
How, with regex could I say: look for user, but not inside of < or > characters? So the match would grab the user between the <a></a> but not the one inside of the href
I'd like this to work for any tag, so it wont matter what tags.
== Update ==
Why i can't use .text() or innerText is because this is being used to highlight results much like the native cmd/ctrl+f functionality in browsers and I dont want to lose formatting. For example, if i search for strong here:
Some <strong>strong</strong> text.
If i use .text() itll return "Some strong text" and then I'll wrap strong with a <span> which has a class for styling, but now when I go back and try to insert this into the DOM it'll be missing the <strong> tags.
If you plan to replace the HTML using html() again then you will loose all event handlers that might be bound to inner elements and their data (as I said in my comment).
Whenever you set the content of an element as HTML string, you are creating new elements.
It might be better to recursively apply this function to every text node only. Something like:
$.fn.highlight = function(word) {
var pattern = new RegExp(word, 'g'),
repl = '<span class="high">' + word + '</span>';
this.each(function() {
$(this).contents().each(function() {
if(this.nodeType === 3 && pattern.test(this.nodeValue)) {
$(this).replaceWith(this.nodeValue.replace(pattern, repl));
}
else if(!$(this).hasClass('high')) {
$(this).highlight(word);
}
});
});
return this;
};
DEMO
It could very well be that this is not very efficient though.
To emulate Ctrl-F (which I assume is what you're doing), you can use window.find for Firefox, Chrome, and Safari and TextRange.findText for IE.
You should use a feature detect to choose which method you use:
function highlightText(str) {
if (window.find)
window.find(str);
else if (window.TextRange && window.TextRange.prototype.findText) {
var bodyRange = document.body.createTextRange();
bodyRange.findText(str);
bodyRange.select();
}
}
Then, after you the text is selected, you can style the selection with CSS using the ::selection selector.
Edit: To search within a certain DOM object, you could use a roundabout method: use window.find and see whether the selection is in a certain element. (Perhaps say s = window.getSelection().anchorNode and compare s.parentNode == obj, s.parentNode.parentNode == obj, etc.). If it's not in the correct element, repeat the process. IE is a lot easier: instead of document.body.createTextRange(), you can use obj.createTextRange().
$("body > *").each(function (index, element) {
var parts = $(element).text().split("needle");
if (parts.length > 1)
$(element).html(parts.join('<span class="highlight">needle</span>'));
});
jsbin demo
at this point it's evolving to be more and more like Felix's, so I think he's got the winner
original:
If you're doing this in javascript, you already have a handy parsed version of the web page in the DOM.
// gives "user"
alert(document.getElementById('user').innerHTML);
or with jQuery you can do lots of nice shortcuts:
alert($('#user').html()); // same as above
$("a").each(function (index, element) {
alert(element.innerHTML); // shows label text of every link in page
});
I like regexes, but because tags can be nested, you will have to use a parser. I recommend http://simplehtmldom.sourceforge.net/ it is really powerful and easy to use. If you have wellformed xhtml you can also use SimpleXML from php.
edit: Didn't see the javascript tag.
Try this:
/[(<.+>)(^<)]*user[(^>)(<.*>)]/
It means:
Before the keyword, you can have as many <...> or non-<.
Samewise after it.
EDIT:
The correct one would be:
/((<.+>)|(^<))*user((^>)|(<.*>))*/
Here is what works, I tried it on your JS Bin:
var s = 'hey user, what are you doing?';
s = s.replace(/(<[^>]*)user([^<]>)/g,'$1NEVER_WRITE_THAT_ANYWHERE_ELSE$2');
s = s.replace(/user/g,'Mr Smith');
s = s.replace(/NEVER_WRITE_THAT_ANYWHERE_ELSE/g,'user');
document.body.innerHTML = s;
It may be a tiny little bit complicated, but it works!
Explanation:
You replace "user" that is in the tag (which is easy to find) with a random string of your choice that you must never use again... ever. A good use would be to replace it with its hashcode (md5, sha-1, ...)
Replace every remaining occurence of "user" with the text you want.
Replace back your unique string with "user".
this code will strip all tags from sting
var s = 'hey user, what are you doing?';
s = s.replace(/<[^<>]+>/g,'');
EDIT [how can I] change the color of every R and r in my HTML document with javascript?
I'd use the highlight plugin for jQuery. Then do something like:
$('*').highlight('r'); // Not sure if it's case-insensitive or not
and in CSS:
.highlight { background-color: yellow; }
Doable, but not super easy. There's no CSS way to do it.
Basically, you'll need to use Javascript and iterate through the all nodes. If it's a text node, you can search it for "R" and then replace the R with a <span style="color:red">R</span>
I am obviously simplifying this a bit, it's probably better to just dynamically add a "highlight" class, rather than hard code a style, and have that defined in CSS. Similarly, I'm sure you'll wanna parameterize the search string. Also, this doesn't take into account what the text node is, for instance, I have special handling to skip comments, but you'll probably find there's other things (script nodes?) you also need to skip.
function updateNodes(node) {
if (node.nextSibling)
updateNodes(node.nextSibling);
if (node.nodeType ==8) return; //Don't update comments
if (node.firstChild)
updateNodes(node.firstChild);
if (node.nodeValue) { // update me
if (node.nodeValue.search(/[Rr]/) > -1){ // does the text node have an R
var span=document.createElement("span");
var remainingText = node.nodeValue;
var newValue='';
while (remainingText.search(/[Rr]/) > -1){ //Crawl through the node finding each R
var rPos = remainingText.search(/[Rr]/);
var bit = remainingText.substr(0,rPos);
var r = remainingText.substr(rPos,1);
remainingText=remainingText.substr(rPos+1);
newValue+=bit;
newValue+='<span style="color:red">';
newValue+=r;
newValue+='</span>';
}
newValue+=remainingText;
span.innerHTML=newValue;
node.parentNode.insertBefore(span,node);
node.parentNode.removeChild(node);
}
}
}
function replace(){ updateNodes(document.body);
}
Yes this is possible with a little Javascript, a smattering of CSS and some regex.
First, you need to define a style which provides the colour you require (in my example below I refer to a CSS class called "new-colour"), and then run some regex over your HTML content which does a search and replace. You are looking to change all 'r' and 'R' characters into something like this (as an example):
<span class="new-colour">r</span>
If you don't know regex, there are oodles of resources out there to get you started. You will be pleased to know that your requirement is very simple, so no worries there. Here are a couple of links:
regexlib.com
8 regular expressions you should know
You would need to use the DOM (or jQuery) to iterate through every text node in the document. Whenever you find the letter R, apply a transformation that wraps the letter in an appropriate element.
e.g. Transform the text node "art" into "a<span class="colored">r</span>t". This adds two new text nodes, "r" and "t", and the new span element.
The highlight plugin for jQuery is one option. Another option - especially since to-morrow - you might want to extend your highlighting into keywords or other terms is to use Google's Closure goog.dom.annotate Class. The beauty of this Class is that it will actually parse the dom tree properly and ONLY
annotate the relevant terms. It will also allow you to EXCLUDE elements or elements with certain classes.
A common problem with annotations is that you can mess your HTML, if you are not careful.
For example the 'simple solution posted above'
var body = document.getElementsByTagName("body")[0];
var html = body.innerHTML
.replace(/(^|>[^<rR]*)([rR])/g, "$1<em>$2</em>");
body.innerHTML = html;
will surely also capture terms in any style attributes. If you had this:
<p class="red">text......</p>
It will become
<p class="<span class="red">r</span>ed .....
that will break your html.
In general DOM parsing is 'slow', so try and avoid annotating the whole body of a webpage, ask yourself why you only need to highlight the R's? Actually I am curious why do you want to annotate the r's?:)
Plain JS solution without need of any 20kB JS library:
var body = document.getElementsByTagName("body")[0];
var html = body.innerHTML
.replace(/(^|>[^<rR]*)([rR])/g, "$1<em>$2</em>");
body.innerHTML = html; // note that you will lose all
// event handlers in this step...