reactive-web: Lift をシンプルに

ライブデモ
Type in this field: [このライブデモは原文サイトでご覧ください]
You say: "" at 0
Scala ソースファイル
package reactive.web.demo.snippet

import reactive._
import web._
import html._

import scala.xml._

import net.liftweb.util.{ Helpers, BindPlus }
import Helpers._
import BindPlus._
import net.liftweb.http._

// Observing を継承し、スニペットがガベージコレクトがされたら (されたあとに)
// リスナーがガベージコレクトされるようにする.
class SimpleDemo extends Observing {
  //////////////////////////////////////////////////////////////////
  // クライアントイベントへの反応のデモ
  //////////////////////////////////////////////////////////////////
  // reactive な text input を作成.デフォルトでは、ブラウザが変更イベントを
  // 発火したときに値が更新される.
  val field = TextInput()

  // value プロパティを keyUp イベントにリンクし、
  // keyUp イベントのたびに更新されるようにする.
  field.value updateOn field.keyUp

  // 幅を設定.
  field.size () = Some(18)

  // field の value をバインドする Signal を作成.
  // 値は自動的に最新の状態に保持される.
  val fieldValue = field.value map { v =>
    { _: NodeSeq => (Text(v): NodeSeq) }
  }

  // Cell のバインド先の要素に反応的に fieldValue をレンダリングする
  // NodeSeq=>NodeSeq を作成.
  val cell = Cell(fieldValue)

  //////////////////////////////////////////////////////////////////
  // サーバーイベントへの反応のデモ
  //////////////////////////////////////////////////////////////////

  // 10 分までタイマーチックを発火する EventStream を作成.
  val clockES = new Timer(0, 2000, { t => this; t > (10 minutes) })

  // 最初のチックを受信するまでは 値が 0L の EventStream から
  // シグナルを作成.
  val clockSig = clockES.hold(0L)

  // 時刻を scala.xml.Text に表示する reactive Span Cell を作成.
  val clockSpan = Span(clockSig.map(t => Text((t / 1000).toString)))

  //////////////////////////////////////////////////////////////////
  // デルタ更新のデモ
  //////////////////////////////////////////////////////////////////

  // 空の BufferSignal[Int] を作成.
  val items = BufferSignal[Int]()

  // アイテムに含まれる各要素を id="number" の html 要素に
  // バインドする Repeater を作成.
  // BufferSignal に対して数字が挿入、削除されると、
  // 対応する html 要素が挿入、削除される.
  // このファクトリは NodeSeq=>NodeSeq を返し、
  // この NodeSeq=>NodeSeq は Lift のバインディングで使用できる.
  val repeater: NodeSeq => NodeSeq = Repeater {
    items.now.map{ i =>
      ("#number" #> i): (NodeSeq => NodeSeq)
    }.signal
  }

  // 1 から無限大までカウント.
  var numbers = Stream.from(1)

  // クロックチックごとに、挿入または削除を行う.
  for (tick <- clockES) {
    // アイテムが空なら常にアペンドする.
    // それ以外の場合には、math.random の値によって、
    // アペンドまたは削除を行う.
    if (items.now.isEmpty || math.random > .4) {
      // Stream の最初の数字を取得し、アイテムにアペンドする.
      items.value += numbers.head
      // Stream の残りの部分をポイントする.
      numbers = numbers.tail
    } else {
      // アイテムからランダムに要素を削除する.
      items.value.remove(math.random * items.now.length toInt)
    }
  }

  /**
   * スニペット関数
   */
  def render =
    "#field" #> field &
      "#span" #> cell &
      "#clock" #> clockSpan &
      "#div" #> repeater
}

/**
 * 同じものをいくらか宣言的に記述
 */
class SimpleDemo2 extends SimpleDemo {
  override def render =
    "#field" #> field &
      "#span" #> Cell {
        field.value map { v => "*" #> Text(v) }
      } &
      "#clock" #> Span {
        clockSig map { t => Text(t / 1000 toString) }
      } &
      "#div" #> Repeater {
        items.now map { i => ("#number" #> i): (NodeSeq => NodeSeq) } signal
      }
}
テンプレート Xml
<div>Type in this field: <input id="field"></input><br></br>
	You say: &quot;<code id="span" style="white-space: pre"></code>&quot;
    at <span id="clock"></span> <br></br>
	<div id="div"><span> [[ <span id="number"></span> ]] </span></div></div>