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.
Post a Comment