Recent Bloggers

Jorge Ferrer

Staff
57 Publications
29 août 2014

Nate Cavanaugh

Staff
36 Publications
29 août 2014

Olaf Kock

Staff
78 Publications
26 août 2014

Matti Tahvonen

3 Publications
26 août 2014

Ronald Sarayudej

Staff
151 Publications
25 août 2014

James Falkner

Staff
95 Publications
25 août 2014

Paulo Fernandes

Staff
15 Publications
22 août 2014

Scott Lee

Staff
3 Publications
21 août 2014

Martin Yan

Staff
5 Publications
20 août 2014

Miguel Ángel Pastor Olivar

Staff
15 Publications
19 août 2014

How calendar events are seen in different time zones

Technical Blogs 12 août 2014 Par Adam Victor Brandizzi Staff

One of the most important features of the new calendar portlet is the ability of consistently showing events to users in different time zones. It is invaluable for global organizations. If your team is scattered around the world, you would really want to make sure things happen at the same time!

However, the consistent behavior can be quite difficult to grasp. It is understandable that 9 AM in São Paulo is 1 PM in London... when there is no Daylight Savings Time. Now, if it is 9 AM in São Paulo, what time is it in London if the United Kingdom is under summer time? What time would it be in London if São Paulo is under summer time? And when both cities are in DST? Recurring events make these questions yet more baffling. Your Paulistano colleague would see that meeting, which used to happen at 9 AM, just changed to 8 AM... Working it out can drive one crazy! (Not me, of course; I work with it all the time but I'm completely sane.)

Luckily, those calculations are no job for humans, the calendar handles it all. Nonetheless, one may need to know how calendar works with diverse time zones; otherwise, one could choose bad times for events and get lost with some strange behaviors. Here we will see what is needed to know.

How users in different time zones see events

Consider an hypothetical company which uses Liferay and the calendar portlet. The company's portal time zone is set to the Coordinated Universal Time (UTC). Also, this company has two employees: Brian, who lives in Los Angeles, and Juán, who lives in Madrid. Brian went into his account configuration and, in the section "Display Settings", changed his time zone to be America/Los Angeles; likewise, Juán changed his time zone to Europe/Madrid.

Now, Brian schedules a meeting with Juán at February 3, 2014. In the calendar, he clicks in the row labeled "8 AM" to create the event:

Once Brian creates the event and invites his colleague, Juán will see the following event in his calendar:

To Juán, the event appears at 5 PM (or 17:00) since this is the corresponding time in Spain:

Note, however, that neither Los Angeles nor Madrid are under daylight savings time. Would Brian schedule the event to March 24 (when Los Angeles is under DST), the meeting would appear in Juán's calendar at 4 PM:

As you can see, the events are displayed in an specific hour depending on the authenticated user time zone. If the user is not authenticated, however, the events are going to be displayed in the portal time zone. So, for example, an unauthenticated user would see the event from February 3 at 4 PM, since this is the corresponding hour in Universal Time; the meeting at March 24 would be displayed at 3 PM to the guest user.

Time zones and recurring events

Users who deal with partners in different time zones probably are used to these details. Subtler than these differences is the behavior of recurring events.

Recurring events are trickier because they should happen in the same hour of the day, all days. Suppose, for example, that Brian scheduled meetings with another colleague from LA every Monday at 10 AM. They would expect the events to always be scheduled to 10 AM regardless of whether DST is in effect or not. Both the meeting at March 3 and the meeting at March 31 would happen at 10 AM in Los Angeles time, although the former would happen at 6 PM (18:00) UTC and the latter at 5 PM (17:00) UTC.

For achieving this behavior, Brian just needs to create the recurring event in his own calendar. Since his user time zone is America/Los Angeles, the calendar portlet will make the event fixed in the same hour at this time zone.

When users are on different time zones, the schedule time should be fixed on only one of the time zones. Otherwise, the same event would be scheduled for two different moments in time! Suppose, for example, that our colleague Brian wants to have weekly meetings with Juán. Brian then creates a recurring event in his own calendar, recurring every Monday, at 8 AM (Los Angeles time), starting at March 3; also, he invites Juán to the event. What will Juán see?

Well, that depends on the Monday Juan is looking at:

  • At March 3, the meeting will be scheduled for 5 PM:

  • California will be under daylight savings time from March 9 on; LA clocks should have one hour added to them. So, at March 10 Juán will see the event happening one hour sooner, at 4 PM:

  • For its turn, DST will start in Spain at March 30, so an hour will be added to each clock in Madrid. This will synchronize them with the Los Angeles clocks, so, at March 31, Juán will see the event scheduled for 5 PM:

As you can see, for consistency, the event can only be fixed in one time zone. For users in other time zones, the event will keep changing its start and end times. It can be a bit dizzying but the user does not need to track it at all. Juán would only need to be aware that the meetings could happen in different times - the calendar will always display the correct time. With proper notifications, he would be informed of the correct time in a timely manner.

If Brian and Juán prefer the event to be fixed in the Madrilenian time zone, then Juán would create the event in his calendar, since his time zone is the Spanish one. If, on the other hand, the event would be created in a site calendar, the recurring event would be fixed in the portal time zone - which, in our example, is UTC.

Which time zones affects the event times?

As you can see, each time an event is rendered in the screen there are two time zones to define its start and end times. The first one is the time zone of the user who sees the event. This display time zone is important to define the hour to be displayed but has no influence on the exact moment the event will take place. It can make the event appear at 8 AM to an LA-located user and 17 PM to another user at Madrid, but it is showing the same hour, since 8 AM in Los Angeles time is the same as 17 PM in Madrid time.

Most of the time, the display time zone will be the time zone the authenticated user has chosen in her Display Settings. If the user is not authenticated, it will be the portal time zone.

There is a way of forcing the calendar to always use the same arbitrary display time zone in a site: just go into the Calendar portlet preferences and disable the "Use Global Time Zone" option at the bottom of the screen. Then, select the time zone you would want to be used by the Calendar as the displayed one. This configuration is site-scoped: the events will be displayed int he selected time zone in all pages of the configured site, regardless of the user time zone.

Apart from the display time zone, each event is associated to other time zone. This one is only relevant for recurring events, since this is used to "fix" the event on a specific hour of the day; since the event is going to be "fixed" it is important to know in which time zone the hour and minute will be the same, regardless of the day. This event time zone, is derived from the calendar in which the event was created. If the event was created in a user calendar, the the event time zone is the user time zone of its creator. If, otherwise, it was created in a site calendar, the event time zone will be the portal time zone.

  What does it affect? How it is defined? When is it relevant?
Display time zone It defines at which time zone the events will be displayed. In our examples, it defines whether de event is displayed in Los Angeles time our Madrid time.
  • If the portlet configuration was changed, it will be the time zone selected in the portlet configuration
  • If the portlet configuration was not changed:
    • If the user is authenticated, it will be the user time zone.
    • If the user is not authenticated, it will be the portal time zone.
Always: no event can be rendered without taking into consideration the display time zone. After all, the hour should be displayed in some specific time zone, otherwise it has no meaning.
Event time zone In recurring events, it defines the time zone at which the hour and minute should be the same. In our examples, it defines that an event should happen always at, let us say, 8 AM in Los Angeles time regardless of whether LA is under daylight savings time.
  • If the event was created in a user calendar, it will be the user time zone of the event creator.
  • If the event was created in a site calendar, it will be the portal time zone.
Only when rendering recurring events.

An experienced reader will feel the need of choosing the event time zone. If the organization of our example does have a site for its office in Madrid, the events created on this site calendar would be preferably bound to the Madrid time zone, not to the portal time zone, which is UTC. Also, I may create an event on my own site but decide that it should be bound to another, completely different time zone. Alas, this is not possible now, but a ticket is already filled for that and this feature will be available in the next major release.

Many may find all this stuff bewildering. Don't worry: most users will never need to think about it. The decisions a user has to make are much simpler. Do you want to see the events in Los Angeles time or in Madrid time? Do you want your weekly event to happen at the same hour in which city? You just need to answer these straightforward questions. The calendar portlet will take care of the details for you - in a consistent way.

The localized rich text editor

General Blogs 4 août 2013 Par Adam Victor Brandizzi Staff

I'm working in an awesome component to be shipped from Liferay 6.2 onwards. Some of the features needed to implement it are rather reusable so they were implemented in the Liferay Porta itself. One of them is the new ability of the <liferay-ui:input-localized> tag of inserting a WYSIWYG editor in the page instead of an one-line input or a textarea.

How to use it

To use it, just set the type parameter of the <liferay-ui:input-localized> tag to editor, as below (source):

<portlet:renderURL var="savePostURL" />

<form action="<%= savePostURL %>" method="post">
    <liferay-ui:input-localized name="localizedEditor" xml="" type="editor" />
    <input type="submit" value="Submit post">
</form>

(Do not forget to put it inside a <form> element; it is mandatory to the <liferay-ui:input-localized> tag regardless of the type.)

Your portlet will be presented as below:

It works as any localized input: click on the flag corresponding to the language you want to translate to. The content of the editor will be cleared and you can enter your translation. If you click in a flag of a language which already has some content, the content will be shown in the editor.

To retrieve the value one can just use the usual steps of dealing with localized inputs. For example, if we want to present the translations inserted, we can use the LocalizationUtil class to retrieve the map with the submitted values. With the map, we can create e.g. a list of values, as in the lines below (source):

<ul>
<%
Map<Locale, String> translations = LocalizationUtil.getLocalizationMap(
    renderRequest, "localizedEditor");

for (Locale locale : translations.keySet()) {
    String translation = translations.get(locale);
    
        if (Validator.isNotNull(translation)) {
        %>
            <li>
                    In <%= locale.getDisplayLanguage() %>:
                    <div>
                        <%= translation %>
                    </div>
            </li>
        <%
        }
}
%>
</ul>

The result could be something like this:

A new model hint

The new feature, however, shows all of its awesomeness when dealing with AlloyUI Forms and the Service Builder because we can change the model hint  of a field to use a localized rich-text editor as its input. To see it at use, let us create a new Service Builder entity named Post (source):

    <entity name="Post">
        <column name="postId" type="long" primary="true" />

        <column name="title" type="String" localized="true" />
        <column name="content" type="String" localized="true" />
    </entity>

Once you generate the service files, you can edit the model hint of the content column at the file docroot/WEB-INF/src/META-INF/portlet-model-hint.xml to use the new EDITOR hint collection. Replace this line...

<field name="content" type="String" localized="true" />

by these lines (source - diff):

        <field name="content" type="String" localized="true">
    <hint-collection name="EDITOR" />
        </field>

Snippets for testing

For testing purposes, we can create a little form to edit it:

	<%
long postId = ParamUtil.getLong(renderRequest, "postId");
Post post = null;
if (postId > 0) {
    post = PostLocalServiceUtil.getPost(postId);
}
%>

<portlet:actionURL name="savePost" var="savePostURL" />

<aui:form action="<%= savePostURL %>" method="post">
    <aui:fieldset>
        <aui:model-context model="<%= Post.class %>" bean="<%= post %>" />    

        <aui:input name="postId" type="hidden" />

        <aui:input name="title" />
	        <aui:input name="content" />

        <aui:button name="submit" type="submit" />
    </aui:fieldset>
</aui:form>

We also create a portlet to process the action request (source)...

public class PostPortlet extends MVCPortlet {

    public void savePost(ActionRequest request, ActionResponse response) 
    			    throws PortalException, SystemException {
        		long postId = ParamUtil.getLong(request, "postId");

        		Map title =
                				LocalizationUtil.getLocalizationMap(request, "title");
        Map content =
				                LocalizationUtil.getLocalizationMap(request, "content");

        		Post post = (postId > 0) 
                				? PostLocalServiceUtil.getPost(postId) 
                				: new PostImpl();

        		post.setTitleMap(title);
        		post.setContentMap(content);

		        if (postId > 0) {
            post.setPostId(postId);
            			PostLocalServiceUtil.updatePost(post);
        }
        else {
            			post.setPostId(CounterLocalServiceUtil.increment(Post.class.getName()));
            			PostLocalServiceUtil.addPost(post);
        }
    }
}

...as well as some code to list the saved values (source):

<ul>
<%
List<Post> posts = PostLocalServiceUtil.getPosts(0, QueryUtil.ALL_POS);

for (Post curPost : posts) {
%>
    <portlet:renderURL var="postURL">
                <portlet:param name="postId" value="<%= String.valueOf(curPost.getPostId()) %>"/>
        </portlet:renderURL>
        <li>
                <a href="<%= postURL %>"><%= curPost.getTitle(LocaleUtil.getDefault()) %></a>
        </li>
    <%
}
%>
</ul>

Now, the input for the content will not be a line input or a textarea solely - it will be a localized rich text editor! After some input, for example, our portlet can look like this:

Summing up

This feature is not available on the bundles yet - it is coming in Liferay 6.2. Nonetheless, one can anticipate its usefulness. We hope it will make your lives easier! In fact, all model hints are pretty useful while not really widely known. Also, note that this feature allows one to define the input of a model field as a non-localized rich-text editor if one wants to.

As usual, the full source code is available at my repository of examples as the simple-localized-editor-portlet plugin.

The Liferay.Store JavaScript component

General Blogs 25 juillet 2013 Par Adam Victor Brandizzi Staff

Since AlloyUI became a base component for Liferay, various amazing tools became available for the Liferay developer.

One of these are the JavaScript components such as Liferay.Store. This component allows the developer to store user-related data quickly on the server. To see how it works, let's consider a portlet which stores how the user prefers her coffee: black, sweet or with milk. We can just create a select input with the options (source):

How do you like your coffee? 

<select id="<portlet:namespace/>coffeePref" name="<portlet:namespace/>coffeePref"> 
    <option value="black">Black</option> 
    <option value="sweet">Sweet</option> 
    <option value="milk">With milk</option> 
</select>

As one would expect, the choice will be lost when the page is reloaded. Here enters  Liferay.Store: it asynchronously saves any information on the server, relating it to the authenticated user. In this case, when the select input changes we will store the new value. First, we open a new <aui:scipt> element, which should declare the use of the liferay-store module:

<aui:script use="liferay-store">

</aui:script>

In it, we retrieve the input using the YUI API and bind a function to its change event:

<aui:script use="liferay-store">
    A.one('#<portlet:namespace/>coffeePref').on(
        'change',
        function(event) { }
    );
</aui:script>

Now comes the beef: the function retrieves the current value of the select input and stores it using the Liferay.Store component:

<aui:script use="liferay-store">
    A.one('#<portlet:namespace/>coffeePref').on(
        'change',
        function(event) {
            var instance = this;

            Liferay.Store('<portlet:namespace/>coffee-preference', instance.val());
        }
    );
</aui:script>

In this case, we store the value of the input in a key composed by the portlet namespase followed by the coffee-preference suffix.

One can retrieve this value from JavaScript but it is a common pattern to fetch it on the server side, since it avoids unnecessary requests and slowdowns. This is what we'll do; for that, we use the get() method from the com.liferay.portal.util.SessionClicks class:

<%

String coffeePreference = SessionClicks.get(request, renderResponse.getNamespace() + "coffee-preference", "");

%>
Now, we can use the retrieved value to verify which option was selected - e.g. this way (source):
<select id="<portlet:namespace/>coffeePref" name="<portlet:namespace/>coffeePref">
    <option value="black" <%= "black".equals(coffeePreference) ? "selected" : "" %>>Black</option>
        <option value="sweet" <%= "sweet".equals(coffeePreference) ? "selected" : "" %>>Sweet</option>
    <option value="milk"  <%= "milk".equals(coffeePreference) ? "selected" : "" %>>With milk</option>
</select>
This is a rather simple process to save small, user-bound bits of data asynchronously and can save a lot of time. The full code can be found at my examples repository as the coffee-portlet plugin.
Affichage de 3 résultat(s).