Liferay

Material Theme for Liferay

So yes, we’ve been busy and made a new theme called Material Theme for Liferay. You can take a wild guess what inspired us but in reality it’s just a simple Google Material Design based theme. I really love their design.

I don’t have time to write up a lot of marketing BS at this point so just look at the damn screenshots and admit it’s just what you have been looking for: Liferay Material Design theme.

 

screen5 screen4
screen2 screen3

Liferay Business Theme

Business Liferay Theme

Yes, we’ve done it again. This time we’ve made a Business Theme for Liferay. If you need a new theme or setup a site with Liferay then please check out one of our themes and give it a try – we won’t disappoint. This theme is fully customizable, you can setup the background images, adjust colors, fonts, sizes, page layouts etc.

You can get it from Liferay Marketplace

product_business_theme

All our themes offer Unlimited colors not specific color-schemes like most liferay themes because we feel that most of the time “Blue” is not quite the same as the blue in your company logo and we don’t want to compromise. Naturally you also get Responsive design that works on All modern browsers and super-great support – If you have a problem with our product then we will fix it.

Company Theme for Liferay

Theme for Liferay

Our code-monkeys have been hard at work lately and we’ve released a new theme called Company Theme for Liferay. If you need a new theme or setup a site with Liferay then please check out one of our themes and give it a try – we won’t disappoint. This theme is fully customizable, you can setup the background images, adjust colors, fonts, sizes, page layouts etc.

With this theme we feel that we have learned a few new tricks and maybe we should start building more themes for Liferay. Maybe it’s time Liferay had equally good quality and flexibility in themes as WordPress?

It’s available on Liferay Marketplace and you can have it NOW.
product_company_theme

All our themes offer Unlimited colors not specific color-schemes like most liferay themes because we feel that most of the time “Blue” is not quite the same as the blue in your company logo and we don’t want to compromise.

Naturally you also get Responsive design that works on All modern browsers and super-great support – If you have a problem with our product then we will fix it.

Liferay Company Theme Preview

Liferay Birthday List portlet

cake

Just wanted to let you guys know that our very basic version of Liferay Birthday List portlet is now available for FREE on Liferay Marketplace.

You can download and install it straight from the Marketplace.
liferay birthday list portlet

The portlet relies on the Liferay’s internal user database and displays the birthdays of Yesterdays, Todays and Tomorrows birthdays.

Can’t really say that’s an achievement, but at least it’s our first Portlet on the Marketplace.
We plan to start publishing many useful Portlets to make the Liferay Ecosystem a little bit friendlier to people who don’t have development teams just hanging around, waiting for work…

We will probably publish a more feature-rich Liferay Birthday List portlet that would also show Work Anniversaries and allow sending quick Congratulations using Liferay Notifications.

New Liferay Layout Templates

Today we submitted 6 new Liferay Layout Templates. Although they are not very original they still fill the void when someone is missing a 15 / 85 or 25 / 75 or 40 / 60 ratio 2 column basic template.

At the moment of writing this they are not approved yet but we hope everything will go smoothly and you’ll be able to get them from Liferay Marketplace very soon!

Here’s a couple of screenshots of 15/85 and 85/15 Basic layout for extra thin portlets. 40/60 and 60/40 Basic Layouts for when you need just a little bit of extra space.

85-15

60-40

40-60

15-85

Basic 25 / 75 ratio layout that uses Liferay 6.2 built in span3 span9 ratios. Real-world percentages will depend on the theme and window sizes.

75-25

25-75

Liferay allPortletsReady event not firing

Today I stumbled upon a problem where I noticed that on some of my pages my portlet was acting weird and the culprit of the problem was the Liferay specific javascript “allPortletsReady” event not firing. I was using the latest version of Liferay 6.2 CE.

So I debugged and Googled around and went through a pretty large array of possible problems for why the allPortletsReady was not working for other people.

Some results suggested that the Related Assets portlet might cause the problem – That’s not the case this time though as I wasn’t using that portlet on my pages.

Next up on the list is Notifications Portlet and sure enough I had no Notification Portlet on those pages either.

Finding no obvious solutions using quick searches on Google and StackOverflow I decided to start testing and debugging. So I created a new clean page and added my portlet on that one. This time the portlet had it’s “allPortletsReady” event nicely firing so I now know that my portlet works on new and clean pages.

Then I deleted all portlets, including my own, from an old page where the portlet was not getting it’s allPortletsReady Javascript event called and then I added my portlet back in there. This time the Javascript event remained unfired and problem was still around the corner.

So by now we know that it works on clean new pages and does not work on old pages even after removing all portlets. I even tried switching themes which had no result.

Liferay allPortletsReady Solution (One of many)

Finally it dawned upon me – there has to be a difference on the pages between the new and old page – I checked the page’s embedded portlets section and discovered that even though I no longer had a Login portlet on those pages, they still had Liferay built-in Login portlet ID 58 listed there with some preferences saved. I removed those preferences and sure enough the allPortletsReady started firing again as normal.

Here’s a screenshot of the embedded portles section (I already removed the Login portlet from the list):
Liferay page embedded portlets

So it seems Liferay has some kind of an issue with old embedded Portlet preferences sticking around and causing issues with our Javascript events.


Solution: Remove old unused embedded portlet preferences from the page.

If you have a question or having trouble getting this thing working then feel free to write me a comment and I’ll do my best to help you out. If you would like to read more about Liferay related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!

Liferay Custom Notifications

Java tutorial on how to handle Liferay custom notifications. There is very little good information on this topic but I’ve seen a good need for a proper documentation so here’s a nice example of how to handle Liferay User Notifications based on Liferay 6.2 Notifications API. The official documentation on Liferay notifications is rather weak but then again the feature is also rather new and it’s not even 100% part of the core Liferay experience. Liferay 6.2 notifications have improved quite well and I feel it’s time to make them a first-class feature.

If you download the Liferay 6.2 bundle with Tomcat then you have the Notifications portlet installed already. If you installed Liferay on your own Tomcat or other Application Server using the WAR installation method then you need to install Notifications Portlet from the Marketplace.
Here’s a link to the Notifications CE Portlet on Liferay Marketplace.

Liferay custom notifications require a User Notification Handler class that turns the notification into a nice HTML fragment that is displayed to the user. Also Liferay wants us to define the notification types that our portlet or application is creating in a definition file upfront.

So the first thing we need to do is to define the location of our class that extends BaseUserNotificationHandler class and definitions inside liferay-portlet.xml. This can be done in liferay-portlet.xml like this:


<portlet>
    <portlet-name>example</portlet-name>
    <icon>/icon.png</icon>
    <user-notification-definitions>example-user-notification-definitions.xml</user-notification-definitions>
    <user-notification-handler-class>com.example.notifications.ExampleUserNotificationHandler</user-notification-handler-class>		
</portlet>

The important part is user-notification-definitions and user-notification-handler-class tags. Now we need to actually define our notification inside that example-user-notification-definitions.xml. This file should go into your resources folder. Since I’m using a maven based project then this file goes into src/main/resources/:

location of example-user-notification-definitions.xml

If you are running an Ant based project then you have to place this file inside docroot/WEB-INF/src/ folder.

Here’s the example-user-notification-definitions.xml I created:


<?xml version="1.0"?>
<!DOCTYPE user-notification-definitions PUBLIC "-//Liferay//DTD User Notification Definitions 6.2.0//EN" "http://www.liferay.com/dtd/liferay-user-notification-definitions_6_2_0.dtd">
<user-notification-definitions>
	<definition>
		<notification-type>${com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID}</notification-type>
		<description>receive-a-notification-when-example-triggered</description>
		<delivery-type>
			<name>email</name>
			<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_EMAIL}</type>
			<default>false</default>
			<modifiable>true</modifiable>
		</delivery-type>
		<delivery-type>
			<name>website</name>
			<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_WEBSITE}</type>
			<default>true</default>
			<modifiable>true</modifiable>
		</delivery-type>
	</definition>
</user-notification-definitions>

Notice how we are using some public final Strings from class files like this: ${com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID}. This allows us to define our Portlet ID in one location and keep things a little bit simpler.
Ok, so now we have our example defined and we can actually Add a new notification for the user. We can do so by using the built-in UserNotificationEventLocalServiceUtil in either our Service Builder class or one of our portlet classes.

Here’s how to add a new notification event:

    JSONObject payloadJSON = JSONFactoryUtil.createJSONObject();
    payloadJSON.put("userId", user.getUserId());
    payloadJSON.put("yourCustomEntityId", exampleEntity.getEntityId());
    payloadJSON.put("additionalData", "Your notification was added!");

    UserNotificationEventLocalServiceUtil.addUserNotificationEvent(user.getUserId(), 
		com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID, 
		(new Date()).getTime(),
		user.getUserId(),
		payloadJSON.toString(),
		false, serviceContext);		

If you are doing this inside a portlet then you need the serviceContext, you can get one like this:

ServiceContext serviceContext = ServiceContextFactory.getInstance(portletRequest);

So now that we have our notification inside the database we want to actually show it to the user. When we look at the dockbar our notification number gets increased so we can be pretty sure that the notification was added. When you attempt to actually look at the notification you will probably see a blank white box. We need the notification handler class to actually handle the display side of our notification. This current example also handles the behavior like providing Approve and Reject buttons right in our example notification so that our example user could perform an action straight from the dockbar notification and not waste extra time when it’s a trivial task. I don’t have the correct screenshot but the look we are going after is similar to that you can see on this picture:
liferay custom notifications

Basic idea being we have an Approve and Reject buttons on our notification, so that you can click on the notification or it’s background to go and View more details, but you can also directly perform some custom actions like Approve or Reject in this Liferay custom notification tutorial. The buttons could also be “Report as Spam” or “Notify the Security”. It’s all up to you where that link takes the user.
So here’s our handler class:


import javax.portlet.ActionRequest;
import javax.portlet.PortletURL;
import javax.portlet.WindowState;

import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.notifications.BaseUserNotificationHandler;
import com.liferay.portal.kernel.portlet.LiferayPortletResponse;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.model.UserNotificationEvent;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.service.UserNotificationEventLocalServiceUtil;

public class ExampleUserNotificationHandler extends
		BaseUserNotificationHandler {
	
	public static final String PORTLET_ID = "example_WAR_exampleportlet";
	
	public ExampleUserNotificationHandler() {

		setPortletId(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);

	}

	@Override
	protected String getBody(UserNotificationEvent userNotificationEvent,
			ServiceContext serviceContext) throws Exception {

		JSONObject jsonObject = JSONFactoryUtil
				.createJSONObject(userNotificationEvent.getPayload());

		long yourCustomEntityId = jsonObject
				.getLong("yourCustomEntityId");

		String title = "<strong>Example notification for entity ID "
				+ yourCustomEntityId
				+ "</strong>";

		String bodyText = "Some other text.";

		LiferayPortletResponse liferayPortletResponse = serviceContext
				.getLiferayPortletResponse();

		PortletURL confirmURL = liferayPortletResponse.createActionURL(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);

		confirmURL.setParameter(ActionRequest.ACTION_NAME, "doSomethingGood");
		confirmURL.setParameter("redirect", serviceContext.getLayoutFullURL());
		confirmURL.setParameter("yourCustomEntityId", String.valueOf(yourCustomEntityId));
		confirmURL.setParameter("userNotificationEventId", String.valueOf(userNotificationEvent.getUserNotificationEventId()));
		confirmURL.setWindowState(WindowState.NORMAL);

		PortletURL ignoreURL = liferayPortletResponse.createActionURL(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);
		ignoreURL.setParameter(ActionRequest.ACTION_NAME, "cancelForExample");
		ignoreURL.setParameter("redirect", serviceContext.getLayoutFullURL());
		ignoreURL.setParameter("yourCustomEntityId", String.valueOf(yourCustomEntityId));
		ignoreURL.setParameter("userNotificationEventId", String.valueOf(userNotificationEvent.getUserNotificationEventId()));
		ignoreURL.setWindowState(WindowState.NORMAL);

		String body = StringUtil.replace(getBodyTemplate(), new String[] {
				"[$CONFIRM$]", "[$CONFIRM_URL$]", "[$IGNORE$]",
				"[$IGNORE_URL$]", "[$TITLE$]", "[$BODY_TEXT$]" }, new String[] {
				serviceContext.translate("approve"), confirmURL.toString(),
				serviceContext.translate("reject"), ignoreURL.toString(),
				title, bodyText });
		
		return body;
	}

	@Override
	protected String getLink(UserNotificationEvent userNotificationEvent,
			ServiceContext serviceContext) throws Exception {

		JSONObject jsonObject = JSONFactoryUtil
				.createJSONObject(userNotificationEvent.getPayload());

		long yourCustomEntityId = jsonObject
				.getLong("yourCustomEntityId");
		
		LiferayPortletResponse liferayPortletResponse = serviceContext
				.getLiferayPortletResponse();		

		PortletURL viewURL = liferayPortletResponse.createActionURL(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);
		viewURL.setParameter(ActionRequest.ACTION_NAME, "showDetails");
		viewURL.setParameter("redirect", serviceContext.getLayoutFullURL());
		viewURL.setParameter("yourCustomEntityId", String.valueOf(yourCustomEntityId));
		viewURL.setParameter("userNotificationEventId", String.valueOf(userNotificationEvent.getUserNotificationEventId()));
		viewURL.setWindowState(WindowState.NORMAL);
		
		return viewURL.toString();
	}

	protected String getBodyTemplate() throws Exception {
		StringBundler sb = new StringBundler(5);
		sb.append("<div class=\"title\">[$TITLE$]</div><div ");
		sb.append("class=\"body\">[$BODY_TEXT$]<a class=\"btn btn-action ");
		sb.append("btn-success\" href=\"[$CONFIRM_URL$]\">[$CONFIRM$]</a>");
		sb.append("<a class=\"btn btn-action btn-warning\" href=\"");
		sb.append("[$IGNORE_URL$]\">[$IGNORE$]</a></div>");
		return sb.toString();
	}

}

NB Important Information: The com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID string that you use as your notification type has to match an actual portlet ID. It doesn’t actually need to be YOUR portlet ID but that would be the right thing to have there. The reason being that Notifications display portlet uses it to display a small portlet icon next to your notification to help the user identify the source of the notification. Providing a bad Portlet ID or something like null leads to a hard-to-trace NullPointerException in the JSP. Took me an hour to track it down.

The problematic part is this code from notifications-portlet view_entries.jsp source

<span class="portlet-icon">
<liferay-portlet:icon-portlet portlet="<%= PortletLocalServiceUtil.getPortletById(company.getCompanyId(), userNotificationEvent.getType()) %>" />
</span>

If you have a question or having trouble getting it working then feel free to write me a comment and I’ll do my best to help you out. If you would like to read more about Liferay related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!

Liferay portlet file upload tutorial

Here’s a Liferay portlet file upload tutorial to show you how to handle file uploading by clients.
First we need to define our upload form JSP. This can be shown using Liferay MVCPortlet’s default view.jsp.
We will define our upload form as an AlloyUI form element and w

view.jsp


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<portlet:defineObjects />

<portlet:actionURL name="upload" var="uploadFileURL"></portlet:actionURL>

<aui:form action="<%= uploadFileURL %>" enctype="multipart/form-data" method="post">

	<aui:input type="file" name="fileupload" />
	
	<aui:button name="Save" value="Save" type="submit" />

</aui:form>

And now we need to handle the upload action that we are calling by submiting this form.
Note some important parts that people often miss in their code, like checking if there’s enough room on the device to actually store the file and refuse additional file uploads when the device has less than 1GB of free space.

Full example class


import java.io.File;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;

import org.apache.commons.io.FileUtils;

import com.liferay.portal.kernel.upload.UploadPortletRequest;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.bridges.mvc.MVCPortlet;

public class RelatedFilesPortlet extends MVCPortlet {

	private final static int ONE_GB = 1073741824;
	
	private final static String baseDir = "/tmp/uploaded/";
	
	private final static String fileInputName = "fileupload";

	public void upload(ActionRequest request, ActionResponse response)
			throws Exception {

		UploadPortletRequest uploadRequest = PortalUtil.getUploadPortletRequest(request);

		long sizeInBytes = uploadRequest.getSize(fileInputName);

		if (uploadRequest.getSize(fileInputName) == 0) {
			throw new Exception("Received file is 0 bytes!");
		}

		// Get the uploaded file as a file.
		File uploadedFile = uploadRequest.getFile(fileInputName);

		String sourceFileName = uploadRequest.getFileName(fileInputName);

		
		// Where should we store this file?
		File folder = new File(baseDir);

		// Check minimum 1GB storage space to save new files...
		
		if (folder.getUsableSpace() < ONE_GB) {
			throw new Exception("Out of disk space!");
		}

		// This is our final file path.
		File filePath = new File(folder.getAbsolutePath() + File.separator + sourceFileName);

		// Move the existing temporary file to new location.
		FileUtils.copyFile(uploadedFile, filePath);
	}

}

Java tutorial: Add Portlet to Liferay Control Panel

You can add portlet to liferay control panel. For your custom control panel portlet you have to modify liferay-portlet.xml file and add 2 lines there:

<control-panel-entry-category>portal</control-panel-entry-category>
<control-panel-entry-weight>1.0</control-panel-entry-weight>

The control-panel-entry-category has to be one of “my”, “content”, “portal” or “server” and specifies under which category should the portlet be displayed.
The weight specifies the ordering or the elements under the different categories. It’s a double number, and the higher you specify the lower your portlet is placed. So if you want to appear in the bottom, add “99.0”.
The portlet has to be non-instanceable (the Control Panel may have only such portlets) so you also have to have this line:

<instanceable>false</instanceable>

Please note that Control Panel by default does not allow you to enter CONFIG mode of the portlet. For a work-around you can use

<preferences-unique-per-layout>false</preferences-unique-per-layout>

Full example liferay-portlet.xml

<portlet>
 <portlet-name>hello-world-portlet</portlet-name>
 <icon>/icon.png</icon>
 <control-panel-entry-category>portal</control-panel-entry-category>
 <control-panel-entry-weight>99.0</control-panel-entry-weight>
 <action-url-redirect>true</action-url-redirect>
 <instanceable>false</instanceable>
 <preferences-unique-per-layout>false</preferences-unique-per-layout>
</portlet>

add portlet to liferay control panel

Why use Liferay?

The Good side of Liferay

Liferay is NOT just a Java based CMS. It’s a complete portal environment that’s suitable for large enterprise usage. It should be used as a platform to develop in-house applications or intranet. If you need just a small website to promote something then Liferay is not for you.

Some people compare Liferay with PHP based CMS systems like Joomla or Wordpress but that’s completely wrong. Liferay does contain CMS and it’s not a bad one. It’s rather useful for providing small applications and even better for big applications.

Supporting JSR 168 and JSR 286 means you can deploy Portlet applications that others have developed. Liferay also has a very good support for groups and communities allowing you to build team sites and custom environments for different user-groups with ease. Some cool features that you get with Liferay out of the box are:

  • User and Group management
  • Multi-site support for having different pages and themes for different user groups
  • Proper Access rights and Role management
  • Internationalization
  • Document library management
  • Wiki, Blogging and Forums
  • Service Oriented Architecture
  • Theme support
  • Integrates with other existing systems using LDAP, RSS, iFrames and other technologies

Liferay

 

The Bad side of Liferay

Developing applications and portals for the first time is a taunting task and comes with a rather massive learning curve. If you plan to use Liferay Service Builder and do everything “the Liferay way” then you have to plan probably at least couple of weeks for learning it first.

If you need to develop a small application quickly AND you don’t have an existing Liferay installation already in-house then you can get your application done way faster by NOT using Liferay. But once you have learned Liferay and have enough need for proper portal then Liferay becomes rather irresistible.

When it comes to documentation and tutorials then it’s not the best situation either. It does have a massive developer’s guide. But the Wiki is filled with tons of content that’s rather old and not updated for the latest releases.

The official developer and administration guides are good but often lack information about some crucial parts or fail to provide decent examples. While they claim to be developer friendly I have to admit that some parts of the development platform is severely lacking. Also note that releases are rather infrequent. Also one should be very cautious because early versions are always rather buggy.

AlloyUI that’s provided with Liferay for Javascript and UI is rather difficult to learn and seems also a very large investment time-wise – save yourself the trouble and use jQuery or some other favorite JS library instead.