Lift + Maven で始める Webサイト開発

先日のScalaハッカソンでは、あまりコーディングはできませんでしたが、LiftWebフレームワークを使っての開発を @NKJason さんにいろいろ教えて頂いたので、忘れないうちに書いておきます。LiftとATNDのAPIを使ってATNDのイベント検索機能を実装しました。自分でも実装してみながら、流れを書きます。ビルドツールは何でもいいのだと思いますが、今回はMavenです。

環境は、

  • MacOSX10.6
  • Maven2.2.1
  • Scala2.8.0

プロジェクトの準備
MavenリポジトリからデフォルトのLiftプロジェクトをダウンロードして配置します。

#!/bin/bash

mvn archetype:generate -U \
-DarchetypeGroupId=net.liftweb \
-DarchetypeArtifactId=lift-archetype-basic_2.8.0.RC7 \
-DarchetypeVersion=2.1-SNAPSHOT \
-DarchetypeRepository=http://scala-tools.org/repo-snapshots \
-DremoteRepositories=http://scala-tools.org/repo-snapshots \
-DgroupId=$2 -DartifactId=$1

何度も使いそうなので、シェルにしました。
この辺の設定内容は、もう少し詳しく確認してから追記します。
ひとまず、-DgroupIdはパッケージ名を、-DartifactIdはプロジェクト名を
指定するところなので、オプションで指定するようにしました。

lift_setup Hello net.knserve.lift.hello

上記のコマンドを実行するとカレントディレクトリにデフォルトのプロジェクトが作られます。
ソースを変更する前にとりあえず動かしてみましょう。
作成されたプロジェクトのルートディレクトリ(pom.xmlがある場所)に移動して、

mvn package
mvn jetty:run

と順番に打つとビルドされ、jettyが起動した状態になります。
localhost:8080にアクセスすれば、何も変更されていないLiftによる画面が見えるはずです。

開発作業
Liftでは主にテンプレートとスニペットというクラスを使った開発を行うようです。

  1. スニペットのクラスの中でビューとなるhtmlタグをbindするように書く。
  2. bindしたhtmlタグをテンプレートから呼び出す。

というような開発手順になります。その辺りの情報は公式wikiで言うとこの辺りでしょうか。

webapp配下です

─ webapp                  
├── WEB-INF             
│      └── web.xml        
├── images              
│      └── ajax-loader.gif
├── index.html          
├── static              
│      └── index.html     
└── templates-hidden    
    ├── default.html    
    └── wizard-all.html 

Scalaのソースコードが置かれているパッケージ配下です。

─ net                                      
└── knserve                              
    └── lift                             
        └── hello                        
            ├── comet                    
            ├── lib                      
            │      └── DepencyFactory.scala
            ├── model                    
            │      └── User.scala          
            ├── snippet                  
            │      └── HelloWorld.scala    
            └── view                     

HelloWrold.scalaをSample.scalaという名前にコピーして編集していきます。
編集前のソースです。

package net.knserve.lift.hello {
package snippet {

import _root_.scala.xml.{NodeSeq, Text}
import _root_.net.liftweb.util._
import _root_.net.liftweb.common._
import _root_.java.util.Date
import net.knserve.lift.hello.lib._
import Helpers._

class HelloWorld {
  lazy val date: Box[Date] = DependencyFactory.inject[Date] // inject the date

  def howdy(in: NodeSeq): NodeSeq =
  Helpers.bind("b", in, "time" -> date.map(d => Text(d.toString)))

  /*
   lazy val date: Date = DependencyFactory.time.vend // create the date via factory

   def howdy(in: NodeSeq): NodeSeq = Helpers.bind("b", in, "time" -> date.toString)
   */
}

}
}


どういうページにするか考え中...。
結局あんま思いつかなかったのでソースを解説します。
基本的に@NKJason さんのコードのままですが、
メソッド名などは変えて、コメントを加えました。

package com.snssuite.atnd {
package snippet {

import _root_.scala.xml.{NodeSeq, Text}
import _root_.net.liftweb.util._
import _root_.net.liftweb.common._
import _root_.java.util.Date
import com.snssuite.atnd.lib._
import Helpers._

//追加でインポート
import scala.xml._
import net.liftweb.util.HttpHelpers._ 
import net.liftweb.http._

class AtndSearch {

  def searchBox( in: NodeSeq ): NodeSeq = {
    //searchパラメータを取得して格納
    //検索窓のデフォルト値として利用
    var keyword = S.param("search") openOr ""
    
    //検索パラメータを付けたURLへリダイレクト
    def search() = {
      S.redirectTo("/static/index?search=" + urlEncode( keyword ))
    }
    
    //コンポーネントをバインド
    Helpers.bind("searchbox", in
             , "input" -> SHtml.text( keyword, keyword = _ )
             , "submit"   -> SHtml.submit("検索", search))
  }

  def resultList( in: NodeSeq ): NodeSeq = {
    //searchパラメータを取得
    val keyword = S.param("search") openOr ""

    //searchパラメータからURLを作成して、xmlで結果を得る
    val xml = XML.load("http://api.atnd.org/events/?keyword_or=" + urlEncode( keyword ) + "&format=xml") 
    //events配下のeventの一覧をリストで取得
    val xmlList = ( xml \\ "events" \\ "event" ) 

    xmlList.flatMap( xm => Helpers.bind("resultlist", in
                , "title" -> (xm \\ "title").map(_.text).mkString
                , "event_id" -> SHtml.link("http://atnd.org/events/" + (xm \\ "event_id").map(_.text).mkString 
                                 , () =>"" , scala.xml.Text((xm \\ "event_id").map(_.text).mkString))
                ))

  }

}

}
}
<lift:surround with="default" at="content">

  <!-- searchBoxメソッドのバインド -->
  <lift:AtndSearch.searchBox form="POST">
    <searchbox:input />&nbsp;
    <searchbox:submit />
  </lift:AtndSearch.searchBox>

  <table border="1">
    <tr>
      <td>title</td>
      <td>eventLink</td>
    </tr>
    <!-- resultListメソッドのバインド -->
    <lift:AtndSearch.resultList>
    <tr>
      <td><resultlist:title /></td>
      <td><resultlist:event_id /></td>
    </tr>
    </lift:AtndSearch.resultList>
  </table> 

</lift:surround>