Custom Fields

Custom Fields are the most common way to build integrations with Agility CMS. They allow you to render a custom UI for a field that an editor can interact with in Agility.

One of the benefits with this approach is you can build a custom field once and then re-use across any of your Content Models or Page Modules.

Use Cases

There are many different reasons why you might use a custom field. Here are a few ideas:

  • Integrate with a Digital Asset Manager to allow editors to search and select external assets
  • Integrate with a third-party/internal API to allow editors to search and select external entities
  • Provide a customized input field such as a Markdown, Block Editor, Vimeo Video, or Color Picker

Examples

Example of a content item in Agility CMS
Examples of image fields in Agility CMS
  • Cloudinary - Gives editors the ability to search and select resources from Cloudinary
  • Widen - Gives editors the ability to search and select resources from Widen
  • Vimeo - Gives editors the ability to paste a Vimeo URL and retrieve metadata for the video to incorporate in content
  • Block Editor (Experimental)- Gives editors a block-styled editor for rich media stories
  • Friendly URL field - Auto-generates a friendly URL value based off another text field
  • Color Picker field - Allows editors to select a color from a color-picker and save the value as rgba/hex
  • Selectlist from External API - A template you can start with to call an external API and allow editors to search and select external entities 
  •  Markdown - Give editors the ability to input markdown

How it Works

On load, the CMS will check for the existence of a a single Custom Fields JS file. If one has been set, the JS will be executed and consequently register each Custom Field declaration you have within the file. This will tell the CMS which custom fields are available for use, and allow you add a custom field to any Content Model or Page Module.

When a custom field is rendered within a content or page module input form in the browser, your custom code is executed.

A custom field typically includes your registration JS function (including the code that is executed when the field is rendered) and a reference to an HTML file representing your presentation layer.

Best Practices

For future backward compatibility, developers should build their Custom Fields logic encapsulated into iframes.

We recommend writing some JavaScript in the Custom Field that loads an iframe and then use iframe messages to communicate between Agility and the frontend of your integration.

An example of this can be found in the Block Editor custom field.

In the future, custom fields will enforce that they work in an iFrame for enhanced security.

Getting Started

You must have a Custom Fields Script URL set in the CMS. This can be found in UI Extensions.

Custom fields settings in Agility CMS

Demo

You can load and evaluate a handful of custom fields defined in this repository by setting your Custom Fields Script URL to https://agility.github.io/CustomFields/custom-fields.js.  

Custom Fields JS File Format

The file is comprised of one or more custom field function declarations their registrations.

The CMS uses KnockoutJS for declarative bindings, automatic UI refresh, dependency tracking, and templating.

While you don't need to use KnockoutJS to build a custom field, you will still likely interface with CMS KnockoutJS observable objects such as contentItem and fieldBinding. It is recommended to have a basic understanding of how Knockout observables work.  

Boilerplate Custom Field Function Declaration

Here is what a typical custom field may look like:

var CustomFieldFunctionDeclaration = function () {
  this.Label = "Sample Custom Field"; //[Requried] the name of the custom field as will appear in content/module def form builder
  this.ReferenceName = "SampleCustomField"; //[Required] the reference name of the custom field for internal purposes

  this.Render = function (options) {
    /// <summary>[Optional] This function is called whenever the field is rendered in an input form - this includes after the item has been saved and the field is re-rendered.</summary>
    /// <param name="options" type="Object">
    ///     <field name="$elem" type="jQueryElem">The .field-row jQuery Dom Element.</field>
    ///     <field name="contentItem" type="ContentItem Object">The entire Content Item object including Values and their KO Observable properties of all other fields on the form.</field>
    ///     <field name="fieldBinding" type="KO Observable">The value binding of thie Custom Field Type. Get and set this field's value by using this property - i.e. fieldBinding('new val')</field>
    ///     <field name="fieldSetting" type="Object">Object representing the field's settings such as 'Required', 'Hidden', 'Label', and 'Description'</field>
    ///     <field name="readonly" type="boolean">Represents if this field should be readonly or not.</field>
    /// </param>
  };

  /// <field name="Template" type="String">[Optional] The absoulte URL to an HTML template that represents your custom field, or the referencename to an Inline Code file. Your ViewModel will be automatically bound to this template.</field>
  this.Template = "https://domain.com/html/somefile.html";

  /// <field name="DepenenciesJS"> type="Array">[Optional] The Javscript dependencies that must be loaded before your ViewModel is bound. They will be loaded in the order you specify.</field>
  this.DependenciesJS = [
    { id: "somejs-reference-name", src: "https://domain.com/js/somefile.js" }, //src is an absolute URL to a JS file, or the referencename to an Inline Code file.
  ];

  /// <field name="DepenenciesCSS" type="Array">[Optional] The CSS dependencies that must be loaded before your ViewModel is bound. They will be loaded in the order you specify.</field>
  this.DependenciesCSS = [
    {
      id: "somecss-reference-name",
      src: "https://domain.com/css/somefile.css",
    }, //src is an absolute URL to a CSS file, or the referencename to an Inline Code file.
  ];

  /// <field name="ViewModel" type="KO ViewModel">The KO ViewModel that will be binded to your HTML template</field>
  this.ViewModel = function (options) {
    /// <summary>[Optional] The KO ViewModel that will be binded to your HTML template.</summary>
    /// <param name="options" type="Object">
    ///     <field name="$elem" type="jQueryElem">The .field-row jQuery Dom Element.</field>
    ///     <field name="contentItem" type="ContentItem Object">The entire Content Item object including Values and their KO Observable properties of all other fields on the form.</field>
    ///     <field name="fieldBinding" type="KO Observable">The value binding of thie Custom Field Type. Get and set this field's value by using this property.</field>
    ///     <field name="fieldSetting" type="Object">Object representing the field's settings such as 'Hidden', 'Label', and 'Description'</field>
    ///     <field name="readonly" type="boolean">Represents if this field should be readonly or not.</field>
    /// </param>

    this.value = options.fieldBinding; //reference the field KO observable value
    this.contentID = options.contentItem.ContentID; //set the contentID of the current loaded item (NewItem = -1)
    this.attrBinding = {}; //pass any custom attributes to the input field

    if (options.fieldSetting.Settings.Required === "True") {
      //if this field is marked as required, add a required parsley attribute
      this.attrBinding["data-parsley-required"] = true;
    }
  };
};

Boilerplate HTML Template

What an HTML template may look like:

<div class="sample-field">
  <input
    type="text"
    class="form-control"
    data-bind="value: value, attr: attrBinding"
  />
</div>

Register the Custom Field

Lastly, you'll need to register the field:

ContentManager.Global.CustomInputFormFields.push(
  new CustomFieldFunctionDeclaration()
);

Verifying your Custom Fields JS File

After you've set your b and added a custom field declaration function and registration, you will need to refresh the Content Manager to load the JS file.

Verify your Custom Field input field has been registered by going to Models > Content Models (or Page Modules) and create or update an existing Model or Page Module. Add a new field, and select the Custom Field type. If successful, you should see your custom field listed under Field Type.

Block Editor JSON in Agility CMS

Testing

There are several ways to test new/changes to a custom field type.

  1. For new custom fields, create a test environment for your field by creating a new Content Model/Page Module that you can use just for testing. If it works there, it will work in ANY model.
  2. For updates to existing custom fields, create a new version of the custom field type in your Custom Fields file (i.e. "Sample Field v2") and test in a test Content Model/Page Module.
  3. If making updates to an existing custom field, and creating test models are not feasible then you can make use of a Development Mode within the CMS to test changes without affecting other users who may be in the CMS.

Testing in Development Mode

Special care should be taken when making code changes to an existing custom field type that is being used across various types of content. Creating new versions of custom fields and test models can be cumbersome at times.

To address this, the Content Manager has a Development Mode that can be turned on and off by a developer. Turning Development Mode ON will set your browser session only to pull-in a Dev Mode Custom Fields JS File . This allows you to create a copy of your current Custom Fields JS file, specify it as your Development Mode JS file and make any required changes and test within the browser.

It is important to note that only your browser session will pull-in this Development Mode JS file. When development is complete, you can simply replace the live file with your dev file.

Detecting Development Mode in JS

In your custom field code, you may want to have conditional logic when running in Development Mode. An example of this could be changing some variable values such as API endpoints, or other references for testing purposes.

Detect Development Mode:

if(ContentManager.Developer.DevMode()) {
    //do something
}

Turning ON Development Mode

In the CMS go to UI Extensions and click Turn on Development Mode. The application will refresh and a pylon icon will now appear in the status bar showing that you are in Development Mode.

Turning on development mode in Agility CMS

Turning OFF Development Mode

Click on the pylon in the status bar and click OK on the dialogue window.