textarea内に入力された文字列を任意の桁数で改行する

textarea内に入力された文字列を任意の桁数で改行する

textareaのバリデート方法に一同驚愕!正規表現がヤバすぎる…その複雑さに涙がなんちゃら
狙いすぎると寒いわな。あー安心してください中身は普通の記事ですよーっと。
今回は<textarea>に入力されたテキストをJSでどうこうしようという話になります。

あらすじ

なんか友達に仕事用のメールアドレスを教えたらこんなのが飛んできたので、
しゃあねえやったるかという運びになります。

ちょっと教えてほしいことがあってですね。
テキストボックスに文字を入力したときに、
一定の文字数入力したら自動で改行できるようにしたいんだけど、
そういうのってJSかCSSで実現できたりしますかね?


具体的には
000001000002000003←こう打ち込まれた文字を


000001
000002
000003
↑こうしたい


ちょっと調べても良いやり方が出てこなかったので、聞いてみました。
よろしくお願いします。

はあ、さいですか。文字列をどうこうすんのって意外とめんどくさいねんな……。
まあ今日も元気にやっていきましょう。

要件とデモ

CSVデータから会員No.をコピペして貼り付ける、みたいな運用を仮定します。
ただしたまにTSVを貼っつけたりもされる意地悪パターンも多少考慮。

  • 数字、区切り記号(カンマおよびタブ)、改行記号以外の入力は弾く
  • 全角数字と全角カンマは半角に変換する。半角カンマとタブは改行に変換
  • 6桁ごとに改行表示させる
  • ただし、”1″や”23″などの値が改行やカンマで区切られていた場合は、
    “000001”、”000023″のようにゼロパディング表示にさせる
  • 重複する内容の行は削除する

動いているものを見ないとイメージが湧かないと思うので、例によってデモを用意しました。
ついでに、今回は1クリックすら面倒な人のために直接テキストエリアを埋め込んでみました。

実装の発想

<textarea name="form01_01" id="jsTextarea" class="sample"></textarea>

みたいな記述がHTML上にあったとします。

その1 イレギュラーを弾く

いきなり100%の完成形を作ろうとすると難しいので、まずは入力テキストの制御から。
正規表現を使って半角数字(0-9)・全角数字(0-9)・カンマ(,,)・
改行記号(\n)・タブ記号(\t)以外の入力を除外します。
全角カンマの入力まで許可するのは蛇足感強いですが一応。

テキストエリアからフォーカスが外れたタイミングで入力された文字を調べて、
イレギュラーがあれば消すっていう感じです。

jQueryだったら最初の部分が$('#jsTextarea').on('change', function(){~~~~});
のような書き方になると思いますが、後は大体変わらないです。

正規表現 is 何

今回は読み物系の記事ではないので正規表現に関する話は割愛し……たいんですが、
ま~たいつもの説明癖が。簡単に言うと、
文章内の文字列を曖昧な条件でも検索できたり、複雑な置換ができたりするすごいやつです。
実際にはそういう使い方もできる表現方法(記法)、って感じですが。

文字列の集合を一つの文字列で表現する方法の一つである。

ウィキるとこんなこと書いてますが、ナンノコッチャって感じですわな。
なんか文字をよしなにやってくれるやつなんだなーって思ってください。例えば……

(ポケモン|Pokemon|Pokémon|pokemon|POKEMON)?(ゴー|GO|Go|go)

(秀丸など)正規表現に対応したエディタを使って上記条件を入力すれば、
「ポケモンGo」でも「ポケモン GO」でも「Pokemon Go」でも
一纏めに検索できますみたいな。ちなみに正式名称はPokémon GOですが。

あと、正規表現はJavaScript固有のものというわけではないので、
覚えておけば別のプログラミング言語でも使えたりします。
さっきエディタ上での検索を例に挙げてたみたいに、
そもそもプログラミング以外の用途でも使えたりしますし。

分からないor覚えるのがめんどいって人は都度適当にググって
用途に合わせてコピペしつつなんとかしたらいいと思います(技術ブログにあるまじき発言)。


閑話休題。今回の例/[^0-90-9,,\n\t]/gの場合は、
文章中の半角数字や全角数字といった任意の文字"以外"を指定しています。
(プログラミングの文脈で『文章』という表現は不適ですが便宜的にそう書いておきます)

text.replace(/[^0-90-9,,\n\t]/g, '');というのは、
任意の文字"以外"を空文字に置換することで、例外となる文字の削除を実現しているわけです。

その2 全角から半角に変換

全角英数の文字コードから65248(16進数表記で0xFEE0)引くと半角英数になるので、
その考え方を利用して変換しています。

まあ偉そうに書いてますが当然私が新発見した画期的な方法などではなく、
遥か昔インターネットの賢者が考えた術式の一つなので、
そういうもんだと割り切ってもらえれば。

こんな感じにすれば半角数字を全角数字に変換もできます。
あと、似たようなロジックで平仮名を片仮名に変換したりもできるので
必要があればググってください。「ひらがな かたかな 変換 js」みたいな
小学生レベルの脳死検索でも望みの情報がヒットするはずです。

その3 任意の桁数で改行させる

ここが一番難解かも。いくつか説明が必要そうなところをピックアップします。

3-1 なぜ一度カンマに変換するか

改行に変換するための記号があれこれ分かれていると鬱陶しいので、一まとまりにしています。
が、正直、カンマに統一する必要性は特にないです。
\nや\tのような特殊文字で区切って配列が作れないというわけでもないですし。

じゃあ全部改行記号の\nに変換すればいいんじゃないの?って思う方もいるかもしれませんし、
そう思う方はそうすればいいと思います。ただ、個人的には
配列に格納するため一時的に利用しているだけの区切り記号と、
text += segments[i] + '\n'部分にて画面上に出力するための改行記号が、
同一なのはややこしいと感じたので、前者の記号はカンマで統一しました。

3-2 RegExpってなんや

正規表現オブジェクトを扱う上で使用するやつです。おわり。

……まあ、掻い摘んで書くと、

text = text.replace(/[\n\t]/g, ',');
var check = /\d/g.test(text);

前者は改行記号やタブ記号をカンマに変換する用途で、
後者は半角数字が入力されているかチェックする用途で
正規表現が用いられてるじゃないですか。
(\dというのは半角数字を意味します)

これは、正規表現リテラルと呼ばれる書き方です。
ただし、JSの場合変数は使用できません参考:
JavaScriptの正規表現のコンセプトを理解する(翻訳)

そこで登場するのがRegExpですよと。やってることは\d{num}、
つまり今回の場合、6桁数字が続いた場合はカンマを挿入するという記述になっております。
メタ文字は\\dや\{、\}といった具合にバックスラッシュが必要なんでそこは注意ですね。

3-3 整形したテキストを改行表示で出力

改行系の記号は全て半角カンマに変更した、6桁超える数字はカンマで区切るようにした、と。
では次にカンマを改行に変換したくなるところです。
ここで上記のソースを見返して一つ疑問に思いませんでしたか?

「どうせ,を\nに変換するだけなら、配列使う必要なくね?」
Exactly(そのとおりでございます)
segmentsなんて配列に格納して、一回一回取り出して改行を付与して更新……
なんか面倒だなと思いませんでしたか?
別に、下記のように書いてもいいんですよ。

text = text.replace(/,/g, '\n');

カンマを改行にする"だけ"、ならね。ただし、この後ゼロパディングなどをする上で、
結局配列に値を格納してどうこうする必要があるためこのような書き方をしています。

ここまでの整理

ここまでで、下記の要件は満たすことができました。

  • 数字、区切り記号(カンマおよびタブ)、改行記号以外の入力は弾く
  • 全角数字と全角カンマは半角に変換する。半角カンマとタブは改行に変換
  • 6桁ごとに改行表示させる

この後の項では、下記の対応を進めます。

  • "1"や"23"などの値が改行やカンマで区切られていた場合は、
    "000001"、"000023"のようにゼロパディング表示にさせる
  • 重複する内容の行は削除する

また、現状だと下記の不具合があるので、その修正も行います。

  • ",123"のような入力をした場合、テキストの最初に空行が生じてしまう
  • ,やEnterキーを連続入力すると大量の改行が挿入される
  • 数字しか入力していないのに、テキストの最後に空行が生じてしまう

その4 総仕上げ

重複行削除とゼロパディング、その他各種改行周りの不具合に対応した完成形。
重複行の削除とゼロパディングについては補足します。

4-1 重複行の削除

return self.indexOf(x) === i;で、配列内で最初に見つかった値ならtrueを返すようにしていて、
そうでなければfalseを返すようにしています。
segments = segments.filter(function (x, i, self) {~~~~});は
上記のうちtrueのものだけを再度segments配列内に格納するという意味の記述です。
分からなければ正直コピペで済ませてもどうにかなるので考えずに感じてください。

4-2 ゼロパディング

理屈としてはよくあるこれです。

('000000' + number).slice(-6);

たとえば入力値(number)が401だったとしたら、
('000000' + number)部分では000000401という文字列になります。
そこから.slice(-6);で末尾6桁分のテキストを取得すると000401という結果になる、
というロジックです。ただし、これだと(6桁にしか対応できず)拡張性がないので、
下記サンプルコードのような書き方になったというわけです。

segments[i] = (Array(num+1).join('0') + segments[i]).slice(-num);

Array(num+1)部分で長さがnum+1の空の配列を作り、.join('0')にて各配列を'0'で連結すると
結果的に任意の回数分0が並んで'000000'みたいになってくれる、と。
for文で書く方が好きだったらそっちで書いてもいいです。
(処理的にはfor文で書く方が早いようですが、記述の簡素さを優先しました)

なお、ES2015に対応していない悪しきブラウザ、
即ちIE11を無視してよい場合は下記のような書き方が一番簡潔です。

segments[i] = ('0'.repeat(num) + segments[i]).slice(-num);

おまけ

CSSに:placeholder-shownという疑似クラスがありまして。
<input>または<textarea>が未入力時のみ反映されるスタイルでございます。

textarea:placeholder-shown { background-color: #F9E2E2; }

こんな感じで、未入力時は入力を促すよう薄紅色になったりするやつが
JSなしでサクッと出来るのです。Microsoft製のブラウザさえなけりゃあなあ…!
このブログいつもIEとEdgeの悪態ついてんな。

IE/Edgeでもテキスト未入力時の装飾がしたい

こうなります。


テキストがある場合にtrue、そうでない場合はFalse、
True状態でIEやEdgeの時だけクラスを振るというシンプルな作りです。

えっ?「:placeholder-shown対応ブラウザと違って、
IEとEdgeだけテキスト入力中に背景色とボーダーの色が変わらないジャン」
って?
えー めんどくさ
addEventListener部分がchangeだとフォーカスが外れた時に発生するので、

textarea.addEventListener('keyup', function() {
(略;IE/Edge用の処理を適宜書く)
});

みたいなのを作って、テキストのあるなしに応じてmsPlaceholder関数を
動かしたらいいんじゃないすかネ(changeと異なりキーを離した時に発火する)。


余談の余談でついでに書いとくと、IEとEdgeはアホの子なので:placeholder-shownだけでなく
::placeholderのテキストにスタイル当てる場合もちょっと配慮が必要です。
例えば初期表示テキストの文字色を変えたい場合、

textarea::placeholder {
color: #A5673F; /* 通常ブラウザ用 */
}
textarea:-ms-input-placeholder {
color: #A5673F; /* IE用 */
}
textarea::-ms-input-placeholder {
color: #A5673F; /* Edge用 */
}

といった書き方をする必要があります。まとめて書くとなぜか動かないので注意。

/* 動作しない例 */
textarea::placeholder,
textarea:-ms-input-placeholder,
textarea::-ms-input-placeholder { color: #A5673F; }

あとがき

(今回はどちらかと言えばバックオフィス方面で使いそうな例でしたが、)
EFO対策なんて言葉があるように、ウェブの世界において最適なフォームの調整というのは
しばしば議題に上がるものです。つまり、それだけウェブフォームで情報を入力して
送信するという一連の作業がしんどいってことです。ユーザー目線で。

それゆえにリアルタイムエラーチェックや入力サジェストのように
フォームの入力を補助する機能を求められることが多いのですが……。
まあ今回の記事を読んでもらえれば分かるように、フォーム制御というのは大概めんどいです。
地味なので軽視しがちなのですが、まあまあ手間がかかることなんだなーと認識してもらえれば。

ちなみに友人からの質問どうこうという話を冒頭でしましたが、
結局めんどくさそうだったからという理由で採用されなかったそうです。草