Widgets

One of Preside's most powerful and easy to build features is its widget framework. Technially, a widget is a Preside viewlet for which the editorial user supplies the configuration arguments through a Preside config form. Editorial users are able to insert a Preside widget in any part of a Preside Richeditor field and the widget will be fully rendered at runtime. Visually, they look like this:

Screenshot showing widget selector

Screenshot showing widget configurator

Screenshot showing widget placeholders

Creating a new widget

A widget consists of three parts, a viewlet (with optional handler), a configuration form and a .properties resource file. Each part is registered through convention of /widgets.{widgetname}. So, to create a widget with an ID of 'tableOfContents', you could create the following files

/forms/widgets/tableOfContents.xml
/i18n/widgets/tableOfContents.properties
/handlers/widgets/TableOfContents.cfc          // optional, if only view is used
/views/widgets/tableOfContents/index.cfm       // optional, if handler is used
/views/widgets/tableOfContents/placeholder.cfm // optional

Info

The new widget dev console command gives an easy to use wizard to scaffold these files for you.

The form

The form is simply any valid Preside form definition (see: Forms system). With that said, we advise setting a i18nBaseUri value to map to the .properties file of the widget; this will make supplying labels, icons and placeholders easy to do all in the same widget resource bundle file, e.g.

<?xml version="1.0" encoding="UTF-8"?>
<form i18nBaseUri="widgets.tableOfContents:">
    <tab id="default">
        <fieldset id="default" sortorder="10">
            <field name="title" control="textinput" required="true" />
            <field name="pages" control="siteTreePagePicker" multiple="true" sortable="true" />
        </fieldset>
    </tab>
</form>

In addition, and as of Preside 10.7.0, you can also specify a categories attribute on your widget form element. This will allow you to later filter available widgets for a particular Richeditor instance (see below), e.g.

<?xml version="1.0" encoding="UTF-8"?>
<form i18nBaseUri="widgets.newsletterPromoBox" categories="newsletter,email">
    <tab id="default">
        <fieldset id="default" sortorder="10">
            ...

The i18n resource file

At a minimum, you should supply three keys, title, description and iconClass:

title=Form Builder Form
description=Embed a Form Builder Form in your content
iconclass=fa-check-square-o

These keys will be used in the widget selector to help your content editors choose which widget to insert into their content.

Additional keys can then be used for any purpose you like, for example, configuration field labels, help and placeholders:

title=Form Builder Form
description=Embed a Form Builder Form in your content
iconclass=fa-check-square-o

# ...

placeholder=Form: {1}

# ...

field.instanceid.title=Instance name
field.instanceid.placeholder=e.g. 'Contact page'
field.instanceid.help=If you plan on embeddeding the same form in multiple locations, you can use the instance name field to report against which instance of the form your visitors used when submitting their responses.

# ...

The render viewlet

The viewlet used to render a widget at runtime will be widgets.{widgetid}, or widgets.{widgetid}.index. If you're creating a handler, create it at /handlers/widgets/MyWidget.cfc and implement an index action to process the render.

The args struct passed to the action will contain the user configured values from the config form. For example:

// /handlers/widgets/FormBuilderForm.cfc
component {
    property name="formbuilderService" inject="formbuilderService";

    private function index( event, rc, prc, args={} ) {
        var formId   = args.form   ?: "";
        var layout   = args.layout ?: "";
        var rendered = "";

        if ( Len( Trim( formId ) ) ) {
            if ( !formbuilderService.isFormActive( formId ) ) {
                if ( !event.isAdminUser() ) {
                    return "";
                }

                rendered = '<div class="alert alert-warning"><p><strong>' & translateResource( "formbuilder:inactive.form.admin.preview.warning") & '</strong></p></div>';
            }
            rendered &= formbuilderService.renderForm(
                  formId           = formId
                , layout           = layout
                , configuration    = args
                , validationResult = rc.validationResult ?: ""
            );
        }

        return rendered;
    }

    ...
}

Placeholder viewlet

In addition to a runtime render viewlet, you can also supply a placeholder viewlet so that you can customize the appearance of the placeholder that appears in the richeditor. The convention based viewlet path is widgets.{widgetid}.placeholder. For example:

// /handlers/widgets/FormBuilderForm.cfc
component {
    property name="formbuilderService" inject="formbuilderService";

    ...

    private string function placeholder( event, rc, prc, args={} ) {
        var fbForm          = formbuilderService.getForm( args.form ?: "" );
        var translationArgs = [ fbForm.name ?: "unknown form" ];

        if ( Len( Trim( args.instanceid ?: "" ) ) ) {
            translationArgs[1] &= " (" & args.instanceid & ")";
        }

        return translateResource( uri="widgets.FormBuilderForm:placeholder", data=translationArgs );
    }
}

Filtering widgets in editors

As of Preside 10.7.0, you can limit the widgets that are selectable in a given richeditor. To do so, use the widgetCategories attribute of the Form control: Rich editor form control. For example, in a form:

<field name="newsletter_body" control="richeditor" widgetCategories="email,newsletter" />

    ...

Or, in a Preside Object:

property name="newsletter_body" type="string" dbtype="text" widgetCategories="email,newsletter";

If a widget does not specify any categories, a category of "default" will be used. Similarly, if no widgetCategories attribute is supplied for the richeditor control, it will be assumed to be "default". With this in mind, if you wish to have a widget categorised for specific scenarios, but also wish it to appear in default richeditor configurations, you should explicitly add the "default" category:

<?xml version="1.0" encoding="UTF-8"?>
<form i18nBaseUri="widgets.myWidget:" categories="default,mySpecialCategory">
    <tab id="default">
        <!-- ... -->