Forums

Home » Liferay Portal » English » 3. Development

Combination View Flat View Tree View
Threads [ Previous | Next ]
toggle
Troy Griffitts
Liferay javascript API Primer
February 2, 2012 11:18 AM
Answer

Troy Griffitts

Rank: New Member

Posts: 16

Join Date: August 6, 2011

Recent Posts

This post began as a frustrated list of questions about using the javascript API from within a Liferay portlet/gadget. It has eventually evolved into a guide through what I've found helpful with using the Liferay Service API from javascript.
________________________


LIFERAY WEB SERVICE API BROWSER

I love so many things about the latest version of Liferay (6.1), but one of the beautiful things for web developers is the cool, new json API browser at:

http://localhost:8080/api/jsonws/
(obviously change localhost and port to point your Liferay instance if not running at localhost:8080)

This browser will let you navigate through each of the API calls exposed for use directly as ajax requests and also wrapped in a nice javascript API available from your portlets or gadgets.


SIMPLE JAVASCRIPT EXAMPLE

In our example which follows, we will work from calling a single, simple-type parameter method (to get a list of Message Board Categories for a site), to calling a more complex multi-parameter method which requires a complex object parameter type (creating a new Message Board Category for a site).

I'm doing this from an OpenSocial Gadget, but this should be identical or very similar from a portlet or any other javascript added to a Liferay page. Obviously when using these methods from an OpenSocial Gadget, your gadget will be tied to Liferay for at least this functionality. Life goes on; so users outside of Liferay won't get Message Board integration features from your gadget.

So, looking at the API for MBCategory.getCategories (single groupId parameter):

http://localhost:8080/api/jsonws?signature=/mbcategory/get-categories-1-groupId

Pretty basic. It takes a 'long' and returns a list of categories. Great. Here's the magic incantation:

1    var categories = parent.Liferay.Service.MB.MBCategory.getCategories({
2        groupID : parent.Liferay.ThemeDisplay.getScopeGroupId()
3    });

Since we are executing this code from within an OpenSocial Gadget, our 'parent' is the portal page. From this page we can get our context. Liferay.ThemeDisplay allows us to discover what scope we are running in, and Liferay.Service is the root of all of our Liferay Service API calls.

This gives us a lovely array of the existing Message Board Categories for my site context (represented by groupId).
Great!


COMPLEX JAVASCRIPT EXAMPLE

Now, onward from this single long parameter method to our lovely multiple parameter whopper addCategory...

http://localhost:8080/api/jsonws?signature=/mbcategory/add-category-22-parentCategoryId-name-description-displayStyle-emailAddress-inProtocol-inServerName-inServerPort-inUseSSL-inUserName-inPassword-inReadInterval-outEmailAddress-outCustom-outServerName-outServerPort-outUseSSL-outUserName-outPassword-mailingListActive-allowAnonymousEmail-serviceContext

It looks like we have a million parameters to pass (well 22) and some of them are not simple types...

Using the lovely generated POST form which the API browser gives us, we can experiement before we dive into our javascript. We need to populate each integer field in the form, otherwise we'll get this form validation error:

{"exception":"Unable to convert value to type: long : jodd.typeconverter.TypeConversionException: Unable to convert value: ; <--- java.lang.NumberFormatException: For input string: \"\""}

OK, fine, simply put a 0 in for each integer param, plus the real data: a category name and description, and whalllaaah: a new category!

Because we couldn't provide the groupId context on the generated form in the API browser, our newly created category became a Global Scope Message Board Category (confirm?).

So, we're off to write our javascript....

NOTE: if you forget any of the parameters, get their names wrong, or supply the wrong data type, you will get a suspicious exception when calling the method:

Uncaught SyntaxError: Unexpected token )

Don't be misled by the error message. The 'SyntaxError' is a runtime exception thrown by Liferay's ajax service while trying to convert your parameters to the payload necessary for the ajax call. It's not your "SyntaxError", but instead this means you have your parameters incorrect.

OK, so the one parameter which we couldn't pass on the auto-generated form for 'addCategory' is the 'serviceContext' parameter. What in the world is this, and how in the world do we construct this complex object type?! Well thankfully, Liferay has excellent helpful employees/developers who hang out on:

irc.freenode.net
#liferay

And with helpful advice from @rotty3000, we learn how to construct this final parameter and tack it on to the end of our params variable like so:

1...,
2    serviceContext : JSON.stringify({
3        scopeGroupId           : parent.Liferay.ThemeDisplay.getScopeGroupId(),
4        userId                 : parent.Liferay.ThemeDisplay.getUserId(),
5        addGroupPermissions    : false,
6        addGuestPermissions    : false
7    })
8...,

NOTE: the JSON.stringify, and also that we are using the parent.Liferay.ThemeDisplay object to get the necessary context to populate the values.

So, let's put our entire call together with our 22 parameters:

 1    var params = {
 2        parentCategoryId       : 0,
 3        name                   : 'My New Category',
 4        description            : 'Really Cool New Category',
 5        displayStyle           : 'default',
 6        emailAddress           : '',
 7        inProtocol             : '',
 8        inServerName           : '',
 9        inServerPort           : 0,
10        inUseSSL               : false,
11        inUserName             : '',
12        inPassword             : '',
13        inReadInterval         : 0,
14        outEmailAddress        : '',
15        outCustom              : false,
16        outServerName          : '',
17        outServerPort          : 0,
18        outUseSSL              : false,
19        outUserName            : '',
20        outPassword            : '',
21        mailingListActive      : false,
22        allowAnonymousEmail    : false,
23
24        serviceContext         : JSON.stringify({
25            scopeGroupId           : parent.Liferay.ThemeDisplay.getScopeGroupId(),
26            userId                 : parent.Liferay.ThemeDisplay.getUserId(),
27            addGroupPermissions    : false,
28            addGuestPermissions    : false
29        }),
30    };
31    var newCategory = parent.Liferay.Service.MB.MBCategory.addCategory(params);

This adds a new Message Board Category to the Liferay portal for our site scope, and now our newCategory variable contains our newly created category!!!
Hurray!


SYNCHRONOUS vs. ASYNCHRONOUS

The Liferay javascript API methods all allow 2 parameters: (params[, callback]). We have only been passing params. This tells the Liferay javascript service to execute the API call synchronously. This means that we will wait for the method to execute and the result from the execution will be returned to us (into our newCategory variable, above).

If we pass a callback function, this tells Liferay to execute the API call asynchronously. This means that we will not wait for the execution to complete, but our thread will continue to execute happily on the next line immediately. This also means that NOTHING will EVER be returned to us from the API method call; it cannot-- we just said, "Don't wait for completion, just launch the request and keep moving along in my code." The result from the execution of the method will INSTEAD be passed to your callback method:

1    var result = parent.Liferay.Service.MB.MBCategory.addCategory(params, function(newCategory) {
2        doSomethingWith(newCategory);
3    });

Here, 'result' will be undefined, our thead will continue immediately to our next line of code, and when the API method is finally finished executing, our inline function will be called and passed our new category.


OVERLOADED METHODS

Many of the Liferay API methods are overloaded. If we wish to call any of the overloaded API methods other than the 'first' listed signature, we are required to pass a new parameter: serviceParameterTypes. For example, MBCategory.getCategories is overloaded with a signature that allows one to retrieve only the sub-categories of a parent category. If one wishes to call this signature of the overloaded getCategories method, one would need to supply the signature parameters with serviceParameterTypes-- to disambiguate which overloaded method to call, as follows:

1    var params = {
2        groupId                  : parent.Liferay.ThemeDisplay.getScopeGroupId(),
3        parentCategoryId         : parentCategoryId,
4        start                    : -1,
5        end                      : -1,
6        serviceParameterTypes    : JSON.stringify([ 'long', 'long', 'int', 'int' ])
7    };
8    var categories = parent.Liferay.Service.MB.MBCategory.getCategories(params);

The components of the serviceParameterTypes can be taken directly from the Liferay API Browser help page and should be quoted exactly as they appear there.

So, I hope this is helpful to some people.

Thanks so much Lifefray for the help on IRC and for the great product!

Troy A. Griffitts
CrossWire Bible Society
http://crosswire.org
David H Nebinger
RE: Liferay javascript API Primer
February 2, 2012 1:36 PM
Answer

David H Nebinger

Rank: Liferay Legend

Posts: 7157

Join Date: September 1, 2006

Recent Posts

Great analysis and writeup. Thanks for sharing!
Gwowen Fu
RE: Liferay javascript API Primer
September 4, 2012 2:43 PM
Answer

Gwowen Fu

Rank: Expert

Posts: 277

Join Date: December 27, 2010

Recent Posts

Troy Griffitts:

1    var categories = parent.Liferay.Service.MB.MBCategory.getCategories({
2        groupID : parent.Liferay.ThemeDisplay.getScopeGroupId()
3    });



Hi Troy,

Does this code work for CE 6.1? I tested and "Liferay.Service.MB" is undefined.

Regards,
Gwowen