2010年2月22日月曜日

[Lift] "?"を含むURLがStatefulSnippet.linkメソッドでStatefulにならない

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
StatefulSnippetで、同じインスタンスのStatefulSnippetを呼び出すようなリンク(<a>タグ)を出力する際には、StatefulSnippet.linkメソッドを使用します。が、StatefulSnippet.linkを使ってもリンク後に別のインスタンスのSnippetが作られてしまう場合があり、困ったのでStatefulになる仕組みを追ってみました。

仕組み

  • StatefulSnippet.linkでリンク用URLを出力すると、ランダムなキーでコールバック関数がセッションに保持され、http://localhost/search?F869982852207GSN=_ の様にキーがクエリ文字列として付加されます。
  • このリンクをクリックすると、LiftSession.runParamsでこのキーで登録されているコールバック関数が実行されます。
  • コールバック関数内で、registerThisSnippet()が呼ばれ、ステートが維持される仕組みになっています。

    ソースを追ってみると、StatefulSnippet.linkはコールバック時に第2引数で指定した関数の前に、registerThisSnippet()を呼ぶようなブロックに差し替えて、SHtml.linkと同じ処理をしているだけです。
    // StatefulSnippet.scala
    def link(to: String, func: () => Any, body: NodeSeq): Elem = SHtml.link(to, () => {registerThisSnippet(); func()}, body)
    

    SHtml.linkは何をやっているかというと、fmapFuncでコールバック関数を登録し、そのキーをクエリ文字列としてURLに付加しています。
    // SHtml.scala
    def link(to: String, func: () => Any, body: NodeSeq,
             attrs: (String, String)*): Elem = {
      fmapFunc((a: List[String]) => {func(); true})(key =>
              attrs.foldLeft(<a href={to + (if (to.indexOf("?") >= 0) "&" else "?") + key + "=_"}>{body}</a>)(_ % _))
    }
    

    ここで、既にクエリ文字列がある(="?"が含まれる)場合には"&"でつなげてることに気づき、問題解決。

    問題解決

    問題となったは、AU携帯で位置情報を付加してリクエストを送るための、以下の様なaタグを出力しようとした部分。
    <a href="device:location?url=http://www.mb4sq.jp/search"/>
    

    def search(in: NodeSeq): NodeSeq =
      bind("f", in, "link" -> link("device:location?url=http://localhost/search", ()=>"", Text("search")))
    
    こんなコードを書いてみたところ、本来欲しいURLは
    device:location?url=http://localhost/search?F869982852207GSN=_ の様なものなんですが、元のURLに"?"が入ってしまっているので、"?"の代わりに"&"が付加されてしまい、
    device:location?url=http://localhost/search&F869982852207GSN=_ になってしまってるわけですね…。これではクエリ文字列として取得できず、ステートを維持するためのregisterThiSnippet()のコールバックが呼ばれずに、ステートが切れてしまっていたと。

    結局、StatefulSnippet.linkをオーバーライドして、GPSのリンクの場合はハードコードで"?"を付けるようにして回避しました。
    override def link(to: String, func: () => Any, body: NodeSeq): Elem = {
      def insert(s: String, i: String, p: Int) = List(s take p, i, s drop p).mkString
      if (to.startsWith("device:")) 
        S.fmapFunc((a: List[String]) => {registerThisSnippet(); func()})(key => {
        val u = if (to.indexOf("&ver=1") > 0) insert(to, ("?" + key + "=_"), to.indexOf("&ver=1"))
            else to + ("?" + key + "=_")
          <a href={u}>{body}</a>
        })
       else super.link(to, func, body)
    }
    
  • 0 件のコメント:

    コメントを投稿