Which technique below is better from a user experience?
In both examples, assume that thumbContainer is set to the return value of document.getElementById('thumbContainer'), and new Thumbnail(thumb).toXMLString() returns a string with XHTML markup in it.
A) Simply += with thumbContainer.innerHTML:
for (thumb in thumbnails) {
thumbContainer.innerHTML += new Thumbnail(thumb).toXMLString();
}
B) Or converting new Thumbnail(thumb).toXMLString() to DOM elements and using appendChild?
for (thumb in thumbnails) {
var shell = document.createElement('div');
shell.innerHTML = new Thumbnail(thumb).toXMLString();
for (i = 0; i < shell.childElementCount; i++) {
thumbContainer.appendChild(shell.children[i]);
}
}
I've used both and get complaints from Firefox that I've got a long-running script, and do I want to stop it?
Which loop is less-tight, that will allow the UI to update as new elements are added to the DOM?
A) Simply += with thumbContainer.innerHTML
Never do this. This has to serialise all the content to HTML, add a string to it, and parse it all back in. It's slow (doing it in a loop is particularly bad news) and it'll lose all JavaScript references, event handlers and so on.
IE's insertAdjacentHTML will do this more efficiently. It's also part of HTML5, but not widely implemented elsewhere yet.
using appendChild?
Yes, that's fine. If you've got a lot of thumbs it will start to get slow (but not as bad as reparsing innerHTML each time). Making a DocumentFragment to insert multiple elements into the container child node list at once can help in some cases. Combining DocumentFragment with Range can do more still, but gets harder to write compatibly since IE's Range implementation is so StRange.
Either way, since you don't seem to be doing anything with the individual shell nodes, why not simply join all the thumbernail HTML strings together before parsing?
for (var child in shell.childNodes) {
Don't use for..in on sequence types, it won't do what you think. It's only meant for use against Object used as a mapping. The correct way to iterate over an Array or NodeList is plain old for (var i= 0; i<sequence.length; i++).
Personally I like the second approach (using appendChild) better, because, you are adding new element to the document tree without affecting the old elements.
When using innerHTML += new content, you affect old content, because all the HTML has to be reassigned (replaced with new HTML that contains of old code and some new code).
But, if you want to increase the performance, I'd suggest using DocumentFragments. Using them you can append a whole set of nodes with just one appendChild call on the document tree. Worth reading: "DOM DocumentFragments" by John Resig, but it's not the case in your problem, because you get the content as a string, that you have to first convert to DOM nodes.
My second suggestion is, to create a string of HTML code and then use innerHTML on a temporary container to convert it to DOM nodes
var htmlCode = "";
for (thumb in thumbnails) {
htmlCode += new Thumbnail(thumb).toXMLString();
}
var element = document.createElement(htmlCode);
element.innerHTML = htmlCode; // ... and append all the elements to document, or thumbContainer.innerHTML += htmlCode;
Related
Can the JavaScript command .replace replace text in any webpage? I want to create a Chrome extension that replaces specific words in any webpage to say something else (example cake instead of pie).
The .replace method is a string operation, so it's not immediately simple to run the operation on HTML documents, which are composed of DOM Node objects.
Use TreeWalker API
The best way to go through every node in a DOM and replace text in it is to use the document.createTreeWalker method to create a TreeWalker object. This is a practice that is used in a number of Chrome extensions!
// create a TreeWalker of all text nodes
var allTextNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT),
// some temp references for performance
tmptxt,
tmpnode,
// compile the RE and cache the replace string, for performance
cakeRE = /cake/g,
replaceValue = "pie";
// iterate through all text nodes
while (allTextNodes.nextNode()) {
tmpnode = allTextNodes.currentNode;
tmptxt = tmpnode.nodeValue;
tmpnode.nodeValue = tmptxt.replace(cakeRE, replaceValue);
}
To replace parts of text with another element or to add an element in the middle of text, use DOM splitText, createElement, and insertBefore methods, example.
See also how to replace multiple strings with multiple other strings.
Don't use innerHTML or innerText or jQuery .html()
// the innerHTML property of any DOM node is a string
document.body.innerHTML = document.body.innerHTML.replace(/cake/g,'pie')
It's generally slower (especially on mobile devices).
It effectively removes and replaces the entire DOM, which is not awesome and could have some side effects: it destroys all event listeners attached in JavaScript code (via addEventListener or .onxxxx properties) thus breaking the functionality partially/completely.
This is, however, a common, quick, and very dirty way to do it.
Ok, so the createTreeWalker method is the RIGHT way of doing this and it's a good way. I unfortunately needed to do this to support IE8 which does not support document.createTreeWalker. Sad Ian is sad.
If you want to do this with a .replace on the page text using a non-standard innerHTML call like a naughty child, you need to be careful because it WILL replace text inside a tag, leading to XSS vulnerabilities and general destruction of your page.
What you need to do is only replace text OUTSIDE of tag, which I matched with:
var search_re = new RegExp("(?:>[^<]*)(" + stringToReplace + ")(?:[^>]*<)", "gi");
gross, isn't it. you may want to mitigate any slowness by replacing some results and then sticking the rest in a setTimeout call like so:
// replace some chunk of stuff, the first section of your page works nicely
// if you happen to have that organization
//
setTimeout(function() { /* replace the rest */ }, 10);
which will return immediately after replacing the first chunk, letting your page continue with its happy life. for your replace calls, you're also going to want to replace large chunks in a temp string
var tmp = element.innerHTML.replace(search_re, whatever);
/* more replace calls, maybe this is in a for loop, i don't know what you're doing */
element.innerHTML = tmp;
so as to minimize reflows (when the page recalculates positioning and re-renders everything). for large pages, this can be slow unless you're careful, hence the optimization pointers. again, don't do this unless you absolutely need to. use the createTreeWalker method zetlen has kindly posted above..
have you tryed something like that?
$('body').html($('body').html().replace('pie','cake'));
So I'm trying to do a little bit of code optimization for an application I've made... Essentially right now I have a container like so...
<div class="Container"></div>
This container is already in the DOM. I then want to insert data into this container in the form of a list.... I want to do something like this....
var list = $("<ul class='DataList'></ul>");
var ListElements = PopulateMeWithALargeList();
for(var i = 0; i < ListElements.length; i++)
{
$(ListElements[i]).appendTo(list);
}
$(list).appendTo(".Container");
Where PopulateMeWithLargeList is just a function that will get a large (about 500 entries) array of strings and return it.
I have a feeling that this would be the same as me inserting the elements directly onto the rendered page, so would it be better if I formed it all into 1 string and then inserted that 1 string into the Container?
You can do this:
$('<ul class="DataList">')
.append(PopulateMeWithALargeList())
.appendTo('.Container');
This is an odd line since there doesn't seem to be a reason for casting each element as a specific jquery object:
$(ListElements[i]).appendTo(list);
Unless you were doing something else that required each list element have the jquery chain available right away in your loop, you can do:
list.append(ListElements[i]);
Using a string buffer will be faster. An array buffer will be even faster. But as others have pointed out, this is an unlikely bottleneck at the current numbers. FWIW, always call any jquery methods as few times as possible.
I have to make a high speed web app and I need a JavaScript templating library/system/engine/technique that returns a DOM Fragment instead of a String containing HTML.
Of course it should have some similar language as Resig's Micro-Templating
I am expecting something like this after compilation:
function myTemplate(dataToRender){
var fragment = document.createDocumentFragment();
fragment = fragment.appendChild(document.createElement('h1'));
fragment.appendChild(document.createTextNode(dataToRender.title));
fragment = fragment.parentNode;
fragment = fragment.appendChild(document.createElement('h2'));
fragment.appendChild(document.createTextNode(dataToRender.subTitle));
fragment = fragment.parentNode;
return fragment;
}
Is there any option?
Edit: Template function should not concatenate HTML strings. It decreases speed.
JQuery templates are working with strings internally. So Resig's Micro-Templating.
Edit2: I just did a benchmark on jsPerf. It is the first benchmark I did in JavaScript so some check it out(I am not sure if it's correct).
Check out jquery templates. http://api.jquery.com/category/plugins/templates/
It let's you create html fragments with keywords like "if", "each", etc and undeclared variables. Then you can call "tmpl" on a fragment from JavaScript with some values, and a DOM element is returned.
I had a go at this in this jsFiddle. Replacing a chunk of content is fastest when using DOM methods, but setting innerHTML isn't cripplingly slower and probably acceptable if your templates aren't very complex and you won't lose too much time in the string manipulation. (This isn't very surprising, "dealing with broken HTML quickly" is kind of what browsers are supposed to do and innerHTML is an ancient and popular property that probably had lots of optimisation go into it.) Adding another join() step in the innerHTML method also didn't really slow it down.
Conversely, using jQuery.tmpl() /and/ the DOM fragment method was orders of magnitude slower in Chrome on Mac. Either I'm doing something wrong in the dom_tmpl function, or deep-cloning DOM nodes is inherently slow.
I commented out the append tests because they froze up the tab process when you run the whole suite - thousands through tens of thousands of nodes shoved into a document probably confuse Chrome somehow. Appending with innerHTML alone ended up glacially slow because the string ends up being really huge.
The conclusion would seem to be: unless done stupidly or on very large strings, concatenating strings in a templating library likely isn't going to be what makes it slow, while trying to be clever with cloning chunks of the DOM will. Also, jQuery.tmpl() handled 2000-ish ops/sec on my computer, and 500-ish on my iPhone 4, this is likely "fast enough" if you're targetting these platforms. It was also in the same ballpark as the DOM fragment function making the latter largely pointless.
If you mostly need to replace the content of existing nodes and your templates aren't very large, use Underscore.js's templating and innerHTML. Underscore.js seems to do ten passes through the whole string, so if your templates /are/ large this could be an issue.
If you need to append to existing nodes, you can avoid serialising and reparsing the existing content by creating a wrapper element, seting its innerHTML, then append the wrapper element to the target node.
If you really want speed or your templates are crazy large, you'll probably have to do something like having a server-side script precompile your templates into Javascript that creates the respective nodes.
(Disclaimer: I don't claim to be any good at constructing test cases and benchmarks and only tested this in WebKit, you should tailor this to your use case and get a more relevant set of numbers.)
Update: I updated my jsFiddle benchmark to not use any jQuery features, in case its presence (user data on nodes etc.) was the cause of DOM node cloning being slow. Didn't help much.
I don't know if this is what you're searching for, but Underscore.js has a template utility.
Also, jquery can return the DOM of a matched element.
You could create individual objects that represent regions of your page or even go down as far as the individual element level, and do all this without resorting to DOM scripting which will be super slow. For instance:
function buttonFrag(data) {
this.data = data;
}
buttonFrag.prototype = (function() {
return {
_html : function(h) {
h.push("<h1>",data.title,"</h1>");
h.push("<h2>",data.subTitle,"</h2>");
},
render : function(id) {
var html = [];
this._html(html);
document.getElementById.innerHTML = html.join("");
}
}
})();
To implement this, you'd simply create a new object then invoke its render method to an id on your page:
var titleFragObj = new titleFrag({title: "My Title",subTitle: "My Subtitle");
titleFragObj.render("someId");
Of course you could get a bit more creative about the render method and use something like jQuery to write to a selector, then use the .html or .append methods like this:
render : function(selectorString, bAppend) {
var html = [];
this._html(html);
var htmlString = html.join("");
var destContainer = $(selectorString);
if (bAppend) {
destContainer.append(htmlString);
} else {
destContainer.html(htmlString);
}
}
In that case you'd just provide a selector string and whether or not you want to append to the end of the container, or completely replace its contents:
titleFragObj.render("#someId",true);
You could even go so far as to create a base object from which all your fragments descend from, then all you'd do is override the _html method:
function baseFragement(data) {
this.data = data;
}
baseFragment.prototype = (function() {
return {
_html : function() {
//stub
},
render : function(selectorString, bAppend) {
var html = [];
this._html(html);
var htmlString = html.join("");
var destContainer = $(selectorString);
if (bAppend) {
destContainer.append(htmlString);
} else {
destContainer.html(htmlString);
}
}
};
})();
Then all descendants would look something like this:
function titleFrag(data) {
baseFragment.call(this,data);
}
titleFrag.prototype = new baseFragment();
titleFrag.prototype._html = function() {
h.push("<h1>",data.title,"</h1>");
h.push("<h2>",data.subTitle,"</h2>");
}
You could create an entire library of little fragment generators that descend from that common base class.
Let's say that we have a DIV x on the page and we want to duplicate ("copy-paste") the contents of that DIV into another DIV y. We could do this like so:
y.innerHTML = x.innerHTML;
or with jQuery:
$(y).html( $(x).html() );
However, it appears that this method is not a good idea, and that it should be avoided.
(1) Why should this method be avoided?
(2) How should this be done instead?
Update:
For the sake of this question let's assume that there are no elements with ID's inside the DIV x.
(Sorry I forgot to cover this case in my original question.)
Conclusion:
I have posted my own answer to this question below (as I originally intended). Now, I also planed to accept my own answer :P, but lonesomeday's answer is so amazing that I have to accept it instead.
This method of "copying" HTML elements from one place to another is the result of a misapprehension of what a browser does. Browsers don't keep an HTML document in memory somewhere and repeatedly modify the HTML based on commands from JavaScript.
When a browser first loads a page, it parses the HTML document and turns it into a DOM structure. This is a relationship of objects following a W3C standard (well, mostly...). The original HTML is from then on completely redundant. The browser doesn't care what the original HTML structure was; its understanding of the web page is the DOM structure that was created from it. If your HTML markup was incorrect/invalid, it will be corrected in some way by the web browser; the DOM structure will not contain the invalid code in any way.
Basically, HTML should be treated as a way of serialising a DOM structure to be passed over the internet or stored in a file locally.
It should not, therefore, be used for modifying an existing web page. The DOM (Document Object Model) has a system for changing the content of a page. This is based on the relationship of nodes, not on the HTML serialisation. So when you add an li to a ul, you have these two options (assuming ul is the list element):
// option 1: innerHTML
ul.innerHTML += '<li>foobar</li>';
// option 2: DOM manipulation
var li = document.createElement('li');
li.appendChild(document.createTextNode('foobar'));
ul.appendChild(li);
Now, the first option looks a lot simpler, but this is only because the browser has abstracted a lot away for you: internally, the browser has to convert the element's children to a string, then append some content, then convert the string back to a DOM structure. The second option corresponds to the browser's native understanding of what's going on.
The second major consideration is to think about the limitations of HTML. When you think about a webpage, not everything relevant to the element can be serialised to HTML. For instance, event handlers bound with x.onclick = function(); or x.addEventListener(...) won't be replicated in innerHTML, so they won't be copied across. So the new elements in y won't have the event listeners. This probably isn't what you want.
So the way around this is to work with the native DOM methods:
for (var i = 0; i < x.childNodes.length; i++) {
y.appendChild(x.childNodes[i].cloneNode(true));
}
Reading the MDN documentation will probably help to understand this way of doing things:
appendChild
cloneNode
childNodes
Now the problem with this (as with option 2 in the code example above) is that it is very verbose, far longer than the innerHTML option would be. This is when you appreciate having a JavaScript library that does this kind of thing for you. For example, in jQuery:
$('#y').html($('#x').clone(true, true).contents());
This is a lot more explicit about what you want to happen. As well as having various performance benefits and preserving event handlers, for example, it also helps you to understand what your code is doing. This is good for your soul as a JavaScript programmer and makes bizarre errors significantly less likely!
You can duplicate IDs which need to be unique.
jQuery's clone method call like, $(element).clone(true); will clone data and event listeners, but ID's will still also be cloned. So to avoid duplicate IDs, don't use IDs for items that need to be cloned.
It should be avoided because then you lose any handlers that may have been on that
DOM element.
You can try to get around that by appending clones of the DOM elements instead of completely overwriting them.
First, let's define the task that has to be accomplished here:
All child nodes of DIV x have to be "copied" (together with all its descendants = deep copy) and "pasted" into the DIV y. If any of the descendants of x has one or more event handlers bound to it, we would presumably want those handlers to continue working on the copies (once they have been placed inside y).
Now, this is not a trivial task. Luckily, the jQuery library (and all the other popular libraries as well I assume) offers a convenient method to accomplish this task: .clone(). Using this method, the solution could be written like so:
$( x ).contents().clone( true ).appendTo( y );
The above solution is the answer to question (2). Now, let's tackle question (1):
This
y.innerHTML = x.innerHTML;
is not just a bad idea - it's an awful one. Let me explain...
The above statement can be broken down into two steps.
The expression x.innerHTML is evaluated,
That return value of that expression (which is a string) is assigned to y.innerHTML.
The nodes that we want to copy (the child nodes of x) are DOM nodes. They are objects that exist in the browser's memory. When evaluating x.innerHTML, the browser serializes (stringifies) those DOM nodes into a string (HTML source code string).
Now, if we needed such a string (to store it in a database, for instance), then this serialization would be understandable. However, we do not need such a string (at least not as an end-product).
In step 2, we are assigning this string to y.innerHTML. The browser evaluates this by parsing the string which results in a set of DOM nodes which are then inserted into DIV y (as child nodes).
So, to sum up:
Child nodes of x --> stringifying --> HTML source code string --> parsing --> Nodes (copies)
So, what's the problem with this approach? Well, DOM nodes may contain properties and functionality which cannot and therefore won't be serialized. The most important such functionality are event handlers that are bound to descendants of x - the copies of those elements won't have any event handlers bound to them. The handlers got lost in the process.
An interesting analogy can be made here:
Digital signal --> D/A conversion --> Analog signal --> A/D conversion --> Digital signal
As you probably know, the resulting digital signal is not an exact copy of the original digital signal - some information got lost in the process.
I hope you understand now why y.innerHTML = x.innerHTML should be avoided.
I wouldn't do it simply because you're asking the browser to re-parse HTML markup that has already been parsed.
I'd be more inclined to use the native cloneNode(true) to duplicate the existing DOM elements.
var node, i=0;
while( node = x.childNodes[ i++ ] ) {
y.appendChild( node.cloneNode( true ) );
}
Well it really depends. There is a possibility of creating duplicate elements with the same ID, which is never a good thing.
jQuery also has methods that can do this for you.
I'm trying to reduce DOM manipulation during my javascript execution, and I thought about lazy-writing DOM elements, versus writing a large section of the DOM hidden and unhiding the needed parts later.
To do this, I've separated out all my DOM content into a JSP which I load into a variable via ajax and manipulate with jQuery. However, if jQuery is doing this manipulation in a hidden div on the DOM, I've achieved no performance gain. Help?
New Answer
I just checked a couple of your recent questions and I think I see what you're asking here.
When you call jQuery like $('selector string', context) it gets routed to this:
jQuery(context).find('selector string')
do you see that? The context argument becomes the first argument to a [sub] call to jQuery. That in turn gets routed to this batch of shortcut code:
// Handle $(DOMElement)
if ( selector.nodeType ) {
this[0] = selector;
this.length = 1;
this.context = selector;
return this;
}
so the context argument gets stored on the returned jQuery object in the .context property.
Now judging from your comment to this question, you are aware that you should have your server side code respond with a "Content-Type: text/xml" header. Doing so will instruct the browser to automatically create a DOMDocument tree from your xml response body (FYI it's accessible in the responseXML property on the XMLHttpRequest object itself - jQuery simply passes it on to your success handler).
To answer your question: if you pass this DOMDocument object on to jQuery as the context parameter and proceed to modify attributes on this object, there is no manipulation in any hidden/temporary div that you have to worry about. The DOMDocument is simply mutated in-place.
Hope that answers your question.
Original Answer:
If you have html in a string and you need it to be nodes in a DOM structure, you've got to convert it somehow. Putting it inside a throwaway element is the only way I know to do this while leveraging the built-in browser parser:
// HTML string
var s = '<span>text</span>';
var div = document.createElement('div');
div.innerHTML = s;
var elements = div.childNodes;
Many of the libraries do this more or less, including jQuery. If it makes you feel any better, the throwaway div above is not inserted into the DOM, thereby avoiding DOM reflow (a very expensive operation), saving some cycles.
You might want to look at document fragments as a container to hold the generated DOM nodes.
var fragment = document.createDocumentFragment();
for ( var e = 0; e < elems.length; e++ ) {
// append elements to fragment
fragment.appendChild( elems[e] );
}