概要
某サイトに入力補助を追加しようと思ってTEXTAREAの文字数をリアルタイムでカウントする関数を書いた。
難しいことはやってない。シンプルな実装。
前提
- HTML_Quickformで既にフォームが構築されており、既存部分に手を加えたくない
- サーバ側の文字数カウント処理と内容を同じにしたい
public function validate($value, $options = null)
{
$length = mb_strlen(str_replace("\r\n", "\n", $value));
switch ($this->name) {
case 'minlength': return ($length >= $options);
case 'maxlength': return ($length <= $options);
default: return ($length >= $options[0] && $length <= $options[1]);
}
}
ざっくりまとめると、全角・半角問わず1文字とカウント。改行も1文字とカウント。
調べたところ、カウント表示エリアがあらかじめHTMLに記載してある前提の実装例が多い。
Bootstrap Maxlength は表示がホバーで良さげだったが
ブラウザによってはTEXTAREAにmaxlength入ってるとお利口さんにも独自処理で文字数制限をかける場合があるので、サーバ側の数え方と一致させられる確証がない。
そのため、この処理専用の関数を書くことにした。
実装
※jQuery導入が前提
//配列にすることで、サロゲートペア文字でも1つと換算して計算できるようにする(1文字=1要素となる)
function stringToArray (str) {
return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
}
function setCount(obj,max){
//カウント表示用エリアを付与
var count_area = $('<p class="count">0/' + max + '</p>').insertAfter($(obj));
//イベント設定
$(obj).bind("keyup change",function(){
//文字数取得
var count = stringToArray($(obj).val()).length;
//文字数反映
count_area.text( count + '/' + max );
//最大数チェック
if(count > max){
count_area.css('color','red');
} else {
count_area.css('color','black');
}
});
//初期設定
$(obj).trigger("keyup");
}
$(document).ready(function(){
//設定する
setCount("[name=test_textarea]",400);
setCount("[name=array1\\[test_comment\\]]",400);
});
これで、設定したフォームの次にカウント表示用エリアが追加される。
あとはcssなどでエリアの形や配置を整えてあげればOK
JavaScriptのネイティブなlengthを取得すると、ブラウザ環境によっては改行が2文字と判定されるが
jQueryのval()を経由してlengthを取得すると1文字で判定されるため、楽ちん。
HTML_Quickformを使っているので、Group要素の場合にはフォーム名がarray1[test_comment]のようになるが、[]は\\でエスケープしてやれば大丈夫。
参考文献
jQuery日本語リファレンス
jQueryでコンテンツ要素の文字数を取得するパターン:BlackFlag
開発メモ:入力された文字数をリアルタイムにカウントして表示する:理総研Web
JavaScriptでのサロゲートペア文字列のメモ
補足
実際には、使用しているサーバーがロリポップでデータベースサーバーの文字コードが変更できないため
サロゲートペア文字が扱えないので、下記のような感じにした。
- サロゲートペア文字は文字数カウント時に無視する
- changeイベントで、サロゲートペア文字を削除する(keyupイベントで拾うと、IME変換を妨げるため)
function setCount(obj,max){
//カウント表示用エリアを付与
var count_area = $('<p class="count">0/' + max + '</p>').insertAfter($(obj));
//イベント設定
$(obj).bind("keyup",function(){
//文字数取得
var count = $(obj).val().replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,'').length;
//文字数反映
count_area.text( count + '/' + max );
//最大数チェック
if(count > max){
count_area.css('color','red');
} else {
count_area.css('color','black');
}
});
//イベント設定
$(obj).bind("change",function(){
//changeイベントでサロゲートペア文字は削除する(ロリポップサーバーでは保存できないため)
var value = $(obj).val().replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,'');
$(obj).val(value);
//文字数取得
var count = $(obj).val().length;
//文字数反映
count_area.text( count + '/' + max );
//最大数チェック
if(count > max){
count_area.css('color','red');
} else {
count_area.css('color','black');
}
});
//初期設定
$(obj).trigger("keyup");
}