Web ページでも使える カレンダーコントロールのサンプル


Windows ストア アプリでは、Internet Explorer 10 がサポートしている HTML5 のコントロールの他、WinJS (Windows Library for JavaScript) が提供するコントロールを使用してアプリの UI を構築することができます。

ちなみに WinJS が提供するコントロールの一覧は以下のページで、

 

クイック スタート: WinJS コントロールとスタイルの追加 (JavaScript と HTML を使った Windows ストア アプリ) (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/hh465493.aspx

 

Windows ストア アプリ (HTML + JavaScript) で使用できるコントロールは以下のページで確認することができます。

 

コントロールの一覧 (JavaScript と HTML を使った Windows ストア アプリ) (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/hh465453.aspx

 

上記のように豊富なコントロールが用意されていますが、なぜかカレンダーのコントロールが用意されていません。

 

Web ページと Windows ストア アプリで使用できるカレンダー コントロールは?

 

Web ページで使用することのできるカレンダーコントロールといえば、jQuery の DatePicker コントロールが有名で、実際のところ Windows ストア アプリでも使用することができます。

同コントロールのカレンダー部分は、日付を表示する時以外は隠れており、それはそれで表示領域を取らなくて良いですネ、というところではあるのですが、場合によってはカレンダーを全面にばばーん、と表示しておきたいあるでしょう。(あるんですよ、あるの。)

そういった場合には、知力を尽くして jQuery のカレンダーコントロールをカスタマイズするか、検索エンジンを駆使して、気前の良い誰かの作成したイカしたコントロールを探すというのが一般的なのでしょうが、不幸にしてニーズを満たすコントロールに出遭えないこともあります。そういった場合には途方に暮れるか、潔くあきらめる、といった消極的な手段を採るというのも尊重すべき選択肢ではあるのですが、幸運にして HTML + JavaScript のプログラミング・スキルを持った人であれば自分で作るというアグレッシブな手段に訴えるということもできます。

今回は、そんな「無いなら自分で作る、作ってやる」と、心に固く誓った、存分にやる気の方のために、参考になるかどうかはアレですが、私が単純な HTML + JavaScript で作成したサンプルを紹介したいと思います。

 

単純な HTML と JavaScript で作ったカレンダーコントロールのサンプル

 

カレンダーのコントロールを作るというと、なにやら非常に面倒くさそうな印象を受けますが、実際のところ面倒くさいです。

ただし、面倒ではありますが、けして難しくはありません。

ざっくりとした作成手順は以下の通りです。

  1. 日付を表示するためのグリッドを table タグで作成
  2. 作成したグリッドに一か月ぶんの日付データを曜日の位置に合わせて流し込み
  3. 日付のセルの選択、月の移動のためのイベントハンドラを設定
  4. 各ハンドラの実装

上記わずか 4 ステップで作成したのが以下のクラスです。

//名前空間の指定
var monoesblog = {};

//クラスの追加
monoesblog.calendarCtrl = function(calendarDiv,startDay,chooseDateCaption,cellClickHandler){  

  //選択されている日付オブジェクトを返すメソッド
    this.getChooseDate  = function () { return this.choosedDate;}
    this.getBaseDivName = function (){ return calendarDiv;}
 
//--- 実際の処理
    //カレンダーを表示するための table タグを主力
    this.renderGrid(calendarDiv);
    //カレンダーを描画
    this.renderCalendar(calendarDiv + "_fDay", startDay,cellClickHandler);
   //カレンダーの表示月の移動矢印のハンドラ設定
    this.setMoveMonthHandler(this);

  //選択された日付に対してキャプションが指定されていれば
  document.getElementById(calendarDiv + "_captionSpan").innerText= (chooseDateCaption)?chooseDateCaption: " ";
}

   //カレンダーを描画
   monoesblog.calendarCtrl.prototype.renderCalendar  = function (tdID, currentDay, dayClickHandler) {
       var todaySelectedFlg = false;
       var this_calendar = this;
       var baseDivName = this_calendar.getBaseDivName();
       var lastWeekTR = document.getElementById(baseDivName + "_fLastWeek");
       lastWeekTR.style.visibility = "visible";
       var currentYear = currentDay.getFullYear();
       var currentMonth = currentDay.getMonth() + 1;

       this.choose_from_date = currentDay;

       //カレンダー上に月を表示
       document.getElementById(baseDivName + "_fMonth").innerText = currentYear
            + " 年 " + currentMonth + "月";

       var startDate = new Date(currentYear + "/" + currentMonth + "/1");

       this.current_fYear = Number(currentYear);
       this.current_fMonth = Number(currentMonth);

       startDate = this.calcDate(startDate, 0 - startDate.getDay());

       for (var i = 1; i <= 42; i++) {
            var dayDiv = document.getElementById(tdID + i);
            dayDiv.innerText = startDate.getDate();
            dayDiv.style.color = "black";
            dayDiv.setAttribute("tapDate", startDate.toString());
            dayDiv.setAttribute("tabindex", 0);
            dayDiv.onclick = function () { this_calendar.chooseDate(this, this_calendar); }

             //外部からのイベントハンドラが指定されていたら
             if(dayClickHandler){
    dayDiv.addEventListener("click",
                    function () {
        dayClickHandler(new Date(this.getAttribute("tapDate")));
                    }, false);
    }
                dayDiv.onkeydown = function (e) { if (e.keyCode == 13) {this_calendar.chooseDate(this, this_calendar);this.click(); }}

            //今日の日付を選択
            if (!todaySelectedFlg) {
                if (currentDay.getFullYear() == startDate.getFullYear()
                    && currentDay.getMonth() == startDate.getMonth()
                    && currentDay.getDate() == startDate.getDate())
                { this.chooseDate(dayDiv, this); }
            }
            if ((startDate.getMonth() + 1) != currentMonth) {
                //先月と翌月の日付の色
                dayDiv.style.backgroundColor = "gainsboro";
                if (i == 36) {
                    //最終週行の調整
                    lastWeekTR.style.visibility = "hidden";
                    break;
                }
            }
            else if (startDate.getDay() == 0)
            {  dayDiv.style.backgroundColor = "#f69";  } //日曜日の色
            else if (startDate.getDay() == 6)
            {   dayDiv.style.backgroundColor = "#9cf"; } //土曜日の色
            else {  dayDiv.style.backgroundColor = "#ffc"; }
            startDate = this.calcDate(startDate, 1);
        }
    }

//カレンダーの日付を選択
   monoesblog.calendarCtrl.prototype.chooseDate =  function (tdDiv, this_calendar) {
        var dayOfWeekArray = ["日", "月", "火", "水", "木", "金", "土"];
        var baseDivName = this_calendar.getBaseDivName();
        tdDiv.style.border = "solid 3px blue";
        var prev_fdate = this_calendar.prev_fdate;
        if (prev_fdate && tdDiv != prev_fdate){ prev_fdate.style.border = ""; }
        this_calendar.prev_fdate = tdDiv;
        var gDate = new Date(tdDiv.getAttribute("tapDate"));
        this_calendar.choose_from_date = gDate;
        document.getElementById(baseDivName + "_chooseDate").innerText = this.formatDate_JP(gDate) + "(" + dayOfWeekArray[gDate.getDay()] + ")";
        //選択された日付を返す
        this_calendar.choosedDate = gDate;
    }

     //カレンダーの月移動
    monoesblog.calendarCtrl.prototype.moveMonth = function (ctrl ,this_calendar) {
        var changedYear = 0;
        var changedMonth = 0;
        var ctrlID = ctrl.id;
        var baseDivName = this_calendar.getBaseDivName();
        var previouseID = baseDivName + "_f_prv";
        var nextID = baseDivName + "_f_next";
        var current_fMonth  = this_calendar.current_fMonth;
        var current_fYear  = this_calendar.current_fYear;
        var prev_fdate =  this_calendar.prev_fdate;

        switch (ctrlID) {
            case previouseID:
                if (current_fMonth == 1)
                { changedMonth = 12; changedYear = current_fYear - 1 }
                else {
                    changedMonth = current_fMonth - 1; changedYear = current_fYear;
                }
                break;
            case nextID:
                if (current_fMonth == 12)
                { changedMonth = 1; changedYear = current_fYear + 1 }
                else {
                    changedMonth = current_fMonth + 1; changedYear = current_fYear;
                }
                break;
        }
        var changedDate = new Date(changedYear.toString() + "/" + changedMonth + "/1");
        this_calendar.renderCalendar(baseDivName + "_fDay", changedDate);

        if (prev_fdate)
        { prev_fdate.style.border = ""; }
    }

    //カレンダーの月移動のイベント設定
   monoesblog.calendarCtrl.prototype.setMoveMonthHandler = function(this_calendar) {
        var baseDivName = this_calendar.getBaseDivName();
        var fPrv = document.getElementById(baseDivName + "_f_prv");
        var fNext = document.getElementById(baseDivName + "_f_next");
        fPrv.onclick = function () { this_calendar.moveMonth(this,this_calendar); };
        fPrv.onkeydown = function (e) { if (e.keyCode == 13) this_calendar.moveMonth(this,this_calendar); };
        fNext.onclick = function () { this_calendar.moveMonth(this,this_calendar); };
        fNext.onkeydown = function (e) { if (e.keyCode == 13) this_calendar.moveMonth(this,this_calendar); };
    }

    //日付を計算する
    monoesblog.calendarCtrl.prototype.calcDate =  function (dt, addDay)
     {
            var baseSec = dt.getTime();
            var addSec = addDay * 86400000;
            var targetSec = baseSec + addSec;
            dt.setTime(targetSec);
            return dt;
    }

    //日付を年月日に
    monoesblog.calendarCtrl.prototype.formatDate_JP =  function (dateObj)
    {
         return dateObj.getFullYear() + "年"
                 + (dateObj.getMonth() + 1) + "月"
                 + dateObj.getDate() + "日";
     }

//カレンダーを表示すめための table タグを出力する
monoesblog.calendarCtrl.prototype.renderGrid =  function (CalendarAreaID)
{
    var area = document.getElementById(CalendarAreaID);
    var captionDiv = document.createElement("div");
    captionDiv.setAttribute("class", "caption");
    var captionSpan = document.createElement("span");
    captionSpan.setAttribute("id", CalendarAreaID + "_captionSpan");
    captionSpan.setAttribute("class", "chooseDateCaption");
    captionDiv.appendChild(captionSpan);
    var dateSpan = document.createElement("span");
    dateSpan.setAttribute("id", CalendarAreaID + "_chooseDate");
    dateSpan.setAttribute("class", "chooseDateCaption");
    captionDiv.appendChild(dateSpan);
    area.appendChild(captionDiv);

    var CalendarGrid =  document.createElement("table");
    CalendarGrid.setAttribute("tabindex", "0");
    var navTR = document.createElement("tr");
    var prvTD = document.createElement("td");
    prvTD.setAttribute("id", CalendarAreaID + "_f_prv");
    prvTD.setAttribute("tabindex", "0");
    prvTD.innerHTML = "&lt;&lt;";
    navTR.appendChild(prvTD);

    var monthDiv =  document.createElement("td");
    monthDiv.setAttribute("id", CalendarAreaID + "_fMonth");
    monthDiv.setAttribute("colspan", "5");
    monthDiv.setAttribute("class", "currentMonthCaption");
    navTR.appendChild(monthDiv);

    var nextTD = document.createElement("td");
    nextTD.setAttribute("id", CalendarAreaID + "_f_next");
    nextTD.setAttribute("tabindex", "0");
    nextTD.innerHTML = "&gt;&gt;";
    navTR.appendChild(nextTD);

    CalendarGrid.appendChild(navTR);
    var weekTR = document.createElement("tr");
    var dayOfWeekArray = ["日", "月", "火", "水", "木", "金", "土"];
   
    for (var i = 0; i < 7; i++) {
        var weekTD = document.createElement("td");
        weekTD.setAttribute("class", "dWeek");
        weekTD.innerText = dayOfWeekArray[i];
        weekTR.appendChild(weekTD);
     }
     CalendarGrid.appendChild(weekTR);
     var cellIndex = 1;

     for (var i = 0; i < 6; i++)
    {
          var dayTR = document.createElement("tr");
          if(i==5){
    dayTR.setAttribute("id", CalendarAreaID + "_fLastWeek");
          }
          for (var i2 = 0; i2 < 7; i2++)
          {
    var dayTD = document.createElement("td");
                dayTD.setAttribute("id", CalendarAreaID + "_fDay" + cellIndex);
    dayTD.setAttribute("class", "dayCell");
                dayTD.innerText = cellIndex;
                dayTR.appendChild(dayTD);
                cellIndex++;
          }   
         CalendarGrid.appendChild(dayTR);
     }
     area.appendChild(CalendarGrid);
}

(sample_calendar.js のソース)

 

ずいぶん長いコードに見えますが、js ファイルに保存してしまえば以下のようにわずか一行です。

<script type="text/javascript" src="js/sample_calendar.js" ></script>

 

どうぞ、前述のコードをテキストエディタに貼り付け、sample_calendar.js という名前で保存してご使用ください。

念のためにダウンロードもできるようにしましたので、テキストエディタを持っていない、という方はどうぞ。

 

以降はこのサンプルのカレンダーコントロールの使い方について紹介します。

 

サンプルのカレンダーコントロールの使い方

サンプルのカレンダーコントロールのコンストラクタの書式は以下の通りです。

calendarCtrl(ID,Date,Caption,Handler)

 

引数の内訳は以下の通りです。

  1. ID :  カレンダーを出現させる <div> の id 属性 (文字列)
  2. Date : カレンダーが描画する日付 (Date オブジェクト)
  3. Caption : 選択された日付の横に表示される文字 (文字列)
  4. Handler : 日付セルがクリックされたときのハンドラ (関数)

引数 3, 4 は省略することができます。

 

基本的な使い方

もっとも単純な呼び出し方は以下の通りです。

var calendar = new monoesblog.calendarCtrl("ElementID", new Date());

上記の記述によって、以下のコントロールが生成されます。実際にマウスでクリックして操作できますので、試してみてください。

[Tab]キーでの日付のフォーカス移動や、[Enter]キーでの日付の選択もサポートしていますので、こちらもお試しください。

このブログのシステムでは <style> タグが使用できず、表示がイマイチな感じですが以下のように CSS を定義することで、そこそこちゃんとした表示になってくれます。

.dayCell{
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 20px;
        color:black;
    }

.caption {
        font-size: 20px;
        color: white;
    }

.currentMonthCaption {
    font-size : 20px;
    text-align:center;
    }

.dWeek {
        background-color: navy;
        color: white;
        font-size: 15px;
        text-align: center;
        height: 20px;
    }

.chooseDateCaption{
       color:navy;
}

上記 CSS を適用したのが以下の画像です。

image(カレンダーコントロールに CSS を適用したところ)

なお、CSS は用途に合わせて自由に書き換えていただければと思います。

 

キャプションとコールバック関数の指定

サンプルカレンダークラスのコンストラクタの第 3 引数に文字列を指定することでキャプションが、第 4 引数に関数を指定することで、日付セルをクリックした際のコールバック関数を指定できます。

このコールバックには引数を一つ設けることがき、この引数には選択された日付の Date オブジェクトが返ります。

たとえば、以下のように記述すると、日付のセルをクリックした際に alert ボックスに日付の内容が表示されます。

(function()
{
    onload = function() {
         var calendar = new monoesblog.calendarCtrl("calenderTable", new Date(),"",myCallBack);
    }

   //コールバック関数
    function myCallBack(chooseDate)
    {
        alert(chooseDate.toString() + "のセルがクリックされましたよ。");
    }
})()

全体的なページの記述例は以下のようになります。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
   <title></title>
<style type="text/css">
   .dayCell{
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 20px;
        color:black;
    }

   .caption {
        font-size: 20px;
        color: white;
    }

   .dWeek {
        background-color: navy;
        color: white;
        font-size: 15px;
        text-align: center;
        height: 20px;
    }

   .chooseDateCaption{
       color:navy;
   }
</style>
<script type="text/javascript" src="js/sample_calendar.js" ></script>
<script type="text/javascript">
(function()
{
    onload = function() {
         var calendar = new calendarCtrl("calenderTable", new Date(),"選択された日付",myCallBack);
    }

    function myCallBack(chooseDate)
    {
        alert(chooseDate.toString() + "のセルがクリックされましたよ。");
    }
})()
</script>
</head>
<body>

<!-- カレンダーを表示する場所 -->
<div id="calenderTable"></div>

</body>
</html>

 

公開されている関数

サンプルのカレンダークラスは、いくつかの関数を公開しています。カレンダークラスのインスタンスを生成後、これらを呼び出して使用することができます。

具体的には以下のような関数が用意されています。

 

関数 : getChooseDate

機能 : 描画されたカレンダーで、現在選択されている Date オブジェクトを返す

引数 : なし

書式 : インスタンス.getChooseDate();

(※) chooseDate プロパティを参照しても 現在選択されている Date オブジェクトを取得できます。

 

関数 : calcDate

機能 : 第 1 引数に指定された Date オブジェクトに対し、第 2 引数で指定された日数の Date オブジェクトを返す

書式 : インスタンス.calcDate(Date オブジェクト,数字);

例 : 本日から 2 日後の Date オブジェクトを取得する

var 2DaysLater = myCalendar.calcDate(new Date(),2);

 

関数 : formatDate_JP

機能 : 引数に指定された Date オブジェクトを YYYY年M月D日 にフォーマットされた文字列を返す

書式 : インスタンス.formatDate_JP(Date オブジェクト)

 

サンプルのカレンダークラスがグローバルな名前空間で使用している変数名

サンプルのカレンダーコントロールがグローバルな名前空間で使用している名前は monoesblog のみです。

これは任意のものに変えてしまっても構いません。

サンプルのカレンダーコントロールが使用している変数は、クラス (function) 内のスコープに完全に閉じられ、メソッド類は匿名関数で定義し、prototype で追加してあります。

 

Firefox での使用

サンプルのカレンダーコントロールは、最新の Internet Explorer と Google Chrome で動作しますが、FireFox ではカレンダーの文字が表示されません。

これは、サンプルのカレンダーコントロールが、カレンダーに日付データを流し込む際に使用しているエレメントの innerText プロパティが、FireFox ではサポートされていないためです。

FireFox では代わりに textContent プロパティがサポートされているので、これに innerText を置換してしまうか、他の Web ブラウザーでの使用も考慮し innerHTML (※) にするかしてください。

(※) なぜ初めから innerHTML にしておかないかというと、もともとこのコントロールは Windows ストア アプリ用に作ったものであり、また、文字列しか設定しないのに innerHTML を使用するのはなんとなく美しくないような気がしたからです。ようするに気分の問題です。

 

WinJS を使用した Windows ストア アプリ専用コントロールの作成

今回の記事では標準的な HTML + JavaScript を使用した Web ページでも使用できるコントロールについて紹介しましたが、Windows ストア アプリでは、そのフレームワークとして用意されている WinJS を使用してカスタムコントロールを作成することができます。

これにつきましては以下のドキュメントをご覧ください。

 

JavaScript 用 Windows ライブラリ (WinJS) を使ってカスタム コントロールを構築する
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/10/16/javascript-windows-winjs.aspx

 

★おしらせ★

前回の記事で紹介したテキストエディタを使用して、今回のクラスを作成してみましたが、使用しているうちに様々な不具合が出ましたのでそれらを修正し、若干の機能追加をしたものを再アップします。

詳細については本日 (2013/01/18) の 17:00 以降に以下の記事をご覧ください。

 

気軽に Web コンテンツを書くためのエディタ
http://blogs.msdn.com/b/osamum/archive/2013/01/10/web-editor-of-osamum.aspx

 

【2013/01/21 追記】

複数インスタンスが作れない問題を回避する修正を行いました。

 

【2013/01/23 追記】

クラス名の calendarCtrl はありがちなので、その上に名前空間として monoesblog を設けました。

プロパティ回りの処理を見直してコード量とメモリ使用量を減らしました。

 

Real Time Analytics

Clicky

Comments (0)

Skip to main content