Monday, December 15, 2008

Advanced Lotus Connections customization - pt.2 - Sidebar Navigation with the WebAppIntegrator


In this post I'd like to describe how to customize and extend the WebApplicationIntegrator for WebSphere Portal in order to deliver a double-top-bar navigation and a sidebar navigation using the portal content model. The extension was developed for Lotus Connections but could be used for integrating other web applications, too.

Main Idea
As described in the first part, the structure of the generated HTML code needs to be modified in order to deliver a sidebar navigation, respectively a table layout. This is the easy part of the modification and require some HTML skills and basic knowledge of JSP.
The slightly more complicated part is to generate the navigation menu for the upper navigation levels, this requires Java, JSP and Portal API skills. But its not that difficult.

To do understand what has to be done, we first must understand how the WAI generates the navigation menu. The link that is generated by the WAI portlet contains the unique ID or the custom unique name of the page that marks the current navigational position. The WAI theme extension searches the Portal for that page using the Portal API. To generate the standard single top navigation bar, the parent page is determined as well.

So the basic idea for generating more than the single top navigation is to determine the parent’s parent and its parent as well. With knowledge of each parent page it is easy to iterate over each child page and create the navigation html code. The page structure that needs to be created in order to have a double-top-bar navigation and a sidebar navigation may look like depicted in the following diagram.



Using a page structure as shown, the WAI would per default only generate a navigation structure that lists all children of a common parent. This means the navigation shown on top of the Profiles page would only list the other URL pages because each of them is a child page of the common parent page portal.DemoLab.LotusConnections.

The extended WAI simply iterates over all children of the LotusConnections page’s parent (the siblings of the LotusConnections page) and the same for the parent of the next levels.

Realization
In the WAI extension of a theme – the webappintegrator directory – the Display.jsp is the main entry point. It includes the JSP fragments (jspf) to aggregate the WAI page respectively the homepage.

Code changes
To iterate over every parent page a handle for each page must be created, therefore we have to add to the variable declaration part in the beginning of the file.


ObjectID pageOid = null;
ObjectID parentOid = null;

ObjectID parent2Oid = null;
ObjectID parent3Oid = null;

String parentUniqueName = null;
String parent2UniqueName = null;
String parent3UniqueName = null;


The three lines in italics already exist in the file and are used for the selected page (pageOid), its parent (parentOid) and the parent page’s uniquename (parentUniqueName), the handles with the 2 and the 3 in the name correspond to the 2nd level and 3rd level parent of the page. These handles are used later on for iterating.

After that the initialization of the WAI needs to be changed as well. This is done in the webAppIntegratorInitialize.jspf file. Open the file and search for the section “Get the selected page object id” and add the following String declarations:

String theSelectedPageid = identification.serialize( pageOid );
String theSelectedParentid = null;
String theSelectedGrandParentid = null;


Again, the line in italics already exist.
In order to find the correct parent pages, search for the section “Get the parent page id” (should be right below the one we just changed), and replace it with the following code:


NavigationNode pageNode = (NavigationNode)navModel.getLocator().findByID(pageOid);
NavigationNode parentNode = null;
if (pageNode != null)
{
parentNode = (NavigationNode)navModel.getParent(pageNode);
parentOid = parentNode.getObjectID();
theSelectedParentid = identification.serialize( parentOid);
parentUniqueName = parentOid.getUniqueName();
if (parentUniqueName == null)
{
parentUniqueName = identification.serialize(parentOid);
}
}

//get the grand parent (parent2)
parentNode = (NavigationNode)navModel.getParent(parentNode);
if(parentNode != null)
{
parent2Oid = parentNode.getObjectID();
theSelectedGrandParentid = identification.serialize( parent2Oid);
parent2UniqueName = parent2Oid.getUniqueName();
if (parent2UniqueName == null)
{
parent2UniqueName = identification.serialize(parent2Oid );
}

//get the grand grand grand parent (parent3)
parentNode = (NavigationNode)navModel.getParent(parentNode);
if(parentNode != null)
{
parent3Oid = parentNode.getObjectID();
parent3UniqueName = parent3Oid.getUniqueName();
if (parent3UniqueName == null)
{
parent3UniqueName = identification.serialize(parent2Oid );
}
}
}


Page Structure
Now we need to modify the page structure to create a table layout. As shown in the Introduction post, the table layout is no black magic and simply done in HTML. Only slight changes are made to the file structure. An additional file is created that contains the page area and another one for the sidebar navigation. The structure created is shown in the following picture.



Generating Navigation Bars
With the code changes applied to the WAI theme extension, we are now able to iterate over each navigation level in order to generate the navigation HTML code. First, we want to generate the double top bar navigation. Therefore we open the webAppIntegratorTopNav.jsp that contains per default the single top navigation.

To iterate over topmost level - 2 levels above the current page’s level – the upper bar of the double-bar navigation - use a code similar to the following. Note, that the style classes that are applied refer to a customer theme, not the default IBM Portal theme! (This might be changed in this documentation in the near future)

The second navigation level – the lower bar of the double bar navigation – is generated using a similar code. Again, this code uses custom CSS style classes, not the default IBM Portal ones.

For creating the sidebar, open the webAppIntegratorSideNav.jsp file. This is a new file that has to be created as described above. The sidebar code is similar to the other navigation bars.


First Menu Bar

//--------------------------------------------------
// Create the Menu1 Tab Bar Markup
//--------------------------------------------------

<c:if test = "${renderTopNavigation}">
<portal-logic:if navigationAvailable="yes" screen="Home,LoggedIn,LoggedOut">
<portal-navigation:navigation startLevel="1" stopLevel = "1" scopeUniqueName="<%=parent3UniqueName%>">
document.write("<ul id='mainMenuOne'>");
<portal-navigation:navigationLoop>
<c:set var="topNavItemTitle"><portal-fmt:title/></c:set>
<% String currentPageID = identification.serialize((( com.ibm.portal.Identifiable) wpsNavNode).getObjectID());
//---------------------------------------------
// generate the markup to render selected page
//---------------------------------------------
if ( currentPageID.equals(theSelectedGrandParentid ) ) { %>
document.write('<li class="pos1 selected">');
<% if (com.ibm.portal.content.ContentNodeType.EXTERNALURL.equals( wpsNavNode.getContentNode().getContentNodeType())) { %>
document.write('<a target="_top" href="<portal-navigation:navigationUrl type="link" />" >');
<% } else { %>
<portal-navigation:urlGeneration contentNode="<%=currentPageID%>" forceAbsolute="true" themeTemplate="">
document.write('<a target="_top" href="<% wpsURL.write(out); %>">');
</portal-navigation:urlGeneration>
<% } %>
document.write("<c:out value='${topNavItemTitle}' escapeXml='true' />");
document.write("</a>");
document.write("</li>");
<% } else {
//---------------------------------------------
// generate the markup to render unselected pages
//---------------------------------------------
%>
document.write('<li class="pos1">');
<portal-navigation:urlGeneration contentNode="<%=currentPageID%>" forceAbsolute="true" themeTemplate="">
document.write('<a target="_top" href="<% wpsURL.write(out); %>">');
</portal-navigation:urlGeneration>
<c:set var="pageTitle" >
<portal-fmt:title />
</c:set>
document.write('<c:out value='${topNavItemTitle}' escapeXml='true' />');
document.write('</a>');
document.write('</li>');
<% } %>
</portal-navigation:navigationLoop>
document.write("</ul>");
<c:if test="${!topNavFirstParam}">
document.write('</div>');
</c:if>
</portal-navigation:navigation>
</portal-logic:if>
</c:if>


Second Menu Bar


//--------------------------------------------------
// Create the Menu2 Tab Bar Markup
//--------------------------------------------------

<c:if test = "${renderTopNavigation}">
<portal-logic:if navigationAvailable="yes" screen="Home,LoggedIn,LoggedOut">
<portal-navigation:navigation startLevel="1" stopLevel = "1" scopeUniqueName="<%=parent2UniqueName%>">
document.write("<ul id='mainMenuTwo'>");
<portal-navigation:navigationLoop>
<c:set var="topNavItemTitle">
<portal-fmt:title/>
</c:set>
<% String currentPageID = identification.serialize(((com.ibm.portal.Identifiable ) wpsNavNode ).getObjectID());
if ( currentPageID.equals(theSelectedParentid) ) {
//---------------------------------------------
// generate the markup to render selected page
//---------------------------------------------
%>
document.write('<li class="pos1 selected">');
<% if (com.ibm.portal.content.ContentNodeType.EXTERNALURL.equals( wpsNavNode.getContentNode().getContentNodeType())) { %>
document.write('<a target="_top" href="<portal-navigation:navigationUrl type="link" />" >');
<% } else { %>
<portal-navigation:urlGeneration contentNode="<%=currentPageID%>" forceAbsolute="true" themeTemplate="">
document.write('<a target="_top" href="<% wpsURL.write(out); %>">');
</portal-navigation:urlGeneration>
<% } %>
document.write("<c:out value='${topNavItemTitle}' escapeXml='true' />");
document.write("</a>");
document.write("</li>");
<% } else {
//---------------------------------------------
// generate the markup to render unselected pages
//---------------------------------------------
%>
document.write('<li class="pos1">');
<portal-navigation:urlGeneration contentNode="<%=currentPageID%>" forceAbsolute="true" themeTemplate="">
document.write('<a target="_top" href="<% wpsURL.write(out); %>">');
</portal-navigation:urlGeneration>
<c:set var="pageTitle" >
<portal-fmt:title />
</c:set>
document.write('<c:out value='${topNavItemTitle}' escapeXml='true' />');
document.write('</a>');
document.write('</li>');
<% } %>
</portal-navigation:navigationLoop>
document.write("</ul>");
<c:if test="${!topNavFirstParam}">
document.write('</div>');
</c:if>
</portal-navigation:navigation>
</portal-logic:if>
</c:if>



Sidemenu Bar

//--------------------------------------------------
// Create the Side Bar Markup
//--------------------------------------------------

<c:if test = "${renderTopNavigation}">
<portal-logic:if navigationAvailable="yes" screen="Home,LoggedIn,LoggedOut">
<portal-navigation:navigation startLevel="1" stopLevel = "1" scopeUniqueName="<%=parentUniqueName%>">
document.write('<ul>');
<portal-navigation:navigationLoop>
<c:set var="topNavItemTitle">
<portal-fmt:title/>
</c:set>
<% String currentPageID = identification.serialize(((com.ibm.portal.Identifiable ) wpsNavNode ).getObjectID());
if ( currentPageID.equals(theSelectedPageid) ) {
//---------------------------------------------
// generate the markup to render selected page
//---------------------------------------------
%>
document.write('<li id="portalSelectedNode" class="selected">');
<% if (com.ibm.portal.content.ContentNodeType.EXTERNALURL.equals(wpsNavNode.getContentNode().getContentNodeType())) { %>
document.write('<a target="_top" href="<portal-navigation:navigationUrl type="link" />" >');
<% } else { %>
<portal-navigation:urlGeneration contentNode="<%=currentPageID%>" forceAbsolute="true" themeTemplate="">
document.write('<a target="_top" href="<% wpsURL.write(out); %>">');
</portal-navigation:urlGeneration>
<% } %>
document.write("<c:out value='${topNavItemTitle}' escapeXml='true' />");
document.write("</a>");
document.write("</li>");
<% } else {
//---------------------------------------------
// generate the markup to render unselected pages
//---------------------------------------------
%>
document.write('<li>');
<portal-navigation:urlGeneration contentNode="<%=currentPageID%>" forceAbsolute="true" themeTemplate="">
document.write('<a target="_top" href="<% wpsURL.write(out); %>">');
</portal-navigation:urlGeneration>
<c:set var="pageTitle" >
<portal-fmt:title />
</c:set>
document.write('<c:out value='${topNavItemTitle}' escapeXml='true' />');
document.write('</a>');
document.write('</li>');
<% } %>
</portal-navigation:navigationLoop>
document.write("</ul>");
</portal-navigation:navigation>
</portal-logic:if>
</c:if>


Known limitations
The code samples posted here always generate a double-top and sidebar navigation. It does not check if enough navigational levels exist. Furthermore this WAI theme ignores any theme policies. So its not capable anymore to only generate a double navigation without sidebar or a single-top navigation.

Second, as described before, this code sample are based on a customized theme. In order to make it work with another theme, the CSS style classes and the structure of the HTML code need to be revised.

The code samples are from a PoC, its no production code, but maybe a good starting point.
Post a Comment