Portlets are a common technology to create plug&play components for modern web applications and are specified by the Java Community Process in several specification requests.
In the following tutorial we’re going to learn how to create custom portlets and how to deploy and embed them in Liferay, the popular open-source enterprise portal.
In addition we’re taking a look at inter-portlet-communication and how to create portlets using annotations.
Finally we’re building a portlet-state-aware Java-Server-Faces portlet using the jsf-portlet-bridge mechanism.
Prerequisites
A few things are needed before we can start to write our first portlets so please be sure to have the following applications installed:
I have used Liferay 6.0 GA4 Community Edition for this tutorial, bundled with GlassFish as application server because I like GlassFish – but Liferay is shipped with a rich variety of bundled execution environments so you may use GlassFish with a Tomcat, JBoss or Resin server if you wish to.
Creating a project with Maven
First we need a new Maven project – fortunately there is a Maven archetype for Liferay portlets .. it creates a standard wep application directory structure, dependencies needed, the portlet descriptor and some additional xml descriptor files for Liferay. If you’re not going to use Liferay you might want to delete the additional xml descriptors (or simple use the archetype org.apache.maven.archetypes:maven-archetype-portlet)
-
We’re creating a new Maven project using the archetype com.liferay.maven.archetypes:liferay-portlet-archetype and your Maven-enabled IDE our via console
mvn archetype:generate
-
The generated pom.xml looks like this
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hascode.tutorial</groupId> <artifactId>liferay-portlet-tutorial</artifactId> <packaging>war</packaging> <name>liferay-portlet-tutorial Portlet</name> <version>0.0.1-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>com.liferay.maven.plugins</groupId> <artifactId>liferay-maven-plugin</artifactId> <version>${liferay.version}</version> <configuration> <autoDeployDir>${liferay.auto.deploy.dir}</autoDeployDir> </configuration> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>com.liferay.portal</groupId> <artifactId>portal-service</artifactId> <version>${liferay.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.liferay.portal</groupId> <artifactId>util-bridges</artifactId> <version>${liferay.version}</version> </dependency> <dependency> <groupId>com.liferay.portal</groupId> <artifactId>util-taglib</artifactId> <version>${liferay.version}</version> </dependency> <dependency> <groupId>com.liferay.portal</groupId> <artifactId>util-java</artifactId> <version>${liferay.version}</version> </dependency> <dependency> <groupId>javax.portlet</groupId> <artifactId>portlet-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies> <properties> <liferay.auto.deploy.dir>../bundles/liferay-portal-6.0/deploy</liferay.auto.deploy.dir> <liferay.version>6.0.6</liferay.version> </properties> </project>
-
If you need more detailed information on the archetype, take a look at Milen Dyankovs blog article “http://milen.commsen.com/2009/10/creating-liferay-portlet-with-liferay-maven-sdk.html[Creating Liferay portlet with liferay-maven-sdk]“
-
Now we’re ready to build our first sample portlet .. everything we need for now is included as a dependency in the pom.xml .. most important for us: portlet-api, servlet api and jsp …
$ mvn dependency:tree [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'dependency'. [INFO] ------------------------------------------------------------------------ [INFO] Building liferay-portlet-tutorial Portlet [INFO] task-segment: [dependency:tree] [INFO] ------------------------------------------------------------------------ [INFO] [dependency:tree {execution: default-cli}] [INFO] com.hascode.tutorial:liferay-portlet-tutorial:war:0.0.1-SNAPSHOT [INFO] +- com.liferay.portal:portal-service:jar:6.0.6:provided [INFO] +- com.liferay.portal:util-bridges:jar:6.0.6:compile [INFO] +- com.liferay.portal:util-taglib:jar:6.0.6:compile [INFO] +- com.liferay.portal:util-java:jar:6.0.6:compile [INFO] +- javax.portlet:portlet-api:jar:2.0:provided [INFO] +- javax.servlet:servlet-api:jar:2.4:provided [INFO] \- javax.servlet.jsp:jsp-api:jar:2.0:provided [INFO] ------------------------------------------------------------------------
Building Portlets
Now that we got a Maven project set up we’re going to create our first portlet …
Obligatory Hello-World-Portlet
Our first portlet is the obligatory Hello-World application ..
-
First our simple portlet class that renders some text: com.hascode.tutorial.portlet.HelloWorldPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import java.io.PrintWriter; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class HelloWorldPortlet extends GenericPortlet { @Override protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException { response.setContentType("text/html"); PrintWriter writer = response.getWriter(); writer.println("<h1>Hello world</h1><br/><br/><div>This is a sample portlet from hasCode.com</div>"); } }
-
In addition we’re going to declare the portlet in the portlet.xml in src/main/webapp/WEB-INF
<?xml version="1.0"?> <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"> <portlet> <portlet-name>hascode-liferay-portlet</portlet-name> <display-name>hascode-liferay-portlet</display-name> <portlet-class>com.hascode.tutorial.portlet.HelloWorldPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>hascode-liferay-portlet</title> <keywords>hascode-liferay-portlet-tutorial</keywords> </portlet-info> <security-role-ref> <role-name>administrator</role-name> </security-role-ref> <security-role-ref> <role-name>guest</role-name> </security-role-ref> <security-role-ref> <role-name>power-user</role-name> </security-role-ref> <security-role-ref> <role-name>user</role-name> </security-role-ref> </portlet> </portlet-app>
-
Because we didn’t have much to do here we’re going to declare some Liferay-specific portlet information.. first the liferay-display.xml we’re putting our portlet in the category ‘Samples’ .. we’re going to see what this means in the following screenshots of the Liferay user interface
<?xml version="1.0"?> <!DOCTYPE display PUBLIC "-//Liferay//DTD Display 6.0.0//EN" "http://www.liferay.com/dtd/liferay-display_6_0_0.dtd"> <display> <category name="category.sample"> <portlet id="hascode-liferay-portlet"/> </category> </display>
-
If you like you may add some vendor and licence information by editing the liferay-plugin-package.properties
name=hascode-liferay-portlet module-group-id=hasCode module-incremental-version=1 tags=tutorial short-description=hasCode Portlets change-log= page-url=https://www.hascode.com author=Micha Kops licenses=LGPL
-
And finally editing the liferay-portlet.xml
<?xml version="1.0"?> <!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.0.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_0_0.dtd"> <liferay-portlet-app> <portlet> <portlet-name>hascode-liferay-portlet</portlet-name> <icon>/icon.png</icon> <instanceable>true</instanceable> <header-portlet-css>/css/main.css</header-portlet-css> <footer-portlet-javascript>/js/main.js</footer-portlet-javascript> </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>
-
Please note that editing of the Liferay descriptors is optional and we won’t change anything there in the following examples!
-
Now build the portlet war file via IDE or
mvn package
Installing and Adding the Portlet
Of course now that we have created a portlet we want to deploy it into our Liferay instance and add it to a page ..
-
First login to your Liferay instance with an account with sufficient permissions to deploy software .. if you’re using the bundled test instance try username/password test@liferay.com/test
Figure 1. Portlet Deployment Step 1 - Login -
Select Manage > Control Panel from the top menu
Figure 2. Portlet Deployment Step 2 - Control Panel -
Now go to Server > Plugins Installation > Install more Portlets > Upload File and upload your created war archive
Figure 3. Portlet Deployment Step 3 - Plugins InstallationFigure 4. Portlet Deployment Step 4 - Install PortletFigure 5. Portlet Deployment Step 5 - Upload File -
Now you should be able to see the following success message
Portlet Deployment Success -
Now click Back To Liferay > Add > More. Search for hascode or simply click on Sample (that’s the category that we’ve assigned to the portlet in the liferay-display.xml. Now you’re able to add the portlet to the screen using drag&drop or simply clicking Add
Figure 6. Selecting the portlet from the control panelFigure 7. Adding the Portlet to the page
Inter-Portlet Communication
Now that we have written some portlets we want them to communicate ..
-
First we’re creating a Portlet to send a text from user input com.hascode.tutorial.portlet.EventSenderPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import java.io.PrintWriter; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.PortletURL; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.xml.namespace.QName; public class EventSenderPortlet extends GenericPortlet { @Override public void processAction(ActionRequest req, ActionResponse res) throws PortletException, IOException { final String message = req.getParameter("message"); if (message != null && !message.isEmpty()) { res.setEvent(new QName("https://www.hascode.com/portlet", "message"), message); } } @Override public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException { res.setContentType("text/html"); PrintWriter writer = res.getWriter(); PortletURL actionUrl = res.createActionURL(); final String output = String.format("<form action=\"%s\" method=\"post\"><label>Enter a message</label><input type=\"text\" name=\"message\"/><br/><input type=\"submit\"/></form>", actionUrl); writer.println(output); } }
-
Of course we need another portlet to receive the message and display it: com.hascode.tutorial.portlet.EventReceiverPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class EventReceiverPortlet extends GenericPortlet { @Override public void processEvent(EventRequest req, EventResponse res) throws PortletException, IOException { final String message = (String) req.getEvent().getValue(); res.setRenderParameter("message", message); } @Override public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException { final String message = req.getParameter("message"); res.getWriter().println("Message received from event: " + message); } }
-
Finally we only need to declare the event, the two portlets and add a binding to the event for both portlets in the portlet.xml
<?xml version="1.0"?> <portlet-app> [..] <portlet> <portlet-name>hascode-event-sender-portlet</portlet-name> <display-name>hascode-event-sender-portlet</display-name> <portlet-class>com.hascode.tutorial.portlet.EventSenderPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>hascode-event-sender-portlet</title> <keywords>hascode-event-sender-portlet</keywords> </portlet-info> <supported-publishing-event> <qname xmlns:hc="https://www.hascode.com/portlet">hc:message</qname> </supported-publishing-event> </portlet> <portlet> <portlet-name>hascode-event-receiver-portlet</portlet-name> <display-name>hascode-event-receiver-portlet</display-name> <portlet-class>com.hascode.tutorial.portlet.EventReceiverPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>hascode-event-receiver-portlet</title> <keywords>hascode-event-receiver-portlet</keywords> </portlet-info> <supported-processing-event> <qname xmlns:hc="https://www.hascode.com/portlet">hc:message</qname> </supported-processing-event> </portlet> <event-definition> <qname xmlns:hc="https://www.hascode.com/portlet">hc:message</qname> <value-type>java.lang.String</value-type> </event-definition> [..] </portlet-app>
Transferring custom objects between portlets
Now we’re going to send a custom object as a message from one portlet to another.Be aware that there are some limitations for such an object: It must implement Serializable, must have a no-arg-constructor and be annotated with @XmlRootElement
-
First our custom message object com.hascode.tutorial.portlet.CustomPortletMessage
package com.hascode.tutorial.portlet; import java.io.Serializable; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class CustomPortletMessage implements Serializable { private static final long serialVersionUID = 1L; private String text; public String getText() { return text; } public void setText(String text) { this.text = text; } }
-
Now we need a portlet to create and send a message com.hascode.tutorial.portlet.AdvancedSenderPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import java.io.PrintWriter; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.PortletURL; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.xml.namespace.QName; public class AdvancedEventSenderPortlet extends GenericPortlet { @Override public void processAction(ActionRequest req, ActionResponse res) throws PortletException, IOException { final String text = req.getParameter("message"); if (text != null && !text.isEmpty()) { CustomPortletMessage msg = new CustomPortletMessage(); msg.setText(text); res.setEvent(new QName("https://www.hascode.com/portlet", "customMessage"), msg); } } @Override public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException { res.setContentType("text/html"); PrintWriter writer = res.getWriter(); PortletURL actionUrl = res.createActionURL(); final String output = String.format("<form action=\"%s\" method=\"post\"><label>Enter a message</label><input type=\"text\" name=\"message\"/><br/><input type=\"submit\"/></form>", actionUrl); writer.println(output); } }
-
In the next step we’re creating the portlet to receive the message object and print its content com.hascode.tutorial.portlet.AdvancedReceiverPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class AdvancedEventReceiverPortlet extends GenericPortlet { @Override public void processEvent(EventRequest req, EventResponse res) throws PortletException, IOException { final CustomPortletMessage message = (CustomPortletMessage) req.getEvent().getValue(); res.setRenderParameter("message", message.getText()); } @Override public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException { final String message = req.getParameter("message"); res.getWriter().println("Message received from event: " + message); } }
-
Finally we need to declare the two portlets and the new event type in our portlet.xml
<portlet> <portlet-name>hascode-advanced-event-sender-portlet</portlet-name> <display-name>hascode-advanced-event-sender-portlet</display-name> <portlet-class>com.hascode.tutorial.portlet.AdvancedEventSenderPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>hascode-advanced-event-sender-portlet</title> <keywords>hascode-advanced-event-sender-portlet</keywords> </portlet-info> <supported-publishing-event> <qname xmlns:hc="https://www.hascode.com/portlet">hc:customMessage</qname> </supported-publishing-event> </portlet> <portlet> <portlet-name>hascode-advanced-event-receiver-portlet</portlet-name> <display-name>hascode-advanced-event-receiver-portlet</display-name> <portlet-class>com.hascode.tutorial.portlet.AdvancedEventReceiverPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>hascode-advanced-event-receiver-portlet</title> <keywords>hascode-advanced-event-receiver-portlet</keywords> </portlet-info> <supported-processing-event> <qname xmlns:hc="https://www.hascode.com/portlet">hc:customMessage</qname> </supported-processing-event> </portlet> <event-definition> <qname xmlns:hc="https://www.hascode.com/portlet">hc:customMessage</qname> <value-type>com.hascode.tutorial.portlet.CustomPortletMessage</value-type> </event-definition>
-
Add the two portlets and send some messages – it should look like this
Message Sender and Receiver Portlet Example
Accessing the Portlet configuration
Our next portlet is going to read some configuration values from the portlet descriptor file and display it ..
-
First we’re creating another portlet com.hascode.tutorial.portlet.SimplePreferencesReadingPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import java.io.PrintWriter; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.PortletPreferences; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class SimplePreferencesReadingPortlet extends GenericPortlet { @Override public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException { res.setContentType("text/html"); PrintWriter writer = res.getWriter(); PortletPreferences prefs = req.getPreferences(); String[] tags = prefs.getValues("tags", null); writer.println("<b>Tags:</b><br/><ul>"); for (String tag : tags) { writer.println("<li>" + tag + "</li>"); } writer.println("</ul>"); } }
-
Finally we’re declaring the portlet in the portlet.xml and add some portlet-preferences, too
<portlet> <portlet-name>hascode-preferences-portlet</portlet-name> <display-name>hascode-preferences-portlet</display-name> <portlet-class>com.hascode.tutorial.portlet.SimplePreferencesReadingPortlet</portlet-class> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>hascode-preferences-portlet</title> <keywords>hascode-preferences-portlet</keywords> </portlet-info> <portlet-preferences> <preference> <name>tags</name> <value>portlet</value> <value>tutorial</value> <value>liferay</value> <value>java</value> </preference> </portlet-preferences> </portlet>
-
Finally our portlet looks like this and displays the keywords from the portlet configuration
Figure 8. Reading Portlet Preferences
Using Annotations
Since Portlet Specification 2.0 it is possible to use annotations to create portlet applications:
-
@RenderMode: Marks a method for execution for a specified render mode. The mode is defined using the annotation’s name flag.. e.g. @RenderMode(name = “view”)
-
@ProcessAction: Marks a method to be executed in a named action request
-
Let’s create a running example .. the following portlet offers a link, when the link is clicked, an action is triggered and the current time is displayed: com.hascode.tutorial.portlet.SimpleAnnotatedPortlet
package com.hascode.tutorial.portlet; import java.io.IOException; import java.util.Date; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletURL; import javax.portlet.ProcessAction; import javax.portlet.RenderMode; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class SimpleAnnotatedPortlet extends GenericPortlet { @RenderMode(name = "view") public void showMeSomeText(RenderRequest req, RenderResponse res) throws IOException { String theTime = req.getParameter("thetime"); PortletURL actionUrl = res.createActionURL(); actionUrl.setParameter(ActionRequest.ACTION_NAME, "mySpecialAction"); res.getWriter().println("The time is: " + theTime + "<br/><small><a href=\"" + actionUrl + "\">Update time</a></small>"); } @ProcessAction(name = "mySpecialAction") public void timeLookup(ActionRequest req, ActionResponse res) { res.setRenderParameter("thetime", new Date().toString()); } }
-
Our portlet looks like this now
Annotation Based Portlet Example
JSF Portlet Bridge and Java Server Faces 2
Finally we’re going to get Java Server Faces running as a Portlet. This isn’t as easy as it sound because the JSF lifecycle contains much more phases than the Portlet lifecycle.
That’s why the JSF Portlet Bridges were invented and specified by the Java Community Process in JSR-301 (Portlet Bridge 1.0) and JSR-329 (Portlet Bridge 2.0). Please note that JSF’s target version for both bridges is 1.2 – afaik there is no JSR for a Portlet Bridge for JSF 2.x, please correct me here if I am wrong.
There are several vendors filling this gap – the one I chose here was Portletfaces that supports bridging between Portlet and JSF 2 lifecycle. If you want to take a deeper look into Portletfaces features, please consults its detailed documentation.
With this information and Portletfaces on board we’re going to create a JSF Portlet that displays a sorted list of strings from a ManagedBean. In addition we’re going to map the Portlet’s action modes EDIT, VIEW and HELP to a corresponding JSF instance so that the user is able to change the way the strings are sorted using the Portlet’s configuration mode.
-
First we need to add some dependencies and repositories for Java Server Faces and Portletfaces to our pom.xml
<dependency> <groupId>org.portletfaces</groupId> <artifactId>portletfaces-bridge</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.1.1-b03</version> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-impl</artifactId> <version>2.1.1-b03</version> </dependency> <repositories> <repository> <id>maven2-repository.dev.java.net</id> <url>http://download.java.net/maven/2</url> </repository> <repository> <id>maven2-repository-portletfaces.org</id> <url>http://repo.portletfaces.org/mvn/maven2</url> </repository> </repositories>
-
In the next step we’re declaring a ManagedBean using the @ManagedBean annotation in com.hascode.tutorial.jsf.UserBean
package com.hascode.tutorial.jsf; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.portlet.ActionResponse; import javax.portlet.PortletMode; import javax.portlet.PortletModeException; @ManagedBean @SessionScoped public class UserBean { private String sortType = "normal"; private final List<String> names = new ArrayList<String>(); { names.add("adam"); names.add("barry"); names.add("douglas"); names.add("ethan"); } public List<String> getUserNames() { if ("reverse".equals(sortType)) { Collections.reverse(names); return names; } Collections.sort(names); return names; } public void save() throws PortletModeException { // Switch the portlet mode back to VIEW. FacesContext facesContext = FacesContext.getCurrentInstance(); ExternalContext externalContext = facesContext.getExternalContext(); ActionResponse actionResponse = (ActionResponse) externalContext.getResponse(); actionResponse.setPortletMode(PortletMode.VIEW); } public String getSortType() { return sortType; } public void setSortType(String sortType) { this.sortType = sortType; } }
-
Now we need to get the FacesServlet Dispatcher running by adding the following web.xml in src/main/webapp/WEB-INF
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> </web-app>
-
Now we need some Facelets for the different modes EDIT, HELP and VIEW .. let’s begin view the template for the view mode by creating a file named viewMode.xhtml in src/main/webapp/xhtml. We don’t do much here – we’re fetching the usernames from the ManagedBeans, iterate over them and output them in a html list
<?xml version="1.0" encoding="UTF-8"?> <f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:portlet="http://java.sun.com/portlet_2_0"> <h:head/> <h:body> <h:outputText value="VIEW"/> <ul> <ui:repeat value="${userBean.userNames}" var="userName"> <li> <h:outputText value="${userName}"/> </li> </ui:repeat> </ul> </h:body> </f:view>
-
Next our template for the help mode .. same directory as above but saved as helpMode.xhtml. We’re not helpful here and just output “HELP” :)
<?xml version="1.0" encoding="UTF-8"?> <f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head/> <h:body> <h:outputText value="HELP"/> </h:body> </f:view>
-
And finally the template to edit the settings editMode.xhtml. The user is able to select his desired sort mode in a select box.
<?xml version="1.0" encoding="UTF-8"?> <f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head/> <h:body> <h:outputText value="EDIT"/> <h:form> <h:messages globalOnly="true"/> <h:selectOneMenu id="selectSortType" value="#{userBean.sortType}"> <f:selectItem id="sort-normal" itemLabel="Normal Sorted" itemValue="normal"/> <f:selectItem id="sort-reverse" itemLabel="Reverse Sorted" itemValue="reverse"/> </h:selectOneMenu> <hr/> <h:commandButton actionListener="#{userBean.save}" value="Submit"/> </h:form> </h:body> </f:view>
-
Having created our facelets we’re going to define a classical JSF navigation by creating the following faces-config.xml in src/main/webapp/WEB-INF. It simply says that an outcome success from the editMode leads to the viewMode.
<?xml version="1.0" encoding="UTF-8"?> <faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" version="2.0"> <navigation-rule> <from-view-id>/xhtml/editMode.xhtml</from-view-id> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/xhtml/viewMode.xhtml</to-view-id> </navigation-case> </navigation-rule> </faces-config>
-
Out JSF fragments do look fine now so it’s time that we arrange a date with the Portlet API using the Portlet bridge. The following declaration added to your portlet.xml enables the EDIT,VIEW,HELP modes and maps them to the corresponding JSF actions and uses org.portletfaces.bridge.GenericFacesPortlet as portlet bridge and mapper between portlet requests and jsf lifecycle.
<?xml version="1.0"?> <portlet> <portlet-name>1</portlet-name> <display-name>JSF2-Portlet-Bridge</display-name> <portlet-class>org.portletfaces.bridge.GenericFacesPortlet</portlet-class> <init-param> <name>javax.portlet.faces.defaultViewId.view</name> <value>/xhtml/viewMode.xhtml</value> </init-param> <init-param> <name>javax.portlet.faces.defaultViewId.edit</name> <value>/xhtml/editMode.xhtml</value> </init-param> <init-param> <name>javax.portlet.faces.defaultViewId.help</name> <value>/xhtml/helpMode.xhtml</value> </init-param> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> <portlet-mode>EDIT</portlet-mode> <portlet-mode>HELP</portlet-mode> </supports> <portlet-info> <title>hasCode JSF2-Portlet-Bridge</title> <short-title>hasCode JSF2-Portlet-Bridge</short-title> <keywords>hasCode JSF2-Portlet-Bridge</keywords> </portlet-info> </portlet>
-
That’s what our portlet looks like in full action
Figure 9. JSF Portlet Component Step 1Figure 10. JSF Portlet Component Step 2Figure 11. JSF Portlet Component Step 3Figure 12. JSF Portlet Component Step 4
Portlet Example Screencast
The following screencast on YouTube demonstrates the usage of the portlets implemented above
Tutorial Sources
I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:
git clone https://github.com/hascode/liferay-portlet-tutorial.git
Additional Information: Java Server Faces
If you’re interested in additional information about Java Server Faces, please feel free to have a look at my other JSF articles on this blog.
Resources
Article Updates
-
2018-06-01: Embedded YouTube video removed (GDPR/DSGVO).
-
2015-04-01: Code formatting fixed, image captions added, links to my JSF articles added.