Inheriting and Extending Sitecore JavaScript

Whenever possible I try to extend Sitecore as cleanly as possible, such as trying to patch into pipelines and event handlers or overriding dialog without replacing the default ones. It’s not always possible and sometimes you just have to dive it and get things done. You’ll see the same thing in my blog posts, some are more “cleanly” implemented than others.

There have been a number of times when I need to supplement some existing code or to fix a bug in some Sitecore code. The usual way I have done this is to get dirty and edit the JS files directly. Not great, but it was quick 😀 It’s also the exact same thing that Sitecore Support does whenever we have to apply a JS fix.

I’ve generally found that it’s easier to extend C# code, since it’s where I am more familiar. But usually with the JS I have hacked and updated the default Sitecore files. Cos you know, when you simply need to add an extra 9 characters, you can’t be spending the whole day trying to figure out “a more clean way”. Ain’t nobody got time….

But that was then and this is now, so I’ll share some techniques for extending Sitecore JavaScript without resorting to direct file edits. It also makes it difficult to build something into a shareable and installable module. All of these methods require you to inject in some additional resource files that I have previously blogged about.

1. Subscribe to events

There are a number of custom events declared in Sitecore scripts that follow the Observer Pattern. This makes it possible to subscribe to certain events and then have a callback triggered when that event is fired.

We’ve all used this in some framework or other, e.g. $(document).ready(), $(element).on('click') and this is essentially the same but using custom events.

Content Editor

In a way way back post I needed to fire some custom code when the content editor was updated. The main script can be located in /sitecore/shell/Applications/Content Manager/Content Editor.js. Have a peek in there and you’ll see a few events being triggered:

document.fire("sc:contenttreeupdated", node);
body.fire("sc:contenteditorupdated");

You can subscribe to these and assign a callback in your own script, no need for hacks:

document.observe("sc:contenteditorupdated", function(event) {
    // do some custom stuff
    alert("i'm the content editor, y'all still love me right?");
});

Just beware that the Content Editor is using PrototypeJS although jQuery is also available should you wish to go that route.

Experience Editor

A similar thing exists in the Experience Editor code using some custom functionality, which you can find in /sitecore/shell/Applications/Page Modes/Event.js. This is used throughout several script files but most notably ChromeManager.js and PageEditor.js

Sitecore.PageModes.PageEditor = new function() {
  this.onSave = new Sitecore.Event();
  this.onLoadComplete = new Sitecore.Event();
  ...
}

Sitecore.PageModes.ChromeManager = new function() {
  this.chromeUpdated = new Sitecore.Event();
  ...

  this.onChromeUpdated = function(chrome, reason) {
    this.chromeUpdated.fire();
    ...
  }
}

Again, you can just subscribe and have a callback triggered when these events are fired:

function CustomCallback() {
    // do some custom stuff
    alert("the PageEditor has loaded, but what about the Experience Editor?");
}

if (typeof Sitecore !== typeof undefined) {
    Sitecore.PageModes.PageEditor.onLoadComplete.observe(CustomCallback);
}

Note we’re being defensive and that we’re checking if Sitecore is defined. If you injected your resource in using a custom Page Extender then you don’t need to do this check. If you don’t do this you’ll see a JS error in the shadow EE frame, but maybe more on this in a future post.

2. Redefine the existing function

Not everything fires an event that you can subscribe to. Another option is to redefine the original function. Since you probably do not want to copy/paste the original code you need to keep a reference to the original method, redefine the existing one then you can call the original and add in your own custom code.

// Makes sure the following code is run only once
if (typeof Sitecore.PageModes.PageEditor.originalSetModified === typeof undefined) {

    // Wrap the Sitecore setModified function
    Sitecore.PageModes.PageEditor.originalSetModified = Sitecore.PageModes.PageEditor.setModified;
    this.onCustomEvent = new Sitecore.Event();

    Sitecore.PageModes.PageEditor.setModified = function(value) {
        // Fire a custom event or do whatever the you want
        this.onCustomEvent.fire();

        // let's be nice and run the existing code as well 😀
        return Sitecore.PageModes.PageEditor.originalSetModified(value);
    }

}

We’re making a copy of the existing setModified function and storing it. We’ll then add in our own custom code to do whatever we need and then run the original code that we are trying to override. In the above instance, the custom code is simply firing a custom event which then other pieces of our code can subscribe too. This continues to keep the code loosely coupled.

3. Extending existing scripts

So far so good, we have not made any direct edits yet! This 3rd and final option will work in some cases, depending on exact part of the code since different areas seem to follow different patterns.

Quite a large portion of the code uses Base.js by Dean Edwards. Go take a read of this, you’ll notice that this was originally written in 2006 to add support/a better way for OO style inheritance. Yes, JavaScript supports prototype based inheritance but Base is what Sitecore is using so let’s utilize this as well. You can find the file if you take a look at /sitecore/shell/Controls/Lib/Base.js.

if (typeof Sitecore !== typeof undefined) {
    Sitecore.PageModes.ChromeTypes.Placeholder = Sitecore.PageModes.ChromeTypes.Placeholder.extend({
        insertRendering: function(data, openProperties) {
            this.base(data, openProperties);
            // do other custom logic
            alert("look ma, no direct edits!");
        }
    },
    {
        emptyLookFillerCssClass: Sitecore.PageModes.ChromeTypes.Placeholder.emptyLookFillerCssClass,
        getDefaultAjaxOptions: Sitecore.PageModes.ChromeTypes.Placeholder.getDefaultAjaxOptions
    });
}

Super easy.

We extend the existing object and override the original method. If you see line 4 we are calling the base method in much the same way as you would do in C# to ensure the original code is also run.

I said this would only work in some parts of the code, but if the existing code uses the .extend({}) method to extend an object with another interface then it should be possible.

Also notice that the second parameter passed into extend is an object. Read the Class Properties and Methods properties section on the Dean Edwards article or see the article by Martin Rinehart under the Base Itself section. The second parameter is _static, i.e. additional parameters in the original instance. When we extend and re-assign these are lost so we need to set these again when we extend. This will only be necessary when additional parameters are set, check the original code.

If we took my previous hack and rewrote it using this technique we can add non-intrusive code like so:

What the bundle!

Trying to figure out what that file a piece of code is in? There is a bundled JS file in the Outputs directory. Simplest to check in there and then search the file system. Or there are settings in Sitecore.ExperienceEditor.config allowing you to disable bundling so individual files are spit out, which is useful for debugging.

<!--
  WEB EDIT ENABLE JS BUNDLING
            Indicates whether web edit specific JavaScript files should be bundled into one file.
            Default value: true
      
-->
<setting name="WebEdit.EnableJSBundling" value="true" />
<!--
  WEB EDIT BUNDLED JS FILES PATH
            Specifies the path where bundled JavaScript files are stored if WebEdit.EnableJSBundling = true
            Default value: /sitecore/shell/Applications/Page Modes/Ouput/
      
-->
<setting name="WebEdit.BundledJSFilesPath" value="/sitecore/shell/Applications/Page Modes/Ouput/" />

What’d I miss?

Hope you found this useful. Comments, suggestions or want to point out something please get in touch.

Advertisements

2 comments

  1. jitendrasonisite · September 10

    Awesome article and useful details.

  2. Pingback: Glass Edit Frame – Immediately Invoking Wrapper | jammykam

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s