EPiServer custom property in DOJO

  • Feb 03, 2015
  • EPiServer
  • DOJO

For a client I had a requirement to manage an email address by country. The countries are stored in categories. There are a number of ways to get this done. The easiest solution would be to make a container page type that contains two properties a category selector and a text field to store the email address. Personally I don’t think it’s the best solution, because another page type needs to be created and all the items are displayed in the page tree. It seemed me a better idea to create a property on the page that displayed a label and a text field for each country. Of course DOJO is the framework for creating custom properties in EPiServer. For this blog I created code that also can be found on my GitHub account.

  • I will explain the following topics:

- Add/edit module.config – configuring the property

- EditorDescriptor – used for passing configuration data to the property script

- Property Type – used for retrieving and storing the property value

- JS module script – JS script for displaying the property

 

Add/ edit module.config

The module.config is used for configuring DOJO modules. This configuration file isn’t standard created with a EPiServer installation. When creating a DOJO module, such as a custom property, the module.config needs to be created in the root directory of the website. In the module.config the path to the property files are configured.

<module>
  <assemblies>
    <add assembly="EPiServerCustomProperty" />
  </assemblies>
   
  <dojoModules>
    <add name="app" path="Scripts" />
  </dojoModules>
</module>

The assemblies section specifies which assemblies belong to the module, also the path to the resource files is configured.

 

EditorDescriptor

 

The EditorDescriptor communicates with the custom property how the property should render. By setting the ClientEditingClass it tells the edit mode what JS module script must be used for rendering this property. The same value must be used within the JS script module. When passing data to the module script the EditorConfiguration can be used, which is a name value collection. To create a EditorDescriptor class it must inherit from the EditorDescriptor class and needs to be decorated with the EditorDescriptorRegistration attribute.

[EditorDescriptorRegistration(TargetType = typeof(IEnumerable<CountryEmailAddress>), UIHint = "CountryEmailAddressesProperty")]
public class CountryEmailAddressEditorDescriptor : EditorDescriptor
{
    public CountryEmailAddressEditorDescriptor()
    {
        var countryService = new CountryService();
 
        ClientEditingClass = "app.editors.CountryEmailAddressesProperty";
 
        EditorConfiguration["Countries"] = countryService.GetAll();
    }
}

In the code above I used the TargetType of the EditorDescriptorRegistration attribute for defining the target type of the property. The UIHint is used for defining the custom property, I will use this later in the code for defining the property on the page type. In the constructor of the class I set the ClientEditingClass and insert a collection of countries on the EditorConfiguration name value collection.

The method GetAll of the CountryService class returns a list of country objects. The country class contains two string properties: Code and Name.

I will explain the TargetType in the next section.

 

Property Type

 

The property type is used for retrieving and storing the data. A property type can be defined by using the PropertyDefinitionTypePlugin attribute and inheriting from the PropertyData class or inheriting from one of the existing property types, such as the PropertyLongString.

[PropertyDefinitionTypePlugIn(Description = "A property for list of country email adress.", DisplayName = "Country Emailaddress Items")]
public class PropertyCountryEmailAddress : PropertyLongString
{
    public override Type PropertyValueType
    {
        get { return typeof(IEnumerable<CountryEmailAddress>); }
    }
 
    public override object Value
    {
        get
        {
            var value = base.Value as string;
            if (value == null)
            {
                return null;
            }
            var serializer = new JavaScriptSerializer();
            return serializer.Deserialize(value, typeof(IEnumerable<CountryEmailAddress>));
        }
        set
        {
            if (value is IEnumerable<CountryEmailAddress>)
            {
                var serializer = new JavaScriptSerializer();
                base.Value = serializer.Serialize(value);
            }
            else
            {
                base.Value = value;
            }
        }
    }
 
    public override object SaveData(PropertyDataCollection properties)
    {
        return LongString;
    }
}

In the above code I created a property type. A number of properties and methods can be overridden from the PropertyLongString. The Type property indicates which Type is used for working with this property. In my case I would like to work with a IEnumerable of CountryEmailAddress objects, I will show the CountryEmailAddress class later. The Value property is used for serializing and deseriarilizing the property value. The value is stored as a string and deserialized as a IEnumerable of CountryEmailAddress objects.

The CountryEmailAddress contains two properties the country code and the email address.

 

public class CountryEmailAddress
{
    public string CountryCode { get; set; }
    public string EmailAddress { get; set; }
}
JS module script
define([
    "dojo/_base/array",
    "dojo/_base/declare",
 
    "dijit/registry",
    "dijit/_Widget",
    "dijit/_TemplatedMixin",
    "dijit/_Container",
    "dojox/layout/TableContainer",
    "dijit/form/ValidationTextBox",
    "dojo/text!./templates/CountryEmailAddresses.html"
],
function (
    array,
    declare,
 
    registry,
    _Widget,
    _TemplatedMixin,
    _Container,
    TableContainer,
     ValidationTextBox,
    template
) {
    return declare("app.editors.CountryEmailAddressesProperty", [
        _Widget,
        _TemplatedMixin,
        _Container], {
            templateString: template,
            postCreate: function () {
 
                var layout = new TableContainer({
                    showLabels: true,
                    orientation: "horiz"
                });
 
                for (var i = 0; i < this.countries.length; i++) {
                    var textBox = new ValidationTextBox({
                        name: "id_" + this.countries[i].code,
                        id: this.countries[i].code,
                        type: "text",
                        class: "countryEmailTextbox",
                        title: this.countries[i].name,
                        invalidMessage: "The entered value is not valid. Must be a valid e-mail.",
                        regExp: "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
                    });
                    layout.addChild(textBox);
                }
                this.container.appendChild(layout.domNode);
            },
            _getValueAttr: function () {
                var jsonObject = [];
                var textboxCollection = dojo.query(".countryEmailTextbox");
 
                for (var i = 0; i < textboxCollection.length; i++) {
 
                    if (textboxCollection[i] != null) {
 
                        var widget = registry.byNode(textboxCollection[i]);
 
                        var item = {
                            CountryCode: widget.id,
                            EmailAddress: widget.get("value")
                        }
 
                        jsonObject.push(item);
                    }
                }
                return jsonObject;
            },
            _setValueAttr: function (val) {
                if (val != null) {
                    for (var i = 0; i < val.length; i++) {
                        var widget = registry.byId(val[i].countryCode);
                        if (widget != null) {
                            widget.set("value", val[i].emailAddress);
                        }
                    }
                }
            },
            isValid: function () {
                var isValid = true;
                var textboxCollection = dojo.query(".countryEmailTextbox");
 
                for (var i = 0; i < textboxCollection.length; i++) {
 
                    if (textboxCollection[i] != null) {
 
                        var widget = registry.byNode(textboxCollection[i]);
                        isValid = isValid && widget.isValid();
                    }
                }
                return isValid;
            }
        }
    );
});

The script uses the AMD (Asynchronous Module Definition) pattern for loading Javascript modules. In my script I use a number of modules, I will explain just a couple of the modules:

dojo/_base/declare Contain functions for define the DOJO class
dijit/registry Stores a collection of widgets within a page. It contains functions for retrieving a widget by ID or DOM node.
dijit/form/ValidationTextBox A TextBox control that checks whether the user input is valid
dojox/layout/TableContainer A layout control that displays the containing widgets in a table layout.
dojo/text!./templates/CountryEmailAddreses.html A reference to the HTML template of the property

 

For more information about DOJO classes and controls visit the reference guide.

My DOJO (property) widget contains five properties, described below:

templateString Is the reference to the HTML template of the property
postCreate This function is called when the widget has been rendered. In this function the list of countries (passed from the EditorDescriptor) is looped and for each country a ValidationTextBox control is created.
_getValueAttr This function is the getter for the value property. The getter will return a JSON that contains a list of countries and email addresses.
_setValueAttr This function is the setter for the value property. For each item in the JSON value the TextBox control is set with the email address.
isValid This function checks all TextBox control values if it’s a valid email address.

 

When defining the property on the page type a couple of things needs to be done. First the UIHint attribute must match with the UIHint value set on the EditorDescriptor class. The BackingType attribute is set to the PropertyCountryEmailAddress class for deserialize the property value to a IEnumerable of CountryEmailAddress.

[UIHint("CountryEmailAddressesProperty")]
[Display(
    Name = "Country email addresses",
    Description = "Country email addresses",
    GroupName = SystemTabNames.Content,
    Order = 10)]
[BackingType(typeof(PropertyCountryEmailAddress))]
public virtual IEnumerable<CountryEmailAddress> CountryEmailAddresses { get; set; }

The result in the CMS:

Because the property returns a IEnumerable a simple loop can display the items on the page:

 

You can find the full source code on my GitHub account.

Share this article

17 June 2015 Patrick

Hi Chris,

EPiServer will call the _getValueAttr method when the value of the textbox changes (onblur event).

17 June 2015 Chris

Hi,
Nice post with high value.
I have one question: how your dojo script notifies EPI that value in any ValidationTextBox has changed?

Chris

22 February 2015 Patrick

Hi Naz,

Take a look at my latest blog post: http://www.patrickvankleef.com/2015/02/22/multiple-page-picker-property-in-dojo/

Patrick

16 February 2015 Naz

Thanks Patrick, I will wait for it,
Anyways, if you care to share a quick start please, my email is nazimzia@hotmail.com.

Cheers,
Naz

15 February 2015 Patrick

At the moment I’m working on a custom property that’s using the page selection property of EPiServer. This week I will publish a blog about it and of course share my code.

Patrick

15 February 2015 Nazim

Thanks Patrik, I am getting it slowly now, One more question:-

Do you know anything in dojo in the script file actually I can call the Page selection property similar to episerver. A button “…” that looks into the editor pages similar to page reference collection and when I select a page it stores the pagereference? I am struggling to find anywhere.

Many thanks,
Naz

12 February 2015 Patrick

Hi Naz,

The property value should be saved as JSON, just as how I did in the example. The PropertyCountryEmailAddress class is responsible how the value should be stored and retrieved. The Dojo script will receive the JSON to set the values when in editing mode and it should return a JSON when saving the page.

11 February 2015 Naz

Excellent !!, very well written and explained everything precisely. Thank you so much.
I have managed to repeat this on my version 7 of episerver and working fine.

My requirement is : I am trying to create a custom property using Dojo which will be like link collection, you click on the button and it opens up a dialogue box where you fill in few fields and a Page reference field and it stores them in a collection. Means for each page there will be number of entries and each entry will have fields say 2 text boxes and 1 page reference field. I fill the value and when I hit save, it should just save to the page. I need to know how the page value will be saved and how do i do this. Have you done anything similar or if you can guide me through and also I can do this in dojo. Correct me please if I am wrong?

Many thanks once again.

06 February 2015 Patrick

Hi Raju,

It’s also possible to override the ModifyMetadata when you need the entire metadata object. EPiServer advises that code is placed in the constructor of the EditorDescriptor class when you don’t need the entire metadata object.

http://world.episerver.com/documentation/Class-library/?documentId=episerverframework/7/2e6efd8c-d09f-7911-f656-f4a038f14877

06 February 2015 Raju

Nice post! learned something. May i know why overridden ModifyMetadata() from EditorDescriptor is not used? is it fine with coding within constructor?