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

本文介绍如何通过定制WebSphere Portal的皮肤和主题,在不改变页面定义的情况下,为用户提供将Portlet分离显示的功能,以便进行并排比较。

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 ."

 

内容概要:本文介绍了一项创新性未发表的研究,即利用多元宇宙优化算法(Multiverse Optimizer, MVO)对分时电价下的需求响应与综合能源系统调度问题进行建模与求解,旨在实现能源系统的经济性、高效性与可持续性运行。该研究构建了包含多种能源设备(如光伏、风机、燃气轮机、储能系统等)及可调节负荷的综合能源系统模型,充分考虑了用户侧的需求响应行为在分时电价机制下的响应特性,通过MVO算法对系统运行成本、能源利用率、碳排放等多目标进行协同优化,实现了日前调度计划的智能决策。研究还提供了完整的MATLAB代码实现,便于研究人员复现实验、验证算法性能,并为进一步研究提供可靠的仿真基础。; 适合人群:具备一定电力系统、优化算法及MATLAB编程基础的科研人员、研究生以及从事能源互联网、综合能源系统规划与运行的技术工程师。; 使用场景及目标:① 学习并掌握多元宇宙优化算法在复杂能源系统调度中的具体应用方法;② 研究分时电价机制如何通过需求响应引导用户参与电网互动,实现削峰填谷;③ 实现综合能源系统(IES)中冷、热、电、气等多种能源的协同优化调度,以降低运行成本、提高新能源消纳能力和系统可靠性;④ 为相关领域的学术研究提供可复现的代码实例和仿真平台。; 阅读建议:此资源以MATLAB代码为核心载体,深入剖析了算法应用与系统建模的全过程。建议读者在学习时,不仅应关注代码的实现细节,更要理解其背后的数学模型、优化目标设定和约束条件的物理意义。建议结合文档中的模型描述,逐步调试代码,观察不同参数和场景下的优化结果,从而深刻掌握综合能源系统优化调度的设计思想与关键技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值