/**
 * JavaScriptでの共通ライブラリ関数を実装したファイルです。
 */

/****************************************
 * 汎用関数
 ****************************************/

/**
 * 指定したIDのエレメントオブジェクトを取得します。
 * ==== 引数
 * id:: エレメントのIDを表す文字列
 * ==== 戻り値
 * エレメントオブジェクト
 * ==== 説明
 * idが文字列のときは、そのIDを持つエレメントオブジェクトを返します。
 * idが文字列以外のときは、idをそのまま返します。
 */
function $( id )
{
  return ( typeof id == "string" ) ? document.getElementById( id ) : id;
}

/**
 * 指定したURLにリダイレクトします。
 * ==== 引数
 * url:: リダイレクト先のURL
 */
function redirect( url )
{
  location.href = url;
}

/**
 * 文字列をURLエンコードします。
 * ==== 説明
 * /、?、&なども含めて、%XX形式にエンコードします。
 *
 * URLのパラメータとして渡す値を個別にエンコードするときに使います。
 * /、?、&もエンコードされてしまうので、URL全体をエンコードすると、不正なURLになります。
 */
function url_encode( str )
{
  return encodeURIComponent( str );
}

/**
 * 指定したオブジェクトのinnerHTMLをセットします。
 * ==== 引数
 * obj:: innerHTMLを書き換えるオブジェクト、またはオブジェクトのID文字列
 * html:: innerHTMLにセットする文字列
 */
function set_html( obj, html )
{
  var target = $(obj);
  if ( target != null ) target.innerHTML = html;
}

/**
 * 指定した<select>タグから、選択されている<option>タグのvalueプロパティを取得します。
 * ==== 引数
 * select:: <select>タグのオブジェクト、またはオブジェクトのID文字列
 * ==== 戻り値
 * 非null:: 選択されている<option>タグのvalueプロパティの値
 * null:: エラー発生
 */
function get_selection( select )
{
  var target = $(select);
  if ( target != null ) {
    var sel_index = target.selectedIndex;
    if ( ( sel_index >= 0 ) && ( sel_index < target.length ) ) {
      var opt = target.options[sel_index];
      if ( opt != null ) return opt.value;
    }
  }
  return null;
}

/**
 * selectオブジェクトが更新されたとき、依存関係にあるselectオブジェクトをリセットし、指定された関数を呼び出します。
 * ==== 引数
 * sel_obj<n>:: 選択状態をリセットするselectオブジェクト、またはオブジェクトのID文字列（複数指定可能）
 * func:: 最後に実行する関数（引数なし）
 * ==== 説明
 * 必ず１つ以上の引数を指定します。
 *
 * sel_obj<n>には、選択状態をリセットするselectオブジェクトまたはそのID文字列を指定します。
 * これらのオブジェクトの選択肢は、先頭のものになります。
 *
 * 最後の引数には、変更後に呼び出す関数を指定します。この関数は、引数なしで呼び出されます。
 * 関数の呼び出しが不要な場合は、最後の引数にはnullを指定します。
 */
function update_selection()
{
  // 引数は１つ以上でなければならない。
  var arglen = update_selection.arguments.length;
  if ( arglen >= 1 ) {
    var func = update_selection.arguments[ arglen - 1 ];
    // selectオブジェクトをリセットする。
    for( var i=0; i<arglen-1; i++ ) {
      var sel_obj = $(update_selection.arguments[i]);
      if ( ( sel_obj != null ) && ( sel_obj.type == "select-one" ) ) sel_obj.selectedIndex = 0;
    }
    if ( func != null ) func();
  }
}

/**
 * 入力項目のチェックを行います。
 * ==== 引数
 * 引数は、3～5個を指定します。
 * ===== 引数が3個の場合
 * obj:: 入力項目のオブジェクト、またはオブジェクトのID文字列
 * func:: チェックに使用する関数名（１引数）
 * message:: チェックに失敗したときのエラーメッセージ文字列
 * ===== 引数が4個の場合
 * obj:: 入力項目のオブジェクト、またはオブジェクトのID文字列
 * func:: チェックに使用する関数名（２引数）
 * par1:: チェック関数の第２引数に渡す値
 * message:: チェックに失敗したときのエラーメッセージ文字列
 * ===== 引数が5個の場合
 * obj:: 入力項目のオブジェクト、またはオブジェクトのID文字列
 * func:: チェックに使用する関数名（３引数）
 * par1:: チェック関数の第２引数に渡す値
 * par2:: チェック関数の第３引数に渡す値
 * message:: チェックに失敗したときのエラーメッセージ文字列
 * ==== 戻り値
 * true:: 入力内容は正しい。
 * false:: 入力内容は誤っている。
 * ==== 説明
 * objで指定されたオブジェクトのvalueプロパティを読み取り、funcで指定された関数で判定します。
 * objが文字列の場合は、IDとみなしてオブジェクトを検索します。それ以外の場合は、オブジェクトとみなします。
 *
 * funcには、１～３引数を取って、ブール値を返す関数を指定します。
 * check関数を３引数で呼び出したときは、funcの引数は１つだけです。
 * check関数を４引数で呼び出したときは、funcの引数は２つです。
 * check関数を５引数で呼び出したときは、funcの引数は３つです。
 * 
 * いずれの場合も、第１引数にはobjのvalueプロパティが渡されます。
 * ４引数の場合は、check関数の第３引数が、funcの第２引数に渡されます。
 * ５引数の場合は、check関数の第４引数が、funcの第３引数に渡されます。
 * 
 * funcがtrueを返した場合は、そのままtrueを返します。
 * funcがfalseを返した場合は、エラーメッセージをalertで表示し、idにフォーカスをセットしてfalseを返します。
 * また、処理の途中でエラーが発生したときも、funcがfalseを返したときと同じ動作をします。
 */
function check()
{
   var obj, func, par1, par2, message;
   
  // 引数が3つならば、１引数のチェック関数を呼び出す。
  // 引数が4つならば、２引数のチェック関数を呼び出す。
  // 引数が5つならば、３引数のチェック関数を呼び出す。
  var arglen = check.arguments.length;
  var result = false;
  
  // １番目をobj、最後から２番目をfunc、最後をmessageとする。
  obj = check.arguments[0];
  func = check.arguments[1];
  message = check.arguments[ arglen - 1 ];
  
  // objが文字列ならば、IDとみなしてElementを取得する。
  var target = $(obj);
  if ( ( target != null ) && ( arglen >= 3 ) && ( arglen <= 5 ) ) {
    // チェック関数を呼び出す。
    if ( arglen == 3 ) result = func( target.value );
    else if ( arglen == 4 ) result = func( target.value, check.arguments[2] );
    else result = func( target.value, check.arguments[2], check.arguments[3] );
    
    // チェック成功ならtrueを返す。
    if ( result ) return true;
  }
  // エラー発生時は、メッセージを表示して、フォーカスをセットしてfalseを返す。
  error( target, message );
  return false;
}

/**
 * エラーメッセージを表示し、指定項目にフォーカスをセットします。
 * 指定項目がテキストボックスであれば、テキストを選択状態にします。
 * ==== 引数
 * obj:: 対象項目のオブジェクト、またはオブジェクトのID文字列
 * message:: 表示するエラーメッセージ文字列
 */
function error( obj, message )
{
  alert( message );
  var target = $(obj);
  if ( target != null ) {
    target.focus();
    if ( ( target.type == "text" ) || ( target.type == "textarea" ) ) target.select();
  }
}

/**
 * テキストボックスでリターンキーが押されたときに、指定された関数を実行します。
 * ==== 引数
 * ev:: eventオブジェクト
 * ret_func:: リターンキーが押されたときに実行する関数
 * ==== 戻り値
 * true:: リターンキー以外が押された。
 * false:: リターンキーが押された。
 * ==== 説明
 * テキストボックスの<input>タグのonKeyPressから、次のように呼び出します。
 *   <input type="text" onKeyPress="return on_retkey( event, my_func );">
 * on_retkey関数の第１引数には、常に"event"と記述します。
 * これは、キー入力イベントを保持しているオブジェクトで、FirefoxではonKeyPressの中で記述する必要があります。
 *
 * my_funcには、リターンキーが押されたときに実行する関数を指定します。
 * この関数は、引数を持たず、戻り値も意味を持ちません。
 *
 * onKeyPressでは、必ずon_retkey関数の戻り値をreturnするようにします。
 * ここでfalseを返すと、押されたキーが入力されません。
 * on_retkey関数では、リターンキーの場合にはfalse、それ以外の場合にはtrueを返します。
 */
function on_retkey( ev, ret_func )
{
  var key;
  if ( window.event ) key = ev.keyCode;
  else key = ev.which;
  if ( key == 13 ) { ret_func(); return false; }
  return true;
}

/****************************************
 * 全角・半角関連
 ****************************************/

/**
 * 半角文字が含まれていることを判定するための正規表現です。
 */
var HANKAKU_CHAR = /[\x20-\x7e]|[｡-ｿ]|[ﾀ-ﾟ]/;

/**
 * 文字列がすべて半角文字であることを判定するための正規表現です。
 */
var HANKAKU_STRING = /^([\x20-\x7e]|[｡-ｿ]|[ﾀ-ﾟ])+$/;

/**
 * 横幅が半角１文字分とみなす文字の正規表現です。
 */
var HANKAKU_WIDTH_CHAR = /[\x00-\x7e]|[｡-ｿ]|[ﾀ-ﾟ]/g;

/**
 * すべて全角文字かどうかをチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * ==== 戻り値
 * true:: 文字列に含まれるのは、すべて全角文字である。
 * false:: 文字列は空か、全角文字以外が含まれている。
 */
function zenkaku( str )
{
  return ( str != "" ) && ( str.match( HANKAKU_CHAR ) == null );
}

/**
 * すべて半角文字かどうかをチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * ==== 戻り値
 * true:: 文字列に含まれるのは、すべて半角文字である。
 * false:: 文字列は空か、半角文字以外が含まれている。
 */
function hankaku( str )
{
  return ( str.match( HANKAKU_STRING ) != null );
}

/**
 * 半角文字が含まれているかどうかをチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * ==== 戻り値
 * true:: 文字列は、１文字以上の半角文字が含まれている。
 * false:: 文字列には、半角文字は含まれていない。
 */
function include_hankaku( str )
{
  return ( str.match( HANKAKU_CHAR ) != null );
}

/**
 * 全角文字が含まれているかどうかをチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * ==== 戻り値
 * true:: 文字列は、１文字以上の全角文字が含まれている。
 * false:: 文字列には、全角文字は含まれていない。
 */
function include_zenkaku( str )
{
  return ( str != "" ) && ( str.match( HANKAKU_STRING ) == null );
}

/**
 * 文字列の幅を半角文字単位で取得します。
 * ==== 引数
 * str:: 幅を求める文字列
 * ==== 戻り値
 * 文字列の幅
 * ==== 説明
 * 文字列の表示上の幅を取得します。
 * 半角文字は1、全角文字は2としてカウントします。
 */
function width( str )
{
  return str.length + str.replace( HANKAKU_WIDTH_CHAR, "" ).length;
}

/**
 * 全角文字数と半角文字数が最大長を超えていないかチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * zen_max:: 全角文字の最大文字数
 * han_max:: 半角文字の最大文字数
 * ==== 戻り値
 * true:: 全角文字・半角文字ともに範囲内。
 * false:: 全角文字・半角文字の片方または両方が最大文字数を超えている。
 */
function check_length( str, zen_max, han_max )
{
  var zen_len = str.replace( HANKAKU_WIDTH_CHAR, "" ).length;
  var han_len = str.length - zen_len;
  return ( zen_len <= zen_max ) && ( han_len <= han_max );
}

/**
 * 文字列の幅が最大長を超えていないかチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * max:: 最大幅（半角単位）
 * ==== 戻り値
 * true:: 範囲内である。
 * false:: 最大幅を超えている。
 */
function check_width( str, max )
{
  return width( str ) <= max;
}

/****************************************
 * 文字列の書式解析
 ****************************************/

/**
 * 空文字列でなければtrue、空文字列ならばfalseを返します。
 * ==== 引数
 * str:: 判定する文字列
 * ==== 戻り値
 * true:: 空文字列ではない。
 * false:: 空文字列である。
 */
function not_empty( str )
{
  return ( str != null ) && ( str != '' );
}

/**
 * 「YYMMDD」形式の日付文字列を解析し、年月日の配列を返します。
 * ==== 引数
 * str:: 「YYMMDD」形式の日付文字列
 * ==== 戻り値
 * (年,月,日)の配列:: 成功
 * null:: 失敗
 * ==== 説明
 * 日付文字列を解析し、年月日の整数値の配列を返します。
 *
 * 先頭と末尾の空白は無視されます。
 * 書式が不正な場合や、日付が実在しない場合は、解析失敗としてnullを返します。
 */
function parse_yymmdd( str )
{
  var date_array = null;
  if ( str.match( /^\s*([0-9]{2})([0-9]{2})([0-9]{2})\s*$/ ) != null ) {
    date_array = Array( parseInt( "20" + RegExp.$1, 10 ), parseInt( RegExp.$2, 10 ), parseInt( RegExp.$3, 10 ) );
    if ( !validate_date( date_array[0], date_array[1], date_array[2] ) ) date_array = null;
  }
  return date_array;
}

/**
 * 「YYYYMM」形式の日付文字列を解析し、年月日の配列を返します。
 * ==== 引数
 * str:: 「YYYYMM」形式の日付文字列
 * ==== 戻り値
 * (年,月,日)の配列:: 成功
 * null:: 失敗
 * ==== 説明
 * 日付文字列を解析し、年月日の整数値の配列を返します。日は、常に1になります。
 *
 * 先頭と末尾の空白は無視されます。
 * 書式が不正な場合や、日付が実在しない場合は、解析失敗としてnullを返します。
 */
function parse_year_month( str )
{
  var date_array = null;
  if ( str.match( /^\s*([0-9]{4})([0-9]{2})\s*$/ ) != null ) {
    date_array = Array( parseInt( RegExp.$1, 10 ), parseInt( RegExp.$2, 10 ), 1 );
    if ( !validate_date( date_array[0], date_array[1], date_array[2] ) ) date_array = null;
  }
  return date_array;
}

/**
 * 「HHMM」形式の時刻文字列を解析し、時分の配列を返します。
 * ==== 引数
 * str:: 「HHMM」形式の時刻文字列
 * ==== 戻り値
 * (時,分)の配列:: 成功
 * null:: 失敗
 * ==== 説明
 * 時刻文字列を解析し、時分の整数値の配列を返します。
 *
 * 先頭と末尾の空白は無視されます。
 * 書式が不正な場合や、時が0～23以外、または分が0～59以外のときは、解析失敗としてnullを返します。
 */
function parse_hhmm( str )
{
  var time_array = null;
  if ( str.match( /^\s*([0-9]{2})([0-9]{2})\s*$/ ) != null ) {
    time_array = Array( parseInt( RegExp.$1, 10 ), parseInt( RegExp.$2, 10 ) );
    if ( ( time_array[0] < 0 ) || ( time_array[0] > 23 ) || ( time_array[1] < 0 ) || ( time_array[1] > 59 ) ) time_array = null;
  }
  return time_array;
}

/**
 * 換算率の文字列を解析し、実数値を返します。
 * ==== 引数
 * str:: 換算率の文字列
 * ==== 戻り値
 * 換算率の実数値:: 成功
 * NaN:: 失敗
 * ==== 説明
 * 換算率の文字列を解析し、実数値に変換して返します。
 * 整数部分が1～3桁、小数部分が0～2桁でなければなりません。
 *
 * 先頭と末尾の空白は無視されます。
 * 書式が不正な場合は、解析失敗としてNaN（Not A Number）を返します。
 */
function parse_rate( str )
{
  var rate = Number.NaN;
  if ( ( str.match( /^\s*([0-9]{1,3})\s*$/ ) != null ) || ( str.match( /^\s*([0-9]{1,3}\.[0-9]{0,2})\s*$/ ) != null ) ) {
    rate = parseFloat( RegExp.$1 );
  }
  return rate;
}

/**
 * URLとして正しいかどうかをチェックします。
 * ==== 引数
 * str:: チェックする文字列
 * ==== 戻り値
 * true:: URLとして正しい。
 * false:: URLとして正しくない。
 * ==== 説明
 * URLとしての書式チェックを行います。
 * ただし、厳密なチェックは複雑なため、"http://"または"https://"で始まっていれば正しい、と判定します。
 * サーバ側で厳密なチェックを行うことにします。
 */
function check_url( str )
{
  return ( str.match( /^\s*https?:\/\//i ) != null );
}

/**
 * 整数値の範囲チェックを行います。
 * ==== 引数
 * value:: チェックする値
 * min:: 許容する最小値（最小値のチェックを行わないときはnull）
 * max:: 許容する最大値（最大値のチェックを行わないときはnull）
 * ==== 戻り値
 * true:: valueは整数値であり、範囲内である。
 * false:: valueは整数値でないか、範囲外である。
 */
function check_integer( value, min, max )
{
  if ( !( '' + value ).match( /^\s*[0-9]+\s*$/ ) ) return false;
  var intval = parseInt( '' + value, 10 );
  if ( isNaN( intval ) ) return false;
  if ( ( min != null ) && ( intval < min ) ) return false;
  if ( ( max != null ) && ( intval > max ) ) return false;
  return true;
}

/****************************************
 * 日付・時刻関連
 ****************************************/

/**
 * 閏年判定を行います。
 * ==== 引数
 * year:: 年
 * ==== 戻り値
 * true:: 閏年である。
 * false:: 閏年ではない。
 * ==== 説明
 * 年が4で割り切れれば閏年です。
 * ただし、100で割り切れるときは閏年ではありません。
 * ただし、400で割り切れるときは閏年です。
 */
function leap_year( year )
{
  return ( ( ( year % 4 ) == 0 ) && ( ( year % 100 ) != 0 ) ) || ( ( year % 400 ) == 0 );
}

/**
 * 日付が実在するかどうかを判定します。
 * ==== 引数
 * year:: 年（0～）
 * month:: 月（1～12）
 * day:: 日（1～31）
 * ==== 戻り値
 * true:: 日付は実在する。
 * false:: 日付は実在しない。
 * ==== 説明
 * 指定した日付が実在すればtrue、実在しなければfalseを返します。
 * 例えば、2007年2月31日は実在しないので、falseを返します。
 */
function validate_date( year, month, day )
{
  var days = Array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
  if ( leap_year( year ) ) days[1]++;
  return ( year >= 0 ) && ( month >= 1 ) && ( month <= 12 ) && ( day >= 1 ) && ( day <= days[month-1] );
}

/****************************************
 * AJAX関連
 ****************************************/

/**
 * ブラウザ種別です。
 * -1:: サポート外のブラウザ
 * 0:: 不明（初期状態）
 * 1:: Internet Explorer以外
 * 2:: Internet Explorer 6以降
 * 3:: Internet Explorer 5
 */
var browser_type = 0;

/**
 * HTTP通信用オブジェクトを取得します。
 * ==== 戻り値
 * null:: 失敗
 * 非null:: HTTP通信用オブジェクト
 */
function create_http_object()
{
  if ( browser_type == 1 ) {
    // IE以外
    return new XMLHttpRequest();
  } else if ( browser_type == 2 ) {
    // IE6以降
    return new ActiveXObject( "Msxml2.XMLHTTP" );
  } else if ( browser_type == 3 ) {
    // IE5
    return new ActiveXObject( "Microsoft.XMLHTTP" );
  } else if ( browser_type == -1 ) {
    // サポート外のブラウザ
    return null;
  } else {
    
    // ブラウザ種別の判別
    var conn = null;
    
    try {
      conn = new XMLHttpRequest();
      browser_type = 1;
      return conn;  // IE以外
    }
    catch( e ) {}
    
    try {
      conn = new ActiveXObject( "Msxml2.XMLHTTP" );
      browser_type = 2;
      return conn;  // IE6以降
    }
    catch( e ) {}
    
    try {
      conn = new ActiveXObject( "Microsoft.XMLHTTP" );
      browser_type = 3;
      return conn;  // IE5
    }
    catch( e ) {}
    
    browser_type = -1;  // サポート外のブラウザ
    return null;
  }
}

/**
 * GETメソッドを送信します。
 * ==== 引数
 * uri:: 接続先のURI
 * receiver:: 通信結果を受け取ったときに呼び出される関数
 * username:: BASIC認証用のユーザ名
 * password:: BASIC認証用のパスワード
 * ==== 戻り値
 * true:: 成功
 * false:: 失敗
 * ==== 説明
 * HTTPオブジェクトを生成し、uriに接続します。
 * GETメソッドを送信し、その応答を取得します。
 *
 * リクエストが完了すると、receiverで指定された関数が呼び出されます。
 * この関数は、次の形式で実装しておく必要があります。
 *   function receiver( status, text )
 *
 * - 関数名は、任意の名前で構いません。
 * - 第１引数には、HTTPのレスポンスコードが渡されます。
 * - 第２引数には、レスポンスコードが200のときのみ、レスポンスのボディ部が渡されます。200以外のときは、nullが渡されます。
 */
function get( uri, receiver, username, password )
{
  var result = false;
  var conn = create_http_object();
  if ( conn ) {
    try {
      conn.onreadystatechange = function() {
        if ( conn.readyState == 4 ) receiver( conn.status, ( conn.status == 200 ) ? conn.responseText : null );
      };
      conn.open( "GET", uri, true, username, password );
      conn.send( null );
      result = true;
    }
    catch( e ) {}
  }
  return result;
}

// End of common.js
