Saturday, March 14, 2020

Liferay Clay Form Elements Tag Libraries


Liferay is introduced Lexicon UI framework to standardize all UI components across the Liferay portal. It is a design language to develop UI components. Liferay 7.2/DXP is following the Lexicon standards to design UI elements. All standards are aligned with latest bootstrap UI framework and its standards.


Clay is one of the web implementation for Lexicon and it contains many UI components that can be used in Liferay application development.


Liferay is implemented clay tag library to create UI components which is based on Clay UI web implementation. Tag libraries are very useful to create reusable UI components by simply placing tags.

These Clay tag libraries can be used in JSP and Freemarker templates.

Clay ag libraries are implemented in “frontend-taglib-clay” OSGi module and it is one of the Liferay 7.2/DXP portal module available by default.

Clay Talib Implementation Module source is available here.


Usage in JSP Pages.

Use following Taglib directive in JSP page. Usually init.jsp is best place to keep the taglibs.


<%@ taglib uri="http://liferay.com/tld/clay" prefix="clay" %>


Liferay is also provided these tags as macro to use in FreeMarker theme templates and Webcontent templates.

Following is usage of Clay Macros in FreeMarker theme templated and Webcontent templates.


<@clay["tag-name"] attribute="string value" attribute=10 />


This article will focus on some of clay form elements that is implemented by Clay Taglib.

The following are Form UI elements available in Clay Taglib.

  1. Clay Checkbox
  2. Clay Radio
  3. Clay Selector

These are the available Taglib and each tag have many attributes to use in JSP pages or freemarker templates.

Clay Checkbox

Sample usage in JSP page


<clay:checkbox label="Music" name="<%=curPortletNameSpace+"hobbies"%>" showLabel="<%= true %>" value="Music"/>



Important Attributes


checked: It take Boolean value. If it is true then by default it will be selected.

disabled: It take Boolean value. If it is true then by Check will be disabled mode.

hideLabel: Make the label is hidden for true attribute value. It takes Boolean value.

indeterminate: Checkbox variable for multiple selection

label: Label for checkbox.

name: Name for checkbox. We will use this name to get value from request object.


Clay Radio

Sample usage in JSP page


<clay:radio checked="<%= true %>" label="Male" name="<%=curPortletNameSpace+"gender"%>" showLabel="<%= true %>" value="male"/>


Important Attributes


checked: It take Boolean value. If it is true then by default it will be selected.

hideLabel: Make the label is hidden for true attribute value. It takes Boolean value.

disabled: It take Boolean value. If it is true then by Check will be disabled mode.

label: Label for checkbox.

name: Name for checkbox. We will use this name to get value from request object.


Clay Selector

Sample usage in JSP page


<clay:select label="Country" name="<%=curPortletNameSpace+"country"%>" options="<%= selectOptions %>"/>


It is required option as List object and we need to provide java list object

Example:


<%@page import="com.liferay.frontend.taglib.clay.servlet.taglib.util.SelectOption" %>
<%
       List<SelectOption> selectOptions = new ArrayList<>();
       selectOptions.add(new SelectOption("India", String.valueOf("India")));
       selectOptions.add(new SelectOption("US", String.valueOf("US")));
       selectOptions.add(new SelectOption("UK", String.valueOf("UK")));
%>


Important Attributes


disabled: It take Boolean value. If it is true then by Check will be disabled mode.

label: The selector’s label

multiple: Whether multiple options can be selected and accepted values true or false.

name: Label for checkbox



Check here for complete tags and its attributes


Important Note:

Taglib element name should be associated with portlet name space otherwise it will be ignored in request parameter list.

Example to get Portlet Namespace in JSP page


<%
String curPortletNameSpace = themeDisplay.getPortletDisplay().getNamespace();
%>


Clay tag with portlet name space


<clay:radio checked="<%= true %>" label="Male" name="<%=curPortletNameSpace+"gender"%>" showLabel="<%= true %>" value="male"/>


Example to get value in Portlet component class


String gender = ParamUtil.getString(actionRequest, "gender");


Liferay recommended to use Clay Taglib instead of AUI Taglib. The following is note from Liferay docs.


Note:
AUI taglibs are deprecated as of Liferay Portal CE 7.1. We recommend that you use Clay taglibs to avoid future compatibility issues.


Clay Taglib Example

I have provided the simple Portlet component which uses the Clay form elements. It is rendering by Clay tags. Some of components from AUI Taglib but you can ignore AUI tags and focus on Clay tags.

Important Points of the Example

  • Init JSP page have all Taglib definitions including Clay Taglib. It also has some common objects which is used across all JSP pages. All JSP pages will include init JSP page.
  • View JSP Page just have anchor tag to navigate to edit employee JSP page
  • Edit employee JSP page contains the Clay Tag UI Elements.
  • Once submit the form it will execute the Edit Employee Action Command. It will get values from request object and put the data in request object to render in JSP page.
  • MVC Render commands will provide navigation between JSP pages.
  • Once action is executed,  it will navigate to view employee JSP page.
  • From View Employee JSP page, we can navigate to Home page, that is view JSP page.
  • We have to add one of the Taglib dependency in pom.xml or “build.gradle”. That is “com.liferay.frontend.taglib.clay

Note:

The objective of example is just showing usage of Clay Taglib and its not covering any other functionality.

Source Code Gradle Build


Source Code MAVEN Build



init.jsp


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet" %>
<%@taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
<%@ taglib uri="http://liferay.com/tld/clay" prefix="clay" %>
<liferay-theme:defineObjects />
<portlet:defineObjects />
<%@ page import="java.util.ArrayList" %>
<%@page import="java.util.HashMap" %>
<%@page import="java.util.List" %>
<%@page import="java.util.Map" %>
<%@page import="com.liferay.frontend.taglib.clay.servlet.taglib.util.SelectOption" %>
<%
String curPortletNameSpace = themeDisplay.getPortletDisplay().getNamespace();
%>


view.jsp


<%@ include file="/init.jsp" %>
<h2>Welcome to Clay Taglib Examples</h2><br/>
<portlet:renderURL var="renderEditEmployee">
       <portlet:param name="mvcRenderCommandName" value="/clayform/edit_employee" />
</portlet:renderURL>
<aui:button href="<%= renderEditEmployee %>" value="Edit Employee" />


edit_employee.jsp



<%@ include file="/init.jsp" %>
<portlet:actionURL name="/clayfrom/edit_employee" var="editEmployeeURL" />
<%
       List<SelectOption> selectOptions = new ArrayList<>();
       selectOptions.add(new SelectOption("India", String.valueOf("India")));
       selectOptions.add(new SelectOption("US", String.valueOf("US")));
       selectOptions.add(new SelectOption("UK", String.valueOf("UK")));
%>

<aui:form action="<%= editEmployeeURL %>" cssClass="edit-entry" enctype="multipart/form-data" method="post" name="fm">
<aui:fieldset-group markupView="lexicon">
       <aui:fieldset>
             <div class="clearfix">
                    <label class="control-label">Gender</label>
                    <clay:radio checked="<%= true %>" label="Male" name="<%=curPortletNameSpace+"gender"%>" showLabel="<%= true %>" value="male"/>
                    <clay:radio checked="<%= true %>" label="Female" name="<%=curPortletNameSpace+"gender"%>" showLabel="<%= true %>" value="female"/>
             </div>
       </aui:fieldset>
       <aui:fieldset>
             <div class="clearfix">
                    <label class="control-label">Hobbies</label>
                    <clay:checkbox checked="<%= true %>" label="Music" name="<%=curPortletNameSpace+"hobbies"%>" showLabel="<%= true %>" value="Music"/>
                    <clay:checkbox label="Travel" name="<%=curPortletNameSpace+"hobbies"%>" showLabel="<%= true %>" value="Travel"/>
                    <clay:checkbox label="Movies" name="<%=curPortletNameSpace+"hobbies"%>" showLabel="<%= true %>" value="Movies"/>
             </div>
       </aui:fieldset>
       <aui:fieldset>
             <div class="clearfix">
                    <clay:select label="Country" name="<%=curPortletNameSpace+"country"%>" options="<%= selectOptions %>"/>
             </div>
       </aui:fieldset>
       <aui:fieldset>
             <div class="clearfix">
                    <aui:button name="saveButton" type="submit" value="Save" />
             </div>
       </aui:fieldset>
      
</aui:fieldset-group>

</aui:form>


view_employee.jsp

<%@ include file="/init.jsp" %>
<%@page import="java.util.List"%>
<%@page import="java.util.Map"%>
<h2>Display Employee Details</h2><br/>
<%
Map<String,String> employeeMap=(Map<String,String>)request.getAttribute("employeeObject");
%>
<b>Gender: </b><%=employeeMap.get("gender")%>    <br/>
<b>Country: </b><%=employeeMap.get("country")%>    <br/>
<b>Hobbies: </b><br/>
<%
List<String> hobbiesList=(List<String>)request.getAttribute("hobbiesList");
if(hobbiesList!=null){
for(String hobby:hobbiesList){
%>
<%=hobby%><br/>
<%}}%>

<portlet:renderURL var="homeURL">
</portlet:renderURL>
<br/>
<br/>
<aui:button href="<%= homeURL %>" value="Home" />


EditEmployeeMVCActionCommand.java


package com.liferaysavvy.gradle.clayform.portlet.action;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.portlet.bridges.mvc.BaseMVCActionCommand;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCActionCommand;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import com.liferay.portal.kernel.util.Constants;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.PortalUtil;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferaysavvy.gradle.clayform.constants.ClayFormPortletKeys;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;

import org.osgi.service.component.annotations.Component;

@Component(
       immediate = true,
       property = {
                    "javax.portlet.name=" + ClayFormPortletKeys.CLAYFORM,
                    "mvc.command.name=/clayfrom/edit_employee"
       },
       service = MVCActionCommand.class
)
public class EditEmployeeMVCActionCommand extends BaseMVCActionCommand {

      
       @Override
       protected void doProcessAction(
                    ActionRequest actionRequest, ActionResponse actionResponse)
             throws Exception {

             String cmd = ParamUtil.getString(actionRequest, Constants.CMD);
             updateEntry(actionRequest,actionResponse);
       }
       protected void updateEntry(ActionRequest actionRequest, ActionResponse actionResponse)
             throws Exception {

             ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
                    WebKeys.THEME_DISPLAY);
             // Get form values from request object
             String gender = ParamUtil.getString(actionRequest, "gender");
             String country = ParamUtil.getString(actionRequest, "country");
             String[] hobbies = ParamUtil.getParameterValues(actionRequest, "hobbies", null);
            
             // Display logs in console for debug
             _log.info("gender"+gender);
             _log.info("gender"+hobbies.toString());
             _log.info("gender"+country);
            
             //Preparing object to send to JSP page.
             Map<String, String> employeeObject = new HashMap();
             employeeObject.put("gender", gender);
             employeeObject.put("country", country);
             //set View details object in request object.
        actionRequest.setAttribute("employeeObject", employeeObject);
        List<String> hobbiesList = new ArrayList();
        hobbiesList = ListUtil.toList(hobbies);
      //set View details object in request object.
        actionRequest.setAttribute("hobbiesList", hobbiesList);
        //Navigation to view employee details JSP page.
             actionResponse.setRenderParameter("mvcRenderCommandName", "/clayform/view_employee");
       }

      
      

       private static final Log _log = LogFactoryUtil.getLog(
             EditEmployeeMVCActionCommand.class);

}


Author

Thursday, March 12, 2020

Unrelated packages Unresolved requirement Import-Package Error in Liferay OSGi Module Framework

When we started using third-party non-OSGi jars, we can see Unresolved requirement: Import-Package” error for the packages but those packages we may not use directly in the current developing module (unrelated packages). This is due to third-party jar transitive dependencies.

If we use compileInclude scope for third-party dependencies for gradle build type module then it will add all its transitive dependencies packages as part of “Import-Package” list during build process.

Example Error


org.osgi.framework.BundleException: Could not resolve module: com.liferaysavvy.employee.portlet [1080]
  Unresolved requirement: Import-Package: com.graphbuilder.curve


Solution:

We can ignore packages using Import-Package header in module bnd.bnd file or we can add specified missing package jar as third party non OSGi jar.

Ignore Packages as follows.

Example

Import-Package: \
!com.graphbuilder.*,\
!com.github.luben.zstd.*,\
!com.github.luben.zstd.*,\
!com.microsoft.schemas.*,\
!net.sf.saxon.*,\
!org.apache.batik.*,\
!org.apache.jcp.xml.dsig.internal.*,\
!org.bouncycastle.asn1.*,\
!org.bouncycastle.*,\
!org.bouncycastle.cms.*,\
!org.brotli.dec.*,\
!org.etsi.uri.*,\
!org.openxmlformats.schemas.*,\
!org.tukaani.xz.*,\
!com.zaxxer.sparsebits.*,\
!org.apache.commons.codec.*,\
!org.apache.commons.collections4.*,\
!org.apache.commons.compress.*,\
!org.apache.commons.math3.*,\
!org.apache.xmlbeans.*,\
!org.w3.x2000.*,\
\
*

If we override the “Import-Package” header in the bnd.bnd file, we should add \ * end of list otherwise it will ignore all Liferay default packages during the build process.  Don’t forget this and if you missed, it will lead to so many unresolved requirement errors.

Before ignoring packages, we must check whether package is really can be ignorable or not. If the package is really unrelated to module then we can ignore it but we need to verify thoroughly after deployment.  If the non-OSGi third party jar and its dependencies is required by module then add it to the module.


Unresolved Import-Package can be only identified after module deployment and it will display only one Unresolved Import-Package for each time and it wont show all at once. Assume if we have 10 Unresolved Import-Packages then we have to deploy module for 10 times to know these 10 Unresolved Import-Packages.  This is very pain point for developer for initial phase of module development. This scenario can be experienced when we are trying to use third party non-OSGi jars in the module.

We can see these Unresolved Import-Package error in console during the deployment of module or we can use Gogo shell to see the error and package.

You can use following Gogo shell command to know the reason


diag [BUNDLE_ID]





Lets take the example, if you are trying add apache poi libraries to the module then we may have to ignore many unrelated packages in the Import-Package of bnd.bnd file. To identify these unrelated packages, we may have to build and deploy modules for several times. These packages we may not use directly in the module.

Ignore Packages as follows.

Example


Import-Package: \
!com.graphbuilder.*,\
!com.github.luben.zstd.*,\
!com.github.luben.zstd.*,\
!com.microsoft.schemas.*,\
!net.sf.saxon.*,\
!org.apache.batik.*,\
!org.apache.jcp.xml.dsig.internal.*,\
!org.bouncycastle.asn1.*,\
!org.bouncycastle.*,\
!org.bouncycastle.cms.*,\
!org.brotli.dec.*,\
!org.etsi.uri.*,\
!org.openxmlformats.schemas.*,\
!org.tukaani.xz.*,\
!com.zaxxer.sparsebits.*,\
!org.apache.commons.codec.*,\
!org.apache.commons.collections4.*,\
!org.apache.commons.compress.*,\
!org.apache.commons.math3.*,\
!org.apache.xmlbeans.*,\
!org.w3.x2000.*,\
\
*


Tip to Identify Unrelated/Ignorable packages

  1. Add your third party-non OSGi jars to module.
  2. Build the module.
  3. Open MANIFEST.MF file and extract Import-Package header list.
  4. Identify the all unrelated packages and ignore it in the bnd.bnd file.

Add your third party-non OSGi jars to module

If you think that your module required third-party non-OSGi jars and add it the module.


Take the example of apache poi libraries.

Build the module

Build the module using your build tool MAVEN or GRADLE

Open MANIFEST.MF file and extract Import-Package header list

Once module build is success it will generate jar file and output files in build directory.

MAVEN we can see these generated files in Target directory in the parent directory of module.


Gradle we can see these generated files in build directory in the parent directory of module




Open MANIFEST.MF file after build. You can use JD-GUI java de-compiler to see files in jar file.



If you are using MAVEN you can see MANIFEST file in classes directory. Extract the Import-Package header package list in editor


Identify the all unrelated packages and ignore it in the bnd.bnd file.

Identify all the unrelated packages to current module from extracted text. Don’t add any Liferay or module specific packages in the ignore list.

Add packages to module bnd.bnd file as ignorable packages. We may see following ignorable packages when we are trying to add apache poi libraries.

Example:


Import-Package: \
!com.graphbuilder.*,\
!com.github.luben.zstd.*,\
!com.github.luben.zstd.*,\
!com.microsoft.schemas.*,\
!net.sf.saxon.*,\
!org.apache.batik.*,\
!org.apache.jcp.xml.dsig.internal.*,\
!org.bouncycastle.asn1.*,\
!org.bouncycastle.*,\
!org.bouncycastle.cms.*,\
!org.brotli.dec.*,\
!org.etsi.uri.*,\
!org.openxmlformats.schemas.*,\
!org.tukaani.xz.*,\
!com.zaxxer.sparsebits.*,\
!org.apache.commons.codec.*,\
!org.apache.commons.collections4.*,\
!org.apache.commons.compress.*,\
!org.apache.commons.math3.*,\
!org.apache.xmlbeans.*,\
!org.w3.x2000.*,\
\
*


This may help you to find all ignorable packages at one time and can avoid number of build deployments to find Unresolved Import-Packages. 

Recent Posts

Recent Posts Widget

Popular Posts