Building simple applications with Liferay WCM

Suppose that you, a portlet developer, need to develop a FAQ portlet that displays questions and answers and that allows certain users to create/update/delete FAQ entries, i.e., a simple CRUD application. Usually, the first thing that comes in mind is to implement a new Java-based portlet, actually, 2 portlets, one for displaying and a second for managing FAQ entries. Fortunately, Ray Augé showed me a different approach, there are some simple cases that can be solved only by using our Web Content framework, this way, each FAQ entry becomes a Web Content entry and for displaying them you can just create a Web Content Template containing a Velocity script.

The ability to create Velocity scripts has been in the portal for a long time but we made some enhancements for 5.3 that will make it easier to build such scripts. Note that some APIs used in this blog post are still in development and will be available only in 5.3. You can download all templates and structures used in this example here.

Portlet example built with Liferay WCM

Here's a screenshot of this portlet in action:
 


 

Each pair of question and answers are Web Content entries, and the template responsible for listing them is located at /templates/FAQ-PORTLET.vm in the zip file.

There are many advantages of building CRUD applications with Liferay WCM, as a developer you don't need to care implementing many features provided by Liferay WCM for free, such as:

1.  Creation, update and deletion of FAQ entries: since each FAQ entry is actually a Web Content entry, you can manage them from the Web Content portlet:




To edit an entry, you just need to fill the form, notice the category select box, FAQ-PORTLET.vm will filter entries accordingly:

 

 

FAQ entries could have other complex field types, such as images, files or any type available in Structures.

2. Versioning: you can have several versions of the same FAQ entry, you can see the history of changes and rollback to older versions if needed, just like any Web Content.

3. Approval workflow: content of each entry can be revised and approved by editors, FAQ entries will be displayed only when they are approved.

4. Tagging: you can aggregate FAQ entries with Asset Publisher portlet.

5. Export/import: you can backup or share FAQ entries with others by exporting and importing them to .lar files.

6. Staging and remote publishing: you can create FAQ entries in the staging community and publish them to the live community when they are ready, they can be also published to a remote Liferay instance.

7. Permissioning: you can define which FAQ entries an user will have permission to view.

7. Setup: Since the portlet that displays all FAQ entries is a Web Content itself with an associated Structure, you can take advantage of this to configure your portlet, in this example, there is a "show_index" option that will display or hide the questions index:


What's new in 5.3?

You might be thinking "OK, there is nothing new here, I know how to create Velocity templates and call Liferay services from them, what's new in 5.3?"

One of the problems we've found while creating these applications is that we can't query for Web Content fields separately. In the example, I needed to filter FAQ entries by their category field, however Web Contents are stored in the database like that:

<?xml version="1.0" encoding="UTF-8"?>

<root>
	<dynamic-element instance-id="uwYU33xF" name="question" type="text" index-type="text">
		<dynamic-content><![CDATA[What is Liferay Portal EE?]]></dynamic-content>
	</dynamic-element>
	<dynamic-element instance-id="rsTKdp5r" name="answer" type="text_area" index-type="text">
		<dynamic-content><![CDATA[<p>Liferay Portal Enterprise...</p>]]></dynamic-content>
	</dynamic-element>
	<dynamic-element instance-id="q7HOfdUk" name="category" type="list" index-type="keyword">
		<dynamic-content><![CDATA[ee_faq]]></dynamic-content>
	</dynamic-element>
</root>


As you can see, it would be hard to make a database query and fetch all FAQ entries associated to the "EE Edition" category, the table is not normalized. We fixed that by indexing Web Content fields separately in Lucene, now you have new options while creating a new Structure, you can define that a field will be indexed as a Keyword or Text by Lucene or won't be indexed at all:



In FAQ-PORTLET.vm we filter FAQ entries by the selected category:

#set ($clauses = [])
#set ($clause = $booleanClauseFactoryUtil.create("category", $selectedCategory, "MUST"))
#set ($void = $clauses.add($clause))
...
#set ($articleHits = $journalArticleService.search($companyId, ... $clauses, ...))

 

Advantages of creating CRUD applications with Liferay WCM

  • Faster coding, no need to deploy .wars, you can dynamically edit and save your velocity script, you'll see your changes right away.
  • Small footprint, it's not a regular portlet with tons of jars consuming memory, it will take longer processing the script but this can be avoided by caching entries.
  • You get all Liferay WCM features for free as I mentioned above, you just need to code a velocity script for displaying entries.

 

Drawbacks

  • Velocity scripts are harder to debug.
  • You can't instantiate Java objects, creation of objects can only be made by calling factories and static methods.
  • If your application has many views or many actions can be performed, your script will become very big, making it harder to maintain. You can't add business logic, there is no data validation.

 

Plans

There are many other useful applications that can be built with Liferay WCM, one that I can think of is Jobs Listing app, we'd like to hear from you which other apps could be useful.

Templates can be easily shared, we are planning to create a new type of plugin, something like a "Webscript" plugin type, a bundle would be basically a .lar file containing templates and structures that we could publish through our plugin installer portlet.

博客
Good write up. For what it's worth, we did our FAQ as a Message Board Category, but this would probably run a lot faster.
Hi Bruno,
Very interesting post, indeed.
Could you just elaborate a little more on what is specific to the 5.3 version and what could be done an 5.2.x ?
Thanks again.
Glad you guys liked it!

Bertrand,

That's the only restriction that I can think of: if for some reason you need to search for web content fields separately (in my case I needed to filter them by the category field), other than that, scripting 5.2.x is very powerful and you may find workarounds for this specific issue. You should give a try.

Remember that in order to reference $serviceLocator from vm scripts you need to remove it from this setting in portal.properties:

journal.template.velocity.restricted.variables=
Thanx Bruno. Something to look forward to!

Something that is also useful is the ability to add "next and previous" buttons, total number of results... In other words: custom paging based on fields. This seems however more something that should be added as feature to the web content list display instead of adding it to the templates functionality. What is missing there is the ability to select content based on the values of custom fields.

A rules engine for more advanced content selection seems a natural step...

These indexing of the fields should also definitely be picked by the search. Perhaps also add "searchable" checkbox. By checking the checkbox, this field can be used from the search which would detect all searchable fields and allow to search on these fields.

Anyhow: that's my wishlist. :-)
Petros,

The Web Content List allows pagination, I can see a preferences called "Display per page".

Paginating entries with VM templates is also easy, the search API allows us to pass start and end parameters, I have here some templates that paginates entries with "next" and "previous" links.

I personally prefer using VM templates for displaying entries because I have full control over the look and feel and there is no need to change jsps.

Perhaps I didn't understand your last comment... Check for the last screenshot in this blog post, you see the highlighted select box with "keyword"? Isn't it the same thing of the "searchable" checkbox you suggested?

These changes were committed today: http://issues.liferay.com/browse/LPS-5376
if writing a custom portlet in any other language besides Java w/velocity, what is the best way to piggy back on the instantiated sql class? Say for instance I am creating a custom portlet in PHP
Can i do validation of input fields of structures like the below while adding some text in structure.(client side validation)

"
<root>
<dynamic-element name='header' type='text_box'repeatable='false'></dynamic-element>
</root>
"
For 6.0+, use this FAQ-PORTLET.vm instead:

http://www.liferay.com/c/document_library/get_file?uuid=ba0bf122-9fe0-492a-8456-528445656b7f&groupId=11325
Hi Bruno,

Is the "Webscript" plugin you proposed to create as a repository for sample webcontents available now?

thx
Hi Bruno,

Thanks for this post. I am using v5.2 and I want to now how to display a list of web contents the way it appears on the blog aggregator portlet. Where the title and an intro text is displayed, with a link to 'read more'

Cheers
Great post! Is there an easy way to add validation? it would be fantastic if we could add attributes for validation to the dynamic-element tag, like so:

<dynamic-element name="foo" type="text" minLength="10" maxLength="200" />

etc.

Also, where can I find the descriptor files that these xsd structure files are already validated against?

thanks! emoticon
Hi Leitman did you manage to get to add the attributes? if yes would you mind providing me link to it? im on 5.2.3. Thanks.
On Liferay 6.0.3, these structures and templates do nothing. All that is displayed is "Enterprise Edition FAQ" in the h1 tag.
did you set property journal.template.velocity.restricted.variables under portal-ext.properties?
I am trying sample on Liferay 5.2.8 EE, it is not bringing Web Contents.

I did some investigation looks like following is not working

#set ($clause = $booleanClauseFactoryUtil.create("category", $selectedCategory, "MUST"))

here $clause is not initialized.

and due to that following statement is not bringing results

#set ($articleHits = $journalArticleService.search($companyId, $groupId, $userId, null, null, $clauses, null, -1, -1))

<p>$articleHits</p>


any suggestion?
Mitesh, i think you are not getting #booleanClauseFactoryUtil. Can you try to load them from spring-ext.xml as below.

<bean id="com.liferay.portal.kernel.search.BooleanClauseFactory" class="com.liferay.portal.search.generic.BooleanClauseFactoryImpl" />
Hi Bruno,

Great Article to reduce effort/time to create a CRUD portlet using Web Content+structure+template.
Where I get complete steps for Liferay 6.03 ,also I need to know can we manage pdf,doc,or xml conversion using this concept.

Thanks
Mr.Bhatia
Can someone please provide the link for the structures and templates since the link provided here is not working.

Also I have the same problem as Henry K with the latest Liferay version (6.0.5) and only the header is being displayed.
See my comment above, it has the correct link.

http://www.liferay.com/c/document_library/get_file?uuid=ba0bf122-9fe0-492a-8456-528445656b7f&groupId=11325
Hi thanks for the quick reply.

I am using the given VM but only the title "Enterprise Edition FAQ" is getting displayed even though I have 2 web contents assigned this template.

I think that I am not linking the web contents entries with the template correctly. Can you please specify how that is done.
Hi Rodrick,

I had the same issue and solved it as follows (using Liferay 6.0.5):
1. look up the structureId of the structure
2. put that in the macro where it says #set ($structure = $journalStructureService.getStructure($groupId, "FAQ")) > replace "FAQ" with your structureId

Hope that works for you as well.

Cheers,

Jan
I am getting the following error when trying to access this link - is there an updated/current link?
"""
Forbidden
You do not have permission to access the requested resource.
http://www.liferay.com/c/document_library/get_file?uuid=ba0bf122-9fe0-492a-8456-528445656b7f&groupId=11325
"""

Thanks!
Unfortunately, the link from "You can download all templates and structures used in this example here." generates the following error:
"""
Not Found
The requested resource was not found.
http://www.liferay.com/c/document_library/get_file?p_l_id=745616&folderId=4090923&name=DLFE-7602.zip
"""

Please update the links and permissions as this could be a very useful post.
I am also getting the following error while downloading the attachment.

Not Found
The requested resource was not found.
http://www.liferay.com/c/document_library/get_file?p_l_id=745616&folderId=409092­3&name=DLFE-7602.zip
Arun/Aaron
You can get the document by going to Bruno's public page, document link at top (next to profile, etc) and you will find it the zip file there.
Here is a link that might work as well
http://www.liferay.com/web/bruno.farache/documents?p_p_lifecycle=0&p_p_id=20&p_p_col_count=2&p_p_col_id=column-2&p_p_state=maximized&_20_struts_action=%2Fdocument_library%2Fview&p_p_mode=view&_20_folderId=4090923
Ibrahim
Like many windows applications having an embedded FileBrowser to pick a file to use in the application, how would you create a custom portlet that uses the existing WCM portlet as a means to create and choose a wcm entry to embed in the custom portlet. Any pointers would be greatly appreciated, since I don't want to recreate the entire functionality of the WCM portlet from scratch.
Hey Bruno ,
good article but unfortunately I am not able to make it work 100% some time I am only Q&A and some time only categories and the title "Enterprise Edition FAQ" is getting displayed.
What I have done is created 2 structures and 2 templates and added article. In this case I am just getting the title "Enterprise Edition FAQ" with categories.
Hi Bruno ,
can you please provide a valid link for For 6.0.5, FAQ-PORTLET.vm since the link you have provide below is not working.
http://www.liferay.com/c/document_library/get_file?uuid=ba0bf122-9fe0-49­2a-8456-528445656b7f&groupId=11325
Hi.

I have the same problem as Mitas has. The $clause seems like not a valid object.
#set ($clause = $booleanClauseFactoryUtil.create("category", $selectedCategory, "MUST"))

here $clause is not initialized.

and due to that following statement is not bringing results

#set ($articleHits = $journalArticleService.search($companyId, $groupId, $userId, null, null, $clauses, null, -1, -1))

<p>$articleHits</p>
Still clueless how to add maxlength via structure and tempalte in 6.0.5. Any help here?
Hi I am Fresher in liferay So i want to know about velocity script using structure format please If you have any tutorials please send for this mail id
sivachandran@formativesolutions.co.in