Wednesday, April 30, 2014

Create Liferay portlet with localized input value

Hi all,
Today, I am going to show you how to create a Liferay portlet with localized input value. You also know how to put a portlet in Control Panel and embed a portlet in theme.

When doing with localization, you can have a couple of choices. If you are in theme template files, you can use #language("your_language_key"), and if you are in portlet view file (.jsp file...), it is LanguageUtil.get(pageContext, "your_language_key"). You will have localization ability.

But it is all the things you know already. You prepare your language properties files, put words on it, along with the same key, and then you show the correct language sentence when user change their language in Portal. What about the value that user puts? Do you want to localize it? Sure. You can see what it looks like by creating a structure or a template in Web Content in Control Panel.

Now, for example, we have to create a manual Menu portlet that allows admin user create the menu on his own. We will localize the Menu label.

We have two portlets, one for managing Menu item in Control Panel, and the another for displaying Menu bar in your theme. These are working on the same Menu Item data model.

Firstly, create Menu Item entity in service.xml file, like below:
<entity name="MenuItem" local-service="true" remote-service="true" cache-enabled="false">

        <!-- PK fields -->

        <column name="menuItemId" type="long" primary="true" />

        <!-- Audit fields -->

        <column name="companyId" type="long" />
        <column name="userId" type="long" />
        <column name="userName" type="String" />
        <column name="createDate" type="Date" primary="false"/>
        <column name="modifiedDate" type="Date" primary="false"/>

        <!-- Other fields -->

        <column name="name" type="String" localized="true"/>
        <column name="description" type="String" localized="true" />
        <column name="path" type="String" />
        <column name="targetType" type="String" />
        <column name="parentId" type="long" />
        <column name="weight" type="int"></column>

        <order by="asc">
            <order-column name="menuItemId" order-by="asc"/>
        </order>

        <!-- Finder methods -->

        <finder name="Name" return-type="Collection">
            <finder-column name="name" />
        </finder>
        <finder name="ParentId" return-type="Collection">
            <finder-column name="parentId" />
        </finder>
    </entity>
In this case, pay your attention on:
<column name="name" type="String" localized="true"/>
<column name="description" type="String" localized="true" />
It makes the Portal save the user input value in xml format that supports localization. One thing else you should know is I set cache-enabled="false". I don't know how cache in portlet level works but if you set this to true, when user adds new value to database, it does not affect to view immediately until I redeploy source code again.

Look at portlet-model-hints.xml file,
<model-hints>
    <model name="vn.edu.bvu.model.MenuItem">
        <field name="menuItemId" type="long" />
        <field name="companyId" type="long" />
        <field name="userId" type="long" />
        <field name="userName" type="String" />
        <field name="createDate" type="Date" />
        <field name="modifiedDate" type="Date" />
        <field name="name" type="String" localized="true" />
        <field name="description" type="String" localized="true" />
        <field name="path" type="String" />
        <field name="targetType" type="String" />
        <field name="parentId" type="long" />
        <field name="weight" type="int" />
    </model>
</model-hints>
From there, in view files, you can use aui taglib to display form input that supports localization. This is my code on edit.jsp file:
 <%@ include file="/html/menu/init.jsp"%>

<portlet:actionURL var="addMenuItemURL" name="add"></portlet:actionURL>

<%
long menuItemId = ParamUtil.getLong(renderRequest, "menuItemId");
MenuItem mi = null;
if (menuItemId > 0) {
    mi = MenuItemLocalServiceUtil.getMenuItem(menuItemId);
}

List<MenuItem> list = MenuItemLocalServiceUtil.getMenuItems(0, MenuItemLocalServiceUtil.getMenuItemsCount());

%>

<aui:form action="<%= addMenuItemURL %>" method="post">
    <aui:fieldset>
        <aui:model-context model="<%= MenuItem.class %>" bean="<%= mi %>" />  

        <aui:input name="menuItemId" type="hidden" />
      
        <aui:input name="name" label="menu-item-name"/>
      
        <aui:input name="description" label="menu-item-description"/>
      
        <aui:input name="path" label="menu-item-path"/>
      
        <aui:select name="targetType" label="menu-item-target-type">
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_self\") ? true : false) : false %>" value="_self">_self</aui:option>
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_blank\") ? true : false) : false %>" value="_blank">_blank</aui:option>
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_parent\") ? true : false) : false %>" value="_parent">_parent</aui:option>
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_top\") ? true : false) : false %>" value="_top">_top</aui:option>
        </aui:select>

        <aui:input name="weight" label="menu-item-weight"/>
      
        <aui:select name="parentId" label="menu-item-parentId">
            <aui:option selected="<%= mi != null ? (mi.getParentId() == 0 ? true : false) : false %>" value="<%= 0 %>"><%= StringPool.BLANK %></aui:option>
            <%
                for(int i = 0; i < list.size(); i++) {
                    MenuItem item = list.get(i);
                    if(item.getMenuItemId() != menuItemId) {
                        %>
                        <aui:option selected="<%= item.getMenuItemId() == menuItemId %>" value="<%= item.getMenuItemId() %>"><%= item.getName(currentLocale) %></aui:option>
                        <%
                    }
                }
            %>
        </aui:select>

        <aui:button name="submit" type="submit" value="Add"/>
      
    </aui:fieldset>
</aui:form>
The magic thing comes from portlet-model-hints.xml file to <aui:input ...> taglib. AUI taglib automatically recognize localized=true and add more component to allow user add more language translation.
See more benefits from model-hints from this link https://www.liferay.com/documentation/liferay-portal/6.1/development/-/ai/using-model-hints-liferay-portal-6-1-dev-guide-en.

In view result file, see my code below:
 <%@ include file="/html/menu/init.jsp"%>

<portlet:renderURL var="addMenuItemUrl" portletMode="edit" />

<liferay-ui:search-container delta="5" emptyResultsMessage="no-menu-items-were-found">
    <liferay-ui:search-container-results>
        <%
            List<MenuItem> list = MenuItemLocalServiceUtil
                            .getMenuItems(0, MenuItemLocalServiceUtil
                                    .getMenuItemsCount());
                    List<MenuItem> tempResults = ListUtil.subList(list,searchContainer.getStart(), searchContainer.getEnd());
                    int tempTotal = list.size();
                    pageContext.setAttribute("results", tempResults);
                    pageContext.setAttribute("total", tempTotal);
        %>
    </liferay-ui:search-container-results>

    <liferay-ui:search-container-row className="vn.edu.bvu.model.MenuItem"
        keyProperty="menuItemId" modelVar="item">
        <liferay-ui:search-container-column-text name="menu-item-id"
            value="<%=StringUtil.valueOf(item.getMenuItemId()) %>" />
        <liferay-ui:search-container-column-text name="menu-item-name"
            value="<%=item.getName(currentLocale) %>" />
        <liferay-ui:search-container-column-text name="menu-item-description"
            value="<%=item.getDescription(currentLocale)%>" />
        <liferay-ui:search-container-column-text name="menu-item-path"
            value="<%=item.getPath()%>" />
        <liferay-ui:search-container-column-text name="menu-item-target-type"
            value="<%=item.getTargetType()%>" />
        <liferay-ui:search-container-column-text name="menu-item-weight"
            value="<%=String.valueOf(item.getWeight())%>" />
        <liferay-ui:search-container-column-text name="menu-item-parentId"
            value="<%= item.getParentId() == 0 ? StringPool.BLANK : MenuItemLocalServiceUtil.getMenuItem(item.getParentId()).getName(currentLocale) %>" />
        <liferay-ui:search-container-column-jsp path="/html/menu/buttons.jsp"
            align="right" />
    </liferay-ui:search-container-row>

    <liferay-ui:search-iterator />
</liferay-ui:search-container>

<a href="<%=addMenuItemUrl%>">Add new</a>
MenuItem model has function help us to fetch Menu label corresponding to current locale.

/htm/menu/buttons.jsp file:
<%@ include file="/html/menu/init.jsp" %>

<%
    ResultRow rslt = (ResultRow)renderRequest.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);
    MenuItem mi = (MenuItem)rslt.getObject();
%>
<liferay-ui:icon-menu>
    <portlet:actionURL name="edit" var="editUrl">
        <portlet:param name="menuItemId" value="<%= StringUtil.valueOf(mi.getMenuItemId()) %>"/>
    </portlet:actionURL>
    <portlet:actionURL name="delete" var="deleteUrl">
        <portlet:param name="menuItemId" value="<%=StringUtil.valueOf(mi.getMenuItemId()) %>"/>
    </portlet:actionURL>
   
    <liferay-ui:icon image="edit" message="Edit" url="<%= editUrl %>"/>
    <liferay-ui:icon image="delete" message="Delete" url="<%= deleteUrl %>"/>
</liferay-ui:icon-menu>

But wait a minute, how can we save data with localization? Here it is:
public void add(
            ActionRequest actionRequest, ActionResponse actionResponse)
        throws IOException, PortletException, SystemException, PortalException {
        Map<Locale, String> name = LocalizationUtil.getLocalizationMap(actionRequest, "name");
        Map<Locale, String> description = LocalizationUtil.getLocalizationMap(actionRequest, "description");
        String path = ParamUtil.getString(actionRequest, "path");
        String targetType = ParamUtil.getString(actionRequest, "targetType");
        int weight = ParamUtil.getInteger(actionRequest, "weight");
        long parentId = ParamUtil.getLong(actionRequest, "parentId");
       
        long menuItemId = ParamUtil.getLong(actionRequest, "menuItemId");
       
        MenuItem menuItem = null;
        if(menuItemId > 0) {
            menuItem = MenuItemLocalServiceUtil.getMenuItem(menuItemId);
        } else {
            menuItemId = CounterLocalServiceUtil.increment(MenuPortlet.class.getName());
            menuItem = MenuItemLocalServiceUtil.createMenuItem(menuItemId);
        }
       
        menuItem.setNameMap(name);
        menuItem.setDescriptionMap(description);
        menuItem.setPath(path);
        menuItem.setTargetType(targetType);
        menuItem.setWeight(weight);
        menuItem.setParentId(parentId);
       
        MenuItemLocalServiceUtil.updateMenuItem(menuItem);
       
        actionResponse.setPortletMode(PortletMode.VIEW);
    }
See my highlighted code. We save a map with locale value, not just a single value as usual.

Add Menu portlet to Control Panel:
Add 2 control panel entry element to portlet definition in liferay-portlet.xml, like below:
<portlet>
        <portlet-name>menu</portlet-name>
        <icon>/icon.png</icon>
        <control-panel-entry-category>
            content
        </control-panel-entry-category>
        <control-panel-entry-weight>1.5</control-panel-entry-weight>
        <instanceable>false</instanceable>
        <header-portlet-css>/css/menu/main.css</header-portlet-css>
        <footer-portlet-javascript>
            /js/menu/main.js
        </footer-portlet-javascript>
        <css-class-wrapper>menu-portlet</css-class-wrapper>
    </portlet>
Also, in liferay-display.xml, add this portlet to hidden categrory to hide it from user selection in add Portlet list on their pages:
 <category name="category.hidden">
        <portlet id="menu"></portlet>
    </category>
Embed Display Menu portlet to theme:
After you get all menu items you need to display in menu bar, embed Menu Display portlet to theme to make it visible to all pages and does not let user change, move or re-configure it. In navigation.vm file, do something like this:
<nav class="$nav_css_class menu" id="navigation">
    #set ($VOID = $velocityPortletPreference.setValue('display-style', '1'))
    #set ($VOID = $velocityPortletPreference.setValue('portlet-setup-show-borders', 'false'))
   
    #set ($portletId = "menudisplay_WAR_portalportlet")
   
    $theme.runtime($portletId, '', $velocityPortletPreferences.toString())
    #set ($VOID = $velocityPortletPreferences.reset())
</nav>
That's all. Tell me if you have any problem. All comments are welcome. See you next time.

No comments:

Post a Comment