SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Scala+Liftによる実践Webアプリケーション開発

Scala+LiftフレームワークのView/Template

Scala+Liftによる実践Webアプリケーション開発(2)

  • X ポスト
  • このエントリーをはてなブックマークに追加

Snippetを作成する

 次に、Snippetクラスを作成します。Snippetは通常のScalaのクラスとして、snippetパッケージ内に作成する必要がありますので、プロジェクトディレクトリ以下の 「src/main/scala/demo/twitterclone/snippet/」ディレクトリに、「Twit.scala」というファイル名で新しいファイルを作成します。

[リスト5]Twit.scala
// パッケージ宣言
package demo.twitterclone.snippet

// 利用するクラスのインポート
import _root_.scala.collection.mutable.ListBuffer
import _root_.scala.xml.{NodeSeq, Text }
import _root_.net.liftweb.http.RequestVar
import _root_.net.liftweb.http.SHtml._
import _root_.net.liftweb.util.Helpers._
import _root_.net.liftweb.util.{Box, Full}
import _root_.demo.twitterclone.model._

class Twit {
  object message extends RequestVar(Full("") )

  // <lift:Twit.post>タグで呼び出されるSnippet関数
  // 入力フォームを生成する
  def post( xhtml:NodeSeq  ):NodeSeq  =  { *1

    // User.currentUserで現在ログインしているユーザーを取得。
    // パターンマッチを利用して、ログインしていない場合は"Guest"を設定している
    var name = User.currentUser match {
      case Full(user) => user.shortName
      case _ => "Guest"
    }

    // bind関数を利用して、引数のXHTML内で<twit:...>で始まるタグの
    // 内容を置き換える
    bind("twit", xhtml,
      "name" -> name,
      "status" -> textarea( "", m => message(Full(m)),
                   "cols" -> "40", "rows" -> "3"),
      "submit" -> submit( "投稿する",
                          () => { Twit.add( message )  })
    )
  }

  // <lift:Twit.show>タグで呼び出されるSnippet関数
  // 投稿されたメッセージを表示する
  def show( xhtml:NodeSeq  ):NodeSeq = *2
    // <xml:Group>で複数のタグをグルーピング
    <xml:Group>{
      Twit.messages.map( m => bind( "twit", xhtml, "message" -> m ))
    }</xml:Group>

}

// 投稿されたメッセージを保持しておくためのシングルトンオブジェクト
object Twit { *3

  // scala.collection.mutable.LiftBufferオブジェクトに
  // 投稿されたメッセージを保持する
  val messages:ListBuffer[String] = new ListBuffer

  // ListBuffer型のフィールドmessagesに、投稿されたメッセージを
  // 追加する関数
  def add(message:Box[String] ) = message match {
    // パターンマッチでmessageが設定されている場合のみ追加
    case Full(m) => messages += m
    case _=> {}
  }
}

 上記のTwitクラスには、2つの関数が定義されています。*1post関数は、リスト4のテンプレートで<lift:Twit.post>タグから呼び出されるSnippet関数です。このpost関数では、引数として渡されたXHTMLの中で、<twit:name/>のようなtwitという名前空間のタグを置き換えて、入力フォームと投稿ボタンを生成しています。

 *2show関数では、それまでに投稿されたメッセージをすべて表示するようになっています。

 *3は、投稿されたメッセージを保持しておくためのシングルトンオブジェクトを定義しています。

 もう少し、それぞれの関数を詳しく解説します。

post関数

 以下のリスト6は、post関数の抜粋です。どのように出力を生成しているかについて解説します。

[リスト6]post関数
  object message extends RequestVar(Full("") )  *1

  // <lift:Twit.post>タグで呼び出されるSnippet関数
  // 入力フォームを生成する
  def post( xhtml:NodeSeq  ):NodeSeq  =  {

    // User.currentUserで現在ログインしているユーザーを取得。
    // パターンマッチを利用して、ログインしていない場合は"Guest"を設定している
    var name = User.currentUser match {  *2
      case Full(user) => user.shortName
      case _ => "Guest"
    }

    // bind関数を利用して、引数のXHTML内で<twit:...>で始まるタグの
    // 内容を置き換える
    bind("twit", xhtml,  *3
      "name" -> name,  *4
      "status" -> textarea( "", m => message(Full(m)),  *5
                   "cols" -> "40", "rows" -> "3"),
      "submit" -> submit( "投稿する",
                          () => { Twit.add( message )  })  *6
    )
  }
(1)ブラウザから送信された内容を保持するmessageオブジェクトを定義する

 *1で宣言されているのは、RequestVarを継承したmessageオブジェクトです。このmessageオブジェクトが、ブラウザから送信された内容を保持します。

 RequestVarクラスは、Snippet間でもリクエスト情報を共有するためのLiftのクラスです。RequestVarに設定された内容は、他のSnippetからも利用できます。

 なぜこのような形でリクエストされたパラメータを保持しているのかと言うと、*5および*6で設定されている2つの関数オブジェクトの間で、送信されたパラメータを共有するためです。

(2)bind関数による出力の生成

 *2は、ログインしているユーザーの名前を設定しています。生成されたプロジェクトには、あらかじめユーザーの登録や認証機能が用意されているので、その機能を利用しています。

 *3は、net.liftweb.util.Helpers#bind関数を利用して、post関数の引数に渡されたXHTML内で、「twit」という名前空間のタグを置き換えています。

 bind関数は、第1引数に置換対象の名前空間(例ではtwit)を指定します。第2引数は置換対象のNodeSeqオブジェクトです。第3引数以降は、タグ名と置き換え内容のペアを指定します。*4のように、"name" -> usernameと指定すると、テンプレート内で<twit:name>となっている部分がusername変数の内容で置き換わります(例では、ログインしているユーザー名またはGuest)。

(3)SHtmlオブジェクトによるフォーム要素の生成

 *5では、「status」タグの内容として、textareaを生成しています。LiftのSnippetでは、net.liftweb.http.SHtmlオブジェクトが持つ各種のヘルパー関数を利用して、form要素を生成することができます。SHtml.textarea関数の第1引数は初期値として表示される値、第2引数は、(String) => Any型の関数オブジェクトで、ここで指定された関数オブジェクトはformが送信された際に実行されます。例では、*1で宣言したmessageオブジェクトに、textareaに入力された文字列を設定します。第3引数以降は、textareaタグに設定する属性を、属性名 -> 値 のペアで指定しています。

 *6は、SHtmlオブジェクトのsubmit関数を利用して、submitボタンを生成しています。submit関数の第1引数はボタンの表示名、第2引数は() => Any型の関数オブジェクトです。ここで渡した関数オブジェクトが、フォームが送信された際に実行されます。この関数オブジェクトの処理内容は、投稿されたメッセージの内容が*1で定義したmessageオブジェクトに設定されているので、内容をシングルトンオブジェクトであるTwitオブジェクトのadd関数に渡して、メッセージを保存しています。

SHtmlオブジェクト

 以下の表は、SHtmlオブジェクトで利用できるヘルパー関数を抜粋したものです。SHtmlオブジェクトのほとんどの関数は、引数にクリックまたはsubmitされた際に実行する関数オブジェクトを設定できます。また、引数に任意の属性と値のペアを渡すことで、生成されるタグにcssクラス名などの属性を追加することが可能です。

 SHtmlオブジェクトの詳細は、LiftのAPIドキュメントを参照ください。

SHtmlオブジェクトで利用できるヘルパー関数(抜粋)
関数名 説明
a クリックされた時に、引数に設定したJavaScriptを実行するリンクを生成
ajaxButton クリックされた時に、引数に設定したJavaScriptを実行するボタンを生成
checkbox チェックボックスを生成
fileUpload ファイルアップロード用の<input type="file">タグを生成する。引数に、送信されたファイルを処理する関数オブジェクトを設定する
hidden Hiddenタグを生成
link 引数のXHMTLを含むaタグを生成
password パスワード入力フィールドを生成
radio ラジオボタンを生成
select 引数のリストからセレクトフィールドを生成
span クリックされた時に、引数に設定したJavaScriptを実行するspanタグを生成
submit submitボタンを生成。引数に、submitされた時に実行する処理を関数オブジェクトで設定する
text テキストフィールドを生成
textarea テキストエリアを生成

show関数

 リスト7は、show関数の内容です。このshow関数は、それまで投稿されたメッセージを保持しているLiftBuffer型のTwit.messagesmap関数を呼び出して、各メッセージをbind関数で表示させています。この例のように、Snippet関数の引数のxhtmlを繰り返しにより何度もbind関数に適用することで、表の生成など要素の繰り返しを簡単に処理できます。

[リスト7]show関数
def show( xhtml:NodeSeq  ):NodeSeq =
  // <xml:Group>で複数のタグをグルーピング
  <xml:Group>{
    Twit.messages.map( m => bind( "twit", xhtml, "message" -> m ))  *1
  }</xml:Group>

 show関数の戻り値が、<xml:Group>タグで囲まれています。このようにする理由としては、*1で保存されいてるメッセージのそれぞれにbind関数を適用した結果が、<li>foo</li><li>bar</li>のような、Rootとなるノードを持たないXMLになってしまうからです。<xml:Group>タグをRootノードとすることで、XMLとして整形しています。

 Snippet関数をよく見ると、処理の中にHTMLタグがまったく記述されていないことが分かると思います。bind関数を利用すると、テンプレートにあるHTMLの構造をそのまま生かして、必要な箇所のみをSnippet関数で処理させることができるのです。LiftのテンプレートとSnippetは、このようなbind関数を利用した仕組みで、デザインと処理の分離を実現しています。

次のページ
Function Mappingについて

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Scala+Liftによる実践Webアプリケーション開発連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 尾崎 智仁(オザキ トモヒト)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4512 2009/11/16 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング