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
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>
[EditorDescriptorRegistration(TargetType = typeof(IEnumerable<ContentReference>), UIHint = "MultiPageProperty")]
public class MultiPageDescriptor : EditorDescriptor
{
public MultiPageDescriptor()
{
ClientEditingClass = "app.editors.MultiPageProperty";
}
}
[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;
}
}
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.