Rendering Field Types

Before you get to the front-end coding necessary to render your field type, there’s another Component to define and a Java class to code.

Implementing a DDMFormFieldRenderer

The Component only has one property, ddm.form.field.type.name, and then you declare that you’re adding a DDMFormFieldRenderer implementation to the OSGi framework:

@Component(
    immediate = true, 
    property = "ddm.form.field.type.name=time",
    service = DDMFormFieldRenderer.class
)

Extend BaseDDMFormFieldRenderer, an abstract class implementing the API’s only required method, render. The Form engine calls the render method for every form field type present in a form, and returns the plain HTML of the rendered field type. The abstract implementation also includes some utility methods. Here’s what the time field’s DDMFormFieldRenderer looks like:

public class TimeDDMFormFieldRenderer extends BaseDDMFormFieldRenderer {

    @Override
    public String getTemplateLanguage() {
        return TemplateConstants.LANG_TYPE_SOY;
    }

    @Override
    public String getTemplateNamespace() {
        return "DDMTime.render";
    }

    @Override
    public TemplateResource getTemplateResource() {
        return _templateResource;
    }

    @Activate
    protected void activate(Map<String, Object> properties) {
        _templateResource = getTemplateResource("/META-INF/resources/time.soy");
    }

    private TemplateResource _templateResource;

}

Set the templating language (Soy closure templates), the template namespace (DDMTime) and name (render), and point to the location of the templates within your module (/META-INF/resources/time.soy).

Writing the Soy Template

Now it’s time to write the template you referenced in the renderer class: time.soy in the case of the time field type.

Create

src/main/resources/META-INF/resources/time.soy

and populate it with this:

{namespace DDMTime}

/**
* Defines the delegated template for the time field.
*/
{deltemplate ddm.field variant="'time'"}
    {call .render data="all" /}
{/deltemplate}

/**
* Prints the time field.
*/
{template .render}
    {@param name: string}
    {@param pathThemeImages: string}
    {@param value: ?}
    {@param visible: bool}
    {@param? dir: string}
    {@param? label: string}
    {@param? predefinedValue: string}
    {@param? readOnly: bool}
    {@param? required: bool}
    {@param? showLabel: bool}
    {@param? tip: string}

    {let $displayValue: $value ? $value : $predefinedValue ? $predefinedValue : '' /}

    <div class="form-group {$visible ? '' : 'hide'} liferay-ddm-form-field-time"
        data-fieldname="{$name}">
        {if $showLabel or $required}
            <label for="{$name}">
                {if $showLabel}
                    {$label}{sp}
                {/if}

                {if $required}
                    <svg aria-hidden="true" class="lexicon-icon lexicon-icon-asterisk reference-mark">
                        <use xlink:href="{$pathThemeImages}/lexicon/icons.svg#asterisk" />
                    </svg>
                {/if}
            </label>
        {/if}

        {if $showLabel}
            {if $tip}
                <span class="form-text">{$tip}</span>
            {/if}
        {/if}

        <div class="input-group">
            <div class="input-group-item">
                <input class="field form-control"
                    {if $dir}dir="{$dir}"{/if}
                    {if $readOnly}disabled{/if}
                    id="{$name}"
                    name="{$name}"
                    type="text"
                    value="{$displayValue}">
            </div>
        </div>
    </div>
{/template}

There are four important things to do in the template:

  1. Define the template namespace. The template namespace can define multiple templates for your field type by adding the namespace as a prefix.

    {namespace DDMTime}
    
  2. Set the template that’s called to render the time field. The variant="'time'" identifies the time field, and the .render names the template that renders it. The template itself follows and is defined through the block {template .render}...{/template}.

    /**
    * Defines the delegated template for the time field.
    */
    {deltemplate ddm.field variant="'time'"}
        {call .render data="all" /}
    {/deltemplate}
    
  3. Describe the template parameters. The template above uses some of the parameters as flags to display or hide some parts of the HTML (for example, the $required parameter). All listed parameters are available by default.

    {@param name: string}
    {@param pathThemeImages: string}
    {@param value: ?}
    {@param visible: bool}
    {@param? dir: string}
    {@param? label: string}
    {@param? predefinedValue: string}
    {@param? readOnly: bool}
    {@param? required: bool}
    {@param? showLabel: bool}
    {@param? tip: string}
    
  4. Write the template logic (everything encapsulated by the {template .render}...{/template} block). In the above example the template does these things:

    • Checks whether to show the label of the field and if so, adds it.

    • Checks if the field is required and adds asterisk if it is.

    • Checks if a tip is provided and displays it.

    • Provides the markup for the time field in the <input> tag. In this case a text input field is defined.

Once the template is defined, write the JavaScript file modeling your field.

Writing the JavaScript Files

Create a time_field.js file and give it these contents:

AUI.add(
    'liferay-ddm-form-field-time',
    function(A) {
        var TimeField = A.Component.create(
            {
                ATTRS: {
                    type: {
                        value: 'time'
                    }
                },

                EXTENDS: Liferay.DDM.Renderer.Field,

                NAME: 'liferay-ddm-form-field-time',

                prototype: {
                }
            }
        );

        Liferay.namespace('DDM.Field').Time = TimeField;
    },
    '',
    {
        requires: ['liferay-ddm-form-renderer-field']
    }
);

The JavaScript above creates a component called TimeField. The component extends Liferay.DDM.Renderer.Field, which gives you automatic injection of the default field parameters.

Next write the *.es.js file to configure the Soy template’s rendering. Create a file called time.es.js and populate it:

import Component from 'metal-component';
import Soy from 'metal-soy';
import templates from './time.soy';
/**
 * Time Component
 */
class Time extends Component {}
// Register component
Soy.register(Time, templates, 'render');
if (!window.DDMTime) {
    window.DDMTime = {
    };
}
window.DDMTime.render = Time;
export default Time;

This dictates that the Soy template is called to render the Time component. Then create the config.js file:

;(function() {
    AUI().applyConfig(
        {
            groups: {
                'field-time': {
                    base: MODULE_PATH + '/',
                    combine: Liferay.AUI.getCombine(),
                    filter: Liferay.AUI.getFilterConfig(),
                    modules: {
                        'liferay-ddm-form-field-time': {
                            condition: {
                                trigger: 'liferay-ddm-form-renderer'
                            },
                            path: 'time_field.js',
                            requires: [
                                'liferay-ddm-form-renderer-field'
                            ]
                        }
                    },
                    root: MODULE_PATH + '/'
                }
            }
        }
    );
})();

This file is entirely boilerplate. In fact, if you use Blade CLI to generate a field type module, you won’t need to touch this file. Functionally, it’s a JavaScript file that defines the dependencies of the declared JavaScript components (requires...), and where the files are located (path...). The Alloy loader uses config.js when it satisfies dependencies for each JavaScript component. For more information about the Alloy loader see its tutorial.

Figure 1: Add your own form field types to the Forms application.

Figure 1: Add your own form field types to the Forms application.

If you build and deploy your new field type module, you get exactly what you described in the time.soy file: a single text input field. Of course, that’s not what you want! You need a time picker.

Adding Behavior to the Field

To do more than provide a text input field, define additional behavior in the time_field.js file.

To add an AlloyUI timepicker, first specify that your component requires the aui-timepicker in the requires... block of the time_field.js and config.js:

{
    requires: ['aui-timepicker','liferay-ddm-form-renderer-field']
}

Change the default rendering of the field by overwriting the base render logic, instantiating the time picker, and adding the chosen time to the field. This occurs in the prototype block:

prototype: {
    render: function() {
        var instance = this;

        TimeField.superclass.render.apply(instance, arguments);

        instance.timePicker = new A.TimePicker(
            {
                trigger: instance.getInputSelector(),
                popover: {
                    zIndex: 1
                },
                after: {
                    selectionChange: A.bind('afterSelectionChange', instance)
                }
            }
        );
    },

    afterSelectionChange: function(event) {
        var instance = this;

        var time = event.newSelection;

        instance.set('value', time);
    }
}

Invoke the original render method—it prints markup required by the Alloy time picker. Then instantiate the time picker, passing the field type input as a trigger. In addition, add a callback method (afterSelectionChange) to be executed after the time is chosen in the time picker. This method updates the field’s value. See the Alloy documentation for more information.

Now when the field is rendered, there’s a real time picker.

Figure 2: The Alloy UI Timepicker in action.

Figure 2: The Alloy UI Timepicker in action.

Now you know how to create a new field type and define its behavior. Currently, the field type only contains the default settings it inherits from its superclasses. If that’s not sufficient, create additional settings for your field type. See the next tutorial to learn how.

« Creating Form Field TypesAdding Settings to Form Field Types »
Was this article helpful?
0 out of 0 found this helpful