« Back to Inter-portlet...

Client-side Inter-Portlet Communication

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.

0 Attachments
32088 Views
Average (3 Votes)
Comments

Showing 12 Comments

Andre Darian Bonner
10/14/08 1:55 PM

bump

Daniel Breitner
11/10/08 4:54 AM

I doesn´t work for me:

My browser tells me Liferay.trigger is not a function.
What did I do wrong ?

Daniel Breitner
11/11/08 5:01 AM

It still doesn´t work:

I am using Liferay 5.01 together with ICEfaces and I used your example 1:1 - but still I get:
"Liferay.trigger()" ist not a function.

Do I have to import something ?
Is Liferay 5.01 the correct Version ?

Daniel Breitner
11/12/08 4:12 AM

OK, I found out the following:

I works with Liferay 5.1.2 but not with Liferay 5.0.1.

I changed the wiki entry above ...

Glen K Brown
3/9/09 2:50 PM

I know this is late in this post but when I try (using 5.2.1) to use Liferay.bind in asset publisher using web content the process is triggered before any content is loaded into the asset publisher. Is that correct? In the end none of my a tags get processed because, it seems, they are not loaded yet. Am I thinking about this wrong?

thanks

Steffen Schuler
5/2/10 2:10 PM

... but what about Liferay 6? It seems to be not available anymore by default. Is it obsolet or do we need to add it manually?

Scott Langeberg
7/27/10 10:39 AM

Yeah, we are transitioning to LR 6.0.3 (soon 6.0.4), and we need this capability.

Scott Langeberg
7/27/10 12:03 PM

Ok, so if you look at js/liferay/events.js, you'll see that Liferay is simply wrapping function calls into jQuery, with same names. Therefore, I'm guessing it's just a matter of hosting jquery yourself, and calling direct to jquery. Is my current solution!

Blaine Boule
7/28/10 10:46 AM

The functionality is available in 6.0, but I think the method signature has changed. The callback used in Liferay.bind used to be a function with signature of functionName(event,data){}, in 6.0 all of our ClientSide Eventing broke because 'data' was undefined. I removed the 'event' from the method signature, so it looked like functionName(data){} and everything is working again.

Blaine

Blaine Boule
7/28/10 11:32 AM

Update to my post. I looked into the JS of 6.x and they have depricated the use of Liferay.bind() and Liferay.trigger() - they will be present for now, but removed in later versions. They have been replaced with Liferay.on() and Liferay.fire()

Check out: http://issues.liferay.com/browse/LPS-2155

Long N.
8/5/11 7:03 AM

Blaine,
Please post some example code.

Thanks,

Keyur Ashra
2/21/12 11:02 PM

I want to use IPC with Ajax. I am having 2 Portlets (MVC Portlet). For eg. Portelt A has 1 textbox through which you can serach something from DB. And in Portlet B I wish to display the Data. I achieve this functionality without AJAX, but as it is refreshing the page every time i wish to do it through AJAX now. Can anyone please kindly help me in this,......