投稿の内容
本投稿では、Scala.jsのUIフレームワークであるLaminarを使ってテキスト入力処理について調べてみました。
Laminarのイベント処理はObserverとObservableと使って処理をしますが、複数のイベントを組み合わせたい場合についてご紹介します。
実現した機能としては、テキストフィールドに入力した文字が隣にあるボタンを押すと、その下に表示されるという簡単なものです。
2種類のイベント
これを実現するためには、以下の2種類のイベントを処理する必要があります。
inputイベントclickイベント
inputイベント
inputイベントは、テキストフィールドに文字が入力される度に発火されるイベントです。このイベントをObserverに入力します。
onInput.map(e =>
Command.Input(
e.target.asInstanceOf[HTMLInputElement].value
)
) --> event.writer
ここで、上記のコードに登場するCommandとeventは以下のように定義しました。
enum Command:
case Input(value: String)
case Click
val event = EventBus[Command]()
ちなみに、このイベントを出力先に入れると、文字を入力する度に、出力先が更新されます。
div(child.text <-- event.events)
今回はボタンを押せば出力先が更新されるようにしたいので、これでは目的は達成されません。
clickイベント
clickイベントは、ボタンをクリックした時に発火されるイベントです。今回はこのタイミングで出力先に入力内容を表示します。
なのでclickイベントが発火した時のテキストフィールドの入力内容が必要です。そこで入力内容をどこかに保存しておき、clickイベントが発火時に、保存していた入力内容を出力先に渡すようにしたいと思います。
まずは、clickイベントをObserverに渡します。
onClick.mapTo(Command.Click) --> event.writer
次にfoldLeftを使用して入力内容を保存すうりょうにします。foldLeftの初期値は空文字列のタプルです。タプルの第一要素は、入力内容の現在値を保存しておく用で、第二要素はclick時の入力内容を表しています。イベントがCommand.Inputの場合は、その値をタプルの第一要素に入れます。イベントがCommand.Clickの場合は、最新の入力値(=タプルの第一要素)を第二要素に入れます。こうすることで、タプルの第二要素が常にclick時の入力内容になります。
val signal = event.events
.foldLeft(("", "")) {
case ((a, b), Command.Click) => (a, a)
case ((a, b), Command.Input(text)) => (text, b)
}
.map(_._2)
最後にclick時の入力内容を出力先のdivに入れます。
div(child.text <-- signal)
全コード
import org.scalajs.dom
import com.raquo.laminar.api.L.{*, given}
import org.scalajs.dom.HTMLInputElement
enum Command:
case Input(value: String)
case Click
@main def main =
documentEvents.onDomContentLoaded.foreach { _ =>
val appContainer: dom.Element = dom.document.body
val event = EventBus[Command]()
val signal = event.events
.foldLeft(("", "")) {
case ((a, b), Command.Click) => (a, a)
case ((a, b), Command.Input(text)) => (text, b)
}
.map(_._2)
val appElement: Div = div(
display := "flex",
justifyContent := "center",
fontSize := "1.2rem",
fontWeight := "bold",
div(
div(
padding := "8px",
div("入力"),
input(
typ := "text",
onInput.map(e =>
Command.Input(
e.target.asInstanceOf[HTMLInputElement].value
)
) --> event.writer
),
button("OK", onClick.mapTo(Command.Click) --> event.writer)
),
div(
padding := "8px",
div("出力"),
div(child.text <-- signal)
)
)
)
render(appContainer, appElement)
}(unsafeWindowOwner)