みなさんは下のように編集できるテキストをみたことはないでしょうか? 普段は通常のテキストなのですが、クリックすることで編集できるようになり、編集を終えれば今度は編集した後のテキストが表示されます。
よくブログプラットフォームなど会員登録のあるウェブサイトでこれまでに登録していたものを再度編集するようなインターフェイスとして利用されています。 この記事では、ReactJSを使ってこういった編集可能なテキスト表示を実装する方法を解説していきます。
まずは簡単に表示から編集にクリックで切り替え、他の場所をクリックされた時に編集を終えて表示に戻す部分を実装します。
この機能に必要な引数として元々の表示する文字列を受け取ります。
編集中か表示中かを表現するisEditing
を状態としてもち、編集されればその内容を都度、内部状態に保存していきます。
autoFocus
はtrue
であれば、要素が表示される際に自動的にこの要素を選択(フォーカス)します。そのため、編集に切り替えた際に改めてこの要素をクリックすることなく編集をすることができます。this.state.value != "" ? this.state.value : "クリックしてテキストを編集を開始"
となっているのは、空文字列が指定されている時にクリックすることができずに編集できなくなることを防ぐためです。import { RewritableText as RewritableText1 } from "react-notebook/dist/rewritable-text/tutorial/01-switch-edit-view"
<RewritableText1
defaultValue="まずクリックによって編集できるようになりました。"
/>
今のままでは編集できるテキストがそこにあるだけですが、実際には編集後のテキストを利用して何かしら別の仕事をさせる必要があります。 例えば、登録しているメールアドレスを再度編集してもらうインターフェイスで利用する場合、通常は編集できることはもちろん、編集したあとでデータベースに送信しなければ意味がありません。また、ReactJSの枠組みで使っていると編集したあとのテキストで他のコンポーネントの状態を変更することも多いと思います。
コールバックを実行する段階として、内容が変更された時のonChange
と編集が終了した時のonFinalized
の2つを設定します。
そこでこうした仕事をさせるためのコールバック関数を与えられるように実装します。
受け取ったコールバック関数を適切に設定すると以下のような実装になります。
デモimport { RewritableText as RewritableText2 } from "react-notebook/dist/rewritable-text/tutorial/02-side-effect"
<RewritableText2
defaultValue="デベロッパーツールで編集過程を、編集が完了した段階でアラームで見られます"
onChange={(value) => console.log(value)}
onFinalized={(value) => alert(value)}
/>
これで最低限用が足りる機能はありますが、実際の利用では様々な追加機能がなければ使いづらいかと思います。例えば以下のような問題・改善点が挙げられます。
これはデザインの問題であり、唯一の決定的な方法があるわけではありませんが、有効な解決策として「クリックできることを示す」ことと「編集できることを示す」ことの二つを実践していきます。
そのためマウスカーソルをcursor: "pointer"
で指定しクリックできる雰囲気を出した上で、編集できる感を出すために鉛筆マークをテキストの右横に配置してみました。
import { RewritableText as RewritableText3 } from "react-notebook/dist/rewritable-text/tutorial/03-icon"
<RewritableText3
defaultValue="マウスの形がポインタになりました"
onChange={(value) => console.log(value)}
onFinalized={(value) => alert(value)}
/>
どうでしょうか。少しはマシになりましたか? 他にも下線の装飾を入れたり色を変えたりなどここでは工夫しているサイトもあります。
現時点での実装では内部状態が空文字列になってしまった時にクリックしてテキストを編集を開始
という文言を表示していますが、これはサイトを国際化するなどの状況でコンポーネントの再利用する上で大きな障害になります。
内部が空文字列になった際に好きな文字列やReactコンポーネントを渡して表示できるようにしましょう。
デモimport { RewritableText as RewritableText4 } from "react-notebook/dist/rewritable-text/tutorial/03-icon"
<RewritableText4
defaultValue="文字列をなくすと怒られます"
onChange={(value) => console.log(value)}
onFinalized={(value) => alert(value)}
displayWhenEmpty={<span style={{color: "red"}}>何か文字列を入力してください!</span>}
/>
キーボードイベントから入力された内容を確認してそれがEnterキーであれば編集と表示を切り替えるtoggleEditing
を呼び出すようにします。
Enterキーを検出する方法としてkeyboardEvent.keyCode
が13
かどうか確認するというのが長く使われてきましたが、こちらは現在標準から廃止(deprecated)されました。
Reactの公式ドキュメントでは対応するReact.KeyboardEvent
では使えるプロパティとしてはっきりとkeyCode
が書いてありますが、ここではMDNのドキュメントにもReactのドキュメントにもあるkey
の内容がEnter
かどうか確認します。
(これはこれでハードコードされている感があって怖い気もしますが....一応公式ドキュメントではW3Cでのイベントの仕様で決まっている範囲のどの値でも取りうるというように言っていますが、それならそれでそのように型をつけてくれたらと思います。)
デモimport { RewritableText as RewritableText5 } from "react-notebook/dist/rewritable-text/tutorial/03-icon"
<RewritableText5
defaultValue="Enterキーで編集を終了できます"
onChange={(value) => console.log(value)}
onFinalized={(value) => alert(value)}
displayWhenEmpty={<span style={{color: "red"}}>何か文字列を入力してください!</span>}
/>
入力された内容を他の場所で利用する上での制約だったり、嘘の入力や誤った入力を避けるためにフォームを送信する場合にバリデーションをかけることがよくあります。 例えばメールアドレスの入力欄であれば含まれない文字列などルールが決まっています。
利用シーンや入力内容に応じて様々なパターンがありますが、ここではそれらを一般化したバリエーション用の関数valiation: (value: string) => boolean
を引数として受け取り、false
になった場合は下にvalidationErrMsg
で生成する警告文を赤字で表示するという形で利用者にバリデーション違反を通知します。
ここは関数ではなく正規表現オブジェクトを渡すようにしてもいいかもしれません。
実際にどのようにバリデーションをかけるかというのはいろいろあり、例えば本来ならそもそもバリデーションでこけた場合はonFinalized
コールバックを呼び出すべきでないとか、そもそも編集を完了できないようにするべきなど、このコンポーネントの利用ケースに応じて改造してください。
onFinalized
を呼び出したくない場合は、input
内のonBlur
イベントでprops.validation
を呼び出して結果がtrue
になっていればprops.onFinalized
を呼ぶようにします。
さらに編集を完了したくない場合はonBlur
中でtoggleEditing
を呼び出さないようにします。Enterキーでfinalize
を呼ばないようにしても良さそうです。
import { RewritableText as RewritableText5 } from "react-notebook/dist/rewritable-text/tutorial/03-icon"
<RewritableText6
onChange={(value) => console.log(value)}
onFinalized={(value) => alert(`入力が完了しました:${value}`)}
defaultValue="sample@sample.com"
displayWhenEmpty="メールアドレスを入力してください"
validation={(value) => {
let reg = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
let result = reg.exec(value)
if (result != null) {
return result[0] === value
} else {
return false
}
}}
validationErrMsg={(value) => `正しいメールアドレスの形式ではありません: ${value}`}
/>
あとは若干のスタイルの調整を施せば以下のようになります。
単純なコンポーネントに見えて型付けや使いやすさのことに気を配ると案外に気を配ることが多くてReactJSのいい練習になったのではないでしょうか? ぜひこのコンポーネントと紹介したテクニックをあなたのソフトウェアに組み込んでみてください。