function Table(id) {
  this.self = this
  
  //テーブルId
  this.id = id
  
  //テーブル名
  this.name = id
  
  //フィールドIdの配列、initializeで設定
  this.fieldIds = []
  
  //主キーIdの配列、initializeで設定
  this.keyIds = []
  
  //レコード数、initializeで設定
  //　新規入力用のレコードも含む
  this.recordCount = null
  
  //サーバ側で保持している構造情報
  //  これを元に入力チェックを行う
  this.fields = null
  
  //現在のレコードで使用されているid接頭辞
  this.idPrefix = null
  
  //カレントレコードの要素の連想配列
  this.elements = {}
  
  //現在が何番目のレコードか、move系メソッド、eachRecordで設定される
  this.recordIndex = null
  
  //テーブルが編集されたかどうか変化したときに実行するイベントハンドラ
  //  setModified(modified)
  //    modified: 編集されたかどうかを示すフラグ
  this.setModified = null
}

//構造情報を設定する
//　構造情報はサーバに保存してあるものをstructToJsonで変換したもの
//  value: 設定する構造情報
Table.prototype.setStruct = function (value) {
  with (this) {
    id = value.id
    name = value.name
    fields = value.fields
  }
}
//テーブルを初期処理する
//　recordCount,fieldIds,keyIdsを設定する
//　HTMLがロードされてから実行すること
Table.prototype.initialize = function() {
  with (this) {
    //先頭レコードの内容からfieldIds・keyIdsを設定する
    var reg = new RegExp('^'+id+'\.0\.([^\.]+)$')
    if ($(id+'.0.')) {
      eachNode($(id+'.0.'), function(elm) {
        if (elm.id && elm.id.match(reg)) {
          var fieldId = RegExp.$1
          fieldIds.push(fieldId)
          if (fieldId.match(/^(.+)_oldKey$/)) keyIds.push(RegExp.$1)
        }
      })
      recordCount = parseInt($(id+'.recordCount').value)
      if (isNaN(recordCount)) alert('$('+id+'.recordCount)が数値でない')
    } else {
      recordCount = 0
    }
    moveFirst()
  }
}

//レコードを移動したときの動作
Table.prototype.enterRecord = function () {
  with (this) {
    if (!eof()) {
      idPrefix = id+'.'+recordIndex+'.'
      fieldIds.each(function(fieldId) {
        elements[fieldId] = $(idPrefix+fieldId)
      })
    }
  }
}

/* 先頭のレコードに移動する */
Table.prototype.moveFirst = function() {
  this.recordIndex = 0
  this.enterRecord()
}

/* 次のレコードに移動する */
Table.prototype.moveNext = function() {
  this.recordIndex++
  this.enterRecord()
}

/* 指定したレコードに移動する */
Table.prototype.move = function(recordIndex) {
  if (this.recordIndex!=recordIndex) {
    this.recordIndex = recordIndex
    this.enterRecord()
  }
}

/* 
　指定した要素が属するレコードに移動する
　要素にはid+'.'+recordIndexで始まるidが付いていること
 */
Table.prototype.moveByElement = function(element) {
  with (this) {
    element.id.match('^'+id+'\\.(\\d)\\.')
    move(RegExp.$1)
  }
}

//レコードをすべて読み出したことを示す
//　新規入力用のレコードは領域が確保されていればeofとならない
Table.prototype.eof = function() {
  return this.recordIndex>=this.recordCount
}

//現在のレコードが新規であることを示す
Table.prototype.getNewRecord = function() {
  return $(this.idPrefix+'_newrecord_').value != ''
}

//現在のレコードが新規かどうかを設定する
//　value: true or false
Table.prototype.setNewRecord = function(value) {
  $(this.idPrefix+'_newrecord_').value = value ? 'true' : ''
  
}

//現在のレコードのActionを取得する
//  戻り値: insert, update, deleteまたは空白
Table.prototype.getRecordAction = function() {
  return $(this.idPrefix+'_action_').value
}

//現在のレコードのActionを設定する
//　value: insert, update, deleteまたは空白を指定
Table.prototype.setRecordAction = function(value) {
  $(this.idPrefix+'_action_').value = value
}

//現在のレコードが編集されたかどうかを設定する
//　内部で_action_を設定する
Table.prototype.setRecordModified = function(value) {
  with (this) {
    if (value) setRecordAction(getNewRecord() ? 'insert' : 'update')
      else setRecordAction('')
  }
}

//レコード毎に呼び出すイテレータ
//　新規入力用のレコードも巡回する
Table.prototype.eachRecord = function(func) {
  this.moveFirst()
  while (!this.eof()) {
    func()
    this.moveNext()
  }
}

//フィールド毎に呼び出すイテレータ、アンダーバーで始まるメタデータは呼び出さない
//  func: フィールド毎に呼び出されるハンドラ function(fieldId)
//    fieldId: フィールドId
Table.prototype.eachField = function(func) {
  with (this) {
    for(var i=0; i<fieldIds.length; i++) {
      if (fieldIds[i].match(/^[^_]/)) {
        func(fieldIds[i])
      }
    }
  }
}

//キー毎に呼び出すイテレータ
//　func: フィールド毎に呼び出されるハンドラ function(keyId)
//　　keyId: キーId
Table.prototype.eachKey = function(func) {
  with (this) {
    for(var i=0; i<keyIds.length; i++) {
      func(keyIds[i])
    }
  }
}

/* 以下はテーブル操作を行う関数 */

//必須項目をチェックする
//　編集されたレコードのみがチェック対象となる
Table.prototype.checkRequired = function () {
  with (this) {
    if (fields) {
      eachRecord(function() {
        if (getRecordAction()>'') {
          eachField(function(fieldId) {
            if (fields[fieldId] && fields[fieldId].required) {
              CheckRequired($(idPrefix+fieldId), fields[fieldId].label)
            }
          })
        }
      })
    }
  }
}

//変更時のイベントハンドラを作成する高階関数
//setOnChangeから呼ばれる。外部から直接呼ぶことは想定していない
//　table: チェック対象のテーブル
Table.prototype.onChangeFactory = function () {
  with (this) {
    var ri = recordIndex //recordIndexを束縛
    return function () {
      move(ri)
      if (getRecordAction()=='') {
        setRecordModified(true)
        if (setModified) setModified(true)
      }
    }
  }
}

//レコードを削除する
//　既存ならactionを_delete_に、新規ならactionをリセット
//  source: コマンドが発行されたボタン
Table.prototype.deleteRecord = function (source) {
  with (this) {
    source.id.match('^'+id+'\\.(\\d+)\\.')
    move(RegExp.$1)
    setRecordAction(getNewRecord() ? '' : '_delete_')
    $(idPrefix).style.display = 'none'
  }
}

//入力要素にイベントハンドラを設定する
Table.prototype.setOnChange = function () {
  with (this) {
    eachRecord(function() {
      var onchange = onChangeFactory()
      eachField(function(fieldId) {
        if (fields && fields[fieldId]) {
          var field = fields[fieldId]
          var type = field.type
          if (type=='Integer') SetIntegerCheck($(idPrefix+fieldId))
          else if (type=='Float') SetFloatCheck($(idPrefix+fieldId), field.digits, field.decimalPlace)
          else if (type=='Date') SetDateNormalize($(idPrefix+fieldId))
          else if (type=='Time') SetTimeNormalize($(idPrefix+fieldId))
          if (field.minValue!=null || field.maxValue!=null)
            SetRangeCheck($(idPrefix+fieldId), field.minValue, field.maxValue)
        }
        AddEventHandler($(idPrefix+fieldId), 'onchange', onchange)
      })
      if ($(idPrefix+'_deleteButton_')) {
        AddEventHandler($(idPrefix+'_deleteButton_'), 'onclick', function(source) {
          source.id.match('^'+id+'\\.(\\d+)\\.')
          move(RegExp.$1)
          setRecordAction(getNewRecord() ? '' : 'delete')
          $(idPrefix).style.display = 'none'
          if (setModified) setModified(true)
        })
      }
    })
  }
}

//現在のレコードの内容を連想配列に設定する
//　キーはfieldId、値は対応するフィールドに入力されている値
//  hash: 設定先の連想配列
Table.prototype.recordToHash = function (hash) {
  with (this) {
    eachField(function(fieldId) {
      var id = idPrefix+fieldId
      if ($(id)) {
        var value = getValue($(id))
        if (value) hash[fieldId] = value
      }
    })
  }
}

