Declare and Respond to a DOM Element Ready Event

By Rob Gravelle

Declare and Respond to a DOM Element Ready Event

In my Respond to DOM Changes with Mutation Observers article, I introduced the W3C DOM4 specification's mutation observers, which replace the deprecated mutation events. Mutation Observers inform your script of changes to the DOM structure using an asynchronous callback mechanism. Both the types of changes and node types reported on include pretty much everything that you'll find in a web page. In today's follow-up, I'd like to provide a concrete example of how Mutation Observers may be utilized to tell us when particular elements have been added to the DOM. This frees us from having to wait for the document load or DOMContentLoaded events and works for dynamically-loaded elements as well!

The HTML Code

For our demo, we'll watch for text input fields. Hence, whenever a text input field is appended to the DOM, whether from the original HTML document or a dynamic script, our callback function will execute. The following form contains two text fields:

  First name:<br>
  <input type="text" id="firstname"><br>
  Last name:<br>
  <input type="text" id="lastname">
<div id="output"></div>

The JavaScript

This Mutation Observer approach is based on the outstanding work of fellow Ontarian Ryan Morr. I made some minor changes to the code to make it jQuery compatible and more my own, but any and all accolades should go to him, and not me.

Speaking of jQuery, it seems that they deprecated the .selector property, so once a DOM collection has been fetched using the $() function, it won't be updated should the DOM change. That presents a problem for us because our code piggybacks on the MutationObserver so that whenever it picks up a change in the document, we perform our own checks, using the stored selectors. I originally wanted to add the ready function to the jQuery.fn object so that it could be chained to a jQuery selector $() call. Without a way to get at a jQuery DOM collection's original selector, that plan crumbled into dust. Instead, I opted to define my own class called DynamicSelector. It stores the selector so that it may be recalled later. It also implements the ready() function so that we can chain it all together like so:

//select all text input fields
new DynamicSelector('input[type="text"]').ready(function(element){
  //display the time (getTime() not shown)
  //and a message that the field is ready
  $('div#output').append(getTime() + ': ' + + ' field is ready!<br/>');

The ready() function accepts a callback function that is invoked for every element matched by the selector. That part is simple enough: ready() fetches a collection of matching DOM elements, iterates over them, and invokes the passed function for each of them. The rub is that the same function must also be invoked whenever a change to the DOM would cause additional elements to match. In fact, that's the main reason for using a MutationObserver. Dynamic Selectors are pushed onto our global "selectors" array so that they may be individually refreshed within the MutationObserver's callback function. Therein, matched elements are given a custom "ready" boolean flag so that they do not trigger a re-invocation of our ready()'s callback function every time the DOM changes. The result is that dynamically appending an element to the DOM will cause the ready() callback function to fire should said element match the DynamicSelector's selector string.

Before we look at the code, I should point out that the MutationObserver is also stored in a global variable named "observer", so that we only create a singleton the first time that the ready() function is invoked, because, once the MutationObserver has been instantiated, it continues to work throughout the lifetime of the page.

'use strict';
var selectors = [],
    DynamicSelector = function(selector) {
      this.selector = selector;
      this.ready = function(fn) {
        // Store the selector and callback to be monitored
        if(observer === undefined){
          // Watch for changes in the document
          observer = new (MutationObserver || WebKitMutationObserver)(function(){
            // Check the DOM for elements matching  a stored selector
            selectors.forEach(function(dynamicSelector) {
              // Query for elements matching the specified selector
              $(dynamicSelector.selector).each(function() {
                // Make sure the callback isn't invoked with the
                // same element more than once
                  this.ready = true;
                  // Invoke the callback with the element
        , this);
          observer.observe(document.documentElement, {
              childList: true,
              subtree: true

Testing for Changes to the DOM

To ascertain whether or not new matching elements are in fact triggering the execution of our callback function(s), we only have to dynamically append an element to the DOM and see what happens. The following code waits 3 seconds after the initial page load to append a new text field with an id of 'new_text_input'.

$(function() {
  //dynamically add another one
  setTimeout(function() {
      .attr('id', 'new_text_input')
      .attr('type', 'text')
        $('form').append('<br>New field:<br>')
  }, 3000);

Here's what was outputted to the page during my test:

10:3:27: firstname field is ready!
10:3:27: lastname field is ready!
10:3:30: new_text_input field is ready!

As you can see, the two text fields that were part of the HTML markup fired our callback function within the same second. Then, exactly three seconds later, we can see our new field on the screen, along with the output of our callback function.

There's a working demo on Codepen for you to try.


If you don't care about changes to the DOM, you are better off placing your JS code directly below the HTML where your element(s) is/are declared. That will execute your code the fastest as compared to the onload and DOMContentLoaded events or a DynamicSelector, which fires only after the two load events. For the best of both worlds, you could include a script for the page load and then create a DynamicSelector to watch for DOM updates.

Rob Gravelle

Rob Gravelle resides in Ottawa, Canada, and is the founder of Rob has built systems for Intelligence-related organizations such as Canada Border Services, CSIS as well as for numerous commercial businesses.

In his spare time, Rob has become an accomplished guitar player, and has released several CDs. His band, Ivory Knight, was rated as one of Canada's top hard rock and metal groups by Brave Words magazine (issue #92) and reached the #1 spot in the National Heavy Metal charts on Reverb Nation.

Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.
Thanks for your registration, follow us on our social networks to keep up-to-date