Tips & Tricks: include jsp into vm template

This tip&trick is dedicated to all developers who work with themes and vm’s. Probably you know that you have only little set of services from VelocityVariables class and some variables from init.vm. If you want call normal service method this is a huge problem. My question was: how can I include a jps file to vm file? Liferay has a lot of scriptlets and calls services from jsp. Why I can’t call it from VM? Many of you tell me: “I know how can you do it. Put this line of code and it’s ready”

#set ($categoriesService = $portal.getClass()
.forName("com.liferay.portlet.asset.service.AssetCategoryLocalServiceUtil")
.getMethod("getService", null).invoke(null, null))

My answer is: Yessss, it works. But I want use a jstl lib (e.g. <aui> or <ui>) as well. In my opinion the simpliest way to do it is include jsp file to themeServletContext. Put your file.jsp into your theme:
your-theme/docroot/path/to/your/jsp/file.jsp

and paste this piece of code into your vm (for example portal_normal.vm):

$theme.include($themeServletContext, "/path/to/your/jsp/file.jsp")

And enjoy all Liferay’s functionalities!

Advertisements

Tips & Tricks: add new structure’s field into web content

During my everyday work some people asked me: „Is it possible to add new type of structure’s field. If yes, how can I do it?”. In my opinion the fastest way to achieve this result is overriding few files in ext plugin or hook. We decided to use hook. In this post We show you the example, how we did it. Our new field is a datetime component.

Firstly, we should know in liferay there are two places where we can add and edit structure’s fields, So we must check our new functionality two times:

  1. CP -> Web content -> Structures (tab) -> Add or edit structure
  2. CP -> Web content -> add or edit web content -> change structure

Files which are responsible to render and serve structure’s fields in first point:

  • html/portlet/journal/js/main.js
  • html/portlet/journal/edit_article_content_xsd_el.jsp
  • html/portlet/journal/edit_structure_xsd_el.jsp

In second point:

  • html/portlet/journal/edit_article_structure extra.jspf
  • html/portlet/journal/edit_article_extra.jsp

Ok, let’s get started.

1. Create a new hook and configure that it can override jsps files. For example:

<hook>
<custom-jsp-dir>/custom_jsps</custom-jsp-dir>
</hook>

2. Copy html/portlet/journal/js/main.js into custom_jsps folder. In this step we must register new model’s field (below registerFieldModel method’s declaration).

registerFieldModel('Date', 'date', 'DateField', true);

Next we add new field’s type to map called „model” in function _fieldInstanceFactory()

var model = {
  'boolean': Journal.FieldModel.Boolean,
  'document_library': Journal.FieldModel.DocumentLibrary,
  'image': Journal.FieldModel.Image,
  'image_gallery': Journal.FieldModel.ImageGallery,
  'link_to_layout': Journal.FieldModel.LinkToPage,
  'list': Journal.FieldModel.List,
  'multi-list': Journal.FieldModel.MultiList,
  'selection_break': Journal.FieldModel.SelectionBreak,
  'text': Journal.FieldModel.Text,
  'text_area': Journal.FieldModel.TextArea,
  'text_box': Journal.FieldModel.TextBox,
  'date': Journal.FieldModel.Date
};

3. Copy html/portlet/journal/edit_article_content_xsd_el.jsp and add code inside journal-article-component-container. We compare our new type with elType variable.

<c:if test='<%= elType.equals("date") %>'>
  <%@ include file="/html/portlet/journal/edit_article_content_xsd_date_el.jspf" %>
</c:if>

Ok, it  looks fine, but what’s is in edit_article_content_xsd_date_el.jspf“? This is the main point of this tutorial. In this file we define „look and feel” our new component. For example:

<aui:fieldset>
<%
Calendar cal = CalendarFactoryUtil.getCalendar(timeZone, locale);
if (elContent != null && !elContent.equals("")) {
  DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd H:m");
  Date date = (Date)formatter.parse(elContent);
  cal.setTime(date);
}

%>

<aui:field-wrapper>
  <div>
    <liferay-ui:input-date
      dayParam="dateDay"
      dayValue="<%= cal.get(Calendar.DATE) %>"
      disabled="<%= false %>"
      firstDayOfWeek="<%= cal.getFirstDayOfWeek() - 1 %>"
      monthParam="dateMonth"
      monthValue="<%= cal.get(Calendar.MONTH) %>"
      yearParam="dateYear"
      yearValue="<%= cal.get(Calendar.YEAR) %>"
      yearRangeStart="<%= cal.get(Calendar.YEAR) %>"
      yearRangeEnd="<%= cal.get(Calendar.YEAR) + 5 %>"
    />

    &nbsp;

    <liferay-ui:input-time
      amPmParam="dateAmPm"
      amPmValue="<%= cal.get(Calendar.AM_PM) %>"
      hourParam="dateHour"
      hourValue="<%= cal.get(Calendar.HOUR) %>"
      minuteParam="dateMinute"
      minuteValue="<%= cal.get(Calendar.MINUTE) %>"
      minuteInterval="<%= 1 %>"
    />
  </div>
</aui:field-wrapper>
</aui:fieldset>

You must know that elContent variable contains our field’s value (date in this example).

4. Copy html/portlet/journal/edit_structure_xsd_el.jsp and add our new type into select’s list.

<option <%= elType.equals("date") ? "selected" : "" %> value="date"><liferay-ui:message key="date" /></option>

Okey, let’s deploy our hook and look into add structure form. What are we seeing? Oh, our new field’s type.
What’s next? We must allow to edit structure directly in edit article form.

5. Let’s look inside edit_article_structure_extra.jspf file and copy them. Then we define „look and feel” our new field inside journalFieldModelContainer div by adding new div with dataType=”<type>” attribute.

<div dataType="date">
<%
String elContent = "";
%>
<%@ include file="/html/portlet/journal/edit_article_content_xsd_date_el.jspf" %>
</div>

6. In file html/portlet/journal/edit_article_extra.jsp we add to menu (lists of field’s types) our new component because we want allow „drag and drop” functionality. So we create new div’s tag with dataType=”<type>” attribute inside journalComponentList div.

<div dataType="date">
<liferay-ui:message key="date" />
</div>

We have new component with great design but how we save selected date and time value. Let’s see main.js file. There is getContent method where there are a lot of “if” conditions. So, we must write a new one:

if (type == 'date') {
	var dateContents = componentContainer.all('select');
	var date = [Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN];

	if (dateContents) {
		dateContents.each(
			function(select, selectIndex, selects) {
				var options = select.all('option');
				if (options) {
					options.each(
						function(option, optionIndex, options) {
							if (option.get('selected')) {
								//name selecta, zawierajacego wybrany item
								var selectName = select.get('name');
								var selectedValue = option.val();

								if (selectName.match(/dateDay$/)) {
									date[0] = selectedValue;
								} else if (selectName.match(/dateMonth$/)) {
									date[1] = selectedValue;
								} else if (selectName.match(/dateYear$/)) {
									date[2] = selectedValue;
								} else if (selectName.match(/dateHour$/)) {
									date[3] = selectedValue;
								} else if (selectName.match(/dateMinute$/)) {
									date[4] = selectedValue;
								} else if (selectName.match(/dateAmPm$/)) {
									date[6] = selectedValue;
								}
							}
						},
						select
					);
				}
			}
		);

	}
	content = instance.getDateAsString(date);

}

Also we must define the „getDateAsString” function

getDateAsString: function(stringDateArray) {
		if (stringDateArray.length != 7) {
			return '';
		}
		var intDateArray = new Array(stringDateArray.length);
		var notNaNExist = false;
		for (var i = 0; i < stringDateArray.length; i++) {
			intDateArray[i] = parseInt(stringDateArray[i], 10);
			if (intDateArray[i] != NaN) {
				notNaNExist = true;
			}
		}

		if (!notNaNExist) {
			return '';
		}

		var day = intDateArray[0];
		var month = intDateArray[1];
		var year = intDateArray[2];
		var hour = intDateArray[3];
		var minute = intDateArray[4];
		var second = 0;
		var ampm = intDateArray[6];

		if (!isNaN(month)) {
			month = month + 1;
		}

		if (ampm == 1 && !isNaN(hour)) {
			hour = hour + 12;
		}

		if (month < 10) {
			month = '0' + month;
		}
		if (day < 10) {
			day = '0' + day;
		}
		if (hour < 10) {
			hour = '0' + hour;
		}
		if (minute < 10) {
			minute = '0' + minute;
		}
		if (second < 10) {
			second = '0' + second;
		}

		return year + '-' + month + '-' + day + ' ' + hour + ':' + minute;
	}

I know that my code is not optimal. This post is only tutorial how to write new fields of structure. If you have better idea how resolve this problem just write to me.

If you want download all source files or download ready to deploy war file click links:

* I wrote this hook using trunk version of liferay CE (rev. 70450)

Tips & Tricks: hide Home Page in menu

As you know Liferay has a very nice feature that allows you to edit structure of your website by dragging and dropping pages where you want them. You might didn’t notice that beacuse there is a little GUI bug –  mouse cursor doesn’t change when you are over the right area.
Ok, so now you now how to change your sitemap. Now a little trik.

You know that Liferay allows you to select default page for your site. It is obvious that you want to show you Home Page (in example above it’s a “Welcome” page). But when you put on the first place it also be shown in you menu. There are websites when such entry in menu is ok but also there are websites with so many items in menu that editors want to skip that one in favor of something more interesting to their users. I know what you are thinking: “I can always mark that page as hidden”.

WRONG.

Liferay doesn’t allow first page in structure to be hidden. Don’t know why but as default he can just show pages that are not hidden and are Portlet, Panel, Embedded or Web Content type. This bug was mentioned in LPS-8007, LEP-2369 and in comments to other tickets but still it isn’t fixed. For our luck there is a little work around.

If you try to set first page as hidden you will see an error. If you move your page to other position (for example second) you will be able to hide it but as the defualt one Liferay will show new first page in structure. But if you move your deisred page, change it to “hidden”, save your changes and then move it (by drag’n’drop) to first position Liferay won’t show you any error.

Voila! You have your defualt first page and it isn’t showed in menu any more and all this without any hook coding 🙂

Tips & Tricks: Private Pages

As you probably know Liferay can automatically create private pages for new user. He can also use existing page as a template for new one. To do this you will need to:
1. Prepare your page using Sitemap, portles, themes etc.
2. Exports your page to lar file
3. Copy this lar to deploy/ (deploy/example.lar)
4. Add this to your portal-ext.properties
default.user.private.layouts.lar=${liferay.home}/deploy/example.lar
5. Restart application

You can also perform some tricks with this functionality. For example you want every user to have his own 3 web contents (resume, about me, my stuff). To do this just follow those steps:
1. Prepare your page using Sitemap, portles, themes etc.
2. Prepare your articles and publish them
3. Exports your page with all data to lar file
4. Copy this lar to deploy/ (deploy/example.lar)
5. Add this to your portal-ext.properties
default.user.private.layouts.lar=${liferay.home}/deploy/example.lar
6. Restart application

Now not only theme and pages are connected to private pages but Web content too. If there already is a web content with ID like on your template page Liferay will create new one changeing Owner to the user he is creating private page for. If there existssuch user in the system LR will publish those WC (Web contet) with that user as owner.