PAL Portal (J2 デスクトップの調査)

続き・・・

postParseAnnotateHtmlは見てみると、必要であれば、URLを書き換えるといった感じだな。URLは基本的には、javascript:doAction(URL,entityId)かjavascript:doRender(URL,entityId)と言う感じになると思われる。

ここまで処理ができたら、どこまで戻るかというと、jetspeed.loadPortletWindowsまで戻り、次は、

jetspeed.page.retrieveAllMenus();   // BOZO: should not be happening here!

だな。というわけで、retrieveAllMenusを見てみよう。これはたぶん、タブメニューのことだろうな。

retrieveAllMenus: function()
{
var contentListener = new jetspeed.om.MenusAjaxApiCallbackContentListener( true );
this.retrieveMenuDeclarations( contentListener );
},

MenusAjaxApiCallbackContentListenerのコンストラクタは、

jetspeed.om.MenusAjaxApiCallbackContentListener = function( /* boolean */ retrieveEachMenu )
{
jetspeed.om.MenusAjaxApiContentListener.call( this, retrieveEachMenu );
};

で、

jetspeed.om.MenusAjaxApiContentListener = function( /* boolean */ retrieveEachMenu )
{
this.retrieveEachMenu = retrieveEachMenu;
};

となっている。おそらく、これも、dojo.io.bindで取得したデータを処理するリスナーだと思う。

次は、retrieveAllMenusのthis.retrieveMenuDeclarationsで、

retrieveMenuDeclarations: function( contentListener )
{
if ( contentListener == null )
contentListener = new jetspeed.om.MenusAjaxApiContentListener( false );
this.clearMenus();
var queryString = "?action=getmenus";
var psmlMenusActionUrl = this.getPsmlUrl() + queryString;
var mimeType = "text/xml";
var ajaxApiContext = new jetspeed.om.Id( "getmenus", { page: this } );
jetspeed.url.retrieveContent( psmlMenusActionUrl, contentListener, null, mimeType, ajaxApiContext, jetspeed.debugContentDumpIds );
},

まず、ここでのcontentListener には、既に定義したと思うので入らないと思う(でも、var contentListenerと言う感じで変数宣言みたいになっているから入るような気もするのだが・・・バグか?)。まぁ、入らないものとして次へ行くと、clearMenusで、

clearMenus: function()
{
this.menus = [];
},

メニュー配列menusを空にするのね。次にいって、psmlMenusActionUrlを作成している。actionをgetmenusとすれば、メニューがとれるんだね~。mimeTypeは text/xml としているな。これをすれば、domツリーでメニューがとれると言うことだな。ajaxApiContextは次のretrieveContentでリスナーのnotifySuccessの引数として渡されるものだね。そして、retrieveContentでdojo.io.bindを使って、コンテンツを取得して、リスナーを呼び出し。notifySuccessは、

notifySuccess: function( /* XMLDocument */ data, /* String */ requestUrl, domainModelObject )
{
var menuDefs = this.getMenuDefs( data, requestUrl, domainModelObject );
var menuContentListener = new jetspeed.om.MenuAjaxApiContentListener( this, menuDefs.length )
for ( var i = 0 ; i < menuDefs.length; i++ )
{
var mObj = menuDefs[i];
domainModelObject.page.putMenu( mObj );
if ( this.retrieveEachMenu )
{
domainModelObject.page.retrieveMenu( mObj.getName(), mObj.getType(), menuContentListener );
}
else if ( i == (menuDefs.length -1) )
{
if ( dojo.lang.isFunction( this.notifyFinished ) )
{
this.notifyFinished( domainModelObject );
}
}
}
},

まずは、menuDefsをセットするのにgetMenuDefsだな。

getMenuDefs: function( /* XMLDocument */ data, /* String */ requestUrl, domainModelObject )
{
var menuDefs = [];
var menuDefElements = data.getElementsByTagName( "menu" );
for( var i = 0; i < menuDefElements.length; i++ )
{
var menuType = menuDefElements[i].getAttribute( "type" );
var menuName = menuDefElements[i].firstChild.nodeValue;
menuDefs.push( new jetspeed.om.Menu( menuName, menuType ) );
}
return menuDefs;
},

戻り値となるmenuDefs配列を作成して、dojo.io.bindで取得したdomツリーから、menuタグを順に取得していって、type属性とmenuタグ内の文字列をjetspeed.om.Menuに渡して new しているな。

jetspeed.om.Menu = function( /* String */ menuName, /* String */ menuType )
{
this._is_parsed = false;
this.name = menuName;
this.type = menuType;
};

と言った感じ。menuDefs配列ができたところで、notifySuccessに返して、jetspeed.om.MenuAjaxApiContentListenerを作成。

jetspeed.om.MenuAjaxApiContentListener = function( /* jetspeed.om.MenusAjaxApiContentListener */ parentNotifyFinishedListener, /* int */ parentNotifyFinishedAfterIndex )
{
this.parentNotifyFinishedListener = parentNotifyFinishedListener;
this.parentNotifyFinishedAfterIndex = parentNotifyFinishedAfterIndex;
this.parentNotified = false;
this.notifyCount = 0;
};

ふむ。次は、メニューを一つずつ実行。まずは、putMenuを実行。

putMenu: function( /* jetspeed.om.Menu */ menuObj )
{
if ( ! menuObj ) return;
var menuName = ( menuObj.getName ? menuObj.getName() : null );
if ( ! menuName ) dojo.raise( "Page.addMenu argument is invalid - no menu-name can be found" );
this.menus[ menuName ] = menuObj;
},

つまり、メニュー名が存在していることを確認してから、jetspeed.om.Pageのメニュー配列に入れるわけね。

次に、this.retrieveEachMenuはtrueなので、retrieveMenuを実行。

retrieveMenu: function( /* String */ menuName, /* String */ menuType, contentListener )     {
if ( contentListener == null )
contentListener = new jetspeed.om.MenuAjaxApiCallbackContentListener();
var queryString = "?action=getmenu&name=" + menuName;
var psmlMenuActionUrl = this.getPsmlUrl() + queryString;
var mimeType = "text/xml";
var ajaxApiContext = new jetspeed.om.Id( "getmenu-" + menuName, { page: this, menuName: menuName, menuType: menuType } );
jetspeed.url.retrieveContent( psmlMenuActionUrl, contentListener, null, mimeType, ajaxApiContext, jetspeed.debugContentDumpIds );
},

まぁ、もう、見慣れたやつで、actionをgetmenuとして、dojo.io.bindを呼び出しね。リスナーのnotifySuccessは、

notifySuccess: function( /* XMLDocument */ data, /* String */ requestUrl, domainModelObject )
{
this.notifyCount++;
var menuObj = this.parseMenu( data, domainModelObject.menuName, domainModelObject.menuType );
domainModelObject.page.putMenu( menuObj );
if ( ! this.parentNotified && this.parentNotifyFinishedListener != null && this.notifyCount >= this.parentNotifyFinishedAfterIndex && dojo.lang.isFunction( this.parentNotifyFinishedListener.notifyFinished ) )
{
this.parentNotified = true;
this.parentNotifyFinishedListener.notifyFinished( domainModelObject );
}
if ( dojo.lang.isFunction( this.notifyFinished ) )
{
this.notifyFinished( domainModelObject, menuObj );
}
},

parseMenuを見てみると、

parseMenu: function( /* XMLNode */ node, /* String */ menuName, /* String */ menuType )
{
var menu = null;
var jsElements = node.getElementsByTagName( "js" );
if ( ! jsElements || jsElements.length > 1 )
dojo.raise( "unexpected zero or multiple <js> elements in menu xml" );
var children = jsElements[0].childNodes;
for ( var i = 0 ; i < children.length ; i++ )
{
var child = children[i];
if ( child.nodeType != dojo.dom.ELEMENT_NODE )
continue;
var childLName = child.nodeName;
if ( childLName == "menu" )
{
if ( menu != null )
dojo.raise( "unexpected multiple top level <menu> elements in menu xml" );
menu = this.parseMenuObject( child, new jetspeed.om.Menu() );
}
}
if ( menu != null )
{
if ( menu.name == null )
menu.name == menuName;
if ( menu.type == null )
menu.type = menuType;
}
return menu;
},

メニューの子ノードを取得しているようだな。つまり、タブメニューを作るのかと思っていたら、普通のメニューを作っている気が。参考までに、parseMenuObjectも見てみると、

parseMenuObject: function( /* XMLNode */ node, /* MenuOption */ mObj )
{
var constructObj = null;
var children = node.childNodes;
for ( var i = 0 ; i < children.length ; i++ )
{
var child = children[i];
if ( child.nodeType != dojo.dom.ELEMENT_NODE )
continue;
var childLName = child.nodeName;
if ( childLName == "menu" )
{
if ( mObj.isLeaf() )
dojo.raise( "unexpected nested <menu> in <option> or <separator>" );
else
mObj.addOption( this.parseMenuObject( child, new jetspeed.om.Menu() ) );
}
else if ( childLName == "option" )
{
if ( mObj.isLeaf() )
dojo.raise( "unexpected nested <option> in <option> or <separator>" );
else
mObj.addOption( this.parseMenuObject( child, new jetspeed.om.MenuOption() ) );
}
else if ( childLName == "separator" )
{
if ( mObj.isLeaf() )
dojo.raise( "unexpected nested <separator> in <option> or <separator>" );
else
mObj.addOption( this.parseMenuObject( child, new jetspeed.om.MenuOptionSeparator() ) );
}
else if ( childLName )
mObj[ childLName ] = ( ( child && child.firstChild ) ? child.firstChild.nodeValue : null );
}
if ( mObj.setParsed )
mObj.setParsed();
return mObj;
}

と言った感じ。パースが終わったら、notifySuccessで、また、putMenuをして、

if ( ! this.parentNotified && this.parentNotifyFinishedListener != null && this.notifyCount >= this.parentNotifyFinishedAfterIndex && dojo.lang.isFunction( this.parentNotifyFinishedListener.notifyFinished ) )
{
this.parentNotified = true;
this.parentNotifyFinishedListener.notifyFinished( domainModelObject );
}
if ( dojo.lang.isFunction( this.notifyFinished ) )
{
this.notifyFinished( domainModelObject, menuObj );
}

だけで、どう処理されるのかね・・・。

まず、parentNotifyFinishedListenerはjetspeed.om.MenusAjaxApiCallbackContentListenerだから、一通り処理が終われば、上のif文に入ることになるだろう。

次のif文は、jetspeed.om.MenuAjaxApiContentListenerだから、notifyFinishedメソッドはないな。というわけで、下のif文にはいることはない。

というわけで、MenusAjaxApiCallbackContentListenerのnotifyFinishedは、

notifyFinished: function( domainModelObject )
{
jetspeed.notifyRetrieveAllMenusFinished();
}

で、

jetspeed.notifyRetrieveAllMenusFinished = function()
{   // dojo.event.connect to this or add to your page content, one of the functions that it invokes ( doMenuBuildAll() or doMenuBuild() )
jetspeed.pageNavigateSuppress = true;
if ( dojo.lang.isFunction( window.doMenuBuildAll ) )
{
window.doMenuBuildAll();
}
var menuNames = jetspeed.page.getMenuNames();
for ( var i = 0 ; i < menuNames.length; i++ )
{
var menuNm = menuNames[i];
var menuWidget = dojo.widget.byId( jetspeed.id.MENU_WIDGET_ID_PREFIX + menuNm );
if ( menuWidget )
{
menuWidget.createJetspeedMenu( jetspeed.page.getMenu( menuNm ) );
}
}
jetspeed.pageNavigateSuppress = false;
};

はて、まず、始めの doMenuBuildAllなんて、見つからんな・・・。というわけで、スキップされるかね。

次にメニュー配列を順番に処理だ。MENU_WIDGET_ID_PREFIXは”jetspeed-menu-“だな・・・。。

menuNmで実際にどういう値が入っているのかわからないと、いまいち、この辺が理解できなくなってくる気が・・・。というわけで、http://www.marevol.com/marevol/ajaxapi?action=getmenus を実行して、実際の値を確認してみる。得られる結果は、

<js>
<status>success</status>
<action>getmenus</action>
<menus>
<menu type="standard">navigations</menu>
<menu type="standard">back</menu>
<menu type="standard">pages</menu>
<menu type="standard">breadcrumbs</menu>
<menu type="custom">site-navigations</menu>
<menu type="custom">additional-links</menu>
<menu type="custom">page-navigations</menu>
</menus>
</js>

という感じ。つまり、jetspeed.om.Pageのmenusに入っている値は、navigations, back, pages…という感じで、J2 の左側に表示されるメニューやタブメニュー、そんでもって、パンくずリストも含んでいたのね。というわけで、jetspeed-menu-にくっつく値は、これらの値か。

っで次に、上の各メニューでさらに取得した値は、http://www.marevol.com/marevol/ajaxapi?action=getmenu&name=pages という感じで、処理されていたので、みてみると、

<js>
<status>success</status>
<action>getmenu</action>
<menu>
<name>pages</name>
<title>pages</title>
<short-title>pages</short-title>
<skin>tabs</skin>
<hidden>false</hidden>
<selected>true</selected>
<option>
<type>page</type>
<title>ホーム</title>
<short-title>ホーム</short-title>
<skin>orange</skin>
<url>/default-page.psml</url>
<hidden>false</hidden>
<selected>true</selected>
</option>
<option>
<type>page</type>
<title>ノートパッド</title>
<short-title>ノートパッド</short-title>
<skin>orange</skin>
<url>/notepad.psml</url>
<hidden>false</hidden>
<selected>false</selected>
</option>
<option>
<type>page</type>
<title>アドレス帳</title>
<short-title>アドレス帳</short-title>
<skin>orange</skin>
<url>/addresslist.psml</url>
<hidden>false</hidden>
<selected>false</selected>
</option>
<option>
<type>page</type>
<title>Yahoo検索</title>
<short-title>Yahoo検索</short-title>
<skin>orange</skin>
<url>/yws.psml</url>
<hidden>false</hidden>
<selected>false</selected>
</option>
<option>
<type>page</type>
<title>Hello World</title>
<short-title>Hello World</short-title>
<skin>orange</skin>
<url>/helloworld.psml</url>
<hidden>false</hidden>
<selected>false</selected>
</option>
<option>
<type>page</type>
<title>チャート</title>
<short-title>チャート</short-title>
<skin>orange</skin>
<url>/chart.psml</url>
<hidden>false</hidden>
<selected>false</selected>
</option>
</menu>
</js>

という感じ。なるほどね。これで、この辺の処理が納得できると思う。

blue.jspで、タグがあることを確認してみよう。

$ grep "jetspeed-menu-" src/webapp/desktop-themes/blue/blue.jsp
<div widgetId="jetspeed-menu-pages" dojoType="PortalTabContainer" style="width: 100%; height: 30px; margin-top: 2px; margin-left: -1px"></div>
<div widgetId="jetspeed-menu-navigations" dojoType="PortalAccordionContainer" style=""></div>

というわけで、menuWidget.createJetspeedMenuが呼ばれるわけね。

createJetspeedMenuはsrc/webapp/javascript/desktop/widget/PortalTabContainer.jsとsrc/webapp/javascript/desktop/widget/PortalAccordionContainer.jsに含まれている。どっちも確認するのは疲れるから、PortalTabContainerの方を見ることにしよう。

createJetspeedMenu: function( /* jetspeed.om.Menu */ menuObj )
{
if ( ! menuObj ) return;
var menuOpts = menuObj.getOptions();
for ( var i = 0 ; i < menuOpts.length ; i++ )
{
var menuOption = menuOpts[i];
if ( menuOption.isLeaf() && menuOption.getUrl() && ! menuOption.isSeparator() )
{
this.addTab( menuOption );
}
}
},

順番にjetspeed.om.Menu内のオプションに対して実行していくのね。実行されるthis.addTabを見てみよう。

addTab: function( /* jetspeed.om.MenuOption */ menuOpt )
{         if ( ! menuOpt ) return;
this.js_addingTab = true;
var tabDomNode = document.createElement( "div" );
var tab = new dojo.widget.HtmlWidget();   // create a fake widget so that widget.addedTo doesn't bomb when we call this.addChild() below
tab.domNode = tabDomNode;
tab.menuOption = menuOpt;
tab.label = menuOpt.getShortTitle();
this.addChild( tab );
//dojo.debug( "PortalTabContainer.addTab" );         if ( jetspeed.page.equalsPageUrl( menuOpt.getUrl() ) )
{
this.selectTab( tab );   // this.selectedTab
this.selectedTab = null;  // to keep it from matching the fake widgets with no widgetdI
}
this.js_addingTab = false;
},

タブをdojo.widget.HtmlWidgetウィジェットとして作成して、addChildするのね。っで、どこへaddChildかと思ったら、

dojo.inherits(jetspeed.ui.widget.PortalTabContainer, dojo.widget.html.TabContainer);

TabContainerなのか。つまり、dojo.widget.html.TabContainerの動作とあまり変わらんと言うことかね。TabContainerを見てみると、addChildをしたら、_setupTabを呼び出して、タグを作っている模様。参考までに、

_setupTab: function(tab){
tab.domNode.style.display="none";
// Create label
tab.div = document.createElement("div");
dojo.html.addClass(tab.div, "dojoTabPaneTab");
var span = document.createElement("span");
span.innerHTML = tab.label;
dojo.html.disableSelection(span);
if (this.closeButton=="tab") {
var img = document.createElement("div");
dojo.html.addClass(img, "dojoTabPaneTabClose");
var self = this;
dojo.event.connect(img, "onclick", function(evt){ self._runOnCloseTab(tab); dojo.event.browser.stopEvent(evt); });
dojo.event.connect(img, "onmouseover", function(){ dojo.html.addClass(img,"dojoTabPaneTabCloseHover"); });
dojo.event.connect(img, "onmouseout", function(){ dojo.html.removeClass(img,"dojoTabPaneTabCloseHover"); });
span.appendChild(img);
}
tab.div.appendChild(span);
this.dojoTabLabels.appendChild(tab.div);
var self = this;
dojo.event.connect(tab.div, "onclick", function(){ self.selectTab(tab); });
if(!this.selectedTabWidget || this.selectedTab==tab.widgetId || tab.selected){
this.selectedTabWidget = tab;
} else {
this._hideTab(tab);
}
},

と言う感じで、今まで見てきたけど、メニューの処理が一通り終わると、表示に関する処理が完了して、ブラウザ上で表示されていることでしょう。結構、じっくりと動作を見てきたので、だいたい理解できた。というわけで、skyデスクトップテーマを作っていきましょ~。

コメントを残す

メールアドレスが公開されることはありません。