4

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

Olivvv
  • 1,140
  • 1
  • 13
  • 37
  • Can you do your manipulation inside a div with its display set to none? – Chris Van Opstal Dec 02 '09 at 14:09
  • 1
    I can but it is too slow. I would like to do node selection and manipulation out of the dom in order to avoid costly dom accesses and unnecessary reflows. – Olivvv Dec 02 '09 at 14:14
  • 1
    Actually, Sizzle (which jQuery embeds) doesn't work on `DocumentFragment` objects either. Check the source yourself: http://github.com/jeresig/sizzle/blob/master/sizzle.js ~line 28. `DocumentFragment` objects have a `nodeType` of `11`, and so if `context` is a document fragment, Sizzle/jQuery simply bails and does nothing. – Crescent Fresh Dec 02 '09 at 15:40
  • So with my last comment in mind, what is your question again? You want `getElementById` on `DocumentFragment` objects? Or is there some confusion on what a document fragment object actually is (since you seem to think it works with jQuery)? – Crescent Fresh Dec 02 '09 at 15:42
  • I want to do out-of-dom manipulation/selection. I want to get our $ function to work on a out-of-dom thing, whether or not it is a document Fragment. Any out-of-dom solution is fine for me. – Olivvv Dec 02 '09 at 15:50
  • @Olivvv: I don't think "document fragment" means what you think it means. This is what I'm talking about: https://developer.mozilla.org/en/dom/documentfragment – Crescent Fresh Dec 02 '09 at 15:55
  • It is exactly what I have in mind. It is out-of-dom. Unfortunately it only supports node methods. But js libs are able to perform selection on out-of-dom structure. What is their trick ? – Olivvv Dec 02 '09 at 16:22
  • 3
    @Olivvv: trick to what?? Searching? Or creating nodes from HTML? What does "get our $ function to work on a out-of-dom thing" mean? **What are you after exactly?** – Crescent Fresh Dec 02 '09 at 16:58
  • 1
    @Crescent Fresh *Selecting* Our app uses a fork of yui 2.2. So it has $ function which is basically a wrapper around getElementById(). $ is pretty a convention across libraries for that kind of function. Our app does a lot of manipulation/selection on the dom, in order to build some kind of widgets, even before to display them. I would like to transfer that processing on the live dom, to a documentFragment in order to lower the number of access to the dom, and get better perfs. I know it is possible, I do that when I use jQuery. but getElementById() (or $) breaks on documentFragment. – Olivvv Dec 02 '09 at 20:50
  • 4
    @Olivvv: as far as selecting elements that are not in the DOM (and this is what jQuery does), you must pass in a reference to a DOM *Node* as the context. That node does not have to be present in the DOM. You can get a non-DOM-attached Node by doing `document.createElement('div')`. However, you can also just let jQuery do that for you by passing the html for your widget to the jQuery `$` function, which creates the non-DOM-attached node for you internally. Hope that helps. – Crescent Fresh Dec 03 '09 at 15:08

6 Answers6

1

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
  • 4,994
  • 4
  • 28
  • 41
  • 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 Dec 02 '09 at 14:21
  • 1
    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 Dec 04 '09 at 04:21
1

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
  • 363
  • 2
  • 10
  • 1
    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 Dec 04 '09 at 04:16
  • 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 Dec 15 '09 at 09:53
0

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
  • 60,050
  • 10
  • 100
  • 114
  • 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 Dec 02 '09 at 15:24
  • 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 Dec 02 '09 at 15:31
  • 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 Dec 02 '09 at 15:47
  • 1
    http://groups.google.com/group/sizzlejs/browse_thread/thread/5b0a14c11248616 My guess is jQuery somehow added the functionality - also there seems to be a fix for documentFragment in the sizzle branch mentioned. – prodigitalson Dec 02 '09 at 15:56
  • "somehow added the functionality" ----> this is my question. how ? – Olivvv Dec 02 '09 at 16:23
  • 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 Dec 02 '09 at 16:30
  • 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 Dec 02 '09 at 21:34
0

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.

Crescent Fresh
  • 115,249
  • 25
  • 154
  • 140
Horia Dragomir
  • 2,858
  • 24
  • 21
  • 1
    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 Dec 03 '09 at 17:13
  • 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 Dec 08 '09 at 16:33
-1

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
  • 6,552
  • 5
  • 36
  • 33
  • 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 Dec 02 '09 at 14:30
  • 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 Dec 02 '09 at 14:40
  • 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 Dec 02 '09 at 14:53
-1

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
  • 21,546
  • 5
  • 36
  • 52
  • 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 Dec 11 '09 at 09:56