Skip to main content

Customizing themes and skins in IBM WebSphere Portal: A case study

James W. Barnes, Team Lead, Systems Documentation, Inc. (SDI)
James W Barnes is a Team Lead for the WebSphere Portal Level 2 Support organization, focusing on API issues. His areas of expertise include WebSphere Portal development and Java Platform, Enterprise Edition application development. He has written articles on theme development and the WebSphere Portal V5.1 Handbook. Jim holds certificates in WebSphere Portal administration (versions 5.0, 5.1, 6.0) and WebSphere Portal development (versions 5.1 and 6.0). He can be reached at jwbarnes@us.ibm.com.

Summary:  This article examines a use case for altering the user interface (UI) of IBM® WebSphere® Portal. Specifically, we explain how to change the themes and skins to add additional options in the drop-down menu of the portlet as rendered by the skin. This addition yields flexibility and provides a method of how you can extend the UI to meet your own use cases.

Date:  09 Sep 2008
Level:  Intermediate
Activity:  1286 views

This article is intended for application developers and administrators who need to implement custom solutions to suit the needs of an individual UI experience within IBM WebSphere Portal. To get the most out of this article, you should have a general understanding of WebSphere Portal administration, JSP development, and creating and deploying themes and skins. More information about WebSphere Portal administration and themes and skins can be found in the WebSphere Portal Information Center.

Although we focus on WebSphere Portal V6.0 or later the main concepts also apply to WebSphere Portal V6.1.0 or later server-side aggregation.

The WebSphere Portal user interface

Themes and skins in WebSphere Portal provide the framework for the UI, which includes page navigation, page layout, containers to hold portlet-rendered markup, and controls for user interaction. Figure 1 provides an overview of the theme and skins layout. You can see how the skin provides the rows and columns for the page layout and holding the portlets-rendered markup. This markup is rendered by the control.jsp for the skin that is assigned to the portlet for that page.


Figure 1. Theme and skins layout
Theme and skins layout

Part of the skin is the portlet title bar, which contains portlet-specific information, such as the title of the portlet that the skin is rendering. It also contains a JavaScriptâ„¢ function to call the portlet context menu to render. Although the portlet context menu is shared between all skins, the content of the menu is portlet dependent. The portlet context menu contains controls to change the portlet's mode, state, and its position on the page.

Figure 2 shows a context menu for a portlet in which you have no edit permissions on the portlet or the page; the only options available are to minimize or maximize the portlet or to change the portlet's mode to Help, which displays help content that is specific to that portlet.


Figure 2. Minimal portlet context menu
Minimal portlet context menu

In figure 3, you can see that with different access rights, you have the ability not only to change the mode and the state, but also to change where that portlet renders on the page. From this menu, you can either move the portlet down in the order of the page or move it to the column on the right.


Figure 3. Context menu with more options
Context menu with more options

The preceding two figures show that the portlet context menu varies based on factors such as permissions, placement, and the portlet with which it is interacting. Additionally, this menu is extensible in two ways. First, you can add items directly to the JSP file that renders the portlet context menu; second, you can use theme extensions to add items to the menu with little coding involved.


Proposed design for portlet detach

In this use case, you want to add a feature to detach the portlet from the page, to give users the ability to compare portlets side-by-side without changing the page definition. This feature is intended to be only a temporary change and is similar to the dynamic UI of WebSphere Portal, but it requires fewer code changes and artifacts. Our initial thought is to provide this feature as another option in the portlet context menu shown in figure 4 because this option fits in with the other options available for the portlet.


Figure 4. Portlet context menu with Detach option
Portlet context menu with Detach option

After users select Detach, the design calls for displaying a new browser window that shows just the portlet and its markup, without the theme navigation being rendered. This display prevents users from changing pages or navigating away from the current portlet in the detached window. Figure 5 shows the page before the Detach action is performed, in which there are three portlets in one column.


Figure 5. Portlet Detach window
Portlet Detach window

Figure 6 shows how this new detached window looks when it is displayed. As you can see, the portlet is self-contained in the new detached window without theme navigation. The new detached window is also opened with minimal browser tool bars, allowing the portlet to fully control navigation and deterring users from manually changing the URL location.


Figure 6. New detached window
New detached window

In figure 7, you can see that the Test Hide portlet has been removed from the original page and is accessible only in the detached window.


Figure 7. Original page updated
Original page updated

Creating a skin to match the use case

The portlet detach feature allows a user to temporarily detach a portlet from the page and open it in a new browser window. It's useful to isolate the portlet from the main page because, even though it is detached from the original page, it creates only a temporary change in the page's real estate or layout.

To implement the detach feature, you need to create a new custom theme. The most simple method for creating a new theme is to copy the existing IBM theme directory. In addition, you must create a new custom skin, again by copying the existing IBM skin directory. For the following example the theme and skin directories are both named IBMdetach in their respective directories (wps.ear/wps.war/themes/html and wps.ear/wps.war/skins/html).

In this implementation, the detach feature is skin dependent. With some minor modification of the example, though, you could offer this feature in all skins that use the default portletContextMenu.jsp file. As its name implies, the portletContextMenu.jsp file controls the rendering of the portlet's context menu, which contains menu items to trigger changes to the portlet. These actions include changing the portlet's state and mode, and so adding the detach menu item here was a natural choice.

It was decided that only certain portlets on a page are able to be detached. Because the example limits the functionality to only the new skin, you need to create a custom portlet context menu. This menu allows all other skins to function normally, meaning that the other skins still call the default context menu and therefore do not contain the new menu entry.

To do this task, follow these steps:

  1. Copy the portletContextMenu.jsp file and rename the copy portletContextMenuCustom.jsp (these files should be located in wps.ear/wps.war/themes/html/IBMdetach).
  2. Now that you have a custom portlet context menu, edit the new IBMdetach skin so that it calls this new portlet context menu:
    1. Edit the control.jsp file in the newly created IBMdetach skin. To do this step, look for the code shown in listing 1.


      Listing 1. control.jsp in IBMdetach skin
      
      <%if(isJSAvail){%> 
      <script type="text/javascript">var menuNoActionsText_<%= myPortletID%>
      ="<portal-fmt:text bundle='nls.engine' key='info.emptymenu' />";
      var menuPortletURL_<%= myPortletID %>='<portal-navigation:url themeTemplate=
      "portletContextMenu" ><portal-navigation:urlParam name="windowID" value="<
      %=currentLayoutNodeStr%>" /></portal-navigation:url>';
      </script><%}%> 
      

    2. Change it to the code shown in listing 2:


      Listing 2. Edited control.jsp in IBMdetach skin
      
      <%if(isJSAvail){%> 
      <script type="text/javascript">var menuNoActionsText_<%= 
      myPortletID%>="<portal-fmt:text bundle='nls.engine' key='info.emptymenu' />";
      var menuPortletURL_<%= myPortletID %>='<portal-navigation:url themeTemplate=
      "portletContextMenuCustom" ><portal-navigation:urlParam name="windowID" value=
      "<%=currentLayoutNodeStr%>" /></portal-navigation:url>';
      </script><%}%> 
      

      The URL generated from the code in listing 2 targets the JSP file that renders the portlet context menu, the themeTemplate attribute tells WebSphere Portal what JSP file to render, and the value in themeTemplate must match the file name of the JSP file excluding the file extension, which in our case is portletContextMenuCustom.

  3. Modify the portlet context menu to add the menu item to perform the detach operation:
    • Add the following import statements to the portletContextMenuCustom.jsp file: <%@page import="com.ibm.wps.l2.urlspihelper.servletrequest.ThemeURLHelper"%>
      <%@page import="com.ibm.wps.l2.urlspihelper.utils.IdentificationHelper"%>
      <%@page import="com.ibm.portal.state.EngineURL"%>
    • Look for this section of code in listing 3 in the portletContextMenuCustom.jsp file:


      Listing 3. Maximize control
      
      <c-rt:if test='<%=!wState.equals(javax.portlet.WindowState.MAXIMIZED) && 
      !isSolo%>' >
      <portal-navigation:urlGeneration contentNode="<%=pageID%>" 
      layoutNode="<%=windowID%>" portletWindowState="maximized" 
      themeTemplate="" ><c:set var="title"><portal-fmt:text bundle="nls.titlebar" 
      key="maximize" /></c:set><% if (menuItemCount > 0) { %>,<% } %>
      "asynchDoFormSubmit('<% wpsURL.write(escapeXmlWriter); %>');","<c-rt:out 
      value='${title}' escapeXml='true' />",""
      <% menuItemCount++; %> 
      </portal-navigation:urlGeneration>
      </c-rt:if>
      

    • Add the code in bold shown in listing 4 to the code snippet in listing 3:


      Listing 4. Maximize and detach control
      
      <c-rt:if test='<%=!wState.equals(javax.portlet.WindowState.MAXIMIZED) && 
      !isSolo%>' >
      <%
      EngineURL flyoutURL = ThemeURLHelper.generateUrlForFlyout(IdentificationHelper.
      getObjectID(pageID),IdentificationHelper.getObjectID(windowID),request, 
      response);%>
      <c:set var="title">Detach</c:set>
      <% if (menuItemCount > 0) { %>,<% } %>"window.open('<% flyoutURL.
      writeDispose(out); %>','portlet_<%= windowID%>','resizable=yes,
      scrollbars=yes,menubar=no,
      toolbar=no,status=no,width=800,height=600,screenX=10,screenY=10,
      top=10,left=10');","<c-rt:out value='${title}' escapeXml='true' />",""
      <% menuItemCount++; %> 
      
      <portal-navigation:urlGeneration contentNode="<%=pageID%>" 
      layoutNode="<%=windowID%>" portletWindowState="maximized" 
      themeTemplate="" >
      <c:set var="title"><portal-fmt:text bundle="nls.titlebar" key="maximize" />
      </c:set>
      <% if (menuItemCount > 0) { %>,<% } 
      %>"asynchDoFormSubmit('<% wpsURL.write(escapeXmlWriter); %>');",
      "<c-rt:out value='${title}' escapeXml='true' />",""
      <% menuItemCount++; %> 
      </portal-navigation:urlGeneration>
      </c-rt:if>
      

      The code in listing 4 does three things. First, it targets the portlet on the page; second, it tells the portlet to load in solo mode; and third, it gives a name to the new detached window. This code separates the portlet's state in the detached window from the state of the portlet on the original page. By naming the new detached window, you can refer to this window programatically, using the code portlet_<%= windowID %>.

    At this stage, the skin opens a new window in which only the targeted portlet displays, with the plain theme being used. Because the new, detached window is still using the same definition, any code added to the theme or skin is accessible in both windows.

  4. Next, add a handler to hide the portlet on the main page by adding the code in listing 5 to the default.jsp file:


    Listing 5. Main window handler
    
    var portletWindows = [];
    
    <!-- This is used to set a name for the default page -->
    window.name='portalPageWindow';
    
    function addToParentWindowList(windowName) {
    	
    	portletWindows.push(windowName);
    	var portletWindowId = windowName.substring(8);
    	
    	obj=document.getElementById('mouseoverTable_'+portletWindowId);
    	obj.style.display="none";
    
    }
    

    Note that this handler also tracks open portlet detached windows from the main page. Also, the portletWindows array is used to hold all the references to the portlet detached windows.

  5. Name the current window so that you can reference it from the child windows. The addToParentWindowList function adds the detached window's name to the array. Then hide the targeted portlet by taking advantage of an existing div tag that is provided in the IBMdetach skin.
  6. After the portlet is hidden, it must be redisplayed, which you can do by adding the function in listing 6 to the default.jsp file.


    Listing 6. Function to show portlet on main page
    
    function removeFromWindowList(windowName) {
    
    	var index = portletWindows.indexOf(windowName);
    	portletWindows.splice(index);
    	var portletWindowId = windowName.substring(8);
    	
    	obj=document.getElementById('mouseoverTable_'+portletWindowId);	
    	obj.style.display="block";	
    }
    

    The removeFromWindowList function removes the detached window's name from the array and then makes visible the div holding the portlet.

  7. The indexOf code is not currently supported in Microsoft® Internet Explorer 7, so you must add it to get that function to work. To do this step, add the code in listing 7 to a script block outside a function so that it is loaded when the page loads.


    Listing 7. IndexOf prototype
    
    if (!Array.prototype.indexOf)
    {
      Array.prototype.indexOf = function(elt /*, from*/)
      {
        var len = this.length;
    
        var from = Number(arguments[1]) || 0;
        from = (from < 0)
             ? Math.ceil(from)
             : Math.floor(from);
        if (from < 0)
          from += len;
    
        for (; from < len; from++)
        {
          if (from in this &&
              this[from] === elt)
            return from;
        }
        return -1;
      };
    }
    

In this implementation, the portlet should display both when the detach window is closed and when the page changes in the main portal window. To achieve this step, you must call the functions defined in the default.jsp file from the detached window. The code in listing 8 is added to the control.jsp file.

First, though, obtain the window name and determine whether the current window is a detached window or part of the original page processing. If it is a detached window (with the substring equal to portlet_), then additional processing can be performed. In our case, this is a call back to the main window to hide the portlet.


Listing 8. JavaScript for processing detached window

var windowName = window.name;
var portletWindow = windowName.substring(0,8);
   if(portletWindow == "portlet_"){
     var w = window.open('', 'portalPageWindow', 'featuresIfNotDefaults');
      if (w && !w.closed)      {
      w.addToParentWindowList(windowName);
      if (window.addEventListener) //DOM method for binding an event
            window.addEventListener("unload", removeFromParentWindowList , false)
       else if (window.attachEvent) //IE exclusive method for binding an event
       window.attachEvent("onunload", removeFromParentWindowList)
      }
   }

The window.open function is used to gain access to a reference to the main window. Use the main window name that was defined in default.jsp as an argument to the window.open call. Having this reference allows functions embedded in the main window to be run from the detached window. The addToParentWindowList function is called to add the portlet to the main window's portlet window array list and to hide the portlet on the original page. Next, an onunload handler is added so that, when the detached window is closed, the removeFromParentWindowList function is called.

The following code is the implementation for the function called by the onunload handler:

function removeFromParentWindowList(){
w.removeFromWindowList(windowName);
}

This function calls the removeFromWindowList function of the original page, which removes the detached window name from the array and makes the portlet visible again on the original page.

So far, the current code can detach the portlet, hide the portlet on the main page, and then cause the main portlet to show again when closing the window. If, however, you navigate to another page, all detached portlet windows remain open. Because this is undesirable functionality, add the CloseAllWindows function shown in listing 9 to the default.jsp file.


Listing 9. CloseAllWindows function

function closeAllWindows() {
	for(var i=0; i < portletWindows.length; i++ ){
		var c = window.open('', portletWindows[i], 'featuresIfNotDefaults');
		c.window.close();
      
	}

}

Finally, add the function in listing 10 to the onunload handler so that the closeAllWindows function is run when you navigate away from the current page.


Listing 10. JavaScript to add handler for closeAllWindows

if (window.addEventListener) //DOM method for binding an event
     window.addEventListener("unload", closeAllWindows , false)
  else if (window.attachEvent) //IE exclusive method for binding an event
     window.attachEvent("onunload", closeAllWindows);

Now, all open detached portlets are cleaned up when you move from page to page or do a refresh, leaving a clean environment while also providing a way for portlets to interact separately.


Conclusion

In this use case, we have explained how to add a feature to detach a portlet from a page, to give the user the ability to compare portlets side-by-side without changing the page definition. This approach provides additional flexibility and exemplifies how you can extend the WebSphere Portal UI to match your own use cases.

NOTE: In this article, we took advantage of the navigation model SPI and the URL generation helper classes. These helper classes are available in the Downloads section of this article and are also provided in the IBM Support technote, "Portal 6.0 Advanced URL Generation Helper classes." In addition, the identification service was used to get the ObjectId object from the string representation of both the Page and the Portlet.

For more information on these topics, see the Information Center and the developerWorks® article, "Leveraging WebSphere Portal V6 programming model: Part 2. Advanced URL generation in themes and portlets."



Download

NameSizeDownload method
filesforarticle.zip2MBHTTP

Information about download methods


Resources

About the author

James W Barnes is a Team Lead for the WebSphere Portal Level 2 Support organization, focusing on API issues. His areas of expertise include WebSphere Portal development and Java Platform, Enterprise Edition application development. He has written articles on theme development and the WebSphere Portal V5.1 Handbook. Jim holds certificates in WebSphere Portal administration (versions 5.0, 5.1, 6.0) and WebSphere Portal development (versions 5.1 and 6.0). He can be reached at jwbarnes@us.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=336518
ArticleTitle=Customizing themes and skins in IBM WebSphere Portal: A case study
publish-date=09092008
author1-email=jwbarnes@us.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers