投稿の内容
本投稿では、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)