Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't `.getElementById` work on a node? [closed]

Tags:

javascript

dom

I thing that if .getElementById were available on all nodes, there would be two main advantages:

  1. It could be possible to select nodes which don't belong to the document.

    For example, I would like to do something like

    function foo(html){
        var el = document.createElement('div');
        el.innerHTML = html;
        var target = el.getElementById('target');
        /* Do something with `target` */
    }
    

    But I can't because I get TypeError: el.getElementById is not a function.

    Then, I don't want to use a class instead of an id, I must do

    function foo(html){
        var el = document.createElement('div');
        el.innerHTML = html;
        document.body.appendChild(el);
        var target = document.getElementById('target');
        document.body.removeChild(el);
        /* Do something with `target` */
    }
    

    But the document could already have an element with id="target". Then, I should do

    function foo(html){
        var iframe = document.createElement('iframe');
        iframe.onload = function(){
            var doc = iframe.contentDocument || iframe.contentWindow.document,
                el = document.createElement('div');
            el.innerHTML = html;
            doc.body.appendChild(el);
            var target = doc.getElementById('target');
            document.body.removeChild(iframe);
            /* Do something with `target` */
        };
        iframe.src = 'about:blank';
        document.body.appendChild(iframe);
    }
    

    But the code above doesn't work if I want foo to return something related with html, because the main code runs after, with the onload event.

  2. It could increase the performance, if the document has lots of elements and you know that the element you are searching is a descendant of an element that you already have in a variable

    For example, if I have a document with the following structure:

    <body>
    <div id="div-1">
      <div id="div-1-1">
        <div id="div-1-1-1">
          ...
        </div>
        <div id="div-1-1-2">
          ...
        </div>
        ...
      </div>
      <div id="div-1-2">
        <div id="div-1-2-1">
      ...
        </div>
        <div id="div-1-2-2">
          ...
        </div>
          ...
      </div>
      ...
    </div>
    <div id="div-2">
      <div id="div-2-1">
        <div id="div-2-1-1">
          ...
        </div>
        <div id="div-2-1-2">
          ...
        </div>
         ...
      </div>
      <div id="div-2-2">
        <div id="div-2-2-1">
          ...
        </div>
        <div id="div-2-2-2">
          ...
        </div>
         ...
      </div>
      ...
    </div>
    ...
    </body>
    

    And I do...

    var el1 = document.getElementById('div-9999999'),
        el2 = document.getElementById('div-9999999-1-2'),
        el3 = document.getElementById('div-9999999-1-2-999999'),
        el4 = document.getElementById('div-9999999-1-2-999999-1-2-3-4-5');
    

    ... it could be much slower than

    var el1 = document.getElementById('div-9999999'),
        el2 = el1.getElementById('div-9999999-1-2'),
        el3 = el2.getElementById('div-9999999-1-2-999999'),
        el4 = el3.getElementById('div-9999999-1-2-999999-1-2-3-4-5');
    

    (Of course, this example is a simplification, and in this case using .childNodes[] or .children[] would be much better);

Then, why doesn't .getElementById work on a node? I don't see any disadvantage, only advantages

like image 226
Oriol Avatar asked Jul 06 '13 23:07

Oriol


People also ask

Why is getElementById value not working?

You are passing string 'idValue.id' to the function and the element with ID doesn't exists, thus the code is not working and you must be getting error in the browser console. As idValue refers to element which invoke the event handler. You can just use it directly to set its property. Save this answer.

Why does getElementById return null?

The getElementById() method returns null if the element does not exist. The getElementById() method is one of the most common methods in the HTML DOM.

What does getElementById return if not found?

If the getElementById method returns an element, it returns a value that has a type of object , which is a truthy value, so our if block would run. On the other side, if no element with the provided id is found, the method would return a null value, which is falsy, therefore our else block would run.

Can we use document getElementById in node JS?

how can I use document. getElementById() in nodejs ? Puppeteer is a Node library which provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome or Chromium.


2 Answers

The primary reason is efficiency.

At the document level, a single ID reference table needs to be maintained, and any alterations to the document require an O(1) modification to this table. To implement it at the node level, an equivalent table would need to be maintained for each node and updates to any given element, regardless of whether it is attached to the document, would need to bubble through every one of its parent nodes. This very quickly eats a lot of memory and takes a long time to update the DOM.

Another important thing to note is that (from a Javascript point of view, at least) every element is owned by a document (or document fragment). Therefore the argument of "but I can have duplicated IDs as long as only one of them is attached to the document" doesn't really stack up - it's only possible to manage this based on the nodes on the DOM when you take this into account.

like image 51
DaveRandom Avatar answered Sep 18 '22 11:09

DaveRandom


Regarding the problems that you have described in your first example/requirement. As getElementById only exists on the document node, because it make use of caches that are only provided by a node tree being part of document. You have three choices for searching a node tree that is not attached to the document. All suffer 0(log n) as they are not taking advantage of the document caches, there is little or no way around this (you tried with an iFrame).

One: A recursive node walker.

Advantage is that this is cross-browser friendly

Disadvantage is that it will always be 0(log n) - if used on document

Javascript

function getElementById(node, id) {
    if (node.id === id) {
        return node;
    }

    var target;

    node = node.firstChild;
    while (node) {
        target = getElementById(node, id);
        if (target) {
            return target;
        }

        node = node.nextSibling;
    }

    return undefined;
}

function foo(html) {
    var el = document.createElement("div");

    el.innerHTML = html;

    var target = getElementById(el, "target");

    /* Do something with `target` */
    if (target) {
        console.log(target);
    }
}

foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>');

​ On jsfiddle

Two: using querySelector which is available per element.

Advantage is that it requires less code

Disadvantage is that it requires IE8+ (and IE8 itself has limitations on the CSS query)

Javascript

function getElementById(node, id) {    
    return node.querySelector("#" + id);
}

function foo(html) {
    var el = document.createElement("div");

    el.innerHTML = html;

    var target = getElementById(el, "target");

    /* Do something with `target` */
    if (target) {
        console.log(target);
    }
}

foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>');

On jsfiddle

Three is to use TreeWalker

Disadvantages are that it requires IE9+, is less understood (often forgotten) than the previous methods, and requires more code than querySelector

Javascript

function getElementById(node, id) {
    return document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT, {
        acceptNode: function (n) {
            if (n.id === id) {
                return NodeFilter.FILTER_ACCEPT;
            }
        }
    }, false).nextNode();
}

function foo(html) {
    var el = document.createElement("div");

    el.innerHTML = html;

    var target = getElementById(el, "target");

    /* Do something with `target` */
    if (target) {
        console.log(target);
    }
}

foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>');

On jsfiddle

Now, regarding performance of these methods, please see this jsperf

Note: The perfomance of the three methods will alter dramatically if the nodes are part of the document!

Regarding your second desire, what you have described is a mute point due to the nature of document caches.

Update: If being asynchronous is not a problem for your requirement, then you can do it with an iframe like so.

Advantage is that you can now use getElementById

Disadvantage is the huge overhead of creating and destroying the iframe

Javascript

var getElementById = (function () {
    var parent = document.body || document.documentElement,
        javascript = "javascript";

    return function (node, id, func) {
        var iframe = document.createElement("iframe");

        iframe.style.display = "none";
        iframe.src = javascript + ":";
        iframe.onload = function () {
            iframe.contentWindow.document.body.appendChild(node);
            func(iframe.contentWindow.document.getElementById(id));
            parent.removeChild(iframe);
        };

        parent.appendChild(iframe);
    };
}());

function foo(html) {
    var el = document.createElement("div");

    el.innerHTML = html;
    getElementById(el, "target", function (target) {
        /* Do something with `target` */
        if (target) {
            console.log(target);
        }
    });
}

foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>')

;

On jsfiddle

like image 37
Xotic750 Avatar answered Sep 20 '22 11:09

Xotic750