2010年11月2日火曜日

ブラウザのリロード防止

リロードの無効化が必要になったので実装しました。
※IE6,7,8で確認

[F5]、[Ctrl + R]によるリロード防止処理。
function preventReload(){
 var KEY_F5     = 116;
 var KEY_R      = 82;
 
 var e = window.event;
 var ctrl = e.ctrlKey;
 var preventFlg = false;
 
 switch (event.keyCode ){ 
  case KEY_F5:
   preventFlg = true;
   break;
  case KEY_R:
   if (ctrl) {
    preventFlg = true;
    break;
   }

 }
 if (preventFlg) {
  e.keyCode = 0;
  e.returnValue = false;
  return false;
 }
}

[F6]とか[Ctrl + Tab]でロケーションバーにフォーカス後
(ロケーションバーを表示してなくても)、[F5]を押下されると
効いてしまうんで、この辺も要件によっては無効化した方が
いいかもしれないですな。

まぁ、js無効化とかタイミング、お気に入り表示とかでも効いてしまったりするし、
そもそもクライアント側で完全に防止するのは不可能という前提で、
完全に防止したい場合は、サーバ側でワンタイムトークンとか
実装しておくのは必須。

ただ、普通の操作における誤操作防止にはある程度有効なので、
入れておくにこしたことはなさそう。


IEショートカットキー 一覧
http://www.microsoft.com/japan/enable/products/keyboard/default.mspx

その他[Alt + ←],[Alt + →]など防止したい場合は、
以下のサイトの実装が参考になると思います。

2010年11月1日月曜日

ブラウザの「閉じるボタン」のイベントを取得する

ちょっと必要になったので調べました。

ブラウザの閉じるボタンを押された時のイベントで処理をしたい場合。
function window.onbeforeunload() {
    if(((event.clientX > document.body.clientWidth) && (event.clientY<0)) || event.altKey ) {
        xxxFunc();
    }
}


IE7などのタブブラウザはこっち。
function window.onbeforeunload() {
    if( event.clientY < 0 || event.altKey ) {
         xxxFunc();
    }
}


▼IEのonbeforeunloadイベントの仕様はこちら
http://msdn.microsoft.com/en-us/library/ms536907.aspx


onbeforeunloadイベントは閉じるボタン以外でも
  • location.hrefに新しいlocation を設定した場合
  • location.reload() メソッド呼び出しの場合
  • window.close() をだした場合

などでも動作するので条件を入れるわけですが、
タブブラウザに合わせた実装だとロケーションバーにフォーカスをあてて
Enter押下でも動作してしまったり。。

なんかいい方法ないかな。。

参考URL
ブラウザの「閉じるボタン」とタブの「閉じるボタン」
IEでのonBeforeUnload の挙動
javascript:ページ遷移時に確認を出して、キャンセルしたら、遷移しないということをしたいと考えています。
IEの特定のキー操作を無効化

2010年10月28日木曜日

jQuery ThickBoxを使ってみた

画像をクリックすると画像を拡大して表示するサイトをよく見かけ、
かっこいいなぁと思っていたので自分もやってみました。

調べたところjQueryとThickboxというものを使用すると簡単に実現出来そうです。


手順
配布ページより以下のファイルをダウンロード。
http://jquery.com/demo/thickbox/
  • jquery.js
  • loadingAnimation.gif
  • thickbox.css
  • thickbox.js

※jquery.jsの名前はjquery-latest.jsだったりするかも


thickbox.jsの8行目でloadingAnimation.gifのパスが設定されているので
loadingAnimation.gifのパスを自分に環境に合わせます。
var tb_pathToImage = "images/loadingAnimation.gif";


ThickBoxを利用したいhtmlのhead要素などでファイルを読み込みます。
<link rel="stylesheet" href="thickbox.css" type="text/css" media="all">
<script type="text/javascript" src="jquery-latest.js" ></script>
<script type="text/javascript" src="thickbox.js"></script>



あとは実際に使用するHTMLに設置するだけです。

▼画像拡大の場合
サムネイル画像と拡大画像へのリンクを張ります。一番見かけますね。
<a href="拡大画像1.jpg" class="thickbox" title="サンプル" rel="test"><img src="サムネイル画像1.jpg" width="160" height="120" alt="サンプル" border="0" /></a>
<a href="拡大画像2.jpg" class="thickbox" title="サンプル" rel="test"><img src="サムネイル画像2.jpg" width="160" height="120" alt="サンプル" border="0" /></a>



▼HTML拡大表示
HTMLを表示することもできます。
<a href="test.html?width=400&height=200" class="thickbox">testhtml</a>


▼iframe拡大表示
iframeを表示することもできるんですね。
<a href="test.html?TB_iframe=true&width=400&height=300" class="thickbox">testhtmlInIframe</a>


▼ページ内HTML拡大表示
ページ内のHTMLを拡大表示することもできます。ページ内のdiv要素をdisplay:noneにしておいて、リンククリックで表示みたいな。
<a href="#TB_inline?width=400&height=300&inlineId=thickboxdivtest" class="thickbox">ページ内HTMLテスト</a>
<div id="thickboxdivtest" style="display:none"><span style="font-weight:bold">テスト文字表示だよ<br/>テスト文字表示だよ<br/></span></div>



注意点
ちなみに、最初グループ化属性のrel属性をつけると画像が表示されませんでした。
jqueryとthickboxのversionに相性があるようで、rel属性をつけると画像が表示されない
場合は以下の修正をすると動作するようになります。

thickbox.js(ver3.1、unCompressed)の79行目を以下のように修正
TB_TempArray = $(“a[@rel="+imageGroup+"]“).get();
                 ↓
TB_TempArray = $(“a[rel="+imageGroup+"]“).get();




ThickBox、簡単に設置できるのでお勧めです。
とりあえず使い方は押さえたので、thickbox.js、thickbox.cssをカスタマイズすればまた色々できそうですね。

2010年10月22日金曜日

seasar2のdicon設定 namespace指定はメモリを喰う?

seasar2のdiconファイル定義では、components要素に
namespace属性を指定できます。
http://s2container.seasar.org/2.4/ja/reference/html/config-dicon.html#d0e1484

名前空間を指定できるわけですが、これを指定すると
メモリ使用量(heapsize)がけっこう増えます
(もちろん登録コンポーネント数にもよりますが)。
heapdumpして確認するとS2ContainerのHashMapの
容量がだいぶ変わることがわかりました。

S2Container内で管理しているコンポーネント名が
長くなると思うので単純に増えそうではありますが。。

今回はAutoRegisterで大量にコンポーネントを登録し、
namespaceを全てのdiconにつけていました。
必要のないnamespace指定を取り除いたところ、
以下のような結果となりました。
namespaceをつける        S2ContainerのHashMap 約1GB
namespaceをつけない(※1) S2ContainerのHashMap 約400MB 

(※1)もともとseasar2のjarに含まれているdiconを拡張したものは
     namespaces指定そのまま。
     データソースの切替等でJDBC関連の必要な部分はnamespaceを指定。

使用しているS2のversionはseasar2.4.25で、だいたい2.4系はSmartDeployを
使用すると思うのであまり注意は必要ないかもしれませんが、
とりあえずnamespaceを無駄に指定するのはよくなさそうですね。

2010年10月19日火曜日

webページの横幅は何pxにすべきか

ふとサイト製作におけるWEBページの横幅の妥当性を知りたくなったので調べてみた。軽くぐぐっただけで大量に情報がありますね。

1000px弱時代のWebデザイン
web屋が主張する「リキッドレイアウト」に騙されないために
webページ作成の際に、横幅を何ピクセルにすべきでしょうか。
横幅を固定するな!後悔しないためのWebデザイン
固定か可変かそれが問題だ
mixiのデザイン変更から学ぶ、ウェブサイトの最小横幅を800px以下にすべき理由
気持ちのいいサイトとは(3)~リキッドデザイン~
リキッドデザイン問題はmax-widthとIE6用expressionで解決



まずは対象となるターゲットユーザは誰か。
一般ユーザなのか、イントラネット内の企業ユーザなのかで
大きく分かれると思います。

企業ユーザであれば、ブラウザの種類や端末の解像度もおおよそ見当が付くこと
多いと思いますのでそれに合わせて作成するのがよいかと。

一般ユーザの場合はブラウザや端末の種類が多岐に渡るのでなかなか難しいですが、
横幅800~1000px弱で作成するのが無難なのではないでしょうか。


リキッドデザインはよいとは思うのですが、「ブラウザを最大化すると逆に見にくい」という問題もあります。
max-widthを利用して広がり過ぎを解決するという方法もあるようです。
リキッドデザイン問題はmax-widthとIE6用expressionで解決

で、自分なりの結論ですが、1000px弱時代のWebデザインにあるように
ほとんどのユーザーが横幅1024px以上表示できることと、デザインのし易さから固定幅横幅800~1000px弱でサイトをデザインすることがよいのではないかと思いました。
もちろん、ケースバイケースではあるのですが。。



あとIE6の場合は、印刷対策をしとかないと見切れたりするので注意が必要ですね。
ブラウザ印刷とスクリーン解像度の不思議な関係
印刷できるページを作るには
印刷用CSS:webページの横幅について

2010年10月15日金曜日

アップロードファイルのローカル最終更新日時を取得できるか

WEBアプリにてアップロードファイルのローカル最終更新日時を
取得できるか調べてみましたが、結論から言うとできません。
言語はJavaですが多言語も同様です。

[調査1]
・リクエストをパケットモニタしましたが、
   飛んでいる情報は以下3つになります。
    フィールド名
    ファイルパス
    ファイル内容


[調査2]
 ・RFC 1867 ファイルアップロードについての規定にも
   タイムスタンプの記載はなし
  RFC原文
  studyinghttp日本語訳
 
上記調査からタイムスタンプのようなファイル属性はhttp通信時に
欠落する(リクエストに付加されない)ためサーバ側で取得
することは不可能。


最終更新日時の取得などはファイルの一意性を確認したい
場合が多いと思うので、別の方法として
java.security.DigestInputStream等を使用してファイル内容の
ハッシュ値を保存しておく方法が考えられる
(よくダウンロードファイルの改変チェックに使用されているもの)。

 ファイル名
 ファイルサイズ
 ハッシュ値
の比較で全て同じであればファイルの一意性としても
よいかな。

2010年10月8日金曜日

Teeda sendRedirectでIllegalStateException

Teedaでrendererの拡張を行っているのですが、sendRedirectでIllegalStateExceptionが発生。
java.lang.IllegalStateException
    at org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)
    at org.seasar.teeda.core.util.ServletExternalContextUtil.redirect(ServletExternalContextUtil.java:188)
    at org.seasar.teeda.extension.context.TmsServletExternalContextImpl.redirect(TmsServletExternalContextImpl.java:298)
    at org.seasar.teeda.core.util.NavigationHandlerUtil.redirect(NavigationHandlerUtil.java:55)
    at org.seasar.teeda.extension.util.TeedaExtensionErrorPageManagerImpl.handleException(TeedaExtensionErrorPageManagerImpl.java:84)
    at org.seasar.teeda.core.lifecycle.LifecycleImpl.handleException(LifecycleImpl.java:116)
    at org.seasar.teeda.core.lifecycle.LifecycleImpl.render(LifecycleImpl.java:135)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:101)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at jp.co.isid.fine.io.file.UploadFilter.doFilter(UploadFilter.java:60)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.seasar.teeda.core.filter.RequestDumpFilter.doFilter(RequestDumpFilter.java:125)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.seasar.framework.container.hotdeploy.HotdeployFilter.doFilter(HotdeployFilter.java:70)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.seasar.framework.container.filter.S2ContainerFilter.doFilter(S2ContainerFilter.java:77)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.seasar.extension.filter.EncodingFilter.doFilter(EncodingFilter.java:69)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at jp.mufg.tms.base.web.filter.SessionInvalidateFilter.doFilter(SessionInvalidateFilter.java:47)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at jp.mufg.tms.base.web.filter.ThreadLocalFilter.doFilter(ThreadLocalFilter.java:72)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at jp.mufg.tms.base.web.filter.NocacheFilter.doFilter(NocacheFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
    at java.lang.Thread.run(Thread.java:619)


スタックトレース見ればわかりますが、レンダリングでエラーが発生した場合は
org.seasar.teeda.core.lifecycle.LifecycleImpl#handleExceptionにデバッグいれると
エラーの内容がわかりますね。

jspで開発していた時に、jsp:includeを使用してflush="ture"にしている場合に、
カスタムタグ内でsendRedirectすると似たような例外が発生した記憶があったので、
(レスポンスをコミットした状態[表示結果を一部でも返している状態]でリダイレクトしていたため)
一瞬全然見当違いな調査をしてしまったのですが、Pageクラスのgetterの名前が正しくないだけでした。。(おつ

2010年10月7日木曜日

javascript Window間オブジェクト操作の注意点

window.openして子ウィンドウ側から親ウィンドウのselectboxの値を変更するコードを実行したところ
「サーバによって例外が返されました」というわけわからないエラーが発生(サーバにアクセスしてないし)。。
※テストブラウザはIE6

var option = new Option('xxxxx','xxxxx');
parentSelectObj.options[parentSelectObj.options.length] = option;
 //↑ここで上記エラー


ふーむ、ネット調べたりオライリー本読み直してみたところ、
・フレーム間、ウィンドウ間ではスコープチェーンが違う
・関数が実行されるスコープは、関数を呼び出したフレームではなく関数が定義されたフレーム(ウィンドウもしかり)
・コンストラクタも関数
・コンストラクタとそのコンストラクタのプロトタイプオブジェクトは、ウィンドウごとに独立

言われてみれば確かに。


というわけで、コードを以下に修正したところ無事動作しました。

var option = window.opener.document.createElement('Option');
option.text  = 'xxxxx';
option.value = 'xxxxx';
parentSelectObj.options[parentSelectObj.options.length] = option;



2010年9月10日金曜日

「Javaフレームワーク開発入門」を読んで 総評



読み終わりました。
まず第一に、かなりの良本ですね。
書籍の構成もちゃんとしているし、実装例もわかりやすい。

読者対象としてはjavaの基本を一通り学んで、フレームワークを使った
実践経験がある人ならかなり有用だと思います。
フレームワークの中身ってどうなっているんだろうと興味をもった方が、
OSSフレームワークのソースを読む前にも。

WEBアプリケーション開発において非機能用件、横断的関心事というと、
・エラー処理
・メッセージ処理
・ログ出力処理
・DB処理(ORマップ、トランザクション、データソース管理、排他制御、自動採番)
・ファイル管理処理
・セキュリティ
・帳票出力処理(PDF,CSV)
・外部システム連携
・バッチ制御処理
・権限・ワークフロー(業務より)
・メール
・多言語対応
・数値演算
・日付時刻
・文字コード
・画面部品

ぱっと思いつくだけで結構あるが、大規模アプリケーションなんかだと、
フレームワークの上に共通基盤、その上に業務APをのせるといった
構成の方が効率がよい。
共通基盤を設計しようと思った場合に、インターフェース・拡張ポイント等
迷うことが多々あると思うが、そういった共通基盤を作成する人なんかも
良い指針になる本だと思います。

また、リフレクションAPIを細かく解説していたり、
デザインパターンをいくつか紹介しています。

デザインパターンの説明は、Gofで紹介する23パターン全て
ではなく、フレームワーク作りでよく使うパターンのみだが、
どの本よりわかりやすく感じました。
Adapterパターン、Proxyパターン、Decoratorパターンなんかは
混同しやすいけど、その違いなんかも説明しています。

説明しているデザインパターン
・Template Methodパターン
・Flyweightパターン
・Factory Methodパターン
・Decoratorパターン
・Adapterパターン
・Proxyパターン
・Singletonパターン
・Intercepting Filterパターン


あとは、3人の登場人物が会話してプロジェクトを進めていくという、
よく海外本にあるような遊び心ある構成が程よい息抜きによかったです。

2010年9月9日木曜日

「Javaフレームワーク開発入門」を読んで4



▼実装例で気になったイディオム

例外をStringに変換するメソッド
public static String toString(Throwable t) {
    StringWriter sw = new StringWriter();
    t.printStackTrace(new PrintWriter(sw));
    return sw.toString();
}

こんなインターフェースあるのね。いつもStackTraceElement[] traces = t.getStackTrace();してt.getCause()してループで回すなんて関数つくってた。

2010年9月7日火曜日

「Javaフレームワーク開発入門」を読んで3




▼AOPのプロダクト比較

・AOPのウィービング(インターセプタ組み込み)のタイミングはプロダクトによって違う
     
  • AspectJ     Compile-time weaving, load-time weaving
  •  
  • Seasar2     run-time weavng
  •  
  • SamuraiAOP  load-time weaving


・ウィービングのタイミングによる考慮点
  • Compile-time weaving
  •  ・コンパイルするときにソースコードが必要なため、jarファイルのようなモジュールに対してはAOPの適用が難しい  ・コードを変更するたびに特殊なコンパイルが必要
  • run-time weaving
  • ・特定のAPIに依存する   Seasar2の場合だとS2コンテナから取得したオブジェクトしかAOPがかからない   S2Container、S2ContainerFactory等に依存 ・クラスが大量に生成される(permanent領域が足りなくなる可能性も)
  • load-time weaving
  • ・VMオプションが必要→GAEのようなVMオプションを設定できない環境は厳しい


・言語の違い
     
  • AspectJ     Java, AspectJ言語
  •  
  • Seasar2     Java, XML
  •  
  • SamuraiAOP  Java, XML


・ポイントカット指定の違い
     
  • AspectJ     アスペクトに定義
  •  
  • Seasar2     diconに定義
  •  
  • SamuraiAOP  XMLに定義


・ジョインポイントの違い
                                                                                                                                                                                                                                                                                                   
プロダクトインスタンスメソッドprivateメソッドstaticメソッドfinalメソッドコンストラクタフィールドアクセス
AspectJ
Seasar2×××××
SamuraiAOP×



それぞれメリット・ディメリットがありますが、設定のしやすさ/学習コストは
Seasar2→SamuraiAOP→AspectJ、機能面ではAspectJ→SamuraiAOP→Seasar2という印象。
実際のプロジェクトではSeasar2のAOPしか使ったことないですが、
privateにAOPをかけることはほとんどなかったので特に困らなかったです。
DIコンテナを使用する場合は、それに付属するAOP機能を使用するのが無難な気がします。


・AOPの標準化使仕様

AOP Alliance(AOP共通アーキテクチャの作成/標準化団体)を利用しているプロダクト

(org.aopalliance.interceptパッケージ)

 Seasar2, Spring, Guice, SamuraiAOP


javaEEを利用しているプロダクト

(javax.interceptorパッケージ)

 EJB3, Seam, SamuraiAOP

「Javaフレームワーク開発入門」を読んで2



▼参照の種類
java.util.WeakHashMapを利用すると、キーへの参照がなくなった場合、キャッシュしていたオブジェクトはGCによって改修される。

参照の種類
・弱参照          java.lang.ref.WeakReference    GCが起きるとほぼ必ず切れる参照
・ソフト参照      java.lang.ref.SoftReference    ヒープ領域が足りなくなると切れる参照
・ファントム参照  java.lang.ref.PhantomReference あるオブジェクトがいつGCされたのかを知りたいときに使う参照
・強参照                                         通常の参照



ここが参考になりますね。「Javaのメモリ管理」
http://blogs.wankuma.com/nagise/archive/2007/07/29/87593.aspx

「Javaフレームワーク開発入門」を読んで1

「Javaフレームワーク開発入門」という本を電車などでちょろちょろ読んでいるので気になったところをメモっていきます。


3-2-2 Flyweightパターン

▼IntegerクラスはvalueOfメソッドを利用した場合、-128<=value<=127の範囲でキャッシュするようにしている。
テストコード
public class IntegerTest {

    /**
     * コンストラクタ。
     */
    public IntegerTest() {

    }

    public static void main(String[] args) {
        Integer i1 = new Integer(10);
        Integer i2 = new Integer(10);
        compare(i1, i2);
        
        i1 = Integer.valueOf(127);
        i2 = Integer.valueOf(127);
        compare(i1, i2);
        
        i1 = Integer.valueOf(128);
        i2 = Integer.valueOf(128);
        compare(i1, i2);
        
        i1 = Integer.valueOf(-128);
        i2 = Integer.valueOf(-128);
        compare(i1, i2);
        
        i1 = Integer.valueOf(-129);
        i2 = Integer.valueOf(-129);
        compare(i1, i2);
        
    }
    
    private static void compare(Integer i1, Integer i2) {
        if (i1 == i2) {
            System.out.println("i1 == i2");
        } else {
            System.out.println("i1 != i2");
        }
    }
}


実行結果 
i1 != i2
i1 == i2
i1 != i2
i1 == i2
i1 != i2

おおっほんとですね。

2010年9月6日月曜日

ToStringBuilderを使用したtoString実装

Apache Commons LangにtoStringを簡単に実装するためのクラスToStringBuilderというのがあったので使ってみた(commons-lang-2.1.jar使用)。

Beanクラス(toString実装クラス)
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public class CommonLangTestBean {

    private String aaa;

    private String bbb;

    private String ccc;

    private String ddd;

    private String eee;

    private String fff[] = {"fff0", "fff1", "fff2"};
    
    /**
     * コンストラクタ。
     */
    public CommonLangTestBean() {

    }

    /**
     * aaa を取得します。
     *
     * @return aaa
     */
    public String getAaa() {
        return aaa;
    }

    /**
     * aaa を設定します。
     *
     * @param aaa 設定する aaa
     */
    public void setAaa(String aaa) {
        this.aaa = aaa;
    }

    /**
     * bbb を取得します。
     *
     * @return bbb
     */
    public String getBbb() {
        return bbb;
    }

    /**
     * bbb を設定します。
     *
     * @param bbb 設定する bbb
     */
    public void setBbb(String bbb) {
        this.bbb = bbb;
    }

    /**
     * ccc を取得します。
     *
     * @return ccc
     */
    public String getCcc() {
        return ccc;
    }

    /**
     * ccc を設定します。
     *
     * @param ccc 設定する ccc
     */
    public void setCcc(String ccc) {
        this.ccc = ccc;
    }

    /**
     * ddd を取得します。
     *
     * @return ddd
     */
    public String getDdd() {
        return ddd;
    }

    /**
     * ddd を設定します。
     *
     * @param ddd 設定する ddd
     */
    public void setDdd(String ddd) {
        this.ddd = ddd;
    }

    /**
     * eee を取得します。
     *
     * @return eee
     */
    public String getEee() {
        return eee;
    }

    /**
     * eee を設定します。
     *
     * @param eee 設定する eee
     */
    public void setEee(String eee) {
        this.eee = eee;
    }
    
    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE);
    }
}



実行クラス
public class CommonLangTest {

    /**
     * コンストラクタ。
     */
    public CommonLangTest() {

    }

    public static void main(String[] args) {
        CommonLangTestBean cltb = new CommonLangTestBean();
        cltb.setAaa("aaahoge");
        cltb.setBbb("bbbhoge");
        cltb.setCcc("ccchoge");
        System.out.println(cltb.toString());
    }
}


実行結果
ommonLangTestBean@b2fd8f[aaa=aaahoge,bbb=bbbhoge,ccc=ccchoge,ddd=<null>,eee=<null>,fff={fff0,fff1,fff2}]


スタイル指定を変更(CommonLangTestBean.java)
@Override
public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}


実行結果 これが一番見やすいかな
CommonLangTestBean@b2fd8f[
  aaa=aaahoge
  bbb=bbbhoge
  ccc=ccchoge
  ddd=<null>
  eee=<null>
  fff={fff1,fff2,fff3}
]


リフレクション使えばできると思うけど、ライブラリとして用意されているので使う価値ありそうですね。toStringを実装しておくとデバッグの時も何かと便利だし。

アノテーションはオーバライドしたメソッドでも有効なのか

親クラスのメソッドに定義したアノテーションが、継承した子クラスでオーバライドしても有効なのか(メタプログラミングで取得可能か)気になったので試してみた。


アノテーションクラス
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface A {

}


親クラス
@A
public class ParentClass {

    public ParentClass() {}

    @A
    public void hoge() {
        System.out.println("parent hoge");
    }
}


子クラス
public class ChildClass extends ParentClass {

    public ChildClass() {}
   
    public void hoge() {
        System.out.println("child hoge");
    }
}


実行クラス
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class AnnotationTest {

    public AnnotationTest() {}

    public static void main(String args[]) throws Exception {
        Class clazz = ChildClass.class;

        //クラスのアノテーション取得
        Annotation[] anns = clazz.getAnnotations();
        for (Annotation a : anns) {
            System.out.println("class:  " + a.annotationType());
        }

        // メソッドのアノテーション取得
        Method m = clazz.getMethod("hoge");
        anns = m.getAnnotations();
        for (Annotation a : anns) {
            System.out.println("method:  " + a.annotationType());
        }

    }
}


実行結果
class:  interface A



クラスのアノテーションは取得可能でオーバライドしたメソッドは取得不可でした。
まぁオーバライドしたら子クラスのメソッドでクラスが構築されるのだから当然といえば当然ですな。
オーバライドしなければもちろんメソッドも取得可能です。
ちなみにアノテーションクラスに@Inheritedをつけないとクラスのアノテーション取得不可ですね。

2010年8月11日水曜日

javascript高速化のポイント12

WEBアプリケーションのパフォーマンスにおいて、RIAの普及によりクライアント側の実装も複雑化してきておりサーバ側だけでなくクライアント側のパフォーマンスも非常に重要な要素となっている今日この頃。


複雑なクライアントを作ったときに、javascriptのパフォーマンスが気になったので実装レベルのイディオム的に高速化できるポイントをまとめてみた(羅列してみた)。

まぁ他にもjavascriptのロード方法とかiframeの使い方とか、はたまたjQueryなどのフレームワークを使用しているとか色々あると思うが、それはまた次回ということで。

  • 1.length の参照を減らす
  • 2.タイマ値はなるべく長く
  • 3.同じイベントに登録する関数が多い場合は実行制御関数を使う
  • 4.withは使わない
  • 5.スコープを越えない、ローカルにコピーする
  • 6.関数はグローバルに定義する、スコープを深くしない
  • 7.ドット演算を減らす
  • 8.関数ポインタをキャッシュする
  • 9.文字列結合する場合は、+= じゃなくて join 使った方が高速
  • 10.style等でDOM要素のプロパティを見に行く場合、いちいちブラウザ側と通信が発生する
  • 11.配列に要素を追加するのはpushよりlengthの方が早い
  • 12.style操作時に画面非表示にする



1.length の参照を減らす


●forループ改善1
    
for (i = 0; i < elements.length; i++) {
    :
}    

     ↓
    
for (i = 0, len = elements.length; i < len; i++) {
    :
}


●forループ改善2
    
for(i in array){~}    

     ↓
    
for (i = 0, len = elements.length; i < len; i++) {
    :
}



            

2.タイマ値はなるべく長く


setIntervalの間隔が短いと重くなるのは当然だが、目安として50ms以上はとりたい。


3.同じイベントに登録する関数が多い場合は実行制御関数を使う


イベントに登録した関数の実行順序制御を行う場合、実行制御関数をイベントに登録して実際に実行される関数は配列などに登録していく方法があるが、実行順序の制御を行わない場合でも、この実装方法の方が複数回イベントに登録するよりオーバーヘッドが少ない。


実行制御関数実装方法(onload):http://buzzmemo.blogspot.com/2010/07/javascript.html



4.withは使わない


withを使うと新しいスコーブがスコープチェーンに追加される。スコープチェーンを作成するのにコストがかかるのと、
withの内側からwithの外側のスコープのプロパティを参照するためにはスコープチェーンをたどる必要があることからパフォーマンスが落ちる場合がある。


      

5.スコープを越えない、ローカルにコピーする


ローカル変数の参照はスコープチェーンをたどらなくて済む(検索の範囲が狭い)のでパフォーマンスが向上する。また、意図しないグローバルスコープの変数を定義したり参照したりすることを防ぐ。


      

6.関数はグローバルに定義する、スコープを深くしない


関数はなるべくグローバルに定義してスコープを深くしない方が速いらしいが、それほどの効果もなさそう。大規模になると名前空間を使わないと厳しいためそこまで気にする必要なし。

      

7.ドット演算を減らす。また、DOMのproperty, attributeへのアクセスを減らしてキャッシュする。


    
function BuildUI(){
    var baseElement = document.getElementById('target');
    baseElement.innerHTML =  '';            
    baseElement.innerHTML += BuildTitle();  
    baseElement.innerHTML += BuildBody();   
    baseElement.innerHTML += BuildFooter(); 
}

     ↓
function BuildUI(){
    var elementText = BuildTitle() + BuildBody() + BuildFooter();
    document.getElementById('target').innerHTML = elementText;
}

これだと1回の参照で済む。上記はDOMノードの参照において効果が大きく現れる。




8.関数ポインタをキャッシュする


Work関数はグローバルスコーブで定義されている関数とする。この関数呼び出しのたびにスコープチェーンをたどって関数を探しにいくことになる。
function IterateWorkOverCollection(){
    var length = myCollection.getItemCount();
    for(var index = 0; index
        Work(myCollection[index]);
    }
}

     ↓
グローバルスコープのWork関すへの参照(ポインタ)をローカル変数のfuncWorkに格納し、ループ内でそのローカル変数を使って関数を呼び出すことでパフォーマンスが向上する。
function IterateWorkOverCollection(){
    var funcWork = Work;
    var length = myCollection.getItemCount();
    for(var index = 0; index
        funcWork(myCollection[index]);
    }
}




9.文字列結合する場合は、+= じゃなくて join 使った方が高速


var run = {
TX_PLUS : function(){
    tx_clear(); var html = ''; for (var i = 0; i < count; i++){ html += 'あ'; } $('tx').innerHTML = html;
},
TX_JOIN : function(){
    tx_clear(); var html = []; for (var i = 0; i < count; i++) { html.push('あ'); } $('tx').innerHTML = html.join("");
}
};

参照サイト:http://www.drk7.jp/MT/archives/001313.html


      

10.style等でDOM要素のプロパティを見に行く場合、いちいちブラウザ側と通信が発生する


elm.style.background = "#FFF";
elm.style.width      = "500px";

     ↓
var elmstyle        = elm.style;
elmstyle.background = "#FFF";
elmstyle.width      = "500px";

参照サイト:http://www.songmu.jp/riji/archives/2008/10/jjug_cross_comm.html



11.配列に要素を追加するのはpushよりlengthの方が早い


ary.push(xxx);

     ↓
ary[ary.length]=xxx



12.style操作時に画面非表示にする


    if (obj.disabled) {
        obj.disabled = false;
    } else {
        obj.disabled = true;
    }

disabledを表示に変更するループ処理が異常に遅い。画面に反映(描写)する際の処理が遅いのか。(IEだけ??)対応としてbodyのdisplayをnoneに設定し画面を非表示にしてdisabled解除処理を行うことにより、描写しないため高速になった。
※unload処理だったので上記対応が可能だったが、画面の見え方に影響あり

2010年7月23日金曜日

inline表示したブラウザでjavascriptを動作させる

別ウィンドウにPDF等の帳票をinlineでを出力することはよくあるが、その場合inlineで表示した別ウィンドウではjavascriptが動作しなくなる。
前回エントリの子ウィンドウを閉じる制御などを組み込みたい場合にどうすればよいか。

frameは使いたくないので、色々試行錯誤した結果とりあえずiframeとcssで頑張ることで解決。
ブラウザのbodyいっぱいにiframeを広げて、iframeのリクエストでファイルをダウンロード。
ダウンロードをGETで行わなければならないが、今回はPRGパターンのPOSTでファイル操作してGETでダウンロードという作りなのでOK



function writeOpenWin(win, requestPath) {
 
 // header各種生成
 var docType = 'xxx'; //xhtml定義など
 var htmlTagStart = 'xxx'; // htmlタグ namespaceスペースなど
 var contentTypeCharset = '<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />';
 var contentTypeJs = '<meta http-equiv="Content-Script-Type" content="text/javascript" />';
 var contentTypeCss = '<meta http-equiv="Content-Style-Type" content="text/css" />';
 
 // スタイル生成
 var wincss = '<style type="text/css">';
  wincss += 'html,body{';
  wincss += 'height:100%;';
  wincss += 'margin:0px;';
  wincss += 'padding:0px;';
  wincss += 'overflow: hidden;';   
  wincss += '}';
  wincss += '</style>';  
 
 // javascript生成
 var winJs = '<script type="text/javascript">';
  winJs += "function hogeFunc(){ alert("xxx"); }"; // onloadに登録する処理など
  winJs += '</script>';

 // ドキュメント書き込み
 win.document.open();
 win.document.write(docType);
 win.document.write(htmlTagStart);
 win.document.write('<head>');
 win.document.write('<title>' + dlFileName + '</title>');
 win.document.write(contentTypeCharset + contentTypeJs + contentTypeCss);
 win.document.write(wincss + winJs);
 win.document.write('</head>');
 win.document.write('<body onload="hogeFunc();" >');
 win.document.write('<iframe src="'+ requestPath + '" width="100%" height="100%" scrolling="auto" name="side" frameborder="0" allowtransparency="true"></iframe>');
 win.document.write('</body>');
 win.document.write('<html>');
 win.document.close();
}

使用例
// requestPath生成処理など
 ・・・
 var win = window.open("../dummy.htm" ,"winname", "width=800,height=600");
 writeOpenWin(win, requestPath);
 ・・・


※IE6,7しか確認してないす

ウィンドウを自動的に閉じるjavascript

親ウィンドウが閉じられた場合や特定の画面で、子ウィンドウを閉じるためのjavascript。
親ウィンドウから子ウィンドウを閉じるのでなく、子ウィンドウが親ウィンドウや条件を元に自分自身を閉じるアプローチ。
子ウィンドウをオープンした場合に、子ウィンドウのonloadに登録して使用する。
1秒おきにチェックするのでパフォーマンスに注意

function setOpenWindowCloser(){
        var count = 0;
        setInterval(function() {
            try {
                //親ウィンドウが閉じられた場合
                if(!window.opener || window.opener.closed){ 
                    window.close(); 
                }
                //エラー画面やログアウト画面等に遷移した場合に閉じる
                //別ウィンドウを閉じる特定の画面にhiddenで埋め込んでおく
                if (window.opener.document) {
                    if(window.opener.document.getElementById('xxx')){ 
                        window.close(); 
                    } 
                }
            }catch(e){
                // 親画面にアクセスできない場合 何故かたまに発生するので5回試してクローズ
                // 出せるとしてalertぐらいかな。。
                count++;
                if (count == 5) {
                    window.close();    
                }
            }            
        },1000);
}


※IE6,7しか確認してないす
   

2010年7月21日水曜日

ファイルダウンロードダイアログの日本語ファイル名クロスブラウザ対応

はまったのでメモ

ファイルダウンロードダイアログでブラウザによってファイル名の設定方法が統一されていない。asciiのみのファイル名ならなら問題ないが日本語の場合困ったことに。

色々調べた結果、以下の対応でOKぽい(IE8はやってない)
・IE6/IE7はUTF-8でURLエンコード
・Chrome/firefoxはISO-2022-JPでbase64エンコード
・opera/safariは全角スペース2ついれてISO-8859-1で復号化した文字列

Content-Dispositionのfilenameに以下のようなメソッドで取得したファイル名を設定

public static String getFilename(String filename) {
        if (filename == null || "".equals(filename)) {
            return "";
        }
        String displayName = null;
        try {
            if (isIE6() || isIE7()) {
                // IE6 IE7はUTF-8でURLエンコード
                displayName = URLEncoder.encode(filename, "UTF-8");
            } else if (isChrome() || isFirefox()) {
                // googlechrome firefox はISO-2022-JPでbase64エンコード
                displayName = MimeUtility.encodeWord(filename, "ISO-2022-JP",
                        "B");
            } else if (isSafari() || isOpera()) {
                // opera safariはlatin1で
                String encodedName = "  " + filename;
                displayName = new String(encodedName.getBytes(), "ISO-8859-1");
            } else {
                // サポート対象外はとりあえずISO-2022-JPでbase64エンコード
                displayName = MimeUtility.encodeWord(filename, "ISO-2022-JP",
                        "B");
            }
        } catch (UnsupportedEncodingException e) {
            // エラー処理
        }
        return displayName;
    }
    


2010年7月20日火曜日

Teedaカスタムコンポーネント作成手順

Teedaカスタムコンポーネント作成手順

今回はTeedaの拡張部品を作成したいと思います。
TeedaはSeasarプロジェクト発のJSFを拡張したWebアプリケーションフレームワークで、
慣れるとなかなか便利で使いやすいフレームワークなんですが、拡張するとなると
色々調べないといけないのでちょっと手間がかかります。
ただカスタムコンポーネントの拡張は、基本的にはJSFのカスタムコンポーネント作成と
流れは一緒なので、そんなに難しくないです。

Teedaに用意されている金額入力コンポーネントがありますが、
この部品を拡張してみます。
pageNameという属性の追加のみで、レンダラ実装はとりあえず空とします。

○やるべきこと
  ・tldファイルの作成
  ・カスタムUIコンポーネントクラスの作成
  ・カスタムファクトリークラスの作成
  ・カスタムタグクラスの作成
  ・カスタムレンダラーの作成
  ・faces-config.xmlにUIコンポーネントを登録
  ・diconにfactoryとrendererのコンポーネント登録
  ・xhtmlでのカスタムタグ使用


○tldファイルの作成
 作成したTLDファイルはアプリケーション内のフォルダならどこにでも配置できるが、通常はWEB-INF/tldなどのようにWEB-INFディレクトリの配下のフォルダに配置して隠ぺい。JAR形式にした場合にはMETA-INFディレクトリ以下に配置。
 何故かtldファイルをWEB-INF直下に配置しただけだと有効にならなかったので、tldファイルはjar化してクラスパスに追加。

 
○customext.taglib.tld
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
  PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
      <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>dekatotoro</short-name>
    <uri>http://www.dekatotoro.co.jp/dekatotoro/teeda/extension</uri>
    <description>Teeda Extension Tag Library</description>

    <tag>
        <name>inputCommaText</name>
        <tag-class>
            jp.co.dekatotoro.ui.tag.ExtTInputCommaTextTag
        </tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>pageName</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>value</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        省略・・・
    </tag>
        
<taglib>




○カスタムUIコンポーネントクラスの作成
 THtmlInputCommaTextクラスを拡張してカスタムUIコンポーネントクラスを作成。

package jp.co.dekatotoro.ui.component;

import javax.faces.context.FacesContext;
import javax.faces.el.VariableResolver;
import org.seasar.framework.util.AssertionUtil;
import org.seasar.teeda.extension.component.html.THtmlInputCommaText;

public class ExtTHtmlInputCommaText extends THtmlInputCommaText {

    /** コンポーネントタイプ */
    public static final String COMPONENT_TYPE        = "jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText";

    /** レンダラータイプ */
    public static final String DEFAULT_RENDERER_TYPE = "jp.co.dekatotoro.ui.render.ExtTHtmlInputCommaTextRenderer";

    /** 追加したPageName属性 */
    private String             pageName;

    public ExtTHtmlInputCommaText() {
        setRendererType(DEFAULT_RENDERER_TYPE);
    }

    // pageName属性からPageクラスを取得
    public Object getPage(final FacesContext context) {
        AssertionUtil.assertNotNull("FacesContext", context);
        final VariableResolver variableResolver = context.getApplication().getVariableResolver();
        return variableResolver.resolveVariable(context, getPageName());
    }


    @Override
    public Object saveState(final FacesContext context) {

        final Object[] values = new Object[2];
        values[0] = super.saveState(context);
        values[1] = pageName;
        return values;
    }

    @Override
    public void restoreState(final FacesContext context, final Object state) {
        final Object[] values = (Object[])state;
        super.restoreState(context, values[0]);
        pageName = (String)values[1];
    }

    public String getPageName() {
        return pageName;
    }

    public void setPageName(String pageName) {
        this.pageName = pageName;
    }

}


○カスタムファクトリークラスの作成
 InputCommaTextFactoryクラスを拡張してカスタムUIコンポーネントクラスを作成。

package jp.co.dekatotoro.ui.factory;

import java.util.Map;
import org.seasar.teeda.extension.ExtensionConstants;
import org.seasar.teeda.extension.html.ActionDesc;
import org.seasar.teeda.extension.html.ElementNode;
import org.seasar.teeda.extension.html.PageDesc;
import org.seasar.teeda.extension.html.factory.InputCommaTextFactory;

public class ExtInputCommaTextFactory extends InputCommaTextFactory {


    public ExtInputCommaTextFactory() {}

    @Override
    protected void customizeProperties(Map properties, ElementNode elementNode, PageDesc pageDesc, ActionDesc actionDesc) {
        super.customizeProperties(properties, elementNode, pageDesc, actionDesc);
        if (!properties.containsKey(ExtensionConstants.PAGE_NAME_ATTR)) {
            properties.put(ExtensionConstants.PAGE_NAME_ATTR, pageDesc.getPageName());
        }
    }

    // tldファイルの取得先
    @Override
    protected String getUri() {
        return "http://www.dekatotoro.co.jp/dekatotoro/teeda/extension";
    }
    
    
    //  金額入力コンポーネントはclass属性にT_currencyを付加するが、
    //  変更したい場合は本メソッドを拡張する
    @Override
    public boolean isMatch(ElementNode elementNode, PageDesc pageDesc, ActionDesc actionDesc) {
        super.isMatch(elementNode, pageDesc, actionDesc);
    }

}



○カスタムタグクラスの作成
 TInputCommaTextTagクラスを拡張します。
package jp.co.dekatotoro.ui.tag;
import javax.faces.component.UIComponent;
import jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText;
import org.seasar.teeda.extension.ExtensionConstants;
import org.seasar.teeda.extension.taglib.TInputCommaTextTag;

public class ExtTInputCommaTextTag extends TInputCommaTextTag {

    /** Page名 */
    private String pageName;


    public ExtTInputCommaTextTag() {}

    /**
     * PageNameをプロパティへセット
     */
    @Override
    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        setComponentProperty(component, "pageName", getPageName());
    }

    /**
     * コンポーネントタイプを取得
     */
    @Override
    public String getComponentType() {
        return ExtTHtmlInputCommaText.COMPONENT_TYPE;
    }

    public String getPageName() {
        return pageName;
    }

    public void setPageName(String pageName) {
        this.pageName = pageName;
    }

}




○カスタムレンダラーの作成
 THtmlInputCommaTextRendererを拡張
 拡張したいメソッドをオーバライドする。

package jp.co.dekatotoro.ui.render;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.seasar.teeda.extension.render.html.THtmlInputCommaTextRenderer;


public class ExtTHtmlInputCommaTextRenderer extends THtmlInputCommaTextRenderer {

    public ExtTHtmlInputCommaTextRenderer() {}

    /**
     * javascript読み込みキーを取得
     */
    @Override
    protected String getScriptKey() {
        return  "view/js/io/ExtTHtmlInputCommaText.js";
    }
    
    @Override
    protected void encodeHtmlHtmlInputTextPrepare(FacesContext context, THtmlInputText htmlInputText)
        throws IOException {
        //拡張処理
    }

    
    @Override
    protected void doEncodeEndStart(FacesContext context, THtmlInputText htmlInputText) throws IOException {
        //拡張処理
    }

    @Override
    protected void renderOnfocus(THtmlInputCommaText htmlInputCommaText, ResponseWriter writer, String groupingSeparator)
        throws IOException {
        //拡張処理
    }

    
    @Override
    protected String getValue(FacesContext context, UIComponent component) {
        //拡張処理
    }
}





○faces-config.xmlにUIコンポーネンの登録
    <component>
        <component-type>
            jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText
        </component-type>
        <component-class>
            jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText
        </component-class>
    </component>



○diconにコンポーネント登録
teeda-extension-1.0.13-spx.jarのteedaExtension.diconに
factoryとrendererの登録がされているので、同じようにfactoryと
rendererを登録します。

とりあえず、jarからteedaExtension.diconコピーしてきて
以下を追加。

※factoryはoriginalより前に定義する必要あり!?
    <!-- factoryはシングルトン -->
    <component name="extInputCommaTextFactory" class="jp.co.dekatotoro.ui.factory.ExtInputCommaTextFactory" />

    <!--
    レンダラはAutoRegisterで登録 一応prototype(毎回生成のスコープ)
    レンダラの実装にもよるけど、Teedaもシングルトンなんでパフォーマンス的にもシングルトンの方がよいのかな。。
    -->
    <component
        class="org.seasar.teeda.core.render.autoregister.TeedaRendererComponentAutoRegister">
        <property name="instanceDef">
            @org.seasar.framework.container.deployer.InstanceDefFactory@PROTOTYPE
        </property>
        <initMethod name="addReferenceClass">
            <arg>@jp.co.dekatotoro.ui.render.ExtTHtmlInputCommaTextRenderer@class</arg>
        </initMethod>
        <initMethod name="addClassPattern">
            <arg>"jp.co.dekatotoro.ui.render"</arg>
            <arg>".*Renderer"</arg>
        </initMethod>
    </component>



○xhtmlでのカスタムタグ使用
 ファクトリのisMatchメソッドを拡張していないので、指定方法はTHtmlInputCommaTextと同様で
 class属性に"T_currency"を指定するのみ

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:te="http://www.seasar.org/teeda/extension" xml:lang="ja" lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title id="title">タイトル</title>
</head>
<body>

<form id="form" autocomplete="off">

<input type="text" id="aaa" class="T_currency" />

</form>





定義を纏めると
・ファクトリ     → diconファイル
・コンポーネント → faces-config.xml
・タグ           → tldファイル
・レンダラ       → ファイル

動き的には
 ファクトリがタグファイル特定
 →tldのtag定義からtagクラス特定
 →tagクラスからコンポーネントクラス特定
 →コンポーネントクラスからレンダラ特定
みたいな感じかな(適当)

javascriptの書き方 色々

javascriptの書き方は色々あるけど、こんな風にかくと
きれいになるのかなぁっと思う書き方を纏めてみた。

javascriptを分業して実装していると、変数名の重複に
注意しなければならないが、名前空間を用いれば気にする
必要がなくなる。クロージャの応用っぽい感じ。

また関数定義やオブジェクト定義、クラス定義など
用途によって使いわける。
prototype.jsやJQueryを使用する場合は、また
ちょっと変わってくるかな。。


▼ config.jsで名前空間と定数定義

var NMSP = {};
NMSP.common = {}; // 共通処理用
NMSP.config = {}; // 定数定義用
NMSP.xxx1 = {}; // 以下用途に合わせて menuやvalidateなど。
NMSP.xxx2 = {};
NMSP.xxx3 = {}; 

// 定数定義
NMSP.config.AAA      = "aaa";
NMSP.config.BBB      = "bbb";
NMSP.config.CCC      = "ccc";



▼ common.jsで関数定義 共通処理とか

(function() {
    
    var xxxArray = [];
    
    function hogefunc1(arg) {
        //処理
    }
        
    function hogefunc2(arg1, arg2) {
        //処理
    }    
    
    function hogefunc3(arg1, arg2) {
        //処理
    }
    
    // 名前空間へのエクスポート
    NMSP.common.hogefunc1 = hogefunc1;
    NMSP.common.hogefunc2 = hogefunc2;
    NMSP.common.hogefunc3 = hogefunc3;
    
})();


▼ hoge1.jsで関数定義

(function() {

    var initObj = {};
    var initWidth = 100;
    var initHeight = 100;
    
    function hogefunc1() {
        //処理
    }

    // 名前空間へのエクスポート
    NMSP.xxx1.hogefunc1 = hogefunc1;

})();


▼ hoge2.jsでクラス定義

// オブジェクト指向に向いている部品の場合はクラスを使用
(function() {

    /**
     *  コンストラクタ
     */
    function Hoge(arg1, arg2, arg3) {
        this.arg1 = arg1;
        this.arg2 = arg2;
        this.arg3 = arg3;
        this._initXxx();
    }

    /**
     *  初期処理
     */
    Hoge.prototype._initXxx = function() {
        // 処理
    }

    /**
     * 実行
     */
    Hoge.prototype._execXxx = function() {
       // 処理
    }
    
    // 名前空間へのエクスポート
    NMSP.xxx2.Hoge = Hoge;

})();


▼ hoge3.jsでオブジェクト定義

NMSP.xxx3 = {
  
  hogefunc1 : function(arg) {
      
  },
  
  hogefunc2 : function(arg) {
      
  },
  
  hogefunc3 : function(arg1, arg2) {
      
  },
  
};


▼ hoge4.jsでオブジェクト定義 別の名前空間を使用

if (typeof(NMSP2) == 'undefined') {
  NMSP2 = {};
}
if (typeof(NMSP2.xxx) == 'undefined') {
  NMSP2.xxx = {};
}

MSP2.xxx = {
  
  hogefunc1 : function(arg) {
      
  },
  
  
  hogefunc2 : function(arg) {
      
  },
  
  hogefunc3 : function(arg1, arg2) {
      
  },
  
};


▼ hoge5.jsでオブジェクト定義 別の名前空間を使用

// 上記とちょっと違う書き方だが好みの問題
if (typeof(NMSP3) == 'undefined') {
  NMSP3 = {};
}
if (typeof(NMSP3.xxx) == 'undefined') {
  NMSP3.xxx = {};
}

NMSP3.xxx.hogefunc1 = function (arg1, arg2) {

};

NMSP3.xxx.hogefunc2 = function (arg1, arg2) {

};

2010年7月16日金曜日

javascriptの初期処理を順番で実行するように制御する

init.js
( function() {

    // namespaceオブジェクト
    var NSOBJ = {};
    NSOBJ.common = {};
    
    var initFunc = [];
    
    function init() {
        initFunc.sort( function(a, b) {
            return a[1] - b[1];
        });
        for ( var i = 0, ilen = initFunc.length; i < ilen; i++) {
            initFunc[i][0]();
        }
    }
    
    // init登録用関数
    function setInit(f, order) {
        var a = [];
        a[0] = f;
        a[1] = (order != null ? parseInt(order) : 9999);
        initFunc.push(a);
    }
    
    // DOMContentLoadedでinit登録
    // 別にonloadでもよいけど
    jQuery(document).ready( function() {
        init();
    });
    
    
    NSOBJ.common.setInit = setInit;
    
    
})();


hoge.js
( function() {

    function hoge1func() {
       ・・・
    }

    function hoge2func() {
       ・・・
    }
    
    function hoge3func() {
       ・・・
    }

    // 関数登録
    NSOBJ.common.setInit(hoge1func, 10);
    NSOBJ.common.setInit(hoge2func, 30);
    NSOBJ.common.setInit(hoge3func, 100);    
})();

IE6でdiv要素をセレクトボックスに勝たせる

div要素でposition: absolute;など指定してポップアップ部品
などを作成することがありますが、
IE6だとdiv要素がセレクトボックスに負けてしまいます。

なのでiframeでかぶせてz-indexを指定することで
セレクトボックスに勝つようにする対応する必要があります。

また、上記対応をしてもブラウザ横スクロールが動作すると
何故かセレクトボックスに負けてしまうので
scrollイベントでスタイルの再割り当てを行うことで勝つ!
ちょっとちらつくけど。。他に方法ないかな~

function iframeCover() {
    //isIe6()はIE6の場合trueを返す関数
    if (isIe6()) {
        var targetObj =document.getElementById("divtarget");
        var html = "<iframe id=\"overlayframe\" src=\"javascript:false\" style=\"position: absolute; display: block; "
            + "z-index: -1; width: 100%; height: 100%; top: 0; left: 0;"
            + "filter: mask(); background-color: #ffffff; \"></iframe>";
        targetObj.innerHTML += html;
        
        // scrollイベントでstyleの再割り当て
        jQuery(window).bind("scroll", function(){
            var targetObj = document.getElementById("divtarget");
            // 何でもよいのでstyleの再割り当てを行う。
            var orignalBorder = targetObj.style.border;
            targetObj.style.border = 1;
            targetObj.style.border = orignalBorder;
        });
    }
}

faceletsカスタムコンポーネント作成

今回はfaceletsでJSFタグCommandLinkを拡張して部品を作成したいと思います。
※とりあえず"career"という属性を追加したレンダリングを行うのみ
faceletsを使用する場合、独自に作成したJSFカスタムコンポーネントは使用できまんせん。


○やるべきこと
  ・taglibファイルの作成
  ・web.xml登録
  ・カスタムUIコンポーネントクラスの作成
  ・カスタムレンダラーの作成
  ・faces-config.xmlにUIコンポーネントとレンダラーの登録
  ・xhtmlでのカスタムタグ使用


○taglibファイルの作成
 カスタムタグをtaglibファイルを作成してWEB-INF直下に配置します。
 以下を定義します。
  ・namespace
  ・tag-name
  ・component-type
  ・renderer-type
 
○custom.taglib.xml
 <?xml version="1.0"?> 
 <!DOCTYPE facelet-taglib PUBLIC 
   "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" 
   "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> 
 <facelet-taglib> 
       <namespace>http://www.arc-mind.com/jsf</namespace> 
 
     <tag> 
         <tag-name>extLink</tag-name> 
         <component> 
             <component-type>ui.component.ExtLink</component-type> 
             <renderer-type>ui.render.ExtLinkRender</renderer-type> 
         </component> 
     </tag> 
 
 </facelet-taglib> 


○web.xmlに登録
 web.xmlに作成したcustom.taglib.xmlを登録します。
 web.xmlに以下のcontext-paramを追加。
 ※faceletsのversionによってparam-nameが違うため、以下のどちらかを指定する。
   <context-param> 
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name> 
        <param-value>/WEB-INF/custom.taglib.xml</param-value> 
   </context-param> 

 ※facelets-1.1.15-jsf1.2はこちらの定義で動作
   <context-param> 
         <param-name>facelets.LIBRARIES</param-name> 
         <param-value> 
         /WEB-INF/custom.taglib.xml 
         </param-value> 
  </context-param> 



○カスタムUIコンポーネントクラスの作成
 HtmlCommandLinkクラスを拡張してカスタムUIコンポーネントクラスを作成。
 オーバライドするべきメソッドは以下の通り。
  getFamily()    ファミリ名を返すメソッド
 package ui.component; 
 
 import javax.faces.component.html.HtmlCommandLink; 
 
 public class ExtLink extends HtmlCommandLink { 
 
 
  @Override
  public String getFamily() {
   return "GpsLinkFamily";
  }
 }



○faces-config.xmlにUIコンポーネントとレンダラーの登録
  <component> 
   <component-type>ui.component.ExtLink</component-type> 
   <component-class>ui.component.ExtLink</component-class> 
  </component> 
 
  <render-kit> 
   <render-kit-id>HTML_BASIC</render-kit-id> 
   <renderer> 
    <component-family>ExtLinkFamily</component-family> 
    <renderer-type>ui.render.ExtLinkRender</renderer-type> 
    <renderer-class>ui.render.ExtLinkRender</renderer-class> 
   </renderer> 
  </render-kit> 


○カスタムレンダラーの作成
 必要に応じてdecodeメソッドと各種encodeメソッドをオーバーライド。
 decodeメソッドはmanagedBean適用時(ブラウザ→サーバ)。
 encodeメソッドはレンダリング時(サーバ→ブラウザ)。

 赤字部分のみ追加であとはCommandLinkRendererのコピペ
 encode処理にて属性にcareerを追加してみる。decode処理はそのまま。
 ※ decodeでリクエストパラメータからコンポーネントに設定する場合はsetValue()ではなく、
  setSubmittedValue()で値を設定する。valueChangeListener()がsetValueだと呼ばれないため。
 package ui.render; 
 
 import java.io.IOException; 
 import java.util.Map; 
 
 import javax.faces.component.UIComponent; 
 import javax.faces.component.UIForm; 
 import javax.faces.context.FacesContext; 
 import javax.faces.context.ResponseWriter; 
 
 import com.sun.faces.renderkit.AttributeManager; 
 import com.sun.faces.renderkit.RenderKitUtils; 
 import com.sun.faces.renderkit.html_basic.CommandLinkRenderer; 
 import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer; 
 
 public class ExtLinkRender extends CommandLinkRenderer { 
 
    public ExtLinkRender() 
      { 
     super(); 
      } 
     private static final String ATTRIBUTES[]; 
     private static final String SCRIPT_STATE = "com.sun.faces.scriptState"; 
 
     static 
     { 
         ATTRIBUTES = AttributeManager.getAttributes(com.sun.faces.renderkit.AttributeManager.Key.COMMANDLINK); 
     } 
 
 
  protected void renderAsActive(FacesContext context, UIComponent command) 
    throws IOException { 
   ResponseWriter writer = context.getResponseWriter(); 
   if (writer == null) 
    throw new AssertionError(); 
   String formClientId = getFormClientId(command, context); 
   if (formClientId == null) 
    return; 
   writer.startElement("a", command); 
   writeIdAttributeIfNecessary(context, writer, command); 
   writer.writeAttribute("href", "#", "href"); 
 
   Map attributeMap = command.getAttributes(); 
   writer.writeAttribute("career", "#", null); 
 
   RenderKitUtils.renderPassThruAttributes(writer, command, ATTRIBUTES); 
   RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, command); 
   String userOnclick = (String) command.getAttributes().get("onclick"); 
   StringBuffer sb = new StringBuffer(128); 
   boolean userSpecifiedOnclick = userOnclick != null 
     && !"".equals(userOnclick); 
   if (userSpecifiedOnclick) { 
    sb.append("var a=function(){"); 
    userOnclick = userOnclick.trim(); 
    sb.append(userOnclick); 
    if (userOnclick.charAt(userOnclick.length() - 1) != ';') 
     sb.append(';'); 
    sb.append("};var b=function(){"); 
   } 
   HtmlBasicRenderer.Param params[] = getParamList(command); 
   String commandClientId = command.getClientId(context); 
   String target = (String) command.getAttributes().get("target"); 
   if (target != null) 
    target = target.trim(); 
   else 
    target = ""; 
   sb.append(getOnClickScript(formClientId, commandClientId, target, 
     params)); 
   if (userSpecifiedOnclick) 
    sb.append("};return (a()==false) ? false : b();"); 
   writer.writeAttribute("onclick", sb.toString(), "onclick"); 
   writeCommonLinkAttributes(writer, command); 
   writeValue(command, writer); 
   writer.flush(); 
  } 
 
  private static boolean hasScriptBeenRendered(FacesContext context) { 
   return context.getExternalContext().getRequestMap().get( 
     com.sun.faces.scriptState) != null; 
  } 
 
  private static void setScriptAsRendered(FacesContext context) { 
   context.getExternalContext().getRequestMap().put( 
     com.sun.faces.scriptState, Boolean.TRUE); 
  } 
 
  private static String getFormClientId(UIComponent component, 
    FacesContext context) { 
   UIForm form = getMyForm(component); 
   if (form != null) 
    return form.getClientId(context); 
   else 
    return null; 
  } 
 
  private static UIForm getMyForm(UIComponent component) { 
   UIComponent parent; 
   for (parent = component.getParent(); parent != null 
     && !(parent instanceof UIForm); parent = parent.getParent()) 
    ; 
   return (UIForm) parent; 
  } 
 
 } 



○xhtmlでのカスタムタグ使用
 taglibファイルに定義したnamespaceでタグを使用する。

<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="/WEB-INF/jsf_custum.tld" prefix="custom" %>
<html>
<head>
<title>HelloWorld</title>
</head>
<body>
<f:view>
<h:form>

文字を入力して下さい:<h:inputText id="string" value="#{HelloWorldBean.helloWorld}"/>
<h:commandButton value="送信"/>
<p><h:outputText id="output" value="#{HelloWorldBean.helloWorld}"/></p>


<h:commandButton id="btn1" action="ok" value="遷移" actionListener="#{HelloWorldBean.assembleMessage}" />
<br>
<custom:extlink id="link1" action="ok" actionListener="#{HelloWorldBean.assembleMessage}">カスタムリンク</custom:extlink> 
</h:form>
</f:view>
</body>
</html>




 注意点
  JBossToolでSeemプロジェクトを作成するとsrc/hotとsrc/mainが作成されるが、src/main側に入れないとClassが見つからない
  と怒られる。
   src/hot    エンティティクラス以外のソースディレクトリ
   src/main    エンティティクラスのソースディレクトリ

2010年6月14日月曜日

JSFカスタムコンポーネント作成

今回はJSFタグCommandLinkを拡張して部品を作成したいと思います。
※とりあえず"test"という属性を追加したレンダリングを行うのみ

やるべきこと          
    ・カスタムタグクラスの作成      
    ・カスタムUIコンポーネントクラスの作成      
    ・カスタムレンダラーの作成      
    ・tldファイルの作成 (タグクラスの登録とプロパティの追加)      
    ・faces-config.xmlにUIコンポーネントとレンダラーの登録      
    ・tldファイルの配置      
    ・jspでのカスタムタグ使用      



○カスタムタグクラスの作成                                          
CommandLinkTagクラスを拡張してカスタムタグクラスを作成。                                          
オーバライドするべきメソッドは以下の通り。                                          
    getRendererType()    レンダラタイプを返すメソッド      
    getComponentType()    コンポーネントタイプを返すメソッド      
    setProperties()    JSPページに指定された属性値を、UIコンポーネントに設定するメソッド      


package ui.tag;                                                                                
                                                                                
import java.util.Map;                                                                                
                                                                                
import javax.faces.component.UIComponent;                                                                                
                                                                                
import com.sun.faces.taglib.html_basic.CommandLinkTag;                                                                                
                                                                                
public class ExtLinkTag extends CommandLinkTag {                                                                                
                                                                                
    private String test;                                                                            
                                                                                
    public void setTest(String test) {                                                                            
        this.test = test;                                                                        
    }                                                                            
                                                                                
    @Override                                                                            
    public String getRendererType() {                                                                            
        return "ExtLinkRenderer";                                                                        
    }                                                                            
                                                                                
    @Override                                                                            
    public String getComponentType() {                                                                            
        return "ExtLink";                                                                        
    }                                                                            
                                                                                
    @Override                                                                            
    protected void setProperties(UIComponent component) {                                                                            
        super.setProperties(component);                                                                        
                                                                                
        Map attributeMap = component.getAttributes();                                                                        
                                                                                
        if (test != null) {                                                                        
            attributeMap.put("test", test);                                                                    
        }                                                                        
    }    
    

  
  


○カスタムUIコンポーネントクラスの作成                                                          
HtmlCommandLinkクラスを拡張してカスタムUIコンポーネントクラスを作成。                                                          
オーバライドするべきメソッドは以下の通り。                                                          
 getFamily() ファミリ名を返すメソッド                                      
package ui.component;                                                            
                                                            
import javax.faces.component.html.HtmlCommandLink;                                                            
                                                            
public class ExtLink extends HtmlCommandLink {                                                            
                                                            
                                                            
    @Override                                                        
    public String getFamily() {                                                        
        return "ExtLinkFamily";                                                    
    }                                                        
}                                                            
                                  





                                                                          
○tldファイルの作成           
タグクラスの登録とプロパティの追加を行う。          
・・・
    <tag>
        <name>
            extlink
        </name>
        <tag-class>
            ui.tag.ExtLinkTag
        </tag-class>
        <body-content>
            JSP
        </body-content>
        <attribute>
            <name>
                action
            </name>
            <required>
                false
            </required>
            <deferred-method>
                <method-signature>
                    java.lang.Object action()
                </method-signature>
            </deferred-method>
        </attribute>
        <attribute>
            <name>
                actionListener
            </name>
            <required>
                false
            </required>
            <deferred-method>
                <method-signature>
                    void actionListener(javax.faces.event.ActionEvent)
                </method-signature>
            </deferred-method>
        </attribute>
        <attribute>
            <name>
                id
            </name>
            <required>
                false
            </required>
            <rtexprvalue>
                true
            </rtexprvalue>
        </attribute>
        <attribute>
            <name>
                test
            </name>
            <required>
                false
            </required>
            <rtexprvalue>
                true
            </rtexprvalue>
        </attribute>        
・・・        






○faces-config.xmlにUIコンポーネントとレンダラーの登録                                          
 <component>                                            
     <component-type>ExtLink</component-type>                                        
     <component-class>ui.component.ExtLink</component-class>                                        
 </component>                                            
                                             
 <render-kit>                                            
     <renderer>                                        
         <component-family>ExtLinkFamily</component-family>                                    
         <renderer-type>ExtLinkRenderer</renderer-type>                                    
         <renderer-class>ui.render.ExtLinkRender</renderer-class>                                    
     </renderer>                                        
 </render-kit>    





○カスタムレンダラーの作成                                              
必要に応じてdecodeメソッドと各種encodeメソッドをオーバーライド。                                              
decodeメソッドはmanagedBean適用時。                                              
encodeメソッドはレンダリング時。                                              
                                              
encode処理にて属性にtestを追加してみる。
decode処理はそのまま。                                              
package ui.render;                                                            
                                                            
import java.io.IOException;                                                            
import java.util.Map;                                                            
import java.util.logging.Level;                                                            
                                                            
import javax.faces.component.UIComponent;                                                            
import javax.faces.component.UIForm;                                                            
import javax.faces.context.FacesContext;                                                            
import javax.faces.context.ResponseWriter;                                                            
                                                            
import com.sun.faces.renderkit.AttributeManager;                                                            
import com.sun.faces.renderkit.RenderKitUtils;                                                            
import com.sun.faces.renderkit.html_basic.CommandLinkRenderer;                                                            
import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer;                                                            
                                                            
public class ExtLinkRender extends CommandLinkRenderer {                                                            
                                                            
    private static final String ATTRIBUTES[];                                                            
    private static final String SCRIPT_STATE = "com.sun.faces.scriptState";                                                            
                                                            
    static                                                             
    {                                                            
        ATTRIBUTES = AttributeManager.getAttributes(com.sun.faces.renderkit.AttributeManager.Key.COMMANDLINK);                                                            
    }                                                            
                                                                
    @Override                                                        
    public void decode(FacesContext context, UIComponent component) {                                                        
        super.decode(context, component);                                                    
                                                            
    }                                                        
                                                            
    @Override                                                        
    public void encodeBegin(FacesContext context, UIComponent component)                                                        
            throws IOException {                                                
                                                            
        rendererParamsNotNull(context, component);                                                    
        if (!shouldEncode(component))                                                    
            return;                                                
        boolean componentDisabled = Boolean.TRUE.equals(component                                                    
                .getAttributes().get("disabled"));                                            
        String formClientId = getFormClientId(component, context);                                                    
        if (formClientId == null && logger.isLoggable(Level.WARNING))                                                    
            logger.log(Level.WARNING,                                                
                    Component {0} must be enclosed inside a form, component                                        
                            .getId());                                
        if (componentDisabled || formClientId == null) {                                                    
            renderAsDisabled(context, component);                                                
        } else {                                                    
            if (!hasScriptBeenRendered(context)) {                                                
                RenderKitUtils.renderFormInitScript(                                            
                        context.getResponseWriter(), context);                                    
                setScriptAsRendered(context);                                            
            }                                                
            renderAsActive(context, component);                                                
        }                                                    
    }                                                        
                                                            
    protected void renderAsActive(FacesContext context, UIComponent command)                                                        
            throws IOException {                                                
        ResponseWriter writer = context.getResponseWriter();                                                    
        if (writer == null)                                                    
            throw new AssertionError();                                                
        String formClientId = getFormClientId(command, context);                                                    
        if (formClientId == null)                                                    
            return;                                                
        writer.startElement("a", command);                                                    
        writeIdAttributeIfNecessary(context, writer, command);                                                    
        writer.writeAttribute("href", "#", "href");                                                    
                                                            
                                                            
        Map attributeMap = command.getAttributes();                                                    
        writer.writeAttribute("test", "#", null);                                                    
                                                            
        RenderKitUtils.renderPassThruAttributes(writer, command, ATTRIBUTES);                                                    
        RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, command);                                                    
        String userOnclick = (String) command.getAttributes().get("onclick");                                                    
        StringBuffer sb = new StringBuffer(128);                                                    
        boolean userSpecifiedOnclick = userOnclick != null                                                    
                && !"".equals(userOnclick);                                            
        if (userSpecifiedOnclick) {                                                    
            sb.append("var a=function(){");                                                
            userOnclick = userOnclick.trim();                                                
            sb.append(userOnclick);                                                
            if (userOnclick.charAt(userOnclick.length() - 1) != ';')                                                
                sb.append(';');                                            
            sb.append("};var b=function(){");                                                
        }                                                    
        HtmlBasicRenderer.Param params[] = getParamList(command);                                                    
        String commandClientId = command.getClientId(context);                                                    
        String target = (String) command.getAttributes().get("target");                                                    
        if (target != null)                                                    
            target = target.trim();                                                
        else                                                    
            target = "";                                                
        sb.append(getOnClickScript(formClientId, commandClientId, target,                                                    
                params));                                            
        if (userSpecifiedOnclick)                                                    
            sb.append("};return (a()==false) ? false : b();");                                                
        writer.writeAttribute("onclick", sb.toString(), "onclick");                                                    
        writeCommonLinkAttributes(writer, command);                                                    
        writeValue(command, writer);                                                    
        writer.flush();                                                    
    }                                                        
                                                            
    private static boolean hasScriptBeenRendered(FacesContext context) {                                                        
        return context.getExternalContext().getRequestMap().get(                                                    
                com.sun.faces.scriptState) != null;                                            
    }                                                        
                                                            
    private static void setScriptAsRendered(FacesContext context) {                                                        
        context.getExternalContext().getRequestMap().put(                                                    
                com.sun.faces.scriptState, Boolean.TRUE);                                            
    }                                                        
                                                            
    private static String getFormClientId(UIComponent component,                                                        
            FacesContext context) {                                                
        UIForm form = getMyForm(component);                                                    
        if (form != null)                                                    
            return form.getClientId(context);                                                
        else                                                    
            return null;                                                
    }                                                        
                                                            
    private static UIForm getMyForm(UIComponent component) {                                                        
        UIComponent parent;                                                    
        for (parent = component.getParent(); parent != null                                                    
                && !(parent instanceof UIForm); parent = parent.getParent())                                            
            ;                                                
        return (UIForm) parent;                                                    
    }                                                        
                                                            
}                                                            





○tldファイルの配置                      
    WEB-INF/jsf_custum.tldに配置 jsf-tlds.jarに含めてもよいと思います。                  




○jspでのカスタムタグ使用                                                  
taglibでtldファイル読込みとprefix定義を行う。                                                  
タグ名はtldファイルに定義したname                                                  



<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="/WEB-INF/jsf_custum.tld" prefix="custom" %>
<html>
<head>
<title>HelloWorld</title>
</head>
<body>
<f:view>
<h:form>

文字を入力して下さい:<h:inputText id="string" value="#{HelloWorldBean.helloWorld}"/>
<h:commandButton value="送信"/>
<p><h:outputText id="output" value="#{HelloWorldBean.helloWorld}"/></p>


<h:commandButton id="btn1" action="ok" value="遷移" actionListener="#{HelloWorldBean.assembleMessage}" />
<br>
<custom:extlink id="link1" action="ok" actionListener="#{HelloWorldBean.assembleMessage}">カスタムリンク</custom:extlink> 
</h:form>
</f:view>
</body>
</html>