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.

Comments