A while ago I saw a forum question about how to Enable/Disable SelectItem in SelectionFactory. I thought that it could be interesting to describe how to implement it.
Standard checkbox list
Let’s start from the basic SelectionFactory usage. To prepare a property rendered as checkbox list we could use selection factory and SelectMany attribute. First we need to create a class that implements ISelectionFactory. The class has only one method that returns list of ISelectItem. In this code sample we will return the list of continents.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System.Collections.Generic; using EPiServer.Shell.ObjectEditing; namespace EpiServerThumbnail.Business.EditorDescriptors { public class ContinentSelectionFactory: ISelectionFactory { public IEnumerable GetSelections(ExtendedMetadata metadata) { return new [] { new SelectItem { Text = "Australia", Value = "Au" }, new SelectItem { Text = "Africa", Value = "Af" }, new SelectItem { Text = "Asia", Value = "As" }, new SelectItem { Text = "Europe", Value = "Eu", }, new SelectItem { Text = "North America", Value = "NoAm" }, new SelectItem { Text = "South America", Value = "SoAm" } }; } } } |
Then we need to mark the property to use CheckBoxListEditor as editing widget and ContinentSelectionFactory as data source. One of the easiest ways to achieve this is to annotate the property with SelectMany attribute. Based on the SelectionFactoryType it will create a desired editor in Edit Mode.
1 2 3 4 5 6 7 8 9 10 |
[SiteContentType(GUID="17583DCD-3C11-49DD-A66D-0DEF0DD601FC", GroupName = Global.GroupNames.Products)] public class ProductPage : StandardPage { //... [SelectMany(SelectionFactoryType = typeof(ContinentSelectionFactory))] public virtual string Continents { get; set; } //... } |
With those few lines of code we have prepared an editable list of checkboxes.
Extended checkbox list
Now we would like to extended this property. It should display a list of all elements defined in the data source. Some of them should be disabled in Edit Mode. Let’s not focus on enable/disable condition, but on the way how to prepare widget with readonly elements. Of course we could create everything from scratch, but let’s try to reuse EPiServer framework code as much as possible.
Backend development
Selection factory contains GetSelections method that returns IEnumerable<ISelectItem>. Thanks to C# support for covariance we could return any collection whose elements implement ISelectItem. The example above use SelectItem class, but now we need to pass Enabled property as well. We could create a new class that inherits from SelectItem and add a new field to it.
1 2 3 4 |
public class ExtendedSelectItem : SelectItem { public bool Enabled { get; set; } } |
Now the selection factory returns IEnumerable of ExtendedSelectItem elements. The Enabled property is set for each element. In this example “Australia” and “South America” should not be selectable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
using System.Collections.Generic; using EPiServer.Shell.ObjectEditing; namespace EpiServerThumbnail.Business.EditorDescriptors { public class ContinentSelectionFactory: ISelectionFactory { public IEnumerable GetSelections(ExtendedMetadata metadata) { var items = new[] { new ExtendedSelectItem { Enabled = false, Text = "Australia", Value = "Au" }, new ExtendedSelectItem { Enabled = true, Text = "Africa", Value = "Af" }, new ExtendedSelectItem { Enabled = true, Text = "Asia", Value = "As" }, new ExtendedSelectItem { Enabled = true, Text = "Europe", Value = "Eu", }, new ExtendedSelectItem { Enabled = true, Text = "North America", Value = "NoAm" }, new ExtendedSelectItem { Enabled = false, Text = "South America", Value = "SoAm" } }; return items; } } } |
Should we do something more in the backend code? Let’s check how the data are serialized on the client.
I set the debugger breakpoint in CheckBoxListEditor buildRendering method. There is property selections used by the widget to prepare the list.
The enabled property is already on the list! There is nothing more to implement.
The Dojo widget
As we saw while debugging, our checkbox list use “epi-cms/contentediting/editors/CheckBoxListEditor” Dojo widget. In the buildRendering method there is a loop over selection property. The _addCheckBoxForItem method creates a checkbox widget and add this checkbox to checkboxes collection. In this example we could try to override buildRendering method, use checkboxes list and override them.
1 2 3 4 5 6 |
//... buildRendering: function () { this.inherited(arguments); array.forEach(this.selections, this._addCheckBoxForItem, this); } //... |
We need to prepare custom editor that inherits from CheckBoxListEditor and override buildRendering method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
define([ "dojo/_base/declare", "dojo/_base/array", "epi-cms/contentediting/editors/CheckBoxListEditor" ], function ( declare, array, _CheckBoxListEditor ) { return declare("alloy.editors.extendedCheckBoxListEditor", _CheckBoxListEditor, { buildRendering: function () { this.inherited(arguments); if (this.readOnly) { return; } array.forEach(this._checkboxes, this._setCheckboxEnabled, this); }, _setCheckboxEnabled: function(checkbox) { var selectedItem = array.filter(this.selections, function (item) { return item.value == checkbox.value; }, this)[0]; checkbox.set("readOnly", selectedItem.enabled === false); } }); }); |
We call the base method with this.inherited(arguments); and then execute our custom code. The _setCheckboxEnabled method sets the checkbox state to ReadOnly when enabled property is false. This will make the checkbox not selectable.
The last thing to implement is to combine backend and frontent code and use Dojo editor and the selection factory together. We cannot use the SelectMany attribute anymore, because there is no way to pass widget class name. We could prepare a separate EditorDescriptor, but there is a simpler way. We will use ClientEditor attribute which allows us to set both client editing class and selection factory.
1 2 3 4 5 6 7 8 |
[SiteContentType(GUID="17583DCD-3C11-49DD-A66D-0DEF0DD601FC", GroupName = Global.GroupNames.Products)] public class ProductPage : StandardPage { //... [ClientEditor(ClientEditingClass = "alloy.editors.extendedCheckBoxListEditor", SelectionFactoryType = typeof(ContinentSelectionFactory))] public virtual string Continents { get; set; } //... } |
The final result is presented below:
We had to write less than 100 lines of code to get interesting results and change the existing functionality. We now have a powerful mechanism of extending editor based on customer needs.
This shows how flexible the EPiServer properties framework is!
Full source code is available on Gists.