AngularJSと UI Bootstrapで日付と時刻の入力をする

JavaScript

カレンダーをポップアップさせて日付を選択する方式の日付入力を AngularJSで実現する方法

Webのフォームを実装する際にいつも面倒な項目といえば日付時刻だ。年・月・日とそれぞれ別のinput要素やselect要素を設けるにせよ、書式を決めてひとつのinput要素に入力してもらうにせよ、ユーザーの入力が正しいことをそれなりに深く検証しなければならない(例えば4月31日などという入力を許すべきではないし、2月29日という入力を許容するかどうかは年までチェックする必要がある)。まして日付だけでなく時刻までユーザーが間違いにくいように入力させるとなればなおさら面倒ということになる。

ユーザーの日付入力を補助するためによく使われるのが Datepicke(デートピッカー)と一般的に呼ばれるUIコンポーネントだ。日付を入力するためのテキストボックス(やその横のボタンなど)をクリックすれば、カレンダーのような表示がポップアップし対象の日をクリックすることで日付の入力が行われる。これならユーザーが間違った書式(特に日本では全角文字を使ってしまうなど)で日付を入力してしまうことが少なくなるし、視覚的に曜日まで認識したうえで日付を選択できるため意図と異なる日付を入力してしまうことも減るだろう。(ただし、誕生日のように現在とは遠く離れておりユーザーが間違えそうもない日付の入力をする際にはDatepickerの利点はあまりないと思われる)

以前に 見た目は Bootstrapなのに動きは AngularJSなサイトを作るにはという記事で、UI Bootstrap を使って AngularJSと Bootstrapを一緒に利用する方法を紹介しているが、この UI Bootstrapには日付や時刻を入力するための Datepickerおよび Timepickerなるコンポーネントが収録されているため、この記事ではそれらを使って日付と時刻を入力するフォームのサンプルを示す。

スクリーンショット

テキストボックスをクリックするとカレンダーがポップアップする

入力エラーは AngularJSのバリデーション機構でチェックされる。このサンプルでは、入力エラーがある時には決定ボタンが押せないようにしてある(ボタンがグレーになっているのに注目)。

入力された日付や時刻は文字列や数値ではなく Dateオブジェクトに格納される。

HTMLソース

input要素にdatepicker-popup属性を付けるとそのテキストボックスは日付入力専用となり、クリックでカレンダーがポップアップし日付を選択できるようになる。また、AngularJSのバリデーション機構により指定の書式(デフォルトではyyyy-MM-dd)で正しい日付が入力されているかどうかのチェックがされるため、慣れているユーザーはポップアップから日付を選択せずキーボードで直接入力することもできる。

時刻入力欄を設けるには、timepicker要素を含んだ div要素を設置する。この時、ng-bind属性は div要素のほうに付ける。

<html lang="ja" ng-app="MyApp">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>UI BootstrapのDatepicker/Timepickerで日時を入力</title>
    <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
    <!-- 日本語ロケールをロードしておくことでカレンダーの曜日表示が日本語になる -->
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular-i18n/1.2.10/angular-locale_ja-jp.min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap-tpls.min.js"></script>
    <script language="javascript">
      angular.module("MyApp", ["ui.bootstrap"])
      .config(["datepickerConfig", "datepickerPopupConfig", "timepickerConfig", 
               function(datepickerConfig, datepickerPopupConfig, timepickerConfig) {
        datepickerConfig.showWeeks = false; // 週番号(日本では馴染みが薄い)を非表示にする
        datepickerConfig.dayTitleFormat = "yyyy年 MMMM";
        datepickerPopupConfig.currentText = "本日";
        datepickerPopupConfig.clearText = "消去";
        datepickerPopupConfig.toggleWeeksText = "週番号";
        datepickerPopupConfig.closeText = "閉じる";
        timepickerConfig.showMeridian = false; // 時刻を24時間表示にする(デフォルトでは12時間表示)
      }])
      .run(["$rootScope", function($scope) {
        $scope.date = new Date();  // デフォルトで現在時刻を表示
        $scope.alert = null;
        $scope.done = function() {
          $scope.alert = { type: 'success', msg: "" + $scope.date + " = " + $scope.date.getTime() };
        }
      }]);
    </script>
    <style type="text/css">
      input.ng-invalid { border: 1px solid red !important; }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>UI BootstrapのDatepicker/Timepickerで日時を入力</h1>
      <form name="myForm" class="form-horizontal">
        <div class="form-group">
          <label class="col-sm-3 control-label">好きな日時を選択(必須)</label>
          <div class="col-sm-3">
            <input type="text" class="form-control" datepicker-popup ng-model="date" ng-required="true"/>
            <div ng-model="date">
              <timepicker></timepicker>
            </div>
          </div>
        </div>
        <div class="form-group">
          <div class="col-sm-offset-3 col-sm-9">
            <button type="submit" class="btn btn-primary" ng-disabled="!myForm.$valid" ng-click="done()">決定</button>
          </div>
        </div>
      </form>
      <alert ng-show="alert" type="alert.type" close="alert = null">{{alert.msg}}</alert>
    </div>
  </body>
</html>

この例では日付と時刻を格納する先として同一のDateオブジェクトを使用しているが、これだと日付側で入力エラーを起こした際に時刻がリセットされてしまったり(またはその逆)するのでそれが嫌であれば日付と時刻にはそれぞれ別の Dateオブジェクトをバインドして最後に合成するように処理してやれば良いだろう。

同じカテゴリの記事

Angular.JSでselect要素にoptionをぶら下げる色々な方法 2014年3月29日
AngularJSで、時間のかかる通信の間にモーダルを使ってプログレスバーを表示する 2014年3月18日
HTML5アプリケーションでの、サーバとクライアントの時差について 2014年3月17日
AngularJSの $resourceを使って application/x-www-form-urlencoded 形式のリクエストを POSTする 2014年3月15日

お勧めカテゴリ

英語でアニメ観ようず
なじみ深い日本製アニメの英語版DVDで、字幕と音声から英語を学びましょうという趣旨のシリーズ記事です。
ScalaのようでJavaだけど少しScalaなJSON API
Scalaと Spring Frameworkを使って REST的なJSON APIを実装してみましょう。
ドクジリアン柔術少女 すから☆ぱいそん
代表 嶋田大貴のブログです。写真は神仏に見せ金をはたらく罰当たりの図