入力フォームで3桁毎にカンマを自動で入れてくれる補助ライブラリ : 3-Digit-Separator

2019年9月12日

テクノロジー プログラミング

経理財務系のシステム構築をする時に、数値表示や入力する数値は、3桁区切りで表示するのが好まれます。 実際に自分が使用してみると、確かに3桁区切りの数値は見やすいのも分かります。 これまで、仕事でこうしたシステム構築に対応してきた時に、普通に入力フォームで登録をしてもらい、登録後の表示部分で3桁区切りにしてきたのですが、間違った登録を無くすために入力フォームも対応したいと思って、簡単に設置できるライブラリを作っておきました。

推奨する使い方

inputタグのtype="number"で使いたくなる数値登録に関して、見た目的にも","カンマを表示することができません。 他のサイトでは、numberではなく、textを使うことを推奨しているようですが、textにした上でこのライブラリを使うことで、全角数値を半角数値に自動で変換することができます。 IMEシステムはスマートフォンなどを考えるともはや手がつけられない領域になってきています。 デフォルトで全角入力をする様な場合は、システム登録をする際に知らず知らずのうちに数字も全角で登録されてしまい、結果システムエラーになってしまうという経験は、恐らく小数派ではないはずです。 そのためにtype="number"というのは、現時点のHTML5仕様では圧倒的に物足りないので、こうしたライブラリが必要であることは容易に理解できますね。 ちなみに、変換は入力時のリアルタイムではなく、項目blur(項目を離れたタイミング)で行っています。 これは、カーソルの誤作動がブラウザごとに発生する恐れがあるため、リアルタイム処理を行わないようにしているためです。 リアルタイム版も検討はしているので、ご要望があれば搭載したいと思います。

3桁区切りの数値の仕様

桁数の多い数値をシステムに登録する際に、登録した跡で、桁数が足りなかったというようなミスを無くすEFO的な意味でも重要な機能になりますが、数値において大きく3つの仕様を考慮しました。

1. 整数値は3桁毎に","カンマを入れる

"100万円"を表示する場合、"1,000,000"と表示されるので、桁数をミスすることは無くなります。

2. 小数3桁毎に" "カンマを入れる

"3.14159265"を3桁セパレータすると、"3.141 592 65"という風に、小数値は、カンマではなくスペースで分割します。 3桁ごとの区切りが整数値は1の位から数えて3桁なのに対して、小数値は、高い値から数えて3桁区切りになるようにしています。 小数値はあまり3桁以上深く掘ることは少ないのですが、表示して目視での桁数確認はセパレートするだけで簡単にできますね。

3. マイナス値にも対応する

素人エンジニアがこうしたシステムを組む時に、数値以外の文字列である","カンマに注意を払いますが、"-"マイナスも数値ではなく、javascriptでは記号扱いになってしまうので、対応しておかなければ、プラスの値しか登録できないシステムになってしまいます。 "-1000.0001"という値は、"-1,000.000 1"という風に変換されることになります。

送信後の考慮

数値に勝手に数値以外の文字列を登録すると、それをsubmitした時にデータベース登録の際に、int型などへの登録でエラーに繋がります。 それを防ぐために、submitイベントでデータを送信する直前に、数値の文字列を取り除く処理を行っています。 サーバー側で、postされたデータに対して自己バリデートしてもいいですが、ここはライブラリで変換した文字列なので、ちゃんと最後まで面倒をみなければ、中途半端だと思い、デフォルトで処理をいれています。 ただ、ajaxなどでのpostを行っているようなシステムでは、必要に応じて自身で対応してもらう必要があるので、注意してください。

動的フォームの対応

すみません、初回バージョンではしていません。 動的な場合は今回は考慮せずに作っているので、ニーズが多いようであれば、バージョンアップさせようと思うので、ご要望くださいませ。

ソースコード

;window.$$3digit_separator = (function(){ var __event = function(target, mode, func){ if (target.addEventListener){target.addEventListener(mode, func, false)} else{target.attachEvent('on' + mode, function(){func.call(target , window.event)})} }; var __construct = function(){ switch(document.readyState){ case "complete" : new $$;break; case "interactive" : __event(window , "DOMContentLoaded" , function(){new $$});break; default : __event(window , "load" , function(){new $$});break; } }; var __options = { input_selector : "input[data-type='number']", hook_selector : [], interlocking_selector : "" }; var $$ = function(options){ if(!options){return;} this.options = this.initOptions(options); this.setForm(); }; $$.prototype.initOptions = function(options){ if(!options){return __options} var res = {}; for(var i in __options){ res[i] = __options[i]; } for(var i in options){ res[i] = options[i]; } return res; }; $$.prototype.setForm = function(){ this.forms = []; var inputs = document.querySelectorAll(this.options.input_selector); for(var i=0; i<inputs.length; i++){ this.setDigitSeparator(inputs[i]); // __event(inputs[i] , "focus" , (function(e){this.eventFocus(e.currentTarget)}).bind(this)); // __event(inputs[i] , "keyup" , (function(e){this.setDigitSeparator_keyup(e.currentTarget,e.keyCode)}).bind(this)); __event(inputs[i] , "blur" , (function(e){this.setDigitSeparator(e.currentTarget)}).bind(this)); if(inputs[i].form){this.forms.push(inputs[i].form)} } if(this.options.hook_selector && this.options.hook_selector.length){ for(var i=0; i<this.options.hook_selector.length; i++){ if(!this.options.hook_selector[i].selector || !this.options.hook_selector[i].event_key){continue;} var hooks = document.querySelectorAll(this.options.hook_selector[i].selector); for(var j=0; j<hooks.length; j++){ __event(hooks[j] , this.options.hook_selector[i].event_key , (function(e){this.setInterlocking(e)}).bind(this)); if(hooks[j].form){this.forms.push(hooks[j].form)} } } } // form-submit this.forms = this.forms.filter(function (x, i, self) { return self.indexOf(x) === i && i !== self.lastIndexOf(x); }); if(Object.keys(this.forms).length){ for(var i=0; i<this.forms.length; i++){ __event(this.forms[i] , "submit" , (function(e){this.submitProc(e)}).bind(this)); } } }; $$.prototype.setDigitSeparator = function(target){ if(!target){return} var value = target.value; if(value === ""){return} // target.value = Number(target.value.replace(/,/g,"")).toLocaleString(); target.value = this.stringNumberFormat(target.value); }; $$.prototype.setInterlocking = function(e){ if(!this.options.interlocking_selector){return;} var interlocking_elements = document.querySelectorAll(this.options.interlocking_selector); for(var i=0; i<interlocking_elements.length; i++){ this.setDigitSeparator(interlocking_elements[i]); } }; // ","(カンマ)を削除する $$.prototype.eventFocus = function(target){ if(!target){return} var value = target.value; if(value === ""){return} target.value = Number(target.value.replace(/,/g,"")); }; // 入力された数値フォーマットを整形する。(半角数値-.)3桁ごとに","カンマを入れる。(全角を半角に変換、不要スペースや記号は排除) $$.prototype.stringNumberFormat = function(str){ // 文字列変換 str = String(str); // 全角->半角変換 str = str.replace(/。/g , "."); str = str.replace(/[A-Za-z0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 65248); }); // マイナス判定 var minus = (str[0] === "-") ? true : false; // 数値 .(ピリオド) -(マイナス)以外の文字列を削除 str = str.replace(/[^0-9\.]/g,""); // マイナス処理 str = (minus) ? "-" + str : str; // 整数値と少数値の分離 var sp = str.split("."); var int = sp[0]; var dic = (sp[1]) ? sp[1] : ""; // 整数 : 3桁ごとにカンマを入れる int = this.numberFormat3_integer(int); // int = Number(int.replace(/,/g,"")).toLocaleString(); // 小数 : 3桁ごとにスペースを入れる dic = this.numberFormat3_decimal(dic); // 返り値 var num = int + ((dic) ? "."+dic : ""); return num; }; // 整数 : 数値の3桁ごとにカンマを入れる $$.prototype.numberFormat3_integer = function(num){ num = String(num); var tmpStr = ""; while (num != (tmpStr = num.replace(/^([+-]?\d+)(\d\d\d)/,"$1,$2"))){num = tmpStr;} return num; }; // 小数 : 数値の3桁ごとにスペースを入れる $$.prototype.numberFormat3_decimal = function(num){ num = String(num); var tmpStr = ""; while (num != (tmpStr = num.replace(/(\d\d\d)(\d+)$/,"$1 $2"))){num = tmpStr;} return num; }; // submit $$.prototype.submitProc = function(e){ var form = e.target; var query = this.options.input_selector; query += (this.options.interlocking_selector) ? ","+this.options.interlocking_selector : ""; var elms = form.querySelectorAll(query); for(var i=0; i<elms.length; i++){ elms[i].value = elms[i].value.replace(/[, ]/g,""); } }; return $$; })(); 上記jsファイル1つのみです。 これをheadタグに挿入して準備完了です。

実行方法

実際に使う場合は、上記ライブラリをHTMLのheadタグ内(bodyタグ内の場合は、できるだけ上位)に設置してもらい、その後bodyタグの下部に、以下のようにscriptタグを設置することで、自動的に機能します。 <script> new $$3digit_separator({ input_selector : "input[data-type='3digit_separator']" }); </script> インスタンスオプションは以下のようになっています。 input_selector : 対象の入力フォーム取得(複数対応) ex) "input[data-type='number']", hook_selector : イベント起動の対象になるelement-selector(複数) ex) [{button[name='btn']" : "event-key"},...] interlocking_selector : hookキーで自動変換するエレメントのselector

GitHub

sample.htmlを参考にしてもらうと、登録が楽にできると思います。 https://github.com/yugeta/3-digit-separator

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。