DevCon 2013: Liferay WCM Apps and an Expando Browser

At Liferay's first ever Developer Conference in Berlin last month, I had the privilege to present my experiences in building apps with Liferay WCM (similar to the techniques discussed in a prior blog post, and this one too). I make use of this feature to quickly build relatively simple apps for our community, saving a lot of time during development and deployment. At DevCon, I demoed a bunch of simple apps, building up to a more complex app used to browse and modify Expando values in Liferay's Expando subsystem (this post is not about Expando specifically; for more details about Expando, check out Ray's excellent series of blog posts [1][2][3]).

In this post you will find the code from the examples shown at Liferay DevCon 2013, along with additional detail about the code above and beyond the amount I was able to squeeze in 30 minutes.

New templating Features in 6.2

In Liferay 6.2 there are a number of new features that enhance this, both in terms of security and development ease, and I briefly touched on them.

  • Enhanced Template Editor - no longer "just a text area", the new editor has automatic code completion, syntax highlighting, and more.
  • Freemarker Debugging in Liferay IDE - starting with Liferay IDE 2.x, you can will be able to do true debugging of Freemarker templates, e.g. stepping line-by-line, inspecting variables, etc.
  • Template Security Configuration - In prior versions it was possible for one to write a poorly- or maliciously-constructed web content template that could do anything its author desired. In 6.2, the default configuration now restricts that using portal properties like freemarker.engine.restricted.classes. To undo all of this (with an understanding of the consequences), you can add this to your portal-ext.properties:
velocity.engine.restricted.classes=
velocity.engine.restricted.variables=
freemarker.engine.restricted.classes=
freemarker.engine.restricted.variables=

You can also fine-tune the above to restrict it down to just the stuff you need/want (or that your web content template developers need/want). For the below examples, the only ones that do not work without this are examples 8 and 9, as they use a Class.forName as an example (which is not needed with Freemarker, see below). Other examples should work without any extra configuration.

Examples from DevCon

The examples I showed were done so in a particular order, from super-simple to more complex, in order to demonstrate some of the important concepts one needs to know to build apps with WCM. So I will repeat this here, using snippets of code with explanation below each.

All of these apps are written in Velocity, however I have started using Freemarker for new stuff, due to its (what I believe to be) superior feature set (like a ton of cool builtins, sophisticated macro features, stronger typing, no silent failures, and more). These apps are all simple enough to be easily converted to Freemarker, but I have not yet done so. It would be a good exercise for Velocity users who may be looking to pick up Freemarker skills. Also, for Freemarker and Liferay fans, check out Andreas Magnusson's awesome solution for using Freemarker to render true portlet views, with storage and versioning of templates provided by Liferay's Document Library.

Each app consists of a Liferay WCM Template, which is used to render a WCM Article based on a WCM Structure. Since the point of this is to demonstrate features using the templating bit, the structure is not important, and a 'dummy' one is used. However, structures with multiple fields can be used to essentially provide configuration options for the app if so desired.

To use these examples, simply create a WCM Structure with one or more fields, then create a template associated with the structure using the example code, and then create an article based on the structure, and add it to a Liferay page.

Example 1: The Hello World app

About as simple as you can get. It shows you how many users are registered on the Liferay instance on which it runs. It also shows how to access structure field values (e.g. if you wanted users of your 'app' to be able to provide customized values for one or more features in your app, using WCM structures)

Example 2: A sucky Hello World app

This is the same app, but with horrible performance, showing what happens when an article is rendered that has a lot of code or long-running process in it. Don't do this.

Example 2a: An improved Hello World app

Here we show how the Portlet lifecycle is exposed when writing WCM templates. As Liferay WCM apps are run in the context of a portlet (the "Web Content Display" portlet or "Asset Publisher" if you use that), you can separate code to run during the RENDER_PHASE of the portlet (when the browser is requesting the page) from the code that runs during the RESOURCE_PHASE, a JSR 286-defined lifecycle event that lets you call back into the template to do asynchronous processing (not synchronous with the RENDER_PHASE) and return stuff, separate from the rendering of the app/portlet itself.

Example 3: Passing parameters to the RESOURCE_PHASE via an HTML form

Here we show how to pass parameters into the RESOURCE_PHASE and access them using the $request variable. Handy for lots of things, but still forces a full page refresh on form submittal (hello, 1998).

Example 4: AJAXification with AUI and JSON

Here we show how to use AlloyUI's built-in AJAX utility to periodically (every second) call the app's RESOURCE_PHASE to get stuff. The 'stuff' in this case is a JSON payload which is then parsed by the app and displayed in the browser. Good times ahead (note there is no parameter passing - that's example 6).

Example 5: Same thing, but using jsonFactoryUtil to properly create and emit JSON objects

The point of this example is that it is difficult and unnecessary to form a JSON string using the templating language. Instead, we use Liferay's built-in jsonFactoryUtil to construct a proper JSON object, fill it with data, then emit it using its toString(), thereby not having to worry about escaping special characters or that we possibly left off a { or a ] or a ,.

Example 6: Same thing as example 5, but passing parameters via AlloyUI and accessing in the RESOURCE_PHASE

Not terribly interesting, we pass parameters during the AJAX call to the app's RESOURCE_PHASE, and use the values to construct a JSON object which is returned and displayed. Note the use of the ${pns} (Portlet Namespace) variable - this is required as of 6.2 to ensure all parameters are namespaced to avoid conflicts from other instances of the same app on the same page. See LPS-39748 for details, and also note that when we pass the parameters we prefix the names with ${pns} but when accessing from code in the RESOURCE_PHASE, we just use the name of the variable without the prefix.

Example 7: Intermission

In this example we illustrate how to asynchronously call Liferay services (like UserLocalService) using a client-side JavaScript library included out of the box, rather than having to do it yourself via an AJAX request to your own code. All of Liferay's services are exposed in this way, as are any custom JSON Web Services you may have running on Liferay. Whee!!

Example 8: Expando Basics

In this example we demonstrate how to use Liferay's Expando services to create new data schemas and get/set data using according to that schema. From templates, you have access to services for creating new tables, rows, columns, and data (e.g. $expandoTableLocalService.getExpandoTable()). In this example, we use it to create a new Expando table, populate it with some fake data, and echo that back, all done in the RENDER_PHASE for brevity.

One thing you see in this example is some complaining about Velocity vs. Freemarker. Since Velocity has no way to natively access static member variables of a Java Class, I had to resort to an ugly workaround. With Freemarker, it's much simpler (see commented-out code). A note for Velocity fans, the Velocity Tools project does include a utility method to access statics, but Liferay does not include this out of the box (I dunno why). We do with Freemarker though (via the staticUtil object).

Example 9: Expando Basics, but slightly better

Same as example 8, but here we are using an AJAX call to the RESOURCE_PHASE of the app, and $jsonFactoryUtil to properly construct the response. The same fake data is created each time the RESOURCE_PHASE is accessed.

Example 10: The complete meal

In most cooking shows, they skip the part of meal preparation where the meal is cooking in the oven for an hour. They instead show you the ingredients, a few basics about how the meal is constructed, then walk over to the oven and pull out the complete dish, and the audience oohs and aahs.

Well, here we are doing the same thing. We put the concepts of Liferay WCM (for templating), accessing Liferay Services like UserService and ExpandoService, AlloyUI (for AJAX calls and a fancy DataTable), parameter passing, JSON object construction, and whip it all together into a single app which is basically a glorified Database browser and editor, but built on top of Liferay Expando. If you use Expando a lot, and are using a traditional RDBMS browser to inspect values, then this final example may be of use to you!

Here's what it looks like when browsing some sample data:

And here's the code

This 'app' renders a few drop-down selectors, and to use this app, do this:

  1. Select one of the Classes from the first dropdown (this list is generated through an AJAX call to the RESOURCE_PHASE, retrieving all possible class names that have one or more Expando Tables associated with it). To understand the relationship between Expando classes and tables, read the wiki page.
  2. After selecting a class, select an Expando Table that is associated with the class (this list is generated the same way as #1 is).
  3. Once a class and table has been selected, an AUI DataTable will be displayed containing all of the rows and columns of the selected Expando Table (again, using an AJAX call to retrieve the data).
  4. If you want to change one of the values, double-click on it, make the change, and click Save. The value will be stored via another AJAX call back to the RESOURCE_PHASE, passing the identification and value of the edited cell, which is used to update the data in the Expando Table.

Because it shows all tables and all classes, you can even see the values for a User's "Custom Fields" (a feature in Liferay). For example, if you go to Control Panel->Custom Fields and create a new field for Users (e.g. "Favorite Color"), and then visit "My Account" and give yourself a value for that field, you can then see that entry in the com.liferay.portal.User / CUSTOM_FIELDS Expando table. Editing that value will change the resulting value for the user, if you re-visit your "My Account" page you will see this in action. Good times indeed.

Liferay Client-side IPC

You may also notice a Google map in the above screenshot. This was a simple demo of Liferay's built-in client-side IPC, which is just a glorified messaging using JavaScript, firing an event with Liferay.fire(eventName, payload) and receiving it in the other portlet with Liferay.on(event). In this example, I am sending data from the clicked row, and displaying the results (the row contains a latitude, longitude, name, pic, etc).

Magic Upload Button

One other tidbit I showed at DevCon - I was using IntelliJ IDEA to show these example templates, and then clicking a magic button in IDEA to cause the template to be immediately updated in Liferay (vs. the old school way of copy/pasting it into Liferay's built-in template editor). Several people wondered about this magic.

There were actually two buttons: one to download all of the WCM templates on a given Site into a directory (the filenames are then the same as the templateId's of the templates on the site). And then another to upload the currently open template back onto the site, overwriting any other templates that may have been found. Yes, this is a big time hack to make it easier to quickly build apps, and could be improved to handle multiple templates. The files are renamed to have a .vm extension, so that IDEA recognizes it as a Velocity template file.

These button were linked to an IDEA External Tool which is a just a convenient way to execute an OS-level command (e.g. a bash script or any other executable) with textual substitution of the path of the file being edited, the root directory of the project, etc. So I had pre-created two "External Tool" buttons which, when I clicked that button, it called one of my custom scripts.

So, if I clicked the Download button, it would end up calling:

/bin/bash get-templates.sh

This script contained:

You can see I hard-coded several things (including the name of the subdirectory containing the downloaded templates, the hostname/port, and the name of the Site from which to download ("guest")). It simply downloads every template into a directory, and renames the file from [number] to [number].vm so that IDEA would "know" it was a Velocity template.

If I clicked the upload button, it would call:

/bin/bash put-templates.sh [path-of-file-being-edited]

This script simply uploaded the currently opened template on top of any other template, based on the filenames contained in the subdirectory:

Both of these scripts relied on a tool called cadaver which is a WebDAV command like client for Mac OS X. The only reason I had to use this is because on Mac OS X, read/write WebDAV is not supported (thanks to Mac OS X, not Liferay). On Windows, it is properly supported, and so you could simply mount the WebDAV directory and edit the files directly on disk, and each time you saved them, they would automatically be updated.

Summary

So, that's it for the demos. The point of the presentation was that Liferay WCM is a good tool for rapidly prototyping (eventually converting to a 'real' app), and a quick and easy way to build simple "apps" on Liferay, without the need to deploy portlets (e.g. no IT needed), but it is NOT a substitution for java or true enterprise app development, and there are performance and security concerns that one must understand and accept. But it sure is fun!

Blogs
Thanks a lot for putting this code online. IT was a great session at DevCon!
Thanks Wouter! Let me know if you have any follow-up questions. One thing I forgot to add, there are several improvements that could be made to the Expando browser, such as handling field types like dates, etc, and configuring the AUI datatable to use a cool date picker when double-clicking on dates. Or, providing better "helper links" for other types of objects that may be in Expando. Cheers!
Hi, this is very good\interesting information. Well done. I tried a simple example and got the render\response working with one issue. When using "<form action="${request['resource-url']}" method="POST">" the resource-phase results get rendered on a blank page (not in the portlet). The resource-url seems to be formed properly. Any ideas on how to fix that?

Second question; when doing a parameters and response sort of solution, would you be better off using two portlets (web-content-display in my case) and establishing some sort of communication between them? Don't know if that makes things harder or easier or if even feasible but may add extra flexibility\reusability (something like what CatNav and AssetPub do).
Skip the first one, (it's probably in your stuff above; was intimidated by volume of code); got something rough working with a bit of AJAX and checking request lifecycle..
@s s - you could do it with two portlets, but I don't think you could use traditional portlet IPC eventing or render parameter mechanisms to do it. You'd have to do it with a client-side solution (using Liferay.fire() and Liferay.on() APIs or perhaps something more robust like OpenAJAX pub/sub). So yeah it would probably add some reusability! Good question!