Multiple page picker property in DOJO

  • Feb 22, 2015
  • EPiServer
  • DOJO
  • |

In my previous blog post I talked about how to create a custom property in DOJO. Now I would like to explain how a property can be created by combining existing EPiServer widgets. For this example I created a property that is using the content selector widget of EPiServer. I will only explain parts of the property that differs then my previous blog post about a basic custom DOJO property. The complete source code can be found on my GitHub account. The repository contains a solution with the code from my previous blog post and the code for this post.






As you can see in the code below I’ve added a path to a style resource path. I’m using this CSS file for some styling on my property. The path that is used is relative to the directory ‘~/ClientResources/’. This means that the full path of the file is ‘~/ClientResources/Styles/MultiPageProperty.css’.

The EditorDescriptor and the Property type are almost identical as the code from my previous blog post. To learn more about those parts read my previous post about creating a basic custom property in DOJO.

        <add assembly="EPiServerCustomProperty" />
        <add name="app" path="Scripts" />
        <add name="epi-cms.widgets.base" path="Styles/MultiPageProperty.css" resourceType="Style"/>
[EditorDescriptorRegistration(TargetType = typeof(IEnumerable<ContentReference>), UIHint = "MultiPageProperty")]
        public class MultiPageDescriptor : EditorDescriptor
            public MultiPageDescriptor()
                ClientEditingClass = "app.editors.MultiPageProperty";
Property Type
[PropertyDefinitionTypePlugIn(Description = "A property for list of content reference.", DisplayName = "Multi page property")]
        public class PropertyMultiPage : PropertyLongString
            public override Type PropertyValueType
                get { return typeof(IEnumerable<ContentReference>); }
            public override object Value
                    var value = base.Value as string;
                    if (value == null)
                        return null;
                    var serializer = new JavaScriptSerializer();
                    return serializer.Deserialize(value, typeof(IEnumerable<ContentReference>));
                    if (value is IEnumerable<ContentReference>)
                        var serializer = new JavaScriptSerializer();
                        base.Value = serializer.Serialize(value);
                        base.Value = value;
            public override object SaveData(PropertyDataCollection properties)
                return LongString;


JS module script
    ], function (
    ) {
        return declare("app.editors.MultiPageProperty", [
        ], {
            _tableNode: null,
            templateString: template,
            postCreate: function () {
                if (this.value == null) {
                    this.value = [];
                this.tableNode = domConstruct.create("table", {
                    className: "selectedPagesTable"
                var colgroup = domConstruct.create("colgroup");
                colgroup.appendChild(domConstruct.create("col", {
                    width: "200"
                colgroup.appendChild(domConstruct.create("col", {
                    width: "50"
            _buildTableRows: function () {
                array.forEach(this.value, function (item) {
                    var page = this._getContentData(item);
                }, this);
            _addRowElementToTable: function (pageId, pageName) {
                var button = domConstruct.create("div",
                    id: "removeButton_" + pageId,
                    className: "removeButton",
                    click: dojo.hitch(this, function (event) {
                var newRow = domConstruct.create("tr");
                var newColl1 = domConstruct.create("td",
                    innerHTML: pageName
                var newColl2 = domConstruct.create("td");
            _onAddButtonClick: function(event) {
                var contentRepositoryDescriptors = this.contentRepositoryDescriptors || dependency.resolve("epi.cms.contentRepositoryDescriptors");
                this.roots = contentRepositoryDescriptors["pages"].roots;
                this.isShowingChildDialog = true;
                this.contentSelectorDialog = new ContentSelectorDialog({
                    canSelectOwnerContent: false,
                    showButtons: false,
                    roots: this.roots,
                    allowedTypes: ["episerver.core.icontentdata"],
                    showAllLanguages: true
                this.dialog = new Dialog({
                    title: "Select a page",
                    dialogClass: "epi-dialog-portrait",
                    content: this.contentSelectorDialog,
                    destroyOnHide: true
                this.connect(this.dialog, "onExecute", "_onDialogExecute");
                this.connect(this.dialog, 'onHide', '_onDialogHide');
                this.isShowingChildDialog = true;
            _onDialogExecute: function () {
                if (this.value == null) {
                    this.value = [];
                var contentSelectorValue = this.contentSelectorDialog.get("value");
                if (array.indexOf(this.value, contentSelectorValue) == -1) {
                    var page = this._getContentData(contentSelectorValue);
                    when(page, lang.hitch(this, function (p) {
            _onDialogHide: function () {
                this.isShowingChildDialog = false;
            _getContentData: function(contentlink) {
                var registry = dependency.resolve("epi.storeregistry");
                var store = registry.get("epi.cms.content.light");
                if (!contentlink) {
                    return null;
                var contentData;
                dojo.when(store.get(contentlink), function (returnValue) {
                    contentData = returnValue;
                return contentData;
            _removeFromValue: function (event) {
                // remove from property value
                var button = event.srcElement;
                var id ="_")[1];
                this.value.splice(this.value.indexOf(id), 1);
                // remove row from table
                var row = query(button).closest("tr");

The widget uses a number of other modules:

dojo/when Apply callbacks to values.
dojo/dom-construct Create HTML elements and returns the DOM node.
dojo/query Create/execute queries for returning DOM nodes.
epi-cms/widget/_HasChildDialogMixin A EPiServer class which provides the property IsShowingChildDialog. This property is used by the blur event of the main dialog.
epi-cms/widget/ContentSelectorDialog Standard EPiServer widget for selecting content.
epi/shell/widget/dialog/Dialog Standard EPiServer widget for showing a dialog.


Below an explanation of all properties/functions of the widget.

postcreate This function is called when the widget has been rendered. In this function I create a table element that is used for showing the selected pages.
_buildTableRows Create rows for the selected pages.
_addRowElementToTable Add a row for a selected page and add it to the table.
_onAddButtonClick Create and show a dialog that contains the content selector widget of EPiServer.
_onDialogExecute This event is called when a page is selected and the dialog is closed. The selected page is retrieved and added to the table. After that the onChange event is called indicating EPiServer that the value of the widget changed.
_onDialogHide This event is called when the dialog is hidden. In this function the isShowingChildDialog is set to false.
_getContentData This function returns a content data object. The function uses the epi.cms.content.light store to retrieve the content data.
_removeFromValue When a page is removed from the table this function is called to remove the row from the DOM and from the widget value. After that the onChange event is called indicating EPiServer that the value of the widget changed.


Because I’m using the BackingType attribute for setting the custom property type, the property returns an IEnumerable of ContentReference objects.

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