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:
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 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 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" /> </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 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 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" /> </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 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 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: