Ajax サンプル カレンダー・コントロール

カレンダー・コントロール

これまでのサンプルが示すように、Ajax を利用すれば
画面遷移することなく通信を行い画面情報を更新することができます。
画面遷移が無ければ、その分ストレスをユーザーに感じさせることのない快適な操作性を提供することが
できます。
現在では Ajax の普及により、より高い操作性や快適さがインターフェイスに求められます。
高機能ながら誰にでも直感的な「使って楽しい」と感じられるインターフェイス。
よりよく Ajax を活用するために、ユーザーインターフェイスに関してのサンプルをご紹介します。

入力画面や照会画面などで日付を入力する機会は多くあります。 このときマウス操作で入力できれば、毎回手入力するわずらわしさを
軽減することができます。 そこで役に立つのが本項でご紹介するカレンダーコントロールです。
カレンダーのボタンをクリックすると入力フィールドの下にカレンダーが表示され、日付をクリックするとその日付がフォームへ
入力されるというものです。

WebではJavascriptでカレンダーコントロールを作るのが主流で、 「Javascript カレンダーコントロール」などのキーワードでWEB検索すると
多数のサンプルライブラリなどが公開されています。 今回は、弊社で作成したカレンダーコントロールをサンプルにご紹介します。

それでは次のサンプルをご覧下さい。

■ 初期画面

サンプル画面

■ HTML サンプルソース
   <HTML>
   <HEAD>
   <meta http-equiv="Content-Language" content="ja">
   <meta http-equiv="Content-Type" content="text/html; charset=Shift-JIS">
   <title>カレンダーコントロール サンプル 納品履歴照会</title>
   <style type="text/css">
   *{margin:0px;padding:0px;}
   body{margin:0px;margin-top:10px;padding:0px;text-align:center;}
   h1{font-size:19pt;color:#666;}
   HR{height:1px;filter:Alpha(opacity=100,finishOpacity=0,style=1,startX=90,finishX=100) Alpha(opacity=0,finishOpacity=100,style=1,startX=0,finishX=10);}
   table.main{border-collapse:collapse;font-size:10pt;margin:10px 0px;}
   table.main th{font-weight:normal;border:1px solid #ccc;height:30px;padding:1px 15px;}
   table.main td{height:25px;}
   table.main input{border:1px solid #666;height:20px;}
   div.container{text-align:left;font-size:10pt;padding:5px 20px;}
   #RESULT{height:350px;overflow-y:scroll;}
   span.explain{font-size:9pt;color:#999;}
   div.explain2{font-size:9pt;color:#999;margin-top:175px;}
   table.REP{font-size:10pt;border-collapse:collapse;border:1px solid #ddd;text-align:center;}
   tr.REP_HEADER td{height:30px;color:#145180;background:#cde9ff;font-weight:bold;}
   tr.REP_TR td{height:20px;padding:1px 4px;}
   tr.ODD td{background:#dee4ec;}
   tr.EVN td{background:#fff;}
   table.REP_BODY{font-size:10pt;border-collapse:collapse;}
   .hand{cursor:hand;}
   .hand2{height:20px;border:1px solid #999;padding:2px;cursor:hand;filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#EEEEEE, EndColorStr=#AAAAAA);}
   .inptCont{width:600px;text-align:left;}
   </style>
   <script type="text/javascript">
   function Search(){
     var formobj=document.forms["frmDsphead"];
     var datefrom=formobj.elements["DTFROM"].value;
     var dateto=formobj.elements["DTTO"].value;
     var kikan=datefrom.replace(/\//g,"")+dateto.replace(/\//g,"");
     var requrl=kikan+".XML";
     requrl+="?DTFROM="+datefrom+"&DTTO="+dateto+"&dummydate="+new Date().getTime();
 D   new Ajax.Request(requrl,{method:"get",onComplete:setRireki,on404:err});
   }
 E function setRireki(httpObj){
     var obj=httpObj.responseXML.documentElement;
     var dta=obj.getElementsByTagName("DATA");
     if(dta!=null){
       var tblbuf='<table class="REP_BODY"><colgroup><col width="100"><col width="100"><col width="150"><col width="100"><col width="40"><col width="120"></colgroup>';
       for(var i=0;i<dta.length;i++){
         var ndate=dta[i].getElementsByTagName("NODT")(0).firstChild.nodeValue;
         var ncode=dta[i].getElementsByTagName("NOSHCOD")(0).firstChild.nodeValue;
         var nname=dta[i].getElementsByTagName("NOSHNAM")(0).firstChild.nodeValue;
         var ntank=dta[i].getElementsByTagName("NOSHTANK")(0).firstChild.nodeValue;
         var nsu=dta[i].getElementsByTagName("NOSURYO")(0).firstChild.nodeValue;
         tblbuf+="<tr class=\"REP_TR "
         tblbuf+=(i%2==0)?"EVN":"ODD";
         tblbuf+="\">";
         tblbuf+="<td>"+ndate+"</td>"
         tblbuf+="<td>"+ncode+"</td>"
         tblbuf+="<td>"+nname+"</td>"
         tblbuf+="<td align=\"right\">"+ntank+"</td>"
         tblbuf+="<td align=\"right\">"+nsu+"</td>"
         var m_su=Number(nsu.replace(/,/,""));
         var m_tank=Number(ntank.replace(/,/,""));
         var m_goukei=addFigure(m_su*m_tank);
         tblbuf+="<td align=\"right\">"+m_goukei+"</td>"
         tblbuf+="</tr>";
       }
       tblbuf+="</table>"
       document.getElementById("RESULT").innerHTML=tblbuf;
     }else{
       document.getElementById("RESULT").innerHTML="<div class=\"explain2\">該当データはありません。</div>"
     }
   }
   
   function err(){
     document.getElementById("RESULT").innerHTML="<div class=\"explain2\">該当データはありません。</div>"
   }
   
   function addFigure(str) {
   var num = new String(str).replace(/,/g, "");
   while(num != (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
   return num;
   }
   </script>
 @ <script type="text/javascript" src="CALENDAR.JS"></script>
   <script type="text/javascript" src="PROTOTYPE.JS"></script>
   </HEAD>
   <BODY>
   <H1>納品履歴照会</H1>
   <div class="container">
   <hr>
   <center>
   <div class="inptCont">
     日付を入力して検索ボタンをクリックしてください。
     <form action="" method="GET" onsubmit="return false;" name="frmDsphead">
       <table class="MAIN">
         <td>日付</td>
           <td>
 A         :<input type="text" name="DTFROM" id="DTFROM"><img src="./IC_CALENDAR.gif" name="CALENDAR_BTN" id="DTFROM_CALENDAR_BTN" align="absmiddle" class="hand">
            〜<input type="text" name="DTTO" id="DTTO"><img src="./IC_CALENDAR.gif" name="CALENDAR_BTN" id="DTTO_CALENDAR_BTN" align="absmiddle" class="hand">          <span class="explain"> 該当データ : 2010/02/01〜2010/02/26</span>
           </td>
         </tr>
       </table>
   </div>
   </center>
     </form>
   <center>
     <input type="button" value="検索" onclick="Search()" class="hand hand2">
   </center>
     <HR>
     <table class="REP" align="center">
       <colgroup>
         <col width="100">
         <col width="100">
         <col width="150">
         <col width="100">
         <col width="40">
         <col width="120">
         <col width="16">
       </colgroup>
       <tr class="REP_HEADER">
         <td>日付</td>
         <td>商品コード</td>
         <td>商品名</td>
         <td>単価</td>
         <td>数量</td>
         <td>金額</td>
         <td> </td>
       </tr>
       <tr>
         <td colspan="7">
           <div id="RESULT"></div>
         </td>
       </tr>
     </table>
   <hr>
   </div>
   <input type="button" value="閉じる" onclick="window.opener=true;window.close()" class="hand hand2">
   </BODY>
   </HTML>


■ JavaScript サンプルソース
  function Calendar(){
    this.target="";
    this.targetcont;
    this.delimiter="/";// 年、月、日の区切り文字を設定します。
    this.currentyear="";
    this.stylesheeturl="./CALENDAR.CSS"; // CALENDAR.CSS のURLを設定します。
B    this.holiday={ //国民の祝日
      "1/1":"元旦"
      ,"2/11":"建国記念の日"
      ,"3/21":"春分の日"
      ,"4/29":"昭和の日"
      ,"5/3":"憲法記念日"
      ,"5/4":"みどりの日"
      ,"5/5":"こどもの日"
      ,"9/23":"秋分の日"
      ,"11/3":"文化の日"
      ,"11/23":"勤労感謝の日"
      ,"12/23":"天皇誕生日"
    }
C    this.happymonday={ //ハッピーマンデー  月:週 で記述します。
      "1:2":"成人の日"
      ,"7:3":"海の日"
      ,"9:3":"敬老の日"
      ,"10:2":"体育の日"
    }
    this.yy="";
    this.mm="";
    this.dd="";
    this.byy="";
    this.bmm="";
    this.bdd="";
    this.date="";
    this.lastday=[31,28,31,30,31,30,31,31,30,31,30,31];
    this.TBLTEMP1='<table id="[_TGT_]_CAL_TBL" class="CALENDAR_TABLE"><tr class="CAL_FNC_HED"><td onclick="cal.PrvMonth(\'[_TGT_]\',\'[_PYY_]/[_PM_]\')"><<</td><td colspan="5"><select onchange="cal.SelYear(\'[_TGT_]\',this.value)">[_YYCMB_]</select><select onchange="cal.SelMonth(\'[_TGT_]\',this.value)">[_MMCMB_]</select></td><td onclick="cal.NxtMonth(\'[_TGT_]\',\'[_NYY_]/[_NM_]\')">>></td></tr>'
    this.TBLTEMP2='<tr><th class="SUN">日</th><th class="DAY">月</th><th class="DAY">火</th><th class="DAY">水</th><th class="DAY">木</th><th class="DAY">金</th><th class="SAT">土</th></tr>'
    this.TBLTEMP4='</table>'
  }

  Calendar.prototype={
  //初期処理ボタンと入力フィールドにイベント付与
    Init:function(arg){
      for(var i=0;i<arg.length;i++){
        var elmid=arg[i]
        var inpid=arg[i].replace("_CALENDAR_BTN","");
        if(document.getElementById(elmid)){
          observe(document.getElementById(elmid),'click',cal.Toggle_Cal_Btn);
        }
        observe(document.getElementById(inpid),'click',cal.Toggle_Cal);
      }
    },
  //その月の1日目の情報取得
    getFirstDate:function(){
      var ndt=new Date(cal.currentyear);
      ndt.setDate(1);
      cal.yy=ndt.getFullYear();
      if((cal.yy%4==0&&cal.yy%100!=0)||cal.yy%400==0){cal.lastday[1]=29}else{cal.lastday[1]=28}
      cal.mm=ndt.getMonth();
      cal.dd=ndt.getDate();
      cal.date=ndt.getDay();
    }
  //カレンダーの表示、非表示(入力フィールドからのクリックイベント)
    ,Toggle_Cal:function(id){
      if(!IE){cal.target=(id==null)?id:event.target.id;}//IE以外
      else{cal.target=(id==null)?id:event.srcElement.id;}//IE
      var calcont=document.getElementById(cal.target+'_CAL_CONT');

      var opp_cont=(cal.target.match('FROM'))?'DTTO_CAL_CONT':'DTFROM_CAL_CONT';
      if(document.getElementById(opp_cont))document.getElementById(opp_cont).parentNode.removeChild(document.getElementById(opp_cont));

      if(calcont==null){
        cal.currentyear=document.getElementById(cal.target).value;
        if(cal.currentyear=="" || isNaN(new Date(cal.currentyear).getTime())){
          var dt=new Date()
          cal.currentyear=dt.getFullYear()+"/"+(dt.getMonth()+1)+"/"+dt.getDate()
        }
        cal.getFirstDate();
        cal.CreateCalTbl();
      }else{
        calcont.parentNode.removeChild(calcont);
      }
    },
  //カレンダーの表示、非表示(ボタンからのクリックイベント)
    Toggle_Cal_Btn:function(){
      if(!IE){var btnid=event.target.id;}//IE以外
      else{var btnid=event.srcElement.id;}//IE
      cal.target=btnid.replace("_CALENDAR_BTN","");
      var calcont=document.getElementById(cal.target+'_CAL_CONT');

      var opp_cont=(cal.target.match('FROM'))?'DTTO_CAL_CONT':'DTFROM_CAL_CONT';
      if(document.getElementById(opp_cont))document.getElementById(opp_cont).parentNode.removeChild(document.getElementById(opp_cont));

      if(calcont==null){
        cal.currentyear=document.getElementById(cal.target).value;
        if(cal.currentyear=="" || isNaN(new Date(cal.currentyear).getTime())){
          var dt=new Date()
          cal.currentyear=dt.getFullYear()+"/"+(dt.getMonth()+1)+"/"+dt.getDate()
        }
        cal.getFirstDate();
        cal.CreateCalTbl();
      }else{
        calcont.parentNode.removeChild(calcont);
      }
    }
  //カレンダーのテーブル作成、表示
    ,CreateCalTbl:function(){
      var bdt=document.getElementById(cal.target).value;
      bdt=bdt.split(cal.delimiter);
      cal.byy=bdt[0];
      cal.bmm=bdt[1];
      cal.bdd=bdt[2];
      var smm=(cal.mm+1<10)?"0"+String(cal.mm+1):String(cal.mm+1);
      var yycmbbuf="";
      var mmcmbbuf="";

      //safari用 PrvMonth引数生成
      var s_nyy=cal.yy;
      var s_pyy=cal.yy;
      var s_pmm=cal.mm;
      var s_nmm=cal.mm+2;
      if(s_pmm==0){s_pyy--;s_pmm=12;}
      if(s_nmm==13){s_nyy++;s_nmm=1;}

      for(var i=cal.yy-5;i<cal.yy+5;i++){
        yycmbbuf+=(i==cal.yy)?"<option value=\""+i+"/"+smm+"\" selected>"+i+"年</option>":"<option value=\""+i+"/"+smm+"\">"+i+"年</option>";
      }
      for(var i=1;i<=12;i++){
      mmcmbbuf+=(i==cal.mm+1)?"<option value=\""+cal.yy+"/"+i+"\" selected>"+i+"月</option>":"<option value=\""+cal.yy+"/"+i+"\">"+i+"月</option>"
      }
      var tblbuf="";
      tblbuf+=cal.TBLTEMP1.replace(/\[_TGT_\]/g,cal.target).replace(/\[_PYY_\]/g,s_pyy).replace(/\[_NYY_\]/g,s_nyy).replace(/\[_MM_\]/g,smm).replace("[_PM_\]",s_pmm).replace("[_NM_\]",s_nmm).replace("[_YYCMB_]",yycmbbuf).replace("[_MMCMB_]",mmcmbbuf);
      tblbuf+=cal.TBLTEMP2;
      var dcnt=0;
      for(var i=0;i<6;i++){
        tblbuf+="<tr>"
        for(var n=0;n<7;n++){
          if((i*7)+n+1>cal.date)dcnt++;
          if(dcnt>0&&dcnt<=cal.lastday[cal.mm]){
            var tdy=new Date();
            var sdd=(dcnt<10)?"0"+String(dcnt):String(dcnt);
            var tdbuf='<td class="[_STYLE_]" onclick="cal.SetDate(\''+cal.target+'\',\''+cal.yy+cal.delimiter+smm+cal.delimiter+sdd+'\')" [_TITLE_]>';
            var td_class="";
            if(dcnt==cal.bdd&&cal.mm+1==cal.bmm&&cal.yy==cal.byy){//選択日
              td_class+="TOD "
            }
            if(n==0){//日曜
              tdbuf=tdbuf.replace("[_TITLE_]","")
              td_class+="SUN "
            }else if(n==6){//土曜
              tdbuf=tdbuf.replace("[_TITLE_]","")
              td_class+="SAT "
            }else if(n==1&&cal.happymonday[(cal.mm+1)+":"+Math.ceil(dcnt/7)]!=null){
              var hm=cal.mm+1;
              tdbuf=tdbuf.replace("[_TITLE_]","title=\""+cal.happymonday[hm+":"+Math.ceil(dcnt/7)]+"\"")
              td_class+="HOL "
            }else if(cal.holiday[(cal.mm+1)+"/"+dcnt]!=null){//祝日
              tdbuf=tdbuf.replace("[_TITLE_]","title=\""+cal.holiday[(cal.mm+1)+"/"+dcnt]+"\"")
              td_class+="HOL "
            }else{//それ以外
              tdbuf=tdbuf.replace("[_TITLE_]","")
              td_class+="DAY "
            }
            tdbuf=tdbuf.replace("[_STYLE_]",td_class);
            tblbuf+=tdbuf;
            tblbuf+=dcnt;
          }else{
            tblbuf+='<td class="NA">';
            tblbuf+="&nbsp;";
          }
          tblbuf+="</td>";
        }
        tblbuf+="</tr>"
        if(dcnt>=cal.lastday[cal.mm])break;
      }
      tblbuf+=cal.TBLTEMP4;
      var cont=document.createElement("DIV");
      cont.className="CALENDAR_CONTAINER";
      cont.id=cal.target+"_CAL_CONT";
      cont.innerHTML=tblbuf;
      cont.style.display="block"
      var bounds = document.getElementById(cal.target).getBoundingClientRect();
      var x = bounds.left-2;
      var y = bounds.top-2;
      cont.style.top=y+document.getElementById(cal.target).offsetHeight+document.body.scrollTop+"px";
      cont.style.left=x+document.body.scrollLeft+"px";
      document.body.insertAdjacentElement("BeforeEnd",cont)
    }
  //曜日クリック、入力フィールドに値を適用
    ,SetDate:function(id,val){
      document.getElementById(id).value=val;
      document.getElementById(id+"_CAL_CONT").parentNode.removeChild(document.getElementById(id+"_CAL_CONT"));
    }
  // << クリック時、先月のカレンダー表示
    ,PrvMonth:function(id,ym){
      cal.target=id
      cal.currentyear=ym+"/1";
      document.getElementById(id+"_CAL_CONT").parentNode.removeChild(document.getElementById(id+"_CAL_CONT"));
      cal.getFirstDate();
      cal.CreateCalTbl();
    }
  // >> クリック時、次月のカレンダー表示
    ,NxtMonth:function(id,ym){
      cal.target=id
      cal.currentyear=ym+"/1";
      document.getElementById(id+"_CAL_CONT").parentNode.removeChild(document.getElementById(id+"_CAL_CONT"));
      cal.getFirstDate();
      cal.CreateCalTbl();
    }
  //年コンボ選択時、年を変更してカレンダー表示
    ,SelYear:function(id,ym){
      cal.target=id
      cal.currentyear=ym+"/1";
      document.getElementById(id+"_CAL_CONT").parentNode.removeChild(document.getElementById(id+"_CAL_CONT"));
      cal.getFirstDate();
      cal.CreateCalTbl();
    }
  //月コンボ選択時、月を変更してカレンダー表示
    ,SelMonth:function(id,ym){
      cal.target=id
      cal.currentyear=ym+"/1";
      document.getElementById(id+"_CAL_CONT").parentNode.removeChild(document.getElementById(id+"_CAL_CONT"));
      cal.getFirstDate();
      cal.CreateCalTbl();
    }
  }
  //カレンダー用エレメントの判断をして初期処理へ
  function InitCalendar(){
    var stl=document.createElement("link");
    stl.rel="stylesheet";
    stl.href=cal.stylesheeturl;
    document.getElementsByTagName("head")[0].insertAdjacentElement("BeforeEnd",stl)
    var elmobj=document.getElementsByName('CALENDAR_BTN');
    var ids=[];
    for(var i=0;i<elmobj.length;i++){
      ids.push(elmobj[i].id)
    }
    cal.Init(ids)
  }
  //イベント(IE firefox safari 共通化)
  function observe(target, type, listener) {
    if (target.addEventListener) target.addEventListener(type, listener, false);
    else if (target.attachEvent) target.attachEvent('on' + type, function() { listener.call(target, window.event); });
    else target['on' + type] = function(e) { listener.call(target, e || window.event); };
  }
  //insertAdjacent(IE firefox safari 共通化)
  if(typeof HTMLElement!="undefined" &&  !HTMLElement.prototype.insertAdjacentElement){
    HTMLElement.prototype.insertAdjacentElement = function(where,parsedNode){
      switch (where.toUpperCase()){
        case 'BEFOREBEGIN':
          this.parentNode.insertBefore(parsedNode,this)
          break;
        case 'BEFOREEND':
          this.appendChild(parsedNode);
          break;
        case 'AFTERBEGIN':
          this.insertBefore(parsedNode,this.firstChild);
          break;
        case 'AFTEREND':
          if (this.nextSibling) this.parentNode.insertBefore(parsedNode,this.nextSibling);
          else this.parentNode.appendChild(parsedNode);
          break;
      }
    }
    HTMLElement.prototype.insertAdjacentHTML = function(where,htmlStr){
      var r = this.ownerDocument.createRange();
      r.setStartBefore(this);
      var parsedHTML = r.createContextualFragment(htmlStr);
      this.insertAdjacentElement(where,parsedHTML)
    }
    HTMLElement.prototype.insertAdjacentText = function(where,txtStr){
      var parsedText = document.createTextNode(txtStr || '')
      this.insertAdjacentElement(where,parsedText)
    }
  };
  //window.event(クロスブラウザ対応)
  var ua = window.navigator.userAgent.toLowerCase();
  var IE = ua.match(/(msie|MSIE)/) || ua.match(/(T|t)rident/);
  if(!IE){
    (function(){
      var events = ["mousedown", "mouseover", "mouseout", "mousemove",
                    "mousedrag", "click", "dblclick"];  
      for (var i = 0; i < events.length; i++){
        window.addEventListener(events[i], function(e){
          window.event = e;
        }, true);
      }
    }());
  };
  //---------------------------
  //カレンダーオブジェクトを生成
  var cal=new Calendar()
  //HTMLの読み込み直後初期化を行う
  if(window.attachEvent){window.attachEvent("onload", InitCalendar);}
  else if(window.addEventListener){window.addEventListener("load", InitCalendar, false);}


サンプルの操作方法

画面上のアイコンをクリックすると、カレンダー・コントロールが表示されます。

カレンダー上にある任意の日付をクリックすると、クリックした日付がフォーム内へ入力されます。(コンボボックスでの選択も可能です。)
「検索」ボタンをクリックすると結果が表示されます。

サンプルソースのコード説明

■ カレンダーコントロールの設置

まずカレンダーコントロール用のJavaScriptファイル:CALENDAR.JS を読み込むために、
HTMLへ @ のように記述します。

<script type="text/javascript" src="(外部Javascriptファイルの保存先/CALENDAR.JS)"></script>

続いて、A カレンダーコントロールの表示用ボタンを設置します。

<img src="IMG/IC_CALENDAR.gif" name="(CALENDAR_BTN(固定値))" id="(<input>タグのid+_CALENDAR_BTN)">
属性内容
name属性CALENDAR_BTN(固定値)カレンダーボタンであることを定義します。
id属性<input>タグのid+_CALENDAR_BTN指定したidを持つ入力フィールドのカレンダーボタンで
あることを定義します。

この二つの記述だけで、簡単にカレンダーコントロールを設置することができます。
編集した HTML を読み込むと、CALENDAR.JS によって onloadハンドラ:InitCalendar が埋め込まれ、実行されます。
InitCalendar が HTML内から A のような形式を持つ <img>タグを自動的に探し出し、セットとなる <input>タグそれぞれに
カレンダーコントロール表示用のイベントハンドラを埋め込みます。

■ 祝祭日のカスタマイズ

カレンダーコントロールのカスタマイズ

カレンダーコントロールでは図のような祝日やハッピーマンデーを独自にカスタマイズ
することができます。

B の配列:this.holiday で祝日
C の配列:this.happymonday でハッッピーマンデー

記述した「祝日名」は指定した日付にマウスオーバーすることで表示されます。

記述方法は次のような形式をとります。

■ this.holiday の記述形式

this.holiday の記述形式

■ this.happymonday の記述形式

this.holiday の記述形式

■ 実行

埋め込まれたカレンダーコントロールの日付をクリックして、フィールドへ値が入力されます。

カレンダーコントロールによって入力された値も手入力した値と違いはありませんので、name=value の形式でリクエストできます。
DAjax.Request によってリクエスト行われ、XMLがレスポンスがされます。
レスポンスされたXMLを E のコールバック関数setRireki でDOM解析し、次のように表示します。

対象データが存在した場合

データが存在した場合の画面表示

対象データが存在しない場合

データが存在しない場合の画面表示