Loading...
Area: Optimizely CMS

Recommended reading 

Table of Contents

Introduction

The EPiServer page editing is based on the Dojo client script toolkit. Each property, or block that is to be edited consists of one or several widgets. A widget is a Dojo JavaScript class that can create a user interface. This example shows how to create a simple editing widget.

You can click here to see the complete source code for the widget used in this example.

Widget Overview

The following properties are mixed into the widget when it is constructed. Further properties can be mixed in specifically for a particular widget by using a PropertyEditorDescriptor.

PropertyDescription
intermediateChanges Indicates whether onChange is fired for each value change or only on demand.
label The title of the property to be edited.
value The current value of the widget.
required Indicates whether this widget should require a value.

The following methods will be used by the wrapper displaying this widget if they are provided.

MethodDescription
onChange Callback event that should be raise from within the widget when the value has changed. The wrapper displaying this widget will listen to this event and update the UI when it occurs.
focus Called when the widget is displayed, should give focus to the start element in the widget.
isValid Called during validation when item is saving, true if the current value is valid.

Creating a Simple Widget

In this example we'll create a simple widget for entering valid email addresses. In order to create a widget which can be used in a dialog editor you must inherit from the dijit._widget class. See the code snippet below for the basic structure of a widget class.

JavaScript
define("acme.widget.EmailTextbox", [
    "dojo/_base/declare",
    "dijit/_Widget",
    "dijit/_TemplatedMixin"
], function (
    declare,
    _Widget,
    _TemplatedMixin) {

    declare("acme.widget.EmailTextbox", [_Widget, _TemplatedMixin], {

        // templateString: [protected] String
        //    A string that represents the default widget template.
        templateString: '<div> \
                         <input type="email" data-dojo-attach-point="email" data-dojo-attach-event="onchange:_onChange" /> \
                     </div>'
    })
});
You'll notice that in the code snippet above, we've also inherited from the dijit._TemplatedMixin class. This class allows us to template the user interface of the widget in either a local string or an external HTML file. We can attach to elements in the template, giving us a programmatic reference to that node in the widget, and we can also attach events to event handlers in our widget.

When this widget is created by the editor the initial value will be passed in as a constructor arguement with the property name value. This property will be automatically mixed into the widget by the constructor in the dijit._widget class. To make sure that the value is updated in the textbox when it is set on the widget we can declare a _setValueAttr method; this method will be called when we call set('value', value).

JavaScript
postCreate: function () {
    // summary:
    //    Set the value to the textbox after the DOM fragment is created.
    // tags:
    //    protected

    this.set('value', this.value);

    if (this.intermediateChanges) {
        this.connect(this.email, 'onkeydown', this._onIntermediateChange);
        this.connect(this.email, 'onkeyup', this._onIntermediateChange);
    }
},

_setValueAttr: function (value) {
    // summary:
    //    Sets the value of the widget to "value" and updates the value displayed in the textbox.
    // tags:
    //    private

    this._set('value', value);
    this.email.value = this.value || '';
}
You'll notice that we've also added a postCreate method in which we call set passing the current value. This is to ensure the widget is updated correctly with the initial value since the widget constructor uses a mixin which doesn't call set.

The set method also references a variable named email. This is the textbox DOM node and it is automatically assigned to this variable name by the dijit._Templated class when it parses the data-dojo-attach-point in the template code.

When the changes made in the widget need to be populated to the page we can call the onChange method passing it the new value. This should be called as often as necessary during the editing in order to give the editor an accurate preview of the changes they are making.

JavaScript
_onChange: function (event) {
    // summary:
    //    Handles the textbox change event and populates this to the onChange method.
    // tags:
    //    private

    this._set('value', event.target.value);
    this.onChange(this.value);
}

This _onChange method is a private event handler that is triggered when a change is made on the textbox. This is wired up in the template using the data-dojo-attach-event syntax. Once it has updated the value it will call the onChange method casuing a page update.

We want to be able to support intermediate changes; this is where we update the UI as soon as the user makes any sort of update. For this example, because we are dealing with a textbox, we want to update the UI when the user starts typing. To do this, in the postCreate method, we've connected to the onKeyDown and onKeyUp events on the textbox element if the intermediateChanges property is set to true.

JavaScript
_onIntermediateChange: function (event) {
    // summary:
    //    Handles the textbox key press events event and populates this to the onChange method.
    // tags:
    //    private

    if (this.intermediateChanges) {
        this._set('value', event.target.value);
        this.onChange(this.value);
    }
}

You can also control the where the focus is set when the control loads by implementing the focus method.

JavaScript
focus: function () {
    // summary:
    //    Put focus on this widget.
    // tags:
    //    public

    dijit.focus(this.email);
}

Validation

In order to support validation the widget must implement the isValid method.

JavaScript
isValid: function () {
    // summary:
    //    Indicates whether the current value is valid.
    // tags:
    //    public

    var emailRegex = '[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+';
    if (!this.required) {
        emailRegex = '(' + emailRegex + ')?';
    }
    var regex = new RegExp('^' + emailRegex + '$');
    return regex.test(this.value);
}

The constraints for a property are also mixed into the widget when it is constructed. For example, if the value has the required checkbox ticked in admin mode then that will be passed through as the property required.

Dealing with Child Dialogs

If your widget needs to launch a dialog of its own, then you will need to extend an additional class and set a few property values in the correct place in order to ensure that the blur event, going from the main dialog to the new one, doesn't close the main dialog. The class that you need to extend is called epi.cms.widget._HasChildDialogMixin and it provides one additional property called isShowingChildDialog.

This property is used in the blur event of the main dialog to determine whether it should hide itself. Therefore if we want to stop the main dialog from being hidden when the widget launches a child dialog we need to set the value to true before lauching the child dialog. We can then set it back to false once the child dialog has closed.

JavaScript
_showChildDialog: function () {
    var dialog = dijit.Dialog({
        title: 'Child Dialog'
    });
    this.connect(dialog, 'onHide', this._onHide);

    this.isShowingChildDialog = true;

    dialog.show();
},

_onHide: function () {
    this.isShowingChildDialog = false;
}
Do you find this information helpful? Please log in to provide feedback.

Last updated: Mar 25, 2013

Recommended reading