Sunday, March 29, 2020

Liferay 7/DXP Dynamic Data Mapping Custom Field


Liferay 7/DXP have provided very interactive web content creation and it is providing structures and templates to design web content.

Structure will use to defined data structure and template will provide look and feel to defined data structure.

To create structures Liferay internally using Dynamic Data Mapping (DDM) and it will provide nice user interface to create web content structures. It will provide default DDM form fields Text, Checkbox, Radio, TextArea, TextHTML, Number, Boolean, Date and Decimal.

These are very regular fields we can use to defined web content data structures.  Liferay also provided way to create custom DDM fields based on requirement. Creating custom DDM field will help us to complete real work application needs.

Assume we wanted mobile number field and it have certain validations. Liferay already have text field but it may not full fill mobile number input requirements.

DDM Custom filed will address such requirements so that we can create our own brand new DDM custom fields.

Default Liferay provided ddm fields




Implementing custom DDM field is required to override few of AUI modules and implement DDM provided component classes.

We can divide implantation into 3 parts

  1. Existing prtal provided module JavaScript changes
  2. Implementing OSGi component java classes
  3. Implement DDM FTL templates

Existing Portal provided module JavaScript changes

Overriding AUI modules

We need to override following two AUI modules and need to add custom DDM field AUI component.

liferay-portlet-dynamic-data-mapping-custom-fields
liferay-portlet-dynamic-data-mapping

These modules are available in portal source code of “dynamic-data-mapping-web”.



Add Liferay-JS-Config header in bnd file

To override above modules, OSGi use “Liferay-JS-Config” extender and it will help to load overridden modules at run time. Config.js is place to define overridden AUI modules information.
The following is header in bnd.bnd file in module to use “Liferay-JS-Config” extender.


Liferay-JS-Config: /META-INF/resources/js/config.js


Add Custom Filed Information in “AVAILABLE_FIELDS” List

Its is required to add custom field information in main_overriden.js file

Example

//add field type to this list
{
hiddenAttributes: MAP_HIDDEN_FIELD_ATTRS.DEFAULT,
iconClass: 'number',
label: 'Mobile Number',
type: 'ddm-mobile-number'
}

Create AUI Component Plugin

Custom AUI Field component plugin will consist of field attributed and its look and feel editors.
Need to add following component creation code in “custom_fields_override.js”

Example AUI Custom Field Component


//Create custom field component plugin
           var regexString = "/^(\\+\\d{1,3}[- ]?)?\\d{10}$/i"
           var customFieldHTML = '<input class="form-builder-field-node field-input field-input-text form-control"></input>'
           var DDMMobileNumberField = A.Component.create(
                {
                      ATTRS: {
                           customCssClass: {
                                 value: ''
                           },
                           fieldWidth: {
                                 value: ''
                           },
                           mobileNumberValidationMassage: {
                                 value: 'Please enter valid mobile number'
                           },
                           mobileNumberRegex: {
                                 value: regexString
                           },
                           dataType: {
                                 value: 'string'
                           },
                           fieldNamespace: {
                                 value: 'ddm'
                           }
                      },

                      EXTENDS: A.FormBuilderTextField,

                      NAME: 'ddm-mobile-number',

                      prototype: {
                           getHTML: function() {
                                 return customFieldHTML;
                           },
                           getPropertyModel: function() {
                                 var instance = this;

                                 var model = originalGetPropertyModel.call(instance);

                                 return model.concat(
                                      [
                                            {
                                                 attributeName: 'customCssClass',
                                                 editor: new A.TextAreaCellEditor(),
                                                 name: 'Custom CSS Class'
                                            },
                                            {
                                                 attributeName: 'mobileNumberValidationMassage',
                                                 editor: new A.TextAreaCellEditor({
                                                      validator: {
                                                            rules: {
                                                                 value: {
                                                                       required: true,
                                                                 }
                                                            }
                                                       }
                                                 }),
                                                 name: 'Mobile Number Validation Massage'
                                            },
                                            {
                                                 attributeName: 'mobileNumberRegex',
                                                 editor: new A.TextCellEditor({
                                                      validator: {
                                                            rules: {
                                                                 value: {
                                                                       required: true,
                                                                 }
                                                            }
                                                       }
                                                 }),
                                                 name: 'Mobile Number Regex'
                                            },
                                            {
                                                 attributeName: 'fieldWidth',
                                                 editor: new A.DropDownCellEditor(
                                                           {
                                                               options : {
                                                               large : 'large',
                                                               medium : 'medium',
                                                               small : 'small'
                                                               }
                                                              }),
                                                 name: 'Field Width'
                                            }
                                      ]
                                 );
                           }
                      }
                }
           );

Each field setting attribute required editor to take input from end user, Field Width is using Drop Down cell editor will have few options to select. It will be as follows in the Structure Creation UI.


Implementing required OSGi component java classes

Following are basic OSGi components which required for DDM custom field. We really don’t need to implement everything and we can get original code from portal source and then modify some piece of code.

DDMFormFieldType

Need to override getName method and it should return custom field name.



DDMFormFieldValueRenderer



DDMFormFieldFreeMarkerRendererHelper


DDM


In “getDDMFormFieldsJSONArray” method need to add custom filed attributes.


CustomFieldDDMFormFieldTypeSettings




DDMFormFieldRenderer


Add custom field attributes to fieldContext in addStructureProperties method.



Implement required DDM FTL templates

Custom field is required few ftl templates to render fields in the web content.These templates related code in “DDMFormFieldRenderer” component class. default.ftl” is template will render read only view while render in the web content.Custom field template ftl file have all render logic which configured while creating field. 

Field type name defined in “DDMFormFieldType” component should be with ddm-*. Example of filed type name is “ddm-mobile-number”

Template name should be without ddm-, it means template name should be “mobile-number.ftl”.

Example of FTL template name and field type name



Example code of custom field template


<#--Field Settings defined for Mobilde Number field and fetch the values -->
<#assign cssClass = "" />

<#if fieldStructure.fieldWidth??>
     <#if stringUtil.equals(fieldStructure.fieldWidth, "large")>
           <#assign cssClass = "input-large" />
     <#elseif stringUtil.equals(fieldStructure.fieldWidth, "medium")>
           <#assign cssClass = "input-medium" />
     <#elseif stringUtil.equals(fieldStructure.fieldWidth, "small")>
           <#assign cssClass = "input-small" />
     </#if>
</#if>
<#assign cssClass += " " />
<#assign cssClass += "${fieldStructure.customCssClass}" />
<#assign data = data + {
     "ddmAuthToken": ddmAuthToken
}>

<#--FMobilde Number field with validation -->
<@liferay_aui["field-wrapper"]
     cssClass="form-builder-field"
     data=data
> 
     <div class="form-group">
           <@liferay_aui.input
                cssClass=cssClass
                dir=requestedLanguageDir
                helpMessage=escape(fieldStructure.tip)
                label=escape(label)
                name=namespacedFieldName
                required=required
                type="text"
                value=fieldValue
           >
        <@liferay_aui.validator errorMessage="${fieldStructure.mobileNumberValidationMassage}" name="custom">
                function(val, fieldNode, ruleValue) {
                        var regex = new RegExp(${fieldStructure.mobileNumberRegex});
                        return regex.test(val);
                }
       </@liferay_aui.validator>
           </@liferay_aui.input>
     </div>

     ${fieldStructure.children}
</@>


Portal Source code references 7.2 GA2



GitHub Source

Gradle Module


 Maven Module



Author

Liferay 7/DXP Overriding Liferay Portal Default AUI/YUI Modules


Liferay 7/DXP is providing many extension points to override portal sources and modules code. Sometimes we may need to override the default AUI and YUI modules JavaScript. Liferay 7/DXP is already providing way to override JavaScript.  OSGi internally used the extender pattern to override default modules with our changes.

The following are steps to override the default AUI and YUI modules

  1. Create Liferay OSGi module
  2. Copy original AUI/YUI JavaScript file from source to created module
  3. Create Config.js file to provide modules information
  4. Add Liferay-JS-Config header in module bnd file

Create Liferay OSGi module

Create brand new OSGi module with “mvc-portlet” template. Here are more details to create Liferay OSGi Modules with BLADE. You can also use Liferay IDE, Liferay Developer Studio and IntelliJ Liferay. Assume we are creating module name with “liferay72-ddm-custom-field”.


Copy original AUI/YUI JavaScript file from source to created module

Identify the AUI/YUI JavaScript files which contains the modification and copy original java script files from source to created module. Maintain the same path which original files already having in the source code.

All JavaScript files should in module “src/main/resources/META-INF/resources/js” directory and if directory not available in the module, create it in the module.

Take the example that we are trying to create new ddm custom field and its required following two AUI Modules need to override and these are available in “dynamic-data-mapping-web/src/main/resources/META-INF/resources/js

liferay-portlet-dynamic-data-mapping-custom-fields
liferay-portlet-dynamic-data-mapping

These modules are available in portal source code of “dynamic-data-mapping-web”.


copy main.js and custom_field.js files form original module to created module liferay72-ddm-custom-field under “/src/main/resources/META-INF/resources/js




Change names to “custom_fields_override.js” and “main_override.js” and edit the files AUI definition names. It should require to change AUI definition name to distinguish from original AUI/YUI definition names.

Example of “main_override.js” after changing the definition name.


AUI.add(
     'liferay-portlet-dynamic-data-mapping-override',
     A => {


Example of “custom_fields_override.js” after changing the definition name

AUI.add(
     'liferay-portlet-dynamic-data-mapping-custom-fields-override',
     A => {

Now add your JavaScript modification to these files.

Create “config.js” file to provide modules information

Create “config.js” file under module “src/main/resources/META-INF/resources/js”. Config.js files have information like modified AUI/YUI JavaScript file location and its original AUI module definition names. This will help OSGi run time to load modified version AUI JavaScript.


The following example of configuration to consider above changes


;(function() {
     var base = MODULE_PATH + '/js/';

     AUI().applyConfig(
           {
                groups: {
                      liferaysavvymodulesoverride: {
                           base: base,
                           combine: Liferay.AUI.getCombine(),
                           filter: Liferay.AUI.getFilterConfig(),
                           modules: {
                                 'liferay-portlet-dynamic-data-mapping-override': {
                                      path: 'main_override.js',
                                      condition: {
                                            name: 'liferay-portlet-dynamic-data-mapping-override',
                                            trigger: 'liferay-portlet-dynamic-data-mapping',
                                            when: 'instead'
                                      }
                                 },
                                 'liferay-portlet-dynamic-data-mapping-custom-fields-override': {
                                      path: 'custom_fields_override.js',
                                      condition: {
                                            name: 'liferay-portlet-dynamic-data-mapping-custom-fields-override',
                                            trigger: 'liferay-portlet-dynamic-data-mapping-custom-fields'
                                      }
                                 }
                           },
                           root: base
                      }
                }
           }
     );
})();



Trigger is original module AUI definition name and name attribute modified version AUI definition name.
There are many examples can find in Liferay source code foe config.js

Add Liferay-JS-Config header in module bnd file

Liferay-JS-Config” is OSGi header and its based-on extender pattern. At run time with header OSGi container will override the original AUI JavaScript with modified JavaScript.
Example module of bnd.bnd file


Liferay-JS-Config: /META-INF/resources/js/config.js



GitHub Source

Gradle Module


Maven Module



This is the mechanism to apply for any AUI/YUI that required modification in our regular development. This example may not relevant to your needs but it will explain the process of overriding default AUI/YUI.

Recent Posts

Recent Posts Widget

Popular Posts