node selection and manipulation out of the dom (What is jQuery's trick ?)

2.2k Views Asked by At

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

6

There are 6 best solutions below

7
On

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 :-)

2
On

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;

3
On

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)



2
On

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!

1
On

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.

2
On

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.