Liferay Navigation Recipes – Part 1

Building portals, navigation naturally becomes an important building block of your implementation. Liferay comes with great support for navigation as is – the absolutely most common way of implementing navigation in a Liferay portal is to iterate through the top site pages in the theme (Velocity or FreeMarker) and using the Navigation Portlet for secondary navigation. Sometimes, however, your site requires navigation that is not covered through the existing means of navigating a Liferay portal. In our projects we have used numerous ways to handle different navigation scenarios. This is the first entry in a series of posts where I plan on expanding further on some of these examples.

Grouped Navigation

On a recent project, we had a design that implemented business areas in the normal main navigation in a slightly different way than other pages:

grouped navigation

The business areas were to be placed in the main navigation, but should be grouped to the left of the other links. Also, the client wanted the ability to change names and friendly URLs of the business areas, and add/remove business areas.

To cater for this, we grouped the business areas into one page that we named “Business Sections” with the friendly URL “/business-sections”:

  • Business Sections (/business-sections, hidden)
    • Business Area 1 (/business-area-1, visible)
    • Business Area 2 (/business-area-2, visible)

The main page was set as hidden so that it was not included in the normal iteration of pages in navigation.vm in the theme. Also, the page type was set as “link-to-page”, linked to the first child page (Business Area 1). Thus, if someone happens to hit the URL “../business-sections”, they would be sent to the page for “Business Area 1”. In navigation.vm in the theme, logic was added that pulls all the child pages of the page with the friendly URL “/business-sections” (using Liferay services from the theme) and displays them before the normal page iteration loop. Also, the last list item of the business-section-pages was given a CSS class that would allow for an extra margin.

Here is an example of the code in Velocity:

## Define variables (in init_custom.vm)
 
## Null variable
#set($null = $some-never-used-variable-name)
 
## Url prefix
#set($url_prefix = "")
#if($layout.isPrivateLayout())
    #set($url_prefix = "/group")
#else
    #set($url_prefix = "/web")
#end
 
#set($url_prefix = $url_prefix + $page_group.getFriendlyURL())
 
## Business section url
#set($friendly_url_business_sections = "/business-sections")
 
## Get business section pages
#set($business_sections_parent_layout = $layoutLocalService.getFriendlyURLLayout($group_id, $layout.isPrivateLayout(), $friendly_url_business_sections))
#set ($business_sections_layouts = $null)
 
#if($business_sections_parent_layout != $null)
    #set ($business_sections_layouts = $layoutLocalService.getLayouts($group_id, $layout.isPrivateLayout(), $business_sections_parent_layout.getLayoutId()))
#end
 
## Iterate business section layouts (in navigation.vm)
#set($business_sections_ticker = 1)
#foreach($business_sections_layout in $business_sections_layouts)
 
    #set($curFriendlyURL = $business_sections_layout.getFriendlyURL())
    #set($curURL = $url_prefix + $curFriendlyURL)
 
    #set($curListCss = "")
    
    ## Mark as selected if this is the current page
    #if($curFriendlyURL == $layout.getFriendlyURL())
        #set($curListCss = "selected")
    #elseif($business_sections_layout.isChildSelected(true, $layout))
        #set($curListCss = "selected") 
    #end
    
    ## Mark as last, if this is the last business section page
    #if($business_sections_ticker == $listTool.size($business_sections_layouts))
        #set($curListCss = $curListCss + " last")
    #end
 
    <li class="$curListCss">
        <a href="$curURL">$business_sections_layout.getName()</a>
    </li>
 
#end
 

Now, this should work fine. But what if we are using a virtual host on the page? We could just emit the $url_prefix parameter in the url. But this is not a solution we were satisfied with. We wanted the navigation to work no matter if a virtual host was used or not. So we added the following check when setting the $url_prefix in init_custom.vm: 

 
#set($layoutSetLocalService = $serviceLocator.findService("com.liferay.portal.service.LayoutSetLocalService"))
 
#set($current_layout_set = $layoutSetLocalService.getLayoutSet($group_id, $layout.isPrivateLayout()))
 
#set($current_layout_set_virtual_host = $current_layout_set.getVirtualHostname())
 
#set($hasVirtualHost = false)
 
#if($current_layout_set_virtual_host != "")
    #set($hasVirtualHost = true)
#end
 
#set($url_prefix = "")
 
#if(!$hasVirtualHost)
 
    #if($layout.isPrivateLayout())
        #set($url_prefix = "/group")
    #else
        #set($url_prefix = "/web")
    #end
    
    #set($url_prefix = $url_prefix + $page_group.getFriendlyURL())
 
#end
 

Now the navigation works in both cases. When there is no virtual host, the URL prefix (for example /web/guest) is added to the URL. When there is no virtual host, the prefix is omitted.

Although the scenario you’re facing might not be exactly the same as the one I’m covering above, I hope my examples will provide you with some valuable insights that can help you on your way.

Cheers,
Erik

博客
I'm doing something similar using expando custom fields on the Layout type instead to give the user a drop down of nav menu locations to select (eg header,left,footer) when they edit a page.
Hi S L B,

That's a great technique as well. We have done something similiar to this using expandos for additional page/layout information. I will do a post about this as well.

Cheers,
Erik
May I add a shameless plug to my marketplace app? It solves a very similar problem (just does not do the grouping, it only includes many different sites, but works with all themes out-of-the-box): https://www.liferay.com/marketplace/-/mp/application/27362781