モーダルウィンドウでカレンダークラスを使用して年月日入力

  1. <!DOCTYPE html>
  2. <html lang="ja">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>モーダルウィンドウでカレンダークラスを使用して年月日入力</title>
  6. <script type="text/javascript" src="jquery-1.5.1.min.js"></script>
  7. <script type="text/javascript" src="knockout-2.1.0beta.js"></script>
  8. <script type="text/javascript">
  9.   /* ==============================================================
  10.    カレンダークラス
  11.   ============================================================== */
  12.   var ClassCalendar = function(argY, argM, argD)
  13.   {
  14.     // ===========================================================
  15.     // メソッド:プライベート
  16.     // 指定がなければ今日
  17.     var now = new Date();
  18.     var year  = argY || now.getFullYear();
  19.     var month = argM || now.getMonth() + 1;
  20.     var day   = argD || now.getDate();
  21.     // 週単位で分割した日にち配列
  22.     var weekAry = new Array();
  23.     var fstWeek = 0;  // 週初めの曜日(0:日曜~6:土曜)
  24.     var fill    = "";  // 日にちの入っていないとこにいれる文字
  25.     // 曜日配列
  26.     var weekLabelAry = new Array("日", "月", "火", "水", "木", "金", "土");
  27.     // ===========================================================
  28.     // パブリックメソッド:プロパティを変更
  29.     /**
  30.      * 年月日をdate()からセット
  31.      * dateData 年月日
  32.      */
  33.     this.setYMDForDate = function(dateData)
  34.     {
  35.       year  = dateData.getFullYear();
  36.       month = dateData.getMonth() + 1;
  37.       day   = dateData.getDate();
  38.       weekAry = new Array(); // 対象となる年月日が変わるなら配列は初期化
  39.     }
  40.     /**
  41.      * 年月日のセット
  42.      * argY, argM, argD 年月日
  43.      */
  44.     this.setYMD = function(argY, argM, argD)
  45.     {
  46.       year  = argY;
  47.       month = argM;
  48.       day   = argD;
  49.       weekAry = new Array(); // 対象となる年月日が変わるなら配列は初期化
  50.     }
  51.     /**
  52.      * 指定数だけ前後する
  53.      * num    nヶ月前 or 後ろ(負の値で前の月、正の値で次の月)
  54.      * target 対象を「y(=n年)、m(=nヶ月)、d(=n日)」
  55.      */
  56.     this.setMoveYMD = function(num, target)
  57.     {
  58.       var target = target || "m";
  59.       var moveMonth = addDate(new Date(year, month-1, day), target, num);
  60.       year  = moveMonth.getFullYear();
  61.       month = moveMonth.getMonth() + 1;
  62.       day   = moveMonth.getDate();
  63.       weekAry = new Array(); // 対象となる年月日が変わるなら配列は初期化
  64.     }
  65.     /**
  66.      * 曜日をセット
  67.      * weekKindAry 曜日のラベル配列
  68.      */
  69.     this.setWeekLabel = function(weekKindAry)
  70.     {
  71.       weekLabelAry = weekKindAry;
  72.     }
  73.     /**
  74.      * スタートなる曜日を決める
  75.      * start   スタートとなる曜日を決める(0:日曜~6:土曜)
  76.      */
  77.     this.setStartWeekDay = function(start)
  78.     {
  79.       start = start || 0;
  80.       if (0 <= parseFloat(start) && parseFloat(start) <= 6) {
  81.         fstWeek = start;
  82.       }
  83.       else {
  84.         // 曜日指定が0~6じゃないなら強制的に0
  85.         fstWeek = 0;
  86.       }
  87.       weekAry = new Array(); // スタートなる曜日が変わるなら配列は初期化
  88.     }
  89.     /**
  90.      * 空白日に埋める文字のセット
  91.      * start   スタートとなる曜日を決める(0:日曜~6:土曜)
  92.      */
  93.     this.setFill = function(addChar)
  94.     {
  95.       addChar = addChar || "";
  96.       fill = addChar;
  97.       weekAry = new Array(); // 区切り文字が変わるなら配列は初期化
  98.     }
  99.     
  100.     // ===========================================================
  101.     // パブリックメソッド
  102.     /**
  103.      * 現在の年月日の各値を取得
  104.      * needVal 欲しい値(y,m,d)
  105.      */
  106.     this.getValueForDate = function (needVal)
  107.     {
  108.       var tempVal = "";
  109.       if (needVal == "y") {
  110.         tempVal = year;
  111.       }
  112.       else if (needVal == "m") {
  113.         tempVal = month;
  114.       }
  115.       else {
  116.         tempVal = day;
  117.       }
  118.       return tempVal;
  119.     }
  120.     /**
  121.      * カレンダー用に変換した週の配列を取得:配列だけ欲しいとき用に。
  122.      * addChar 「1日まで」と「月末以降」を埋める文字
  123.      * start   スタートとなる曜日を決める(0:日曜~6:土曜)
  124.      */
  125.     this.getWeekAry = function (addChar, start)
  126.     {
  127.       if (weekAry.length == 0)
  128.       {
  129.         this.setStartWeekDay(start);
  130.         this.setFill(addChar);
  131.         dayAryForCalendar();
  132.       }
  133.       return weekAry;
  134.     }
  135.     /**
  136.      * 年月日の書式(yyyy/mm/dd等)を整えて返す
  137.      * type 年月日の書式
  138.      */
  139.     this.convertDateFormat = function(type)
  140.     {
  141.       type = type || "yyyy/mm/dd";
  142.       // いずれ各書式に対応させる予定
  143.       if (type == "yyyy/mm") {
  144.         tempM = convertNumberDigits(2, month);
  145.         convertDate = year + "/" + tempM;
  146.       }
  147.       else {
  148.         // 未対応記述とyyyy/mm/ddの場合
  149.         tempM = convertNumberDigits(2, month);
  150.         tempD = convertNumberDigits(2, day);
  151.         convertDate = year + "/" + tempM + "/" + tempD;
  152.       }
  153.       return convertDate;
  154.     }
  155.     /**
  156.      * カレンダー用の週配列をtableタグにする
  157.      * weekKindAry 週単位にした日にちの配列
  158.      */
  159.     this.makeTableForCalendar = function(weekKindAry)
  160.     {
  161.       weekKindAry = weekKindAry || weekLabelAry;
  162.       if (weekAry.length == 0) {
  163.         dayAryForCalendar();
  164.       }
  165.       var tableTag = "";
  166.       tableTag += '<table class="calendarTable">';
  167.       // 年月
  168.       tableTag += '<caption>';
  169.       tableTag += year + "年" + month + "月";
  170.       tableTag += '</caption>';
  171.       // 曜日:指定された週初めから曜日を並び替え
  172.       useWeekAry = convertWeekLabelOfCalendar(weekKindAry);
  173.       tableTag += '<tr>';
  174.         for (var i=0; i<useWeekAry.length; i++) {
  175.           tableTag += '<th>';
  176.           tableTag += useWeekAry[i];
  177.           tableTag += '</th>';
  178.         }
  179.       tableTag += '</tr>';
  180.       // 日にち
  181.       var styleHol = "";
  182.       var styleTod = "";
  183.       for (var i=0; i<weekAry.length; i++) {
  184.         tableTag += '<tr>';
  185.         for (var j=0; j<weekAry[i].length; j++) {
  186.           calClass = "";
  187.           classHol = "";
  188.           classTod = "";
  189.           if (weekAry[i][j]["num"] == 0) {
  190.             // 日曜
  191.             classHol = 'calHol';
  192.           }
  193.           if (weekAry[i][j]["day"] == day) {
  194.             // 今日
  195.             classTod = ' calTod';
  196.           }
  197.           if (classHol != "" || classTod != "") {
  198.             calClass = ' class="' + classHol + classTod + '"';
  199.           }
  200.           tableTag += '<td' + calClass + '>';
  201.           tableTag += weekAry[i][j]["day"];
  202.           tableTag += '</td>';
  203.         }
  204.         tableTag += '</tr>';
  205.       }
  206.       tableTag += '</table>';
  207.       return tableTag;
  208.     }
  209.     // ===========================================================
  210.     // プライベートメソッド
  211.     /**
  212.      * カレンダー用の配列を返す
  213.      */
  214.     var dayAryForCalendar = function()
  215.     {
  216.       // 「週初めから1日まで」と「月末から週末まで」を文字を埋める
  217.       var dayAry = convertDayOfCalendar();
  218.       // 1週ずつ分割していく
  219.       weekAry = convertWeekOfCalendar(dayAry);
  220.     }
  221.     // 桁数をそろえるために0を埋める
  222.     var convertNumberDigits = function(digit, num)
  223.     {
  224.       var src = new String(num);
  225.       var cnt = digit - src.length;  // 桁数(digit)から足りない文字数を調査
  226.       if (0 < cnt) {
  227.         while (cnt-- > 0) {
  228.           src = "0" + src;
  229.         }
  230.       }
  231.       return src;
  232.     }
  233.     // 指定された曜日を週初めとして空白日を埋める
  234.     var convertDayOfCalendar = function ()
  235.     {
  236.       // 月初と月末のDate()
  237.       var startMonth = new Date(year, month-1, 1);
  238.       var endMonth   = new Date(year, month, 0);
  239.       // 指定された週初めの曜日から週末の曜日を求めておく
  240.       endEeek = (fstWeek != 0) ? fstWeek - 1: 6;
  241.       // カレンダー用に日にちを配列化:
  242.       var DayArray = new Array();
  243.       // 日曜(指定された週初めの曜日)~1日の空白を埋める。
  244.       if (startMonth.getDay() < fstWeek) {
  245.         diffWeekToFst = 7 - Math.abs(startMonth.getDay() - fstWeek);
  246.       }
  247.       else {
  248.         diffWeekToFst = Math.abs(startMonth.getDay() - fstWeek);
  249.       }
  250.       for (i=0; i<diffWeekToFst; i++) {
  251.         DayArray.push(fill);
  252.       }
  253.       // 月初め(1日)~月末までを埋める
  254.       for (i=0; i<endMonth.getDate(); i++) {
  255.         DayArray.push(i+1);
  256.       }
  257.       // 月末~土曜(指定された週末の曜日)の空白を埋める
  258.       if (endMonth.getDay() != endEeek) {
  259.         // 週初めから何日目か
  260.         if (endMonth.getDay() < fstWeek) {
  261.           diffWeekForEnd = 7 - Math.abs(endMonth.getDay() - fstWeek);
  262.         }
  263.         else {
  264.           diffWeekForEnd = Math.abs(endMonth.getDay() - fstWeek);
  265.         }
  266.         // 週末まで何日あるか
  267.         diffWeekFromEnd = 6 - diffWeekForEnd;
  268.         for (i=0; i<diffWeekFromEnd; i++) {
  269.           DayArray.push(fill);
  270.         }
  271.       }
  272.       return DayArray;
  273.     }
  274.     // 年月日に指定された数値を足して返す
  275.     var addDate = function (ymd, target, add)
  276.     {
  277.       var addNum = parseFloat(add);
  278.       var addY = ymd.getFullYear();
  279.       var addM = ymd.getMonth() + 1;
  280.       var addD = ymd.getDate();
  281.       // ymdが月末のときに使う
  282.       var endMonthDay = new Date(addY , addM, 0).getDate();
  283.       var tempD = addD;
  284.       if(target == "y") {
  285.         addY += addNum;
  286.       }
  287.       else if(target == "m") {
  288.         addM += addNum;
  289.       }
  290.       else if(target == "d") {
  291.         addD += addNum;
  292.       }
  293.       // 月末で、年・月を足したとき:月初めに変更して足し、最後に月末をセットする
  294.       if ((target == "y" || target == "m") && tempD == endMonthDay) {
  295.         // 月初めで年月を移動
  296.         dateNoDay = new Date(addY, addM-1, 1);
  297.         // 移動した年月の月末を取得
  298.         var nextY = dateNoDay.getFullYear();
  299.         var nextM = dateNoDay.getMonth() + 1;
  300.         dateLast = new Date(nextY, nextM, 0).getDate();
  301.         // 移動した年月の月末でDate()に変換
  302.         tempDate = new Date(nextY, nextM-1, dateLast);
  303.       }
  304.       else {
  305.         tempDate = new Date(addY , addM-1, addD);
  306.       }
  307.       return tempDate;
  308.     }
  309.     // カレンダー用に週単位で配列にして返す
  310.     var convertWeekOfCalendar = function(dayAry)
  311.     {
  312.       var ary = new Array();
  313.       for (var i=0, j=0, k=fstWeek; i<dayAry.length; i++, k++) {
  314.         j = parseInt(i/7);
  315.         if (i%7 == 0) {
  316.           ary[j] = new Array();
  317.         }
  318.         if (6 < k) {
  319.           k = 0;
  320.         }
  321.         // kが曜日、dayが日にち
  322.         ary[j].push({"num": k, "day": dayAry[i]});
  323.       }
  324.       return ary;
  325.     }
  326.     // 指定されている週初めに合わせて曜日配列を入れ替える
  327.     var convertWeekLabelOfCalendar = function(weekKindAry)
  328.     {
  329.       var ary = new Array();
  330.       for (var i=0, j=fstWeek; i<7; i++, j++) {
  331.         if (6 < j) {
  332.           j = 0;
  333.         }
  334.         ary.push(weekKindAry[j]);
  335.       }
  336.       return ary;
  337.     }
  338.   }
  339.   
  340.   // カスタムバインディング ===============================================
  341.   // モーダルウィンドウのマスク
  342.   ko.bindingHandlers.koModalMask = {
  343.     update: function(element, valueAccessor, allBindingsAccessor)
  344.     {
  345.       // 現状の値と、サブプロパティ一覧の取得
  346.       var value = valueAccessor(), allBindings = allBindingsAccessor();
  347.       var valueUnwrapped = ko.utils.unwrapObservable(value);
  348.       
  349.       // サブプロパティ:
  350.       var maskBgColor = allBindings.maskBgColor || "#000"; // maskBgColor:マスクの背景色
  351.       var maskOpacity = allBindings.maskOpacity || "0.3";  // maskOpacity:maskOpacityマスクの不透明度
  352.       
  353.       // DOMをいじる
  354.       if (valueUnwrapped == false) {
  355.         $(element).hide();
  356.       }
  357.       else {
  358.         $(element).css({'width':$(window).width(), 'height':$(document).height()});
  359.         $(element).css({'backgroundColor': maskBgColor});
  360.         $(element).css({'position': 'absolute', 'left': '0', 'top': '0'});
  361.         $(element).css({'z-index': '9000'});
  362.         $(element).fadeTo(0, maskOpacity);
  363.       }
  364.     }
  365.   };
  366.   
  367.   // モーダルウィンドウ
  368.   ko.bindingHandlers.koModalWindow = {
  369.     update: function(element, valueAccessor, allBindingsAccessor)
  370.     {
  371.       // 現状の値と、サブプロパティ一覧の取得
  372.       var value = valueAccessor(), allBindings = allBindingsAccessor();
  373.       var valueUnwrapped = ko.utils.unwrapObservable(value);
  374.       
  375.       // サブプロパティ:modalBgColorの値をセット
  376.       var modalBgColor = allBindings.modalBgColor || "#fff";
  377.       
  378.       // DOMをいじる
  379.       if (valueUnwrapped == false) {
  380.         $(element).hide();
  381.       }
  382.       else {
  383.         $(element).css({'backgroundColor': modalBgColor});
  384.         $(element).css({'z-index': '9999'});
  385.         $(element).fadeIn(500);
  386.       }
  387.     }
  388.   };
  389.   
  390.   // knockout.js ===============================================
  391.   function ViewModel() {
  392.     var self = this;
  393.     self.inputData = ko.observable("");
  394.     // モーダルウィンドウで使うヤツ
  395.     self.maskModal = ko.observable(false);    // マスク用
  396.     self.windowModal = ko.observable(false);  // ウィンドウ
  397.     // カレンダーのクラス
  398.     self.calendar = new ClassCalendar();
  399.     // カレンダーで使うヤツ
  400.     var now = new Date();
  401.     self.year = ko.observable(now.getFullYear());
  402.     self.mont = ko.observable(now.getMonth() + 1);
  403.     // カレンダーの年月の表示
  404.     self.valYearMonth = ko.computed( function()
  405.     {
  406.       self.calendar.setYMD(self.year(), self.mont(), 1);
  407.       return self.calendar.convertDateFormat("yyyy/mm");
  408.     }, this);
  409.     // カレンダー用に週ごとに配列化
  410.     self.weekData = ko.computed( function()
  411.     {
  412.       self.calendar.setYMD(self.year(), self.mont(), 1);
  413.       return self.calendar.getWeekAry();
  414.     }, this);
  415.     // clickバインディング:前月・翌月・今月([move]を月に足し引きする)
  416.     self.moveCal = function(move)
  417.     {
  418.       move = move || 0;
  419.       if (move == 0) {
  420.         // 「0」だったら今月にする
  421.         self.calendar.setYMDForDate(new Date());
  422.       }
  423.       else {
  424.         // 「0」じゃないんだったら[move]を月に足し引きする
  425.         self.calendar.setYMD(self.year(), self.mont(), 1);
  426.         self.calendar.setMoveYMD(move);
  427.       }
  428.       
  429.       self.year(self.calendar.getValueForDate("y"));
  430.       self.mont(self.calendar.getValueForDate("m"));
  431.     }
  432.     // clickバインディング: モーダルウィンドウを開く(今月を表示)
  433.     self.openModal = function(data, event)
  434.     {
  435.       var now = new Date();
  436.       self.year(now.getFullYear());
  437.       self.mont(now.getMonth()+1);
  438.       
  439.       self.maskModal(true);
  440.       self.windowModal(true);
  441.     }
  442.     // clickバインディング: モーダルウィンドウを閉じる
  443.     self.closeModal = function(data, event)
  444.     {
  445.       self.maskModal(false);
  446.       self.windowModal(false);
  447.     }
  448.     // クリックされた日にちを入力:日にちの無いトコがクリックされたら何もしない。
  449.     self.addData = function(koData)
  450.     {
  451.       if (koData.day.toString().match(/^[0-9]+$/) != null) {
  452.         self.calendar.setYMD(self.year(), self.mont(), parseFloat(koData.day));
  453.         var ymd = self.calendar.convertDateFormat("yyyy/mm/dd");
  454.         self.inputData(ymd);
  455.         self.maskModal(false);
  456.         self.windowModal(false);
  457.       }
  458.     }
  459.     // 「今日」だけCSS追加
  460.     self.cToday = function(koDataDay)
  461.     {
  462.       // 表示されたカレンダーの日にち:数字以外ならfalse
  463.       var ymd = "";
  464.       if (koDataDay.toString().match(/^[0-9]+$/) != null) {
  465.         self.calendar.setYMD(self.year(), self.mont(), parseFloat(koDataDay));
  466.         ymd = self.calendar.convertDateFormat("yyyy/mm/dd");
  467.       }
  468.       else {
  469.         return false;
  470.       }
  471.       
  472.       // 今日の年月日
  473.       self.calendar.setYMDForDate(new Date());
  474.       var today = self.calendar.convertDateFormat("yyyy/mm/dd");
  475.       
  476.       // 表示されたカレンダーの日にちと今日の年月日が同じかチェック
  477.       if (ymd == today) {
  478.         return true;
  479.       }
  480.       return false;
  481.     }
  482.   }
  483.   $(document).ready(function () {
  484.     ko.applyBindings(new ViewModel());
  485.   });
  486. </script>
  487. <style type="text/css">
  488. .inData
  489. {
  490.   position:absolute;
  491.   top:3px;
  492.   left:80px;
  493. }
  494. .inData:before{
  495.   content: "";
  496.   position: absolute;
  497.   top: 2px;
  498.   left: 0px;
  499.   border-top: 7px solid transparent;
  500.   border-bottom: 7px solid transparent;
  501.   border-right: 11px solid #999;
  502. }
  503. .inDataBox
  504. {
  505.   background:#f0f0f0;
  506.   border:1px solid #999;
  507.   padding:30px 10px 10px;
  508.   margin-left:10px;
  509. }
  510. .closeBtn
  511. {
  512.   position:absolute;
  513.   top:5px;
  514.   right:5px;
  515.   background:#666;
  516.   color:#fff;
  517.   font-weight:bold;
  518.   cursor:pointer;
  519.   font-size:10px;
  520. }
  521. .closeBtn:hover
  522. {
  523.   background:#ff0000;
  524. }
  525. .calendar
  526. {
  527.   border-collapse:collapse;
  528.   margin:0;
  529. }
  530. .calendar th, .calendar td
  531. {
  532.   border:1px solid #ccc;
  533.   padding:3px;
  534.   text-align:center;
  535.   cursor:pointer;
  536.   font-size:10pt;
  537. }
  538. .calendar th
  539. {
  540.   background:#ffddff;
  541. }
  542. .cToday
  543. {
  544.   font-weight:bold;
  545. }
  546. .calendar td:hover
  547. {
  548.   background:#ffddff;
  549. }
  550. .moveCal
  551. {
  552.   color:#0000ff;
  553.   cursor:pointer;
  554. }
  555. .moveCal:hover, .nowCal:hover
  556. {
  557.   color:#ff0000;
  558.   cursor:pointer;
  559. }
  560. .nowCal
  561. {
  562.   position:absolute;
  563.   bottom:0;
  564.   left:3px;
  565.   color:#0000ff;
  566.   cursor:pointer;
  567.   font-size:10pt;
  568. }
  569. </style>
  570. </head>
  571. <body>
  572. <!-- モーダルウィンドウのマスク用 -->
  573. <div data-bind="koModalMask: maskModal, maskOpacity: 0.15, click: closeModal"></div>
  574. <h1>モーダルウィンドウでカレンダークラスを使用して年月日入力</h1>
  575. <div style="position:relative;top:0;left:0;">
  576.   <input type="text" tabindex="1" data-bind="value: inputData" />
  577.   <button data-bind="click: openModal">モーダル</button><br />
  578.   <!-- ▼モーダルウィンドウ内 -->
  579.   <div class="inData" data-bind="koModalWindow: windowModal, modalBgColor: 'transparent'">
  580.     <div class="inDataBox">
  581.       <table class="calendar">
  582.       <caption>
  583.       <div style="position:relative;top:0;left:0;">
  584.       <span class="moveCal" data-bind="click: function(){moveCal(-1);}">&laquo;</span>
  585.       <span data-bind="text: valYearMonth"></span>
  586.       <span class="moveCal" data-bind="click: function(){moveCal(1);}">&raquo;</span>
  587.       <span class="nowCal" data-bind="click: function(){moveCal(0);}">今月</span>
  588.       </div>
  589.       </caption>
  590.       <thead><tr><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr></thead>
  591.       <tbody data-bind="foreach: weekData()">
  592.       <tr data-bind="foreach: $data">
  593.       <td data-bind="text: day, click: $root.addData, css: {'cToday': $root.cToday(day)}, style: num==0 ? {'color': 'red'}: {}"></td>
  594.       </tr>
  595.       </tbody>
  596.       </table>
  597.       <span class="closeBtn" data-bind="event: {click: $root.closeModal }, clickBubble: false">×</span>
  598.     </div>
  599.   </div>
  600.   <!-- ▲モーダルウィンドウ内 -->
  601. </div>
  602. <input type="text" tabindex="2" /><br />
  603. <button tabindex="2" data-bind="click: function(){alert('本来ならココにDBに書き込む処理とかする');}" />登録完了</button><br />
  604. <div style="font-size:10pt;text-align:right;margin-top:0.5em;">
  605. <a href="//tips.recatnap.info/" target="_top">PCスキルの小技・忘却防止メモ</a> -
  606. <a href="//tips.recatnap.info/wiki/" target="_top">PCスキルの小技・忘却防止メモのまとめ(wiki)</a>
  607. </div>
  608. <div style="font-size:10pt;text-align:center;margin-top:0.5em;padding:0.5em;border-top:1px solid #ccc;">
  609. Copyright &copy; 2009 by PCスキルの小技・忘却防止メモ. All rights reserved.
  610. </div>
  611. </body>
  612. </html>