linuse
Jan 16, 2014
  4772
(4 votes)

Editing complex properties

In this post I’ll show how to create editors for complex properties in EPiServer. With complex properties I am referring to block properties that have several sub properties. For this blog post, I’ll be using the ButtonBlock of the Alloy Template package:

public class ButtonBlock : SiteBlockData
{
    public virtual string ButtonText { get; set; }
 
    public virtual Url ButtonLink { get; set; }
}

 

When the editing system in EPiServer scaffolds the meta data used to create the editor form we recursively go through the object graph until we find an object type with a registered editor. By default, this would mean that we would not find an editor for the ButtonBlock type and therefore go down to the properties for the type, in this case string and Url which both has editor descriptors registered. Let us add an editor descriptor for ButtonBlock to take over editing the entire block:

[EditorDescriptorRegistration(TargetType = typeof(ButtonBlock))]
public class ButtonBlockEditorDescriptor : EditorDescriptor
{
    public ButtonBlockEditorDescriptor()
    {
        ClientEditingClass = "alloy/editors/ButtonBlockEditor";
    }
}

 

We’ll also add a simple editor widget

define([
    "dojo/_base/declare",
    "dojo/_base/lang",
 
    "dijit/_Widget",
    "dijit/_TemplatedMixin",
    "dijit/_WidgetsInTemplateMixin",
 
    "dijit/form/ValidationTextBox"
],
function (
    declare,
    lang,
 
    _Widget,
    _TemplatedMixin,
    _WidgetsInTemplateMixin,
 
    ValidationTextBox
) {
 
    return declare([_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
 
        templateString: "<div class=\"dijitInline\">\
                           <div data-dojo-attach-point=\"buttonText\" data-dojo-type=\"dijit/form/ValidationTextBox\"></div>\
                        </div>",
 
        value: null,
 
        onChange: function (value) {
            // Event to trigger updates
        },
 
        postCreate: function () {
            // call base implementation
            this.inherited(arguments);
 
            // Init textarea and bind event
            this.own(this.buttonText.on("change", lang.hitch(this, "_onTextAreaChanged")));
        },
 
        // Event handler to trigger saving complex value
        _onTextAreaChanged: function (value) {
 
            var newValue = {
                buttonText: value,
                buttonLink: Date().toLowerCase()
            };
            this.value = newValue;
            this.onChange(newValue);
        }
    });
});

 

The Button Block editor looks like this:

SimpleEditor

Though the example above doesn’t really make any sense, it shows how you can create an editor for a complex object and setting the value to a complex object (in this case just auto-populating the buttonLink value to a date and time). If you want to create an editor that actually does something useful you would probably have to add some more input widgets to the editor and combine the values from the different fields. This also means that you will have to layout the sub fields to fix nicely to the form. Though surely possible, it raises the complexity and amount of work quite a lot. Given the block mentioned above for instance, you need to know how an URL is edited in EPiServer.

Extending the FormContainer

In many cases, you don’t want to control how the sub editors are created. You just want to attach events and logic so that several widgets are bound to each other. For instance:

  • Hide/show a widget depending on another widgets value.
  • Clear the value of a property when another property changes.
  • Populate a drop down with certain values depending on the values of another property.

This can be done by inheriting from "epi/shell/widget/FormContainer" and attaching event handlers to the onFieldCreated and onFormCreated events. Let’s look at an example:

define([
    "dojo/_base/declare",
    "dojo/_base/lang",
 
    "epi/shell/widget/FormContainer"
],
function (
    declare,
    lang,
 
    FormContainer
) {
 
    return declare([FormContainer], {
        onFieldCreated: function (fieldName, widget) {
            this[this.getIndexSecurePropertyName(fieldName)] = widget;
        },
 
        onFormCreated: function () {
 
            var textWidget = this[this.getIndexSecurePropertyName("buttonText")];
            var linkWidget = this[this.getIndexSecurePropertyName("buttonLink")];
 
            this.own(linkWidget.on("change", lang.hitch(this, function (newValue) {
                if (!newValue) {
                    textWidget.set("value", "");
                }
            })));
        },
 
        getIndexSecurePropertyName: function (propertyName) {
            return "prefix-" + propertyName;
        }
    });
});

 

And the editor now looks the same as in the default Alloy templates:

ComplexEditor

We are basically doing two things in this widget:

  1. We store a reference for each sub widget with the property name in the onFieldCreated event.
  2. In inFormCreated we attach an event handler to the linkWidget’s changed event and clear the textWidget if the new link value is empty. (Don’t forget to “own” the event handler so that the widget destroys this when the widget is destroyed so that we don’t get a memory leak)

Note: For the sample above I noticed that the “change” event is not triggered when adding a link through drag and drop or when removing a link through clicking on the (x) next to an existing link. I have added a bug report for this.

Solving the dependent drop down case could be done by calling a custom method on a widget, for instance a “populateDropDown” method with the value from another widget but I will not go into more detail in this blog post.

My colleague Duong an Nguyen has written a more detailed sample with a similar but slightly different approach that you probably want to read as well: http://world.episerver.com/Blogs/Duong-Nguyen/Dates/2014/1/Country-Region-drop-down-lists-in-All-properties-mode/

Jan 16, 2014

Comments

Please login to comment.
Latest blogs
Plug-in manager is back in CMS 12

Plug-in manager is back in the UI, what is it and how can i use it?

Luc Gosso (MVP) | Oct 6, 2022 | Syndicated blog

Display Child Pages in Content Delivery API Response

The below example will implement an instance of IContentConverterProvider to customise the serialisation of PageData and output child pages in the...

Minesh Shah (Netcel) | Oct 4, 2022

Bring the Report Center back in Optimizely CMS 12

The Report Center has been a part of Optimizely CMS since its first debut in version 5R2 in 2008, but in CMS 12, it's removed! Don't despair! Make...

Tomas Hensrud Gulla | Oct 4, 2022 | Syndicated blog

Customizing Property Lists in Optimizely CMS

Generic property lists is a cool editorial feature that has gained a lot of popularity - in spite of still being unsupported (officially). But if y...

Allan Thraen | Oct 2, 2022 | Syndicated blog

Optimizely names Luminary Senior Developer, Ynze Nunnink, OMVP

Luminary Senior Developer and Optimizely Lead, Ynze Nunnink has secured the coveted position of Optimizely MVP. Earning a Platinum badge for his...

Ynze | Oct 2, 2022 | Syndicated blog

Content Delivery API – The Case of the Duplicate API Refresh Token

Creating a custom refresh provider to resolve the issues with duplicate tokens in the DXC The post Content Delivery API – The Case of the Duplicate...

David Lewis | Sep 29, 2022 | Syndicated blog