We have a Rails application where we are including our application dependencies in the html head within application.js
:
//= require jquery
//= require analytics
// other stuff...
Then on individual pages we have a script tag at the bottom of the page for analytics
:
<script>
analytics.track('on that awesome page');
</script>
This normally works fine, but very occasionally we see the error analytics is not defined
, most recently on Chrome 43. Because everything should be loaded synchronously, this seems like it ought to work out of the box, but I changed the script to:
<script>
$(document).ready(function () {
analytics.track('on that awesome page');
});
</script>
And now instead every once in a while we see $ is not defined
instead. We don't see any other errors from the same IP, otherwise I would suspect something went wrong in application.js
. Any other ideas why it might break? You can see an example page here.
The full application.js
:
// Polyfills
//= require es5-shim/es5-shim
//= require es5-shim/es5-sham
//= require polyfills
//
// Third party plugins
//= require isMobile/isMobile
//= require jquery
//
//= require jquery.ui.autocomplete
//= require jquery.ui.dialog
//= require jquery.ui.draggable
//= require jquery.ui.droppable
//= require jquery.ui.effect-fade
//= require jquery.ui.effect-slide
//= require jquery.ui.resizable
//= require jquery.ui.tooltip
//
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backbone-sortable-collection
//= require bootstrap
//= require load-image
//= require react
//= require react_ujs
//= require classnames
//= require routie
//= require mathjs
//= require moment
//= require stink-bomb
//= require analytics
//
// Our code
//= require_self
//= require extensions
//= require extend
//= require models
//= require collections
//= require constants
//= require templates
//= require mixins
//= require helpers
//= require singletons
//= require actions
//
//= require object
//= require components
//= require form_filler
//= require campaigns
//= require form_requests
//= require group_wizard
//= require step_adder
Chalk = {};
underscore = _;
_.templateSettings = {
evaluate: /\{\{(.+?)\}\}/g,
interpolate: /\{\{=(.+?)\}\}/g,
escape: /\{\{-(.+?)\}\}/g
};
moment.locale('en', {
calendar: {
lastDay: '[Yesterday at] LT',
sameDay: '[Today at] LT',
nextDay: '[Tomorrow at] LT',
lastWeek: 'dddd [at] LT',
nextWeek: '[Next] dddd [at] LT',
sameElse: 'L LT'
}
});
Update:
We're still seeing this on production occasionally. We've also seen it in a case where we load a script before application.js
and then reference it within:
javascript_include_tag 'mathjs'
javascript_include_tag 'application'
Every so often we see a math is not defined
error. I'm wondering if an error happens during the loading of mathjs
or other scripts preventing it from being loaded, but the fact that it happens on so many different libraries, and so infrequently, makes it seem less likely. We did put in some debug checks to see whether our application.js
is fully loaded and it often doesn't seem to be, even if accessing something like Jquery later in the page.
One motivation in this was to avoid old browser notifications about scripts running too long, but we may just give up and pull it all into application.js
to avoid the errors.
Check the javascript console in the browser. One of the most effective ways to program is learning from your error messages. Javascript error messages can be reached view>>Developer>>Javascript console. When you are in the javascript console, run the code again and check for errors and go from there.
On the web browser menu click on the "Edit" and select "Preferences". In the "Preferences" window select the "Security" tab. In the "Security" tab section "Web content" mark the "Enable JavaScript" checkbox.
Approach: We can use window. location property inside the script tag to forcefully load another page in Javascript. It is a reference to a Location object that is it represents the current location of the document. We can change the URL of a window by accessing it.
This can happen if you don't wait for the script to load that defines analytics
or if you do not define the order in which the javascript files are loaded. Make sure that the script that defines analytics
is always loaded before you try to call its method track
. Depending on your setup the scripts could load in random order, leading to this unpredictable behavior.
You tried to make sure everything was loaded, but the listener $(document).ready(function () {});
just makes sure that the DOM is ready, not that analytics is available. And here you have the same problem. $
is just jQuery so $ is not defined
means jQuery hasn't been loaded yet. So probably your script came before jQuery was loaded and tried to call what wasn't defined yet.
The basis of your problem probably lies in your assumption:
everything should be loaded synchronously
Everything is most decidedly not loaded synchronously. The HTTP 1.1 protocol supports piplining and due to chunked transfer encoding, your referenced objects may or may not complete loading before your main webpage has finished loading.
All of this happens asynchronously and you can't guarantee the order in which they are loaded. This is why browsers make multiple parallel connections to your web server when loading a single webpage. Because javascript and jQuery are event driven, they are by nature asynchronous which can become confusing if you don't understand that behavior well.
Compounding your problem is the fact that document onload JavaScript event (remember, jQuery just extends JavaScript) "is called when the DOM is ready which can be prior to images and other external content is loaded." And yes, this external content can include your link to the jquery.js script. If that loads after the DOM, then you will see the error "$ is not defined". Because the linked script has not yet loaded, the jquery selector is undefined. Likewise, with your other linked libraries.
Try using $(window).load() instead. This should work when all the referenced objects and the DOM has loaded.
This code will load each script URL put in libs
in the exact order, waiting for one to fully load before adding the next one.
It's not as optimised than letting the browser doing it, but it allow you to monitor the errors and force the order of the loading.
(function(){
var libs = [
"http://example.com/jquery.js",
"http://example.com/tracker.js",
"http://example.com/myscript.js"
];
var handle_error = function() {
console.log("unable to load:", this.src);
};
var load_next_lib = function() {
if(libs.length) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = libs.shift();
script.addEventListener("load", load_next_lib);
script.addEventListener("error", handle_error);
document.body.appendChild(script);
}
};
load_next_lib();
})();
But I would advise you to check every <script>
tag of your website and see if they have a defer=""
or async=""
attribute.
Most issues come from these because they tell the browser to execute the script later.
They may also just be in the wrong order.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With