ReactでTextInputを使う時は、入力内容をonChangeで取得し、それを状態として保持するコンポーネントを使います。
Halogenでも同じようなコンポーネントがあると便利です。
ここでは以下の機能を備えたTextInputコンポーネントを作成します。
- 入力内容を状態で保持している
- 親コンポーネントからはQueryでTextInputの入力内容を取得する
- TextInputのスタイルは外部から設定できるようにする
ではコンポーネントを作成するために必要なものを順次みていきましょう。
TextInputの入力
親コンポーネントから渡されるプロパティです。
type Input = { value :: String
, style :: Maybe CSS }
valueというのがTextInputの初期値です。styleはCSSです。
TextInputの状態
TextInputの状態は親コンポーネントからの入力と同じものを保持します。TextInputに何か入力される度にvalueが更新されます。styleは初期値から更新されることはありません。
type State = { value :: String
, style :: Maybe CSS }
TextInputのアクション
TextInputに何か入力された時のアクションです。onChangeで捕捉します。
data Action = OnChange Event
TextInputのクエリ
親コンポーネントから呼ばれるクエリです。親コンポーネントがTextInputの状態に入っているvalueを取得します。親コンポーネントのボタンが押下されたときなどに呼ばれることを想定しています。
data Query a = GetValue (String -> a)
TextInputの状態の初期値
TextInputの状態の初期値は、TextInputのInputと同じです。
initialState :: Input -> State
initialState input = input
TextInputのビュー
TextInputのビューは単純にHTMLInputElementです。onChange時に先程定義したOnChangeアクションを発行します。
render :: State -> H.ComponentHTML Action () m
render state = HH.input
$ [ style state.style
, HP.type_ HP.InputText
, HP.value state.value
, HE.onChange OnChange ]
<> case state.style of
Just s -> [style s]
_ -> []
TextInputのActionの処理
TextInputのActionの処理は、onChange時に発行されたOnChangeアクションを受け取って、EventのtargetであるHTMLInputElementのvalueで状態を更新します。
handleAction :: Action -> H.HalogenM State Action () output m Unit
handleAction (OnChange e) = do
state <- H.get
value <- H.liftEffect $ getInputText e
H.put state { value = value }
pure unit
getInputText :: Event.Event -> Effect String
getInputText e = e
# Event.target
>>= HTMLInputElement.fromEventTarget
# case _ of
Just a -> HTMLInputElement.value a
_ -> throw "Failed to convert a HTMLInputElement"
TextInputのQueryの処理
親コンポーネントからクエリを受け取った時の処理です。状態として保持しているvalueを親コンポーネントに返します。
handleQuery :: forall a. (Query a) -> H.HalogenM State Action () output m (Maybe a)
handleQuery (GetValue reply) = do
state <- H.get
pure $ Just $ reply state.value
TextInputコンポーネントの定義
上記で定義したものをまとめてtextInputコンポーネントを作成します。
textInput :: forall output m. MonadEffect m => H.Component Query Input output m
textInput = H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction
, handleQuery = handleQuery } }
TextInputのSlot
親コンポーネントで使用することになるTextInputのSlotの定義です。TextInputのはOutputがないのでVoidにしています。slotId**は親コンポーネントで定義することにします。
type Slot slotId = H.Slot Query Void slotId
親コンポーネントで使用する
上記で定義したTextInputコンポーネントを親から使用する例です。
子コンポーネントをビュー内に入れるにはslot/slot_を使用します。ここではTextInputコンポーネントはOutputがないのでslot_を使っています。
ここではTextInputコンポーネントを複数使う場合のためにslotIdの型をIntにしていますが、もっと分かりやすくStringにしてもよいと思います。Ordクラスのインスタンスであれば何でも使用できます。
type Slots = ( textField :: Slot Int )
textInput_ = Proxy::Proxy "textInput"
slot_はrender関数の中で使用します。HTMLの中にコンポーネントを埋め込むためにはslot_で囲んであげる必要があります。
第2引数のslotIdをIntにしたので、ここでは0としています。
第3引数にはTextInputコンポーネントを入れます。
第4引数である子コンポーネントの入力値としてTextInputのInputの値を入れます。ここではHTMLInputElementのvalueの初期値は""にしています。
HH.slot_ textInput_ 0 textInput { value: "", style: Nothing }
以下は親コンポーネントでTextInputの値が必要になったときに、その値を取得する方法です。親コンポーネントのhandleAction等で呼ばれることを想定しています。
value <- H.request textField_ 0 GetValue