Why does jQuery or a DOM method such as getElementById not find the element?

  1. Home
  2. javascript
  3. Why does jQuery or a DOM method such as getElementById not find the element?

What are the possible reasons for document.getElementById, $("#id") or any other DOM method / jQuery selector not finding the elements?

Example problems include:

  • jQuery silently failing to bind an event handler
  • jQuery “getter” methods (.val(), .html(), .text()) returning undefined
  • A standard DOM method returning null resulting in any of several errors:

    Uncaught TypeError: Cannot set property ‘…’ of null
    Uncaught TypeError: Cannot read property ‘…’ of null

    the most common forms are:

    Uncaught TypeError: Cannot set property ‘onclick’ of null

    Uncaught TypeError: Cannot read property ‘addEventListener’ of null

    Uncaught TypeError: Cannot read property ‘style’ of null

First answer

The element you were trying to find wasn’t in the DOM when your script ran.

The position of your DOM-reliant script can have a profound effect upon its behavior. Browsers parse HTML documents from top to bottom. Elements are added to the DOM and scripts are (generally) executed as they’re encountered. This means that order matters. Typically, scripts can’t find elements which appear later in the markup because those elements have yet to be added to the DOM.

Consider the following markup; script #1 fails to find the <div> while script #2 succeeds:

<script>
  console.log("script #1: %o", document.getElementById("test")); // null
</script>
<div id="test">test div</div>
<script>
  console.log("script #2: %o", document.getElementById("test")); // <div id="test" ...
</script>

So, what should you do? You’ve got a few options:


Option 1: Move your script

Move your script further down the page, just before the closing body tag. Organized in this fashion, the rest of the document is parsed before your script is executed:

<body>
  <button id="test">click me</button>
  <script>
    document.getElementById("test").addEventListener("click", function() {
      console.log("clicked: %o", this);
    });
  </script>
</body><!-- closing body tag -->

Note: Placing scripts at the bottom is generally considered a best practice.


Option 2: jQuery’s ready()

Defer your script until the DOM has been completely parsed, using ready():

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(document).ready(function() {
    $("#test").click(function() {
      console.log("clicked: %o", this);
    });
  });
</script>
<button id="test">click me</button>

Note: You could simply bind to DOMContentLoaded or window.onload but each has its caveats. jQuery’s ready() delivers a hybrid solution.


Option 3: Event Delegation

Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.

When an element raises an event (provided that it’s a bubbling event and nothing stops its propagation), each parent in that element’s ancestry receives the event as well. That allows us to attach a handler to an existing element and sample events as they bubble up from its descendants… even those added after the handler is attached. All we have to do is check the event to see whether it was raised by the desired element and, if so, run our code.

jQuery’s on() performs that logic for us. We simply provide an event name, a selector for the desired descendant, and an event handler:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(document).on("click", "#test", function(e) {
    console.log("clicked: %o",  this);
  });
</script>
<button id="test">click me</button>

Note: Typically, this pattern is reserved for elements which didn’t exist at load-time or to avoid attaching a large amount of handlers. It’s also worth pointing out that while I’ve attached a handler to document (for demonstrative purposes), you should select the nearest reliable ancestor.


Option 4: The defer attribute

Use the defer attribute of <script>.

[defer, a Boolean attribute,] is set to indicate to a browser that the script is meant to be executed after the document has been parsed.

<script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>

For reference, here’s the code from that external script:

document.getElementById("test").addEventListener("click", function(e){
   console.log("clicked: %o", this); 
});

Note: The defer attribute certainly seems like a magic bullet but it’s important to be aware of the caveats…
1. defer can only be used for external scripts, i.e.: those having a src attribute.
2. be aware of browser support, i.e.: buggy implementation in IE < 10

Second answer

Reasons why id based selectors don’t work

  1. The element/DOM with id specified doesn’t exist yet.
  2. The element exists, but it is not registered in DOM [in case of HTML nodes appended dynamically from Ajax responses].
  3. More than one element with the same id is present which is causing a conflict.

Solutions

  1. Try to access the element after its declaration or alternatively use stuff like $(document).ready();

  2. For elements coming from Ajax responses, use the .bind() method of jQuery. Older versions of jQuery had .live() for the same.

  3. Use tools [for example, webdeveloper plugin for browsers] to find duplicate ids and remove them.

Third answer

As @FelixKling pointed out, the most likely scenario is that the nodes you are looking for do not exist (yet).

However, modern development practices can often manipulate document elements outside of the document tree either with DocumentFragments or simply detaching/reattaching current elements directly. Such techniques may be used as part of JavaScript templating or to avoid excessive repaint/reflow operations while the elements in question are being heavily altered.

Similarly, the new “Shadow DOM” functionality being rolled out across modern browsers allows elements to be part of the document, but not query-able by document.getElementById and all of its sibling methods (querySelector, etc.). This is done to encapsulate functionality and specifically hide it.

Again, though, it is most likely that the element you are looking for simply is not (yet) in the document, and you should do as Felix suggests. However, you should also be aware that that is increasingly not the only reason that an element might be unfindable (either temporarily or permanently).

Reprint:https://stackoverflow.com/questions/14028959/why-does-jquery-or-a-dom-method-such-as-getelementbyid-not-find-the-element
Spread the love

Related articles

Comments are closed.