Table of contents
Introduction
Once a portlet is placed on a portal page, its data is not directly shared with other portlets. IPC (Inter Portlet Communication) defines the ways that a Portlet can interact and communicate with another Portlet. There are four ways to make inter portlet communication :
In this post, we will see how to communicate between portlets in client side using Ajax.
Client side communication
The JSR-286 (Portlet 2.0) specification don’t provide any approach to make an inter portlet communication on the client side. Liferay provides a powerful API based on JavaScript to perform communication between portlets within the client side.
One of the advantage of this solution is that there is no need for portlet configuration (portlet.xml) and the communication is done without page refresh. But the inconvenient is that all portlets must be in the same page since it uses JavaScript.
Below steps to follow to make a Portlet communication on the client side :
- Sender portlet
The Liferay JavaScript API provides the function Liferay.fire to fire the event. It takes two parameters: the first one is the event name and the second is the data to send.
Below how to use it :
1 2 3 4 5 6 |
// Sending the event "eventName" with 3 parameters. Liferay.fire('eventName',{ param1: value1, param2: value2, param3: value3, }); |
You will often need to use Ajax calls to get data from the client side. To allow that you must set the property requires-namespaced-parameters to false in the liferay-portlet.xml file as below:
1 2 3 4 5 6 7 8 |
<portlet> <portlet-name>portletName</portlet-name> <icon>/icon.png</icon> <requires-namespaced-parameters>false</requires-namespaced-parameters> <header-portlet-css>/css/main.css</header-portlet-css> <footer-portlet-javascript>/js/main.js</footer-portlet-javascript> <css-class-wrapper>portletName-portlet</css-class-wrapper> </portlet> |
- Receiver portlet
To process the fired event, The Liferay JavaScript API provides Liferay.on. The first parameter is the event name and should be the same as the fired one. The second parameter is the function to process the event :
1 2 3 4 5 6 7 8 |
// Receiving the event "eventName" and retrieving the parameter values. Liferay.on('eventName',function(event) { var parameterValue1 = event.param1; var parameterValue2 = event.param2; var parameterValue3 = event.param3; // Your code here... }); |
You will often need to use Ajax calls to get data from the client side. To allow that you must set the property requires-namespaced-parameters to false in the liferay-portlet.xml file as below:
1 2 3 4 5 6 7 8 |
<portlet> <portlet-name>portletName</portlet-name> <icon>/icon.png</icon> <requires-namespaced-parameters>false</requires-namespaced-parameters> <header-portlet-css>/css/main.css</header-portlet-css> <footer-portlet-javascript>/js/main.js</footer-portlet-javascript> <css-class-wrapper>portletName-portlet</css-class-wrapper> </portlet> |
Concrete example
Let’s see a full example of using the IPC in client side using Ajax.
The code source is available on Github. Download the source
Let’s consider two portlets :
- CarList (Sender portlet): This portlet shows a list of cars. The user can select a car from the list. When the user select a car, an Ajax call is done in the client side to get the corresponding car and an event will be fired with the selected car. The car information will be displayed in the second portlet.
- CarInformation (Receiver portlet): If an event is fired, it will be processed in the client side of the second portlet which will display the car information.
Sender portlet : CarList
- Portlet.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?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" 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" version="2.0"> <portlet> <portlet-name>car-list</portlet-name> <display-name>Car List</display-name> <portlet-class>com.roufid.tutorials.portlet.CarList</portlet-class> <init-param> <name>view-template</name> <value>/html/carlist/view.jsp</value> </init-param> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> </supports> <portlet-info> <title>Car List</title> <short-title>Car List</short-title> <keywords></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> |
- Liferay-portlet .xml
Since an Ajax call will be done in this portlet, we will set the property requires-namespaced-parameters to false in the liferay-portlet.xml file as below:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?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>car-list</portlet-name> <icon>/icon.png</icon> <requires-namespaced-parameters>false</requires-namespaced-parameters> <header-portlet-css>/css/main.css</header-portlet-css> <footer-portlet-javascript>/js/main.js</footer-portlet-javascript> <css-class-wrapper>car-list-portlet</css-class-wrapper> </portlet> </liferay-portlet-app> |
- Firing the event
Below the jsp view in which an event is fired with the selected car :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
<%@taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%> <%@taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%> <%@taglib uri="http://liferay.com/tld/aui" prefix="aui"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <portlet:defineObjects /> <liferay-theme:defineObjects /> <portlet:resourceURL var="getSelectedCar" /> <script> /* * On button click. A resourceURL is called to retreive the selected car and * fire the event on success. */ $(document).on('ready', function() { jQuery('[id*=selectButton]').click(function(event) { var carId = this.id.split("|")[1]; $.ajax({ url : '<%=getSelectedCar%>', dataType : "json", data : { carId : carId }, type : "get", success : function(data) { // Firing the event. Liferay.fire('getSelectedCar', { car : data }); } }); }); }); </script> <table class="table table-striped"> <thead> <tr> <th></th> <th>Id</th> <th>Year</th> <th>Brand</th> <th>Color</th> <th>Action</th> </tr> </thead> <tbody> <c:forEach var="car" items="${cars}"> <!-- Defining a form for each car. --> <aui:form action="<%=getSelectedCar%>" method="post" name="carList"> <tr> <td> <img src="${car.imagePath}" width="50" height="50" /> </td> <td> <c:out value="${car.id}" /> </td> <td> <c:out value="${car.year}" /> </td> <td> <c:out value="${car.brand}" /> </td> <td> <c:out value="${car.color}" /> </td> <td> <aui:button type="button" name="selectButton|${car.id}" value="Select" id="${car.id}" /> </td> </tr> </aui:form> </c:forEach> </tbody> </table> |
I used a portlet:resourceURL to serve the resource in Ajax call. Below the backing bean serving the resource :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import java.io.IOException; import java.io.PrintWriter; import java.util.List; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import com.liferay.portal.kernel.json.JSONFactoryUtil; import com.liferay.portal.kernel.json.JSONObject; import com.liferay.portal.kernel.util.ParamUtil; import com.liferay.util.bridges.mvc.MVCPortlet; import com.roufid.tutorials.bean.Car; import com.roufid.tutorials.service.CarService; /** * Listing cars. * * @author Radouane ROUFID * */ public class CarList extends MVCPortlet { /** * Singleton carService bean injection. */ private CarService service = CarService.getInstance(); @Override public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException { List<Car> cars = service.getCars(); renderRequest.setAttribute("cars", cars); super.doView(renderRequest, renderResponse); } @Override public void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) throws IOException, PortletException { String carId = ParamUtil.getString(resourceRequest, "carId"); Car car = service.getCarWithId(carId); JSONObject jsonCar = JSONFactoryUtil.createJSONObject(); if (car != null) { jsonCar.put("id", car.getId()); jsonCar.put("imagePath", car.getImagePath()); jsonCar.put("brand", car.getBrand()); jsonCar.put("color", car.getColor()); jsonCar.put("price", car.getPrice()); jsonCar.put("year", car.getYear()); } PrintWriter writer = resourceResponse.getWriter(); writer.println(jsonCar); } /** * @return the service */ public CarService getService() { return service; } /** * @param service * the service to set */ public void setService(CarService service) { this.service = service; } } |
Note that the car is sended as a parameter of type JSon.
Receiver portlet : CarInformation
- Portlet.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?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" 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" version="2.0"> <portlet> <portlet-name>car-information</portlet-name> <display-name>Car Information</display-name> <portlet-class>com.roufid.tutorials.portlet.CarInformation</portlet-class> <init-param> <name>view-template</name> <value>/html/carinformation/view.jsp</value> </init-param> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> </supports> <portlet-info> <title>Car Information</title> <short-title>Car Information</short-title> <keywords></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> |
- Liferay-portlet .xml
Set the property requires-namespaced-parameters to false in the liferay-portlet.xml file as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?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>car-information</portlet-name> <icon>/icon.png</icon> <requires-namespaced-parameters>false</requires-namespaced-parameters> <header-portlet-css>/css/main.css</header-portlet-css> <footer-portlet-javascript> /js/main.js </footer-portlet-javascript> <css-class-wrapper>car-information-portlet</css-class-wrapper> </portlet> </liferay-portlet-app> |
- Processing the event
The event is processed on the client side as below :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<%@taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%> <%@taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@page import="com.roufid.tutorials.bean.Car"%> <portlet:defineObjects /> <liferay-theme:defineObjects /> <script> // Processing the event. Liferay.on('getSelectedCar',function(event) { // Getting the car. var car = event.car; // Building the result. if (car.hasOwnProperty('id')) { jQuery('#userInformation').empty(); var htmlString = "<table class='table table-striped'>" + "<tr><td><img src='"+car.imagePath+"' width='200' height='200' /></td></tr>" + "<tr><td>Id</td><td>" + car.id + "</td></tr>" + "<tr><td>Brand</td><td>" + car.brand + "</td></tr>" + "<tr><td>Color</td><td>" + car.color + "</td></tr>" + "<tr><td>Year</td><td>" + car.year + "</td></tr>" + "<tr><td>Price</td><td>" + car.price+ "</td></tr>" + "</table>"; jQuery('#carInformation').html(htmlString); } }); </script> <div id="carInformation">No car is selected !</div> |
Note that the object event contains the parameter car.
Rendering after deployment
The CarList portlet is on the left. CarInformation portlet is on the right.
Below the result after selecting Ferrari car.
The code source is available on Github. Download the source