Skip to content
Menu
Search
Browse documentation & this page

Developer Guide

Adding a field editor

Give a field a control of your own in the editor, when none of the built-in ones fits.

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: setValue on every input, commit on blur.
  • A control that is one click: setValue, then commit straight 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 mount is 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 is false. setValue is 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 IFieldValidator on 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.