How to avoid javascript parsing before symbol is defined?

56 views Asked by At

For Rails and other frameworks, it is common to bundle all javascript into one file, so that browser requests are minimized.

However, not all symbols are defined in the DOM at all time. For example,

$(document).ready(function () {    
    $('#storyCarousel').carousel({
        interval: 3500
    });
});

only works when

<div id="storyCarousel" class="carousel slide">

is defined in the html part. When loading the same javascript with a different html, this throws an error. It states that the method 'carousel' is not defined: jquery3.self-5af507e…74ad.js?body=1:3819 jQuery.Deferred exception: $(...).carousel is not a function TypeError: $(...).carousel is not a function Edit: @RoryMcCrossan pointed out that the error is not dependent on <div id="storyCarousel" class="carousel slide"> being defined or not. Indeed, for this specific problem (setting the interval), no js is needed, it can be done with an attribute, see How to change the interval time on bootstrap carousel?

To remove the error, I tried this trick to load page specific javascript. It boils down to

$(document).ready(function () {    
    $(".page.specific").ready(function () {
        $('#storyCarousel').carousel({
          interval: 3500
        });
    });
});

However, the trick does not work, and even though the "page.specific" element is never loaded in my html, the error about 'carousel' still shows up. I am not using turbolinks, by the way.

A much easier approach seems to be to put the javascript into the html file that defines the id:

  <div id="storyCarousel" class="carousel slide">
    <!-- Carousel items ... -->

    <script>
      $('#storyCarousel').carousel({
        interval: 3500
      });
    </script>
  </div>

However, many people seem to agree that html and javascript should be kept separate.

What is the common solution to this problem? What might be the reason that 'ready()' is called? Is it just the parser that already tries to evaluate the parameters for ready(), even though it is not called yet?

2

There are 2 answers

3
Pointy On

Put such code in a document "ready" handler. The "ready" event is only fired for certain elements; for the whole page, you just need

$(function() {
  // general initialization code goes here, as much as you want
});

You can have more than one of those.

0
Tetramputechture On

A good pattern for this is a JS dispatcher, in which you associate each page with its controller name (say, a data attribute on the body element). On each page load, you run a small JS file that only gets the page name, and run a case statement that runs page-specific JS. This way, you avoid problems like this, and speed up your site by not having to run JS for pages that aren't loaded.

If you're still concerned about undefined elements, just check those elements before you use them.

For example,

class Dispatcher {
  constructor() {
    this.pageName = document.body.dataset.page;
  }

  global() {
    // any global JS here
  }

  route() {
    switch (this.pageName) {
      case 'users:index':
        new Users.IndexFunctionality().init();
        break;
      case 'users:show':
        new Users.ShowFunctionality().init();
        break;
      ...
    }
  }
}


$(document).on('ready page:load', () => {
  new Dispatcher().route();
});