/**
 *  object to show and modify a table
 *
 *  @author   Tobias Hettinger
 *  @version  $Id: tableview.js,v 1.44 2009/05/25 14:12:31 tobias Exp $
 */


/**
 *  constructor
 *
 *  @param string name               name of the table management object
 *  @param string tableId            id of the table to control
 *  @param string rowsOnPage         number of rows to be shown on one page or -1 if there is no limit
 *  @param string navId              id of the div for the page navigation or null if there is no page navigation
 *  @param string imgBaseUrl         image base url with a path separator at the end
 *  @param string groupTypeSelectId  id of the group type select box or null if there is no group type select box
 */
var WATTableView = function(name, tableId, rowsOnPage, navId, imgBaseUrl, groupTypeSelectId)
{
  //  set the member variables
  this.name = name;
  this.tableId = tableId;
  this.rowsOnPage = rowsOnPage;
  this.imgBaseUrl = imgBaseUrl;
  this.currentPage = 0;
  this.navElement = navId ? document.getElementById(navId) : null;
  this.tableElement = document.getElementById(tableId);
  this.tbodyElement = null;
  this.theadElement = null;
  this.columns = new Array();
  this.rows = new Array();
  this.selectedRows = new Array();
  this.sortColumnName = null;
  this.sortDesc = false;
  this.pendingRowRefresh = false;
  this.selectMode = 2; // [0: no selection, 1: single selection, 2: multiple selection, 3: invisible selection]
  this.groupTypeSelect = groupTypeSelectId ? document.getElementById(groupTypeSelectId) : null;
  this.groupTypes = new Array();
  this.selectedGroupType = null;
  
  //  the key and the data values can be used to store additional information (e.g. the key of the
  //  gadget, the table belongs to)
  this.key = null;
  this.type = null;
  this.d1 = null;
  this.d2 = null;
  this.d3 = null;
  
  //  data memory variables
  this.memFilter = null;

  //  check if the table exists
  if (!this.tableElement) { alert('error: the table with id ' + tableId + ' does not exist'); return 0; }
  
  //  search the table body
  var tbodyElements = this.tableElement.getElementsByTagName('tbody');
  if (tbodyElements && tbodyElements.item(0)) this.tbodyElement = tbodyElements.item(0);
    else alert('warning: the table with id ' + tableId + ' has no body');

  //  search the table header
  var theadElements = this.tableElement.getElementsByTagName('thead');
  if (theadElements && theadElements.item(0)) this.theadElement = theadElements.item(0);
    else alert('warning: the table with id ' + tableId + ' has no header');
}


//=====================================================================================================================
//  private functions to show the table
//=====================================================================================================================

/**
 *  function show the passed column
 *
 *  @param string columnName  name of the column to show. Since the column does not know its own (internal) name,
 *                            the name has to be passed separately
 *  @param class  column      column object to show
 */
WATTableView.prototype.__ShowColumn = function(columnName, column)
{
  //  a caption can only be appended if there is a table header
  if (this.theadElement)
  {
    //  get the number of rows in the header
    var rowCount = this.theadElement.rows.length;
    
    //  get the header row, create a new one if the header does not yet contain any row
    if (rowCount == 0)
    {
      //  create a new row
      tr = document.createElement('tr');
      this.theadElement.appendChild(tr);
    }
    else
    {
      //  get the existing row
      tr = this.theadElement.rows[0];
    }
    
    //  create a new column ...
    var td = document.createElement('td');
    td.className = 'list';
    if (column.width > 0) td.style.width = column.width + 'px';
    var content = document.createElement('div');
    td.appendChild(content);
    
    //  create the filter input control
    var filterInput = '';
    if (column.filterValues.length == 0)
    {
      //  there are no filter values, use the filterInput flag
      filterInput = column.filterInput ? " <input type=\"text\" id=\"" + this.name + 'fi' + columnName + 
                    "\" class=\"filter\" onkeyup=\"javascript:" + this.name + ".__OnFilterInput('" + columnName +
                    "', this);\" " + (column.filterString ? "value=\"" + column.filterString + "\"" : '')+"/>" : '';
    }
    else
    {
      //  there are filter values, do not use the filterInput flag in this case
      filterInput = ' <select id="' + this.name + 'fi' + columnName + '" class="filter" onchange="javascript:' +
                                      this.name + ".__OnFilterInput('" + columnName + "', this);\">";
      filterInput += '<option value=""></option>';
      for (var i in column.filterValues)
        filterInput += '<option value="' + i + '" ' + (column.filterString == i ? "selected=\"true\"" : '') + '>' +
                       column.filterValues[i] + '</option>';       
      filterInput += '</select>';
    }
    
    //  start a new line for the filter input
    if (filterInput != '' && column.filterInputNewLine == true)
      filterInput = '<br />' + filterInput;
    
    //  ... and create the content
    if (column.sortable)
    {
      //  create the sort buttons if an image path is available
      var sortButton = '';
      if (this.imgBaseUrl)
      {
        sortButton = '&nbsp;<img id="' + this.name + columnName + 'sortAsc" src="' + this.imgBaseUrl + 'sort-asc.gif" ' +
                     'style="display:' + ((columnName == this.sortColumnName && !this.sortDesc) ? 'inline' : 'none') +
                     '" alt="aufsteigend sortiert" title="aufsteigend sortiert" /><img id="' + this.name + columnName +
                     'sortDesc" src="' + this.imgBaseUrl + 'sort-desc.gif" style="display:' +
                     ((columnName == this.sortColumnName && this.sortDesc) ? 'inline' : 'none') +
                     '" alt="absteigend sortiert" title="absteigend sortiert" />';
      }
      
      //  create the content
      content.innerHTML = "<a class=\"list_caption\" href=\"javascript:"+ this.name + ".ToggleSort('" + columnName +
                          "');\"" + (column.verticalText ? ' style="writing-mode:tb-rl;">' : '>') + column.caption +
                          "</a>" + sortButton + filterInput;
    }
    else
      content.innerHTML = column.caption + filterInput;
      
    //  append the new column to the header
    tr.appendChild(td);
  }
}

/**
 *  function to append the passed row to the table
 *
 *  @param class row    row object to show
 *  @param int   index  index of the row in the table. Since a row does not know its index, this has to be passed
 *                      separately. The index is e.g. required to alternate the background color of table rows
 */
WATTableView.prototype.__ShowRow = function(row, index)
{
  //  onclick handler which is called after the user clicked on a table row
  function __SetCurrentRow()
  {
    if (row.tableView.IsRowSelected(row)) row.tableView.DeselectRow(row);
      else row.tableView.SelectRow(row);
  }
  
  //  onDblClick handler which is called after the user double-clicked on a table row
  function __DblClickRow()
  {
    row.tableView.SelectRow(row);
    WATOnTableViewDblClickRow.Raise(row.tableView, row);
  }
  
  //  onContextMenu handler which is called after the user pressed the right mouse button
  function __ContextMenuRow()
  {
    WATOnTableViewContextMenuRow.Raise(row.tableView, row);
    return false;
  }

  //  create a new table row
  tr = document.createElement('tr');
  
  //  setup the row style
  if (index % 2 == 0) row.cssClass = 'list_light';
    else row.cssClass = 'list_dark';
  if (this.IsRowSelected(row) && this.selectMode < 3) tr.className = 'list_selected';
    else tr.className = row.cssClass;
  if (this.selectMode > 0) tr.style.cursor = 'pointer';
  
  //  create the event handlers
  if (WATBrowserInfo.IsIE)
  { 
    tr.attachEvent('onclick', __SetCurrentRow);
    tr.attachEvent('ondblclick', __DblClickRow);
    tr.attachEvent('oncontextmenu', __ContextMenuRow);
  }
  else
  {
    tr.addEventListener('click', __SetCurrentRow, true);
    tr.addEventListener('dblclick', __DblClickRow, true);
    tr.addEventListener('contextmenu', __ContextMenuRow, true);
  }
  
  //  setup the row data
  row.tr = tr;
  
  //  append the row to the table
  this.tbodyElement.appendChild(tr);

  //  create the cells
  var cells = row.cells;
  for (var columnName in this.columns)
  {
    //  create a new column
    var td = document.createElement('td');
    if (this.columns[columnName].width > 0) td.style.width = this.columns[columnName].width + 'px';
    
    //  check if the cell for the current column is defined
    if (cells[columnName])
    {
      //  create the cell
      var cell = cells[columnName];
      td.className = 'list_item';

      //  create the content
      var content = document.createElement('div');
      var value = cell.value != null ? cell.value : '&nbsp;';
  
      //  append the cell
      td.appendChild(content);
      content.innerHTML = watDivDecode(value);
      tr.appendChild(td);
    }
    else
    {
      //  the cell for the current column is not defined, show an empty cell
      td.innerHTML = '&nbsp;';
      tr.appendChild(td);
    }
  }
}

/**
 *  function to update the passed row
 * 
 *  @param class row  definition of the row to update
 */
WATTableView.prototype.__UpdateCells = function(row)
{
  var cells = row.cells;
  var tr = row.tr;
  var td = tr.firstChild;
  for (var columnName in this.columns)
  {
    if (cells[columnName])
    {
      //  set the cell content
      var value = cells[columnName].value != null ? cells[columnName].value : '&nbsp;';
      td.innerHTML = watDivDecode(value);
    }
    else
    {
      //  the cell is not defined, clear the current content
      td.innerHTML = '&nbsp;';
    }
    
    //  get the next cell
    td = td.nextSibling;
  }
}

/**
 *  function to clear the rows in the (HTML) table
 *
 *  only the HTML table rows are cleared, the underlying data structure remains untouched
 */
WATTableView.prototype.__ClearRows = function()
{
  //  get the current number of rows
  var rowCount = this.tbodyElement.rows.length;

  //  remove the references of the DHTML elements from the row data
  for (var index in this.rows)
  {
    this.rows[index].tr = null;
    this.rows[index].cssClass = null;
  }
 
  //  remove all rows
  for (var i=rowCount; i>0; i--)
    this.tbodyElement.deleteRow(i-1);
}

/**
 *  function to clear the columns and the rows in the (HTML) table
 *
 *  only the HTML table is cleared, the underlying data structure remains untouched
 */
WATTableView.prototype.__ClearTable = function()
{
  //  get the current number of rows in the body
  var rowCount = this.tbodyElement.rows.length;
  //  remove all rows from the body
  for (var i=rowCount; i>0; i--)
    this.tbodyElement.deleteRow(i-1);
    
  //  remove the references of the DHTML elements from the row data
  for (var index in this.rows)
  {
    this.rows[index].tr = null;
    this.rows[index].cssClass = null;
  }
    
  //  get the current number of rows in the header
  var rowCount = this.theadElement.rows.length;
  //  remove all rows from the header
  for (var i=rowCount; i>0; i--)
    this.theadElement.deleteRow(i-1);
}

/**
 *  callback function that is required for the table sort function
 */
WATTableView.prototype.__SortStringComparison = function(a, b)
{
  //  get the cells
  var ca = a.cells[a.sortColumnName] ? a.cells[a.sortColumnName] : null;
  var cb = b.cells[a.sortColumnName] ? b.cells[a.sortColumnName] : null;

  //  get the values
  var va = ca ? (a.cells[a.sortColumnName].sortableValue ? a.cells[a.sortColumnName].sortableValue :
                                                           a.cells[a.sortColumnName].value) : '';
  var vb = cb ? (b.cells[a.sortColumnName].sortableValue ? b.cells[a.sortColumnName].sortableValue :
                                                           b.cells[a.sortColumnName].value) : '';
 
  //  a value which is null can not be sorted
  if (ca && va == null) va = (a.cells[a.sortColumnName].sortDataType == 's' ? '' : 0);
  if (cb && vb == null) vb = (a.cells[a.sortColumnName].sortDataType == 's' ? '' : 0);

  //  convert the values to the correct data type
  if (ca && cb && a.cells[a.sortColumnName].sortDataType == 'n')
  {
    va = parseFloat(va);
    vb = parseFloat(vb);
  }
 
  //  compare the values
  return va > vb ? (a.sortDesc ? -1 : 1) : va < vb ? (a.sortDesc ? 1 : -1) : 0;
}

/**
 *  function to create the page navigation bar
 */
WATTableView.prototype.__CreateNavigation = function()
{
  //  create the navigation if there is a navigation element
  if (this.navElement)
  {
    //  initialize the variables
    var nav = '';
    var pageCount = this.GetPageCount();
    var start = this.currentPage - 2;
    var end = this.currentPage + 2;
    if (start < 0) start = 0;
    if (end > pageCount-1) end = pageCount-1;
    
    //  create the navigation if there are at least 2 pages
    if (pageCount > 1)
    {
      //  create a link to the first page if the link is not shown in the main navigation bar
      if (start > 0)
        nav += '<span class="page"><a href="javascript:' + this.name + '.SetPage(0, true);">1</a>&nbsp;...</span>';
        
      //  create the main navigation bar
      for (var i=start; i<=end; i++)    
        nav += '<span class="' + (i == this.currentPage ? 'current_page' : 'page') +
               '"><a href="javascript:' + this.name + '.SetPage(' + i + ', true);">' + (i+1) + '</a></span>';
      
      //  create a link to the last page if the link is not shown in the main navigation bar
      if (end < pageCount-1)
        nav += '<span class="page">...&nbsp;<a href="javascript:' + this.name + '.SetPage(' + (pageCount-1) +
               ', true);">' + pageCount + '</a></span>';
    }
    
    //  show the navigation
    if (nav != '') this.navElement.innerHTML = nav;
    else this.navElement.innerHTML = '';
  }
}

/**
 *  function to show the passed group
 *
 *  @param class group  group object to show
 */
WATTableView.prototype.__ShowGroup = function(group)
{
  //  create a new table row
  var tr = document.createElement('tr');
  tr.className = 'list_group';
  
  //  create the cell
  var td = document.createElement('td');
  td.className = 'list_group';  
  var colspan = 1;
  for (var i in this.columns) colspan++;
  if (colspan > 1) td.colSpan = colspan;

  //  create the content
  var content = document.createElement('div');
  content.innerHTML = group.caption;
  td.appendChild(content);
  tr.appendChild(td);
  
  //  append the group row to the table
  this.tbodyElement.appendChild(tr);
}

/**
 *  function to show the rows
 *
 *  @param array filterColumns  array of columns for which the filter has to be applied
 *  @param int   counter        total row position
 *  @param int   index          start index on the current page
 *  @param class group          group to show or null if rows of all groups should be shown
 */
WATTableView.prototype.__ShowRows = function(filterColumns, counter, index, group)
{
  //  show the table rows
  var rowCount = this.rows.length;
  var groupShown = false;
  var firstRow = (this.rowsOnPage * this.currentPage)+1;
  for (var i=0; (this.rowsOnPage == -1 || index < this.rowsOnPage) && i<rowCount; i++)
  {
    //  apply the filter
    var canShow = true;
    var groupFound = false;
    
    //  filter for the group if a group has been passed
    if (group)
    {
      //  check if the current row belongs to the group
      for (var groupIndex in group.rows)
        if (group.rows[groupIndex] == this.rows[i]) groupFound = true;
      if (!groupFound) canShow = false;
    }
    
    //  the row must not be shown if it does not belong to the passed group (if there is one)
    if (canShow)
    {
	    for (var columnName in filterColumns)
	    {
	      //  do not show the row if the filter string does not match
	      var cellValue = (this.rows[i] && this.rows[i].cells[columnName]) ? (this.rows[i].cells[columnName].filterValue ?
	                       this.rows[i].cells[columnName].filterValue : this.rows[i].cells[columnName].value) : null;
	      var filterString = this.columns[columnName].filterString;
	      //  the filter string never matches if the value is null but a filter is defined
	      if (cellValue == null) canShow = false;
	        else if (!cellValue.match(new RegExp(filterString,"gi"))) canShow = false;
	    }
    
      //  total line counter
      if (canShow) counter++;
      if (this.rowsOnPage > -1 && counter < firstRow) canShow = false;
    
      //  the row fits on the page
	    if (canShow)
	    {    
	      //  show the group headline if it has not yet been shown
	      if (group && !groupShown)
	      {
	        this.__ShowGroup(group);
	        groupShown = true;
	      }
	    
	      //  show the row if it is allowed
	      this.__ShowRow(this.rows[i], index);
	      index++;
	    }
    }
  }

  //  return the total index and the page index
  var result = new Array();
  result['counter'] = counter;
  result['index'] = index;
  return result;
}


//=====================================================================================================================
//  event handler
//=====================================================================================================================

/**
 *  event handler that is called after the value of a filter input field has been changed
 */
WATTableView.prototype.__OnFilterInput = function(columnName, inputField)
{
  //  set the filter string
  this.columns[columnName].filterString = (inputField.value ? inputField.value : null);
  if (!this.pendingRowRefresh)
  {
    this.pendingRowRefresh = true;
    setTimeout(this.name + '.ShowRows()', 400);
  }
}


//=====================================================================================================================
//  user interface
//=====================================================================================================================

/**
 *  function to (re-)show the defined table
 */
WATTableView.prototype.ShowTable = function()
{
  //  clear the existing table if there is one
  this.__ClearTable();
  
  //  show the columns
  for (var key in this.columns)
    this.__ShowColumn(key, this.columns[key]);

  //  show the table rows
  this.ShowRows();
}

/**
 *  function to (re-)show the table rows (without refreshing the captions)
 */
WATTableView.prototype.ShowRows = function()
{
  //  clear the existing rows if there are some
  this.__ClearRows();

  //  get the number of pages
  var pageCount = this.GetPageCount();
  if ((pageCount-1) < this.currentPage) this.currentPage = pageCount - 1;
  
  //  get the columns to filter
  var filterColumns = new Array();
  for (var columnName in this.columns)
    if (this.columns[columnName].filterString)
      filterColumns[columnName] = this.columns[columnName];
  
  //  show all rows if no group is selected
  if (this.selectedGroupType == null) this.__ShowRows(filterColumns, 0, 0, null);
  else
  {
    //  a group is selected
    var groupType = this.groupTypes[this.selectedGroupType];
    var counter = 0;
    var rowResult = new Array();
    rowResult['counter'] = 0;
    rowResult['index'] = 0;
    for (var index in groupType.groups)
    {
      var group = groupType.groups[index];
      rowResult = this.__ShowRows(filterColumns, rowResult['counter'], rowResult['index'], group);
    }
  }
  
  //  create the navigation bar
  this.__CreateNavigation();
  //  reset the pendingRowRefresh flag
  this.pendingRowRefresh = false;
}

/**
 *  function to add a new column to the table
 *
 *  @param string columnName  name of the column (NOTE: the name MUST NOT be numeric)
 *  @param class  column      column object that is appended to the table header
 */
WATTableView.prototype.AddColumn = function(columnName, column)
{
  //  the columns are index by their (internal) names
  this.columns[columnName] = column;
}

/**
 *  function to define a new row for the table
 *
 *  @param class  row     row object for the table
 *  @param string groups  string of group keys (structure '[type]|[group];[type]|[group];...')
 *
 *  @todo 'Was passiert wenn die Gruppendefinition ungültige Typen/Gruppen enthält und/oder nicht vollständig ist?'
 */
WATTableView.prototype.AddRow = function(row, groups)
{
  //  add the row to the passed groups
  if (groups)
  {
    //  split the [types|group] definition
    var defs = groups.split(';');
    for (var i=0; i<defs.length; i++)
    {
      //  split the definition
      var def = defs[i].split('|');
      if (this.groupTypes[def[0]])
      {
        var groupType = this.groupTypes[def[0]];
        if (groupType.groups[def[1]])
        {
          var group = groupType.groups[def[1]];
          group.rows.push(row);
        }
      }
    }
  }
  else
  {
    //  no group is defined, add the row to the default group
    for (var i in this.groupTypes)
      this.groupTypes[i].groups['default'].rows.push(row);
  }

  //  save a pointer to the table view
  row.tableView = this;
  //  the rows are numerically indexed
  this.rows.push(row);
}

/**
 *  function to get the row with the passed key
 *  
 *  @param string key  key of the required row
 *  
 *  @return WATTableRow  the function returns the searched table row object or null if the row was not found
 */
WATTableView.prototype.GetRow = function(key)
{
  for (var i in this.rows)
    if (this.rows[i].key == key)
      return this.rows[i];
  
  //  the row was not found
  return null;
}

/**
 *  function remove all rows from the table body (also in the underlying data structure)
 */
WATTableView.prototype.ClearRows = function()
{
  //  clear the data structure
  this.rows = new Array();
  //  clear the references of the groups
  for (var i in this.groupTypes)
    for (var j in this.groupTypes[i].groups)
      this.groupTypes[i].groups[j].rows = new Array();
  //  clear the rows in table if a table is currently shown
  this.__ClearRows();
}

/**
 *  function to clear the table (remove the head and the body, also in the underlying data structure)
 */
WATTableView.prototype.ClearTable = function()
{
  //  clear the data structure
  this.columns = new Array();
  this.rows = new Array();
  //  clear the table if it is currently shown
  this.__ClearTable();
}

/**
 *  function to sort the table rows
 *
 *  @param string columnName  name of the column to sort
 *  @param bool   sortDesc    if true, the column is sorted in descending order,
 *                            if false the column is sorted in ascending order
 */
WATTableView.prototype.Sort = function(columnName, sortDesc)
{
  //  save the new order type
  this.sortColumnName = columnName;
  this.sortDesc = sortDesc;

  //  update the sort icon
  if (this.imgBaseUrl)
  {
    for (var index in this.columns)
    {
      var imgAsc = document.getElementById(this.name + index + 'sortAsc');
      var imgDesc = document.getElementById(this.name + index + 'sortDesc');
      if (imgAsc && imgDesc)
      {
        imgAsc.style.display = (index == columnName) ? (sortDesc ? 'none' : 'inline') : 'none';
        imgDesc.style.display = (index == columnName) ? (sortDesc ? 'inline' : 'none') : 'none';
      }
    }
  }
  
  //  each row has to know the order (because the __SortStringComparison function can not (??) access the
  //                                  member variables of the tableview object)
  for (var index in this.rows)
  {
    this.rows[index].sortDesc = this.sortDesc;
    this.rows[index].sortColumnName = this.sortColumnName;
  }

  //  sort the table rows
  this.rows.sort(this.__SortStringComparison);
}

/**
 *  the ToggleSort is a kind of "intelligent" sort which can automatically toggle the order mode.
 *  This function is e.g. used by the links that are automatically created for the column captions
 *
 *  the table is automatically refreshed (whereas the main sort function does not redraw the table automatically)
 *
 *  @param string columnName  name of the column to sort
 */
WATTableView.prototype.ToggleSort = function(columnName)
{
  //  sort the table
  this.Sort(columnName, (this.sortColumnName == columnName ? (this.sortDesc ? false : true) : this.sortDesc));
  //  redraw the table rows
  this.ShowRows();
}

/**
 *  function to sort the groups by their order value
 */
WATTableView.prototype.SortGroups = function()
{
  //  sort help function  
  function __SortGroup(a, b)
  {
    return a['value'].order > b['value'].order ? 1 : a['value'].order < b['value'].order ? -1 : 0;
  }
  
  //  sort the groups
  for (var key in this.groupTypes)
  {
    //  convert the groups array of the selected type into a numerically indexed array in order
    //  to be able to sort the assoziative groups array
    var groupType = this.groupTypes[key];
    var groups = new Array();
    for (var index in groupType.groups)
    {
      var group = new Array();
      group['key'] = index;
      group['value'] = groupType.groups[index];
      groups.push(group);
    }
    
    //  now the array can be sorted
    groups.sort(__SortGroup);
    groupType.groups = new Array();
    for (var index in groups)
      groupType.groups[groups[index]['key']] = groups[index]['value'];
  }
}

/**
 *  function to check if the passed row is selected
 *
 *  @return bool  the function returns true if the row is selected or false if the row is not selected
 */
WATTableView.prototype.IsRowSelected = function(row)
{
  for (var key in this.selectedRows)
    if (this.selectedRows[key] == row) return true;
  return false;
}

/**
 *  function to deselect a row if it is selected
 *
 *  @param class row  row to deselect
 */
WATTableView.prototype.DeselectRow = function(row)
{
  //  remove the row from the selectedRows array
  for (var key in this.selectedRows)
    if (this.selectedRows[key] == row) this.selectedRows[key] = null;
  //  reset the style
  row.tr.className = row.cssClass;
  //  throw a select row event
  WATOnTableViewSelectRow.Raise(this, row, false);
}

/**
 *  function to select a row if it is not yet selected
 *
 *  @param class row  row to select
 */
WATTableView.prototype.SelectRow = function(row)
{
  //  check if the row is not yet selected
  if (this.selectMode > 0 && !this.IsRowSelected(row))
  {
    //  deselect the selected row(s) if the function is in single select mode
    if (this.selectMode == 1)
      for (var index in this.selectedRows)
        if (this.selectedRows[index] != null)
          this.DeselectRow(this.selectedRows[index]);
  
    //  select the row
    this.selectedRows.push(row);
    //  set the css style for selected rows
    if (this.selectMode < 3) row.tr.className = 'list_selected';
    //  throw a select row event
    WATOnTableViewSelectRow.Raise(this, row, true);
  }
}

/**
 *  function to set the number of rows on one page
 *
 *  @param int  rowsOnPage  number of rows on one page of the table
 *  @param bool refresh     if true, the rows are refreshed automatically after the new number of rows has been set
 */
WATTableView.prototype.SetRowsOnPage = function(rowsOnPage, refresh)
{
  //  check the parameter
  if (rowsOnPage > 0)
  {
    //  set the number of rows on one page ...
    this.rowsOnPage = rowsOnPage;
    //  ... and refresh the table
    if (refresh) this.ShowRows();
  }
}

/**
 *  function to get the number of pages
 *
 *  @return int  the function returns the number of pages
 */
WATTableView.prototype.GetPageCount = function()
{
  //  get the columns to filter
  var filterColumns = new Array();
  for (var columnName in this.columns)
    if (this.columns[columnName].filterString)
      filterColumns[columnName] = this.columns[columnName];

  //  get the page count
  var rowCount = this.rows.length;
  var counter = 0;
  for (var i=0; i<rowCount; i++)
  {
    //  apply the filter
    var canShow = true;
    for (var columnName in filterColumns)
    {
      //  do not show the row if the filter string does not match
      var cellValue = (this.rows[i] && this.rows[i].cells[columnName]) ? (this.rows[i].cells[columnName].filterValue ?
                       this.rows[i].cells[columnName].filterValue : this.rows[i].cells[columnName].value) : null;
      var filterString = this.columns[columnName].filterString;
      //  the filter string never matches if the value is null but a filter is defined
      if (cellValue == null) canShow = false;
        else if (!cellValue.match(new RegExp(filterString,"gi"))) canShow = false;
    }
    
    //  count the row that could be shown
    if (canShow) counter++;
  }
  
  //  calculate the number of pages
  return Math.ceil(counter / this.rowsOnPage);
}

/**
 *  function to set the current page of the table
 *
 *  @param int  page     number of the page to show whereas the number of the first page is 0
 *  @param bool refresh  if true, the rows are refreshed automatically after the new page has been selected
 */
WATTableView.prototype.SetPage = function(page, refresh)
{
  //  check the parameter
  if (page >= 0 && page <= this.GetPageCount())
  {
    //  set the page
    this.currentPage = page;
    //  refresh the table
    if (refresh) this.ShowRows();
  }
}

/**
 *  function to show the page of the table that contains the row with the passed key
 *
 *  @param string key      key of the selected row
 *  @param bool   refresh  if true, the rows are refreshed automatically after the new page has been selected
 */
WATTableView.prototype.SetPageByRowKey = function(key, refresh)
{
  var found = false;
  var counter = 0; 

  //  search the row
  for (var index in this.rows)
  {
    if (this.rows[index].key == key) found = true;
    if (!found) counter++;
  }
  
  //  select the page if it has been found
  if (found)
  {
    var page = Math.floor(counter / this.rowsOnPage);
    this.SetPage(page, refresh);
  }
}

/**
 *  function to set the selection mode for the table
 *
 *  @param int selectMode  select mode [0: no selection, 1: single selection, 2: multiple selection]
 */
WATTableView.prototype.SetSelectMode = function(selectMode)
{
  //  check the parameter
  if (selectMode >= 0 && selectMode <= 2)
  {
    this.selectMode = selectMode;
    //  remove existing selections for selection mode 0
    if (selectMode == 0)
      for (var index in this.selectedRows)
        if (this.selectedRows[index] != null)
          this.DeselectRow(this.selectedRows[index]);
          
    //  modify the cursor for each row
    for (var index in this.rows)
      if (this.rows[index].tr) this.rows[index].tr.style.cursor = (selectMode == 0 ? 'default' : 'hand');
  }
}

/**
 *  function to save the values of the filter input fields. This is useful if the table is rebuilt whereas the
 *  filter values should be kept
 */
WATTableView.prototype.SaveFilter = function()
{
  //  initialize the filter memory
  this.memFilter = new Array();
  //  save the filter strings
  for (var columnName in this.columns)
    if (this.columns[columnName].filterString || this.columns[columnName].filterValues.length > 0)
      this.memFilter[columnName] = this.columns[columnName].filterString;
}

/**
 *  function to restore the filter values that have been saved by the 'SaveFilter' function
 */
WATTableView.prototype.RestoreFilter = function()
{
  //  do nothing if no filter values have been saved before
  if (this.memFilter)
  {
    for (var columnName in this.memFilter)
      if (this.columns[columnName])
      {
        //  set the value of the filter input field if it (already) exists
        var field = document.getElementById(this.name + 'fi' + columnName);
        if (field) field.value = this.memFilter[columnName];
        //  save the value within the column descriptor
        this.columns[columnName].filterString = this.memFilter[columnName];
      }
  }
  
  //  remove the saved filter values
  this.memFilter = null;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  group management

/**
 *  function to enable the possibility to disable grouping event if group types are defined
 */
WATTableView.prototype.AllowNoGroupType = function()
{
  if (this.groupTypeSelect)
    this.groupTypeSelect.options[this.groupTypeSelect.options.length] =
      new Option('keine Gruppierung', null, true, false);
}

/**
 *  function to add a new group type
 *
 *  @param class groupType  group type object of add
 */
WATTableView.prototype.AddGroupType = function(groupType)
{
  if (this.groupTypeSelect)
    this.groupTypeSelect.options[this.groupTypeSelect.options.length] =
      new Option(groupType.caption, groupType.key, true, false);

  //  save the group type
  this.groupTypes[groupType.key] = groupType;
  //  create a default group for each group type
  this.groupTypes[groupType.key].groups['default'] = new WATTableGroup('default', 'nicht zugeordnet');
}

/**
 *  function to get the group type object with the passed key
 *
 *  @param string key  key of the requested group type
 */
WATTableView.prototype.GetGroupType = function(key)
{
  return this.groupTypes[key] ? this.groupTypes[key] : null;
}

/**
 *  function to select the group type with the passed key
 *
 *  @param string key  key of the group type to select or null (or string 'null') if no group is to be selected
 */
WATTableView.prototype.SelectGroupType = function(key)
{
  if (key == 'null') key = null;
  this.selectedGroupType = key;
  this.ShowRows();
}

/**
 *  function to add a new group if it does not yet exist
 *
 *  @param string key    key of the group type
 *  @param class  group  group to add
 */
WATTableView.prototype.AddGroup = function(key, group)
{
  if (this.groupTypes[key] && !this.groupTypes[key].groups[group.key])
    this.groupTypes[key].groups[group.key] = group;
}
