Sitecore 8: Fixing Experience Editor JavaScript Errors When Using jQuery and RequireJS

Are you sure element.attachEvent is not a function?

October 2, 2016

By Dan Cruickshank

This is a tough one. When using RequireJS or any other AMD JS (Asynchronous Module Definition) framework to load jQuery within a Sitecore 8+ website, you're going to get an error in *Experience Editor* and *Preview* modes. Here's the error. Let me assure you - it's not your fault. ~~~ Error: prototype.js:5644 Uncaught TypeError: element.attachEvent is not a function Error: prototype.js:5734 Uncaught TypeError: element.dispatchEvent is not a function ~~~ Unfortunately in *Preview* and *Experience Editor* modes Sitecore loads JavaScript that is not AMD compliant. I'm looking at you *Protoype.js*. *Prototype.js* is where we find the errors, but the problem is a recent hack (which is even documented as such) in jQuery +2. I won't do a super deep dive on this, but Sitecore is loading the *Prototype.js* and *jQuery* which both use *window.$*. That is the variable *$* in the global namespace of *window*. Sitecore is very specific about the order *Prototype* and *jQuery* are loaded and used so that with no other JavaScript on the page they're never sharing the *window.$*. Clear? This is where jQuery's hack gets us into trouble. Even when we're using an AMD framework like RequireJS, jQuery 2.x & 3.x willfully pollutes *window.$* in global namespace. See below.

// Expose jQuery and $ identifiers, even in AMD
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( !noGlobal ) {
	window.jQuery = window.$ = jQuery;
}

return jQuery;
}));
If I were my parents, I'd spank jQuery. Explicit defiance to modern web. You'll find this offensive snippet at the very end of your uncompressed jQuery file. For example, in jQuery 2.2.3 it starts at line 9834. The problem is that Sitecore expects *window.$* to belong to Prototype.js as it loads the Experience Editor / Preview UI but "window.$" gets overwritten by jQuery instead. Sitecore ends up calling *attachEvent()* on a jQuery object (whoops!) instead of Prototype.js object. Here's how you can fix it.

/*

DC - Removing this "fix" as it's storing jQuery in the global namespace, overwriting protoype.js from Sitecore asyncronously.

// Expose jQuery and $ identifiers, even in AMD
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if (!noGlobal) {
    window.jQuery = window.$ = jQuery;
}

*/

return jQuery;
}));
The fix is simply to comment out or delete those lines. You could also add conditional logic to expand the *if()* clause such as *if(!noGlobal && !window.define)*. These lines of code we're removing aren't that bad in truth, but Sitecore's Experience Editor is a mash-up of many different libraries plus our own code. It's a fragile ecosystem. Our levels of assumptions need to be very low. This is the whole reason why use AMD JS. It's definitely frustrating to see jQuery make their own code-breaking assumptions that are antithetical to the AMD JS pattern - while attempting to support AMD. Thanks for reading.
Dan Headshot

Dan Cruickshank

President | Sitecore MVP x 11

Dan is the founder of Fishtank. He's a multi-time Sitecore MVP and Coveo MVP award winner. Outside of technology, he is widely considered to be a top 3 father (routinely receiving "Father of the Year" accolades from his family) and past his prime on the basketball court.