views:

504

answers:

6

Hi I would like to do dom selection and manipulation out of the dom.

The goal is to build my widget out of the dom and to insert it in the dom only once it is ready.

My issue is that getElementById is not supported on a document fragment. I also tried createElement and cloneNode, but it does not work either.

I am trying to do that in plain js. I am used to do this with jQuery which handles it nicely. I tried to find the trick in jQuery source, but no success so far...

Olivier

+1  A: 

I have done something similar, but not sure if it will meet your needs. Create a "holding area" such as a plain <span id="spanReserve"></span> or <td id="cellReserve"></td>. Then you can do something like this in JS function:

var holdingArea = document.getElementById('spanReserve'); holdingArea.innerHTML = widgetHTMLValue;

Jay
I can, but this would defeat the purpose. I want it to be out of the dom, in order to keep the dom as small as possible, and avoid costly dom accesses when selecting a node.
Olivvv
Given the many wonderful answers and comments on this page, and finding that you are quite limited in terms of native DOM functions when working with Fragments, perhaps a home-brewed solution to do what you want on the fragment instead of in the document might not be any more efficient than relying on the browser's native `getElementById` implementation and taking the performance "hit" from doing it this way. The only real way to know how costly this is or not, would be for you to test it both ways in your particular situation. Good luck!
Funka
A: 

It sounds like you are doing to right things. Not sure why it is not working out.



// if it is an existing element
var node = document.getElementById("footer").cloneNode(true);

// or if it is a new element use
// document.createElement("div");

// Here you would do manipulation of the element, setAttribute, add children, etc.
node.childNodes[1].childNodes[1].setAttribute("style", "color:#F00; font-size:128px");

document.documentElement.appendChild(node)



The Who
If there would be tags in the innerHTML of your "object", then getElementById would not work on them. It seems that objects returned by cloneNode are similar to documentFragment, and only support methods defined in the node api http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 not those defined in the doument api.But there must be a workaround, since jQuery is totally able to work with structures that are out of the dom. I would like to understand jQuery's trick and replicate it.
Olivvv
I'm confused. So in the above example. I have taken the footer element from this page, cloned it, altered some of it's style and then appended it to the document. This leaves the original footer as it was. Ultimately this is why jQuery and family exists -> the DOM is a mess.
The Who
It is probably a wrong idea to use "object" as a variable name.I tried your code in firebug (replaced object with obj) and it works fine. childNodes is supported by the dom api, so no surprise here.
Olivvv
Good point, object might be reserved. I changed it to Node.
The Who
A: 

EDIT:

what about something simple along these lines:

  DocumentFragment.prototype.getElementById = function(id) {
    for(n in this.childNodes){
      if(id == n.id){
        return n;
      }
    }

    return null;
  }


Why not just use jQuery or the selection API in whatever other lib youre using? AFAIK all the major libs support selection on fragments.

If you wan tto skip a larger lib like jQ/Prototype/Dojo/etc.. then you could jsut use Sizzle - its the selector engine that powers jQ and Dojo and its offered as a standalone. If thats out of the question as well then i suppose you could dive in to the Sizzle source and see whats going on. All in all though it seems like alot of effort to avoid a few 100k with the added probaility that the code you come up with is going to be slower runtime wise than all the work pulled into Sizzle or another open source library.

http://sizzlejs.com/

Oh also... i think (guessing) jQ's trick is that elements are not out of the DOM. I could be wrong but i think when you do something like:

$('<div></div>');

Its actually in the DOM document its just not part of the body/head nodes. Could be totally wrong about that though, its just a guess.

So you got me curious haha. I took a look at sizzle.. than answer is - its not using DOM methods. It seems using an algorithm that compares the various DOMNode properties mapped to types of selectors - unless im missing something... which is entirely possible :-)

However as noted below in comments it seems Sizzle DOES NOT work on DocumentFragments... So back to square one :-)

prodigitalson
This is really a big app, several teams working on it, so I cant do structural changes on my own initiative. We have under the hood a forked version of yui 2.2, which apparently breaks on documentFragment. Yes adding sizzle to the mix is an excellent idea, but before that, I would like to understand how those libs do to work on document fragments. Also 100k is huge ! I am trying to shrink that application wherever I can.
Olivvv
Well sizzle is only 25k (not sure if thats compressed or not). However given the nature of the development team - i guess your options is to take a look at the Sizzle source and see whats happening. I would think it would be easier to track down than JQ itself since youre removing all the jQ "cruft from the equation". Another thought would be to simply post this question directly to the jQ and/or Sizzle mailing lists.
prodigitalson
Sizzle doesn't work on `DocumentFragments`.
Crescent Fresh
Crescent Fresh - if sizzle does not work on document fragments, the how does jQuery out-of-dom selection/manipulation ? I know it can, I used jQuery that way often.
Olivvv
http://groups.google.com/group/sizzlejs/browse_thread/thread/5b0a14c11248616My guess is jQuery somehow added the functionality - also there seems to be a fix for documentFragment in the sizzle branch mentioned.
prodigitalson
"somehow added the functionality" ----> this is my question. how ?
Olivvv
look at the last item in that thread... the user gives a link to his git branch of sizzle with the fix. However, this needs to be reviewed in the context of sizzle as a whole.
prodigitalson
Yes I see, thanks for the link. He is emulating getElementsByTagName(). On this other SO thread http://stackoverflow.com/questions/1643349/is-there-any-way-to-find-an-element-in-a-documentfragment/1643512#1643512, there is an emulation of getElementById(). I think I have to map all methods of the "interface document" missing in the "interface node" in w3c terminology http://www.w3.org/TR/DOM-Level-2-Core/core.html If this is case then I think it will be easier and safer to backport this from a later version of YUI, which I hope, contains that kind of code.
Olivvv
+1  A: 

jQuery will try to use getElementById first, and if that doesn't work, it'll then search all the DOM elements using getAttribute("id") until it finds the one you need.

For instance, if you built the following DOM structure that isn't attached to the document and it was assigned to the javascript var widget:

<div id="widget">
    <p><strong id="target">Hello</strong>, world!</p>
</div>

You could then do the following:

var target;

// Flatten all child elements in the div
all_elements = widget.getElementsByTagName("*");

for(i=0; i < all_elements.length; i++){
    if(all_widget_elements[i].getAttribute("id") === "target"){
        target = all_widget_elements[i];
        break;
    }
}

target.innerHTML = "Goodbye";

If you need more than just searching by ID, I'd suggest installing Sizzle rather than duplicating the Sizzle functionality. Assuming you have the ability to install another library.

Hope this helps!

Peter
I think your approach is interesting, but I just read a comment from John Resig himself (found in a link that prodigitalson left in a comment elsewhere on this page) that indicates the following: "Browsers don't provide the very basic methods needed to select elements within a document fragment. For example there is no `getElementsByTagName`. Some of the new browsers support `querySelectorAll` on fragments, but that's not guaranteed in all browsers."
Funka
Yes, there is no getElementByTagsName for documentFragments. Also IE is apparently faking documentFragments, and it has no performance difference with "normal" elements created out of the dom. So the benefits of documentFragments seem very limited.
Olivvv
A: 

Modern browsers ( read: not IE ) have the querySelector method in Element API. You can use that to get and element by id within a DocumentFragment.

jQuery uses sizzle.js

What it does on DocumentFragments is: deeply loop through all the elements in the fragment checking if an element's attribute( in your case 'id' ) is the one you're looking for. To my knowledge, sizzle.js uses querySelector too, if available, to speed things up.

If you're looking for cross browser compatibility, which you probably are, you will need to write your own method, or check for the querySelector method.

Horia Dragomir
Sizzle source: http://github.com/jeresig/sizzle/blob/master/sizzle.js if `context` is a `DocumentFragment`, `return []`. Also, Resig believes the Selectors api is broken wrt `DocumentFragment`: http://groups.google.com/group/sizzlejs/msg/517c78d0aad82c78
Crescent Fresh
"Modern browsers ( read: not IE )" :)
Tom
Crescent Fresh, Resig only stated that it's not cross browser. But his comment on google groups is insightful as to why Olivier is having such a hard time.http://groups.google.com/group/sizzlejs/msg/517c78d0aad82c78
Horia Dragomir
A: 

You really have two tools to work with, html() and using the normal jQuery manipulation operators on an XML document and then insert it in the DOM.

To create a widget, you can use html():

$('#target').html('<div><span>arbitrarily complex JS</span><input type="text" /></div>');

I assume that's not what you want. Therefore, look at the additional behaviors of the jQuery selector: when passed a second parameter, it can be its own XML fragment, and manipulation can happen on those documents. eg.

$('<div />').append('<span>').find('span').text('arbitrarily complex JS'). etc.

All the operators like append, appendTo, wrap, etc. can work on fragments like this, and then they can be inserted into the DOM.

A word of caution, though: jQuery uses the browser's native functions to manipulate this (as far as I can tell), so you do get different behaviors on different browsers. Make sure to well formed XML. I've even had it reject improperly formed HTML fragments. Worst case, though, go back and use string concatenation and the html() method.

ndp
Sorry, but you misunderstood the question. I dont want to learn how to use jQuery, and the documentFragments I am referring to are not XML fragments but DOM documentFragments.
Olivvv