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

続き・・・

jetspeed.url.retrieveContentは既に確認しているので、詳細は飛ばすが、jetspeed.url.retrieveContentはアクセスに成功すると、contentListener.notifySuccess( data, requestUrl, domainModelObject ) ;を呼び出す。ここで、contentListenerは、PortletContentListenerなので、

jetspeed.om.PortletContentListener.prototype =
{
notifySuccess: function( /* String */ portletContent, /* String */ requestUrl, /* Portlet */ portlet )
{
portlet.setPortletContent( portletContent, requestUrl );
},
notifyFailure: function( /* String */ type, /* String */ error, /* String */ requestUrl, /* Portlet */ portlet )
{
dojo.debug( "PortletContentListener notifyFailure url=" + requestUrl + " type=" + type + " error=" + error ) ;
}
};

portletContentはStringっぽいな。retrieveContentでmimeTypeがtext/htmlとしてアクセスしたからかね。setPortletContentを見てみよう。

setPortletContent: function( portletContent, renderUrl )
{
var windowWidget = this.getPortletWindow();
if ( windowWidget )
{
windowWidget.setPortletContent( portletContent, renderUrl );
}
},

まず、getPortletWindowは

getPortletWindow: function()
{
var windowWidgetId = this.getProperty( jetspeed.id.PORTLET_PROP_WIDGET_ID );
if ( windowWidgetId )
return dojo.widget.byId( windowWidgetId );
return null;
},

dojoのポートレットウィンドウのウィジェットを取得しているのね。それをsetPortletContentに戻して、そのウィジェットのsetPortletContentを見てみると、

setPortletContent: function( html, url )
{
var initialHtmlStr = html.toString();
if ( ! this.getInitProperty( jetspeed.id.PORTLET_PROP_EXCLUDE_PCONTENT ) )
{
initialHtmlStr = '<div class="PContent" >' + initialHtmlStr + '</div>';   // BOZO: get this into the template ?
}
var ppR = null;
if ( this.portlet )
{
ppR = this.portlet.preParseAnnotateHtml( initialHtmlStr, url );
}
else
{
ppR = jetspeed.ui.preParseAnnotateHtml( initialHtmlStr, url );
}
//this.executeScripts = true;
var setContentObj = { titles: [], scripts: ppR.preParsedScripts, linkStyles: [], styles: [], remoteScripts: ppR.preParsedRemoteScripts, xml: ppR.preParsedContent, url: url, requires: [] };
this.setContent( setContentObj );
if ( setContentObj.scripts && setContentObj.scripts.length > 0 )
{   // do inline scripts  - taken from dojo ContentPane.js _executeScripts
var repl = null;
for( var i = 0; i < setContentObj.scripts.length; i++ )
{
// not sure why comment and carraige return clean is needed
// but better safe than sorry so we keep it, Fredrik
// Clean up content: remove inline script  comments
repl = new RegExp('//.*?$', 'gm');
setContentObj.scripts[i] = setContentObj.scripts[i].replace(repl, '\n');
// BOZO: despite the comment above from the dojo code, we cannot do this (carriage returns are syntatically required in javascript)
// Clean up content: remove carraige returns
//repl = new RegExp('[\n\r]', 'g');
//setContentObj.scripts[i] = setContentObj.scripts[i].replace(repl, ' ');
// Execute commands
if ( jetspeed.debug.setPortletContent )
dojo.debug( "setPortletContent [" + ( this.portlet ? this.portlet.entityId : this.widgetId ) + "] script: " + setContentObj.scripts[i] );
eval( setContentObj.scripts[i] );
}
}
else
{
if ( jetspeed.debug.setPortletContent )
dojo.debug( "setPortletContent [" + ( this.portlet ? this.portlet.entityId : this.widgetId ) + "]" );
}
this._executeScripts( { scripts: [], remoteScripts: setContentObj.remoteScripts } );
if ( this.portlet )
this.portlet.postParseAnnotateHtml( this.containerNode );
}

まず、始めのif文は、jetspeed.id.PORTLET_PROP_EXCLUDE_PCONTENTはnullだろうから、入って、ポートレットのコンテンツに対して、PContentクラスを持つ div タグを追加するだろう。

次に、this.portletはnullでないので、this.portlet.preParseAnnotateHtmlを実行。

preParseAnnotateHtml: function( /* String */ portletContent )
{
return jetspeed.ui.preParseAnnotateHtml( portletContent );
},

jetspeed.ui.preParseAnnotateHtmlは、

jetspeed.ui.preParseAnnotateHtml = function( /* String */ initialHtmlStr, /* String */ url )
{   // deal with embedded script tags -  /=/=/=/=/=  taken from dojo ContentPane.js  splitAndFixPaths()  =/=/=/=/=/
var scripts = [];
var remoteScripts = [];
// cut out all script tags, stuff them into scripts array
var match = [];
while ( match )
{
match = initialHtmlStr.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);
if(!match){ break; }
if(match[1]){
attr = match[1].match(/src=(['"]?)([^"']*)\1/i);
if ( attr )
{
// remove a dojo.js or dojo.js.uncompressed.js from remoteScripts
if ( (attr[2].search(/\/?\bdojo.js(?:\.uncompressed.js)?/i) != -1) && (dojo.hostenv.getBaseScriptUri() == attr[2].match(/[.\/]*/)[0]) )
{
dojo.debug("Security note! inhibit:"+attr[2]+" from  beeing loaded again.");
}
else
{
remoteScripts.push( attr[2] );
}
}
}
if ( match[2] )
{
// get rid of html comment blanket
var scriptText = match[2].replace(/^\s*<!--/, "");
scriptText = scriptText.replace(/-->\s*$/, "");
scriptText = scriptText.replace(/function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g, "window.$1 = function(" );
// strip out all djConfig variables from script tags nodeValue
// this is ABSOLUTLY needed as reinitialize djConfig after dojo is initialised
// makes a dissaster greater than Titanic
scripts.push(scriptText.replace(/(?:var )?\bdjConfig\b(?:[\s]*=[\s]*\{[^}]+\}|\.[\w]*[\s]*=[\s]*[^;\n]*)?;?|dojo.hostenv.writeIncludes\(\s*\);?/g, ""));
}
initialHtmlStr = initialHtmlStr.replace(/<script[^>]*>[\s\S]*?<\/script>/i, "");
}
//dojo.debug( "= = = = = =  annotated content for: " + ( url ? url : "unknown url" ) );
//dojo.debug( initialHtmlStr );
//if ( scripts.length > 0 )
//{
//    dojo.debug( "      = = =  script content for: " + ( url ? url : "unknown url" ) );
//    for ( var i = 0 ; i < scripts.length; i++ )
//        dojo.debug( "      =[" + (i+1) + "]:" + scripts[i] );
//}
//     /=/=/=/=/=  end of taken from dojo ContentPane.js  splitAndFixPaths()  =/=/=/=/=/
//dojo.debug( "preParse  scripts: " + ( scripts ? scripts.length : "0" ) + " remoteScripts: " + ( remoteScripts ? remoteScripts.length : "0" ) );
return { preParsedContent: initialHtmlStr, preParsedScripts: scripts, preParsedRemoteScripts: remoteScripts };
};

やっていることをまとめると、ポートレットのコンテンツにJavaScriptのタグが含まれているなら、それをscriptsかremoteScriptsに入れる。remoteScriptsはjsファイルみたいな呼び出しをされていた場合みたいだな。

それがポートレットウィンドウのsetPortletContentでppRにセットする。そしたら、setContentObjを作成して、setContentを呼び出す。このsetContentはdojo.widget.html.ContentPaneのメソッド。コンテンツは、ここで、this.containerNodeまたはthis.domNodeにくっつけることになる。続いて、javascriptの処理。まず、scriptタグで記述されているスクリプトを順にevalで実行。続いて、src属性でscriptタグにあるものをthis._executeScriptsに渡す。この_executeScriptsもdojo.widget.html.ContentPaneのメソッド。dojo.io.bindを使って、取得して処理するようだ。そして、最後に、this.portlet.postParseAnnotateHtmlを実行。

postParseAnnotateHtml: function( /* DOMNode */ containerNode )
{
if ( containerNode )
{
var cNode = containerNode;
var formList = cNode.getElementsByTagName( "form" );
var debugOn = jetspeed.debug.postParseAnnotateHtml;
if ( formList )
{
for ( var i = 0 ; i < formList.length ; i++ )
{
var cForm = formList[i];
var cFormAction = cForm.action;
var cFormPortletEntityId = this.entityId;  // BOZO:can I assume that it is always my entity-id (ignoring the one in parsedPseudoUrl)
var parsedPseudoUrl = this.parseJSPseudoUrlActionRender( cFormAction );
var submitOperation = parsedPseudoUrl.operation;
if ( submitOperation == this.PORTLET_REQUEST_ACTION || submitOperation == this.PORTLET_REQUEST_RENDER )
{
var replacementActionUrl = this._generateJSPseudoUrlActionRender( parsedPseudoUrl );
if ( replacementActionUrl == cFormAction )
{
if ( debugOn )
dojo.debug( "postParseAnnotateHtml [" + this.entityId + "] adding onSubmit (portlet-" + submitOperation + ") and leaving form action as is: " + cFormAction );
}
else
{
cForm.action = replacementActionUrl;
if ( debugOn )
dojo.debug( "postParseAnnotateHtml [" + this.entityId + "] adding onSubmit (portlet-" + submitOperation + ") and changing form action attribute from: " + cFormAction + " to: " +  replacementActionUrl );
}
this._addOnSubmitActionRender( cForm, cFormPortletEntityId, submitOperation );
}
else
{
if ( djConfig.isDebug )  // want to see this, for now
dojo.debug( "postParseAnnotateHtml [" + this.entityId + "] form action attribute doesn't match annotation criteria, leaving as is: " + cFormAction ) ;
}
}
}
var aList = cNode.getElementsByTagName( "a" );
if ( aList )
{
for ( var i = 0 ; i < aList.length ; i++ )
{
var aNode = aList[i];
var aHref = aNode.href;
var parsedPseudoUrl = this.parseJSPseudoUrlActionRender( aHref );
var replacementHref = this._generateJSPseudoUrlActionRender( parsedPseudoUrl );
if ( ! replacementHref )
{
if ( debugOn )
dojo.debug( "postParseAnnotateHtml [" + this.entityId + "] leaving href as is: " + aHref );
}
else if ( replacementHref == aHref )
{
if ( debugOn )
dojo.debug( "postParseAnnotateHtml [" + this.entityId + "] href parsed and regenerated identically: " + aHref );
}
else
{
if ( debugOn )
dojo.debug( "postParseAnnotateHtml [" + this.entityId + "] href parsed, replacing: " + aHref + " with: " + replacementHref );
aNode.href = replacementHref;
}
}
}
}
},

長いな・・・。これをざっと見た感じでは、formとaタグのリンクの書き換えだな。

今日はここまで・・・

エンコーディングがハードコード?

何かメーリングリストにエンコーディングがハードコードされていると言っているけど、質問自体が中途半端で何をしたいのかよくわからん。何をどうしたいのかわからんのに加えて、大して確認せずに、だめだみたいなこと言い出しているので、答えるのが面倒になってきた。そもそもどういう状況でGETによるパラメータ私を使いたいのだろうか(状況がわからんと対応しようがないのだけど)。海外から来る質問みたいのは、こんな中途半花質問ばっかりな気がするのは気のせいだろうか・・・。

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

jetspeed.ui._loadPortletWindowsから・・・

jetspeed.ui._loadPortletWindows = function( /* Portlet[] */ portletArray, windowsToRender, portletWindowFactory )
{
if ( portletArray )
{
for ( var i = 0; i < portletArray.length; i++ )
{
var portlet = portletArray[i];
if ( jetspeed.debugPortletEntityIdFilter )
{
if ( ! dojo.lang.inArray( jetspeed.debugPortletEntityIdFilter, portlet.entityId ) )
portlet = null;
}
if ( portlet )
{
windowsToRender.push( portlet );
portlet.createPortletWindow( portletWindowFactory, null );
}
}
}
};

デバッグの部分は気にしないで進むと、ポートレット配列があれば、ポートレットを順にwindowsToRender配列に入れて、createPortletWindowを呼び出していく。そのcreatePortletWindowは

createPortletWindow: function( portletWindowFactory, portletContentListener )
{
jetspeed.ui.createPortletWindow( this, portletWindowFactory, portletContentListener );
},

だから、jetspeed.ui.createPortletWindowに行き、

jetspeed.ui.createPortletWindow = function( windowConfigObject, portletWindowFactory, portletContentListener )
{
if ( portletWindowFactory == null )
{
var winPositionStatic = windowConfigObject.getProperty( jetspeed.id.PORTLET_PROP_WINDOW_POSITION_STATIC );
if ( winPositionStatic == null )
winPositionStatic = ( jetspeed.prefs.windowTiling ? true : false );    // BOZO: what to do about setting the value here ( putProperty )
if ( ! winPositionStatic )
portletWindowFactory = new jetspeed.om.PortletWindowFactory() ;
else
portletWindowFactory = new jetspeed.om.PortletTilingWindowFactory() ;
}
var windowWidget = dojo.widget.byId( windowConfigObject.getProperty( jetspeed.id.PORTLET_PROP_WIDGET_ID ) );   // get existing window widget
if ( windowWidget )
{
portletWindowFactory.reset( windowConfigObject, windowWidget ) ;
}
else
{
windowWidget = portletWindowFactory.create( windowConfigObject ) ;
}
if ( windowWidget )
{
portletWindowFactory.layout( windowConfigObject, windowWidget );
}
};

ここで、引数を確認すると、windowConfigObjectはポートレットで、 portletWindowFactoryとportletContentListenerはnullと思われる。というわけで、始めのif文には入るわけだな。っで、winPositionStaticは true と思われるから、jetspeed.om.PortletTilingWindowFactoryの方になるのかね。そのコンストラクタでは何もしないようだ。

jetspeed.om.PortletTilingWindowFactory = function()
{
};

というわけで、次に進み、windowConfigObject.getProperty( jetspeed.id.PORTLET_PROP_WIDGET_ID )は、jetspeed.om.Portletのinitializeで、jetspeed.id.PORTLET_WINDOW_ID_PREFIX + this.entityIdを入れている。まぁ、この段階では、windowWidgetはnullだろうから、portletWindowFactory.createの方に入って、

create: function( windowConfigObject )
{
return jetspeed.ui.createPortletWindowWidget( windowConfigObject );
},

っで、

jetspeed.ui.createPortletWindowWidget = function( windowConfigObject, createWidgetParams )
{
if ( ! createWidgetParams )
createWidgetParams = {};
if ( windowConfigObject instanceof jetspeed.om.Portlet )
{
createWidgetParams.portlet = windowConfigObject;
}
else
{
jetspeed.ui.widget.PortletWindow.prototype.staticDefineAsAltInitParameters( createWidgetParams, windowConfigObject );
}
// NOTE: other parameters, such as widgetId could be set here (to override what PortletWindow does)
var nWidget = dojo.widget.createWidget( 'PortletWindow', createWidgetParams );
return nWidget;
};

createWidgetParamsはnullだろうから、始めのif文に入り、windowConfigObjectはjetspeed.om.Portletだから、createWidgetParams.portletにセットされ、dojo.widget.createWidgetが実行される。createWidgetについて、たいした情報はないけど、http://dojo.jot.com/WidgetExamples あたりだろうか。

PortletWindow については、src/webapp/javascript/desktop/widget/PortletWindow.jsに定義してあるみたい。そのコンストラクタは、

jetspeed.ui.widget.PortletWindow = function()
{
jetspeed.ui.widget.BaseFloatingPane.call( this );
this.widgetType = "PortletWindow";
this.portletInitialized = false;
};

っで、親がBaseFloatingPaneなので、

jetspeed.ui.widget.BaseFloatingPane = function(){
dojo.widget.html.ContentPane.call(this);
}

その親がdojoのContentPaneなわけね。こんな感じで、ウィジェットが作られて、jetspeed.ui.createPortletWindowに戻る。

次に

if ( windowWidget )
{
portletWindowFactory.layout( windowConfigObject, windowWidget );
}

だから、そのlayoutは

layout: function( windowConfigObject, /* PortletWindow */ portletWindowWidget )
{
var windowPositionStatic = windowConfigObject.getProperty( jetspeed.id.PORTLET_PROP_WINDOW_POSITION_STATIC );
if ( ! windowPositionStatic )
{
portletWindowWidget.domNode.style.position = "absolute";
var addToElmt = document.getElementById( jetspeed.id.DESKTOP );
addToElmt.appendChild( portletWindowWidget.domNode );
}
else
{
var useColumnElmt = null;
var useColumnIndex = -1;
var preferredColumn = windowConfigObject.getProperty( jetspeed.id.PORTLET_PROP_COLUMN );
if ( preferredColumn >= 0 && preferredColumn < jetspeed.columns.length )
{
useColumnIndex = preferredColumn;
useColumnElmt = jetspeed.columns[ useColumnIndex ];
}
if ( useColumnIndex == -1 )
{   // select a column based on least populated (least number of child nodes)
for ( var i = 0 ; i < jetspeed.columns.length ; i++ )
{
var columnElmt = jetspeed.columns[i];
if ( ! columnElmt.hasChildNodes() )
{
useColumnElmt = columnElmt;
useColumnIndex = i;
break;
}
if ( useColumnElmt == null || useColumnElmt.childNodes.length > columnElmt.childNodes.length )
{
useColumnElmt = columnElmt;
useColumnIndex = i;
}
}
}
if ( useColumnElmt )
{
useColumnElmt.appendChild( portletWindowWidget.domNode );
}
}
}
}

位置が固定じゃないなら、if文に入り、jetspeed.id.DESKTOP属性を持つdivタグにdomNodeを貼り付けて、位置が固定なら、列のdivタグを見つけて、それに貼り付ける。はて、domNodeはどの段階で作られたのだろうか・・・。

この謎を解くためには、http://manual.dojotoolkit.org/widget/Widget.html にあるように、いくつか上書きするメソッド見たいのがあるみたい。PortletWindowを見てみると、postCreate がある。つまり、ウィジェットを作ると、こいつが呼ばれると思われる。というわけで、見ていこう。

postCreate: function( args, fragment, parentComp )
{
jetspeed.ui.widget.PortletWindow.superclass.postCreate.call( this );
this.dragSource = new jetspeed.ui.widget.PortletWindowDragMoveSource( this );
if ( this.constrainToContainer )
{
this.dragSource.constrainTo();
}
this.dragSource.setDragHandle( this.titleBar );
this.domNode.id = this.widgetId;  // BOZO: must set the id here - it gets defensively cleared by dojo
this.portletPostCreate();
this.createTitleBarContextMenu();
if ( jetspeed.debug.createWindow )             dojo.debug( "createdWindow [" + ( this.portlet ? this.portlet.entityId : this.widgetId ) + "]" + " width=" + this.domNode.style.width + " height=" + this.domNode.style.height + " left=" + this.domNode.style.left + " top=" + this.domNode.style.top ) ;
this.portletInitialized = true;
var initWindowState = this.getInitProperty( jetspeed.id.PORTLET_PROP_WINDOW_STATE );
if ( initWindowState == "minimized" )
this.minimizeWindow();
},

まぁ、始めの~superclass.postCreateは、BaseFloatingPaneが持っていないので、その親に行くのかな。BaseFloatingPaneの親はdojoのウィジェットだから、そこまでは追うのはやめておこ。っで次には、ドラッグアンドドロップ関連のウィジェットを作るみたいだな。

jetspeed.ui.widget.PortletWindowDragMoveSource = function( /* jetspeed.ui.widget.PortletWindow */ portletWindow, type)
{
this.portletWindow = portletWindow;
dojo.dnd.HtmlDragMoveSource.call(this, portletWindow.domNode, type);
};
dojo.inherits( jetspeed.ui.widget.PortletWindowDragMoveSource, dojo.dnd.HtmlDragMoveSource );
dojo.lang.extend( jetspeed.ui.widget.PortletWindowDragMoveSource, {
onDragStart: function()
{
// BOZO: code copied from dojo.dnd.HtmlDragMoveSource.onDragStart to change dragObject
var dragObj = new jetspeed.ui.widget.PortletWindowDragMoveObject( this.portletWindow, this.dragObject, this.type );
if ( this.constrainToContainer )
{
dragObj.constrainTo( this.constrainingContainer );
}
return dragObj;
},
onDragEnd: function()
{
}
});

まぁ、以上のような感じで、親がdojo.dnd.HtmlDragMoveSourceということで、ここではこんな感じかとわかれば、深追いする必要はないだろう。this.dragSource.setDragHandle( this.titleBar );では、たぶん、タイトルバーでのドラッグアンドドロップを有効にするのだろう(推測)。次に、this.domNode.id = this.widgetId;としているが、つまり、この段階で、既に、domNodeが生成されていると言うことになる。なんだかわかりにくいので、ちょっと、dojoのウィジェット処理について、確認しよう。現在のPortletWindowの継承図は以下のようになる。

dojo.widget.Widget
↑
dojo.widget.DomWidget
↑
dojo.widget.HtmlWidget
↑
dojo.widget.html.ContentPane
↑
jetspeed.ui.widget.BaseFloatingPane
↑
jetspeed.ui.widget.PortletWindow

では、ウィジェットはどう作られるのだろうか。現状の情報から推測すると、createで作成されると思われる。これらのウィジェットを確認すると、dojo.widget.Widgetしかcreateを持っていないので、それが呼ばれているみたい。createは、

create: function(args, fragment, parentComp){
// dojo.debug(this.widgetType, "create");
this.satisfyPropertySets(args, fragment, parentComp);
// dojo.debug(this.widgetType, "-> mixInProperties");
this.mixInProperties(args, fragment, parentComp);
// dojo.debug(this.widgetType, "-> postMixInProperties");
this.postMixInProperties(args, fragment, parentComp);
// dojo.debug(this.widgetType, "-> dojo.widget.manager.add");
dojo.widget.manager.add(this);
// dojo.debug(this.widgetType, "-> buildRendering");
this.buildRendering(args, fragment, parentComp);
// dojo.debug(this.widgetType, "-> initialize");
this.initialize(args, fragment, parentComp);
// dojo.debug(this.widgetType, "-> postInitialize");
this.postInitialize(args, fragment, parentComp);
// dojo.debug(this.widgetType, "-> postCreate");
this.postCreate(args, fragment, parentComp);
// dojo.debug(this.widgetType, "done!");
return this;
},

ふむ、なるほど、こういう順番で呼ばれるのね。postCreateは本当に最後だな。どうやら見てみると、domNodeはbuildRenderingのところで作られているっぽい。一連のウィジェットの中では、dojo.widget.DomWidgetがそれを持つ。それは、以下のようなもの。

buildRendering: function(args, frag){
// DOM widgets construct themselves from a template
var ts = dojo.widget._templateCache[this.widgetType];
if(
(!this.preventClobber)&&(
(this.templatePath)||
(this.templateNode)||
(
(this["templateString"])&&(this.templateString.length)
)||
(
(typeof ts != "undefined")&&( (ts["string"])||(ts["node"]) )
)
)
){
// if it looks like we can build the thing from a template, do it!
this.buildFromTemplate(args, frag);
}else{
// otherwise, assign the DOM node that was the source of the widget
// parsing to be the root node
this.domNode = this.getFragNodeRef(frag);
}
this.fillInTemplate(args, frag);    // this is where individual widgets
// will handle population of data
// from properties, remote data
// sets, etc.
},

たぶん、buildFromtemplate で domNodeがテンプレートから作成されると思われる。ここで、最後のfillInTemplateとあるが、このメソッドは、PortletWindowにも定義されているので、それが呼び出されるだろう。

fillInTemplate: function( args, frag )
{
this.portletFillInTemplate();          //dojo.debug( "fillInTemplate-a [" + this.widgetId + "] containerNode-outerwidth: " + dojo.style.getOuterWidth( this.containerNode ) + " containerNode-contentwidth: " + dojo.style.getContentWidth( this.containerNode ) + " domNode-outerwidth: " + dojo.style.getOuterWidth( this.domNode ) );         jetspeed.ui.widget.PortletWindow.superclass.fillInTemplate.call( this, args, frag );         //dojo.debug( "fillInTemplate-b [" + this.widgetId + "] containerNode-outerwidth: " + dojo.style.getOuterWidth( this.containerNode ) + " containerNode-contentwidth: " + dojo.style.getContentWidth( this.containerNode ) + " domNode-outerwidth: " + dojo.style.getOuterWidth( this.domNode ) );
},

デバッグの部分を無視すると、portletFillInTemplateだから、

portletFillInTemplate: function()
{
var isResizable = this.resizable;
if ( isResizable )
{
this.resizeBar.style.display = "block";
var rh = null;
var rhWidgetId = this.widgetId + "_resize";
if ( ! this.portletInitialized )
rh = dojo.widget.createWidget( "PortletWindowResizeHandle", { targetElmId: this.widgetId, id: rhWidgetId, portletWindow: this } );
else
rh = dojo.widget.byId( rhWidgetId );
if ( rh )
{
if ( this.windowPositionStatic && dojo.render.html.mozilla )  // dojo.render.html.ie
rh.domNode.style.position = "static";
else
rh.domNode.style.position = "absolute";
if ( ! this.portletInitialized )
this.resizeBar.appendChild( rh.domNode );
}
}
},

ざっとみると、リサイズが起きたときのハンドラが貼り付けられたように見える。まぁ、これ以上ここでは追わなくても大丈夫だろう。

あと、dojo.widget.Widgetのcreateで気になるのは、postMixInPropertiesかね。これまた、PortletWindowにある。

postMixInProperties: function( args, fragment, parentComp )
{
jetspeed.ui.widget.PortletWindow.superclass.postMixInProperties.call( this );
this.portletIndex = this._getNextIndex();
var initWidgetId = this.getInitProperty( jetspeed.id.PORTLET_PROP_WIDGET_ID );         if ( this.portlet )         {             if ( this.widgetId )             {                 dojo.raise( "PortletWindow.widgetId (" + this.widgetId + ") should not be assigned directly" );             }             if ( ! initWidgetId )             {
dojo.raise( "PortletWindow.widgetId is not defined for portlet [" + this.portlet.entityId + "] - Portlet.initialize may not have been called" );
}
this.widgetId = initWidgetId;
}
else
{
if ( initWidgetId )
this.widgetId = initWidgetId;
else if ( ! this.widgetId )
this.widgetId = jetspeed.id.PORTLET_WINDOW_ID_PREFIX + this.portletIndex;
}
this._incrementNextIndex();
this.templatePath = jetspeed.ui.getDefaultFloatingPaneTemplate();
this.portletMixinProperties( fragment );
//dojo.debug("PortletWindow  widgetId=" + this.widgetId + "  windowtheme=" + windowtheme + "  templateCssPath=" + this.templateCssPath);
},

this.portletIndexをセットして、idがおかしければ、dojo.raiseでエラーをあげる。dojo.raiseについては、http://manual.dojotoolkit.org/dojo.html を参照。そんで、インデックスをインクリメントしておき、templatePathを取得。jetspeed.ui.getDefaultFloatingPaneTemplateは、

jetspeed.ui.getDefaultFloatingPaneTemplate = function()
{
return new dojo.uri.Uri(jetspeed.url.basePortalDesktopUrl() + "/javascript/desktop/widget/HtmlFloatingPane.html");   // BOZO: improve this junk
};

一応、HtmlFloatingPane.htmlも見てみると、

<div id="${this.widgetId}" class="dojoFloatingPane" >  <!-- dojoAttachEvent="onMouseDown" -->
<div id="${this.widgetId}_titleBar" dojoAttachPoint="titleBar" class="dojoFloatingPaneTitleBar"  dojoAttachEvent="onMouseDown;onMouseOver:titleMouseOver;onMouseOut:titleMouseOut" style="display:none">
<img dojoAttachPoint="titleBarIcon"  class="dojoFloatingPaneTitleBarIcon">
<div dojoAttachPoint="closeAction" dojoAttachEvent="onClick:closeWindow"
class="dojoFloatingPaneCloseIcon"></div>
<div dojoAttachPoint="maximizeAction" dojoAttachEvent="onClick:maximizeWindow"
class="dojoFloatingPaneMaximizeIcon"></div>
<div dojoAttachPoint="restoreAction" dojoAttachEvent="onClick:restoreWindow"
class="dojoFloatingPaneRestoreIcon"></div>
<div dojoAttachPoint="minimizeAction" dojoAttachEvent="onClick:minimizeWindow"
class="dojoFloatingPaneMinimizeIcon"></div>
<div dojoAttachPoint="titleBarText" class="dojoFloatingPaneTitleText">${this.title}</div>
</div>
<div dojoAttachPoint="containerNode" class="dojoFloatingPaneClient"></div>
<div dojoAttachPoint="resizeBar" class="dojoFloatingPaneResizebar"></div>
</div>

と言う感じ。つまり、これにあわせてスタイルシートを書けば、そのデザインになるのね。

っで、戻って、this.portletMixinPropertiesだね。

portletMixinProperties: function( fragment )
{
this.setWindowTheme( fragment );
this.setWindowTitle( fragment );
this.setWindowIcon( fragment );
if ( dojo.render.html.mozilla )  // dojo.render.html.ie
{
//this.hasShadow = "true";
//        dojo.debug( "nWidget.domNode.cssText: " +
//nWidget.domNode.style = "overflow: visible;";   // so that drop shadow is displayed
}
this.setWindowDimensions( fragment );
},

ここで、fragmentも少々気になるが、「Object. A subtree section from the return of the dojo.xml.Parser system.」とあるけど、実際にはどんなものかわからん(まぁ、fragmentを使って、何かしているわけではないみたいなので、次へ進もう)。まず、setWindowThemeは、

setWindowTheme: function( fragment )
{
var windowtheme = this.getInitProperty( jetspeed.id.PORTLET_PROP_WINDOW_THEME );
if ( ! windowtheme )
{
if ( this.portletWindowTheme )
windowtheme = this.portletWindowTheme;
else
windowtheme = jetspeed.page.getWindowThemeDefault();
}
this.portletWindowTheme = windowtheme ;
var prevCssPath = ( this.templateCssPath == null ? null : this.templateCssPath.toString() );
this.templateCssPath = new dojo.uri.Uri( jetspeed.url.basePortalWindowThemeUrl( windowtheme ) + "/css/styles.css" );
if ( this.portletInitialized )
{   // load new stylesheet    // BOZO: it would be nice to check if this were necessary
if ( prevCssPath == null || prevCssPath != this.templateCssPath.toString() )
dojo.style.insertCssFile( this.templateCssPath, null, true );
}
},

ここでは、テーマのtemplateCssPathをセットするのが目的みたいだな。確認しておくべきところは、

jetspeed.url.basePortalWindowThemeUrl = function( windowtheme )
{
return jetspeed.url.basePortalDesktopUrl() + "/javascript/desktop/windowthemes/" + windowtheme;
};

だな。ポートレットウィンドウのテーマは、/javascript/desktop/windowthemes/以下に置かれると言うことだ。なるほどね~。ここにおかんといかんのね。

次は、setWindowTitleで、

setWindowTitle: function( fragment )
{
var windowtitle = this.getInitProperty( jetspeed.id.PORTLET_PROP_WINDOW_TITLE );
if ( windowtitle )
this.title = windowtitle;
else if ( this.title == null )
this.title = "";
if ( this.portletInitialized )
{
// BOZO: update title         }
},

やっぱり、タイトルは、現状、PSMLのIDが入るような気が・・・。

っで、setWindowIconは、

setWindowIcon: function( fragment )
{
var windowicon = this.getInitProperty( jetspeed.id.PORTLET_PROP_WINDOW_ICON );
if ( ! windowicon )
{
if ( jetspeed.debugPortletWindowIcons )
{
windowicon = jetspeed.debugPortletWindowIcons[Math.floor(Math.random()*jetspeed.debugPortletWindowIcons.length)];
}
}
if ( windowicon )
this.iconSrc = new dojo.uri.Uri(jetspeed.url.basePortalDesktopUrl() + "/javascript/desktop/windowicons/" + windowicon ) ;
else
this.iconSrc = new dojo.uri.Uri(jetspeed.url.basePortalDesktopUrl() + "/javascript/desktop/windowicons/document.gif" ) ;
if ( this.portletInitialized )
{
if ( this.titleBarIcon )
this.titleBarIcon.src = this.iconSrc.toString();
}
},

か。PSMLにアイコン情報まで置いておかんといかんのかね。これも、置く場所は、/javascript/desktop/windowicons/だね。

次はsetWindowDimensionsは、

setWindowDimensions: function( fragment )
{
this.windowPositionStatic = this.getInitProperty( jetspeed.id.PORTLET_PROP_WINDOW_POSITION_STATIC );
this.windowColumnSpan = this.getInitProperty( jetspeed.id.PORTLET_PROP_COLUMN_SPAN );
if ( this.windowColumnSpan != null || this.windowPositionStatic )
this.windowIsColumnBound = true;
this.constrainToContainer = 0;
var portletWidth = null, portletHeight = null, portletLeft = null, portletTop = null;
if ( this.portlet )
{
var portletWindowState = this.portlet.getLastSavedWindowState();
portletWidth = portletWindowState.width;
portletHeight = portletWindowState.height;
portletLeft = portletWindowState.left;
portletTop = portletWindowState.top;
// NOTE: portletWindowState.zIndex;  - should be dealt with in the creation order
}
else
{
portletWidth = this.getInitProperty( jetspeed.id.PORTLET_PROP_WIDTH );
portletHeight = this.getInitProperty( jetspeed.id.PORTLET_PROP_HEIGHT );
portletLeft = this.getInitProperty( jetspeed.id.PORTLET_PROP_LEFT );
portletTop = this.getInitProperty( jetspeed.id.PORTLET_PROP_TOP );
}
if ( portletWidth != null && portletWidth > 0 ) portletWidth = Math.floor(portletWidth) + "px";
else portletWidth = jetspeed.prefs.defaultPortletWidth;
if ( portletHeight != null && portletHeight > 0 ) portletHeight = Math.floor(portletHeight) + "px";
else portletHeight = jetspeed.prefs.defaultPortletHeight;
if ( portletLeft != null && portletLeft >= 0 ) portletLeft = Math.floor(portletLeft) + "px";
else portletLeft = (((this.portletIndex -2) * 30 ) + 200) + "px";
if ( portletTop != null && portletTop >= 0 ) portletTop = Math.floor(portletTop) + "px";
else portletTop = (((this.portletIndex -2) * 30 ) + 170) + "px";
if ( ! this.portletInitialized )
{
var source = this.getFragNodeRef( fragment );
var dimensionsCss = "width: " + portletWidth + ( ( portletHeight != null && portletHeight.length > 0 ) ? ( "; height: " + portletHeight ) : "");
if ( ! this.windowPositionStatic )
dimensionsCss += "; left: " + portletLeft + "; top: " + portletTop + ";";
source.style.cssText = dimensionsCss;
//dojo.debug( "PortletWindow.setWindowDimensions: " + dimensionsCss );
}
else
{   // update dimensions
this.domNode.style.position = "absolute";
this.domNode.style.width = portletWidth;
this.domNode.style.height = portletHeight;
if ( ! this.windowPositionStatic )
{
this.domNode.style.left = portletLeft;
this.domNode.style.top = portletTop;
}
}
},

長い・・・。でも、やっていることは、場所情報の設定だね。

一通り、見たところで、話をpostCreateに戻そう。this.domNode.id = this.widgetIdとした後は、portletPostCreateなので、

portletPostCreate: function()
{
if ( this.windowPositionStatic )
{
this.domNode.style.position = "static";  // can't be done earlier
this.domNode.style.left = "auto";
this.domNode.style.top = "auto";
}
if ( this.windowPositionStatic && ! jetspeed.prefs.windowTilingVariableHeight )
{
this.domNode.style.overflow = "visible";
this.domNode.style.height = "";
}
else
this.domNode.style.overflow = "hidden";
if ( this.windowPositionStatic && ! jetspeed.prefs.windowTilingVariableWidth )
{
this.domNode.style.width = "";
if ( this.titleBar )
this.titleBar.style.width = "";
if ( this.resizeBar )
this.resizeBar.style.width = "";
}
if ( ! this.templateDomNodeClassName )
this.templateDomNodeClassName = this.domNode.className;
var domNodeClassName = this.templateDomNodeClassName;
if ( this.portletWindowTheme )
{
domNodeClassName = this.portletWindowTheme + ( domNodeClassName ? ( " " + domNodeClassName ) : "" );
}
this.domNode.className = jetspeed.id.PORTLET_STYLE_CLASS + ( domNodeClassName ? ( " " + domNodeClassName ) : "" );
if ( this.containerNode )
{
if ( ! this.templateContainerNodeClassName )
this.templateContainerNodeClassName = this.containerNode.className;
var containerNodeClassName = this.templateContainerNodeClassName;
if ( this.portletWindowTheme )
{
containerNodeClassName = this.portletWindowTheme + ( containerNodeClassName ? ( " " + containerNodeClassName ) : "" );
}
this.containerNode.className = jetspeed.id.PORTLET_STYLE_CLASS + ( containerNodeClassName ? ( " " + containerNodeClassName ) : "" );
if ( this.windowPositionStatic && ! jetspeed.prefs.windowTilingVariableHeight )
{
this.containerNode.style.overflow = "visible";
this.containerNode.style.height = "";
}
else
this.containerNode.style.overflow = "auto";
if ( this.windowPositionStatic && ! jetspeed.prefs.windowTilingVariableWidth )
{
//this.containerNode.style.width = "";   // commented-out with change to ie width 100% in resizeTo
//dojo.debug( "portletPostCreate containerNode-width: " + dojo.style.getOuterWidth( this.containerNode ) + " domNode-width: " + this.domNode.style.width );
}
}
//dojo.debug( "PortletWindow.portletPostCreate [" + this.portlet.entityId + "] setting domNode.className=" + this.domNode.className + " containerNode.className=" + this.containerNode.className );
this.width = dojo.style.getOuterWidth( this.domNode );
this.height = dojo.style.getOuterHeight( this.domNode );
this.resetLostHeightWidth();
this.titleDim( true );
},

これまた、長いな・・・。なるほど、前半部分でスタイルを設定している。おもしろいのは、ポートレットのスタイルの書き換えだ。どうやって、ポートレットのテーマを設定するのかと思ったら、ここで、差し替えていたのね。始めは、this.domNode.classNameはdojoFloatingPaneだと思われるけど、それにテーマをくっつけているのだね。そんで、コンテンツ部分についても同様にスタイルを設定するのだね。そして、高さと幅を与えて、resetLostHeightWidthは、BaseFloatingPane.jsにあるから、

resetLostHeightWidth: function()
{
// figure out how much space is used for padding/borders etc.
this.lostHeight=
(dojo.style.getOuterHeight(this.domNode)-dojo.style.getContentHeight(this.domNode))
+dojo.style.getOuterHeight(this.titleBar)
+dojo.style.getOuterHeight(this.resizeBar);
this.lostWidth=
dojo.style.getOuterWidth(this.domNode)-dojo.style.getContentWidth(this.domNode);
},

これをみると、コンテンツ部分以外の高さと幅みたいだな。

次に、titleDimは、

titleDim: function( immediateForce )
{
var mightBeExtinguished = [ this.restoreAction, this.maximizeAction, this.minimizeAction, this.closeAction ] ;
var toBeExtinguished = [] ;
for ( var i = 0 ; i < mightBeExtinguished.length ; i++ )
{
var btn = mightBeExtinguished[i];
if ( immediateForce )
btn.style.visibility = "hidden" ;
else if ( btn.style.visibility != "hidden" )
toBeExtinguished.push( btn );
}
for ( var i = 0 ; i < toBeExtinguished.length ; i++ )
{
toBeExtinguished[i].style.visibility = "hidden" ;
}
this.restoreAction.style.display = "none";
//jetspeed.ui.fadeOut( toBeExtinguished, 280, [ this.restoreAction ] );   // nodes in 3rd arg will be set to display=none
this.titleLit = false ;
},

ここでは、アクションボタンにhiddenを設定して見えなくしているのかね。titleLitをfalseにしているけど、何の値かね。

っで、postCreateに戻り、this.createTitleBarContextMenu();だな。

createTitleBarContextMenu: function()
{
var portletWindow = this;
var titleBarContextMenu = dojo.widget.createWidget( "PopupMenu2", { id: this.widgetId + "_ctxmenu", targetNodeIds: [ this.titleBar.id ], contextMenuForWindow: false }, null );
var dumpPosMenuItem = dojo.widget.createWidget( "MenuItem2", { caption: "Dump Position"} );
var makeFreeFloating = dojo.widget.createWidget( "MenuItem2", { caption: "Make Free Floating"} );
//var twoColummLayoutMenuItem = dojo.widget.createWidget( "MenuItem2", { id: "jstb_menu_item3", caption: "Two Column Layout"} );
//var threeColummLayoutMenuItem = dojo.widget.createWidget( "MenuItem2", { id: "jstb_menu_item4", caption: "Three Column Layout"} );
dojo.event.connect( dumpPosMenuItem, "onClick", function(e) { portletWindow.dumpPostionInfo(); } );
dojo.event.connect( makeFreeFloating, "onClick", function(e) { portletWindow.makeFreeFloating(); } );
//dojo.event.connect( freeFormLayoutMenuItem, "onClick", function(e) { jetspeed.prefs.windowTiling = false; jetspeed.page.resetWindowLayout(); jetspeed.page.reload(); } );
//dojo.event.connect( twoColummLayoutMenuItem, "onClick", function(e) { jetspeed.prefs.windowTiling = 2; jetspeed.page.reload(); } );
//dojo.event.connect( threeColummLayoutMenuItem, "onClick", function(e) { jetspeed.prefs.windowTiling = 3; jetspeed.page.reload(); } );
titleBarContextMenu.addChild( dumpPosMenuItem );
titleBarContextMenu.addChild( makeFreeFloating );
//titleBarContextMenu.addChild( twoColummLayoutMenuItem );
//titleBarContextMenu.addChild( threeColummLayoutMenuItem );
document.body.appendChild( titleBarContextMenu.domNode );
},

タイトルのポップアップメニューを作るみたい。ポートレットウィンドウのタイトルバーを右クリックしてポップアップメニューが出てきたからな。

っで、postCreateの残りの部分は、

if ( jetspeed.debug.createWindow )
dojo.debug( "createdWindow [" + ( this.portlet ? this.portlet.entityId : this.widgetId ) + "]" + " width=" + this.domNode.style.width + " height=" + this.domNode.style.height + " left=" + this.domNode.style.left + " top=" + this.domNode.style.top ) ;
this.portletInitialized = true;
var initWindowState = this.getInitProperty( jetspeed.id.PORTLET_PROP_WINDOW_STATE );
if ( initWindowState == "minimized" )
this.minimizeWindow();

というわけで、デバッグはいいとして、初期化を判定する値のportletInitializedをtrueにして、最小化されているなら、minimizeWindowを実行するみたいだね。

だいぶ、離れたところで、戻るとjetspeed.loadPortletWindowsまで戻り、続きを見ると、

portletArray = jetspeed.page.getPortletArrayByZIndex();
jetspeed.ui._loadPortletWindows( portletArray, windowsToRender, portletWindowFactory );
if ( windowsToRender && windowsToRender.length > 0 )
{
jetspeed.doRenderAll( null, windowsToRender, true );
}
jetspeed.page.retrieveAllMenus();   // BOZO: should not be happening here!

からかな。getPortletArrayByZIndexは、

getPortletArrayByZIndex: function()
{
var portletArray = this.getPortletArray();
if ( ! portletArray ) return portletArray;
var filteredPortletArray = [];
for ( var i = 0 ; i < portletArray.length; i++ )
{
if ( ! portletArray[i].getProperty( jetspeed.id.PORTLET_PROP_WINDOW_POSITION_STATIC ) )
filteredPortletArray.push( portletArray[i] );
}
filteredPortletArray.sort( this._portletZIndexCompare );
return filteredPortletArray;
},

だね。今度は、場所が固定じゃないやつを配列にして、ソートして返すと言うことだね。ソートの方法は、

_portletZIndexCompare: function( portletA, portletB )
{   // uses saved state only - does not check with window widget
var aZIndex = null;
var bZIndex = null;
var windowState = null;
windowState = portletA.getLastSavedWindowState();
aZIndex = windowState.zIndex;
windowState = portletB.getLastSavedWindowState();
bZIndex = windowState.zIndex;
if ( aZIndex && ! bZIndex )
return -1;
else if ( bZIndex && ! aZIndex )
return 1;
else if ( aZIndex == bZIndex )
return 0;
return ( aZIndex - bZIndex );
},

だな。つまり、z方向でソートすると言うことだな。っで、配列ができたら、また、jetspeed.ui._loadPortletWindowsを実行。まぁ、また、追う必要はないだろう。でも、さっきと違って、今度は場所が固定じゃない処理が実行されることになるのだろう。

そしたら、今まで処理したポートレットの配列windowsToRenderに対して、jetspeed.doRenderAllを実行。

jetspeed.doRenderAll = function( url, windowArray, isPageLoad )
{
var debugMsg = jetspeed.debug.doRenderDoAction;
var debugPageLoad = jetspeed.debug.pageLoad && isPageLoad;
if ( ! windowArray )
windowArray = jetspeed.page.getPortletArray();
var renderMsg = "";
for ( var i = 0; i < windowArray.length; i++ )
{
var renderObj = windowArray[i];
if ( (debugMsg || debugPageLoad) )
{
if ( i > 0 ) renderMsg = renderMsg + ", ";
if ( renderObj.entityId )
{
renderMsg = renderMsg + renderObj.entityId;
if ( debugPageLoad && renderObj.getProperty( jetspeed.id.PORTLET_PROP_WINDOW_TITLE ) )
renderMsg = renderMsg + " " + renderObj.getProperty( jetspeed.id.PORTLET_PROP_WINDOW_TITLE );
}
else
{
var widgetId = null;
if ( renderObj.getProperty != null )
widgetId = renderObj.getProperty( jetspeed.id.PORTLET_PROP_WIDGET_ID );
if ( ! widgetId )
widgetId = renderObj.widgetId;
if ( ! widgetId )
widgetId = renderObj.toString();
renderMsg = renderMsg + widgetId;
}
}
renderObj.retrieveContent( null, url );
}
if ( debugMsg )
dojo.debug( "doRenderAll [" + renderMsg + "] url: " + url );
else if ( debugPageLoad )   // this.getPsmlUrl() ;
dojo.debug( "doRenderAll page-url: " + jetspeed.page.getPsmlUrl() + " portlets: [" + renderMsg + "]" + ( url ? ( " url: " + url ) : "" ) );
};

何かいろいろと書いてあるけど、デバッグコードを無視すると、表示するためのポートレット配列に対して、renderObj.retrieveContentを実行しているだけっぽいな。urlはここでは、nullなので、retrieveContentには、null,nullを渡すことになるな。

retrieveContent: function( contentListener, renderOrActionUrl, actionForm )
{
if ( contentListener == null )
contentListener = new jetspeed.om.PortletContentListener() ;
var portlet = this ;
var requestUrl = portlet.getPortletUrl( renderOrActionUrl ) ;
this.contentRetriever.getContent( requestUrl, contentListener, actionForm, null, portlet, jetspeed.debugContentDumpIds );
},

contentListernerはnullだから、PortletContentListenerを作成する。

jetspeed.om.PortletContentListener = function()
{
};

コンストラクタでは何もしていない模様。っで、次に進み、getPortletUrlでurlを取得する。引数にはnullを渡すことになるな。

getPortletUrl: function( renderUrl )
{
var queryString = "?entity=" + this.entityId + "&portlet=" + this.name + "&encoder=desktop";
if (renderUrl)
return renderUrl + queryString;
return jetspeed.url.basePortalUrl() + jetspeed.url.path.PORTLET + queryString;
},

作成されるURLは、http://localhost:8080/palportal/portlet?entity=<Entity ID>&portlet=<Portlet Name>&encoder=desktop といった感じだろうか。そして、this.contentRetriever.getContentを呼び出し。contentRetrieverについては、既に確認したけど、jetspeed.om.PortletContentRetrieverである。それで、getContentは、

jetspeed.om.PortletContentRetriever.prototype =
{   // /* Portlet */ portlet, /* String */ requestUrl, /* PortletContentListener */ portletContentListener
getContent: function( /* String */ requestUrl, contentListener, /* DOM Form */ formObject, /* String */ mimeType, domainModelObject, /* String[] */ debugContentDumpIds )
{
jetspeed.url.retrieveContent( requestUrl, contentListener, formObject, mimeType, domainModelObject, debugContentDumpIds );
}
};

jetspeed.url.retrieveContentを呼び出し。このメソッドは、既に PSML の取得で見たやつだな。

今日はここまで・・・