Saturday, December 20, 2008

Advanced Lotus Connections customization - pt.3 - Portal Awareness

This post describes, how a Portal-integrated Lotus Connections installation can be customized to be aware of the Portal it runs in. The awareness is used to hide and show details depending from where the user navigated to Lotus Connections.

One use case for which the Portal awareness can be used is in combination with the WAI. When being accessed from Portal, the application shows the Portal navigation using the WAI (left image). When accessed from outside of Portal, i.e. from a bookmark, it shows the default standalone navigation. In case of Lotus Connections the default navigation bar shows all the five features and the homepage in a single top navigation bar (right image).







The application awareness is described in the WAI documentation in general for any web application that is integrated in Portal using the WAI. Unfortunately this general approach does not work for all the different features of Lotus Connections, actually it does only work for Activities and even for this not without errors. But though its not impossible, and how it is realized is shown in the following sections.

Main Idea

The main idea to achieve Portal awareness as described in the WAI documentation is to initially pass a parameter to the application when navigating from Portal and remember this parameter using a session cookie for all consecutive clicks. When the application is accessed from a link outside of Portal, no parameter is passed to the application. When the Browser window is closed, the session cookie expires.
The principle is the same for every feature and depicted in the following diagram.

The diagram shows the states the overall system consisting of the Portal Server, the Connections Server(s) and the client’s browser could be in. Each state represents an internal state consisting of user authentication (LTPA token), request parameter containing the information whether a user navigates from Portal server to Connections or not, and a session cookie representing if the user visited a certain Connections feature before. Note that the session cookie is maintained for each feature. Each composite state also represents the view of the user’s browser respectively which application the user accesses at the moment.

After an initialization phase where the user initially navigates to Portal and Connections the user afterwards switches between the three states in the lower section of the diagram.



Besides this, when the user navigates directly to Lotus Connections, he can reach the Portal only manually be navigating to the explicit Portal URL or by using a bookmark to Portal. Furthermore, the transitions from the left path (the Portal path) to the right path (Connections standalone) via the widgets on the homepage are considered as known limitations of the current solution and occur due to the titles of the widgets in the homepage do not contain the request parameter required. Anyway, the confusion in the user experience is limited because the feature on whose widget the user clicked is opened in a new window respectively browser tab.

The following diagram depicts the decision flow for the render process inside the browser.



As shown, the browser script first checks, if a request parameter is set, if the parameter is set, it is stored as session cookie. If the parameter is not set, the script checks, if the cookie is set – in other words, if the user visited Connections from Portal before - the Portal navigation is displayed, otherwise, the standalone navigation is displayed.

Realization

The easiest part is to hide and include the WebAppIntegrator navigation when navigating from Portal, the difficult part was to hide the standalone navigation bar when inside Portal. The dynamic removal of the code, for instance via JSP or JavaScript produces JavaScript errors when the page is rendered because some of the JavaScript scripts access or modify elements in the navigation bar such as the Login/Logout button. So the solution that worked fine in the end without big modifications was to simply hide the bar using CSS. This applies to all features of Lotus Connections.

The hide/show mechanism has to be applied in the header.html files in the sn-core.ui.war/templates directory of each Connections feature. Simply add to the file the following code:


<script type="text/javascript">
if(testCookieForFromPortal()) {
document.getElementById("lotusBanner").style.display = "none";
}
</script>


For applying the WAI script, follow the guideline described in the Lotus Connections wiki (see Resources chapter). Add to every file described in the wiki, at the position where the WAI script has to be placed (as first element after the opening body-tag) the following code:


<script type="text/javascript">
if (("<c:out value='${param.FromPortal}'/>" != "true") && !testCookieForFromPortal()) { }
else {
// if we get here, we have come from Portal, set a cookie
document.cookie="FromPortal=true";
}
function testCookieForFromPortal() {
if (getCookieValue("FromPortal") == "true")
{ return true; }
else
{ return false; }
}
function getCookieValue(cookieName) {
var retVal = "";
var i = document.cookie.indexOf(cookieName);
if (i != -1) {
var iVal = i + cookieName.length + 1;
var j = document.cookie.indexOf(";", iVal);
if (j != -1) {
retVal = document.cookie.substring(iVal,j);
}
}
return retVal;
}
</script>


This applies to all features except for Blogs. The Blog solution is described below. This solution also uses JSP tags only and no JSP scriptlets, since scriptlets are disabled in some of the Connections JSPs.

The actual code to integrate Portal navigation is special to almost every single feature of Lotus Connections because every component follows a different approach to assembling the UI. This is not unexpected when you look at the history of Lotus Connections and that every feature was a standalone application before.

The following sections contain descriptions for every feature how to apply the dynamic Portal aware behaviour.

Activities, Communities and Profiles

Instead of the snippet provided by the WAI portlet, apply the following snippet and replace $$URL$$ with the URL of the generated snippet:

<!-- BEGIN PORTAL NAVIGATION INTEGRATION -->
<c:if test="${param.FromPortal=='true' || sessionScope.FromPortal=='true'}" >
<script type="text/javascript" src="$URL$}"></script>
</c:if>
<c:if test="${sessionScope.FromPortal!='true'}" >
<c:set var="FromPortal" value="${param.FromPortal}" scope="session" />
</c:if>
<!-- END PORTAL NAVIGATION INTEGRATION -->


The snippet checks, if the request parameter or the session contains the FromPortal parameter. If no session parameter is set, the session parameter is set to the value of the request parameter. Ensure that this snippet is inserted on all positions in the file described in the documentation.

Modify the URL of the URL page in Portal that links to Connections Activities to:

https://<activities-server>/<activities-context-root>/service/html/mainpage?FromPortal=true


Modify the URL of the URL page in Portal that links to Connections Communities to:

https://<communities-server>/<communities-context-root>/service/html/allcommunities?FromPortal=true


Modify the URL of the URL page in Portal that links to Connections Profiles to:

https://<profiles-server>/<profiles-context-root>/home.do?FromPortal=true


In Activities a rendering problem occurred, where the error message box appeared during load of the page. This was not due to an error but a simple timing condition. The error box is part of the mainpage and is rendered per default. Its visibility is set to hidden via Javascript. However, with the changes for the WAI applied, the box was not hidden during load but after the load process. The easiest way to solve this problem is to set the style attribute of this box to “visibility:hidden” which hides the error box during load. Nevertheless, the error box is shown in case of error because its visibility is set to visible in case of errors by Javascript.

Homepage

In the homepages feature the advanced search page jsp file contained an invalid HTML structure, where the static navigation header is included in the section of the file, which is syntactically wrong. When the Javascript functions as described above are put below the <body> element, the navigation could not be hidden, because the functions defined in the body element are not visible in the head element. The only reasonable solution for this is to move the jsp include statement that includes the header and other HTML content to the body section.

These statements are as follows, put them below the snippet for the WAI navigation:

<jsp:include page='pageHeader.jspf' flush="true" />
<jsp:include page='menuBar.jspf' flush="true" />


Similar to Activities etc put the following code at first element in the body tag and replace $$URL$$ with the URL of the generated snippet:


<!-- BEGIN PORTAL NAVIGATION INTEGRATION -->
<c:if test="${param.FromPortal=='true' || sessionScope.FromPortal=='true'}" >
<script type="text/javascript" src="$URL$}"></script>
</c:if>
<c:if test="${sessionScope.FromPortal!='true'}" >
<c:set var="FromPortal" value="${param.FromPortal}" scope="session" />
</c:if>
<!-- END PORTAL NAVIGATION INTEGRATION -->


Modify the URL of the URL page in Portal that links to Connections Homepage to:
https://<homepage-server>/<homepage-context-root>/web/getuserpref?FromPortal=true


Dogear

Dogear does not allow serverside session handling, so the solution of Activities and Communities does not work. Second, the UI does not allow to include a script such as the WAI script directly and on condition. If the full snippet of the WAI portlet would be written to the document using a single write statement, the inclusion of the Portal navigation will not work.

So kind of Javascript hack needs to be applied, that prevents the conditionally included script from being executed during page loading and executes the script at first, when the whole page has been processed. This is done by splitting the Javascript inclusion into several substring which each of them contain unexecutable code like in the following code snippet. Again, replace $$URL$$ with the URL of the generated snippet of the WAI portlet.

<script type="text/javascript">
if(testCookieForFromPortal()) {
document.write('<scr');
document.write('ipt type="text/javascript" ');
document.write('src="$URL$"');
document.write('></scr');
document.write('ipt>');
}
</script>


Modify the URL of the URL page in Portal that links to Connections Dogear to:

https://<dogear-server>/<dogear-context-root>/?FromPortal=true


Blogs

Similar to Dogear, blogs does no support serverside session therefore the WAI inclusion script must be injected using the following code:

<script type="text/javascript">
if(testCookieForFromPortal()) {
document.write('<scr');
document.write('ipt type="text/javascript" ');
document.write('src="$URL$"');
document.write('></scr');
document.write('ipt>');
}
</script>


Additionally, Blogs uses the Velocity templating engine, and the layout of the whole Blogs feature depends on the theme that is used, including the homepage of Blogs. Each theme is a set of Velocity templates which have to be modified according the documentation in the Lotus Connections wiki.

But instead of the snippet for the Javascript functions described above, use the following snippet for the VTL.


<script type="text/javascript">

#if($model.getRequestParameter('FromPortal') == 'true')
document.cookie="FromPortal=true";
#else
if(testCookieForFromPortal()) {
document.cookie="FromPortal=true";
}
#end

function testCookieForFromPortal() {
if (getCookieValue("FromPortal") == "true") {
return true;
} else {
return false;
}
}
function getCookieValue(cookieName) {
var retVal = "";
var i = document.cookie.indexOf(cookieName);
if (i != -1) {
var iVal = i + cookieName.length + 1;
var j = document.cookie.indexOf(";", iVal);
if (j != -1)
{
retVal = document.cookie.substring(iVal,j);
}
}
return retVal;
}
// the themeTemplateURL variable should be set to the URL defined
// as the src attribute of the <script/> tag generated by the
// WebAppIntegrator portlet.

</script>


Modify the URL of the URL page in Portal that links to Connections Blogs to:
https://<blogs-server>/<blogs-context-root>/?FromPortal=true


Known Limitations

Navigating from the Homepage/Widget to a feature without previously navigating from the Portal Navigation to the feature (and thereby setting the FromPortal cookie) will bring up the standalone look and feel of the feature

If the session cookie is still available but the LTPA token expired or is not available, no navigation shown

The customization of blogs as described here is a hack to show that its possible, its not a best practice. Blogs provides a way to change the configuration in order to apply a custom theme/footer.

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.

Thursday, December 11, 2008

I don't like travelling by train

For this weekend's roundtrip I started in Zurich with a train (InterCity Express - ICE, german "high-speed" train) to Stuttgart. And it really pissed me off. Ok, the advantages of train is, you can do something during travelling. This is true if you don't get sick. Unfortunately I belong the the group that gets, especially in high-speed trains that tilt in the curves. So no effective working during the travel. Then the air conditioning in the train. It starts with a summer-like t-shirt temperature and dropped during the 3h trip to temperatures where I almost had to put my jacket on - over the pullover. And on top of all: this train stops on every single tiny city on the road. In this 3hr trip over 200km the train stopped 7times in between. 7 times! for a handfull of people to get in. That makes an average distance of less than 30km between a stop. A plane from Zurich to Hamburg doesn't land on each airport on the way, does it? This is no high-speed train, this is an annoyance!
When I travel by car, it takes a bit more that 2hr for the same distance and costs the same - all supplemental costs included

Don't get me wrong, I think public transport is important and make ecological and economical sense. But very often I don't have the feeling the German public transportation follows the right approach. From my (and I think for a lot of other clients) perspective its slower, more expensive and less comfortable than a car. So car or plane are in most cases the most reasonable choice - from a customer perspective, although both are the worst choices considering the environement.

Tuesday, December 9, 2008

Advanced Lotus Connections customization - pt.1

In this blog-post-series I'd like to describe, how we achieved to integrate Lotus Connections into WebSphere Portal using the Web Application Integrator.
It will consist of three parts.

The first - this one - will provide a general overview of the solution and a little background information about the WAI and the Portal awareness.
The second one will the describe the modifications we had to apply on the WAI to achieve the double-top and sidebar-navigation.
The third part will describe how we integrated the WAI into Lotus Connections and how we made Lotus Connections Portal-aware.

After completion, all three parts will also be made available on Cattail or Quickr.

Introduction
The intension was, to integrate Lotus Connections seamlessly into an existing Portal look&feel using the Web Application Integrator. The requirements were:
  • One Portal URL page for each Connections feature
  • All feature URL pages underneath a single Lotus Connections page
  • The Lotus Connections page underneath a Collaboration page
  • The Collaboration page as top-level page under Portal Home page
  • The double-top navigation and the sidebar navigation should always be visible
  • When accessing Lotus Connections outside of Portal, no Portal navigation should be shown but the default Lotus Connections top-bar navigation.
The starting point was, that the WebAppIntegrator officially only supports single-bar top navigation and that the Portal awareness is described as advanced topic in the WebAppIntegrator in General.
The customization consists of two steps
  1. Extending the WebAppIntegrator to support multiple navigation hierarchies
  2. Integrate the WebAppIntegrator into Lotus Connections and make the navigation Portal Aware
The article describes, how the solution was implemented, what has to be modified and which problems came up during the customization.

Web Application Integrator
The Web Application Integrator (WAI) is a solution for WebSphere Portal that allows the integration of external web applications into a Portal Look&Feel.
Basically, the WAI does not help to integrate applications physically into the Portal, instead it exports the Portal Look&Feel and Navigation to the web application and thus achieves a visual integration of the web application.

The Portal Look&Feel is injected into the web applications UI using javascript, which means the integration is done at runtime, on client side, during the render process in the browser.

In order to deliver the correct navigational state of the portal, the web application refers to a page in the Portal content model for which the navigation is rendered. In order to address this page, a URL is generated using the Web Application Integrator Portlet.

To have access to the theme content that should be displayed, the theme that should be used have to be enabled for the WAI. This is described in the WAI documentation. The standard IBM Portal theme is already enabled for being used with the WAI. However, this enablement only supports a single top-bar navigation.

How the Web Application Integrator works
A detailed description how the WAI works can be found in its documentation. But this section focuses on how it works inside the browser, especially how the HTML code looks like that is generated.

Regarding the visual display of the WAI, the WAI seems only to integrate at the WebUI’s top position as shown in the picture. With a little HTML background, such things are easy to implement by simply injecting the entire code surrounded by a div block as very first element inside the body element.
But the WAI works slightly different, since it wraps around the content respectively the application, providing closing html elements below the applications content area.




Taking this into account makes it easy to put the content into a table cell instead of a div block, which is a prerequisite for applying a sidebar navigation. The top navigation is placed inside a table row. This is depicted in the next image.



An alternative to this layout would also be to place the top navigation in a div block and the table with a single row and two cells containing the sidebar and the content area.
Deploying a different page structure is done in the webappintegrator/ files of the theme and consists basically of defining the opening tags.
How the navigation page structure is generated is described in the chapter Extending the WebAppIntegrator.

Portal Awareness
Portal aware means that the application is aware if it runs visually inside the Portal or outside of this. In other words, elements of the application are hidden or shown depending from the user has navigated to the application.

The basic principle how this is achieved is described in the Advanced Topics section of the WAI and is simply realized by appending a parameter to the URLs of the URL pages that are created in Portal. The customized application checks if the parameter is existent and shows or hides content accordingly. In order to keep this information while clicking through the application without modifying each link, a simple session cookie is set and the application just checks if the parameter exists in the request or as a cookie.

Unfortunately each module of Lotus Connection works differently and the sample code of the WAI documentation can’t be used. Furthermore, since each module is structured differently, a unique solution for almost every single module needs to be applied. How this is done is decribed in chapter Portal Awareness in Lotus Connections.

Wednesday, November 26, 2008

A friend in need's a friend indeed, a friend that tweet is better

A couple of months ago shortly after I moved to Zurich, my friends from Stuttgart started to use Twitter and said, its cool for letting others know what one does in the evening so that others could join. Well, I gave it a look and signed up. But it proved no real value to me, not only because it was no use for me knowing what my friends in Stuttgart do in the evening, because I definitely don't drive 200km to join them, moreover they soon lost interest in Twitter (possibly they saw no use for that purpose, too, because they all lived in the same neighborhood)

So I lost twitter a bit out of sight, until last week when I was on the presales meeting about Lotus Connection with another colleague who told me, that twitter could be easily integrated into facebook to update the facebook status. Well, I started updating my facebook status regularly some weeks ago, so I thought I'll give it a closer look.

Connecting the Facebook status with Twitter is described on several blogs like this one. So that was easy. But having to open the Twitter page everytime I want to post something? Thats not going to work out. Following the principle of "Simple ubiquitous online access" (the third principle, thanks Luis for the link :) I had to find a better way of updating my twitter.

First of all, I use my browser almost 95% of my time, so I had to find a way to post to twitter from my browser. After a quite short search I found Twitterbar which lets you post to Twitter directly from the addressbar, but the downside was, that it did not came with a keybord shortcut and I had to press the post button using my mouse (how horrible :). But I found another cool add-on named keyconfig that let me define custom hotkeys. Unfortunately its yet not officially available for FireFox3, so I had to use an experimental version, but thats no problem. In keyconfig I assigned Ctrl+Shift+P to post to the Twitterbar (the command for posting is simply "TWITTERBAR.post();"). So that was done, I was now able to update my facebook status from my browser, that was a real value-add (for me).

With diving deeper into the Social Software topic I also began reading more blogs and following other guys from IBM on twitter I also began to use a news reader (this stuff exists for years now and I justed started using them last weekend, I'm definitely everything but not an early adopter ... )
With using the feedreader and following more on twitter, I also received responses to my posts and liked to answer them, therefor I used another cool tool, Twhirl, which I intend to use to respond. When it comes to posting, I find out its also possible to post via GoogleTalk.

One thing I found out these days for me personally is, that twitter has a real value-add, if its used by others as well and regularly. I can easily been kept informed on what is going on around me, what my colleagues are doing. And not only staying informed, but also getting help. I can post what I am doing, with which I am struggeling or what I'd like to know, and the more knowledgekeepers I have that follow me or read the same feed (like our Lotus Connections Twitter feed) I can get easily help. That is cool!

So as a non-early adopter as I see myself, I experience some aspects of what we are preaching when it comes to business adoption.
  • I need someone I can follow, I need content already inside "the system", even if this "content" are other users that are using it (like in twitter)
  • I need easy access from everywhere to use it. Now, I have multiple channels of posting to twitter.
  • I need some value-add, I don't contribute just because I can. With twitter I even kill two birds with a stone: I keep my facebook status up-to-date (somewhat over-up-to-date I admit) , I see what is going on around me and I have easy and direct access when it comes to help each other or share knowledge.
So slowly I see what's the point on this thingy named Web2.0 :)

Btw. the next milestone I'll probably try out when it comes to integration is how I import my blog posts to Facebook notes.

Monday, November 24, 2008

Blogworld, here I come

So thats it, my first blog.

Feels a bit strange, now I also belong to those that feed the world with unasked opinions... but hey, nobody needs to read it if not wanting to.

Why do I start to blog ... well, let me shortly introduce myself to the world, since July this year I'm an IT consultant, working at IBM, in the field of Portals (WebSphere Portal) and social software, mostly Lotus Connections. Well, as I get deeper into the matter, I'm getting familiar with the concepts, and the use of it, and the benefits, and of course I need to know the stuff we are selling. And in terms of social software, we're not only selling the software (Lotus Connections) but moreover the idea behind it.

Last friday we had a presales event at a customer for Lotus Connections, and the customer was very keen on the topic and very interested. The audience ranged from really early adopters to very reluctant but nevertheless very interessted participants. We had a very intense and interessting discussion making this event a quite exciting one.
This discussion dropped my final barrier and after the event I came to the conclusion that I personally have to open myself to the web 2.0 now... I have the feeling that the time has come (and I'm really not an early adopter, too)... and here I am, writing my first blog entry. But I think for a first entry, it is enough for now, but stay tuned, there will be more to follow :)