/**
 * @fileoverview Plugin for southprojects Grid to display grid.
 *
 * <pre>
 * Copyright (c) 2006 by SouthProjects
 * http://www.southprojects.com
 * Santiago, Chile,
 * Version 1.2 Dec 2006.
 * All rights reserved.
 * </pre>
 */

/* $Id: spgrid-output.js 4726 2006-10-12 14:21:15Z shacka $ */

/**
 * If there are several containers, synchronizes their width.
 * @private
 */
southprojects.Grid.prototype.synchronizeContainers = function() {
  // At least 2 containers must be defined
  if (!this.container || (!this.headerContainer && !this.totalsContainer &&
   !this.paginationContainer)) {
    return;
  }
  // Get container width
  var iContainerWidth = this.container.clientWidth;
  var objTable = null;
  if (this.container.firstChild && this.container.firstChild.firstChild) {
    objTable = this.container.firstChild.firstChild.firstChild;
  }
  if (objTable && (!iContainerWidth || !this.container.style.width ||
   objTable.offsetWidth < iContainerWidth)) {
    iContainerWidth = objTable.offsetWidth;
  }
  if (iContainerWidth) {
    // If there is separate container for header
    if (this.headerContainer) {
      this.headerContainer.style.width = iContainerWidth + 'px';
    }
    // If there is separate container for totals
    if (this.totalsContainer) {
      this.totalsContainer.style.width = iContainerWidth + 'px';
    }
    // If there is separate container for pagination
    if (this.paginationContainer) {
      this.paginationContainer.style.width = iContainerWidth + 'px';
    }
  }
};

/**
 * Synchronizes scrolling.
 * @private
 */
southprojects.Grid.prototype.synchronizeScrolling = function() {
  if (this.headerContainer) {
    southprojects.Utils.createProperty(this.container, 'spgridHeader',
     this.headerContainer);
  }
  if (this.totalsContainer) {
    southprojects.Utils.createProperty(this.container, 'spgridTotals',
     this.totalsContainer);
  }
  if (this.config.fixedLeft > 0) {
    southprojects.Utils.createProperty(this.container, 'spgridFixedLeft',
     document.getElementById('spgrid' + this.id + 'FixedLeft'));
    if (this.headerContainer) {
      southprojects.Utils.createProperty(this.container, 'spgridDataFixedLeft',
       document.getElementById('spgrid' + this.id + 'DataFixedLeft'));
    }
    if (this.totalsContainer) {
      southprojects.Utils.createProperty(this.container, 'spgridTotalsFixedLeft',
       document.getElementById('spgrid' + this.id + 'TotalsFixedLeft'));
    }
    // Synchronize fixed and scrollable header heights
    var objFieldHidden =
     document.getElementById('spgrid' + this.id + 'Field0Hidden');
    var objField = document.getElementById('spgrid' + this.id + 'Field0');
    if (objFieldHidden && objField) {
      this.synchronizeHeight(objFieldHidden, objField);
    }
    // Synchronize fixed and scrollable row heights
    var arrRows = this.applyPaging();
    for (var iRow = 0; iRow < arrRows.length; iRow++) {
      var objRow = arrRows[iRow];
      if (objRow) {
        var objCellHidden = document.getElementById('spgrid' + this.id + 'Row' +
         objRow.i + 'Cell0Hidden');
        var objCell = document.getElementById('spgrid' + this.id + 'Row' +
         objRow.i + 'Cell0');
        if (objCellHidden && objCell) {
          this.synchronizeHeight(objCellHidden, objCell);
        }
      }
    }
    // Synchronize fixed and scrollable total heights
    for (var iRow = 0; true; iRow++) {
      var objCellHidden = document.getElementById('spgrid' + this.id + 'Total' +
       iRow + 'Cell0Hidden');
      var objCell = document.getElementById('spgrid' + this.id + 'Total' +
       iRow + 'Cell0');
      if (!objCellHidden || !objCell) {
        break;
      }
      this.synchronizeHeight(objCellHidden, objCell);
    }
  }
  if (this.container.spgridHeader || this.container.spgridTotals ||
   this.container.spgridFixedLeft) {
    southprojects.Utils.createProperty(this.container, 'onscroll', function() {
      var iScrollLeft = this.scrollLeft;
      // Synchronize fixed columns
      if (this.spgridFixedLeft) {
        this.spgridFixedLeft.style.left = iScrollLeft + 'px';
      }
      if (this.spgridDataFixedLeft) {
        this.spgridDataFixedLeft.style.left = iScrollLeft + 'px';
      }
      if (this.spgridTotalsFixedLeft) {
        this.spgridTotalsFixedLeft.style.left = iScrollLeft + 'px';
      }
      // Synchronize header
      if (this.spgridHeader) {
        this.spgridHeader.scrollLeft = iScrollLeft;
      }
      // Synchronize totals
      if (this.spgridTotals) {
        this.spgridTotals.scrollLeft = iScrollLeft;
      }
    });
    // Workaround for Opera 8
    if (window.opera) {
      this.synchronizeScrollingOpera();
    }
  }
};

/**
 * Synchronizes fixed and scrollable row heights.
 *
 * @private
 * @param {object} objCellHidden First cell object of scrollable row
 * @param {object} objCell First cell object of fixed row
 */
southprojects.Grid.prototype.synchronizeHeight = function(objCellHidden, objCell) {
  // Get difference of heights
  var iDifference = southprojects.Utils.getElementOffset(objCellHidden).height -
   southprojects.Utils.getElementOffset(objCell).height;
  // If there is difference
  if (iDifference) {
    // Get tr element
    var objCellRow = objCell.parentNode;
    if (objCellRow) {
      // Calculate new row height
      var iHeight = southprojects.Utils.getElementOffset(objCellRow).height +
       iDifference;
      // Set height of tr element
      objCellRow.style.height = iHeight + 'px';
      // Adjust height in IE where border and padding are part of height
      iDifference = southprojects.Utils.getElementOffset(objCellHidden).height -
       southprojects.Utils.getElementOffset(objCell).height;
      // If there is still difference
      if (iDifference) {
        // Set height of tr element
        objCellRow.style.height = (iHeight + iDifference) + 'px';
      }
    }
  }
};

/**
 * Synchronizes scrolling in Opera. Workaround for Opera 8 which doesn't support
 * DOM 3 scroll event.
 * @private
 */
southprojects.Grid.prototype.synchronizeScrollingOpera = function() {
  var objHeaderDiv = null;
  if (this.headerContainer) {
    objHeaderDiv = this.headerContainer.firstChild;
    objHeaderDiv.style.position = 'relative';
  }
  var objTotalsDiv = null;
  if (this.totalsContainer) {
    objTotalsDiv = this.totalsContainer.firstChild;
    objTotalsDiv.style.position = 'relative';
  }
  // Synchronize position every 10 ms
  if (this.syncIntervalId) {
    clearInterval(this.syncIntervalId);
  }
  var objGrid = this;
  this.syncIntervalId = setInterval(function() {
    // Synchronize fixed columns
    var strScrollLeft = objGrid.container.scrollLeft + 'px';
    var objFixedLeft = objGrid.container.spgridFixedLeft;
    if (objFixedLeft && objFixedLeft.style.left != strScrollLeft) {
      objFixedLeft.style.left = strScrollLeft;
    }
    var objDataFixedLeft = objGrid.container.spgridDataFixedLeft;
    if (objDataFixedLeft && objDataFixedLeft.style.left != strScrollLeft) {
      objDataFixedLeft.style.left = strScrollLeft;
    }
    var objTotalsFixedLeft = objGrid.container.spgridTotalsFixedLeft;
    if (objTotalsFixedLeft && objTotalsFixedLeft.style.left != strScrollLeft) {
      objTotalsFixedLeft.style.left = strScrollLeft;
    }
    // Move header instead of scrolling
    var strLeft = -objGrid.container.scrollLeft + 'px';
    if (objHeaderDiv && objHeaderDiv.style.left != strLeft) {
      objHeaderDiv.style.left = strLeft;
    }
    // Move totals instead of scrolling
    if (objTotalsDiv && objTotalsDiv.style.left != strLeft) {
      objTotalsDiv.style.left = strLeft;
    }
  }, 10);
};

/**
 * Synchronizes containers when theme is loaded.
 * @private
 */
southprojects.Grid.prototype.visualizeThemeLoad = function() {
  this.synchronizeContainers();
};

/**
 * Synchronizes containers when grid is refreshed.
 * @private
 */
southprojects.Grid.prototype.visualizeRefresh = function() {
  // Synchronize containers
  this.synchronizeContainers();
};

/**
 * Sets column widths and row heights after data loading.
 * @private
 */
southprojects.Grid.prototype.visualizeDataLoad = function() {
  if (!this.container) {
    return;
  }
  // If there is separate container for header
  if (this.headerContainer) {
    // Set header overflow
    this.headerContainer.style.overflow = 'hidden';
  }
  // If there is separate container for totals
  if (this.totalsContainer) {
    // Set totals overflow
    this.totalsContainer.style.overflow = 'hidden';
  }
  // Sets column widths and row heights
  if (southprojects.StyleSheet) {
    this.applyCellDimensions();
  } else {
    // Load StyleSheet class
    var objGrid = this;
    southprojects.Transport.loadJS({
      module: 'stylesheet',
      onLoad: function() {
        objGrid.applyCellDimensions();
      }
    });
  }
};

/**
 * Sets column widths and row heights.
 * @private
 */
southprojects.Grid.prototype.applyCellDimensions = function() {
  // Get stylesheet
  if (!this.syncStyleSheet) {
    this.syncStyleSheet = new southprojects.StyleSheet();
  } else {
    this.syncStyleSheet.removeRules();
  }
  // Set default column width and row height
  this.syncStyleSheet.addRule(
   '#spgrid' + this.id + 'Container .spgridTable .spgridDiv',
   'width:' + this.config.columnWidth);
  this.syncStyleSheet.addRule(
   '#spgrid' + this.id + 'Container .spgridTable .spgridRow',
   'height:' + this.config.rowHeight);
  // Header
  if (this.headerContainer) {
    this.syncStyleSheet.addRule(
     '#spgrid' + this.id + 'DataContainer .spgridTable .spgridDiv',
     'width:' + this.config.columnWidth);
    this.syncStyleSheet.addRule(
     '#spgrid' + this.id + 'DataContainer .spgridTable .spgridRow',
     'height:' + this.config.rowHeight);
  }
  // Totals
  if (this.totalsContainer) {
    this.syncStyleSheet.addRule(
     '#spgrid' + this.id + 'TotalsContainer .spgridTable .spgridDiv',
     'width:' + this.config.columnWidth);
    this.syncStyleSheet.addRule(
     '#spgrid' + this.id + 'TotalsContainer .spgridTable .spgridRow',
     'height:' + this.config.rowHeight);
  }
  // Set fixed column widths
  for (var iFld = 0, iCol = 0; iFld < this.fields.length; iFld++) {
    // Get field
    var objField = this.fields[iFld];
    // Skip hidden columns
    if (!objField || objField.hidden) {
      continue;
    }
    if (objField.columnWidth) {
      var strColumnWidth = southprojects.Utils.correctCssLength(objField.columnWidth);
      // Set column width
      this.syncStyleSheet.addRule('#spgrid' + this.id +
       'Container .spgridTable .spgridCell' + iCol + ' .spgridDiv',
       'width:' + strColumnWidth);
      // Header
      if (this.headerContainer) {
        this.syncStyleSheet.addRule('#spgrid' + this.id +
         'DataContainer .spgridTable .spgridCell' + iCol + ' .spgridDiv',
         'width:' + strColumnWidth);
      }
      // Totals
      if (this.totalsContainer) {
        this.syncStyleSheet.addRule('#spgrid' + this.id +
         'TotalsContainer .spgridTable .spgridCell' + iCol + ' .spgridDiv',
         'width:' + strColumnWidth);
      }
    }
    iCol++;
  }
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {boolean) boolFixed (Optional) Indicates that this is fixed part of
 * data
 */
southprojects.Grid.prototype.outputTableOpen = function(arrHtml, boolFixed) {
  arrHtml.push('<table class="');
  arrHtml.push(this.getClassName({prefix: 'spgrid'}));
  arrHtml.push(
   '" cellpadding="0" cellspacing="0"><tbody><tr><td class="spgridTable');
  if (boolFixed) {
    arrHtml.push(' spgridTableFixed');
  }
  arrHtml.push('"><table class="spgridTableTable" style="');
  if (this.data.style) {
    arrHtml.push(this.data.style);
  }
  arrHtml.push('"><tbody>');
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 */
southprojects.Grid.prototype.outputTableClose = function(arrHtml) {
  arrHtml.push('</tbody></table></td></tr></tbody></table>');
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {object} arrCols Array with field objects to output
 * @param {boolean) boolFixed (Optional) Indicates that this is fixed part of
 * header
 * @param {boolean) boolSecondRow (Optional) Indicates that this is second row
 * of header
 * @param {object) arrSpans (Optional) Array with spans formed on previous call
 */
southprojects.Grid.prototype.outputFields = function(arrHtml, arrCols, boolFixed, boolSecondRow, arrSpans) {
  arrHtml.push('<tr id="spgrid');
  arrHtml.push(this.id);
  arrHtml.push('Head');
  if (boolFixed) {
    arrHtml.push('Fixed');
  }
  if (boolSecondRow) {
    arrHtml.push('2');
  }
  arrHtml.push('" class="spgridRow spgridRow0 spgridRowEven"');
  if (this.data.headerStyle) {
    arrHtml.push(' style="');
    arrHtml.push(this.data.headerStyle);
    arrHtml.push('"');
  }
  arrHtml.push('>');
  // Get field number
  var iFields = this.fields.length;
  var iShow = boolFixed ? this.config.fixedLeft : iFields;
  // Get spans
  var boolTwoRows = false;
  if (!arrSpans) {
    arrSpans = [];
    for (var iField = 0; iField < iFields; iField++) {
      // Get span
      var iSpan = this.getFieldSpan(this.fields[iField]);
      if (iSpan) {
        // Remove hidden fields from span
        for (var iSpanOffset = 0; iSpanOffset < iSpan; iSpanOffset++) {
          var objSpanField = this.fields[iField + iSpanOffset];
          if (!objSpanField || objSpanField.hidden) {
            iSpan--;
          }
        }
        if (iSpan) {
          arrSpans[iField] = iSpan;
          if (!boolSecondRow) {
            boolTwoRows = true;
          }
        }
      }
    }
  }
  // Display fields
  var iCols = arrCols.length;
  var boolHiddenCols = (!boolFixed && this.config.fixedLeft);
  var iCol = 0;
  var iSpan = 0
  for (var iField = 0; iField < iFields && iField < iShow; iField++) {
    // Get field
    var objField = this.fields[iField];
    // Skip hidden columns
    if (!objField || objField.hidden) {
      continue;
    }
    // Get span
    if (arrSpans[iField]) {
      iSpan += arrSpans[iField];
    }
    // Display field
    var boolHidden = (boolHiddenCols && iCol < this.config.fixedLeft);
    if (!boolSecondRow) {
      // Show all columns and spans instead of spanned columns
      if (iSpan) {
        // Display span
        this.outputSpan(arrHtml, iCols, objField, iCol, iSpan, boolHidden);
        iCol += iSpan;
        iField += iSpan - 1;
        iSpan = 0;
      } else {
        this.outputField(arrHtml, iCols, objField, iCol, boolHidden,
         boolTwoRows);
        iCol++;
      }
    } else {
      // Show only spanned columns
      if (iSpan) {
        this.outputField(arrHtml, iCols, objField, iCol, boolHidden);
        // Decrement span
        iSpan--;
      }
      iCol++;
    }
  }
  arrHtml.push('</tr>');
  // Display second row if needed
  if (boolTwoRows) {
    this.outputFields(arrHtml, arrCols, boolFixed, true, arrSpans);
  }
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {number} iCols Visible column count
 * @param {number} objField Field object
 * @param {number} iCol Visible column number
 * @param {number} iSpan Number of spanned columns
 * @param {boolean} boolHidden (Optional) Indicates that this is hidden part of
 * fixed part of header
 */
southprojects.Grid.prototype.outputSpan = function(arrHtml, iCols, objField, iCol, iSpan, boolHidden) {
  var strCol = iCol.toString();
  var arrClass = [];
  arrClass.push('spgridCell spgridCell');
  arrClass.push(strCol);
  arrClass.push(' spgridCell');
  arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
  if (iCol + iSpan == iCols) { // Last column
    arrClass.push(' spgridCellLast');
  }
  var strClass = arrClass.join('');
  arrClass.push(' spgridCellActive spgridCellActive');
  arrClass.push(strCol);
  arrClass.push(' spgridCellActive');
  arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
  if (iCol + iSpan == iCols) { // Last column
    arrClass.push(' spgridCellActiveLast');
  }
  var strClassActive = arrClass.join('');
  arrHtml.push('<td colspan="');
  arrHtml.push(iSpan);
  arrHtml.push('" class="');
  arrHtml.push(strClass);
  arrHtml.push('"');
  if (!boolHidden) {
    arrHtml.push(' onmouseover="this.className=\'');
    arrHtml.push(strClassActive);
    arrHtml.push('\'" onmouseout="this.className=\'');
    arrHtml.push(strClass);
    arrHtml.push('\'"');
  }
  if (objField.spanStyle) {
    arrHtml.push(' style="');
    arrHtml.push(objField.spanStyle);
    arrHtml.push('"');
  }
  arrHtml.push('>');
  arrHtml.push(objField.spanTitle);
  arrHtml.push('</td>');
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {number} iCols Visible column count
 * @param {number} objField Field object
 * @param {number} iCol Visible column number
 * @param {boolean} boolHidden (Optional) Indicates that this is hidden part of
 * fixed part of header
 * @param {boolean} boolTwoRows (Optional) Indicates that this cell spans two
 * rows
 */
southprojects.Grid.prototype.outputField = function(arrHtml, iCols, objField, iCol, boolHidden, boolTwoRows) {
  var strCol = iCol.toString();
  var arrClass = [];
  arrClass.push('spgridCell spgridCell');
  arrClass.push(strCol);
  arrClass.push(' spgridCell');
  arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
  if (iCol + 1 == iCols) { // Last column
    arrClass.push(' spgridCellLast');
  }
  if (objField.sorted) { // Sorted ascending
    arrClass.push(' spgridSortedAsc');
  } else if (objField.sortedDesc) { // Sorted descending
    arrClass.push(' spgridSortedDesc');
  }
  var strClass = arrClass.join('');
  arrClass.push(' spgridCellActive spgridCellActive');
  arrClass.push(strCol);
  arrClass.push(' spgridCellActive');
  arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
  if (iCol + 1 == iCols) { // Last column
    arrClass.push(' spgridCellActiveLast');
  }
  if (objField.sorted) { // Sorted ascending
    arrClass.push(' spgridSortedAscActive');
  } else if (objField.sortedDesc) { // Sorted descending
    arrClass.push(' spgridSortedDescActive');
  }
  var strClassActive = arrClass.join('');
  arrHtml.push('<td');
  if (boolTwoRows) {
    arrHtml.push(' rowspan="2"');
  }
  arrHtml.push(' class="');
  arrHtml.push(strClass);
  arrHtml.push('" id="spgrid');
  arrHtml.push(this.id);
  arrHtml.push('Field');
  arrHtml.push(objField.i);
  if (boolHidden) {
    arrHtml.push('Hidden"');
  } else {
    arrHtml.push('" onmouseover="this.className=\'');
    arrHtml.push(strClassActive);
    arrHtml.push('\'" onmouseout="this.className=\'');
    arrHtml.push(strClass);
    arrHtml.push('\'"');
    if (!objField.nosort) {
      arrHtml.push(' onclick="southprojects.Grid.sort(\'');
      arrHtml.push(this.id);
      arrHtml.push("','");
      arrHtml.push(objField.i);
      arrHtml.push('\')"');
    }
  }
  if (objField.style) {
    arrHtml.push(' style="');
    arrHtml.push(objField.style);
    arrHtml.push('"');
  }
  // div is needed to be able to set column width
  // span is needed to be able to set right margin for arrow
  arrHtml.push('><div class="spgridDiv');
  // Set cell type
  if (this.getClassByType) {
    arrHtml.push(' ');
    arrHtml.push(this.getClassByType(this.getFieldType(objField)));
  }
  arrHtml.push('"><span class="spgridSpan">');
  arrHtml.push(objField.title);
  arrHtml.push('</span></div></td>');
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {object} arrCols Array with field objects to output
 * @param {object} arrRows Array with row objects to output
 * @param {object} iRow Index in arrRows array
 * @param {boolean) boolFixed (Optional) Indicates that this is fixed part of
 * row
 */
southprojects.Grid.prototype.outputRow = function(arrHtml, arrCols, arrRows, iRow, boolFixed) {
  // Get row
  var objRow = arrRows[iRow];
  if (!objRow) {
    return;
  }
  var iRowN = iRow + 1;
  var strRow = iRowN.toString();
  var arrClass = [];
  arrClass.push('spgridRow spgridRow');
  arrClass.push(strRow);
  arrClass.push(' spgridRow');
  arrClass.push(iRowN % 2 == 1 ? 'Odd' : 'Even');
  if (iRowN == arrRows.length) { // Last row
    arrClass.push(' spgridRowLast');
  }
  if (this.config.selectRows && objRow.selected) {
    arrClass.push(' spgridRowSelected spgridRowSelected');
    arrClass.push(strRow);
    arrClass.push(' spgridRowSelected');
    arrClass.push(iRowN % 2 == 1 ? 'Odd' : 'Even');
    if (iRowN == arrRows.length) { // Last row
      arrClass.push(' spgridRowSelectedLast');
    }
  }
  if (objRow.invalid) {
    arrClass.push(' spgridRowInvalid spgridRowInvalid');
    arrClass.push(strRow);
    arrClass.push(' spgridRowInvalid');
    arrClass.push(iRowN % 2 == 1 ? 'Odd' : 'Even');
    if (iRowN == arrRows.length) { // Last row
      arrClass.push(' spgridRowInvalidLast');
    }
  }
  var strClass = arrClass.join('');
  var arrClassActive = [];
  arrClassActive.push(' spgridRowActive spgridRowActive');
  arrClassActive.push(strRow);
  arrClassActive.push(' spgridRowActive');
  arrClassActive.push(iRowN % 2 == 1 ? 'Odd' : 'Even');
  if (iRowN == arrRows.length) { // Last row
    arrClassActive.push(' spgridRowActiveLast');
  }
  var strClassActive = arrClassActive.join('');
  arrHtml.push('<tr id="spgrid');
  arrHtml.push(this.id);
  arrHtml.push('Row');
  arrHtml.push(objRow.i);
  if (boolFixed) {
    arrHtml.push('Fixed');
  }
  arrHtml.push('" class="');
  arrHtml.push(strClass);
  arrHtml.push('" onmouseover="if(this.className.indexOf( \
   \'spgridRowActive\')==-1){this.className+=\'');
  arrHtml.push(strClassActive);
  arrHtml.push("'");
  if (boolFixed) {
    arrHtml.push(";document.getElementById('spgrid");
    arrHtml.push(this.id);
    arrHtml.push('Row');
    arrHtml.push(objRow.i);
    arrHtml.push("').className+='");
    arrHtml.push(strClassActive);
    arrHtml.push("'");
  } else if (this.config.fixedLeft > 0) {
    arrHtml.push(";document.getElementById('spgrid");
    arrHtml.push(this.id);
    arrHtml.push('Row');
    arrHtml.push(objRow.i);
    arrHtml.push("Fixed').className+='");
    arrHtml.push(strClassActive);
    arrHtml.push("'");
  }
  arrHtml.push("}\" onmouseout=\"this.className=this.className.replace( \
   /spgridRowActive[^ ]*/g,'').split(/\\s+/).join(' ')");
  if (boolFixed) {
    arrHtml.push(";var objRow=document.getElementById('spgrid");
    arrHtml.push(this.id);
    arrHtml.push('Row');
    arrHtml.push(objRow.i);
    arrHtml.push("');objRow.className=objRow.className.replace( \
     /spgridRowActive[^ ]*/g,'').split(/\\s+/).join(' ')");
  } else if (this.config.fixedLeft > 0) {
    arrHtml.push(";var objRow=document.getElementById('spgrid");
    arrHtml.push(this.id);
    arrHtml.push('Row');
    arrHtml.push(objRow.i);
    arrHtml.push("Fixed');objRow.className=objRow.className.replace( \
     /spgridRowActive[^ ]*/g,'').split(/\\s+/).join(' ')");
  }
  arrHtml.push('" ondblclick="this.onmouseout()" style="');
  if (objRow.style) {
    arrHtml.push(objRow.style);
  }
  arrHtml.push('">');
  // Display cells
  var iFields = this.fields.length;
  var iShow = boolFixed ? this.config.fixedLeft : iFields;
  var iCols = arrCols.length;
  var boolHiddenCols = (!boolFixed && this.config.fixedLeft);
  for (var iField = 0, iCol = 0; iField < iFields && iField < iShow; iField++) {
    var boolHidden = (boolHiddenCols && iCol < this.config.fixedLeft);
    iCol += this.outputCell(arrHtml, iRow, objRow, iCols, iField, iCol,
     boolHidden);
  }
  arrHtml.push('</tr>');
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {object} iRow Number of row on the page
 * @param {object} objRow Row object
 * @param {number} iCols Visible column count
 * @param {number} iField Column id
 * @param {number} iCol Visible column number
 * @param {boolean} boolHidden (Optional) Indicates that field is hidden
 * @return Number of displayed cells (0 or 1)
 * @type number
 */
southprojects.Grid.prototype.outputCell = function(arrHtml, iRow, objRow, iCols, iField, iCol, boolHidden) {
  // Get field
  var objField = this.fields[iField];
  // Skip hidden columns
  if (!objField || objField.hidden) {
    return 0;
  }
  // Get cell
  var objCell = objRow.cells[iField];
  if (!objCell) {
    return 0;
  }
  var strCol = iCol.toString();
  var arrClass = [];
  arrClass.push('spgridCell spgridCell');
  arrClass.push(strCol);
  arrClass.push(' spgridCell');
  arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
  if (iCol + 1 == iCols) { // Last cell
    arrClass.push(' spgridCellLast');
  }
  if (this.config.selectCells && objCell.selected) {
    arrClass.push(' spgridCellSelected spgridCellSelected');
    arrClass.push(strCol);
    arrClass.push(' spgridCellSelected');
    arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
    if (iCol + 1 == iCols) { // Last cell
      arrClass.push(' spgridCellSelectedLast');
    }
  }
  if (objCell.invalid) {
    arrClass.push(' spgridCellInvalid spgridCellInvalid');
    arrClass.push(strCol);
    arrClass.push(' spgridCellInvalid');
    arrClass.push(iCol % 2 == 1 ? 'Odd' : 'Even');
    if (iCol + 1 == iCols) { // Last cell
      arrClass.push(' spgridCellInvalidLast');
    }
  }
  var strClass = arrClass.join('');
  var arrClassActive = [];
  arrClassActive.push(' spgridCellActive spgridCellActive');
  arrClassActive.push(strCol);
  arrClassActive.push(' spgridCellActive');
  arrClassActive.push(iCol % 2 == 1 ? 'Odd' : 'Even');
  if (iCol + 1 == iCols) { // Last cell
    arrClassActive.push(' spgridCellActiveLast');
  }
  var strClassActive = arrClassActive.join('');
  arrHtml.push('<td class="');
  arrHtml.push(strClass);
  arrHtml.push('" id="spgrid');
  arrHtml.push(this.id);
  arrHtml.push('Row');
  arrHtml.push(objRow.i);
  arrHtml.push('Cell');
  arrHtml.push(objCell.i);
  if (boolHidden) {
    arrHtml.push('Hidden"');
  } else {
    arrHtml.push('" onmouseover="if(this.className.indexOf( \
     \'spgridCellActive\')==-1)this.className+=\'');
    arrHtml.push(strClassActive);
    arrHtml.push('\'" onmouseout="this.className=this.className.replace( \
     /spgridCellActive[^ ]*/g,\'\').split(/\\s+/).join(\' \')" \
     onclick="southprojects.Grid.rowOnClick(\'');
    arrHtml.push(this.id);
    arrHtml.push("','");
    arrHtml.push(objRow.i);
    arrHtml.push("','");
    arrHtml.push(objCell.i);
    arrHtml.push('\')" ondblclick="this.onmouseout(); \
     southprojects.Grid.rowOnDblClick(\'');
    arrHtml.push(this.id);
    arrHtml.push("','");
    arrHtml.push(objRow.i);
    arrHtml.push("','");
    arrHtml.push(objCell.i);
    arrHtml.push('\')"');
  }
  var strStyle = this.getCellStyle(objCell, iRow);
  if (strStyle) {
    arrHtml.push(' style="');
    arrHtml.push(strStyle);
    arrHtml.push('"');
  }
  arrHtml.push('>');
  this.outputCellValue(arrHtml, objField, objRow, objCell);
  arrHtml.push('</td>');
  return 1;
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 * @param {object} objField Field object
 * @param {object} objRow Row object
 * @param {object} objCell Cell object
 */
southprojects.Grid.prototype.outputCellValue = function(arrHtml, objField, objRow, objCell) {
  // div is needed to be able to set column width
  arrHtml.push('<div class="spgridDiv');
  // Set cell type
  if (this.getClassByType) {
    arrHtml.push(' ');
    arrHtml.push(this.getClassByType(this.getFieldType(objField)));
  }
  arrHtml.push('">');
  var strData = this.getCellData(objCell);
  if (strData && typeof strData != 'string') {
    strData = strData.toString();
  }
  // Empty cell may cause visual problems in editable grid extension
  if (!strData || !strData.length) {
    strData = '&nbsp;';
  }
  arrHtml.push(strData);
  arrHtml.push('</div>');
};

/**
 * Forms grid output.
 *
 * @private
 * @param {object} arrHtml Output array
 */
southprojects.Grid.prototype.outputPagination = function(arrHtml) {
  if (typeof this.config.callbackPaginationDisplay == 'function' ||
   this.config.rowsPerPage <= 0) {
    // No pagination
    return;
  }
  if (this.paginationContainer) {
    arrHtml.push('<div id="spgrid');
    arrHtml.push(this.id);
    arrHtml.push('PaginationContainer"><div><table class="');
    arrHtml.push(this.getClassName({prefix: 'spgrid'}));
    arrHtml.push('" cellpadding="0" cellspacing="0" \
     style="width:100%"><tbody>');
  }
  arrHtml.push('<tr><td class="spgridPagination">Page');
  if (this.currentPage > 0) {
    // Don't display previous on the first page
    arrHtml.push(' <span class="spgridFirstPage" \
     onclick="southprojects.Grid.firstPage(\'');
    arrHtml.push(this.id);
    arrHtml.push('\')">&lt;&lt;</span> <span class="spgridPrevPage" \
     onclick="southprojects.Grid.previousPage(\'');
    arrHtml.push(this.id);
    arrHtml.push('\')">&lt;</span>');
  }
  // Get number of pages
  var iPages = this.totalPages();
  // Display up to 10 pages
  var iCurrentPage = this.getCurrentPageNumber();
  var iFirstPage = iCurrentPage - 4;
  var iLastPage = iCurrentPage + 5;
  for (var iPage = iFirstPage; iPage < iLastPage && iPage <= iPages; iPage++) {
    if (iPage < 1) {
      // Current page < 10
      continue;
    }
    arrHtml.push(' <span class="spgrid');
    if (iPage == iCurrentPage) {
      arrHtml.push('CurrentPage">');
    } else {
      arrHtml.push('Pagina" onclick="southprojects.Grid.gotoPage(\'');
      arrHtml.push(this.id);
      arrHtml.push("','");
      arrHtml.push(iPage);
      arrHtml.push('\')">');
    }
    arrHtml.push(iPage);
    arrHtml.push('</span>');
  }
  if (this.currentPage < iPages - 1) {
    // Don't display next on the last page
    arrHtml.push(' <span class="spgridNextPage" \
     onclick="southprojects.Grid.nextPage(\'');
    arrHtml.push(this.id);
    arrHtml.push('\')">&gt;</span> <span class="spgridLastPage" \
     onclick="southprojects.Grid.lastPage(\'');
    arrHtml.push(this.id);
    arrHtml.push('\')">&gt;&gt;</span>');
  }
  arrHtml.push(' of ');
  arrHtml.push(iPages);
  arrHtml.push(' (');
  arrHtml.push(this.recordsDisplayed());
  arrHtml.push(' registros)</td></tr>');
  if (this.paginationContainer) {
    arrHtml.push('</tbody></table></div></div>');
  }
};

/**
 * Displays grid. Forms new grid as plain HTML. Pushes strings into array, then
 * joins array to achieve maximum speed. Replaces previous contents of container
 * element with formed grid. Adds classes that can be used to create different
 * themes. Adds user defined styles.
 * @private
 */
southprojects.Grid.prototype.refreshContainer = function() {
  // Check container
  if (!this.container) {
    alert("Can not find container for grid");
    return;
  }
  this.container.style.position = 'relative';
  // Get columns to display
  var arrCols = [];
  for (var iField = 0; iField < this.fields.length; iField++) {
    var objField = this.fields[iField];
    // Skip hidden columns
    if (!objField || objField.hidden) {
      continue;
    }
    arrCols.push(objField);
  }
  // Get rows to display
  var arrRows = this.applyPaging();
  // Get totals
  var arrTotals;
  if (this.getTotals) {
    arrTotals = this.getTotals();
  }
  // Display grid
  if (this.headerContainer) {
    this.headerContainer.style.position = 'relative';
    var arrHtml = [];
    arrHtml.push('<div id="spgrid');
    arrHtml.push(this.id);
    arrHtml.push('Container"><div>');
    // Header
    this.outputTableOpen(arrHtml);
    this.outputFields(arrHtml, arrCols);
    this.outputTableClose(arrHtml);
    arrHtml.push('</div>');
    // Fixed header
    if (this.config.fixedLeft > 0) {
      arrHtml.push('<div id="spgrid');
      arrHtml.push(this.id);
      if (this.headerContainer.style.setAttribute) {
        // IE
        arrHtml.push('FixedLeft" style="position:absolute;top:0px; \
         left:expression(this.offsetParent.scrollLeft+\'px\')"><div>');
      } else {
        // Other browsers don't support expressions yet
        arrHtml.push('FixedLeft" style="position:absolute;top:0px;left:');
        arrHtml.push(this.container.scrollLeft);
        arrHtml.push('px"><div>');
      }
      this.outputTableOpen(arrHtml, true);
      this.outputFields(arrHtml, arrCols, true);
      this.outputTableClose(arrHtml);
      arrHtml.push('</div></div>');
    }
    arrHtml.push('</div>');
    // Draw header
    this.headerContainer.innerHTML = arrHtml.join('');
    // Clean container
    this.container.innerHTML = '';
    // Data
    var objContainer = southprojects.Utils.createElement('div', this.container, true);
    objContainer.id = 'spgrid' + this.id + 'DataContainer';
    // Rows
    arrHtml = [];
    this.outputTableOpen(arrHtml);
    for (var iRow = 1, iRowNum = 0; iRow <= arrRows.length; iRow++, iRowNum++) {
      if (this.headerContainer && iRowNum > 0 && iRowNum % 10 == 0) {
        // Draw 10 rows
        this.outputTableClose(arrHtml);
        var objDiv = southprojects.Utils.createElement('div', objContainer, true);
        objDiv.innerHTML = arrHtml.join('');
        // Next 10 rows
        arrHtml = [];
        this.outputTableOpen(arrHtml);
      }
      this.outputRow(arrHtml, arrCols, arrRows, iRowNum);
    }
    // Totals
    if (arrTotals && !this.totalsContainer && this.outputTotals) {
      this.outputTotals(arrHtml, arrCols, arrTotals);
    }
    // Pagination
    if (this.paginationContainer) {
      // Draw the rest of rows
      this.outputTableClose(arrHtml);
      var objDiv = southprojects.Utils.createElement('div', objContainer, true);
      objDiv.innerHTML = arrHtml.join('');
      // Pagination
      arrHtml = [];
      this.outputPagination(arrHtml);
      this.paginationContainer.innerHTML = arrHtml.join('');
    } else {
      // Pagination
      arrHtml.push('</tbody></table></td></tr>');
      this.outputPagination(arrHtml);
      arrHtml.push('</tbody></table>');
      // Draw the rest of rows
      var objDiv = southprojects.Utils.createElement('div', objContainer, true);
      objDiv.innerHTML = arrHtml.join('');
    }
    // Fixed columns
    if (this.config.fixedLeft > 0) {
      var objFixed = southprojects.Utils.createElement('div', objContainer, true);
      objFixed.id = 'spgrid' + this.id + 'DataFixedLeft';
      if (objFixed.style.setAttribute) {
        // IE
        objFixed.style.setAttribute('cssText', 'position:absolute;top:0px; \
         left:expression(this.offsetParent.scrollLeft+"px")', 0);
      } else {
        // Other browsers don't support expressions yet
        objFixed.style.position = 'absolute';
        objFixed.style.top = '0px';
        objFixed.style.left = this.container.scrollLeft + 'px';
      }
      var arrHtml = [];
      this.outputTableOpen(arrHtml, true);
      // Rows
      for (var iRow = 1, iNum = 0; iRow <= arrRows.length; iRow++, iNum++) {
        if (this.headerContainer && iNum > 0 && iNum % 10 == 0) {
          // Draw 10 rows
          this.outputTableClose(arrHtml);
          var objDiv = southprojects.Utils.createElement('div', objFixed, true);
          objDiv.innerHTML = arrHtml.join('');
          // Next 10 rows
          arrHtml = [];
          this.outputTableOpen(arrHtml, true);
        }
        this.outputRow(arrHtml, arrCols, arrRows, iNum, true);
      }
      // Totals
      if (arrTotals && !this.totalsContainer && this.outputTotals) {
        this.outputTotals(arrHtml, arrCols, arrTotals, true);
      }
      this.outputTableClose(arrHtml);
      var objDiv = southprojects.Utils.createElement('div', objFixed, true);
      objDiv.innerHTML = arrHtml.join('');
    }
  } else {
    // Table
    var arrHtml = [];
    arrHtml.push('<div id="spgrid');
    arrHtml.push(this.id);
    arrHtml.push('Container"><div>');
    this.outputTableOpen(arrHtml);
    // Header
    this.outputFields(arrHtml, arrCols);
    // Rows
    for (var iRow = 1, iNum = 0; iRow <= arrRows.length; iRow++, iNum++) {
      this.outputRow(arrHtml, arrCols, arrRows, iNum);
    }
    // Totals
    if (arrTotals && !this.totalsContainer && this.outputTotals) {
      this.outputTotals(arrHtml, arrCols, arrTotals);
    }
    // Pagination
    if (this.paginationContainer) {
      // Draw table
      this.outputTableClose(arrHtml);
      arrHtml.push('</div></div>');
      this.container.innerHTML = arrHtml.join('');
      // Pagination
      arrHtml = [];
      this.outputPagination(arrHtml);
      this.paginationContainer.innerHTML = arrHtml.join('');
    } else {
      // Pagination
      arrHtml.push('</tbody></table></td></tr>');
      this.outputPagination(arrHtml);
      arrHtml.push('</tbody></table></div></div>');
      // Draw table
      this.container.innerHTML = arrHtml.join('');
    }
    // Fixed columns
    if (this.config.fixedLeft > 0) {
      var objFixed = southprojects.Utils.createElement('div',
       document.getElementById('spgrid' + this.id + 'Container'), true);
      objFixed.id = 'spgrid' + this.id + 'FixedLeft';
      if (objFixed.style.setAttribute) {
        // IE
        objFixed.style.setAttribute('cssText', 'position:absolute;top:0px; \
         left:expression(this.offsetParent.scrollLeft+"px")', 0);
      } else {
        // Other browsers don't support expressions yet
        objFixed.style.position = 'absolute';
        objFixed.style.top = '0px';
        objFixed.style.left = this.container.scrollLeft + 'px';
      }
      var arrHtml = [];
      this.outputTableOpen(arrHtml, true);
      // Header
      this.outputFields(arrHtml, arrCols, true);
      // Rows
      for (var iRow = 1, iNum = 0; iRow <= arrRows.length; iRow++, iNum++) {
        this.outputRow(arrHtml, arrCols, arrRows, iNum, true);
      }
      // Totals
      if (arrTotals && !this.totalsContainer && this.outputTotals) {
        this.outputTotals(arrHtml, arrCols, arrTotals, true);
      }
      this.outputTableClose(arrHtml);
      var objDiv = southprojects.Utils.createElement('div', objFixed, true);
      objDiv.innerHTML = arrHtml.join('');
    }
  }
  // If there is separate container for totals
  if (arrTotals && this.totalsContainer && this.outputTotals) {
    this.totalsContainer.style.position = 'relative';
    var arrHtml = [];
    arrHtml.push('<div id="spgrid');
    arrHtml.push(this.id);
    arrHtml.push('TotalsContainer"><div>');
    // Totals
    this.outputTableOpen(arrHtml);
    this.outputTotals(arrHtml, arrCols, arrTotals);
    this.outputTableClose(arrHtml);
    arrHtml.push('</div>');
    // Fixed totals
    if (this.config.fixedLeft > 0) {
      arrHtml.push('<div id="spgrid');
      arrHtml.push(this.id);
      if (this.totalsContainer.style.setAttribute) {
        // IE
        arrHtml.push('TotalsFixedLeft" style="position:absolute;top:0px; \
         left:expression(this.offsetParent.scrollLeft+\'px\')"><div>');
      } else {
        // Other browsers don't support expressions yet
        arrHtml.push('TotalsFixedLeft" style="position:absolute;top:0px;left:');
        arrHtml.push(this.container.scrollLeft);
        arrHtml.push('px"><div>');
      }
      this.outputTableOpen(arrHtml, true);
      this.outputTotals(arrHtml, arrCols, arrTotals, true);
      this.outputTableClose(arrHtml);
      arrHtml.push('</div></div>');
    }
    arrHtml.push('</div>');
    // Draw totals
    this.totalsContainer.innerHTML = arrHtml.join('');
  }
  // Synchronize scrolling
  this.synchronizeScrolling();
  // Custom totals
  if (typeof this.config.callbackTotalsDisplay == 'function') {
    this.config.callbackTotalsDisplay(this, arrTotals);
  } else if (arrTotals &&
   typeof this.config.callbackTotalDisplay == 'function') {
    for (var iRow = 0; iRow < arrTotals.length; iRow++) {
      this.config.callbackTotalDisplay(this, arrTotals[iRow]);
    }
  }
  // Custom pagination
  if (typeof this.config.callbackPaginationDisplay == 'function' &&
   this.config.rowsPerPage > 0) {
    this.config.callbackPaginationDisplay(this);
  }
};

/**
 * Visualizes row selection.
 *
 * @private
 * @param {object} objRow Row object
 */
southprojects.Grid.prototype.visualizeSelectRow = function(objRow) {
  // Check arguments
  if (!objRow) {
    return;
  }
  // Get table row element
  var objTr = document.getElementById('spgrid' + this.id + 'Row' + objRow.i);
  if (objTr) { // Can be on different page
    // Get row number because rows can be sorted and filtered
    /spgridRow(\d+)/.exec(objTr.className);
    var strRow = RegExp.$1;
    // Select row
    var arrClassSelected = [];
    arrClassSelected.push(' spgridRowSelected spgridRowSelected');
    arrClassSelected.push(strRow);
    arrClassSelected.push(' spgridRowSelected');
    arrClassSelected.push(strRow % 2 == 1 ? 'Odd' : 'Even');
    if (objTr.className.indexOf('spgridRowLast') >= 0) { // Last row
      arrClassSelected.push(' spgridRowSelectedLast');
    }
    var strClassSelected = arrClassSelected.join('');
    objTr.className += strClassSelected;
    // Get fixed part of row
    objTr = document.getElementById('spgrid' + this.id + 'Row' + objRow.i +
     'Fixed');
    if (objTr) {
      objTr.className += strClassSelected;
    }
  }
};

/**
 * Visualizes row unselection.
 *
 * @private
 * @param {object} objRow Row object
 */
southprojects.Grid.prototype.visualizeUnselectRow = function(objRow) {
  // Check arguments
  if (!objRow) {
    return;
  }
  // Get table row element
  var objTr = document.getElementById('spgrid' + this.id + 'Row' + objRow.i);
  if (objTr) { // Can be on different page
    objTr.className = objTr.className.replace(/spgridRowSelected[^ ]*/g, '')
     .split(/\s+/).join(' ');
    // Get fixed part of row
    objTr = document.getElementById('spgrid' + this.id + 'Row' + objRow.i +
     'Fixed');
    if (objTr) {
      objTr.className = objTr.className.replace(/spgridRowSelected[^ ]*/g, '')
       .split(/\s+/).join(' ');
    }
  }
};

/**
 * Visualizes cell selection.
 *
 * @private
 * @param {object} objCell Cell object
 */
southprojects.Grid.prototype.visualizeSelectCell = function(objCell) {
  // Check arguments
  if (!objCell) {
    return;
  }
  // Get table cell element
  var objTd = document.getElementById('spgrid' + this.id + 'Row' + objCell.r +
   'Cell' + objCell.i);
  if (objTd) { // Can be on different page
    // Select cell
    var arrClassSelected = [];
    arrClassSelected.push(' spgridCellSelected spgridCellSelected');
    arrClassSelected.push(objCell.i);
    arrClassSelected.push(' spgridCellSelected');
    arrClassSelected.push(objCell.i % 2 == 1 ? 'Odd' : 'Even');
    if (objCell.i == this.fields.length - 1) { // Last cell
      arrClassSelected.push(' spgridCellSelectedLast');
    }
    objTd.className += arrClassSelected.join('');
  }
};

/**
 * Visualizes cell unselection.
 *
 * @private
 * @param {object} objCell Cell object
 */
southprojects.Grid.prototype.visualizeUnselectCell = function(objCell) {
  // Check arguments
  if (!objCell) {
    return;
  }
  // Get table row element
  var objTd = document.getElementById('spgrid' + this.id + 'Row' + objCell.r +
   'Cell' + objCell.i);
  if (objTd) { // Can be on different page
    objTd.className = objTd.className.replace(/spgridCellSelected[^ ]*/g, '')
     .split(/\s+/).join(' ');
  }
};

/**
 * Displays filter out forms.
 *
 * @private
 * @param {object} objFilterOut Object passed through config option
 * @param {object} arrVals Array of unique column values
 */
southprojects.Grid.prototype.visualizeFilterOut = function(objFilterOut, arrVals) {
  // Get container
  if (!objFilterOut) {
    return;
  }
  var objContainer = southprojects.Widget.getElementById(objFilterOut.container);
  if (!objContainer) {
    return;
  }
  // Get columns
  var arrCols = objFilterOut.column;
  if (!(arrCols instanceof Array)) {
    arrCols = [arrCols];
  }
  // Get fields
  var arrFields = [];
  for (var iCol = 0; iCol < arrCols.length; iCol++) {
    var objField = this.fields[arrCols[iCol]];
    if (!objField) {
      continue;
    }
    arrFields.push(objField);
  }
  if (!arrFields.length) {
    return;
  }
  // Join column numbers
  var strCols = arrCols.join(',');
  // "Select all" and "Clear" links
  var arrHtml = [];
  arrHtml.push('<div><a href="javascript:void(0)" \
   onclick="southprojects.Grid.checkboxSelectAllOnClick(\'');
  arrHtml.push(this.id);
  arrHtml.push("',[");
  arrHtml.push(strCols);
  arrHtml.push('])">Select all</a> | <a href="javascript:void(0)" \
   onclick="southprojects.Grid.checkboxClearAllOnClick(\'');
  arrHtml.push(this.id);
  arrHtml.push("',[");
  arrHtml.push(strCols);
  arrHtml.push('])">Clear</a></div>');
  // Checkboxes
  for (var iVal = 0; iVal < arrVals.length; iVal++) {
    var strVal = arrVals[iVal].v + '';
    var strEscaped = escape(strVal);
    arrHtml.push('<div><input type="checkbox" ');
    // Check if this value is hidden
    for (var iField = 0; iField < arrFields.length; iField++) {
      var objField = arrFields[iField];
      if (!(objField.hiddenValues instanceof Array) ||
       southprojects.Utils.arrIndexOf(objField.hiddenValues, strVal) < 0) {
        arrHtml.push('checked ');
        break;
      }
    }
    arrHtml.push('onclick="');
    if (objFilterOut.onclick) {
      arrHtml.push(objFilterOut.onclick);
      arrHtml.push(';');
    }
    arrHtml.push("southprojects.Grid.checkboxOnClick('");
    arrHtml.push(this.id);
    arrHtml.push("',[");
    arrHtml.push(strCols);
    arrHtml.push("],unescape('");
    arrHtml.push(strEscaped);
    arrHtml.push("'),this.checked)\" /><a href=\"javascript:void(0)\" \
     onclick=\"southprojects.Grid.checkboxLinkOnClick('");
    arrHtml.push(this.id);
    arrHtml.push("',[");
    arrHtml.push(strCols);
    arrHtml.push("],unescape('");
    arrHtml.push(strEscaped);
    arrHtml.push("'))\">");
    arrHtml.push(strVal);
    arrHtml.push('</a></div>');
  }
  objContainer.innerHTML = arrHtml.join('');
};

/**
 * Returns container for "Loading" or "Updating" image.
 *
 * @private
 * @return Container for "Loading" or "Updating" image
 * @type object
 */
southprojects.Grid.prototype.getBusyContainer = function() {
  // Container must exist
  if (!this.container) {
    return;
  }
  // Get container
  // Set fixed width of container if it is auto
  var objContainer = this.container.firstChild;
  if (objContainer) {
    objContainer = objContainer.firstChild;
    if (objContainer) {
      var objTable = objContainer.firstChild;
      if (objTable && objTable.tagName.toLowerCase() == 'table') {
        if (objTable.offsetWidth > this.container.offsetWidth) {
          // Container has fixed width
          objContainer = this.container;
        } else {
          // Container has auto width
          objContainer = this.container;
          objContainer.style.overflow = 'visible';
          objContainer.style.width = objTable.offsetWidth + 'px';
        }
      } else {
        // Already busy
        objContainer = null;
      }
    }
  }
  if (!objContainer) {
    objContainer = this.container;
  };
  return objContainer;
};

/**
 * Displays "Loading" image.
 * @private
 */
southprojects.Grid.prototype.displayLoading = function() {
  // Remove "Updating"
  this.removeUpdating();
  // Get container
  var objContainer = this.getBusyContainer();
  if (!objContainer) {
    return;
  }
  // Display image
  southprojects.Transport.showBusy({
    busyContainer: objContainer
  });
};

/**
 * Removes "Loading" image.
 * @private
 */
southprojects.Grid.prototype.removeLoading = function() {
  // Get container
  var objContainer = this.getBusyContainer();
  if (!objContainer) {
    return;
  }
  // Remove image
  southprojects.Transport.removeBusy({
    busyContainer: objContainer
  });
};

/**
 * Displays "Updating" image.
 * @private
 */
southprojects.Grid.prototype.displayUpdating = function() {
  // Blinking message is irritative. Display it only when it's really needed.
  // Message can be safely skipped in large grids when there are < 50 of lines
  // to display because rendering takes most of time.
  if (!this.config.dataOnDemand && !this.config.totals.length) {
    var iFields = this.fields.length;
    if (this.totalRecords() * iFields < 2500) {
      if (this.recordsDisplayed() * iFields < 250) {
        return;
      }
      if (this.config.rowsPerPage > 0) {
        if (this.config.rowsPerPage * iFields < 250) {
          return;
        }
      }
    }
  }
  // Remove "Loading"
  this.removeLoading();
  // Get container
  var objContainer = this.getBusyContainer();
  if (!objContainer) {
    return;
  }
  // Display image
  southprojects.Transport.showBusy({
    busyContainer: objContainer,
    busyImage: 'zpupdating.gif'
  });
};

/**
 * Removes "Updating" image.
 * @private
 */
southprojects.Grid.prototype.removeUpdating = function() {
  // Get container
  var objContainer = this.getBusyContainer();
  if (!objContainer) {
    return;
  }
  // Remove image
  southprojects.Transport.removeBusy({
    busyContainer: objContainer,
    busyImage: 'zpupdating.gif'
  });
};

/**
 * Returns table row element or undefined if row is on different page or not
 * found.
 *
 * @param {object} objRow Row object
 * @return Table row element
 * @type object
 */
southprojects.Grid.prototype.getRowElement = function(objRow) {
  if (objRow) {
    return document.getElementById('spgrid' + this.id + 'Row' + objRow.i);
  }
};

/**
 * Returns table row element or undefined if row is on different page or not
 * found.
 *
 * @param {object} objCell Cell object
 * @return Table row element
 * @type object
 */
southprojects.Grid.prototype.getRowElementByCell = function(objCell) {
  if (objCell) {
    return document.getElementById('spgrid' + this.id + 'Row' + objCell.r);
  }
};

/**
 * Returns table cell element or undefined if cell is on different page or not
 * found.
 *
 * @param {object} objCell Cell object
 * @return Table cell element
 * @type object
 */
southprojects.Grid.prototype.getCellElement = function(objCell) {
  if (objCell) {
    return document.getElementById('spgrid' + this.id + 'Row' + objCell.r +
     'Cell' + objCell.i);
  }
};

/**
 * Redraws specified cell without redrawing whole grid. Useful after changing of
 * cell value.
 *
 * @param {object} objCell Cell object
 */
southprojects.Grid.prototype.visualizeCellReadOnly = function(objCell) {
  var iGridId = this.id;
  var objRow = this.getRowByCell(objCell);
  var iRowId = this.getRowId(objRow);
  var iCellId = this.getCellId(objCell);
  // Get table row element
  var strRowId = 'spgrid' + iGridId + 'Row' + iRowId;
  var objTr = document.getElementById(strRowId);
  // Get table cell element
  var strCellId = strRowId + 'Cell' + iCellId;
  var objTd = document.getElementById(strCellId);
  // Can be on different page
  if (objTr && objTd) {
    // Redraw cell
    var arrHtml = [];
    this.outputCellValue(arrHtml, this.getFieldByCell(objCell), objRow,
     objCell);
    var strHtml = arrHtml.join('');
    objTd.innerHTML = strHtml;
    // Redraw hidden cell
    var objTdHidden = document.getElementById(strCellId + 'Hidden');
    if (objTdHidden) {
      objTdHidden.innerHTML = strHtml;
    }
    // Update row className
    objTr.className = objTr.className.replace(/spgridRowEditable[^ ]*/g, '')
     .replace(/spgridRowInvalid[^ ]*/g, '').split(/\s+/).join(' ');
    // Update fixed part of the row className
    var objTrFixed = document.getElementById(strRowId + 'Fixed');
    if (objTrFixed) {
      objTrFixed.className = objTrFixed.className
       .replace(/spgridRowEditable[^ ]*/g, '')
       .replace(/spgridRowInvalid[^ ]*/g, '').split(/\s+/).join(' ');
    }
    // Update cell className
    objTd.className = objTd.className.replace(/spgridCellEditable[^ ]*/g, '')
     .replace(/spgridCellInvalid[^ ]*/g, '').split(/\s+/).join(' ');
    // Validate cell
    if (!this.validateCell(objCell)) {
      // Update row className
      var arrClass = [];
      arrClass.push(' spgridRowInvalid spgridRowInvalid');
      arrClass.push(iRowId);
      arrClass.push(' spgridRowInvalid');
      arrClass.push(objTr.className.indexOf('spgridRowOdd') >= 0 ?
       'Odd' : 'Even');
      if (objTr.className.indexOf('spgridRowLast') >= 0) {
        // Last row
        arrClass.push(' spgridRowInvalidLast');
      }
      var strClass = arrClass.join('');
      objTr.className += strClass;
      // Update fixed part of the row className
      if (objTrFixed) {
        objTrFixed.className += strClass;
      }
      // Update cell className
      var arrClass = [];
      arrClass.push(' spgridCellInvalid spgridCellInvalid');
      arrClass.push(iCellId);
      arrClass.push(' spgridCellInvalid');
      arrClass.push(iCellId % 2 == 1 ? 'Odd' : 'Even');
      if (objTr.className.indexOf('spgridCellLast') >= 0) {
        // Last cell
        arrClass.push(' spgridCellInvalidLast');
      }
      objTd.className += arrClass.join('');
    }
  }
};
