Don't miss out Virtual Happy Hour this Friday (April 26).

Try our conversational search powered by Generative AI!

Håkon Nordli
Oct 20, 2015
  9015
(10 votes)

Learn how to develop your own Dojo widget

“It was like getting a brick in the head, » a developer told me after a dojo seminar a while back.   Often when I talk to developers about dojo, I get the same message, they have no idea where to start. They find it a bit too difficult and it takes too much time to understand the basics. 

This has led me to write a new blog post on how to build your own Dojo widget. I have decided to split the article in two separate parts, first how and where to start building a widget and second, 10 tips that may come in handy when developing. 

Before we start developing, I would like to point out a few things. Dojo is a library much like jQuery, you may do DOM manipulations and other fun things. Together with Dojo, you will also get Dijit, which is Dojo’s UI library. We use Dijit when dealing with widgets in EPiServer. 

Step by step guide 

Here is a list of files that are necessary for you to build your first widget. Depending on what you would like to develop, you might need other files as well. However, these are the ones you must have. 

Step 1 – EditorDescriptor.cs 

First, you need an EditorDescriptor. I suggest putting it in your Business/EditorDescriptors folder. 

It defines which editor to use for a specific property for a page or shared content. You may read more about it here. http://world.episerver.com/documentation/Items/Developers-Guide/EPiServer-CMS/7/Editing/How-To/Registering-a-Custom-Editor/

using System;
using System.Collections.Generic;
using EPiServer.Shell.ObjectEditing.EditorDescriptors;

namespace Alloy.Business.EditorDescriptors
{
    [EditorDescriptorRegistration(TargetType = typeof(String), UIHint = "NewTextarea")]
    public class NewTextareaEditorDescriptor : EditorDescriptor
    {
        public override void ModifyMetadata(EPiServer.Shell.ObjectEditing.ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
        {
            ClientEditingClass = "alloy/editors/NewTextarea";
            base.ModifyMetadata(metadata, attributes);
        }
    }
}

Step 2 – The dojo widget (JavaScript file)

This is the actual widget where a lot of the magic happens. I suggest putting the file in your ClientResources/Scripts/Editors folder. 

Here is an example of a small widget, a textarea. I recommend starting with this when building a widget for the first time. Later you can rewrite it and make it do whatever you need it to be. This way you have an excellent starting point with no errors.

The reason I have built a textarea is because EPiServer already have a textarea. That way no one will actually use this widget, but instead use it as a starting point. 

The widget is based on StringList from Alloy, but are rewritten and have several comments that explain several of the methods and modules.

define([
    "dojo/_base/array",
    "dojo/query",
    "dojo/on",
    "dojo/_base/declare",
    "dojo/_base/lang",

    "dijit/_CssStateMixin",
    "dijit/_Widget",
    "dijit/_TemplatedMixin",
    "dijit/_WidgetsInTemplateMixin",

    "dijit/form/Textarea",
   
    "epi/epi",
    "epi/shell/widget/_ValueRequiredMixin"
],

function (
    array,
    query,
    on,
    declare,
    lang,

    _CssStateMixin, //_CssStateMixin is a mixin for widgets that set CSS classes on their nodes depending on hover/active/focused state, and also semantic state (checked, selected, disabled, etc.).
    _Widget, //BaseClass for all dijit widgets
    _TemplatedMixin, //dijit/_TemplatedMixin is a mixin for most widgets in dijit. It takes an HTML template, and creates the widget’s DOM tree according to that template.


    _WidgetsInTemplateMixin, //When using this template in a directly extended widget class, you will need to mixin dijit._WidgetsInTemplateMixin in addition to dijit._TemplatedMixin.
   
    Textarea,
   
    epi,
    _ValueRequiredMixin //Extends dijit/_CssStateMixin: http://world.episerver.com/Documentation/Javascript-Library/?documentId=episerverframework/7.1/epi/shell/widget/_ValueRequiredMixin
) {
    return declare("alloy.editors.NewTextarea", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _CssStateMixin, _ValueRequiredMixin], {
        templateString: "<div class=\"dijitInline\">\
                            <div data-dojo-attach-point=\"textArea\" data-dojo-type=\"dijit.form.Textarea\"></div>\
                        </div>",

        intermediateChanges: false, //default false. intermediatechanges is a dijit property which fire the change event when there is an actually change. 
        value: null,
        onChange: function (value) {
            // Event
        },

        //built-in dijit widget. postCreate is run after all the properties are set up, but before the templateString is added to the DOM
        postCreate: function () {

            // call base implementation
            this.inherited(arguments);

            // Init textarea and bind event
            this.textArea.set("intermediateChanges", this.intermediateChanges);
           
            // Notice the lang.hitch. It forces the on method to retain it’s original scope.  
            on(this.textArea, "change", lang.hitch(this, this._onTextAreaChanged));
        },
       
        //built-in dijit widget. Runs after the templateString is added to the DOM. 
        startup: function () {

        },

        // build-in dojo method. Prevents an invalid form from submitting
        isValid: function () {
            return !this.required || lang.isArray(this.value) && this.value.length > 0 && this.value.join() != "";
        },

        // Setter for value property. Runs at startup. 
        _setValueAttr: function (value) {
            this._setValue(value, true);
        },

        //runs at startup. 
        _setReadOnlyAttr: function (value) {
            this._set("readOnly", value);
            this.textArea.set("readOnly", value);
        },

        //A setter for intermediateChanges
        _setIntermediateChangesAttr: function (value) {
            this.textArea.set("intermediateChanges", value);
            this._set("intermediateChanges", value);
        },

        // Event handler for textarea
        _onTextAreaChanged: function (value) {
            this._setValue(value, false);
        },

        _setValue: function (value, updateTextarea) {

            //avoids running this if the widget already is started
            if (this._started && epi.areEqual(this.value, value)) {
                return;
            }

            // set value to this widget (and notify observers). Built-in dijit method. Source: https://www.sitepen.com/blog/2013/10/16/dojo-faq-what-is-the-difference-between-set-and-_set/
            this._set("value", value);

            // set value to textarea
            updateTextarea && this.textArea.set("value", value);

            if (this._started && this.validate()) {
                // Trigger change event
                this.onChange(value);
            }
        },
    });
});

Step 3 - Module.config

Module.config should be in your web root folder. It is to add a mapping from alloy to ~/ClientResources/Scripts to the dojo loader configuration. If you do not have this, you will get 404 errors for your widget file. 

<?xml version="1.0" encoding="utf-8"?>
<module>
    <dojo>
        <!-- Add a mapping from alloy to ~/ClientResources/Scripts to the dojo loader configuration -->
        <paths>
            <add name="alloy" path="Scripts" />
        </paths>
    </dojo>
</module>


Here is EPiServer’s documentation about module.config 
http://world.episerver.com/documentation/items/developers-guide/episerver-framework/7/configuration/configuring-moduleconfig/ 

 

Step 4 – Use the widget on a page

To add the widget to a property, use the UIHint showed below:

[UIHint("NewTextarea")]
[Display(GroupName = SystemTabNames.Content, Order = 10)]
public virtual string NewTextarea { get; set; }

The End

These files are only meant as a starting point. You may use them and rewrite them to whatever you want. 

Remember to update the namespaces. 

Happy coding! 

Oct 20, 2015

Comments

Oct 20, 2015 01:06 PM

Nice! And a lighter brick! Much needed, thanks for sharing.

jukkahyv
jukkahyv Oct 21, 2015 01:29 PM

When programming client side with Angular and Knockout I've been used to separating the view from the logic (viewmodel/controller, however you call it). My main reason for disliking Dojo is that to me it seems that Dojo doesn't support this approach since all the DOM handling is mixed with the JavaScript code. I would like the code to be more testable. Even this article doesn't mention unit testing at all. So my question is: is it possible to break the dependencies to browser and EPi so that you could test this widget without initializing the EPi context?

Håkon Nordli
Håkon Nordli Oct 25, 2015 05:58 PM

@Jukka If you remove the epi and _ValueRequiredMixin modules it should be possible to run this widget outside EPiServer. I've tried it once before with another widget and it worked. Dojo got their own test framework, DOH. I've never used it so I don't know more than that it exists. According to it's documentation it supports non-browser environments. https://dojotoolkit.org/reference-guide/1.9/util/doh.html  

nitin anand
nitin anand Jul 18, 2018 01:29 PM

Hi I am trying to get template from my controller class as  templateString: dojo.cache("/LatestChanges")   The controller is LatestChangesController.cs which returns an Index view in turn. It works in alloy project but not on my actual project. i get error 404 as displayed in image

https://imgur.com/93jlr5A

Any help would be appreciated

Please login to comment.
Latest blogs
Optimizely Unit Testing Using CmsContentScaffolding Package

Introduction Unit tests shouldn't be created just for business logic, but also for the content and rules defined for content creation (available...

MilosR | Apr 26, 2024

Solving the mystery of high memory usage

Sometimes, my work is easy, the problem could be resolved with one look (when I’m lucky enough to look at where it needs to be looked, just like th...

Quan Mai | Apr 22, 2024 | Syndicated blog

Search & Navigation reporting improvements

From version 16.1.0 there are some updates on the statistics pages: Add pagination to search phrase list Allows choosing a custom date range to get...

Phong | Apr 22, 2024

Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language – Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog