A field's control is chosen by its type: a string gets a textbox, a DropDown a dropdown, an
HtmlString the WYSIWYG. When none of the built-in controls fits, you can write your own and
name it on the field.
Reach for the built-in controls first. A field editor is for the case they cannot express: a colour swatch, a map pin, a rating, a lookup against a system of yours.
A field editor is JavaScript: it runs in the editor, in the author's browser, and draws inside the field's row. The CMS still draws the row itself, with its label, required mark, help, validation error and description.
Step 1: name the editor on the field
[FieldId("banner.colour")]
[FieldDisplay("Colour", "Settings", "The banner's background colour.")]
[FieldOptions("eaffd8", "Light Green", "cbf0ac", "Dark Green")]
[FieldEditor("swatch")]
public DropDown? Colour { get; set; }
[FieldEditor("swatch")] says which control draws the field. The property's type still decides
what is stored and how your template reads it.
Step 2: write the editor
Add a script to your site's wwwroot. This example is wwwroot/js/cms-field-editors.js.
window.cmsExtensions.addFieldEditor('swatch', {
mount: function (host, editor) {
var buttons = (editor.options.options || []).map(function (option) {
var button = document.createElement('button');
button.type = 'button';
button.title = option.label;
button.style.background = '#' + option.value;
button.onclick = function () {
editor.setValue(option.value);
draw(option.value, editor.editable);
editor.commit();
};
host.appendChild(button);
return { el: button, value: option.value };
});
function draw(value, editable) {
buttons.forEach(function (b) {
b.el.classList.toggle('is-selected', b.value === value);
b.el.disabled = !editable;
});
}
draw(editor.value, editor.editable);
return { update: draw };
}
});
mount(host, editor) is called once for each field using the editor, when its row is drawn.
Build your control inside host.
The editor object
| Member | What it is |
|---|---|
editor.value |
The field's current value. |
editor.editable |
false when the author may not change the page: an older version, no Edit right, or somebody else holds the edit lock. |
editor.setValue(value) |
Changes the value and marks the page as having unsaved changes. Ignored when the page is read-only. |
editor.commit() |
Saves the draft, as leaving a built-in field does. |
editor.field |
fieldId, displayName, description and fieldType. |
editor.options |
The field's options: options (the [FieldOptions] list, as { value, label }), required, helpText, cssClass. |
When to call setValue and commit
- A control the author types into:
setValueon every input,commiton blur. - A control that is one click:
setValue, thencommitstraight away.
setValue alone does not save. commit is what writes the draft.
What mount returns
Both are optional.
| Member | When it is called |
|---|---|
update(value, editable) |
The value changed from outside your control, or the page became read-only or editable. Redraw from the arguments. It is not called for a value you just set. |
destroy() |
The row is going away. Stop timers and remove listeners you added outside host. |
Step 3: load it into the editor
Name the script in Program.cs, and a stylesheet if the control needs one:
builder.Services.AddCendia(options =>
{
builder.Configuration.GetSection("Cendia").Bind(options);
options.EditorScripts.Add("/js/cms-field-editors.js");
options.EditorStylesheets.Add("/css/cms-field-editors.css");
});
Step 4: run it
Restart the site and open an item with the field. A change to [FieldEditor] is a change to the
content type, so it goes through schema reconciliation like any other.
What is stored
A value is text. A control that holds more than one thing stores it as a JSON string, and the property that reads it should be a type that expects that. Changing a field's editor does not change values already saved, so move an existing field only to an editor that stores the same shape. The example above stores the option's value, exactly as the dropdown did.
Rules to know
- The name is matched exactly, including case. Use lowercase, and prefix a package's names
(
acme-swatch) so two packages cannot claim the same one. Registering a name twice is an error. - A name nothing registers is a textbox. A field whose script is missing, or whose package is not installed, is still editable as plain text and loses nothing.
- An editor that throws in
mountis reported in the browser console and the row says its editor could not be loaded. The value is untouched and still saves with the page. - Honour
editable. Disable your control when it isfalse.setValueis ignored then anyway, but a control that looks live and does nothing is worse than one that is greyed out. - Version compare shows the value as text. The side-by-side comparison does not run your control.
- Validate on the server. An
IFieldValidatoron the field decides what may be saved; its message appears on the row's label line as for any field.
From a package
A NuGet package registers its editors from a script in its own wwwroot, named in its
AddXxx() call. See the end of Adding tree actions.