jQuery UI Datepicker-土・日・祝日・非営業日対応

| トラックバック(0)

jQueryjQuery UI は、とても便利です。各ブラウザのJavascript実装の差異を吸収してくれるだけでなく、ドラッグアンドドロップやタブビューなど様々なリッチなUIを利用することができます。あのIE6対応を考えなくても済むというだけでも、利用する価値があるというものです。
これらjQuery/jQuery UIは、コンシューマ向けのWebサイトのみならず、企業内の基幹業務用Webシステムであっても利用して当たり前、というほど普及していると言ってもよいでしょう。

ただし実際の業務システムで利用する際には、「ここをカスタマイズできれば!」という部分が出てくるものです。
jQuery/jQuery UIはオープンソースですから、ソースコードを直接修正してしまえば何でもカスタマイズできます。ただしそのような手段を取ると、今後リリースされるであろう新しいjQueryライブラリに更新するたびに、修正パッチを当て続けなければなりません。これでは運用が大変です。

実はjQuery UIには、個別のカスタマイズをしやすくするための拡張ポイントが準備されています。ライブラリ本体のソースコードにはなるべく手を付けず、できる限り拡張ポイントを利用するのがカスタマイズのコツです。

今回はjQuery UI Datepickerを拡張し、土・日・祝日・非営業日の背景色を変更し、選択不可にするという小ネタを紹介します。
datepicker.png

jQuery/jQuery UIのバージョン

本記事は、下記バージョンのjQuery/jQuery UIで検証しています。

javascriptのみで実現

まずはjavascriptのみで実現するバージョンです。javascript内に静的に休日情報が定義されておりajaxによる動的な情報取得が不必要です。以下のような場合におすすめです。

  1. 一度決めた祝日や非営業日は、基本的に変更されない
  2. 定期的にシステムメンテナンスをするタイミングが取れる
  3. 通信帯域が狭く、通信頻度はなるべく減らしたい

css

土・日・祝日・非営業日の背景色をCSSで定義します。

.date-sunday   .ui-state-default {
  background-image: none; background-color: #FF9999
}
.date-saturday .ui-state-default {
  background-image: none; background-color: #66CCFF
}
.date-holiday0 .ui-state-default {
  background-image: none; background-color: #FF99FF
}
.date-holiday1 .ui-state-default {
  background-image: none; background-color: #FFFF33
}

注意点:

  • .ui-state-defaultはjQuery UIが動的生成するclassです。将来のjQuery UIでは、違うクラス名になってしまう可能性があります。
    • ここはjQuery UIの内部構造に依存しています。イケテナイ・・・
  • .date-holidayXのXは、後述する「休日タイプ」に一致させます。この例では2種類ですが、もっと細分化することも可能です。

javascript

jQuery UI datepickerを拡張するjavascript本体です。
この例では、2011年と2012年の土・日・祝日・非営業日が定義されています。

実際の運用としては、このjsファイルを年に一回更新する形となります。
ブラウザや経路途中でのキャッシュに悪影響を受けないように、jsファイルのファイル名以降に「?更新日」をつけるようにし、ファイルを更新した際には更新日を変更するようにしましょう。

$(function() {
// 毎年書き換える部分 ココカラ
  /* javascriptのDate関数に与える月は、0〜11であることに注意 */
  var minD = new Date(2011,0,1);   //カレンダーでピックアップできる最前の日
  var maxD = new Date(2012,11,31); //カレンダーでピックアップできる最後の日
  
  /* 
   * 祝日・非営業日の定義
   * (土・日は自動計算)
   * "yyyyMMdd":{type:?, title:"休日の名前"} を要素とするオブジェクト
   * type は任意の意味を持たせることが可能
   * (この例では、type:0は国民の祝日、type:1は企業独自の非営業日としている)
   */
  var holidays = {
    "20110101":{type:0, title:"元日"},
    "20110110":{type:0, title:"成人の日"},
    "20110211":{type:0, title:"建国記念の日"},
    "20110321":{type:0, title:"春分の日"},
    "20110429":{type:0, title:"昭和の日"},
    "20110503":{type:0, title:"憲法記念日"},
    "20110504":{type:0, title:"みどりの日"},
    "20110505":{type:0, title:"こどもの日"},
    "20110718":{type:0, title:"海の日"},
    "20110919":{type:0, title:"敬老の日"},
    "20110923":{type:0, title:"秋分の日"},
    "20111010":{type:0, title:"体育の日"},
    "20111031":{type:1, title:"創立記念日"},
    "20111103":{type:0, title:"文化の日"},
    "20111123":{type:0, title:"勤労感謝の日"},
    "20111223":{type:0, title:"天皇誕生日"},
    "20120101":{type:0, title:"元日"},
    "20120102":{type:0, title:"振替休日"},
    "20120109":{type:0, title:"成人の日"},
    "20120211":{type:0, title:"建国記念の日"},
    "20120320":{type:0, title:"春分の日"},
    "20120429":{type:0, title:"昭和の日"},
    "20120430":{type:0, title:"振替休日"},
    "20120503":{type:0, title:"憲法記念日"},
    "20120504":{type:0, title:"みどりの日"},
    "20120505":{type:0, title:"こどもの日"},
    "20120716":{type:0, title:"海の日"},
    "20120917":{type:0, title:"敬老の日"},
    "20120922":{type:0, title:"秋分の日"},
    "20121008":{type:0, title:"体育の日"},
    "20121031":{type:1, title:"創立記念日"},
    "20121103":{type:0, title:"文化の日"},
    "20121123":{type:0, title:"勤労感謝の日"},
    "20121223":{type:0, title:"天皇誕生日"},
    "20121224":{type:0, title:"振替休日"}
  };
// 毎年書き換える部分 ココマデ

  // この例では、"datepicker"というclassを持つテキストフィールドにdatepickerを挿入する
  $(".datepicker").datepicker({
    // 選択後にテキストフィールドに入力される日付のフォーマット。なぜかyyが4桁の年を意味する。。。
    dateFormat: "yy/mm/dd", 
    minDate: minD,
    maxDate: maxD,
    beforeShowDay: function(day) {
      var result;
      var holiday = holidays[$.format.date(day, "yyyyMMdd")]
      // 祝日・非営業日定義に存在するか?
      if (holiday) {
        result =  [false, "date-holiday"+holiday.type, holiday.title];
      } else {
        switch (day.getDay()) {
          case 0: // 日曜日か?
            result = [false, "date-sunday"];
            break;
          case 6: // 土曜日か?
            result = [false, "date-saturday"];
            break;
          default:
            result = [true, ""];
            break;
        }
      }
      return result;
    }
  });
});

重要な拡張ポイントは、beforeShowDayイベントハンドラです。
このイベントハンドラは「カレンダーの各日が描画される直前」に呼び出され、戻り値として

 [その日は選択可能か?(true/false), その日に適用されるCSS, (必要であれば)popupするツールチップ]

という配列を返す必要があります。
このイベントハンドラ内部で、祝日・非営業日・土・日をチェックし、CSSを切り替え、日付選択を不可にしています。

html

上記のcssとjavascriptを利用するHTMLです。ひじょーにシンプルです。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>date picker addon</title>
   	<link type="text/css" href="css/ui-lightness/jquery-ui-1.8.16.custom.css" rel="stylesheet"/>	
    <link type="text/css" href="css/datepicker-addon.css" rel="stylesheet"/>
    
    
    
    
  </head>
  <body>
    <h1>date picker addon</h1>
    From:<input type="text" value="" class="datepicker" id="from_date"></input><br/>
    To:<input type="text" value="" class="datepicker" id="to_date"></input>
  </body>
</html>

cssとjavascriptの読み込み順序に注意してください。jQuery/jQuery UIのcss/jsのから読み込まれる必要があります。

祝日・非営業日情報をajaxで取得

上記の例では、祝日・非営業日情報はjavascript内部に静的に持っていました。この実装はサーバー・クライアント間の通信を必要としないため、通信負荷をかけないというメリットがありますが、「ユーザーやデータの状況に従って非営業日を動的に変更したい」というニーズに答えることは困難です。
そのため次は、休日情報をajaxで動的取得してみます。

サーバ側

サーバ側は何で作ってもかまいません。本記事では、

 http://server_url:3000/holidays/search.json に パラメータ year=2011&month=10 など

でPOSTリクエストした際に、holidaysの形式に従ったjsonが返ってくるようなサーバをrails3で作りました。

...
  def search
    self.allow_forgery_protection = false
    @holidays = Holiday.where("year=? and month=?", 
			      params[:year], 
			      params[:month])
    hs = Hash.new
    @holidays.each do |h|
      v = {:type => h.typecd, :title => h.title}
      k = Date.new(h.year.to_i, h.month.to_i, h.day.to_i).strftime("%Y%m%d")
      hs[k] = v
    end
    respond_to do |format|
      format.html
      format.json { 
        render json: hs.to_json
      }
    end
  end
...

このサーバから取得されるjsonの例(見やすいように改行や空白を追加しています)

{
  "20111010":{
    "type":0,
    "title":"\u4f53\u80b2\u306e\u65e5"
  },
  "20111031":{
    "type":1,
    "title":"\u5275\u7acb\u8a18\u5ff5\u65e5"
  }
}

javascript

上記のjavascriptとやっている内容は同じですが、休日定義(holidays)をajaxで動的に追加していくようになっています。

$(function() {
  // 空の休日定義 ajaxによって取得した休日情報が動的に追加される
  var holidays = {};
  /*
   * 休日情報を取得するajax
   * サーバーから情報を取得するまでカレンダーのレンダリングを止めたいので
   *  async:false
   * としている
   */
  function searchHolidays(year, month) {
    $.ajax({
      url:      "/holidays/search.json", // 接続先のURL
      type:     "POST",
      data:     "year="+year+"&month="+month, // パラメータ
      async:    false,
      success:  function(json, status) {
        // JSONで取得した休日情報を休日定義にマージ
        // 同じ休日の情報を複数回取得した場合、新しく取得した情報で上書きされる
        // (現状のスペックでは、休日を削除された場合は対応できない。ページのリロードが必要)
        holidays = $.extend(holidays, json);
      }
    });
  }
  $(".datepicker").datepicker({
    dateFormat: "yy/mm/dd",
    // カレンダーがレンダリングされる直前に呼び出される
    beforeShow: function(input, inst) {
      // テキストフィールドに値が入っていればその日付を、入っていなければ今日の日付を取得
      var date = $(input).datepicker("getDate") || new Date();
      // その月の休日情報をajaxで取得
      searchHolidays(date.getFullYear(), date.getMonth()+1);
    },
    // カレンダーの月が変更されたら呼び出される
    onChangeMonthYear: function(year, month, inst) {
      // その月の休日情報をajaxで取得
      searchHolidays(year, month);
    },
    // beforeShowDayは前回とおなじ
    beforeShowDay: function(day) {
      ...
    }
  });
});

重要な拡張ポイントは、beforeShowイベントハンドラとonChangeMonthYearイベントハンドラです。

beforeShowイベントハンドラは、カレンダーが描画される直前に呼び出されます。
引数のinputはdatepickerを対応付けたテキストフィールド、instはdatepickerそのものを指します。カレンダーが描画される際、テキストフィールドに既に値が入力されていればその月を、入力されていなければ当日の月が表示されます。その値を取得し、ajaxでその月の休日情報を取得しています。

onChangeMonthYearイベントハンドラは、描画されたカレンダーの月を変更したタイミングで呼び出されます。引数で描画する予定の年と月が与えられますので、ajaxで休日情報を取得しています。

cssとHTML

javascriptのみ版と同じ

さらなる拡張

上記のコードでひととおり動きますが、月を変更する毎にajaxでサーバアクセスを行うため、ちょっと非効率です。
サーバ側の休日テーブルの変更頻度にもよりますが、「onloadでその年の休日情報を非同期に取得する」、「月ごとではなく年ごとに休日情報を取得する or 前後数カ月分の休日情報を取得する」等、状況に応じてさらなるカスタマイズをしてください。

最後に

今回はここまでにします。
jQuery/jQuery UIをいじり倒して、幸せな開発を行ないましょう!

トラックバック(0)

トラックバックURL: http://tech-sketch.jp/mt-tb.cgi/44

このブログ記事について

このページは、松井 暢之が2011年12月12日 10:00に書いたブログ記事です。

ひとつ前のブログ記事は「OSS統合監視ツール「Zabbix」を利用して大規模環境監視(2)」です。

次のブログ記事は「システム間連携 その4:ZeroMQ」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ