Build Custom Editor Components
Why build a custom editor
Section titled “Why build a custom editor”The default CMS property editors handle text, numbers, dates, and references well. When editors need a richer interaction — a star rating picker, a tag selector, or a color palette — you build a custom editor component. A good custom editor reduces mistakes and speeds up content entry.
What you will do
Section titled “What you will do”- Create a Dojo widget under
ClientResources - Register the widget with an editor descriptor in C#
- Wire the descriptor to a content property via
UIHint - Test the widget in the CMS editing interface
Create the Dojo widget
Section titled “Create the Dojo widget”Place your widget in the ClientResources/Scripts folder of your project. The CMS editor loads Dojo modules from this path.
define([
"dojo/_base/declare",
"dijit/_Widget",
"dijit/_TemplatedMixin",
"epi/shell/widget/_ValueRequiredMixin"
], function (declare, _Widget, _TemplatedMixin, _ValueRequiredMixin) {
return declare(
"mysitewidgets.StarRatingEditor",
[_Widget, _TemplatedMixin, _ValueRequiredMixin],
{
templateString:
'<div class="star-rating">' +
' <span data-dojo-attach-event="onclick:_select" data-value="1">★</span>' +
' <span data-dojo-attach-event="onclick:_select" data-value="2">★</span>' +
' <span data-dojo-attach-event="onclick:_select" data-value="3">★</span>' +
' <span data-dojo-attach-event="onclick:_select" data-value="4">★</span>' +
' <span data-dojo-attach-event="onclick:_select" data-value="5">★</span>' +
'</div>',
value: null,
_select: function (evt) {
var rating = parseInt(evt.target.getAttribute("data-value"), 10);
this._set("value", rating);
this.onChange(rating);
},
_setValueAttr: function (val) {
this._set("value", val);
}
}
);
}); Key details:
- Extend
dijit/_Widgetanddijit/_TemplatedMixinfor lifecycle management. - Mix in
_ValueRequiredMixinso the CMS validation pipeline recognizes your widget. - Call
this.onChange(value)whenever the user changes the value — this notifies the CMS to mark content as dirty.
Register a module path
Section titled “Register a module path”Add your module path to module.config so the CMS can resolve your widget.
<?xml version="1.0" encoding="utf-8"?>
<module>
<dojo>
<paths>
<add name="mysitewidgets" path="Scripts" />
</paths>
</dojo>
</module> Place this file in the root of your ClientResources folder. The name attribute must match the module prefix used in your define call.
Create the editor descriptor
Section titled “Create the editor descriptor”The editor descriptor connects your widget to a .NET property type or UIHint.
using Optimizely.Cms.Shell.ObjectEditing;
using Optimizely.Cms.Shell.ObjectEditing.EditorDescriptors;
namespace MySite.EditorDescriptors;
[EditorDescriptorRegistration(
TargetType = typeof(int?),
UIHint = "StarRating")]
public class StarRatingEditorDescriptor : EditorDescriptor
{
public override void ModifyMetadata(
ExtendedMetadata metadata,
IEnumerable<Attribute> attributes)
{
ClientEditingClass = "mysitewidgets/StarRatingEditor";
base.ModifyMetadata(metadata, attributes);
}
} Apply the UIHint to a property
Section titled “Apply the UIHint to a property”using Optimizely.Cms.Core;
using Optimizely.Cms.Core.Attributes;
using System.ComponentModel.DataAnnotations;
namespace MySite.Models.Pages;
[ContentType(
DisplayName = "Review Page",
GUID = "b2c3d4e5-f6a7-8901-bcde-234567890abc")]
public class ReviewPage : PageData
{
[Display(Name = "Review Title", Order = 10)]
[Required]
public virtual string ReviewTitle { get; set; }
[Display(Name = "Rating", Order = 20)]
[UIHint("StarRating")]
[Range(1, 5)]
public virtual int? Rating { get; set; }
} Build the project, navigate to the editor, and create a new Review Page. You should see the star rating widget instead of a plain number input.
Add CSS styling
Section titled “Add CSS styling”Place a CSS file alongside your widget and reference it in module.config.
.star-rating span {
font-size: 24px;
cursor: pointer;
color: #ccc;
transition: color 0.15s;
}
.star-rating span:hover,
.star-rating span:hover ~ span {
color: #f5a623;
} Common issues
Section titled “Common issues”| Issue | Cause | Fix |
|---|---|---|
| Widget not loading | Module path not registered | Verify module.config path and name |
| Value not saving | onChange not called | Call this.onChange(value) on every change |
| Widget renders but is empty | _setValueAttr missing | Implement the setter to handle initial value |