Lessons learned working with dojo in #Episerver backend UI
Some dos and don'ts I wished i knew.... Learning by doing.
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
- https://world.episerver.com/documentation/Items/Developers-Guide/Episerver-CMS/8/User-interface/Introduction-to-Dojo/
- https://dojotoolkit.org/documentation/tutorials/1.10/declare/index.html
- https://dojotoolkit.org/reference-guide/1.10/dojo/ready.html
- https://dojotoolkit.org/reference-guide/1.10/dojo/_base/lang.html#hitch
- https://dojotoolkit.org/reference-guide/1.10/dojo/when.html
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