
Client-side Inter-Portlet Communication
Table of Contents [-]
Note: This document refers to Liferay 5. To see a similar document valid for Liferay 6, refer to Portlet to Portlet Communication or an article especially for using Java Script.
Introduction #
Communication across portlets is one of the hottest topics of portlet and portal application development. By allowing communication across portlets it's possible to show changes in one portlet based on user interactions with another portlet.
The JSR-286 (Portlet 2.0) specification provides a standard mechanism to do inter-portlet communication (IPC). Actually, it provides two ways: a simple method based on shared parameters, and a more complex event-based approach. If either of these methods fits your needs, by all means use them, since they are started and fully supported in Liferay since version 5.1. But IPC is a very broad topic and there is no solution that fits all, so Liferay provides an additional method based on JavaScript to perform communication purely within the client.
Liferay JavaScript Event System #
The client-side mechanism to communicate portlets is based on events. Using events instead of direct scripting calls across portlets is very important since it's not possible to know at deployment time which other portlets will be available in a given page. An event-based model allows one portlet to provide a notification that something of significance has happened, without establishing a hard dependency. Any other portlets on the same page that are interested in that event and have registered listeners are then able to react accordingly.
The API of this system is very simple and is based on two methods
Liferay.trigger(eventName, data) Liferay.bind(eventName, function, [scope])
These methods have been deprecated in Liferay 6 in favor of
Liferay.fire(eventName, data) Liferay.on(eventName, function, [scope])
Binding to events #
So, what Liferay.bind does is that it lets you listen in for any arbitrary event (it could be anything the developer decides, or an existing one, though we don't have many just yet). I'll use two examples of bind using events that are currently in place:
Let's say we want to listen for when a portlet has loaded, and if it's a journal content portlet, we'd like to limit the article so that it only shows the first 3 paragraphs, and you have to click a "More" link to show the rest. Here is how we would do it:
Liferay.bind( 'portletReady', function(event, data){ var portletId = data.portletId; var portlet = data.portlet; if(portletId.indexOf('56_INSTANCE') > -1){ var paragraphs = portlet.find('p'); var hiddenP = paragraphs.slice(3); hiddenP.hide(); var showLink = jQuery('<a href="javascript: ;">More</a>'); hiddenP.after(showLink); showLink.click( function(){ hiddenP.show(); showLink.remove(); } ); } } );
So, that's one way, if we wanted to execute when a portlet is ready. But let's say we wanted to listen for when portlets are closed. Currently, this is how we'd do it:
Liferay.bind( 'closePortlet', function(event, data){ // run javascript // data contains data.plid and data.portletId } );
The optional scope argument allows the code listening to the event say what scope the function should run in. For instance, by default, when you run the function, and you use the "this" variable, it points to the document object. But let's say you have an existing function that sits inside another object (let's say Liferay.Navigation, for instance), and the function has to have access to the methods of that object. Since the method is already defined you wouldn't want to overwrite it, so what you can do is say:
Liferay.bind('portletReady', Liferay.Navigation.init, Liferay.Navigation);
This will tell the Liferay.Navigation.init to point this to itself, rather than to the document object.
Okay, so that's the listening aspect. The pushing aspect is Liferay.trigger.
Triggering events #
Liferay.trigger() pushes the event out there so that any possible events that are listening will be executed. Liferay.trigger takes the two arguments, the eventName, and the data you wish to send to the listening functions. It doesn't have to be an object hash, but it's highly recommended, since it can contain multiple values.
Let's see a quick example of how you would quickly communicate two portlets: Let's say in Portlet 1 you have a list of users, and in Portlet 2 you have a list of articles published by different users. Assuming both are on the same page, you could have functionality where, when you click on a user in Portlet 1, it does an ajax call and grabs all of the articles by that user. But you want it loosely coupled, and only want it to happen if they're both on the same page.
I'm going to make a big assumption with the HTML, but it doesn't have to be this way. But in Portlet 1, we have our user links, which all have a class of .user-name, and just for simplicity in this case, a hidden input right next to the users link.
In Javascript, we would do this:
jQuery( function () { jQuery('a.user-name').click( function(event) { var userId = jQuery(this).next().val(); // Other related javascript... Liferay.trigger('userSelected', {userId: userId}); return false; } ) } );
This says, on page load, grab all links with the class name of user-name, and assign a click event (what we would normally do with JS actions on links). We'll grab the users ID, run some normal portlet specific JS (in this case where the comment would be) and then we would trigger our event, which is userSelected, in this case.
Then, in Portlet 2, you would have a listener set up, like this:
Liferay.bind( 'userSelected', function(event, data) { var userId = data.userId; jQuery('.article-results').html(''); if (userId) { jQuery.ajax( { url: '/our/portlet/url', data: { userId: userId }, error: function() { jQuery('.article-results').html('' + Liferay.Language.get('sorry-there-was-an-error') + ''); }, success: function(message) { jQuery('.article-results').html(message); } ); } } );
This does an ajax call which grabs our results, and handles the different scenarios.