arcanum_jp’s blog

おっさんの日記

Wicketでの処理って

ボタンとか押して処理が終わり、画面が表示されたときにページ内の任意の場所に移動する方法。たとえばこんな感じだったと思う。

<body onload="location.href=#hoge">

これで画面表示時にHTML内で定義されたnameなりidなりがhogeの部分に移動する。Wicketでは<body>タグにあたるコンポーネントが無いみたいでどうやったら<body>の属性を変更できるんだろうと悩んでいた。調べていたらBodyTagAttributeModifierってクラスがあってこれが <body>の属性を変更できるらしい。Webにあったサンプルはこんな感じ。

ページ内のイベントとかでこんな風に使うらしい。

BodyTagAttributeModifier battr = 
    new BodyTagAttributeModifier(
            "onLoad", 
            true, 
            new Model("location.href='#hoge'"),
            this
    );
add(battr);

 で、作ったのが下のコード。いろんなコンポーネントのイベントメソッドでBodyTagAttributeModifierを生成するのも面倒なので、イベントメソッド内では_flagmentに移動先のidなりnameなりを設定するだけにして、ページのレンダリングの前処理で入れるようにするイメージです。

html
<html>
    <head>...</head>
    <body>
        ...
    </body>
</html>
class HogePage extends WebPage{

    // イベント終了時とかに移動するフラグメントを設定する
    String _flagment;
    
    ...省略
    
    protected void onBeforeRender() {
        new BodyTagAttributeModifier(
                "onLoad", 
                true, 
                new Model("location.href='#" + _flagment + "'"),
                this
        );
        add(battr);
        _flagment==null;  // 次のイベントに情報が残らないように
    }

}

う、動かねぇ・・・なんでだ??いつまで経っても <body>は <body>のまま。何か制約なりおまじないがあるのかな。いろいろWebを探し回ったけどよく分からん。


そう思い、一応邪道かもしれないけど自分はこんな感じに解決したので一応メモ。内容は、<body>タグのonload属性を変更するのではなく、画面のヘッダー部分に <script>タグを動的にいれてしまい、location.hrefを設定する方法。

class HogePage extends WebPage{

    // イベント終了時とかに移動するフラグメントを設定する
    String _flagment;
    
    ...省略

    protected void onBeforeRender() {
        if(_flagment != null){
            AbstractBehavior battr = new AbstractBehavior() { 
            
                private String _target;
                {
                    _target = _flagment;
                }
            
                public void renderHead(IHeaderResponse response) { 
                    super.renderHead(response); 
                    response.renderJavascript("location.href='#" + _target + "'", null);
                } 
            };
            add(battr);
        }
        _flagment==null;
    }
}


 一応これでこんな感じにHTMLのヘッダ内に追加されました。実際は1xxxxxの部分にイベントとかで_flagmentに設定した文字列が設定されます。なお、下記HTMLは改行を施しています。

<html>
    <head>
        ...省略
    
        <script type="text/javascript" >
            <!--/*-->
                <![CDATA[/*><!--*/
                    location.href='#xxxxx'
                /*-->]]>*/
        </script>
    
    </head>
    
    ...省略
    
    <div id="xxxxx" >
        ...省略
    </div>
    
    
</html>

 上記コードは最低限な部分のみ記載していますが、ビヘイビアは一旦add()するとずっとadd()されっぱなしになるみたいなので、_flagmentがnullの場合には前イベントで追加したビヘイビアを削除するコードが必要になってきます



 ほんとはこんな邪道っぽい方法じゃなくてBodyTagAttributeModifierが使えればいいんだけど。


コメントにてid:t_yanoさんに指摘を受けましたので、追記します。(しかしこんなことでidトラックバックって使っていいのだろうか・・・)


 上記のような場合、ページにIHeaderContributorインターフェースを実装するとしかるべきときにIHeaderContributorで定義されたrenderHead()が呼び出されるようになりますので、そこで下記のように直接レスポンスに追加するように書けば、OKでした。また、この方法だと、上記コードで懸念していたビヘイビアを除去するコードなども不要になるのでこちらの方が簡単ですね。


 指摘の中のrenderJavascript()を呼び出した場合については、下記のようなコードになります。(WebPageのイメージもあわせて記載)
 

class HogePage extends WebPage implements IHeaderContributor {

    private String _flagment;    // イベントとかで、移動先のフラグメントを設定
    
    ...省略

    public void renderHead(IHeaderResponse response) {
		
        if(_flagment != null){
            response.renderJavascript("location.href='#" + _flagment + "'", null);
        }
        _flagment = null;

    }
}


 また、renderHead()の引数、IHeaderResponseオブジェクトにはrenderOnDomReadyJavascript()というのもあり、こちらはHTMLのDOMがすべて生成された後に呼び出されますということなので、あわせて実行してみました。なお、こちらは引数が1つしかありません。

public void renderHead(IHeaderResponse response) {
	
    if(_flagment != null){
        response.renderOnDomReadyJavascript("location.href='#" + _flagment + "'");
    }
    _flagment = null;

}

体感的には殆ど変わりませんが実行してみると、なにやら下記のようなJavaScriptが出力されました。

<script type="text/javascript" >
    <!--/*--><![CDATA[/*><!--*/
        Wicket.Event.add(window, "domready", function() { location.href='#xxxxxx';});
    /*-->]]>*/</script>


 とりあえず、こちらの意図する機能としてはIHeaderContributorインターフェースを実装する方が簡単でしたが、<body>lにwicket:idをつけて、WebMarkupContainerとしてページにaddする方法も後ほど追記したいと思います。



 追記。コメントにあったように <body>タグにwicket:idをつけて、WebMarkupContainerとしてページにaddしておく方法もやってみました。


 <body>タグに対応したコンポーネントJava側で認識するようコンポーネントをページに追加して上記で動かないと感じていたBodyTagAttributeModifierをページに追加した<body>タグに対応するオブジェクトにadd()します。

class HogePage extends WebPage{

    private String _flagment;

    public HogePage(){

        MarkupContainer body = new MarkupContainer("body", null){};
        add(body);
        
        Form from = new ...
        form.add(new TextField(...));
        form.add(new TextField(...));
        body.add(form);
        
    }

    protected void onBeforeRender() {

        super.onBeforeRender();

        // ボディタグへの指示
        if(_flagment != null){
            
            BodyTagAttributeModifier battr =  
                new BodyTagAttributeModifier(
                        "onLoad", 
                        true, 
                        new Model("location.href='#" + _flagment + "'"),
                        this
                );
            get("body").add(battr);
        }
        _flagment = null;
    }
}

 上記では<body>タグに対応するコンポーネントは無いらしいって書いてしまったけど、こうやって<body>タグをコンポーネントとして認識できるらしい。勉強になった。この方法は、一番はじめに自分が導き出した解答(AbstractBehaviorを使う方法)と同じようにビヘイビアを除去する仕組みが必要になるみたいなので、IHeaderContributorを使うほうがより簡単ですね。