arcanum_jp’s blog

おっさんの日記

Wicketでエラーコンポーネントの色を変えるのってどうするの??


 Wicketでエラー処理とかでメッセージを画面に表示した場合に、該当するコンポーネントを分かるように赤くしたいとかいった処理ってどうやるんだろうと悩む。結局エラー処理で該当コンポーネントにSimpleAttributeModifierで該当するスタイルシートを指定すればいいんだろうか。こんな風に

class HogePage extends WebPage{

    private TextField _userid = new TextField("userid" ...);
    private TextField _password = new TextField("password" ...);


    private void onClickLogin(){

        if(_useridがエラー){
            error("ユーザIDがエラー");
            _userid.add(new SimpleAttributeModifier("class", "message"));
            return;
        }
        if(_passwordがエラー){
            error("パスワードがエラー");
            _password.add(new SimpleAttributeModifier("class", "message"));
            return;
        }

    }

}


 これだと、メッセージの設定なんかはプログラマーの責務なんだろうけど、ビヘイビアをプログラマがわざわざ登録しなくちゃならなくなるような感じがするし、前のエラーで設定したビヘイビアの除去とかしないといけない点をプログラマに責任を転化してしまう点とか定型的な処理をいろんなクラスに散らせてしまう点(コピペの再利用につながる)とか嫌だなぁと感じる。
 

 実はなんかいい方法ないかなと思って考えた方法。もしかすると実はもっと簡単なGoodな方法があるかもしれませんが、調べ切れませんでした。処理にはWicket-jaで質問させていただいた内容を元に作った部分が含まれています。なお、エラーメッセージは画面に1つしか出力されないという画面処理規約みたいなもんがあると仮定しています。

参考:
Wicket-ja:Re: コンポーネントにadd()したコンポーネントの一覧を取得する方法についての質問
http://lists.sourceforge.jp/mailman/archives/wicket-ja-user/2009-March/000310.html


 まず、マーカーとなるビヘイビアを作ります。これはマーカー(デザインパターンでいうマーカーインターフェースとしてのクラス定義)なので拡張も何もおこないません。

class MarkerBehavior extends SimpleAttributeModifier{
    public MarkerBehavior(final String attr, final CharSequence value){
        super(attr, value);
    }
}

 画面クラスを作り、描画の前処理でFeedbackMessageが登録されたコンポーネントにだけスタイルシートを設定します。

public abstract class HogePage extends WebPage{


    // 描画の前処理
    protected void onBeforeRender() {

        super.onBeforeRender();

        /**
         * 画面に登録されたコンポーネントの中から、メッセージが登録されているコンポーネントは
         * スタイルシートのクラスをマーカークラスで設定する。また、メッセージが登録されていない
         * のにマーカークラスがある場合は、コンポーネントから取り除く
         */
        IVisitor visitor = new IVisitor(){

            public Object component(Component comp) {

                if(comp.getFeedbackMessage()!=null){
                    MarkerBehavior attr = new MarkerBehavior("class","message");
                    comp.add(attr);
                }
                else{
                    
                    List blist = comp.getBehaviors();
                    for(int i = 0 ; i < blist.size(); i++){
                        IBehavior bhav = (IBehavior)blist.get(i);
                        if(bhav instanceof MarkerBehavior){
                            comp.remove(bhav);
                        }
                        
                    }
                    
                }
                return null;
            }
            
        };
        visitChildren(visitor); // 再帰的にIVisitor#component()を行わせる。

    }


 一応これでメッセージを設定したコンポーネントには"message"というスタイルシートが勝手に設定されます。ただし、コーディングなどでエラー時のメッセージ登録先コンポーネントを明記する必要があります。

エラー時には、該当するコンポーネントにメッセージを設定すること。
<example>
TextField txt;
if(error){
    txt.error("入力が変です。");
    ~~~~~~~~~~
}


 実際にはonBeforeRender()は基底クラスを作ってそちらで一気に行うのがよろしいかと思います。
 


 追記id:t_yanoさんにコメントいただいたので、他の方法も追記


 上記ではページのonBeforeRender()をオーバーライドして毎回リクエストのたんびにビヘイビアを追加したり削除したりと危ういことをしていたのですが、その辺はビヘイビアに任せる方法にします。


 まず、ビヘイビアを作ります。これはコメントにあったようなものそのまんまです。

class ErrorMessageStyleBehavior extends AttributeModifier{
    
    public ErrorMessageStyleBehavior(final Component related){
        super(
            "class", 
            true, 
            new AbstractReadOnlyModel() {
                public String getObject() {
                    if(Session.get().getFeedbackMessages().hasErrorMessageFor(related)){
                        return "message";
                    }
                    return null;
                };
        
            }
        );
    }
}


 次に、Applicationのサブクラス側のinit()で、各コンポーネントが生成されたときにこのビヘイビアが勝手にadd()されるようにします。今回使ったのはIComponentInstantiationListenerインターフェースで、これは、画面のコンポーネントが生成されるときに「このコンポーネントが生成されたぜ!」って通知されるリスナーのようです。

public class HogeApp extends WebApplication {

    
    protected void init() {
        super.init();

        ...省略
        
        // コンポーネントが生成されるときに入力系は特定のビヘイビアを追加する。
        addComponentInstantiationListener(
                new IComponentInstantiationListener(){

                    public void onInstantiation(Component comp) {
                        if(comp instanceof TextField){
                            comp.add(new ErrorMessageStyleBehavior(comp));
                        }
                        
                    }
                    
                }
        );

}

 一応、これで同じような動きになりました。ただ、この場合、RadioGroupなどは追加されないので(なんでかな?ソースを追っかける必要がありますね)そこに関しては何かしら検討する必要がありそうです。