Lessons learned working with dojo in #Episerver backend UI

Some dos and don'ts I wished i knew.... Learning by doing.

icon of user profile

Published 28th sept 2019
Episerver CMS version > 8

Had the *priviledge* working with Dojo when doing an Episerver addon, the task was extending the ThumbnailSelector.

EPiServer back-end UI uses the Dojo Toolkit as the JavaScript framework for all CMS client-side development.

Episerver uses version 1.8.9, a version from december 2014. While version 1 still being maintained, new development is primarily focused on modern Dojo by JsFoundation. Version 6 exists.

Explaining Dojo toolkit v1 basics with 2 examples

Declare a Dojo class

Class Inheriting from Another Class
// Create a new class named "mynamespace.myClass"
dojo.declare("mynamespace.myClass",mynamespace.myParentClass,{
 
// mynamespace.myClass now has all of mynamespace.myParentClass's properties and methods
// properties and methods override parent's
 
});

Import libs/instantiated classes and declare

define("myintegration/MyThumbnailSelector", [
    // dojo
    "dojo/_base/declare",
    "dojo/when",
    "dojo/on",
    "dojo/topic",
    "dojo/dom-class",
    "dojo/ready",
    "dojo/_base/lang",
    // epi
    "epi/shell/DialogService",
    "epi/shell/XhrWrapper",
    // epi-cms
    "epi-cms/widget/ThumbnailSelector",
    "dojo/text!./templates/MyThumbnailSelector.html",
    "epi/i18n!epi/cms/nls/episerver.cms.widget.thumbnailselector"
], function (
    // dojo
    declare,
    when,
    on,
    topic,
    domClass,
    ready,
    lang,
    // epi
    _dialogService,
    XhrWrapper,
    // epi-cms   
    ThumbnailSelector,
    template,
    resources
) {
        return declare([ThumbnailSelector], {           
            //override the ThumbnailSelector._onButtonClick 
            _onButtonClick: function () {
                if (this.readOnly) {
                    return;
                }
                //custom stuff  
                if (this.disableMediaSelector) {
                    this._dialogService.alert("This one is disabled!");
                    return;
                }

                this.inherited(arguments);
            }
        });
    });

In this example i override the _onButtonClick, add som custom stuff, and trigger the base _onButtonClick.

The this.inherited(arguments) statement calls the parent class’ method of the same name.

DON’TS

Do not mix frameworks

I used Jquery $.ajax in a method – seemed to work at the begining but I lost track of the context (this). Doh!

Do not set global variables

While loosing “this”, i parked my object in a global variable, like window.MyPlugin = this;

it seemed like a good idea as a quick fix, but somehow you lost context of wrapping dojo widgets. Doh!

Do not loose the context

The main conclusion here is to always have “this” pointing to your dojo class. if this point to window, you lost it.

Below are some tips

DOS

Use lang.hitch to stay in context with “this”

lang.hitch returns a function that will execute a given function in a given scope.

require ["dojo/_base/lang"]
setTimeout(lang.hitch(this, function () {
     this.customProp = true;
}), 100)

Postmessage and dojo

window.postMessage is a common pattern to allows an object or string to be sent to another window or frame using JavaScript. The window.postMessage method helps solve the Cross-Origin communication challenge by providing a controlled method of communicating between windows in different origins.

To listen on “message” use window.addEventListener(“message”, this._listener, true)

BUT NOT ENOUGH the _listener method is sent by value, meaning context will be gone.

to solve this

this._listener = this._listener.bind(this);
window.addEventListener("message", this._listener, true);

.bind(this) will pass by ref

Remember to remove the Listener properly or else you will have alot of listeners

//remove listener
window.removeEventListener("message", this._listener, true);

Use When and XhrWrapper for Ajax calls

dojo/when transparently applies callbacks to values and/or promises as well as converts foreign promises to native ones.

define("myintegration/MyThumbnailSelector", [
    // dojo
    "dojo/_base/declare",
    "dojo/when",
    "dojo/on",
    "dojo/topic",
    "dojo/dom-class",
    "dojo/ready",
    "dojo/_base/lang",
    // epi
    "epi/shell/DialogService",
    "epi/shell/XhrWrapper",
    // epi-cms
    "epi-cms/widget/ThumbnailSelector",
    "dojo/text!./templates/MyThumbnailSelector.html",
    "epi/i18n!epi/cms/nls/episerver.cms.widget.thumbnailselector"
], function (
    // dojo
    declare,
    when,
    on,
    topic,
    domClass,
    ready,
    lang,
    // epi
    _dialogService,
    XhrWrapper,
    // epi-cms   
    ThumbnailSelector,
    template,
    resources
) {
        var xhr = new XhrWrapper();

        return declare([ThumbnailSelector], {           

	     _getSomeData: function (url, data) {
                var self = this;
                return xhr.xhrGet({
                    url: url,
                    content: data,
                    failOk: true,
                    error: function(response) {
                        self._dialogService.alert(response.responseText);
                    }
                });
            },
            _onButtonClick: function () {
                if (this.readOnly) {
                    return;
                }

                if (this.disableMediaSelector) {
			var self = this;
			when(self._getSomeData("/myurl/",
                                    { //get params
                                        id: 45
                                    }
				),
                                function(data) { //success
                                    self._setValueAndFireOnChange(data + "");//fire the change - must be string
                                });  
                    return;
                }

                this.inherited(arguments);
            }
        });
    });

Also look at dojo/request as a better alternative
https://dojotoolkit.org/documentation/tutorials/1.10/ajax/index.html

Run code when dom ready

dojo/ready registers a function to run when the DOM is ready and all outstanding require() calls have been resolved, and other registered functions with a higher priority have completed.

ready(lang.hitch(this, function () {
     this.loaded = true;
});

Passing values from backend cms to plugin

Use Editor descriptor:

[EditorDescriptorRegistration(TargetType = typeof(ContentReference), UIHint = "image", EditorDescriptorBehavior = EditorDescriptorBehavior.PlaceLast)]

public class CustomImageContentReferenceEditorDescriptor : ImageReferenceEditorDescriptor
{
     public CustomImageContentReferenceEditorDescriptor ()
     {
     }

     public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
     {
            ClientEditingClass = "myintegration/editors/MyThumbnailSelector";
            metadata.EditorConfiguration["disableMediaSelector"] = true;

            base.ModifyMetadata(metadata, attributes);
     }
}

This ends upp in “this.disableMediaSelector” in the dojo declared class object.

Keep focus on a module

The dijit module need to be in focus when used, if you for example work in epi, then window focus is lost, the dijit is not focused back and the property will not save onchange.

use dijit/focus and save the focus data when you have it

require([“dijit/focus”], function (focusUtil) {// focus is lost, so store it temporarly
self.activeElement = focusUtil.curNode; // returns null if there is no focused element
});

Later get / check the focus back

if (self.activeElement !== null) {//set focus if saved and needed
require([“dijit/focus”], function (focusUtil) {
focusUtil.curNode = self.activeElement;
self.activeElement = null;
});
}

if not focus the property will not trigger the _onchange event, and “publish” will not be shown.

Use the DialogService

Require “epi/shell/DialogService”

this._dialogService.alert(“some message”);

More Dojo reading

More plugin help

How to pack a nuget addon for #Episerver, Example FotoWare Plugin

About the author

Luc Gosso
– Independent Senior Web Developer
working with Azure and Episerver

Twitter: @LucGosso
LinkedIn: linkedin.com/in/luc-gosso/
Github: github.com/lucgosso