Multiple page picker property in 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.

 

Result

 

Module.config

 

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.

<module>
   
  <assemblies>
    <add assembly="EPiServerCustomProperty" />
  </assemblies>
   
  <dojoModules>
    <add name="app" path="Scripts" />
  </dojoModules>
 
  <clientResources>
    <add name="epi-cms.widgets.base" path="Styles/MultiPageProperty.css" resourceType="Style"/>
  </clientResources>
   
</module>
EditorDescriptor
[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
        {
            get
            {
                var value = base.Value as string;
                if (value == null)
                {
                    return null;
                }
                var serializer = new JavaScriptSerializer();
                return serializer.Deserialize(value, typeof(IEnumerable<ContentReference>));
            }
            set
            {
                if (value is IEnumerable<ContentReference>)
                {
                    var serializer = new JavaScriptSerializer();
                    base.Value = serializer.Serialize(value);
                }
                else
                {
                    base.Value = value;
                }
            }
        }
 
        public override object SaveData(PropertyDataCollection properties)
        {
            return LongString;
        }
    }

 

JS module script
define([
     "dijit",
     "dojo",
     "dojo/_base/declare",
     "dojo/_base/lang",
     "dojo/when",
     "dojo/dom-construct",
     "dojo/_base/array",
     "dojo/query",
 
    "dijit/_Widget",
    "dijit/_TemplatedMixin",
    "epi-cms/widget/_HasChildDialogMixin",
     
    "epi/dependency",
    "epi-cms/widget/ContentSelectorDialog",
    "epi/shell/widget/dialog/Dialog",
 
    "dojo/text!./templates/MultiPage.html"
], function (
    dijit,
    dojo,
    declare,
    lang,
    when,
    domConstruct,
    array,
    query,
 
    _Widget,
    _TemplatedMixin,
    _HasChildDialogMixin,
 
    dependency,
    ContentSelectorDialog,
    Dialog,
 
    template
) {
    return declare("app.editors.MultiPageProperty", [
        _Widget,
        _TemplatedMixin,
        _HasChildDialogMixin
    ], {
        _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"
            }));
            this.tableNode.appendChild(colgroup);
            this.container.appendChild(this.tableNode);
 
            this._buildTableRows();
        },
 
        _buildTableRows: function () {
            array.forEach(this.value, function (item) {
                var page = this._getContentData(item);
                this._addRowElementToTable(item, page.name);
            }, this);
        },
 
        _addRowElementToTable: function (pageId, pageName) {
            var button = domConstruct.create("div",
            {
                id: "removeButton_" + pageId,
                className: "removeButton",
                click: dojo.hitch(this, function (event) {
                    this._removeFromValue(event);
                })
            });
            var newRow = domConstruct.create("tr");
            var newColl1 = domConstruct.create("td",
            {
                innerHTML: pageName
            });
            var newColl2 = domConstruct.create("td");
            newColl2.appendChild(button);
 
            newRow.appendChild(newColl1);
            newRow.appendChild(newColl2);
 
            this.tableNode.appendChild(newRow);
        },
 
        _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;
            this.dialog.show();
        },
 
        _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) {
 
                    this._addRowElementToTable(contentSelectorValue, p.name);
 
                    this.value.push(contentSelectorValue);
                }));
 
                this.onChange(this.value);
            }
        },
 
        _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 = button.id.split("_")[1];
 
            this.value.splice(this.value.indexOf(id), 1);
 
            // remove row from table
            var row = query(button).closest("tr");
            row.remove();
 
            this.onChange(this.value);
        }
    });
});

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.

Share this article
Comments
27 November 2015 dharani

Hi All,
I am new to episerver as well as dojo.
I am following this article for image to url custom dojo widget. I have got the image id in the function
_onDialogExecute: function () {
if (this.value == null) {
this.value = [];
}
var contentSelectorValue = this.contentSelectorDialog.get(“value”);
if (array.indexOf(this.value, contentSelectorValue) == -1) {
urlValue = this._getContentData(contentSelectorValue);
this.onChange(this.value);
}
},
On the contentSelectorValue , i am getting the image id, iwould like to get the image name, can anyone help me ?

23 March 2015 Patrick

Hi Josef,

I’ve updated the blog and my code on GitHub. I think something happens with the value property after the postcreate is executed. I’ve added the same check to the _onDialogExecute method.

23 March 2015 Petrouchka

Set this.value = this.Val in _onAddButtonClick.

23 March 2015 Petrouchka

Hi Josef
I think this.value is a reserved property you can’t change it, it will remain null.
Add another array property, for example we can name it to Val, and use this.Val instead, the code will work.

03 March 2015 Patrick

Hi Josef,

I haven’t look yet if the DOJO widget works in EPiServer 8, I’ve created it with version 7.5. Maybe something has changed, I will look into this problem soon,.

What you could do is set a break-point and see what happens when the postcreate function is called.

28 February 2015 Josef Ottosson

Im using EPiServer 8 (mvc) btw!

28 February 2015 Josef Ottosson

Awesome post!

One thing though, it seems that this code in the postCreate function doesn’t fire:

if (this.value == null) {
this.value = [];
}

When I’ve added a page I get an error in the console saying that I can’t call push on something that’s null.

I added the same code right before the push statement and now it works, it’s really dirty so could you please clarify why the above code doesn’t work?

24 February 2015 Patrick

The code applies for CMS verison 7.5, I’ve not tested it on a previous version.

23 February 2015 Naz

Thanks, very nice post, Implementing it straightaway, but facing few errors.
I am using 7.1 atm. It seems it requires few more module files.

Does this apply to CMS 7.5 versions or 7.1?.

Following is the javascript error I am getting

http://localhost:17007/episerver/Shell/2.1.109/ClientResources/dtk/dojo/dojo.js:15:436 req.injectUrl/_10ahttp://localhost:17007/episerver/Shell/2.1.109/ClientResources/dtk/dojo/dojo.js:15:17711

24 February 2015 Henrik Fransas

Thanks for sharing!

10 August 2016 Sharoze

By using this property iam unable to save the selected property . Can any one help me . Iam able to select but once i click on publish , the selected pages are vanished .

Rgds
Ali