携帯サイト作成時の色々

前回の続き。。

これまで約1ヶ月かけて作ってきたやつをとうとうリリースしました。
っていっても地元友達数人向けだけどね。。評価は上々。よかったです。

今回本格的に携帯向けサイトを作ってて発生した問題を色々殴り書き

携帯絵文字を使う

とりあえず1番はじめに出た要望がこれ。やらなきゃと思ってたけどめんどくさそうで後回しにしてたこと。
とりあえず以下のことをすれば絵文字は使えそう

  • リクエストパラメータ(GET、POST同様)に絵文字が含まれていたらサーバーで管理する文字列に変換する
  • レスポンスデータにサーバーで管理する文字列が含まれていたらキャリアごとに絵文字に変換する(PCの場合は画像で表示)

これを実現するためにサーブレットフィルタをかますことにした。wicketでパラメータを受け取った後だと?に変換されてるし、レスポンスはwicketがページを作った後やらなきゃいけないので、自作フィルタはwicketフィルタより前に定義。

リクエスト側はHTTPServletRequestWrapperを継承したクラスを作成し、getParameter関連をオーバーライドする。その中で絵文字を管理文字列にする処理を追加する。絵文字判定処理は以下のとおり

@Override
public String getParameter(String key){
    String param = super.getParameter(key);
    StringBuilder sb = new StringBuilder();
    if(param != nul){
        for(char c : param.toCharArray()){
            if(Character.UnicodeBlock.of(c) == Character.UnicodeBlock.PRIVATE_USE_AREA){
                //絵文字→管理文字列変換処理
                sb.append(newC);
            }else{
                sb.append(c);
            }
        }
    }
    return sb.toString();
}

レスポンス側はHTTPServletResponseWrapperを継承したクラスを作成し、getWriterとgetOutputをオーバーライドして、操作できるようにする。

これで絵文字を実現することができた。
auだけはエミュレータがないから未確認だけどね。。
腹減ったので飯でも食おうっと。。続く。。

Wicketで複数選択のチェックボックス(2)

前回の続き。

前回、複数選択のチェックボックスを実装したんだけど、携帯でアクセスしてみるとラベルの文字が化ける。「ははぁん。また数値参照の問題だな!」と思い、ドロップダウンでやったときと同じようにやってみた。

final CheckBoxMultipleChoice<User> userList = new CheckBoxMultipleChoice<User>("sendTo",
				users,
				new ChoiceRenderer<User>("userName","userId"));

userList.setEscapeModelStrings(false);

これでいけるはずと思い、再び携帯でアクセス。。。。ダメだ!!!
そこでソースを追ってみるとorg.apache.wicket.markup.html.form.CheckBoxMultipleChoiceクラスにこんなメソッドが。。。

/**
 * Overridable method to determine whether the label markup should be escaped.
 * 
 * @return true if label markup should be escaped
 */
protected boolean isEscapeLabelMarkup()	{
   return true;
}

なるほど。数値参照でエスケープしたくなかったらこいつをオーバーライドしなさいってことね?
てかsetEscapeModelStrings(false);じゃだめなのは何故?ww
まぁ深くはつっこまずにやってみる

final CheckBoxMultipleChoice<User> userList = new CheckBoxMultipleChoice<User>("sendTo",
				users,
				new ChoiceRenderer<User>("userName","userId")){
   @Override
   protected boolean isEscapeLabelMarkup(){
      return false;
   }
};

これでどうだ!!携帯でアクセスしてみる。。。。。できた。。。

こんなところにも数値参照の罠があった。携帯サイトを作るときは注意しましょう。
っていうか携帯のブラウザが数値参照をちゃんと変換してくれればいいんだ。。。
あとDocomoの独自仕様はへこむ。。。DocomoIEに思えてきた今日この頃。。

以上

Wicketで複数選択のチェックボックス

前回の続き

複数のチェックボックスをひとまとめで取得したいっていう話。

メッセージ送信機能で、送信先のユーザーをチェックボックスで指定させたい。モデルはこんな感じ。

public class Message{
   private String title;
   private String text;
   private List<User> sendTo;

   ....
}

このモデルのsendToにチェックされたユーザーだけを格納するListがセットされてほしい。

で、ページクラスで以下のようにしてみる。

//ページクラスにモデルを設定
this.setDefaultModel(new CompoundPropertyModel<Message>(new Message()));

...


//送信対象のユーザーを取得する		
List<User> users = this.userService.findAll();

//複数選択チェックボックスを作る
final CheckBoxMultipleChoice<User> userList = new CheckBoxMultipleChoice<User>("sendTo",
				users,
				new ChoiceRenderer<User>("userName","userId"));

ChoiceRenderクラスはDropDownのときにもでてきたけど、要はUserクラスのどのプロパティをlabel、valueに設定するか決めるためのクラス。
この場合は、userNameがlabel、userIdがvalueになるので以下のように出力される。

<input type="check" value="user_id_001" id="user_id_001"/><label for="user_id_001">taka</label>

結局value値でどれがチェックされたかってのをCheckBoxMultipleChoiceクラスの内部で振り分けてるので、valueに出力される値はユニークにならなければいけない。。。なのでIDなどのプロパティを指定する。

submit時に以下のようにすると

form.add(new Button("submit"){
   @Override
   public void onSubmit() {

      Message message = (Message)this.getPage().getDefaultModelObject();
      List<User> sendTo = message.getSendTo();

      }
   });

選択されたユーザーのみが格納されたListが取得できる。

CheckBoxMultipleChoice#setRequired(true)ってすると選択されてない場合エラーになるのかなぁ?帰ったら試してみよう。

その他にもCheckGroupクラスとかもあったけどこっち使った方が楽だったりして。。。試してないけど。。。

以上。

Wicketで戻るボタンの実装(2)

前回の続き。

前回id:t_yanoさん(Wicket本頑張ってください)からコメントをいただき、Wicketで戻るボタンを実装する際の簡単なやり方をご教授いただいたのでやってみる。

LoginPage.java

public class LoginPage extends WebPage {
   public LoginPage() {
      super();
      this.setDefaultModel(new CompoundPropertyModel<UserAccount>(new UserAccount()));
      this.add(this.createForm());
      this.add(new FeedbackPanel("message"));
   }

   private Form<Void> createForm(){
      //省略
      form.add(new Button("submit"){
         @Override
         public void onSubmit(){
            //ログイン処理
            

            //いきなりログインページにアクセスされた場合はHomePage.classに遷移する
            if(!this.continueToOriginalDestination()){
               this.setResponsePage(HomePage.class);
            }
         }
      });
   }
}

こうするとログイン後にはじめにアクセスしようとしたページに遷移された。
へぇ〜〜こんだけでよかったんだ〜・・。Wicketですからコレぐらいありますよね〜。さすがWicket!!

ついでにログインが必要なページにアクセスがあってログインしてなかったときにログインページに遷移させるためのクラスも書いておこう。

public class LoginAuthorizationStrategy implements IAuthorizationStrategy{
   @Override
   public boolean isActionAuthorized(Component component, Action action) {
      return true;
   }

   @Override
   public <T extends Component> boolean isInstantiationAuthorized(Class<T> comp) {
      if(comp == MyPage.class){
         if(((MySession)Session.get()).getUser() == null){
            throw new RestartResponseAtInterceptPageException(LoginPage.class);
         }
      }
      return true;
   }	
}

これはorg.apache.wicket.authorization.IAuthorizationStrategyインターフェースを実装したクラス。コンポーネントが表示されるとき?(そこまで調べてない)に必ず通るメソッドで、ここでチェックをしてログインが必要なMyPageにアクセスされた場合、問答無用でLoginPageに遷移されるっていう処理。
これはPageだけじゃなくてコンポーネントごとに処理されるので、例えば特定のラベルを表示させないとかいう制御もできる。
これを適用させるにはApplicationクラスのinitメソッドに

this.getSecuritySettings().setAuthorizationStrategy(new LoginAuthorizationStrategy());

と記述すればOK。

id:t_yanoさんありがとうございました。

ていうかこのブログは俺のためのFuckin'な殴り書きだったけどいろんな人が見てるんだなぁ・・。
いいかげんなこと書けないなこりゃ・・・。

以上。

Wicketで戻るボタンを実装する

前回の続き。。。

戻るボタンもそうなんだけど、例えばログインが必要なページにアクセスしたときに、ログインしていなかった場合、ログインページに遷移する。で、ログインした後に、最初にアクセスしようとしたページに遷移する。

ということがしたい。というわけで、以下のようにしてみる。

LoginPage.java

public class LoginPage extends WebPage {
   private Class<? extends Page> clazz;
   public LoginPage() {
       this(HomePage.class);
   }
   public LoginPage(Class<? extends Page> clazz) {
      super();
      this.clazz = clazz;
      this.setDefaultModel(new CompoundPropertyModel<UserAccount>(new UserAccount()));
      this.add(this.createForm());
      this.add(new FeedbackPanel("message"));
   }

   private Form<Void> createForm(){
      //省略
      form.add(new Button("submit"){
         @Override
         public void onSubmit(){
            //ログイン処理

            this.setResponsePage(LoginPage.this.clazz);
         }
      });
   }
}

これで呼び出し元に遷移することができると思う。
でもパラメータとか考えたときにClassオブジェクトじゃなくて、WebPageオブジェクトを引数にした方がいいのかも。。。とか思った。あ、でもPageParametersを第二引数で受け取るコンストラクタつくればいいのか。。。

Wicket的にこのやり方で正しいのかどうかはまったく不明だが、俺がやりたいことは一応これでできる。しかもSessionやhiddenで前のページのURLを保持したりとかいうめんどうなことをしなくていいのですごく楽だ。

でも一応、Wicketの作法というかそういうのがあったり、「このやりかたがいいよ~」っていうのを知ってる方がいらっしゃったら是非教えてください。

以上。


2008/12/1追記
id:t_yanoさんからいいやり方を教わったので続きを書きました。

Wicketで外部サイトの画像を動的に表示する

前回の続き。

外部サイトっていうかアップロードしてもらった画像を表示するときの話。
アップロードしてもらった画像はWicketを経由しないでアクセスできるようにもしたかった。
WicketのImageクラスを使ってやるとWicketプロジェクト内の画像しか表示できない仕様になってるらしい。

そこでここを参考に外部サイトのURLで画像が表示できる自作Imageクラスを作った。

StaticImage.java

public class StaticImage extends WebComponent{
	private static final long serialVersionUID = 1L;
	
	public StaticImage(String id,final String url) {
		this(id,new AbstractReadOnlyModel<String>(){
			@Override
			public String getObject() {
				return url;
			}
		});
	}
	
	public StaticImage(String id, IModel<?> model) {
        super(id, model);
    }
	
	@Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);
        checkComponentTag(tag, "img");
        tag.put("src", this.getDefaultModelObjectAsString());
    }
}

これで、外部サイトの画像を表示したいときに

this.add(new StaticImage("image","http://master.of.taka/image/image.jpg");
<img src="" wicket:id="imgae"/>

上のようにすると

<img src="http://master.of.taka/image/image.jpg" />

という具合になる。

ちょっと改造すればalt属性も操作できるようにするのも簡単にできそうだ。

以上。

Wicketで携帯Webアプリを作る際の注意点

前回の続き。

画面が少しずつできてきたので携帯からアクセスして見た。すると何故かドロップダウンのラベルが表示されてない。数字のところはでてるのに日本語のところがでてない。PCからだと表示されているが、携帯からだと表示されない。。。「Docomoの嫌がらせか?」と思い、早速調べてみると

http://www.eisbahn.jp/yoichiro/2007/01/wicket_4.html

こんなようなことになってるらしい。。。
出力されたソースを見てみるとたしかに日本語の部分が数値参照になっている。携帯では数値参照の部分を解釈してくれないらしく、何も表示されてなかったんだね。。。

どうやらWicketを修正してビルドしなおすと解決するらしい。めんどいなぁと思いつつソースを追っているとこんな部分を発見。

AbstractChoise.java(416行目あたり)

if (getEscapeModelStrings())
{
   escaped = escapeOptionHtml(display);
}
buffer.append(escaped);
...

ん?なにやら分岐してるじゃないか?getEscapeModelStringsを追い求めてソースを追うこと5クラス目。あったあったComponent#setEscapeModelStrings((final boolean escapeMarkup)メソッドが!!
というわけで試してみる。

DropDownChoice<SexType> sex = new DropDownChoice<SexType>("sexType",
                                    Arrays.asList(SexType.MAN,SexType.WOMAN),
                                      new ChoiceRenderer<SexType>("label","code"));
sex.setEscapeModelStrings(false);

ちなみにSexTypeはenumです。こんなふうにもできるWicketすばらしい。
で、実行結果。。
まずはPCで出力ソースを見てみる。うん。数値参照になってない。そして携帯でアクセス。うん。ちゃんと表示されてる。
こことはやりかた違ったけど俺が使ってるのは1.4rc1(知らない間にでてたのでm3からrc1にした)なので改良されたっぽい。。。

このブログWicketの人に報告するよ〜って書いてあったので報告して改良されたのかな?すばらしいことです。大変参考になりました。

ともかく携帯で数値参照問題も解決&さらにWicket詳しくなれた。

以上。