yewで複数のInputを扱う

yewでinput要素を扱う方法は公式ドキュメントの[Eventsについてのページ](https://yew.rs/ja/docs/concepts/html/events#typed-event-target)に詳しく書かれています．イベント型から取得できる`EventTarget`を`web_sys::HtmlInputElement`に変換して`value`や`clicked`などを取得する方法と，`NodeRef`をInput要素に結び付けて`NodeRef`を`web_sys::HtmlInputElement`に変換する方法が書かれています．今回はその二つの方法で複数のinput要素(`<input type="text"/>`と`<input type="checkbox">`)から得られた値の親コンポーネントへの送信(送信は更新ボタンで行う)を実装してみます．コード全体は[こちら](https://github.com/deepgreenAN/yew-input-tutorial)にあります．

## 共通部分
checkboxのために列挙体を定義します．列挙体と文字列の変換や列挙体のイテレーターへの変換，バリアントのカウントなどができる[strum](https://github.com/Peternator7/strum) を利用します．Mozillaのリファレンスの例を利用しています．
```rust
use strum::EnumCount;
use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter as EnumIterMacro, EnumString};

#[derive(Display, EnumCountMacro, EnumString, EnumIterMacro, Clone, Copy)]
pub enum CheckBoxName {
    Scales,
    Horns,
}
```

コンポーネントへ渡すpropsは以下のようにします．`String`の代わりに`yew::prelude::AttrValue`を使っています．
```rust
use yew::prelude::*;

/// 親子コンポーネント間で渡し合うデータ
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DoubleInputValue {
    pub text: AttrValue,
    pub checkbox: Vec<bool>,
}

impl Default for DoubleInputValue {
    fn default() -> Self {
        Self {
            text: AttrValue::default(),
            checkbox: {
                let mut bool_vec = vec![false; CheckBoxName::COUNT];
                bool_vec[CheckBoxName::Scales as usize] = true;
                bool_vec
            },
        }
    }
}

/// props
#[derive(Properties, PartialEq, Clone)]
pub struct DouubleInputProps {
    pub double_input_value: DoubleInputValue,
    pub onsubmit: Callback<DoubleInputValue>,
}
```

## JsCastを利用する方法
公式ドキュメントにあるように`EventTarget`を`web_sys::HtmlInputElement`などに変換する方法です．素直に要素の`value`などに対応する状態を利用して，`onchange`で変更を追跡します．更新ボタンのクリックで`MultiInputValue`を親コンポーネントへ送信します．

```rust
use strum::IntoEnumIterator;
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::prelude::*;

#[function_component(DoubleInputV1)]
pub fn double_input_v1(double_input_props: &DouubleInputProps) -> Html {
    let double_input_value = &double_input_props.double_input_value;

    let checkbox_handle = use_mut_ref(|| double_input_value.checkbox.clone());
    let text_handle = use_mut_ref(|| double_input_value.text.clone());

    let checkbox_onchange = {
        let checkbox_handle = checkbox_handle.clone();
        Callback::from(move |e: Event| {
            let input_element = e
                .target()
                .expect("Event should have a target when dispatched")
                .dyn_into::<HtmlInputElement>()
                .expect("This EventTarget should cast into HtmlInputElement");

            let changed_checkbox_name: CheckBoxName = input_element
                .value()
                .parse()
                .expect("This Value should parse into CheckBoxName");

            checkbox_handle.borrow_mut()[changed_checkbox_name as usize] = input_element.checked();
        })
    };

    let text_onchange = {
        let text_handle = text_handle.clone();
        Callback::from(move |e: Event| {
            let input_element = e
                .target()
                .expect("Event should have a target when dispatched")
                .dyn_into::<HtmlInputElement>()
                .expect("This EventTarget should cast into HtmlInputElement");

            *text_handle.borrow_mut() = AttrValue::from(input_element.value());
        })
    };

    let onsubmit = {
        let parent_onsubmit = double_input_props.onsubmit.clone();
        let checkbox_handle = checkbox_handle;
        let text_handle = text_handle;

        Callback::from(move |_: MouseEvent| {
            let double_input_value = DoubleInputValue {
                checkbox: checkbox_handle.replace_with(|value| value.clone()),
                text: text_handle.replace_with(|value| value.clone()),
            };
            parent_onsubmit.emit(double_input_value);
        })
    };

    html! {
        <fieldset class="double-form">
            <legend>{"Double nput V1"}</legend>
            <div>
                {
                    for CheckBoxName::iter().map(|check_box_name|{
                        html_nested!{
                            <label>
                                {check_box_name.to_string()}
                                <input
                                    type="checkbox"
                                    onchange={checkbox_onchange.clone()}
                                    checked={double_input_value.checkbox[check_box_name as usize]}
                                    value={check_box_name.to_string()}
                                />
                            </label>
                        }
                    })
                }
            </div>
            <div>
                <label>
                    {"Name:"}
                    <input
                        type="text"
                        onchange={text_onchange}
                        value={double_input_value.text.clone()}
                    />
                </label>
            </div>
            <div>
                <button onclick={onsubmit}>{"更新"}</button>
            </div>
        </fieldset>
    }
}

```

長いため一つずつ説明します．状態の初期化の部分は以下です．今回はinput要素の値に応じて状態を更新するだけであるため`use_mut_ref`を用いています．`use_mut_ref`が返すのは`Rc<RefCell<T>>`です．つまりこのコンポーネントの再レンダリングのタイミングは親コンポーネントが渡すpropsが変更された時のみです．
バリデーションを走らせてその結果を表示したりするなど状態の更新ごとに再レンダリングが必要な場合は`use_state`や`use_reducer`を使います．
```rust
let checkbox_handle = use_mut_ref(|| double_input_value.checkbox.clone());
let text_handle = use_mut_ref(|| double_input_value.text.clone());
```

changeイベントに結び付けるコールバックの定義は以下です．`Result`や`Option`が返りますが，`Err`や`None`となるのは結びつけるhtml要素やイベントを間違えた時であるため`expect`でアンラップしています．checkboxでは`value`の値を先ほど定義した列挙体にパースしていますが，こちらも`html!`内で`value`の与え方を間違えなければエラーとはならないはずです．

```rust
let checkbox_onchange = {
	let checkbox_handle = checkbox_handle.clone();
	Callback::from(move |e: Event| {
		let input_element = e
			.target()
			.expect("Event should have a target when dispatched")
			.dyn_into::<HtmlInputElement>()
			.expect("This EventTarget should cast into HtmlInputElement");

		let changed_checkbox_name: CheckBoxName = input_element
			.value()
			.parse()
			.expect("This Value should parse into CheckBoxName");

		checkbox_handle.borrow_mut()[changed_checkbox_name as usize] = input_element.checked();
	})
};

let text_onchange = {
	let text_handle = text_handle.clone();
	Callback::from(move |e: Event| {
		let input_element = e
			.target()
			.expect("Event should have a target when dispatched")
			.dyn_into::<HtmlInputElement>()
			.expect("This EventTarget should cast into HtmlInputElement");

		*text_handle.borrow_mut() = AttrValue::from(input_element.value());
	})
};
```

更新ボタンに結びつける(親コンポーネントに値を送信する)コールバックは以下です．`Rc<RefCell<T>>`から`clone`で値を取り出しています．
```rust
let onsubmit = {
	let parent_onsubmit = double_input_props.onsubmit.clone();
	let checkbox_handle = checkbox_handle;
	let text_handle = text_handle;

	Callback::from(move |_: MouseEvent| {
		let double_input_value = DoubleInputValue {
			checkbox: checkbox_handle.replace_with(|value| value.clone()),
			text: text_handle.replace_with(|value| value.clone()),
		};
		parent_onsubmit.emit(double_input_value);
	})
};
```

`html!`におけるチェックボックスの部分は以下です．`for`と`html_nested!`を用いて複数のチェックボックスを定義します．strumを用いることで列挙体のイテレーターへの変換やバリアントの`to_string`が利用できるため`value`に間違った値を与える心配はありません．これはselect要素や`<input type="radio"/>`などを扱うときにも便利です．
```rust
{
	for CheckBoxName::iter().map(|check_box_name|{
		html_nested!{
			<label>
				{check_box_name.to_string()}
				<input
					type="checkbox"
					onchange={checkbox_onchange.clone()}
					checked={double_input_value.checkbox[check_box_name as usize]}
					value={check_box_name.to_string()}
				/>
			</label>
		}
	})
}
```

テキストボックスの部分と更新ボタンの部分は公式ドキュメントの例と大差ないため割愛します．
この方法ではchangeイベントごとにコールバックを実行するため，今回の例では冗長に感じます．更新ボタンのclickイベントに紐付けたコールバックの中でinputイベントの`value`などを取得できれば，状態として保持し変更を追跡させる必要もありません．その場合は次に説明する`NodeRef`を利用できます．

## NodeRefを利用する方法
公式ドキュメントにあるように`use_node_ref`で得られた`NodeRef`をhtml要素に結び付け，コールバック内で`web_sys::HtmlInputElement`などに変更する方法です．コールバックは`onsubmit`のみであり，その中で`value`などを取得します．
```rust
use strum::{EnumCount, IntoEnumIterator};
use web_sys::HtmlInputElement;
use yew::prelude::*;

#[function_component(DoubleInputV2)]
pub fn double_input_v2(double_input_props: &DouubleInputProps) -> Html {
    let double_input_value = &double_input_props.double_input_value;

    let checkbox_refs: [NodeRef; CheckBoxName::COUNT] = [use_node_ref(), use_node_ref()];
    let text_ref = use_node_ref();

    let onsubmit = {
        let parent_onsubmit = double_input_props.onsubmit.clone();
        let checkbox_refs = checkbox_refs.clone();
        let text_ref = text_ref.clone();

        Callback::from(move |_| {
            let checkbox = checkbox_refs
                .iter()
                .map(|node_ref| {
                    node_ref
                        .cast::<HtmlInputElement>()
                        .expect("This NodeRef should cast into HtmlInputElement")
                        .checked()
                })
                .collect::<Vec<_>>();

            let text: AttrValue = text_ref
                .cast::<HtmlInputElement>()
                .expect("This NodeRef should cast into HtmlInputElement")
                .value()
                .into();

            let value = DoubleInputValue { checkbox, text };

            parent_onsubmit.emit(value);
        })
    };

    html! {
        <fieldset class="double-form">
            <legend>{"Double nput V2"}</legend>
            <div>
                {
                    for CheckBoxName::iter().map(|check_box_name|{
                        html_nested!{
                            <label>
                                {check_box_name.to_string()}
                                <input
                                    type="checkbox"
                                    ref={checkbox_refs[check_box_name as usize].clone()}
                                    checked={double_input_value.checkbox[check_box_name as usize]}
                                    value={check_box_name.to_string()}
                                />
                            </label>
                        }
                    })
                }
            </div>
            <div>
                <label>
                    {"Name:"}
                    <input
                        type="text"
                        ref={text_ref}
                        value={double_input_value.text.clone()}
                    />
                </label>
            </div>
            <div>
                <button onclick={onsubmit}>{"更新"}</button>
            </div>
        </fieldset>
    }
}

```
長いため一つずつ説明します．状態の初期化の部分は以下です．`use_node_ref`で`NodeRef`を取得します．チェックボックスはその要素の数だけ`NodeRef`が必要になります．`CheckBoxName`列挙体をイテレーションしてhtml要素と結びつけるため，この配列の`NodeRef`の順番は列挙体のバリアントの順番と対応します．
```rust
let checkbox_refs: [NodeRef; CheckBoxName::COUNT] = [use_node_ref(), use_node_ref()];
// let checkbox_refs: [NodeRef; CheckBoxName::COUNT] = Default::default(); //　こちらでも良い
let text_ref = use_node_ref();
```

更新ボタンに結びつける(親コンポーネントに値を送信する)コールバックは以下です．`NodeRef`と結びつけるhtml要素を間違えなければ`NodeRef::cast`が`None`を返すことは無いため，`expect`でアンラップしています．
```rust
let onsubmit = {
	let parent_onsubmit = double_input_props.onsubmit.clone();
	let checkbox_refs = checkbox_refs.clone();
	let text_ref = text_ref.clone();

	Callback::from(move |_| {
		let checkbox = checkbox_refs
			.iter()
			.map(|node_ref| {
				node_ref
					.cast::<HtmlInputElement>()
					.expect("This NodeRef should cast into HtmlInputElement")
					.checked()
			})
			.collect::<Vec<_>>();

		let text: AttrValue = text_ref
			.cast::<HtmlInputElement>()
			.expect("This NodeRef should cast into HtmlInputElement")
			.value()
			.into();

		let value = DoubleInputValue { checkbox, text };

		parent_onsubmit.emit(value);
	})
```
`html!`におけるチェックボックスの部分は以下です．`JsCast`を用いた例と異なる点は`ref`を用いて`NodeRef`と結びつけていることと`onchange`を利用していないことです．
```rust
{
	for CheckBoxName::iter().map(|check_box_name|{
		html_nested!{
			<label>
				{check_box_name.to_string()}
				<input
					type="checkbox"
					ref={checkbox_refs[check_box_name as usize].clone()}
					checked={double_input_value.checkbox[check_box_name as usize]}
					value={check_box_name.to_string()}
				/>
			</label>
		}
	})
}
```

テキストボックスの部分の部分は以下です．`onchange`を用いずに`ref`を用いています．
```rust
<label>
	{"Name:"}
	<input
		type="text"
		ref={text_ref}
		value={double_input_value.text.clone()}
	/>
</label>
```

## 親コンポーネント
親コンポーネントでは以下のように利用できます．`double_input_onsubmit`で子コンポーネントから送られた`DoubleInputvalue`を利用したコールバックを定義しています．
```rust
use yew::prelude::*;

#[function_component(App)]
fn app() -> Html {
    let double_input_value_handle = use_state(DoubleInputValue::default);

    let double_input_onsubmit = {
        let double_input_value_handle = double_input_value_handle.clone();
        Callback::from(move |double_input_value| {
            double_input_value_handle.set(double_input_value);
        })
    };
    let double_input_props = DouubleInputProps {
        double_input_value: (*double_input_value_handle).clone(),
        onsubmit: double_input_onsubmit,
    };

    html! {
        <>
        <DoubleInputV1 ..double_input_props.clone()/>
        <DoubleInputV2 ..double_input_props/>
        <div>{format!("{:?}", *double_input_value_handle)}</div>
        </>
    }
}
```