Liferay Dynamic Data List is a powerful tool to create simple CRUD applications without writing any code. But it is not powerful enough to create a real dynamic application, because simple CRUD actions do not contain business logic. For example, if we want to execute some custom code before/after adding or updating a record and perhaps want to make the custom code configurable for the end user, Liferay does not support these use cases out of the box.

The whole idea is:

  • Let the user provide some JavaScript functions to implement the custom logic.
  • Save these functions somewhere as a definition of the dynamic data list, for example, in “description” field.
  • Override existing Liferay service with a hook.
  • Inside hook, use Java Scripting API to call the configured JavaScript functions, like: // create a script engine manager
    ScriptEngineManager factory = new ScriptEngineManager();
    // create a JavaScript engine
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    // evaluate JavaScript code from String
    engine.eval("print('Hello, World')");

Here is an example:

Scenario: we want to create a dynamic form, it has “User ID” and “User Name” fields. The form has a custom logic, when the user is adding a new record, we are suppose to replace an empty user name with “Steve Jobs”. If the user is updating a record, we also have to replace an empty user name with “Tim Cook”. The most important part is, this custom logic should be configurable for the end user.

Solution:

  1. Configure a dynamic data list, it has two fields, field labels are “User ID” and “User Name”. Corresponding field names are “id” and “name”; Here is the “Data Definitions” screen:1.PNG
    2.PNG
  2. Let’s use the description field to save our configurable custom logic. Input this code in the “description” field and save it. function beforeAdd(fields) {
    if(fields.get("name").getValue().isEmpty()) {
    fields.get("name").setValue("Steve Jobs");
    }
    }
    function beforeUpdate(fields) {
    if(fields.get("name").getValue().isEmpty()) {
    fields.get("name").setValue("Tim Cook");
    }
    }
    3.PNG
    Yes, this is a piece of JavaScript code. We are going to call those two JavaScript functions from Java.
  3. Create a Liferay hook, overwrite a service com.liferay.portlet.dynamicdatalists.service.DDLRecordService with a class named DDLRecordServiceImplHook. The new class (DDLRecordServiceImplHook ) extends com.liferay.portlet.dynamicdatalists.service.DDLRecordServiceWrapper.
  4. Create an interface named IScriptAction in the hook project, it defines two action methods:     public void beforeAdd(com.liferay.portlet.dynamicdatamapping.storage.Fields fields);
    public void beforeUpdate(com.liferay.portlet.dynamicdatamapping.storage.Fields fields);

    The JavaScript provided by the user has to “implement” this interface. That is why we have two JavaScript functions (beforeAdd and beforeUpdate) in the configured JavaScript Code.
  5. In DDLRecordServiceImplHook class, overwrite these two methods:
    //addRecord method
    public com.liferay.portlet.dynamicdatalists.model.DDLRecord addRecord(long groupId, long recordSetId, int displayIndex,
    com.liferay.portlet.dynamicdatamapping.storage.Fields fields, com.liferay.portal.service.ServiceContext serviceContext)
    throws com.liferay.portal.kernel.exception.PortalException, com.liferay.portal.kernel.exception.SystemException;
    //updateRecord method

    public com.liferay.portlet.dynamicdatalists.model.DDLRecord updateRecord(long recordId, boolean majorVersion, int displayIndex,
    com.liferay.portlet.dynamicdatamapping.storage.Fields fields, boolean mergeFields, com.liferay.portal.service.ServiceContext serviceContext)
    throws com.liferay.portal.kernel.exception.PortalException, com.liferay.portal.kernel.exception.SystemException;
  6. In the addRecord method, add these lines of code before calling super class’s addRecord method:
    //Get record set
    DDLRecordSet recordSet = DDLRecordSetServiceUtil.getRecordSet(ParamUtil.getLong(serviceContext, "recordSetId"));
    //Get configured script from “Description”
    String script = recordSet.getDDMStructure().getDescriptionCurrentValue();
    if (!script.isEmpty()) {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
    engine.eval(script);
    if (engine instanceof javax.script.Invocable) {
    IScriptAction scriptAction = ((Invocable) engine).getInterface(IScriptAction.class);
    scriptAction.beforeAdd(fields);
    }
    }
  7. Make corresponding changes to the updateRecord method, call scriptAction.beforeUpdate method before calling super class’s updateRecord method.
  8. Deploy the hood and test it.

Advantage:

  1. Custom logic can be configured by the end user dynamically.
  2. Java Scripting API supports many script languages like Ruby, Groovy, and JavaScript and etc. User has enough option to implement their dynamic logic.
  3. No need to add any extra library.
  4. Script can interact with java objects, java can call script, and the script can call java. Script can modify java object parameters.

Disadvantage:

  1. End user has to be able to implement the custom logic with script languages.
  2. The configured JavaScript code may have security issue.
More about Liferay Dynamic Data: http://www.liferay.com/documentation/liferay-portal/6.1/user-guide/-/ai/dynamic-data-lists-in-liferay
More about Java Scripting API: http://docs.oracle.com/javase/6/docs/technotes/guides/scripting/programmer_guide/