Add Custom Field Capability To Custom Portlet

Enable-Custom-Field-in-Custom-Portlet


Custom Field is one of the nice feature in Liferay Portal. Using Custom Field we can easily add extra fields to the Entities which are generated by Liferay Service Builder Tool. In this article we will enable custom field feature  for custom portlet. Once this feature is enabled we will be able to manage custom fields easily in Liferay Control Panel.Once a custom field is added in the control panel it will appear in the UI of the custom portlet which can be edited,deleted etc.


service.xml:

Expandos are associated with Entities which are generated by Liferay Service Builder Tool. Lets have a look on the service.xml file


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd">
<service-builder package-path="com.proliferay.sbuilder.example.custom.field">
<author>Hamidul Islam</author>
<namespace>CUSTOMFIELD</namespace>
<entity name="Book" table="CUSTOMFILELD_DEMO_PORTLET" local-service="true" remote-service="true">
<column name="bookId" type="long" primary="true" id-type="increment"/>
<column name="bookName" type="String" />
<column name="description" type="String" />
<column name="authorName" type="String" />
<column name="isbn" type="int" />
<column name="price" type="int" />

<reference package-path="com.liferay.portlet.expando" entity="ExpandoValue" />
<reference package-path="com.liferay.portlet.expando" entity="ExpandoRow" />
<reference package-path="com.liferay.portlet.expando" entity="ExpandoTable" />
<reference package-path="com.liferay.portal" entity="ClassName" />
</entity>


</service-builder>

So according to the service.xml our Entity is Book. Our aim is to enable Custom Field Capability of the Entity Book  so that it can be managed fully by Liferay.

Extend Class BaseCustomAttributesDisplay.java:

If you access Custom Fields Section of Control Panel then you will see there are many entries where we can add custom fields. Now we want our Book Entity to be presented in the Control Panel. This is done by creating a class which should extends BaseCustomAttributesDisplay.java. For example


package com.proliferay.admin;

import com.liferay.portal.theme.ThemeDisplay;
import com.liferay.portlet.expando.model.BaseCustomAttributesDisplay;
import com.proliferay.sbuilder.example.custom.field.model.Book;

public class CustomAttributeDisplay extends BaseCustomAttributesDisplay {

public static final String CLASS_NAME = Book.class.getName();

@Override
public String getClassName() {

return CLASS_NAME;
}

@Override
public String getIconPath(ThemeDisplay themeDisplay) {

return themeDisplay.getPathThemeImages() + "/common/pages.png";
}


}

Tell Liferay to display our Book Entity in Control Panel:

CustomAttributeDisplay

In the above we have extended BaseCustomAttributesDisplay.java. Now let our portlet use it. This is done by using <custom-attributes-display> tag in liferay-portlet.xml. For example


<?xml version="1.0"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.2.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_2_0.dtd">

<liferay-portlet-app>
<portlet>
<portlet-name>custom-field-demo</portlet-name>
<icon>/icon.png</icon>
<custom-attributes-display>com.proliferay.admin.CustomAttributeDisplay</custom-attributes-display>
<header-portlet-css>/css/main.css</header-portlet-css>
<footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
<css-class-wrapper>custom-field-demo-portlet</css-class-wrapper>
</portlet>
<role-mapper>
<role-name>administrator</role-name>
<role-link>Administrator</role-link>
</role-mapper>
<role-mapper>
<role-name>guest</role-name>
<role-link>Guest</role-link>
</role-mapper>
<role-mapper>
<role-name>power-user</role-name>
<role-link>Power User</role-link>
</role-mapper>
<role-mapper>
<role-name>user</role-name>
<role-link>User</role-link>
</role-mapper>
</liferay-portlet-app>

After this Step Book Entity will appear in the Control Panel as like below

model.resource.com.proliferay.sbuilder.example.custom.field.model.Book

Using Language.properties file we ca give a meaningful name. For example in Language.properties add below

model.resource.com.proliferay.sbuilder.example.custom.field.model.Book=Book

Now we are capable of adding custom fields for Book Entity through Liferay Control Panel.

Display Custom Fields in JSP while adding Book:

Once custom Fields are added through control panel it wont appear automatically in our portlet. We have to write some code for it. For this we can use liferay-ui:custom-attributes-available tag to display all the custom fields associated with the Entity.

<%@ include file="/html/init.jsp" %>
<%
Book book = new BookImpl();

ExpandoBridge&nbsp; expandoBridge = ExpandoBridgeFactoryUtil.getExpandoBridge(themeDisplay.getCompanyId(), Book.class.getName());

List<String> attributeList = Collections.list(expandoBridge.getAttributeNames());

%>

<portlet:actionURL name="addBook" var="addBookURL" />

<portlet:renderURL windowState="normal" var="backURL">
<portlet:param name="jspPage" value="/html/view.jsp"></portlet:param>
</portlet:renderURL>

<liferay-ui:header
backURL="<%= backURL %>"
title="Back"
/>


<aui:form name="fm" action="<%=addBookURL.toString()%>" method="post">

<aui:input name="bookName" label="Book Name"/>
<aui:input type="textarea" name="description" label="Description"/>
<aui:input name="authorName" label="Author Name"/>
<aui:input name="isbn" label="ISBN">
<aui:validator name="digits"/>
</aui:input>
<aui:input name="price" label="Price">
<aui:validator name="digits"/>
</aui:input>

<%if(attributeList.size() != 0){ %>

<liferay-ui:header title="Custom Fields"/>

<%} %>

<liferay-ui:custom-attributes-available&nbsp;&nbsp; className="<%=Book.class.getName() %>">
<liferay-ui:custom-attribute-list
className="<%= Book.class.getName() %>"
classPK="<%= (book != null) ? book.getBookId() : 0 %>"
editable="<%= true %>"
label="<%= true %>"
/>
</liferay-ui:custom-attributes-available>

<aui:button-row>
<aui:button name="addBook" type="submit" value="Add Book"&nbsp; />
</aui:button-row>

</aui:form>

 

Edit:
We can also edit the custom fields in our portlet along with Book entry

<%@ include file="/html/init.jsp" %>

<%
Book book = (Book) request.getAttribute(WebKeys.BOOK);
ExpandoBridge&nbsp; expandoBridge = ExpandoBridgeFactoryUtil.getExpandoBridge(themeDisplay.getCompanyId(), Book.class.getName(),book.getBookId());

List<String> attributeList = Collections.list(expandoBridge.getAttributeNames());
%>

<portlet:renderURL windowState="normal" var="backURL">
<portlet:param name="jspPage" value="/html/view.jsp"></portlet:param>
</portlet:renderURL>

<portlet:actionURL name="updateBook" var="updateBookURL" />

<liferay-ui:header
backURL="<%= backURL %>"
title="Back"
/>


<aui:form name="fm" method="post" action="<%= updateBookURL.toString()%>">
<aui:model-context bean="<%=book%>" model="<%=Book.class%>" />
<aui:input name="bookName" label="Book Name"/>
<aui:input type="textarea" name="description" label="Description"/>
<aui:input name="authorName" label="Author Name"/>
<aui:input name="isbn" label="ISBN">
<aui:validator name="digits"/>
</aui:input>
<aui:input name="price" label="Price">
<aui:validator name="digits"/>
</aui:input>

<%if(attributeList.size() != 0){ %>

<liferay-ui:header title="Custom Fields"/>

<%} %>
<liferay-ui:custom-attributes-available&nbsp; className="<%=Book.class.getName() %>">
<liferay-ui:custom-attribute-list
className="<%= Book.class.getName() %>"
classPK="<%= (book != null) ? book.getBookId() : 0 %>"
editable="<%= true %>"
label="<%= true %>"
/>
</liferay-ui:custom-attributes-available>
<aui:button-row>
<aui:button name="updateBook" type="submit" value="Update Book"&nbsp; />
</aui:button-row>

</aui:form>

View:
We can also view the value associated with a custom field.


<%@page import="com.proliferay.util.WebKeys"%>
<%@page import="com.proliferay.sbuilder.example.custom.field.model.Book"%>
<%@page import="com.proliferay.sbuilder.example.custom.field.service.BookLocalServiceUtil"%>
<%@page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%@ include file="/html/init.jsp" %>

<portlet:renderURL var="backURL">
<portlet:param name="jspPage" value="/html/view.jsp"/>
</portlet:renderURL>

<liferay-ui:header backURL="<%=backURL%>" title="Back" />

<%
Book book = (Book) request.getAttribute(WebKeys.BOOK_ENTRY);
ExpandoBridge&nbsp; expandoBridge = ExpandoBridgeFactoryUtil.getExpandoBridge(themeDisplay.getCompanyId(), Book.class.getName(),book.getBookId());

List<String> attributeList = Collections.list(expandoBridge.getAttributeNames());
%>


<aui:form>
<aui:model-context bean="<%=book%>" model="<%=Book.class%>" />
<aui:input name="bookName" label="Book Name" disabled="true"/>
<aui:input type="textarea" name="description" label="Description" disabled="true"/>
<aui:input name="authorName" label="Author Name" disabled="true"/>
<aui:input name="isbn" label="ISBN" disabled="true"/>
<aui:input name="price" label="Price" disabled="true"/>

<%if(attributeList.size() != 0){ %>

<liferay-ui:header title="Custom Fields"/>

<%} %>

<liferay-ui:custom-attributes-available&nbsp; className="<%=Book.class.getName() %>">
<liferay-ui:custom-attribute-list
className="<%= Book.class.getName() %>"
classPK="<%= (book != null) ? book.getBookId() : 0 %>"
editable="<%= false %>"
label="<%= true %>"
/>
</liferay-ui:custom-attributes-available>
</aui:form>

Portlet Class:
Below is the whole portlet class


package com.proliferay.portlet;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletSession;
import javax.portlet.ProcessAction;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.servlet.SessionMessages;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.service.ServiceContextFactory;
import com.liferay.util.bridges.mvc.MVCPortlet;
import com.proliferay.sbuilder.example.custom.field.model.Book;
import com.proliferay.sbuilder.example.custom.field.service.BookLocalServiceUtil;
import com.proliferay.util.ActionUtil;
import com.proliferay.util.WebKeys;

/**
*
* @author Hamidul Islam
*
*/

public class BookPortlet extends MVCPortlet{


/**
*
* This method will persist the data in database
* @throws PortalException
*/
@ProcessAction(name = "addBook")
public void addBook(ActionRequest actionRequest,ActionResponse actionResponse) throws SystemException, PortalException {


ServiceContext serviceContext = ServiceContextFactory.getInstance(Book.class.getName(), actionRequest);
Book book = ActionUtil.bookFromRequest(actionRequest);


//Calling service method to persist book.
BookLocalServiceUtil.addCustomBook(book, serviceContext);


SessionMessages.add(actionRequest, "added-book");
_log.info("#################Added Book Successfully#########################");

}

@ProcessAction(name = "deleteBook")
public void deleteBook(ActionRequest actionRequest,ActionResponse actionResponse) {

try {
long bookId = ParamUtil.getLong(actionRequest, "bookId");
BookLocalServiceUtil.deleteCustomBook(bookId);
SessionMessages.add(actionRequest, "deleted-book");
_log.info("#################Book Deleted Successfully#########################");
} catch (PortalException e) {
_log.error(e);
} catch (SystemException e) {
_log.error(e);
}


}

@ProcessAction(name = "viewBook")
public void viewBook(ActionRequest actionRequest,ActionResponse actionResponse){

try {
long bookId = ParamUtil.getLong(actionRequest, "bookId");

Book book = BookLocalServiceUtil.getBook(bookId);
actionRequest.setAttribute(WebKeys.BOOK_ENTRY, book);
} catch (PortalException e) {
_log.error(e);
} catch (SystemException e) {
_log.error(e);
}

//Show view_book.jsp
actionResponse.setRenderParameter("jspPage", "/html/view_book.jsp");
}

@ProcessAction(name = "updateBook")
public void updateBook(ActionRequest actionRequest,ActionResponse actionResponse) throws SystemException, PortalException {

ServiceContext serviceContext = ServiceContextFactory.getInstance(Book.class.getName(), actionRequest);
long bookId = (Long) actionRequest.getPortletSession().getAttribute(WebKeys.BOOK_ID,PortletSession.PORTLET_SCOPE);
Book book = ActionUtil.bookFromRequest(actionRequest);
book.setBookId(bookId);
book.setExpandoBridgeAttributes(serviceContext);
/**
* Calling service method to update book.
* While calling update method we must have primary key in the passing object.
*/
BookLocalServiceUtil.updateBook(book);

SessionMessages.add(actionRequest, "updated-book");
_log.info("#################Updated Book Successfully#########################");

}

@ProcessAction(name = "viewEdit")
public void viewEdit(ActionRequest actionRequest,ActionResponse actionResponse) throws SystemException, PortalException {
long bookId = ParamUtil.getLong(actionRequest, "bookId",0);
Book book = BookLocalServiceUtil.getBook(bookId);
actionRequest.setAttribute(WebKeys.BOOK, book);
//Show edit_book.jsp
actionResponse.setRenderParameter("jspPage", "/html/edit_book.jsp");
actionRequest.getPortletSession().setAttribute(WebKeys.BOOK_ID, book.getBookId(),PortletSession.PORTLET_SCOPE);

}
private Log _log = LogFactoryUtil.getLog(BookPortlet.class.getName());

}

Service Impl:
Below is the Service Impl Class

package com.proliferay.sbuilder.example.custom.field.service.impl;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.model.ClassName;
import com.liferay.portal.security.auth.CompanyThreadLocal;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.expando.model.ExpandoRow;
import com.liferay.portlet.expando.model.ExpandoTable;
import com.proliferay.sbuilder.example.custom.field.model.Book;
import com.proliferay.sbuilder.example.custom.field.service.base.BookLocalServiceBaseImpl;

/**
* The implementation of the book local service.
*
*

* All custom service methods should be put in this class. Whenever methods are added, rerun ServiceBuilder to copy their definitions into the {@link com.proliferay.sbuilder.example.custom.field.service.BookLocalService} interface.
*
*

* This is a local service. Methods of this service will not have security checks based on the propagated JAAS credentials because this service can only be accessed from within the same VM.
* 

*
* @author Hamidul Islam
* @see com.proliferay.sbuilder.example.custom.field.service.base.BookLocalServiceBaseImpl
* @see com.proliferay.sbuilder.example.custom.field.service.BookLocalServiceUtil
*/
public class BookLocalServiceImpl extends BookLocalServiceBaseImpl {
/*
* NOTE FOR DEVELOPERS:
*
* Never reference this interface directly. Always use {@link com.proliferay.sbuilder.example.custom.field.service.BookLocalServiceUtil} to access the book local service.
*/

public Book addCustomBook(Book book, ServiceContext serviceContext) throws PortalException, SystemException{

long bookId = counterLocalService.increment();
book.setBookId(bookId);
book = bookLocalService.addBook(book);
if (serviceContext != null) {
book.setExpandoBridgeAttributes(serviceContext);
book = bookPersistence.update(book);
}
return book;
}

public void deleteCustomBook(long bookId) throws PortalException, SystemException{

ClassName className = classNameLocalService.getClassName(Book.class.getName());
ExpandoTable expandoTable = expandoTableLocalService.getDefaultTable(CompanyThreadLocal.getCompanyId(), className.getClassNameId());

/**
* Delete ExpandoRow before deleteting Book Entitity
* If we delete row, corresponding values will be also deleted
* In the below code expandoRowPersistence.fetchByT_C will return null if there is no row
*/

ExpandoRow expandoRow = expandoRowPersistence.fetchByT_C(expandoTable.getTableId(), bookId);

if(expandoRow !=null){
expandoRowLocalService.deleteRow(expandoRow);
}

bookLocalService.deleteBook(bookId);
}

public Book updateCustomBook(Book book, ServiceContext serviceContext) throws PortalException, SystemException{

book = bookLocalService.updateBook(book);
if (serviceContext != null) {
book.setExpandoBridgeAttributes(serviceContext);
book = bookPersistence.update(book);
}
return book;
}
}

Download Source Code:

custom-field-demo-portlet

About The Author

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top