Hacking the Weblogic Portal UI Framework

Tuesday Feb 28th 2006 by Scott Nelson
Share:

By using some useful hacks of the BEA portal, you can give your users functionality that they want and the extensibility that you crave.

There's nothing like the feeling of the first production release of a new portal. After weeks or months of hard work and long hours, the application you and your team have put their heart, soul, and talent into is being accessed by real users. E-mails fly around the network with congratulations and the textual equivalents of oohs and ahhs. Your audience is applauding and thanking you for putting many disparate enterprise pieces into one place for easy access and integration. And what makes this euphoria so wonderful is that it only lasts until the first person asks the inevitable portal questions "Can we add this?" And you know they're right to ask for it. Back to work.

This article will present some useful hacks of the BEA portal that will give your users functionality that they want and the extensibility that you crave.

Customizing the Titlebar: 3 Features Your Users Will Love

Okay, so if you are already using all of the built-in titlebar functionality, the last thing you want to do is to put more icons there. That disclaimer out of the way, if you are using all of them, you probably want to re-examine your design. Take a look at the functionality you have built in:

  • Minimize: If there are so many portlets on the page that the user wants to minimize some to clearly see others, it would probably be better to break them up into separate pages (and with your extra navigation hack up your sleeve, you can).
  • Maximize: This is sometimes useful in a multi-column layout, though it is usually more user friendly to go to a single column layout.
  • Delete: This icon doesn't store as a user preference without a bunch of coding, so you annoy the user by having it come back every time, not to mention they have to start a new session if they actually want it back.
  • Help: No doubt about it, this is useful to have.

So, it looks like you have only one icon in the titlebar that users will like. You can give them two more that they will actually use.

Print Me All by Myself

Remember when the paperless office was right around the corner? Like space exploration, that corner seems to be a lot longer than we thought. Whether you agree that printouts of Web pages should already be as quaint as gas-fueled street lights, portal users still hit the print icon all the time. And, except for expense reports and travel plans, what they really want to print out is just one part of the page: a single portlet. As developers, are job is to give the people what they want, whether we think it sane or not. So, here's how you print a single portlet.

This hack depends on naming conventions. For whatever reason, the naming used out of the box won't do the trick, but changing the frameworks use of IDs doesn't effect the application itself, so what the heck? Starting with the window.jsp in your skeletons path, find the following line:

<render:writeAttribute name="id"
   value="<%= window.getPresentationId() %>"/>

Here, you just need to change the ID value to window.getDefinitionLabel() because it is accessible in all version of the portal from anywhere where you have the Window context.

Now that you have a handle on the portlet, you can create a short script that will open a printable page.

function printPortlet(appPath, portletId, portletName)
{
   var pageString = appPath+'/resources/jsp/
      printPage.jsp?portletId='+portletId;
   var printWindow = window.open(pageString, 'PrintPage',
      "location=no,scrollbars=no,resizable");
   printWindow.focus();
}

The above script should live in a .js file so that only a single copy is loaded per page.

Next, open titlebar.jsp and find:

<td class="bea-portal-window-titlebar-buttons"
    nowrap="nowrap">

Inside this empty TD (populated by the BEA icons), put your print icon and a call to your script, passing in the parameters dynamically for portability:

<img src="<render:getSkinPath
     imageName="printerIcon.gif" />"
     style="cursor:pointer; position:relative; top:-2px"
     onclick="printPortlet('<%=request.getContextPath()%>',
        '<%=window.getDefinitionLabel()%>')">

Which result in your new icon your users' titlebar:



Click here for a larger image.

Finally, you need to create your print page to make the icon do something useful. Because the page is not part of the portal context, you'll need to hard-code your style references. Also, due to the inheritance features of the nested DIVs in the the standard framework, you'll need some of the HTML normally rendered for you, followed by your script to render and print the portlet:

<%@ page language="java"
    contentType="text/html;charset=UTF-8"%>
<%@ taglib uri="netui-tags-html.tld" prefix="netui"%>
<netui:html>
   <head>
      <title>
         Portlet Print Page
      </title>
      <meta name="bea-portal-meta-skin"
            content="/framework/skins/default"/>
      <meta name="bea-portal-meta-skin-images"
            content="/framework/skins/default/images"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/body.css"
            rel="stylesheet" type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/button.css"
            rel="stylesheet"
            type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   alert/css/window-alert.css"
            rel="stylesheet"
            type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/window.css"
            rel="stylesheet"
            type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   plain/css/window-plain.css"
            rel="stylesheet"
            type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/portlet.css"
            rel="stylesheet"
            type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/book.css"
            rel="stylesheet"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/layout.css"
            rel="stylesheet"
            type="text/css"/>
      <link href="/snelsondemo/framework/skins/default/
                   css/form.css"
            rel="stylesheet"
            type="text/css"/>
      <script type="text/javascript"
              src="/snelsondemo/framework/skins/default/js/
              menu.js"></script>
      <script type="text/javascript"
              src="/snelsondemo/framework/skins/default/js/
                    util.js"></script>
      <script type="text/javascript"
              src="/snelsondemo/framework/skins/default/js/
                    delete.js"></script>
      <script type="text/javascript"
              src="/snelsondemo/framework/skins/default/js/
                    float.js"></script>
      <script type="text/javascript"
              src="/snelsondemo/framework/skins/default/js/
                    menufx.js"></script>
      <script type="text/javascript"
              src="/snelsondemo/framework/skins/default/js/
                    skin.js"></script>
      <style type="text/css">
         .bea-portal-window-titlebar-title{font-weight:bold}
      </style>
   </head>
   <body class="bea-portal-body">
      <div class="bea-portal-book-primary">
         <div class="bea-portal-book-primary-content">
            <div class="bea-portal-book-primary-page"
                 style="margin-right:10px">
               <div id="portletHtml"
                    class="bea-portal-window">
               </div>
            </div>
         </div>
      </div>
      <script language="JavaScript">
         var portletId = '<%=request.getParameter("portletId")%>';
         document.getElementById('portletHtml').innerHTML =
            self.opener.document.getElementById(portletId).
            innerHTML;
            window.print();
      </script>
   </body>
</netui:html>

Reading the markup and code above, you'll see that our definition label has been carried along is now used to populate our pop-up with just the portlet, leaving out the rest of the page for printing, like this:



Click here for a larger image.

There are refinements you can make to the print page, such as disabling links or feeding the page to a hidden iFrame and calling print for the user. If there are enough readers who ask for the specifics for these tweaks, I'll write them in a follow-up article.

Would You Like Spreadsheets with That?

Next to printing, the most frequent thing business users want to do is to play with table data. Sure, you can do some AJAX thing to let them view the numbers in different ways while they are logged into your portal, and you probably should (again, contact me if you're interested in specifics). But, what about when you user wants to take your data with them and play with it on the flight? Rather than bog them down with a cached version of the whole portal, or hints on how to copy the data to a spreadsheet (unless you have a huge tech-support budget), why not just give it to them the way they want it?

We already found out how to get a handle on our portlet with label definitions, and building a spreadsheet instead of a printout just takes a little refinement.

One difference is you are going to create a hidden iFrame on our page to receive the page (since the result is a file, not a page) by adding the following to footer.jsp:

<iframe id="scriptRender"
        src=""
        style="visibility:hidden;
        height:0px; padding:0px;
        margin:0px"></iframe>

Then, you will make a new function in your js similar to the print function but with some changes to hide the page that is called and pass in the table values:

function excelPortlet(appPath, tableId, portletName)
{
   var tableData = document.getElementById(tableId).outerHTML;
   var pageString =
      appPath+'/resources/jsp/excelPage.jsp?tableData='
             +tableData+'&portletName='+portletName;
   document.getElementById('scriptRender').src = pageString;
}

Next, you will add your icon to the toolbar in the same way you did the print icon except that you will hide the icon until you want it and reference your table instead of the whole portlet:

<img src="<render:getSkinPath
     imageName="excelIcon.gif" />"
     id="<%=window.getDefinitionLabel()%>.excelIcon"
     style="cursor:pointer;
     position:relative; top:-2px;
     visibility:hidden"
     onclick=
        "excelPortlet('<%=request.getContextPath()%>',
                      '<%=window.getDefinitionLabel()%>.
                       excelTable',
                      '<%= title %>')">

Of course, to use the icon, you need to unhide it. You do this in the portlet JSP itself by adding a reference to the Window context then calling your label definition handle, like this:

<%@ page import="com.bea.netuix.servlets.controls.window.
                 WindowPresentationContext"%>
<%
   WindowPresentationContext window =
      WindowPresentationContext.
      getWindowPresentationContext(request);
%>
<script language="javascript">
document.getElementById('<%=window.getDefinitionLabel()%>.
                         excelIcon')
   .style.visibility = 'visible';
</script>

And, of course, write a small JSP to generate your spreadsheet:

<%@ page language="java"
         contentType="text/html;charset=UTF-8"%>
<%
   String tableData = request.getParameter("tableData");
   response.reset();
      response.setContentType("application/xls");
      response.setHeader("Content-Disposition",
                         "attachment;filename="
                         +request.getParameter("portletName")
                         +".xls");
%>

<%=tableData%>

Between the hidden iFrame and response changes, the user only sees the download, and none of the behind the screens magic:



Click here for a larger image.

If you use the <th> tag for your table's heading row, Excel will set it as a header row for sorting.

If you have multiple tables in a portlet, you can set the script call dynamically using the same ID referenced to turn on the icon.

Serving Data? Freshness Counts!

The last new titlebar item (in this article) is to set the date the portlet was last updated into the titlebar. Sure, you could put this into the portlet layout itself, but this saves some space.

Because BEA didn't think of this first, you need to make a slight change to titelbar.jsp by moving the declarations outside of the render context. Find the scriptlet section where WindowPresentationContext is declared and move this section above <render:beginRender> tag. There, that's better. Now that you can get at the window object outside of the render scope, add the following after the </tr>:

<tr><td colspan="3"
        id="<%=window.getDefinitionLabel()%>.dateCell"></td></tr>

And if you think that was easy, look at all you need to add to your portlet:

<% String myDate = "As of March 1, 2006"; %>
document.getElementById('<%=window.getDefinitionLabel()%>
                         .dateCell').innerHTML = '<%=myDate%>';

Yes, I'm too lazy to type all the Calendar and date formatting to do this with a real date, but you get the picture:



Click here for a larger image.

Together for the First Time on the BEA Desktop: Horizontal and Vertical Navigation

With all of these neat features, your portal's popularity will grow and folks will think of more and more portlets to add. More portlets leads to more pages (hopefully, or it's going to get awful crowded on the page, not to mention the coffee breaks prompted by slow page loads). If the page count continues to grow, your portal will eventually become difficult to navigate with out-of-the-box components (not to mention user-annoying side-scrolling as shown below).



Click here for a larger image.

The standard single-level navigation will wrap, but the extra rows of page navigation will eat up precious real estate at the top of the screen (if they have to scroll to it, they probably won't know it's there). You can use multi-level menus, but this adds two more mouse moves for the user to go where they want (although if your site has lots of pages, this solution is inevitable).

There is a cool hack to solve your growing navigation problem: Add a vertical navigation in addition to your horizontal tabs. This will last you as long as your visual design and page count can accommodate, giving you roughly double the page access on a single screen.

The key to the dual navigation approach is in using the hidden page setting.



Click here for a larger image.

The standard single-level menu code skips the hidden pages like this:

if (!pageCtx.isHidden() && pageCtx.isVisible())

At the end of the single-level menu code, you can add a DIV for your vertical menu and then run the same loop using the opposite check:

if (pageCtx.isHidden())

I've dropped the isVisible check because it is redundant anyway.

Now, with some simple CSS, you can position this menu anywhere you want. In the example, I chose to put it on the right:

<div id="verticalmenu"
     style="position:absolute;
     top:40px;
     right:20px;
     width:20px;">



Click here for a larger image.

Voilà! You've just bought a whole bunch of page real estate in 10 minutes.

If you already have hidden pages, use icons and then check for the existence of icons. I prefer using icons regardless for this type of navigation. In the portals where I have implemented this, the choice between horizontal or vertical navigation links was determined by the type of page it was. For example, in a portal that is geared towards business intelligence, there frequently are pages that are not BI related but still a desirable feature for your executive audience (such as event calendars and industry news). These "utility" pages lend themselves naturally to a different navigation model because they are separate from the rest of the portals' focus.

Prior to service pack 4, there's an annoying quirk in the API: pageCtx.isHidden()) never returns true because the framework pulls the hidden pages from the page list before returning it to the book context. You still can use hidden pages, but you have to hard code their values rather than being able to dynamically expand your vertical navigation simply by adding more hidden pages. To make sure your hard-coded menu is portable, be sure to use the render tag for the link, like this:

<a href="<render:pageUrl pageLabel=" hackinguiNews"/>">News</a>

So, even if your users were delighted the first time you released your portal into the wild and never asked for anything more, now you can give them a few extra things with very little work. I usually time that along with the next budget request.

About the Author

Scott Nelson is a Principal Developer in the Architectural Services division of Keane, Inc. where he has implemented custom portal functionality thought up by the best Information Architects and Visual Designers in the industry for large multi-national client companies.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved