Managing Pages in Liferay Portal 5.2 Systems Development

by Jonas X. Yuan | May 2009 | Java

In this article by Jonas Yuan, we will first discuss how to extend the Communities portlet with additional fields. Later, we will also learn how to customize the Manage Pages portlet with additional fields and how to apply layout templates in runtime.

Each site is represented as a community and each community is made up of a lot of pages, for example, public pages and private pages. In order to build web sites, we need to manage communities and, further, manage pages for each community. The Communities portlet provides the ability to create and manage communities and their users, as well as of the Manage Pages portlet.

Extending Communities portlet

The Communities portlet provides the ability to create and manage communities and their users. A community is a special group holding a number of users who share common interests. By default, a community is represented by the Group_ table with fields such as groupId, companyId, creatorUserId, name, description, type, typeSettings, friendlyURL, active, and so on. Now let's take an in-depth look at the customization of the community.

As shown in the following screenshot, we may want to add one searchable field for each community, which is Keywords. For example, suppose we are creating a new community with the name Book Street, and the description a community for website www.bookpubstreet.com. Now we have a chance to add the new Keywords field with the value, for example, Book; Street; Palm Tree; Publication. Similarly, when editing the properties of a community—for example Name, Description, Type, and Active—we again have a chance to edit Keywords.

Liferay Portal 5.2 Systems Development

In addition, we expect to have more fields in the customized communities: Created (when the community was created), ModifierUserId (who modified the community), and Modified (when the community was modified).

Liferay Portal 5.2 Systems Development

As shown in the preceding screenshot, when listing communities, not only should the default fields (for example, Name, Type, Members, Online Now, Active, Pending Requests) and Actions icons (for example, Edit, Permissions, Manage Pages, Assign User Roles, Assign Members, Leave, and Delete) be displayed, but also the customized columns (for example, the username and Keywords) should be displayed.

How do we implement these features? In this article, we're going to show how to customize the Communities portlet using the above requirements as examples. Obviously, it is open for you to customize this portlet in a number of ways according to your own requirements. In general, the processes for customization of this portlet should be the same.

Building Ext Communities portlet

The Communities portlet can be used to create and manage new portal communities and their users. As you can see, a community can be regarded as a separate portal instance; each community gets its own set of pages, content management system, shared calendar, and default permissions. Moreover, a user belonging to multiple communities can navigate among them within the same portal session.

Generally speaking, we do not want to update the Communities portlet, but keep it as it is. Our goal is to customize and extend it. In general, this can be done by using the following two steps:

  1. Build a customized Ext Communities portlet, which has exactly the same functions, look, and feel as that of the original Communities portlet.
  2. Extend this customized portlet and let it have an additional model and service, and moreover, its own look and feel.

In this part, let's build the Ext Communities portlet, having exactly same functions, look, and feel as that of the Communities portlet.

Constructing the portlet

Now let's define a Struts portlet with the name "Ext Communities". We first need to configure it in both portlet-ext.xml and liferay-portlet-ext.xml, and then set the title in Language-ext.properties, and then add the Ext Communities portlet to the Book category in liferay-display.xml.

  1. Locate the portlet-ext.xml file in the /ext/ext-web/docroot/WEB-INF folder and open it.
  2. Add the following lines between </portlet> and </portlet-app> and save the file:
  3. <portlet>
    <portlet-name>extCommunities</portlet-name>
    <display-name>Ext Communities</display-name>
    <portlet-class>com.liferay.portlet.StrutsPortlet</portlet-class>
    <init-param><name>view-action</name>
    <value>/ext/communities/view</value></init-param>
    <expiration-cache>0</expiration-cache>
    <supports><mime-type>text/html</mime-type></supports>
    <resource-bundle>
    com.liferay.portlet.StrutsResourceBundle
    </resource-bundle>
    <security-role-ref>
    <role-name>power-user</role-name>
    </security-role-ref>
    <security-role-ref>
    <role-name>user</role-name>
    </security-role-ref>
    </portlet>

As shown in the code above, the portlet-name element contains the canonical name of the portlet (for example, extCommunities). The display-name element contains a short name that is intended to be displayed in the portal (for example, Ext Communities). The portlet-class element contains the fully qualified class name of the portlet (for example, com.liferay.portlet.StrutsPortlet). The init-param element contains a name-value pair, for example view-action-ext/communities/view, as an initialization parameter of the portlet. Further, the expiration-cache defines expiration-based caching for this portlet. The supports element contains the supported MIME-type. The resource-bundle element contains a resource bundle class, for example com.liferay.portlet. StrutsResourceBundle. Finally, the security-role-ref element contains the declaration of a security role reference in the code of the web application.

Secondly, let's register the extCommunities portlet in liferay-portlet-ext.xml as follows:

  1. Locate the liferay-portlet-ext.xml file in the /ext/ext-web/docroot/WEB-INF folder and open it.
  2. Add the following lines immediately after <!-- Custom Portlets --> and save it:
  3. <portlet>
    <portlet-name>extCommunities</portlet-name>
    <struts-path>ext/communities</struts-path>
    <use-default-template>false</use-default-template>
    <restore-current-view>false</restore-current-view>
    </portlet>

As shown in the code above, the Ext Communities portlet is registered in the portal. The portal will check struts-path to see whether a user has the required permissions to access the portlet or not. As you can see, struts-path has the value ext/communities. It means that all requests to the ext/communities/* path are considered a part of this portlet scope. Only those users whose request paths match ext/communities/* will be granted access.

Moreover, the use-default-template element has the value false, so the portlet will not use any user's default template. The restore-current-view element has the value false so the portlet will reset the current view when toggling between maximized and normal states.

Thirdly, add a title (for example, Ext Communities), for the Ext Communities portlet at Language-ext.properties as follows:

  1. Locate the Language-ext.properties file in the /ext/ext-impl/src/content folder and open it.
  2. Add the following line after javax.portlet.title.book_reports=Reports for Books and save it:
  3. javax.portlet.title.extCommunities=Ext Communities

The code above provides mapping for the title of the portlet. If the mapping is not provided, the portal will show the default title javax.portlet.title.extCommunities.

Finally, add the Ext Communities portlet to the Book category in liferay-display.xml as follows:

  1. Locate the liferay-display.xml file in the /ext/ext-web/docroot/WEBINF folder and open it.
  2. Add the following line immediately after the line <portlet id="book_reports" /> and save it:
  3. <portlet id="extCommunities" />

As shown in the code above, it adds the Ext Communities portlet to the category Book. From now on, you are able to select this portlet from the Book category directly when adding portlets to pages.

Setting up actions

Now, let's set up all actions required for the Ext Communities portlet. We need to prepare an action class, for example, ExtEditGroupAction. So how do we build this action? You can build the actions from scratch, but our purpose is to customize and extend the Communities portlet. In one word, we expect to reuse the out of the box portlet source code as much as possible and to write minimum code.

As mentioned earlier, we have the portal project for the portal source code in the Eclipse IDE, which is referred to as the /portal prefix. We also have the ext project for customized code, which is referred to as the /ext prefix. The following is a process flow to build the ExtEditGroupAction action class of the Ext Communities portlet.

  1. Create a com.ext.portlet.communities.action package in the /ext/ext-impl/src folder.
  2. Create an ExtEditGroupAction class in this package and open it.
  3. Add the following lines and save it:
  4. public class ExtEditGroupAction extends EditGroupAction {
    public void processAction( ActionMapping mapping, ActionForm
    form, PortletConfig portletConfig,
    ActionRequest actionRequest, ActionResponse actionResponse)
    throws Exception {
    String cmd = ParamUtil.getString(actionRequest,
    Constants.CMD);
    try {
    if (cmd.equals(Constants.ADD) ||
    cmd.equals(Constants.UPDATE)) {
    updateGroup(actionRequest);
    }
    else if (cmd.equals(Constants.DELETE)) {
    deleteGroup(actionRequest);
    }
    sendRedirect(actionRequest, actionResponse);
    }
    catch (Exception e) {
    if (e instanceof NoSuchGroupException ||
    e instanceof PrincipalException) {
    SessionErrors.add(actionRequest, e.getClass().getName());
    setForward(actionRequest, "portlet.ext.communities.error");
    }
    else if (e instanceof DuplicateGroupException ||
    e instanceof GroupFriendlyURLException ||
    e instanceof GroupNameException ||
    e instanceof RequiredGroupException) {
    SessionErrors.add(actionRequest, e.getClass().getName(), e);
    if (cmd.equals(Constants.DELETE)) {
    actionResponse.sendRedirect(
    ParamUtil.getString(actionRequest, "redirect"));
    }
    } else { throw e;}
    }
    }
    public ActionForward render(ActionMapping mapping, ActionForm
    form, PortletConfig portlonfig,
    RenderRequest renderRequest, RenderResponse renderResponse)
    throws Exception {
    try { ActionUtil.getGroup(renderRequest); }
    catch (Exception e) {
    if (e instanceof NoSuchGroupException ||
    e instanceof PrincipalException) {
    SessionErrors.add(renderRequest,
    e.getClass().getName());
    return mapping.findForward
    ("portlet.ext.communities.error");
    }
    else {throw e;}
    }
    return mapping.findForward(getForward(renderRequest,
    "portlet.ext.communities.edit_community"));
    }
    }

As shown in the code above, ExtEditGroupAction extends EditGroupAction from the com.liferay.portlet.communities.action package in the /portal/portal-impl/src folder. It overrides two methods (render and processAction) of EditGroupAction.

Setting up page flow and page layout

We have set up the action. We have also updated the forward path as the portlet.ext.communities.* value. In order to get the page flow working, we need to set up an action path and a page flow.

First, let's set up the action path and page flow in struts-config.xml as follows:

  1. Locate the struts-config.xml file in the /ext/ext-web/docroot/WEB-INF folder and open it.
  2. Add the following lines after <struts-config> <action-mappings> and save it:
  3. <!-- Ext Communities -->
    <action path="/ext/communities/edit_community"
    type="com.ext.portlet.communities.action.
    ExtEditGroupAction">
    <forward name="portlet.ext.communities.edit_community"
    path="portlet.ext.communities.edit_community" />
    <forward name="portlet.ext.communities.error"
    path="portlet.ext.communities.error" />
    </action>
    <action path="/ext/communities/view"
    forward="portlet.ext.communities.view" />

The code above defines a set of action paths associated with the action and forward paths, as well as those mentioned earlier. For example, the action path /ext/communities/edit_community is associated with the com.ext.portlet.communities.action.ExtEditGroupAction action and the forward path names portlet.ext.communities.edit_community and portlet.ext.communities.error.

Then based on the page flow and JSP files, let's define the page layout in tiles-defs.xml:

  1. Locate the tiles-defs.xml file in the ext/ext-web/docroot/WEB-INF folder and open it.
  2. Add the following lines after <struts-config> <action-mappings> and save it:
      <!-- Ext Communities -->
      <action path="/ext/communities/edit_community"
      type="com.ext.portlet.communities.action.
      ExtEditGroupAction">
      <forward name="portlet.ext.communities.edit_community"
      path="portlet.ext.communities.edit_community" />
      <forward name="portlet.ext.communities.error"
      path="portlet.ext.communities.error" />
      </action>
      <action path="/ext/communities/view"
      forward="portlet.ext.communities.view" />

The code above defines a set of action paths associated with the action and forward paths, as well as those mentioned earlier. For example, the action path /ext/communities/edit_community is associated with the com.ext.portlet.communities.action.ExtEditGroupAction action and the forward path names portlet.ext.communities.edit_community and portlet.ext.communities.error.

Then based on the page flow and JSP files, let's define the page layout in tiles-defs.xml:

  1. Locate the tiles-defs.xml file in the ext/ext-web/docroot/WEB-INF folder and open it.
  2. Add the following lines after <tiles-definitions> and save it:
      <!-- Ext Communities -->
      <definition name="portlet.ext.communities" extends="portlet" />
      <definition name="portlet.ext.communities.edit_community"
      extends="portlet.ext.communities">
      <put name="portlet_content"
      value="/portlet/ext/communities/edit_community.jsp" />
      </definition>
      <definition name="portlet.ext.communities.view" extends="portlet">
      <put name="portlet_content"
      value="/portlet/ext/communities/view.jsp" />
      </definition>
      <definition name="portlet.ext.communities.error"
      extends="portlet">
      <put name="portlet_content"
      value="/portlet/communities/error.jsp" />
      </definition>

The code above defines the page layout for the Ext Communities portlet. For example, portlet.ext.communities.edit_community is associated with the JSP file /portlet/ext/communities/edit_community.jsp. In addition, it specifies that the community view page layout (for example, portlet.ext.communities.view) is associated with the JSP page file /portlet/ext/communities/view.jsp.

Preparing JSP files

We have now set up the actions. We have also set up page flow and page layout. Now let's set up the JSP files that are required for the Ext Communities portlet. We need to prepare JSP files such as view.jsp, edit_community.jsp, group_search.jsp, and so on. So how do we build this? You can build them from scratch. However, here we will copy and modify JSP files of the Communities portlet. In this section we expect to reuse the source code, including JSP files, as much as possible.

First, let's create the view.jsp JSP file as follows:

  1. Create a communities folder within the /ext/ext-web/docroot/html/portlet/ext/ folder.
  2. Locate the view.jsp JSP file in the /portal/portal-web/docroot/html/portlet/communities folder, and copy it to the /ext/ext-web/docroot/html/portlet/ext/communities folder.
  3. Open view.jsp in the /ext/ext-web/docroot/html/portlet/ext/communities folder, update /communities/edit_community with /ext/communities/edit_community as shown in the following two lines, and save it:
      portletURL.setParameter("struts_action", "/ext/communities/view");
      <liferay-ui:search-form
      page="/html/portlet/ext/communities/group_search.jsp"
      searchContainer="<%= searchContainer %>"
      showAddButton="<%= showTabs1 %>" />

Next, we need to create the JSP file edit_community.jsp as follows:

  1. Locate the JSP file edit_community.jsp in the /portal/portal-web/docroot/html/portlet/communities folder, and copy it to the /ext/extweb/docroot/html/portlet/ext/communities folder.
  2. Open edit_community.jsp in the /ext/ext-web/docroot/html/portlet/ext/communities folder, update /communities/edit_community with /ext/communities/edit_community as shown in following line, and save it:
      <form action="<portlet:actionURL 
      windowState="<%= WindowState.MAXIMIZED.
      toString() %>">
      <portlet:param name="struts_action"
      value="/ext/communities/edit_community" />
      </portlet:actionURL>"
      method="post" name="<portlet:namespace />fm"
      onSubmit="<portlet:namespace />saveGroup(); return false;">

In addition, we need to make the button Add Community available, in the following manner:

  1. Locate JSP file group_search.jsp in the /portal/portal-web/docroot/html/portlet/enterprise_admin folder.
  2. Copy the JSP file group_search.jsp from /portal/portal-web/docroot/html/portlet/enterprise_admin to /ext/ext-web/docroot/html/portlet/ext/communities, and open it.
  3. Update /communities/edit_community with /ext/communities/edit_community as shown in the following lines, and save it:
      submitForm(document.<portlet:namespace />fm, '<portlet:renderURL 
      windowState="<%= WindowState.MAXIMIZED.toString() %>">
      <portlet:param name="struts_action"
      value="/ext/communities/edit_community" />
      <portlet:param name="redirect"
      value="<%= currentURL %>" />
      </portlet:renderURL>');

Congratulations! You have cloned the Communities portlet. Finally, we can deploy updates into Tomcat as follows:

  1. Stop Tomcat if it is running.
  2. Click on the Ant target: deploy at the Ant view ext.
  3. Start Tomcat.
  4. Open up a new browser with the URL http://localhost:8080.
  5. Click on Sign in and enter test@liferay.com / test.
  6. Click on Add Application | Book

Setting up the Ext Communities portlet in the backend

We have cloned the Communities portlet as well. Now let's see how to implement new features in the backend.

Creating database structure

First, we need to add new fields for the Ext Communities portlet. These fields could be represented in service.xml. In order to generate services, let's build the fields as follows:

  1. Create a com.ext.portlet.group package in the /ext/ext-impl/src folder.
  2. Create a service.xml file in this package and open it.
  3. Add the following lines and save it:
      <?xml version="1.0"?><!DOCTYPE service-builder PUBLIC "-//
      Liferay//DTD Service Builder 5.2.0//EN" "http://www.liferay.com/dtd
      /liferay-service-builder_5_2_0.dtd">
      <service-builder package-path="com.ext.portlet.group">
      <namespace>ExtGroup</namespace>
      <entity name="ExtGroup" uuid="false"
      local-service="true"
      remote-service="true"
      persistence-class="com.ext.portlet.group.service.
      persistence.ExtGroupPersistenceImpl">
      <column name="groupId" type="long" primary="true" />
      <column name="keywords" type="String" />
      <column name="creator" type="String" />
      <column name="modifierUserId" type="String" />
      <column name="created" type="Date" />
      <column name="modified" type="Date" /></entity>
      <exceptions><exception>ExtGroup</exception></exceptions>
      </service-builder>

As shown in the code above, package-path com.ext.portlet.group is the path that the class will generate in the /ext/ext-impl/src/com/ext/portlet/group folder. The entity has the ExtGroup namespace with an ExtGroup name. It will use local service, remote service, and so on. The entity has the primary key field groupId, and other fields such as keywords, created, modifierUserId, modified, and so on. Accordingly, an exception with the ExtGroup name is also specified - it will generate a class ExtGroupException in the com.ext.portlet.group package.

Then, we need to build a service with ServiceBuilder. After preparing service.xml, you can build the services as follows:

  1. Locate the XML file /ext/ext-impl/build-parent.xml and open it.
  2. Add the following lines between </target> and <target name="buildservice-portlet-reports"> and save it:
      <target name="build-service-portlet-extCommunities">
      <antcall target="build-service">
      <param name="service.file"
      value="src/com/ext/portlet/group/service.xml" />
      </antcall>
      </target>
  3. Expand ext-impl in the Ant view.
  4. Double-click on the target: build-service-portlet-extCommunities.
  5. Refresh the /ext project.

ServiceBuilder will first generate models, services, and service persistence in the ext/ext-service/src folder. The following are the packages that contain services (SOAP-based web services, REST style web services, or JSON access via JavaScript, and so on):

  • com.ext.portlet.group
  • com.ext.portlet.group.model
  • com.ext.portlet.group.service
  • com.ext.portlet.group.service.persistence

Similarly, ServiceBuilder will generate implementations of models, services, and service persistence in the /ext/ext-impl/src folder. The following are the packages that contain the implementation of the services (SOAP-based web services, REST style web services, or JSON access via JavaScript, and so on):

  • com.ext.portlet.group.model.impl
  • com.ext.portlet.group.service.base
  • com.ext.portlet.group.service.http
  • com.ext.portlet.group.service.impl
  • com.ext.portlet.group.service.persistence

Finally, we need to create a new table ExtGroup in the database in the following manner:

drop table if exists ExtGroup;
create table ExtGroup (
groupId bigint not null primary key,
keywords varchar(511), modifierUserId bigint,
created datetime null, modified datetime null
);

As shown in code above, it drops the ExtGroup table if it exists. Then it createsan ExtGroup table with columns groupId, keywords, and so on.

Creating methods to update, delete, and retrieve

As stated above, the basic services and models are automatically generated by ServiceBuilder. Now we can add the database insert method (to add the community column Keywords) and the database retrieve method (to get all of the additional information about the community). Let's create the action named AddGroupLocalServiceUtil under the com.ext.portlet.group.action of /ext/ext-impl/src package.

public class AddGroupLocalServiceUtil {
public static ExtGroup getGroup(long groupId) {
ExtGroup group = null;
try {
group = ExtGroupLocalServiceUtil.getExtGroup(groupId);
}
catch (Exception e){}
if(group == null)
group = ExtGroupLocalServiceUtil.createExtGroup(groupId);
return group;
}
public static void deleteGroup(ActionRequest actionRequest) {
long groupId = ParamUtil.getLong(actionRequest, "groupId");
try{ ExtGroupLocalServiceUtil.deleteExtGroup(groupId);}
catch (Exception e){}
}
public static void updateGroup(ActionRequest actionRequest) {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.
getAttribute(WebKeys.THEME_DISPLAY);
String name = ParamUtil.getString(actionRequest, "name");
String keywords = ParamUtil.getString(actionRequest, "keywords");
long groupId = ParamUtil.getLong(actionRequest, "groupId");
Date now = new Date(); ExtGroup group = null;
try{
Group o = GroupServiceUtil.getGroup
(themeDisplay.getCompanyId(), name);
if(groupId <= 0) groupId = o.getGroupId();
group = ExtGroupLocalServiceUtil.getExtGroup(groupId);
}
catch (Exception e){}
if (group == null) {
group = ExtGroupLocalServiceUtil.createExtGroup(groupId);
group.setCreated(now);
}
else { group.setModified(now);
group.setModifierUserId(themeDisplay.getUser().getUserId());}
group.setKeywords(keywords);
try{ ExtGroupLocalServiceUtil.updateExtGroup(group);}
catch (Exception e){}
}
}

As shown in the code above, there are three methods: updateGroup, getGroup, and deleteGroup. The updateGroup method inserts or updates extGroup into the database, the getGroup method retrieves extGroup, and the deleteGroup method deletes extGroup by group ID. We use the name AddGroupLocalServiceUtil for demo purpose only. You can use a different name. Moreover, the service generated by the ServiceBuilder, for example, ExtGroupLocalServiceUtil, includes CRUD operations and handling transaction. We do not expect to modify this service. We simply add a customized wrapper AddGroupLocalServiceUtil on top of the service, for example, ExtGroupLocalServiceUtil. Thus, when upgrading ServiceBuilder, we only need to upgrade this wrapper if any changes are involved.

Updating the action classes

After creating the methods to update, delete, and retrieve the community's additional information, we need to associate these methods with the corresponding methods in the action class.

First, let's associate the deleteGroup method in AddGroupLocalServiceUtil with the deleteGroup method in ExtEditGroupAction, using the following steps:

  1. Locate the ExtEditGroupAction.java file under the com.ext.portlet.communities.action package in the /ext/ext-impl/src folder and open it.
  2. Add following line before the line deleteGroup(actionRequest); and save it:
  3. AddGroupLocalServiceUtil.deleteGroup(actionRequest);

The code above specifies a function to delete ExtGroup.

Similarly, we can associate the updateGroup method in AddGroupLocalServiceUtil with the updateGroup method in ExtEditGroupAction.

  1. Open the ExtEditGroupAction.java file.
  2. Add following line after the line updateGroup(actionRequest); and save it.
  3. AddGroupLocalServiceUtil.updateGroup(actionRequest);

The code above shows a function to update ExtGroup after updating Group_.

Setting up the Ext Communities portlet in the frontend

We have set up the Ext Communities portlet in the backend. Now let's set it up in the frontend.

Updating and deleting Community customized columns

First, we need to add the service and model imports in the init.jsp file as follows:

  1. Create an init.jsp file in the /ext/ext-web/docroot/portlet/ext/communities folder and open it.
  2. Add the following lines at the beginning of this file and save it:
  3. <%@ include file="/html/portlet/communities/init.jsp" %>
    <%@ page import="com.ext.portlet.group.action.AddGroupLocalServiceUtil" %>
    <%@ page import="com.ext.portlet.group.model.ExtGroup" %>

Then, we need to update the edit_community.jsp file in the /ext/ext-web/docroot/portlet/ext/communities folder in order to support the updates of community-customized fields. To do so, update <%@ include file="/html/portlet/communities/init.jsp" %> with <%@ include file="/html/portlet/ext/communities/init.jsp" %>, and add the following lines after the line long groupId = BeanParamUtil.getLong(group, request, "groupId"); in edit_community.jsp file:

ExtGroup extGroup = null;
if(group != null)
extGroup = AddGroupLocalServiceUtil.getGroup(group.getGroupId());

Further, add the following lines after the lines <liferay-ui:input-field model="<%= Group.class %>" bean="<%= group %>" field="name" /></td></tr>:

<tr>
<td class="lfr-label"><liferay-ui:message key="keywords" /></td>
<td>
<liferay-ui:input-field model="<%= ExtGroup.class %>"
bean="<%= extGroup %>" field="keywords" />
</td>
</tr>

As shown in the code above, the JSP gets ExtGroup by the group ID as import. Then it adds a message Keywords, an input with model ExtGroup, bean extGroup, and field Keywords.

Retrieving community-customized columns

Last but not the least, we need to update the view.jsp file in the /ext/extweb/docroot/portlet/ext/communities folder to properly display community columns Keywords and Creator. To do so, first update <%@ include file="/html/portlet/communities/init.jsp" %> with <%@ include file="/html/portlet/ext/communities/init.jsp" %>. Then add the following lines after the line headerNames.add("name"); in the view.jsp file:

headerNames.add("Creator"); //Ext
headerNames.add("Keywords"); //Ext

Finally, add the following lines between row.addText(sb.toString()); and // Type:

// EXT
User creator = UserLocalServiceUtil.getUser
(group.getCreatorUserId());
row.addText(creator.getFullName());
ExtGroup extGroup = AddGroupLocalServiceUtil.getGroup
(group.getGroupId());
row.addText(extGroup.getKeywords());

As shown in the code above, when retrieving a normal group message, it tries to get the community additional information via the ExtGroup object. Then, it lists the headers for the community columns Creator and Keywords. Finally, it adds the value for customized rows, for example creator.getFullName() and extGroup.getKeywords().

Customizing the Manage Pages portlet

As stated earlier, a community may consist of a set of pages, including public pages and private pages. The Communities portlet also provides the ability to manage pages for a given community. In addition, Liferay portal also provides a portlet named Manage Pages (known as Page Settings previously) to manage the pages—either public or private—for a given group or community. Unfortunately, you cannot use it independently. That is, you cannot find it by clicking on Add Applications when you log in as an admin. A link is provided only in the theme at /portal/portal-web/docroot/html/themes/classic/templates/dock.vm as follows:

#if ($show_page_settings)
<li class="page-settings">
<a href="$page_settings_url">$page_settings_text</a>
</li>
#end

Liferay Portal 5.2 Systems Development

As shown in this screenshot, you can click on the link Manage Pagesto update page settings. Let us suppose that you are in the Guest community, in the public pages by default and the Welcome page is under editing.

As shown in the following screenshot, here we want to customize the Manage Pages portlet. First, clone this portlet and make it standalone. Then, call it as Ext Manage Pages, and dynamically track the community to which the current page belongs. For example, if the portlet named Ext Manage Pages was added in the page of the Guest community, it should dynamically get the Guest community as a group name. Most importantly, we expect to extend the model of the page layout so that it supports additional messages, for example keywords, created(when it was created), creatorUserId (who created it), modifierUserId (who modified it), modified (when it was modified), and so on.

Liferay Portal 5.2 Systems Development

How to implement the above requirements? As was shown for the Ext Communities portlet, we will show how to customize the Manage Pages portlet. We will use the above requirements as examples to show what will be customized. However, it is up to you to make any decisions about what should be customized according to your requirements.

Building a standalone layout management portlet

Here we are not going to update the Manage Pages portlet directly. Instead, we are going to build a new portlet named Ext Manage Pages by customizing the Manage Pages portlet.

Constructing the portlet

Now let's specify a portlet with the name Ext Manage Pages. Thus, we are required to first configure the Ext Manage Pages portlet in both portlet-ext.xml and liferayportlet-ext.xml, set the title mapping in Language-ext.properties, and add the Ext Manage Pages portlet to the Book category in liferay-display.xml.

To do so, first add the following lines between </portlet> and </portlet-app> in portlet-ext.xml:

<portlet>
<portlet-name>extLayoutManagement</portlet-name>
<display-name>Ext Manage Pages</display-name>
<portlet-class>com.liferay.portlet.StrutsPortlet</portlet-class>
<init-param><name>view-action</name>
<value>/ext/layout_management/edit_pages</value>
</init-param><expiration-cache>0</expiration-cache>
<supports><mime-type>text/html</mime-type></supports>
<resource-bundle>
com.liferay.portlet.StrutsResourceBundle
</resource-bundle>
<security-role-ref>
<role-name>power-user</role-name>
</security-role-ref>
<security-role-ref>
<role-name>user</role-name>
</security-role-ref>
</portlet>

Then add the following lines after the line <!-- Custom Portlets --> in liferay-portlet-ext.xml:

<portlet>
<portlet-name>extLayoutManagement</portlet-name>
<struts-path>ext/layout_management</struts-path>
<use-default-template>false</use-default-template>
<restore-current-view>false</restore-current-view>
</portlet>

Moreover, add the following line after the line javax.portlet.title.extCommunities=Ext Communities in Language-ext.properties:

javax.portlet.title.extLayoutManagement=Ext Manage Pages

Finally, add the following line after the line <portlet id="extCommunities" /> in liferay-display.xml:

<portlet id="extLayoutManagement" />

Setting up the action

Let's set up the ExtEditPagesAction action required for the Ext Manage Pages portlet as follows:

  1. Create an ExtEditPagesAction class in the com.ext.portlet.communities.action package under the /ext/ext-impl/src folder and open it.
  2. Add the following lines in this class and save it:
  3. public class ExtEditPagesAction extends EditPagesAction {
    public void processAction(ActionMapping mapping, ActionForm
    form, PortletConfig portletConfig,
    ActionRequest actionRequest, ActionResponse actionResponse)
    throws Exception {
    String cmd = ParamUtil.getString(actionRequest,
    Constants.CMD);
    try {
    if (cmd.equals(Constants.ADD) ||
    cmd.equals(Constants.UPDATE)) {
    updateLayout(actionRequest, actionResponse);
    }
    else if (cmd.equals(Constants.DELETE)) {
    CommunitiesUtil.deleteLayout(actionRequest,
    actionResponse);
    }
    String redirect = ParamUtil.getString(
    actionRequest, "pagesRedirect");
    sendRedirect(actionRequest, actionResponse, redirect);
    }
    catch (Exception e) {
    if (e instanceof NoSuchLayoutException ||
    e instanceof NoSuchProposalException ||
    e instanceof PrincipalException) {
    SessionErrors.add(actionRequest, e.getClass().getName());
    setForward(actionRequest,
    "portlet.ext.communities.error");
    }
    else if (e instanceof RemoteExportException) {
    SessionErrors.add(actionRequest,
    e.getClass().getName(), e);
    String redirect = ParamUtil.getString(
    actionRequest, "pagesRedirect");
    sendRedirect(actionRequest, actionResponse, redirect);
    }
    else if (e instanceof LayoutFriendlyURLException ||
    e instanceof LayoutHiddenException ||
    e instanceof LayoutNameException ||
    e instanceof LayoutParentLayoutIdException ||
    e instanceof LayoutSetVirtualHostException ||
    e instanceof LayoutTypeException ||
    e instanceof RequiredLayoutException ||
    e instanceof UploadException) {
    if (e instanceof LayoutFriendlyURLException) {
    SessionErrors.add(actionRequest,
    LayoutFriendlyURLException.class.getName(), e);
    }
    else {
    SessionErrors.add(actionRequest, e.getClass().
    getName(), e);
    }
    }
    else { throw e; }
    }
    }
    public ActionForward render(ActionMapping mapping, ActionForm
    form, PortletConfig portletConfig,
    RenderRequest renderRequest, RenderResponse renderResponse)
    throws Exception {
    ThemeDisplay themeDisplay = (ThemeDisplay)renderRequest.
    getAttribute(WebKeys.THEME_DISPLAY);
    long groupId = themeDisplay.getScopeGroupId();
    renderRequest.setAttribute("GROUP",
    GroupLocalServiceUtil.getGroup(groupId));
    return mapping.findForward( getForward(renderRequest,
    "portlet.ext.communities.edit_pages"));
    }
    }

Setting up page flow and page layout

We have set up the action. So how do we get the page flow working? We need to set up an action path and page flow. To do so, first add the following lines after the lines <struts-config> <action-mappings> in struts-config.xml under the /ext/ext-web/docroot/WEB-INF folder:

<!-- Ext Layout Management -->
<action path="/ext/layout_management/edit_pages"
type="com.ext.portlet.communities.action.ExtEditPagesAction">
<forward name="portlet.ext.communities.edit_pages"
path="portlet.ext.layout.edit_pages" />
<forward name="portlet.ext.communities.error"
path="portlet.ext.communities.error" />
</action>

Then add the following lines after <tiles-definitions> in tiles-defs.xml under the /ext/ext-web/docroot/WEB-INF folder:

<!-- Ext Layout Management -->
<definition name="portlet.ext.layout.edit_pages" extends="portlet">
<put name="portlet_content"
value="/portlet/ext/communities/edit_pages.jsp" />
</definition>

Preparing JSP files

As mentioned above, we used the edit_pages.jsp file in tiles-defs.xml. That is, we need to build the edit_pages.jsp file:

  1. Locate the edit_pages.jsp file in the /portal/portal-web/docroot/html/portlet/communities folder and copy it to the /ext/ext-web/docroot/html/portlet/ext/communities folder.
  2. Open this file and first update <%@ include file="/html/portlet/communities/init.jsp" %> with <%@ include file="/html/portlet/ext/communities/init.jsp" %>.
  3. Then update "/communities/edit_pages" with "/ext/layout_management/edit_pages", as shown in following lines:
  4. portletURL.setParameter("struts_action", 
    "/ext/layout_management/edit_pages");
    <form action="<portlet:actionURL windowState="<%=
    WindowState.MAXIMIZED.toString() %>">
    <portlet:param name="struts_action"
    value="/ext/layout_management
    /edit_pages" />
    </portlet:actionURL>"
    method="post"
    name="<portlet:namespace />fm"
    onSubmit="<portlet:namespace />savePage(); return false;">

Cool! You have cloned the Manage Pages portlet successfully. After re-deploying these changes in Tomcat, you will now see the Ext Manage Page portlet with the Ext Manage Page title and contents under the Book category.

Setting up the Ext Layout Management portlet in the backend

After cloning the Manage Pages portlet, we will implement new features in the backend. By the way, the implementation flow is close to that of the Ext Communities portlet.

Creating a database structure

First of all, a database structure for new features is represented in service.xml. Let's create a new XML file service.xml as follows:

  1. Create a com.ext.portlet.layout package in the /ext/ext-impl/src folder.
  2. Create a service.xml file in the com.ext.portlet.layout package and open it.
  3. Add the following lines in it and save it:
  4. <?xml version="1.0"?><!DOCTYPE service-builder PUBLIC "-//
    Liferay//DTD Service Builder 5.2.0//EN" "http://www.liferay.com/
    dtd/liferay-service-builder_5_2_0.dtd">
    <service-builder package-path="com.ext.portlet.layout">
    <namespace>ExtLayout</namespace>
    <entity name="ExtLayout"
    uuid="false"
    local-service="true"
    remote-service="true"
    persistence-class="com.ext.portlet.layout.service.
    persistence.ExtLayoutPersistenceImpl">
    <column name="plid" type="long" primary="true" />
    <column name="keywords" type="String" />
    <column name="creatorUserId" type="long" />
    <column name="modifierUserId" type="long" />
    <column name="created" type="Date" />
    <column name="modified" type="Date" /></entity>
    <exceptions><exception>ExtLayout</exception></exceptions>
    </service-builder>

As shown in the code above, the entity has the ExtLayout namespace with a name ExtLayout. The entity has the primary key field plid and other fields such as keywords, creatorUserId, created, modifierUserId, and modified.

Then, we need to build the services using ServiceBuilder. This process is the same as that of building services of Ext Communities, which is as follows:

  1. Locate the XML file /ext/ext-impl/build-parent.xml and open it.
  2. Add the following lines between </target> and <target name="buildservice-portlet-reports"> and save it:
  3. <target name="build-service-portlet-extLayout">
    <antcall target="build-service">
    <param name="service.file"
    value="src/com/ext/portlet/layout/service.xml" />
    </antcall>
    </target>
  4. Expand ext-impl in the Ant view.
  5. Double-click on the build-service-portlet-extLayout target.
  6. Refresh the /ext project.

Finally, we should create a new table named ExtLayout in the database as follows:

drop table if exists ExtLayout;
create table ExtLayout (
plid bigint not null primary key,
keywords varchar(511),
creatorUserId bigint, modifierUserId bigint,
created datetime null,modified datetime null
);

As shown in the code above, it first drops the ExtLayout table (if it exists). Then it creates an ExtLayout table with a set of fields such as plid and keywords.

Creating methods to update, delete, and retrieve

The basic services and models are automatically generated by ServiceBuilder. Now we can add the database insert method (to add page columns Keywords) and retrieve method (to get layout page additional information). Let's create the action named AddLayoutLocalServiceUtil under the com.ext.portlet.layout.action package of the /ext/ext-impl/src folder with the following contents:

public class AddLayoutLocalServiceUtil {
public static ExtLayout getLayout(long plid) {
ExtLayout extLayout = null;
try{
extLayout = ExtLayoutLocalServiceUtil.getExtLayout(plid);
}
catch (Exception e){}
if(extLayout == null)
extLayout = ExtLayoutLocalServiceUtil.createExtLayout(plid);
return extLayout;
}
public static void deleteLayout(ActionRequest actionRequest) {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.
getAttribute(WebKeys.THEME_DISPLAY);
long layoutId = ParamUtil.getLong(actionRequest, "layoutId");
long groupId = themeDisplay.getScopeGroupId();
boolean privateLayout = ParamUtil.getBoolean
(actionRequest, "privateLayout");
long plid = ParamUtil.getLong(actionRequest, "plid");
try{
if(plid <= 0 )
plid = LayoutLocalServiceUtil.getLayout(groupId,
privateLayout, layoutId).getPlid();
ExtLayoutLocalServiceUtil.deleteExtLayout(plid);
}
catch (Exception e){}
}
public static void updateLayout(ActionRequest actionRequest) {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.
getAttribute(WebKeys.THEME_DISPLAY);
String cmd = ParamUtil.getString(actionRequest, Constants.CMD);
long layoutId = ParamUtil.getLong(actionRequest, "layoutId");
String keywords = ParamUtil.getString(actionRequest, "keywords");
long groupId = themeDisplay.getScopeGroupId();
boolean privateLayout = ParamUtil.getBoolean(
actionRequest, "privateLayout");
Date now = new Date();ExtLayout extLayout = null;
long plid = 0;
try{
if (cmd.equals(Constants.ADD))
layoutId = getLayoutId(groupId, privateLayout);
Layout layout = LayoutLocalServiceUtil.getLayout(groupId,
privateLayout, layoutId);
plid = layout.getPlid();
}
catch (Exception e){}
try{
extLayout = ExtLayoutLocalServiceUtil.getExtLayout(plid);
}
catch (Exception e){}
if(extLayout == null){
extLayout = ExtLayoutLocalServiceUtil.createExtLayout(plid);
extLayout.setCreatorUserId(themeDisplay.getUser().getUserId());
extLayout.setCreated(now);
}
else {
extLayout.setModified(now);
extLayout.setModifierUserId(themeDisplay.getUser().getUserId());
}
extLayout.setKeywords(keywords);
try{
ExtLayoutLocalServiceUtil.updateExtLayout(extLayout);
}
catch (Exception e){}
}
public static long getLayoutId(long groupId, boolean privateLayout)
throws SystemException {
long layoutId = 0;
List<Layout> layouts = LayoutLocalServiceUtil.getLayouts
(groupId, privateLayout);
for (Layout curLayout : layouts) {
long curLayoutId = curLayout.getLayoutId();
if (curLayoutId > layoutId) {layoutId = curLayoutId; } }
return layoutId;
}
}

As shown in code above, there are three methods: updateLayout, getLayout, and deleteLayout. The updateLayout method updates extLayout into the database, the getLayout method retrieves extLayout by page layout ID, and the deleteLayout method deletes extLayout.

Updating the action class

After creating the methods to update and delete, and getting the page additional information, we need to associate these methods with the corresponding methods in the action class.

First, let's associate the deleteLayout method in AddLayoutLocalServiceUtil.java with the deleteLayout method in ExtEditPagesAction.java. To do so, add the following line before the line CommunitiesUtil.deleteLayout(actionRequest, actionResponse); in ExtEditPagesAction.java:

AddLayoutLocalServiceUtil.deleteLayout(actionRequest);

The code above indicates that when using original method deleteLayout, applies for the extended method deleteLayout of AddLayoutLocalServiceUtil.

In the same way, we need to associate the updateLayout method in AddLayoutLocalServiceUtil.java with the updateLayout method in ExtEditLayoutAction.java. To do so, add the following line after the line updateLayout(actionRequest, actionResponse); in ExtEditLayoutAction.java:

AddLayoutLocalServiceUtil.updateLayout(actionRequest);

Setting up the layout management portlet in the frontend

We have set up the Ext Manage Pages portlet in the backend. It is time to construct the Ext Manage pages portlet in the frontend.

First, we need to add the service and model imports in the init.jsp file under the /ext/ext-web/docroot/portlet/ext/communities folder, similar to that of Ext Communities. To do so, simply add the following lines at the end of init.jsp:

<%@ page import="com.ext.portlet.layout.action.
AddLayoutLocalServiceUtil" %>
<%@ page import="com.ext.portlet.layout.model.ExtLayout" %>

Then, we need to update the /ext/ext-web/docroot/portlet/ext/communities/edit_pages.jsp file in order to use the Ext Manage Pages portlet (the portlet name is extLayoutManagement) instead of the original portlet name PortletKeys.LAYOUT_MANAGEMENT. Simply add a line String extPortlet = "extLayoutManagement"; after the line String tabs4 = ParamUtil.getString(request, "tabs4"); in the edit_pages.jsp. Now update PortletKeys.LAYOUT_MANAGEMENT with extPortlet as follows:

String extPortlet = "extLayoutManagement";
if (portletName.equals(extPortlet) ||
portletName.equals(PortletKeys.MY_ACCOUNT)) {
portletURL.setParameter("backURL", backURL);
}

The code above tries to use the Ext Manage Pages portlet as well. Further, we need to update the /html/portlet/communities/edit_pages_public_and_private.jspf string with the /html/portlet/ext/communities/edit_pages_public_and_private.jspf string.

As you can see, we involve the JSP files edit_pages_public_and_private.jspf directly and edit_pages_page.jsp indirectly. Let's build these JSP files as follows:

  1. Copy the file edit_pages_public_and_private.jspf in the /portal/portal-web/docroot/portlet/communities folder to the /ext/ext-web/docroot/portlet/ext/communities folder and open it.
  2. Update the /html/portlet/communities/edit_pages_page.jsp string with the /html/portlet/ext/communities/edit_pages_page.jsp string and save it.
  3. Copy the file edit_pages_page.jsp in the /portal/portal-web/docroot/portlet/communities folder to the /ext/ext-web/docroot/portlet/ext/communities folder.

Finally, we need to update the JSP file edit_pages_page.jsp in the /ext/extweb/docroot/portlet/ext/communities/ folder in order to support the updates of the customized information. To do so, first update <%@ include file="/html/portlet/communities/init.jsp" %> with <%@ include file="/html/portlet/ext/communities/init.jsp" %> in edit_pages_page.jsp, and then add the following lines after the line Layout selLayout = (Layout)request.getAttribute("edit_pages.jsp-selLayout");

ExtLayout extLayout = null; 
if(selLayout != null)
0extLayout = AddLayoutLocalServiceUtil.getLayout
(selLayout.getPlid());

Moreover, add the following lines after the lines <tr><td colspan="3"><br /></td></tr><tr><td><liferay-ui:message key="type" />:

<tr>
<td><liferay-ui:message key="keywords" /></td>
<td>
<liferay-ui:input-field model="<%= ExtLayout.class %>"
bean="<%= extLayout %>"
field="keywords" />
</td>
<td></td>
</tr>

As shown in the code above, the JSP receives ExtLayout by the page layout ID. It also adds a Keywords message and an input with the ExtLayout model, the extLayout bean, and the keywords field.

Summary

This article first discussed how to customize and extend the Communities portlet. It showed how the Manage Pages portlet provides the ability to manage a lot of pages of different communities. Then it introduced how to customize the Manage Pages portlet.


If you have read this article you may be interested to view :


About the Author :


Jonas X. Yuan

Jonas X. Yuan is a Chief Architect of ForgeLife LLC and an expert on Liferay Portal, e-commerce, and Content Management Systems (CMS). As an open source community contributor, he has published five Liferay books from 2008 to 2012. He is also an expert on Liferay integration with Ad Server OpenX, different search engines, enterprise content including videos, audio, images, documents, and web contents, and other technologies, such as BPM Intalio and Business Intelligence Pentaho, LDAP, and SSO. He holds a Ph.D. in Computer Science from the University of Zurich, where he focused on Integrity Control in federated database systems.

He earned his M.S. and B.S. degrees from China, where he conducted research on expert systems for predicting landslides. Previously, he worked as a Project Manager and a Technical Architect in Web GIS (Geographic Information System).
He is experienced in Systems Development Lifecycle (SDLC) and has deep, hands-on skills in J2EE technologies. He developed a BPEL (Business Process Execution Language) engine called BPELPower from scratch at the NASA data center. As the chief architect, Dr. Yuan successfully led and launched several large-scale Liferay/Alfresco e-commerce projects for millions of users each month.

He has worked on the following books: Liferay Portal Enterprise Intranets, 2008; Liferay Portal 5.2 Systems Development, 2009; Liferay Portal 6 Enterprise Intranets, 2010; Liferay User Interface Development, 2010; Liferay Portal Systems Development, 2012.

Books From Packt


Apache Maven 2 Effective Implementations: RAW
Apache Maven 2 Effective Implementations: RAW

Pentaho Reporting 1.0 for Java Developers
Pentaho Reporting 1.0 for Java Developers

Drools JBoss Rules 5.0 Developer's Guide
Drools JBoss Rules 5.0 Developer's Guide

Grails 1.1 Web Application Development
Grails 1.1 Web Application Development

Seam 2.x Web Development
Seam 2.x Web Development

JBoss Drools Business Rules
JBoss Drools Business Rules

JBoss Tools 3 Developers Guide
JBoss Tools 3 Developers Guide

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development


Your rating: None Average: 5 (1 vote)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
z
A
M
B
H
t
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software