I am actually making a Sidebar Gadget, (which is AJAX-based) and I am looking for a way to extract a single element from an AJAX Request.
The only way I found yet was to do something like that:
var temp = document.createElement("div");
temp.innerHTML = HttpRequest.innerText;
document.body.appendChild(temp);
temp.innerHTML = document.getElementByID("WantedElement").innerText;
But it is pretty ugly, I would like to extract WantedElement directly from the request without adding it to the actual document...
Thank you!
If you're in control of the data, the way you're doing it is probably the best method. Other answers here have their benefits but also they're all rather flawed. For instance, the querySelector() method is only available to Windows Desktop Gadgets running in IE8 mode on the host machine. Regular expressions are particularly unreliable for parsing HTML and should not be used.
If you're not in control of the data or if the data is not transferred over a secure protocol, you should be more concerned about security than code aesthetics -- you may be introducing potential security risks to the gadget and the host machine by inserting unsanitized HTML into the document. Since gadgets run with user or admin level privileges, the obvious security risk is untrusted source/MITM script injection, leaving a hole for malicious scripts to wreak havoc on the machine it's running on.
One potential solution is to use the htmlfile ActiveXObject:
function getElementFromResponse(divId)
{
var h = new ActiveXObject("htmlfile");
h.open();
// disable activex controls
h.parentWindow.ActiveXObject = function () {};
// write the html to the document
h.write(html);
h.close();
return h.getElementById("divID").innerText;
}
You could also make use of IE8's toStaticHTML() method, but your gadget would need to be running in IE8 mode.
One option would be to use regular expressions:
var str = response.match(/<div id="WantedElement">(.+)<\/div>/);
str[0]; // contents of div
However, if your server response is more complex, I'd suggest you to use a data format like JSON for the response. Then it would be much cleaner to parse at the client side.
You could append the response from XMLHttpRequest inside a hidden div, and then call getElementById to get the desired element. Later remove the div when done with it. Or maybe create a function that handles this for you.
function addNinjaNodeToDOM(html) {
var ninjaDiv = document.createElement("div");
ninjaDiv.innerHTML = html;
ninjaDiv.style.display = 'none';
return ninjaDiv;
}
var wrapper = addNinjaNodeToDOM(HttpRequest.innerText);
var requiredNode = wrapper.getElementById("WantedElement");
// do something with requiredNode
document.body.removeChild(wrapper); // remove when done
The only reason for appending it to the DOM was because getElementById will not work unless its part of the DOM tree. See MDC.
However, you can still run selector and XPath queries on detached DOM nodes. That would save you from having you to append elements to the DOM.
var superNinjaDiv = document.createElement('div');
superNinjaDiv.innerHTML = html;
var requiedNode = superNinjaDiv.querySelector("[id=someId]");
I think using getElementById to lookup the element in this case is not a good approach. This is because of extra steps you have to take to use it. You wrap the element in a DIV, inject in DOM, lookup your element using getElementById and then remove the injected DIV from DOM.
DOM manipulation is expensive and injection might cause unnecessary reflow as well. The problem is that you have a document.getElementById and not a element.getElementById which would allow you to query without injection in the document.
To solve this, using querySelector is an obvious solution which is far more easier. Else, I would suggest using getElementsByClassName if you can and if your element has a class defined.
getElementsByClassName is defined on ELEMENT and hence can be used without injecting the element in DOM.
Hope this helps.
It's somewhat unusual to pass HTML through an AJAX request; normally you pass a JSON string that the client can evaluate directly, and work with that
That being said, I don't think there's a way to parse HTML in javascript the way you want that's cross-browser, but here's a way to do it in Mozilla derivatives:
var r = document.createRange();
r.selectNode(document.body);
var domNode = r.createContextualFragment(HTTPRequest.innerText);
Related
I was wondering sense there were so many ways to add content dynamically, which way is better.
I know of only of three methods
that add to a body's text node
document.body.innerHTML+="<div>"+myContent+"</div>";
add to the current text node
document.write("<div>"+myContent+"</div>");
and add a whole new node
//case based but for example purpose
var node = document.createElement("div");
node.appendChild(document.createTextNode(myContent));
document.body.appendChild(node);
The third example is obviously more lines. Which is why I'm wondering why I should even consider it. Given download times tend to be worse off then parse times?
document.body.innerHTML+="<div>"+myContent+"</div>";
Destroys any existing event handlers and form data, and creates new elements from the generated source code.
Doesn't safely escape myContent.
document.write("<div>"+myContent+"</div>");
Wipes out the entire document if it is in a closed state
Doesn't safely escape myContent.
var node = document.createElement("div");
node.appendChild(document.createTextNode(myContent));
document.body.appendChild(node);
Verbose, but safe
Option 3 is usually the best.
The third example is obviously more lines. Which is why I'm wondering why I should even consider it. Given download times tend to be worse off then parse times?
Micro-optimisations generally aren't worth the effort. Most of the size difference will be eliminated by HTTP compression anyway.
1) document.body.innerHTML+="<div>"+myContent+"</div>";
Useful when you inserting static HTML (Event Listener not required) in to specific <element>. In this case performance is good.
2) document.write("<div>"+myContent+"</div>");
I will not recommend this way to do. This will overwrite full page.
3) Useful in case Event Listener adding dynamically.
var node = document.createElement("div");
// You can Add Event here.
node.addEventListener("click", function(){ alert("Event Added"); });
node.appendChild(document.createTextNode(myContent));
document.body.appendChild(node);
Pretty simple question that I couldn't find an answer to, maybe because it's a non-issue, but I'm wondering if there is a difference between creating an HTML object using Javascript or using a string to build an element. Like, is it a better practice to declare any HTML elements in JS as JS objects or as strings and let the browser/library/etc parse them? For example:
jQuery('<div />', {'class': 'example'});
vs
jQuery('<div class="example></div>');
(Just using jQuery as an example, but same question applies for vanilla JS as well.)
It seems like a non-issue to me but I'm no JS expert, and I want to make sure I'm doing it right. Thanks in advance!
They're both "correct". And both are useful at different times for different purposes.
For instance, in terms of page-speed, these days it's faster to just do something like:
document.body.innerHTML = "<header>....big string o' html text</footer>";
The browser will spit it out in an instant.
As a matter of safety, when dealing with user-input, it's safer to build elements, attach them to a documentFragment and then append them to the DOM (or replace a DOM node with your new version, or whatever).
Consider:
var userPost = "My name is Bob.<script src=\"//bad-place.com/awful-things.js\"></script>",
paragraph = "<p>" + userPost + "</p>";
commentList.innerHTML += paragraph;
Versus:
var userPost = "My name is Bob.<script src=\"//bad-place.com/awful-things.js\"></script>",
paragraph = document.createElement("p");
paragraph.appendChild( document.createTextNode(userPost) );
commentList.appendChild(paragraph);
One does bad things and one doesn't.
Of course, you don't have to create textNodes, you could use innerText or textContent or whatever (the browser will create the text node on its own).
But it's always important to consider what you're sharing and how.
If it's coming from anywhere other than a place you trust (which should be approximately nowhere, unless you're serving static pages, in which case, why are you building html?), then you should keep injection in mind -- only the things you WANT to be injected should be.
Either can be preferable depending on your particular scenario—ie, if everything is hard-coded, option 2 is probably better, as #camus said.
One limitation with the first option though, is that this
$("<div data-foo='X' />", { 'class': 'example' });
will not work. That overload expects a naked tag as the first parameter with no attributes at all.
This was reported here
1/ is better if your attribubes depends on variables set before calling the $ function , dont have to concatenate strings and variables. Aside from that fact ,since you can do both , and it's just some js code somebody else wrote , not a C++ DOM API hardcoded in the browser...
I'm creating a document fragment as follow:
var aWholeHTMLDocument = '<!doctype html> <html><head></head><body><h1>hello world</h1></body></html>';
var frag = document.createDocumentFragment();
frag.innerHTML = aWholeHTMLDocument;
The variable aWholeHTMLDocument contains a long string that is the entire html document of a page, and I want to insert it inside my fragment in order to generate and manipulate the DOM dynamically.
My question is, once I have added that string to frag.innerHTML, shouldn't it load this string and convert it to a DOM object?
After setting innerHTML, shouldn't I have access to the DOM through a property?
I tried frag.childNodes but it doesn't seem to contain anything, and all I want is to just access that newly created DOM.
While DocumentFragment does not support innerHTML, <template> does.
The content property of a <template> element is a DocumentFragment so it behaves the same way. For example, you can do:
var tpl = document.createElement('template');
tpl.innerHTML = '<tr><td>Hello</td><td>world</td></tr>';
document.querySelector('table').appendChild(tpl.content);
The above example is important because you could not do this with innerHTML and e.g. a <div>, because a <div> does not allow <tr> elements as children.
NOTE: A DocumentFragment will still strip the <head> and <body> tags, so it won't do what you want either. You really need to create a whole new Document.
You can't set the innerHTML of a document fragment like you would do with a normal node, that's the problem. Adding a standard div and setting the innerHTML of that is the common solution.
DocumentFragment inherits from Node, but not from Element that contains the .innerHTML property.
In your case I would use the <template> tag. In inherits from Element and it has a nifty HTMLTemplateElement.content property that gives you a DocumentFragment.
Here's a simple helpermethod you could use:
export default function StringToFragment(string) {
var renderer = document.createElement('template');
renderer.innerHTML = string;
return renderer.content;
}
I know this question is old, but I ran into the same issue while playing with a document fragment because I didn't realize that I had to append a div to it and use the div's innerHTML to load strings of HTML in and get DOM Elements from it. I've got other answers on how to do this sort of thing, better suited for whole documents.
In firefox (23.0.1) it appears that setting the innerHTML property of the document fragment doesn't automatically generate the elements. It is only after appending the fragment to the document that the elements are created.
To create a whole document use the document.implementation methods if they're supported. I've had success doing this on Firefox, I haven't really tested it out on other browsers though. You can look at HTMLParser.js in the AtropaToolbox for an example of using document.implementation methods. I've used this bit of script to XMLHttpRequest pages and manipulate them or extract data from them. Scripts in the page are not executed though, which is what I wanted though it may not be what you want. The reason I went with this rather verbose method instead of trying to use the parsing available from the XMLHttpRequest object directly was that I ran into quite a bit of trouble with parsing errors at the time and I wanted to specify that the doc should be parsed as HTML 4 Transitional because it seems to take all kinds of slop and produce a DOM.
There is also a DOMParser available which may be easier for you to use. There is an implementation by Eli Grey on the page at MDN for browsers that don't have the DOMParser but do support document.implementation.createHTMLDocument. The specs for DOMParser specify that scripts in the page are not executed and the contents of noscript tags be rendered.
If you really need scripts enabled in the page you could create an iFrame with 0 height, 0 width, no borders, etc. It would still be in the page but you could hide it pretty well.
There's also the option of using window.open() with document.write, DOM methods or whatever you like. Some browsers even let you do data URI's now.
var x = window.open( 'data:text/html;base64,' + btoa('<h1>hi</h1>') );
// wait for the document to load. It only takes a few milliseconds
// but we'll wait for 5 seconds so you can watch the child window
// change.
setTimeout(function () {
console.log(x.document.documentElement.outerHTML);
x.console.log('this is the console in the child window');
x.document.body.innerHTML = 'oh wow';
}, 5000);
So, you do have a few options for creating whole documents offscreen/hidden and manipulating them, all of which support loading the document from strings.
There's also phantomjs, an awesome project producing a headless scriptable web browser based on webkit. You'll have access to the local filesystem and be able to do pretty much whatever you want. I don't really know what you're trying to accomplish with your full page scripting and manipulation.
For a Firefox add-on, it probably makes more sense to use the document.implementation.createHTMLDocument method, and then go from the DOM that gives you.
With a document fragment you would append elements that you had created with document.createElement('yourElement'). aWholeHTMLDocument is merely text. Also, unless your using frames I'm not sure why you would need to create the whole HTML document just use what is inside the <body> tags.
Use appendChild
see https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment
var fragment = document.createDocumentFragment();
... fragment.appendChild(some element);
document.querySelector('blah').appendChild(fragment);
Here is a solution for converting a HTML string into a DOM object:
let markup = '<!doctype html><html><head></head><body><h1>hello world</h1></body></html>';
let range = document.createRange();
let fragment = range.createContextualFragment(markup); //Creates a DOM object
The string does not need to be a complete HTML document.
Use querySelector() to get a child of the document fragment (you probably want the body, or some child of the body). Then get the innerHTML.
document.body.innerHTML = aWholeHTMLDocument.querySelector("body").innerHTML
or
aWholeHTMLDocument.querySelector("body").childNodes;
See https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment.querySelector
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'));
I got this bad feeling about how I insert larger amounts of HTML.
Lets assume we got:
var html="<table>..<a-lot-of-other-tags />..</table>"
and I want to put this into
$("#mydiv")
previously I did something like
var html_obj = $(html);
$("#mydiv").append(html_obj);
Is it correct that jQuery is parsing html to create DOM-Objects ? Well this is what I read somewhere (UPDATE: I meant that I have read, jQuery parses the html to create the whole DOM tree by hand - its nonsense right?!), so I changed my code:
$("#mydiv").attr("innerHTML", $("#mydiv").attr("innerHTML") + html);
Feels faster, is it ? And is it correct that this is equivalent to:
document.getElementById("mydiv").innerHTML += html ? or is jquery doing some additional expensive stuff in the background ?
Would love to learn alternatives as well.
Try the following:
$("#mydiv").append(html);
The other answers, including the accepted answer, are slower by 2-10x: jsperf.
The accepted answer does not work in IE 6, 7, and 8 because you can't set innerHTML of a <table> element, due to a bug in IE: jsbin.
innerHTML is remarkably fast, and in many cases you will get the best results just setting that (I would just use append).
However, if there is much already in "mydiv" then you are forcing the browser to parse and render all of that content again (everything that was there before, plus all of your new content). You can avoid this by appending a document fragment onto "mydiv" instead:
var frag = document.createDocumentFragment();
frag.innerHTML = html;
$("#mydiv").append(frag);
In this way, only your new content gets parsed (unavoidable) and the existing content does not.
EDIT: My bad... I've discovered that innerHTML isn't well supported on document fragments. You can use the same technique with any node type. For your example, you could create the root table node and insert the innerHTML into that:
var frag = document.createElement('table');
frag.innerHTML = tableInnerHtml;
$("#mydiv").append(frag);
What are you attempting to avoid? "A bad feeling" is incredibly vague. If you have heard "the DOM is slow" and decided to "avoid the DOM", then this is impossible. Every method of inserting code into a page, including innerHTML, will result in DOM objects being created. The DOM is the representation of the document in your browser's memory. You want DOM objects to be created.
The reason why people say "the DOM is slow" is because creating elements with document.createElement(), which is the official DOM interface for creating elements, is slower than using the non-standard innerHTML property in some browsers. This doesn't mean that creating DOM objects is bad, it is necessary to create DOM objects, otherwise your code wouldn't do anything at all.
The answer about using a DOM fragment is on the right track. If you have a bunch of html objects that you are constant inserting into the DOM then you will see some speed improvements using the fragment. This post by John Resig explains it pretty well:
http://ejohn.org/blog/dom-documentfragments/
The fastest way to append items
The fastest way to append to the DOM tree is to buffer all of your append in to a single DOM fragment, then append the dom fragment to the dom.
This is the method I use in my game engine.
//Returns a new Buffer object
function Buffer() {
//the framgment
var domFragment = document.createDocumentFragment();
//Adds a node to the dom fragment
function add(node) {
domFragment.appendChild(node);
}
//Flushes the buffer to a node
function flush(targetNode) {
//if the target node is not given then use the body
var targetNode = targetNode || document.body;
//append the domFragment to the target
targetNode.appendChild(domFragment);
}
//return the buffer
return {
"add": add,
"flush": flush
}
}
//to make a buffer do this
var buffer = Buffer();
//to add elements to the buffer do the following
buffer.add(someNode1);
//continue to add elements to the buffer
buffer.add(someNode2);
buffer.add(someNode3);
buffer.add(someNode4);
buffer.add(someN...);
//when you are done adding nodes flush the nodes to the containing div in the dom
buffer.flush(myContainerNode);
Using this object i am able to render ~1000 items to the screen ~40 times a second in firefox 4.
Here's a use case.
For starters, write a script that times how long it takes to do it 100 or 1,000 times with each method.
To make sure the repeats aren't somehow optimized away--I'm no expert on JavaScript engines--vary the html you're inserting every time, say by putting '0001' then '0002' then '0003' in a certain cell of the table.
I create a giant string with and then append this string with jquery.
Works good and fast, for me.
You mention being interested in alternatives. If you look at the listing of DOM-related jQuery plugins you'll find several that are dedicated to programatically generating DOM trees. See for instance SuperFlyDom or DOM Elements Creator; but there are others.