I sometimes need to add elements (such as a new link and image) to an existing HTML page, but I only have access to a small portion of the page far from where I need to insert elements. I want to use DOM based JavaScript techniques, and I must avoid using document.write().
Thus far, I've been using something like this:
// Create new image element
var newImg = document.createElement("img");
newImg.src = "images/button.jpg";
newImg.height = "50";
newImg.width = "150";
newImg.alt = "Click Me";
// Create new link element
var newLink = document.createElement("a");
newLink.href = "/dir/signup.html";
// Append new image into new link
newLink.appendChild(newImg);
// Append new link (with image) into its destination on the page
document.getElementById("newLinkDestination").appendChild(newLink);
Is there a more efficient way that I could use to accomplish the same thing? It all seems necessary, but I'd like to know if there's a better way I could be doing this.
There is a more efficient way, and seems to be using documentFragments if possible.
Check it out: http://ejohn.org/blog/dom-documentfragments/ . Also this way should be less error prone and more maintainable than starting to mix up huge strings literals and setting them as innerHTML of some other DOM objects.
Just beware, that innerHTML is both non-standard and notoriously buggy.
Nothing wrong with that. Using innerHTML would be marginally faster and probably fewer characters but not noticeable for something of this scale, and my personal preference is for the more standard, uniformly supported and safer DOM methods and properties.
One minor point: the height and width properties of <img> elements should be numbers rather than strings.
If you're not adding many things, the way you've been doing it is ideal vs innerHTML. If you're doing it frequently though, you might just create a generic function/object that takes the pertinent information as parameters and does the dirty work. IE
function addImage(src,width,height,alt,appendElem,href) {...}
I do this often in my own projects using prototyping to save time.
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'));
When appending elements to a page with jQuery, seems I have basically 3 options, summarized here:
function tripleAppend() {
//1 - append using jQuery, like so:
var withjquery = $("<p></p>").text("Appending with jquery")
//2 - Using the html directly formatted:
var withhtml = "<p>Appending html</p>"
//3 - Create & append a DOM element:
var withDOM = document.createElement("p");
withDOM.innerHTML = "Appending a DOM element"
//or withDOM.textContent = (....)
$("body").append(withjquery, withhtml, withDOM)
}
Are there important differences between those 3 approaches - any one that is more portable or more limited for a more advanced append? If I'm going to pick one these as my "go-to" solution, any reason to lean for one or the other (saved personal syntactic preferences)?
Whichever one fits your project.
It's both circumstantial and preferential.
Do you like shorter syntax and the features of the JQuery object? Then use JQuery.
On that note, JQuery is a subset of JavaScript, so in the background JQuery is doing the same thing that JavaScript is doing, just with an easier to understand and less cluttered syntax.
One difference between JavaScript createElement and innerHTML/JQuery in your example is that JQuery and innerHTML both require the provided HTML to be parsed, and then turned into DOM nodes, and then appended. Pure JavaScript will always be faster in that scenario,because you are skipping that parsing step, however, it's most likely a negligible difference in performance.
It really all comes down to what you're comfortable with and how concerned you are with optimizing your code for performance - and in this case I would venture to say that if you did decide to use createElement because of performance, it could be considered a superfluous optimization.
Is there a way to create a new element in a Document object from the tag source? Essentially what I'd like to do is something to effect of:
myDocument.createElement('<sometag attr="lol">');
No, native DOM API for .createElement doesn't support that syntax. You need to create the plain Element and set any property either directly on the object
newElem.attr = "lol";
or (better), use .setAttribute()
newElem.setAttribute( 'attr', 'lol' );
What you "could" do is do create a documentFragment, then use .innerHTML to write that string into that fragment and finally .append its contents to its target destination.
well, use innerHTML instead of trying to hack dom manipulation for this kind of thing.
To do so, create a valid node with dom if you want (or get it via getElementById), then set innerHTML of this node with your code
The innerHTML property works here - but it's a tad bit difficult to place the thing where you want it sometimes. For example:
var div = document.createElement('div');
div.innerHTML = "<img src='http://www.google.com/logos/classicplus.png'/>";
In my humble opinion, it's a heck of a lot easier to use jQuery to handle it for you, as it comes with all of the jQuery methods already attached.
var div = $("<div class='blah'><img src='http://www.google.com/logos/classicplus.png'/></div>");
div.find('img').hide();
div.appendTo('body');
div.find('img').fadeIn();
This question already has answers here:
Deep cloning vs setting of innerHTML: what's faster?
(2 answers)
Closed 8 years ago.
I'm looking at a problem that needs a complex block of divs to be created once for each element in a set of ~100 elements.
Each individual element is identical except for the content, and they look (in HTML) something like this:
<div class="class0 class1 class3">
<div class="spacer"></div>
<div id="content">content</div>
<div class="spacer"></div>
<div id="content2">content2</div>
<div class="class4">content3</div>
<div class="spacer"></div>
<div id="footer">content3</div>
</div>
I could either:
1) Create all the elements as innerHTML with string concatenation to add the content.
2) Use createElement, setAttribute and appendChild to create and add each div.
Option 1 gets a slightly smaller file to download, but option 2 seems to be slightly faster to render.
Other than performance is there a good reason to go via one route or the other? Any cross-browser problems / performance gremlins I should test for?
...or should I try the template and clone approach?
Many thanks.
Depends on what's "better" for you.
Performance
From a performance point of view, createElement+appendChild wins by a LOT. Take a look at this jsPerf I created when I compare both and the results hit in the face.
innerHTML: ~120 ops/sec
createElement+appendChild: ~145000 ops/sec
(on my Mac with Chrome 21)
Also, innerHTML triggers page reflow.
On Ubuntu with Chrome 39 tests get similar results
innerHTML: 120000 ops/sec
createElement: 124000 ops/sec
probably some optimisation take place.
On Ubuntu with QtWebkit based browser Arora (wkhtml also QtWebkit) results are
innerHTML: 71000 ops/sec
createElement: 282000 ops/sec
it seems createElement faster in average
Mantainability
From a mantainability point of view, I believe string templates help you a lot. I use either Handlebars (which I love) or Tim (for project which need smallest footprints). When you "compile" (prepare) your template and it's ready for appending it to the DOM, you use innerHTML to append the template string to the DOM. On trick I do to avoid reflow is createElement for a wrapper and in that wrapper element, put the template with innerHTML. I'm still looking for a good way to avoid innerHTML at all.
Compatibility
You don't have to worry here, both methods are fully supported by a broad range of browsers (unlike altCognito says). You can check compatibility charts for createElement and appendChild.
Neither. Use a library like jQuery, Prototype, Dojo or mooTools because both of these methods are fraught with trouble:
Did you know that innerHTML on tables for IE is readonly?
Did you know for the select element it's broken as well?
How about problems with createElement?
The writers of the major javascript libraries have spent a lot of time and have entire bug tracking systems to make sure that when you call their DOM modifying tools they actually work.
If you're writing a library to compete with the above tools (and good luck to you if you are), then I'd choose the method based on performance, and innerHTML has always won out in the past, and since innerHTML is a native method, it's a safe bet it will remain the fastest.
altCognito makes a good point - using a library is the way to go. But if was doing it by hand, I would use option #2 - create elements with DOM methods. They are a bit ugly, but you can make an element factory function that hides the ugliness. Concatenating strings of HTML is ugly also, but more likely to have security problems, especially with XSS.
I would definitely not append the new nodes individually, though. I would use a DOM DocumentFragment. Appending nodes to a documentFragment is much faster than inserting them into the live page. When you're done building your fragment it just gets inserted all at once.
John Resig explains it much better than I could, but basically you just say:
var frag = document.createDocumentFragment();
frag.appendChild(myFirstNewElement);
frag.appendChild(mySecondNewElement);
...etc.
document.getElementById('insert_here').appendChild(frag);
Personally, I use innerHTML because it's what I'm used to and for something like this, the W3C methods add a lot of clutter to the code.
Just a possible way to cut down on the number of div's however, are there any reasons you are using spacer elements instead of just editing the margins on the content divs?
I don't think there's much to choose between them. In the olden days (IE6, FF1.5), innerHTML was faster (benchmark), but now there doesn't seem to be a noticeable difference in most cases.
According to the mozilla dev. docs there are a few situations where innerHTML behaviour varies between browsers (notably inside tables), so createElement will give you more consistency - but innerHTML is usually less to type.
Since you mentioned template and clone, you may be interested in this question:
Deep cloning vs setting of innerHTML: what’s faster?
Another option is to use a DOM wrapper, such as DOMBuilder:
DOMBuilder.apply(window);
DIV({"class": "class0 class1 class3"},
DIV({"class": "spacer"}),
DIV({id: "content"}, "content"),
DIV({"class": "spacer"}),
DIV({id: "content2"}, "content2"),
DIV({"class": "class4"}, "content3"),
DIV({"class": "spacer"}),
DIV({id: "footer"}, "content3")
);
Personally, if each item is going to need the exact same structure created I would go with the cloning approach. If there's any logic involved in creating the structure into which the content will go, I'd rather maintain something like the above than fiddling about with strings. If that approach turned out to be too slow, I'd fall back to innerHTML.
Neither. Use cloneNode.
var div = document.createElement('div');
var millionDivs = [div];
while (millionDivs.length < 1e6) millionDivs.push(div.cloneNode())
As I know, the fastest way is to evade DOM editing as long as it is possible. That mean, it is better to create a big string and then put it into innerHTML. But there is a remark for this: don't make too many operation on big strings, it is faster to use array of strings and then join them all.
For a complex problem like this, I usually use the innerHTML methods because they are a)easier to read and modify code-wise b)more convenient to use in loops. As the top post says, they unfortunately fail in IE(6,7,8) on <table>, <thead>,<tbody>,<tr>,<tfoot>, <select>, <pre> elements.
1) Create all the elements as innerHTML with string concatenation to add the content.
2) Use createElement, setAttribute and appendChild to create and add each div.
3) compromise. Create all the elements in one go as innerHTML (which avoids a lot of childnode list manipulation slowness), then write the content that changes on each item using data/attribute access (avoiding all that nasty mucking around with having to HTML-escape content). eg. something like:
var html= (
'<div class="item">'+
'<div class="title">x</div>'+
'<div class="description">x</div>'+
'</div>'
);
var items= [
{'id': 1, 'title': 'Potato', 'description': 'A potato'},
{'id': 2, 'title': 'Kartoffel', 'description': 'German potato'},
// ... 100 other items ...
];
function multiplyString(s, n) {
return new Array(n+1).join(s);
}
var parent= document.getElementById('itemcontainer');
parent.innerHTML= multiplyString(html, items.length);
for (var i= 0; i<items.length; i++) {
var item= items[i];
var node= parent.childNodes[i];
node.id= 'item'+item.id;
node.childNodes[0].firstChild.data= item.title;
node.childNodes[1].firstChild.data= item.description;
}
Can also be combined with Neall's tip about DocumentFragments.
I'm trying to find a way that will add / update attribute using JavaScript. I know I can do it with setAttribute() function but that doesn't work in IE.
You can read here about the behaviour of attributes in many different browsers, including IE.
element.setAttribute() should do the trick, even in IE. Did you try it? If it doesn't work, then maybe
element.attributeName = 'value' might work.
What seems easy is actually tricky if you want to be completely compatible.
var e = document.createElement('div');
Let's say you have an id of 'div1' to add.
e['id'] = 'div1';
e.id = 'div1';
e.attributes['id'] = 'div1';
e.createAttribute('id','div1')
These will all work except the last in IE 5.5 (which is ancient history at this point but still is XP's default with no updates).
But there are contingencies, of course.
Will not work in IE prior to 8:e.attributes['style']
Will not error but won't actually set the class, it must be className:e['class'] .
However, if you're using attributes then this WILL work:e.attributes['class']
In summary, think of attributes as literal and object-oriented.
In literal, you just want it to spit out x='y' and not think about it. This is what attributes, setAttribute, createAttribute is for (except for IE's style exception). But because these are really objects things can get confused.
Since you are going to the trouble of properly creating a DOM element instead of jQuery innerHTML slop, I would treat it like one and stick with the e.className = 'fooClass' and e.id = 'fooID'. This is a design preference, but in this instance trying to treat is as anything other than an object works against you.
It will never backfire on you like the other methods might, just be aware of class being className and style being an object so it's style.width not style="width:50px". Also remember tagName but this is already set by createElement so you shouldn't need to worry about it.
This was longer than I wanted, but CSS manipulation in JS is tricky business.
Obligatory jQuery solution. Finds and sets the title attribute to foo. Note this selects a single element since I'm doing it by id, but you could easily set the same attribute on a collection by changing the selector.
$('#element').attr( 'title', 'foo' );
What do you want to do with the attribute? Is it an html attribute or something of your own?
Most of the time you can simply address it as a property: want to set a title on an element? element.title = "foo" will do it.
For your own custom JS attributes the DOM is naturally extensible (aka expando=true), the simple upshot of which is that you can do element.myCustomFlag = foo and subsequently read it without issue.