目次


WebSphere Portal におけるテーマおよびスキンの開発

Comments

この記事は、アプリケーション開発者およびシステム管理者で IBM WebSphere Portal 内の個々の ユーザー・インターフェースの要望に適したカスタム・ソリューションを実装する必要がある方を対象としています。この記事を最大限に利用するには、WebSphere Portal 管理、JSP 開発、テーマとスキンの作成およびデプロイを一般的に理解していることが必要です。WebSphere Portal 管理、およびテーマとスキンの詳細については、WebSphere Portal Information Center を参照してください。

この記事では WebSphere Portal V6.0 以降に焦点を当てていますが、主な概念は WebSphere Portal V6.1.0 以降のサーバー・サイド集約にもあてはまります。

WebSphere Portal のユーザー・インターフェース

WebSphere Portal のテーマとスキンは ユーザー・インターフェース のフレームワークであり、ページ・ナビゲーション、ページ・レイアウト、ポートレットによってレンダリングされたマークアップを保持するコンテナー、およびユーザー対話用のコントロールが含まれています。テーマとスキンのレイアウトの概要を図 1 に示します。スキンによってページ・レイアウトの列と行が提供され、ポートレットがレンダリングしたマークアップが保持されている様子がわかります。このマークアップは、そのページのポートレットに割り当てられたスキンの control.jsp によってレンダリングされます。

図 1. テーマとスキンのレイアウト
テーマとスキンのレイアウト
テーマとスキンのレイアウト

スキンの一部はポートレットのタイトル・バーで、スキンがレンダリングしているポートレットのタイトルなど、ポートレット固有の情報が含まれています。また、レンダリングするポートレット・コンテキスト・メニューを呼び出す JavaScript™ 関数も含まれています。ポートレット・コンテキスト・メニューはすべてのスキンで共有されますが、メニューのコンテンツはポートレットに依存します。ポートレット・コンテキスト・メニューには、ポートレットのモード、状態、およびページ内での位置を変更するコントロールが含まれています。

ユーザーがポートレットまたはページへの編集権限を持たないときのポートレットのコンテキスト・メニューを図 2 に示します。利用できるオプションは、ポートレットの最小化と最大化、およびポートレット固有のヘルプ目次を表示する「ヘルプ」モードへの変更です。

図 2. ポートレットの最小限のコンテキスト・メニュー
ポートレットの最小限のコンテキスト・メニュー

図 3 は、ユーザーが上の例とは異なるアクセス権を持つ場合で、モードと状態を変更できるだけでなく、ポートレットがページでレンダリングされる位置も変更できることがわかります。このメニューでは、ポートレットをページの1つ下方に移動するか、右側の列に移動できます。

図 3. より多くのオプションが表示されたコンテキスト・メニュー
より多くのオプションが表示されたコンテキスト・メニュー

上の 2 つの図から、ポートレットのコンテキスト・メニューは、アクセス権、ページに配置されている場所、対話しているポートレットはどれかなどの要因に基づいて変化することがわかります。また、このメニューは 2 通りの方法で拡張できます。1 つは、ポートレットのコンテキスト・メニューをレンダリングする JSP ファイルに直接アイテムを追加する方法です。もう 1 つは、テーマ拡張を使用し、ほとんどコーディングせずにアイテムをメニューに追加する方法です。

ポートレット・デタッチの設計案

このユース・ケースでは、ポートレットをページから切り離す(デタッチする)機能を追加します。この機能により、ユーザーはページ定義を変更せずに、ポートレットを横に並べて比較できるようになります。この機能は一時的な変更のみを目的とし、WebSphere Portal の動的 ユーザー・インターフェース と同様のものですが、必要なコード変更と成果物はより少なくなります。初期の方針としては、この機能をポートレットのコンテキスト・メニューの別オプションとして提供します (図 4 参照)。

図 4. 「デタッチ」オプション付きのポートレット・コンテキスト・メニュー
「デタッチ」オプション付きのポートレット・コンテキスト・メニュー

ユーザーが「デタッチ」(Detach) を選択すると、テーマ・ナビゲーションがレンダリングされないポートレットとマークアップだけを表示する新しいブラウザー・ウィンドウが設計によって呼び出されます。この表示では、ユーザーはデタッチされたウィンドウでのページ変更や現在のポートレットからの移動ができません。「デタッチ」アクションの実行前のページを図 5 に示します。3 つのポートレットが 1 つの列に表示されています。

図 5. ポートレットのデタッチ・ウィンドウ
Portlet Detach window
Portlet Detach window

デタッチされた新規ウィンドウがどのように表示されるのかを図 6 に示します。デタッチされた新規ウィンドウにはテーマ・ナビゲーションがなく、ポートレット自身が表示されていることがわかります。デタッチされた新規ウィンドウは、ブラウザーの最小限のツールバーだけがある状態で開かれるため、ポートレットはナビゲーションを完全に制御できますが、ユーザーは手動で URL ロケーションを変更することができません。

図 6. デタッチされた新規ウィンドウ
New detached window
New detached window

図 7 では、「Test Hide」ポートレットが元のページから削除され、デタッチされたウィンドウ内でのみこのポートレットにアクセスできることがわかります。

図 7. 更新された元のページ
更新された元のページ
更新された元のページ

ユース・ケースに一致するスキンの作成

ポートレットのデタッチ機能により、ユーザーはページからポートレットを一時的にデタッチし、新しいブラウザー・ウィンドウで開くことができます。元のページからポートレットをデタッチしても、一時的な変更がページの配置、つまりレイアウトに加えられるだけなので、この機能はポートレットをメイン・ページから切り離すときに役立ちます。

デタッチ機能をインプリメントするには、新しいカスタム・テーマを作成する必要があります。新しいテーマを作成する最も簡単な方法は、既存のテーマ・ディレクトリー(例えばIBMテーマ)をコピーすることです。また、既存の IBM スキン・ディレクトリーをコピーして、新しいカスタム・スキンも作成しなければなりません。以下で説明する例では、テーマ・ディレクトリーとスキン・ディレクトリーはどちらも「IBMdetach」という名前で、それぞれのディレクトリーは順に wps.ear/wps.war/themes/html と wps.ear/wps.war/skins/html です。

このインプリメンテーションでは、デタッチ機能はスキンに依存します。しかし、サンプルを少し変更するだけで、デフォルトの portletContextMenu.jsp ファイルを使用するすべてのスキンにこの機能を提供できます。名前が示すように、portletContextMenu.jsp ファイルはポートレット・コンテキスト・メニューのレンダリングを制御します。このコンテキスト・メニューには、ポートレットへの変更のきっかけとなるメニュー項目が含まれています。これらのアクションには、ポートレットの状態およびモードの変更が含まれているので、デタッチ・メニュー項目をここに追加することは自然な選択です。

ページ上の特定のポートレットだけをデタッチできるようにします。このサンプルでは、機能は新しいスキンだけに適用されるよう制限するので、カスタムのポートレット・コンテキスト・メニューを作成する必要があります。このメニューにより、他のすべてのスキンが通常どおり機能します。つまり、他のスキンはまだデフォルトのコンテキスト・メニューを呼び出すため、新しいメニュー・エントリーは含まれません。

これを行うには、次の手順で作業します。

  1. portletContextMenu.jsp ファイルをコピーし、「portletContextMenuCustom.jsp」という名前に変更します (これらのファイルは wps.ear/wps.war/themes/html/IBMdetach に存在する必要があります)。
  2. これで、カスタムのポートレット・コンテキスト・メニューを得られたので、この新しいポートレット・コンテキスト・メニューを呼び出すように新しい IBMdetach スキンを編集します。
    1. 新たに作成した IBMdetach スキンで control.jsp ファイルを編集します。まず、リスト 1 のコードを参照してください。
      リスト 1. IBMdetach スキンの control.jsp
      <%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. これを、リスト 2 で示すコードに変更します。
      リスト 2. IBMdetach スキンの変更された control.jsp
      <%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><%}%>

      リスト 2 のコードから生成された URL はポートレット・コンテキスト・メニューをレンダリングする JSP ファイルをターゲットとし、themeTemplate 属性はレンダリングする JSP ファイルを WebSphere Portal に指示します。また、themeTemplate の値は、ファイル拡張子を除き、JSP ファイルのファイル名に一致する必要があります。このサンプルでは、このファイル名は「portletContextMenuCustom」です。

  3. ポートレット・コンテキスト・メニューを変更し、デタッチ操作を実行するメニュー項目を追加します。
    • 次のインポート・ステートメントを portletContextMenuCustom.jsp ファイルに追加します。 <%@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"%>
    • リスト 3 に示したコードのセクションを portletContextMenuCustom.jsp ファイルで検索します。
      リスト 3. 最大化コントロール
      <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>
    • リスト 4 の太字で示したコードをリスト 3 のコードに追加します。
      リスト 4. 最大化とデタッチのコントロール
      <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>

      リスト 4 に示したコードは 3 つのことを実行します。まず、ページ上のポートレットをターゲットに指定します。次に、ポートレットをソロ・モード(そのポートレットだけを表示)でロードするように指示します。最後に、デタッチされた新規ウィンドウに名前を付けます。このコードは、デタッチされたウィンドウ内のポートレットの状態を、元のページのポートレットの状態から切り離します。デタッチされた新規ウィンドウに名前を付けることにより、「portlet_<%= windowID %>」というコードを使用して、プログラムでこのウィンドウを参照できます

    この段階では、スキンによって新規ウィンドウが開かれ、このウィンドウ内にはターゲットに指定したポートレットだけがプレーンなテーマを使用して表示されます。デタッチされた新規ウィンドウは同じ定義をまだ使用しているため、テーマまたはスキンに追加されたどのコードにも、両方のウィンドウでアクセスできます。

  4. 次に、リスト 5 のコードを default.jsp ファイルに追加することにより、メイン・ページでポートレットを非表示にするハンドラーを追加します。
    リスト 5. メイン・ウィンドウのハンドラー
    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";
    
    }

    このハンドラーも、メイン・ページからデタッチされて開かれたポートレット・ウィンドウを追跡することに注意してください。また、portletWindows 配列を使用して、ポートレットのデタッチされたウィンドウへのすべての参照を保持しています。

  5. 子ウィンドウから現行ウィンドウを識別できるようにするために、現行ウィンドウに名前を付けます。addToParentWindowList 関数は、デタッチされたウィンドウの名前を配列に追加します。IBMdetach スキンに用意されている既存の div タグを利用して、ターゲットのポートレットを非表示にします。
  6. 非表示にした後、ポートレットを再び表示する必要があります。これを行うには、リスト 6 に示した関数を default.jsp ファイルに追加します。
    リスト 6. ポートレットをメイン・ページに表示する関数
    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";	
    }

    removeFromWindowList 関数は、デタッチされたウィンドウの名前を配列から削除し、ポートレットを保持している div が表示されるようにします。

  7. 現在、Microsoft® Internet Explorer 7 では indexOf コードはサポートされていないため、その関数を機能させるために次のコードを追加する必要があります。この手順を行うには、リスト 7 のコードを関数外部のスクリプト・ブロックに追加し、ページのロード時にこのコードもロードされるようにします。
    リスト 7. IndexOf プロトタイプ
    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;
      };
    }

このインプリメンテーションでは、デタッチされたウィンドウが閉じられるとき、およびメイン・ポータル・ウィンドウでページが変更されるときに、ポートレットが表示される必要があります。この手順を実現するには、デタッチされたウィンドウから default.jsp ファイルで定義された関数を呼び出さなければなりません。リスト 8 のコードを control.jsp ファイルに追加します。

しかし、最初に、ウィンドウ名を取得し、現行ウィンドウがデタッチされたウィンドウか、あるいは処理中の元のページの一部であるかを判断する必要があります。デタッチされたウィンドウの場合は (サブストリングが portlet_ と等しい)、追加処理を実行できます。この例では、この処理はメイン・ウィンドウへのコールバックで、ポートレットを非表示にします。

リスト 8. デタッチされたウィンドウを処理する JavaScript
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)
      }
   }

window.open 関数を使用して、メイン・ウィンドウへの参照にアクセスします。default.jsp で定義されたメイン・ウィンドウ名を window.open 呼び出しへの引数として使用します。この参照を得ることにより、デタッチされたウィンドウから、メイン・ウィンドウに埋め込まれた関数を実行できます。addToParentWindowList 関数を呼び出し、メイン・ウィンドウのポートレット・ウィンドウ配列リストにポートレットを追加し、元のページでポートレットを非表示にします。次に、onunload ハンドラーを追加し、デタッチされたウィンドウが閉じられるときに removeFromParentWindowList 関数を呼び出すようにします。

次のコードは、onunload ハンドラーによって呼び出される関数のインプリメンテーションです。

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

この関数は、元のページの removeFromWindowList 関数を呼び出します。removeFromWindowList 関数は、デタッチされたウィンドウ名を配列から削除し、元のページでポートレットを再表示します。

これまでに現在のコードにより、ポートレットのデタッチ、メイン・ページでのポートレットの非表示、およびウィンドウを閉じるときのメイン・ポートレットの再表示が可能になりました。しかし、ユーザーが他のページに移動すると、デタッチされたポートレット・ウィンドウが開かれたまま残ってしまいます。これは望ましくない機能なので、リスト 9 に示す CloseAllWindows 関数を default.jsp ファイルに追加します。

リスト 9. CloseAllWindows 関数
function closeAllWindows() {
	for(var i=0; i < portletWindows.length; i++ ){
		var c = window.open('', portletWindows[i], 'featuresIfNotDefaults');
		c.window.close();
      
	}

}

最後に、リスト 10 の関数を onunload ハンドラーに追加し、現在のページから移動したときに closeAllWindows 関数が実行されるようにします。

リスト 10. closeAllWindows 用のハンドラーを追加する JavaScript
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);

これで、別のページに移動したとき、または更新を実行したときに、デタッチされて開かれているすべてのポートレットがクリーンアップされるようになりました。クリーンな環境が残されるとともに、それぞれのポートレットが個別に対話する方法が与えられるようになります。

まとめ

このユース・ケースでは、ポートレットをページからデタッチする機能の追加方法と、ページ定義を変更せずに、ポートレットを並べて比較する機能をユーザーに与える方法について説明しました。この手法により、柔軟性が増すとともに、個々のユース・ケースに合わせて WebSphere Portal ユーザー・インターフェース を拡張する方法の実例が得られます。

メモ: この記事では、ナビゲーション・モデル SPI と URL 生成ヘルパー・クラスを利用しました。これらのヘルパー・クラスは、この記事の「ダウンロード」セクションで入手できます。また、IBM Support technote「Portal 6.0 Advanced URL Generation Helper classes」でも提供されています。さらに、ページとポートレットの両方のストリング表現から ObjectId オブジェクトを取得するために、識別サービスを使用しました。

これらのトピックの詳細については、Information Center および developerWorks® の記事「Leveraging WebSphere Portal V6 programming model: Part 2. Advanced URL generation in themes and portlets」を参照してください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Lotus, WebSphere
ArticleID=366048
ArticleTitle=WebSphere Portal におけるテーマおよびスキンの開発
publish-date=09092008