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

Are you sure element.attachEvent is not a function?

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.

Fish