// set of all grid components in the current page for use by the
// isii_JSGrid.updateScrollBars() function.
var gridSet = new Array(0);
var timeId = null;

/**
 * Used for printing out a grid table.
 *
 * @version 8.5
 * @author InetSoft Technology Corp
 * @depend util.js jscomponent.js
 */


/**
 * Used as data model for a grid table.
 *
 * @version 8.5
 * @author InetSoft Technology Corp
 * @depend util.js
 */

/**
 * Constructor.
 */
isii_JSGridModel = function() {
   this._class = "isii_JSGridModel";
   this._extend("isii_Object");
   var _this = this._this;

   // protected
   this.data = new Array(0);

   /**
    * Get column count.
    * @public.
    */
   this.getColCount = getColCountFunction;
   this.getColCount._class = this._class;

   function getColCountFunction() {
      var data = _this.data;

      if(data[0] && data[0].length) {
         return data[0].length;
      }

      return 0;
   }

   /**
    * Get column data type.
    * @param col the specified column number.
    * @public.
    */
   this.getColType = getColTypeFunction;
   this.getColType._class = this._class;

   function getColTypeFunction(col) {
      return "String";
   }

   /**
    * Get cell data.
    * @param row row index.
    * @param col column index.
    * @public.
    */
   this.getData = getDataFunction;
   this.getData._class = this._class;

   function getDataFunction(row, col) {
      var data = _this.data;

      if(data[row]) {
         return data[row][col];
      }

      return null;
   }

   /**
    * Set cell data.
    * @param row row index.
    * @param col column index.
    * @param value specified value.
    * @public.
    */
   this.setData = setDataFunction;
   this.setData._class = this._class;

   function setDataFunction(row, col, value, ignore) {
      var data = _this.data;
      data[row] = data[row] ? data[row] : [];
      data[row][col] = value;

      if(!ignore) {
         _this.resetSort(false);
      }

      _this.dispatchEvent("cell_changed", {"row":row, "col":col, "data":data});
   }

   this.resetData = function(adata) {
      _this.data = adata;
      _this.resetSort();
      _this.dispatchEvent("structure_changed");
   }

   this.resetSort = function(keep) {
      if(isNaN(_this.osortedCol) || _this.osortedCol == -1) {
         if(!keep) {
            _this.osortedCol = _this.sortedCol;
            _this.sortedAsc = false;
            _this.sortedCol = -1;
         }
         else if(keep && !isNaN(_this.sortedCol)) {
            _this.sortData(_this.sortedCol, true);
         }
      }
   }

   /**
    * Delete a row.
    * @public.
    */
   this.deleteRow = deleteRowFunction;
   this.deleteRow._class = this._class;

   function deleteRowFunction(index) {
      var data = _this.data;

      if(index < 0 || index >= data.length) {
         return;
      }

      data.splice(index, 1);

      _this.dispatchEvent("structure_changed", {});
   }

   /**
    * Get header row count.
    * @public.
    */
   this.getHeaderRowCount = getHeaderRowCountFunction;
   this.getHeaderRowCount._class = this._class;

   function getHeaderRowCountFunction() {
      return 1;
   }

   /**
    * Get span for the specified header cell.
    * @param row the specified row index.
    * @param col the specified column index.
    * @public.
    */
   this.getHeaderSpan = getHeaderSpanFunction;
   this.getHeaderSpan._class = this._class;

   function getHeaderSpanFunction(row, col) {
      return 1;
   }

   /**
    * Get the row span for the specified header cell.
    *
    * @param row the row index of the cell.
    * @param col the column index of the cell.
    *
    * @public
    */
   this.getHeaderRowSpan = getHeaderRowSpanFunction;
   this.getHeaderRowSpan._class = this._class;

   function getHeaderRowSpanFunction(row, col) {
      return 1;
   }

   /**
    * Get row count.
    * @public.
    */
   this.getRowCount = getRowCountFunction;
   this.getRowCount._class = this._class;

   function getRowCountFunction() {
      return _this.data.length;
   }

   /**
    * Add cell data changing listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.addCellDataChangedListener = addCellDataChangedListenerFunction;
   this.addCellDataChangedListener._class = this._class;

   function addCellDataChangedListenerFunction(func, obj) {
      _this.addEventListener("cell_changed", func, obj);
   }

   /**
    * Remove cell data changing listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.removeCellDataChangedListener = removeCellDataChangedListenerFunction;
   this.removeCellDataChangedListener._class = this._class;

   function removeCellDataChangedListenerFunction(func, obj) {
      _this.removeEventListener("cell_changed", func, obj);
   }

   /**
    * Add inserting row listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.addRowInsertedListener = addRowInsertedListenerFunction;
   this.addRowInsertedListener._class = this._class;

   function addRowInsertedListenerFunction(func, obj) {
      _this.addEventListener("row_inserted", func, obj);
   }

   /**
    * Remove inserting row listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.removeRowInsertedListener = removeRowInsertedListenerFunction;
   this.removeRowInsertedListener._class = this._class;

   function removeRowInsertedListenerFunction(func, obj) {
      _this.removeEventListener("row_inserted", func, obj);
   }

   /**
    * Add removing row listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.addRowRemovedListener = addRowRemovedListenerFunction;
   this.addRowRemovedListener._class = this._class;

   function addRowRemovedListenerFunction(func, obj) {
      _this.addEventListener("row_removed", func, obj);
   }

   /**
    * Remove removing row listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.removeRowRemovedListener = removeRowRemovedListenerFunction;
   this.removeRowRemovedListener._class = this._class;

   function removeRowRemovedListenerFunction(func, obj) {
      _this.removeEventListener("row_removed", func, obj);
   }

   /**
    * Add structure changed listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.addStructureChangedListener = addStructureChangedListenerFunction;
   this.addStructureChangedListener._class = this._class;

   function addStructureChangedListenerFunction(func, obj) {
      _this.addEventListener("structure_changed", func, obj);
   }

   /**
    * Remove structure changed listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.removeStructureChangedListener = removeStructureChangedListenerFunction;
   this.removeStructureChangedListener._class = this._class;

   function removeStructureChangedListenerFunction(func, obj) {
      _this.removeEventListener("structure_changed", func, obj);
   }

   this.sortData = function(idx, keep) {
      if(idx < 0 || idx >= _this.getColCount()) {
         return;
      }

      if(_this.unSortedCols && _this.unSortedCols.indexOf(idx) != -1) {
         return;
      }

      _this.osortedCol = _this.sortedCol;
      _this.sortedAsc = keep ? _this.sortedAsc : _this.sortedCol == idx ? !_this.sortedAsc : true;
      _this.sortedCol = idx;
      var ndata = new Array(0);
      var hcnt = _this.getHeaderRowCount();

      for(var i = 0; i < hcnt; i++) {
         ndata[i] = _this.data[i];
      }

      for(var i = hcnt; i < _this.getRowCount(); i++) {
         var obj = _this.getData(i, idx);
         var obj2;
         var idx2 = ndata.length;

         for(var k = hcnt; k < ndata.length; k++) {
            if(!ndata[k]) {
               continue;
            }

            obj2 = ndata[k][idx];

            if(_this.comparator) {
               var res = _this.comparator.call(_this, idx, obj, obj2);

               if(_this.sortedAsc && res < 0  || !_this.sortedAsc && res > 0) {
                  idx2 = k;
                  break;
               }
            }
            else if(_this.sortedAsc && obj2 > obj  || !_this.sortedAsc && obj2 < obj) {
               idx2 = k;
               break;
            }
         }

         ndata.splice(idx2, 0, _this.data[i]);
      }

      _this.data = ndata;
      _this.dispatchEvent("structure_changed", {});
   }
}

/**
 * Scroll bar component used for scrolling a grid table.
 *
 * @version 8.5
 * @author InetSoft Technology Corp
 * @depend util.js
 */
/**
 * Constructor.
 * @param vparent the parent that contains this scroll bar.
 * @param unitIncrement the increment/decrement for each scroll action.
 * @param extent block increment.
 * @param value current value.
 * @param min minimum value.
 * @param max maximum value.
 */
isii_JSScrollBar = function(vparent, unitIncrement, extent, value, min, max) {
   this._class = "isii_JSScrollBar";
   this._extend("isii_Object");
   this.vparent = vparent;
   this.unitIncrement = unitIncrement;
   this.extent = extent;
   this.value = value;
   this.min = min;
   this.max = max;

   var _this = this._this;
   var height = this.vparent.style.height;
   var arrowHeight = 16;
   var container;
   var blockContainer;
   var blockdiv;
   var topdiv;
   var topDisableDiv;
   var bottomDisableDiv;
   var bottomdiv;
   var isdown;
   var moveObj = {};

   initScroll();

   /**
    * Set unit increment of this scroll bar.
    * @param inc unit increment.
    * @public.
    */
   this.setUnitIncrement = setUnitIncrementFunction;
   this.setUnitIncrement._class = this._class;

   function setUnitIncrementFunction(inc) {
      this.unitIncrement = inc;
   }

   /**
    * Set the value of this scroll bar.
    * @param the value is srcroll bar's.
    * @public.
    */
   this.setValue = setValueFunction;
   this.setValue._class = this._class;

   function setValueFunction(value) {
      this.value = value;

      if(blockContainer && blockdiv && value > -1) {
         var spaceHeight = truncatePixel(blockContainer.style.height) -
            truncatePixel(blockdiv.style.height);
         var divisor = (_this.max - _this.min) - _this.extent;
         var percent = divisor == 0 ? 0 : (_this.value - _this.min) / divisor;

         blockdiv.style.top =
            Element.toPixel((percent > 1 ? 1 : percent) * spaceHeight);

         _this.dispatchEvent("scrolling", {index:_this.value});
      }
   }

   /**
    * Get unit increment of this scroll bar.
    * @return unit increment.
    * @public.
    */
   this.getUnitIncrement = getUnitIncrementFunction;
   this.getUnitIncrement._class = this._class;

   function getUnitIncrementFunction() {
      return this.unitIncrement;
   }

   /**
    * Set the scrollbar's minimum, maximum, extent and current value.
    * @param extent block increment.
    * @param value current value.
    * @param min minimum value.
    * @param max maximum value.
    * @param height height of this scroll bar.
    * @public.
    */
   this.setValues = setValuesFunction;
   this.setValues._class = this._class;

   function setValuesFunction(extent, value, min, max, height) {
      this.extent = extent;
      this.value = value;
      this.min = min;
      this.max = max;

      if(height) {
         _this.vparent.style.height = Element.toPixel(height);
         container.style.height = Element.toPixel(height);
         var vheight = height - 2 * arrowHeight;
         blockContainer.style.height =
            Element.toPixel(vheight > 0 ? vheight : 0);
      }

      if(blockdiv) {
         if(this.max - this.min == 0 || this.max - this.min - this.extent == 0)
         {
            return;
         }

         var percent = this.extent / (this.max - this.min);
         var block_h = percent * truncatePixel(blockContainer.style.height);

         if(_root.is_ie && block_h < 16) {
            blockdiv.style.height = "16px";
         }
         else {
            blockdiv.style.height = Element.toPixel(block_h);
         }

         var spaceHeight = truncatePixel(blockContainer.style.height) -
            truncatePixel(blockdiv.style.height);
         percent = (this.value - this.min) /
            ((this.max - this.min) - this.extent);
         blockdiv.style.top = Element.toPixel(percent * spaceHeight);
      }
   }

   /**
    * Get extent.
    * @return extent.
    * @public.
    */
   this.getExtent = getExtentFunction;
   this.getExtent._class = this._class;

   function getExtentFunction() {
      return this.extent;
   }

   /**
    * Get current value.
    * @return current value.
    * @public.
    */
   this.getValue = getValueFunction;
   this.getValue._class = this._class;

   function getValueFunction() {
      return this.value;
   }

   /**
    * Get minimum value.
    * @return minimum value.
    * @public.
    */
   this.getMin = getMinFunction;
   this.getMin._class = this._class;

   function getMinFunction() {
      return this.min;
   }

   /**
    * Get maximum value.
    * @return maximum value.
    * @public.
    */
   this.getMax = getMaxFunction;
   this.getMax._class = this._class;

   function getMaxFunction() {
      return this.max;
   }

   /**
    * Add scrolling listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.addScrollingListener = addScrollingListenerFunction;
   this.addScrollingListener._class = this._class;

   function addScrollingListenerFunction(func, obj) {
      _this.addEventListener("scrolling", func, obj);
   }

   /**
    * Set visibility of this scroll bar.
    * @param visible show the scroll if true, hide otherwise.
    * @public.
    */
   this.setVisibility = setVisibilityFunction;
   this.setVisibility._class = this._class;

   function setVisibilityFunction(visible) {
      this.vparent.style.display = visible ? "block" : "none";
   }

   this.setDisability = setDisabilityFunction;
   this.setDisability._class = this._class;

   function setDisabilityFunction(disable) {
      blockdiv.style.display = disable ? "none" : "block";
      topdiv.style.display = disable ? "none" : "block";
      topDisableDiv.style.display = !disable ? "none" : "block";
      bottomdiv.style.display = disable ? "none" : "block";
      bottomDisableDiv.style.display = !disable ? "none" : "block";
   }

   /**
    * Init scroll bar.
    * @private.
    */
   function initScroll() {
      container = Element.create("div", "sr_scrollbar", false, document);
      Element.append(_this.vparent, container);
      container.style.top = Element.toPixel(0);
      container.style.left = Element.toPixel(0);
      container.style.height = height;
      container.style.width = _this.vparent.style.width;
      container.innerHTML = "";

      topdiv = Element.create("div", "sr_scrollbar_top", false, document);
      topdiv.style.position = "relative";
      topdiv.style.left = Element.toPixel(0);
      topdiv.style.top = Element.toPixel(0);
      topdiv.style.height = Element.toPixel(arrowHeight);

      topDisableDiv = Element.create("div", "sr_scrollbar_top_disable",
                                     true, document);
      topDisableDiv.style.position = "relative";
      topDisableDiv.style.left = Element.toPixel(0);
      topDisableDiv.style.top = Element.toPixel(0);
      topDisableDiv.style.height = Element.toPixel(arrowHeight);
      topDisableDiv.style.display = "none";

      blockdiv = Element.create("div", "sr_scrollbar_block", false, document);
      Element.append(container, blockdiv);
      blockdiv.style.zIndex = 467;
      blockdiv.style.position = "relative";

      blockContainer = Element.create("div", "sr_blockContainer", false,
                                      document);
      blockContainer.style.position = "relative";
      blockContainer.style.width = container.style.width;
      var vheight = truncatePixel(height + "") - 2 * arrowHeight;
      blockContainer.style.height = Element.toPixel(vheight > 0 ? vheight : 0);

      if(_this.max - _this.min == 0) {
         return;
      }

      var percent = _this.extent / (_this.max - _this.min);
      var block_h = percent * truncatePixel(blockContainer.style.height);

      // the scrollbar_block's height is actually about more than 16px in ie.
      if(_root.is_ie && block_h < 16) {
         blockdiv.style.height = "16px";
      }
      else {
         blockdiv.style.height = Element.toPixel(block_h);
      }

      bottomdiv = Element.create("div", "sr_scrollbar_bottom", false,
                                     document);
      bottomdiv.style.position = "relative";
      bottomdiv.style.left = Element.toPixel(0);
      bottomdiv.style.height = Element.toPixel(arrowHeight);

      bottomDisableDiv = Element.create("div", "sr_scrollbar_bottom_disable",
                                        false, document);
      bottomDisableDiv.style.position = "relative";
      bottomDisableDiv.style.left = Element.toPixel(0);
      bottomDisableDiv.style.top = Element.toPixel(0);
      bottomDisableDiv.style.height = Element.toPixel(arrowHeight);
      bottomDisableDiv.style.display = "none";

      Element.append(container, topdiv);
      Element.append(container, topDisableDiv);
      Element.append(blockContainer, blockdiv);
      Element.append(container, blockContainer);
      Element.append(container, bottomdiv);
      Element.append(container, bottomDisableDiv);
      blockdiv.style.top = Element.toPixel(0);
      changeScrollStatus();

      Element.addEventListener(blockContainer, "mousedown",
                               pressBlockContainer.bind(this));
      Element.addEventListener(blockContainer, "mouseup",
                               releaseBlockContainer.bind(this));
      Element.addEventListener(blockdiv, "mousedown", mouseDown.bind(this));
      Element.addEventListener(document, "mousemove", mouseMove.bind(this));
      Element.addEventListener(document, "mouseup", mouseUp.bind(this));
      Element.addEventListener(topdiv, "mousedown",
                               pressScrollButton.bind(this, true));
      Element.addEventListener(topdiv, "mouseup",
                               releaseScrollButton.bind(this, true));
      Element.addEventListener(topdiv, "mouseout",
                               releaseScrollButton.bind(this, true));
      Element.addEventListener(bottomdiv, "mousedown",
                               pressScrollButton.bind(this,false));
      Element.addEventListener(bottomdiv, "mouseup",
                               releaseScrollButton.bind(this,false));
      Element.addEventListener(bottomdiv, "mouseout",
                               releaseScrollButton.bind(this, true));
   }

   /**
    * Notify change.
    * @private.
    */
   function notifyScroll() {
      var spaceHeight = truncatePixel(blockContainer.style.height) -
         truncatePixel(blockdiv.style.height);
      var divisor = (_this.max - _this.min) - _this.extent;
      var percent = divisor == 0 ? 0 : (_this.value - _this.min) / divisor;
      blockdiv.style.top = Element.toPixel(percent * spaceHeight);

      _this.dispatchEvent("scrolling", {index:_this.value});
   }

   /**
    * Press block container to scroll a page amount.
    * @private.
    */
   function pressBlockContainer(evt) {
      if((new IEvent(evt)).src == blockContainer) {
         var ymouse = getOffset(evt).offsetY;
         var blockTop = truncatePixel(blockdiv.style.top);
         var blockHeight = truncatePixel(blockdiv.style.height);

         if(ymouse <= blockTop) {
            var val = _this.value - _this.extent;
            _this.value =  val < _this.min ? _this.min : val;
         }
         else if(ymouse >= blockTop + blockHeight) {
            var val = _this.value + _this.extent;
            var maxVal = _this.max - _this.extent;

            _this.value =  val > maxVal ? maxVal : val;
         }

         notifyScroll();
      }
   }

   /**
    * Release block container.
    * @private.
    */
   function releaseBlockContainer(evt) {
   }

   /**
    * Scrolling.
    * @private.
    */
   function pressScrollButton(isUp) {
      if(isUp) {
         changeScrollStatus("up");

         if(_this.value <= _this.min) {
            return;
         }

         _this.value--;
      }
      else {
         changeScrollStatus("down");

         if(_this.value >= _this.max - _this.extent) {
            return;
         }

         _this.value++;
      }

      notifyScroll();
   }

   function releaseScrollButton(isUp) {
      changeScrollStatus();
   }

   function changeScrollStatus(status) {
      if(status == "up") {
         topdiv.className = "sr_scrollbar_top_press";
      }
      else if(status == "down") {
         bottomdiv.className = "sr_scrollbar_bottom_press";
      }
      else {
         topdiv.className = "sr_scrollbar_top";
         bottomdiv.className = "sr_scrollbar_bottom";
      }
   }

   /**
    * Mouse down.
    * @private.
    */
   function mouseDown(evt) {
      var ievent = new IEvent(evt);
      var src = ievent.src;
      moveObj.src = src;
      moveObj._y = ievent.pageY;
   }

   /**
    * Mouse move.
    * @private.
    */
   function mouseMove(evt) {
      if(moveObj.src) {
         var ievent = new IEvent(evt);
         //var distance = evt.clientY - _y;
         var distance = ievent.pageY - moveObj._y;

         if((_this.value == _this.min && distance <= 0) || distance == 0 ||
            (_this.value >= _this.max - _this.extent && distance >= 0))
         {
            return;
         }

         var spaceHeight = truncatePixel(blockContainer.style.height) -
            truncatePixel(blockdiv.style.height);

         if(spaceHeight == 0) {
            return;
         }

         var percent = distance / spaceHeight;
         var increment = Math.round(percent * (_this.max -
            _this.min - _this.extent));
         var val = _this.value + increment;
         var newValue = _this.value;

         if(val < _this.min) {
            newValue = _this.min;
         }
         else if(val > _this.max - _this.extent) {
            newValue = _this.max - _this.extent;
         }
         else {
            newValue = val;
         }

         if(newValue != _this.value) {
            moveObj._y = ievent.pageY;
            _this.value = newValue;
            notifyScroll();
         }
      }
   }

   /**
    * Mouse up.
    * @private.
    */
   function mouseUp() {
      delete moveObj.src;
   }

   /**
    * Remove the "px" suffix if neccessary.
    * @private.
    */
   function truncatePixel(pixelStr) {
      var idx = pixelStr.indexOf("px");
      pixelStr = idx < 0 ?
         pixelStr : pixelStr.substring(0, pixelStr.length - 2);

      try{
         return parseInt(pixelStr);
      }
      catch(e) {
         return null;
      }
   }
}

/**
 * Cell renderer for grid.
 *
 * @version 8.5
 * @author InetSoft Technology Corp
 * @depend util.js
 */
/**
 * Constructor.
 */

isii_JSGridCellRenderer = function() {
   this._class = "isii_JSGridCellRenderer";

   /**
    * Get grid cell renderer string.
    * @param grid the grid view.
    * @param value value contains rendering information.
    * @public.
    */
   this.getGridCellRendererString = getGridCellRendererStringFunction;
   this.getGridCellRendererString._class = this._class;

   function getGridCellRendererStringFunction(grid, value) {
      var row = value.row;
      var col = value.col;
      var cell = value.cell;
      var data = grid.model.getData(row, col);
      cell.style.width = cell.style.width ? cell.style.width :
         Element.toPixel(grid.getSize().width / (grid.model.getColCount() + 2));

      if(row < grid.model.getHeaderRowCount()) {
         cell.align = "center";
      }
      else {
         if(grid.model.getColType(col) == "String") {
            cell.align = "left";
         }
         else if(grid.model.getColType(col) == "Number") {
            cell.align = "right";
         }
      }

      return trim(data) == "" ? "&nbsp;" : data;
   }
}

/**
 * Used as data model for a grid table.
 *
 * @version 8.5
 * @author InetSoft Technology Corp
 * @depend tool.js, util.js
 */

/**
 * Constructor.
 * @param uri uri string.
 * @param op operation type.
 * @param action action type.
 * @param handler handler type.
 * @param async communicate async or not.
 */
isii_JSAJAXGridModel = function(uri, op, action, handler, async) {
   this._class = "isii_JSAJAXGridModel";
   this._extend("isii_JSGridModel");
   var _this = this._this;

   this.uri = uri;
   this.op = op;
   this.action = action;
   this.handler = handler;
   this.async = async;
   var lastColCount = 0;

   /**
    * Add structure changed listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.addRefreshFinishListener = addRefreshFinishListenerFunction;
   this.addRefreshFinishListener._class = this._class;

   function addRefreshFinishListenerFunction(func, obj) {
      _this.addEventListener("refresh_finish", func, obj);
   }

   /**
    * Remove structure changed listener.
    * @param func callback function.
    * @obj calling object.
    * @public.
    */
   this.removeRefreshFinishListener = removeRefreshFinishListenerFunction;
   this.removeRefreshFinishListener._class = this._class;

   function removeRefreshFinishListenerFunction(func, obj) {
      _this.removeEventListener("refresh_finish", func, obj);
   }

   /**
    * Refresh data.
    * @param parameter.
    * @public.
    */
   this.refresh = refreshFunction;
   this.refresh._class = this._class;

   function refreshFunction(parameter) {
      _this.setEventEnabled(false);
      _this.lastColCount = _this.getColCount();
      _this.data = [];
      var ajax = new isii_AJAX(_this.handler, _this.op, _this.action,
         _this.uri, _this.async);

      if(_this.async) {
         ajax.addFinishListener(getDataOver.bind(_this));
      }

      ajax.send(parameter);

      if(!_this.async) {
         getDataOver({"ajax" : ajax, "element" : ajax.element});
      }
   }

   /**
    * Init data in grid model.
    * @param ajax the ajax object.
    * @private.
    */
   this.initData = initDataFunction;
   this.initData._class = this._class;

   function initDataFunction(element) {
      var rows = element.getElementsByTagName("row");

      if(!rows) {
         return;
      }

      for(var i = 0; i < rows.length; i++) {
         var row = rows[i];
         var cells = row.getElementsByTagName("cell");

         for(var j = 0; j < cells.length; j++) {
            var cell = cells[j];
            var value = null;

            if(cell.firstChild) {
               value = byteDecode(cell.firstChild.nodeValue);
               value = value == "^null^" ? null : value;
            }
            _this.setData(i, j, value, true);
         }
      }
   }

   /**
    * Get data over.
    * @private.
    */
   function getDataOver(msg) {
      var ajax = msg.ajax;
      var element = msg.element;
      var _root = getRoot(self);

      // fix firefox bug
      var err = ajax.getError();

      if(err != null) {
         if(err.indexOf('0x80004005') < 0 || !_root.is_firefox) {
            alert(err);
         }
      }
      else {
         _this.initData(element);
      }
      _this.resetSort(true);

      _this.setEventEnabled(true);

      // don't refresh grid when error occure
      if(err == null) {
         _this.dispatchEvent("structure_changed",
            {"refreshHeader" : _this.lastColCount != _this.getColCount()});
      }

      _this.dispatchEvent("refresh_finish", {"error":err != null});
   }
}

/**
 * Constructor.
 * @param doc document.
 * @param visibleAmounvisible amount for this grid.
 * @param model the grid data model.
 * @param cellRenderer the grid cell renderer.
 * @config the config object.
 */
isii_JSGrid = function(doc, model, render, config, tipManager) {
   this._class = "isii_JSGrid";
   this._extend("isii_JSComponent", doc);
   var _this = this._this;

   // register this component so that its scrollbars are updated by the
   // isii_JSGrid.updateScrollBars() function
   gridSet.push(_this);

   this.model = model;
   this.cellRenderer = render ? render : new isii_JSGridCellRenderer();
   var dndManager = isii_DndManager.getManager();
   tipManager = tipManager ? tipManager : new isii_TooltipManager(window);

   config = config ? config : {};
   var visibleAmount = config.visibleAmount ? config.visibleAmount : 10;
   visibleAmount += _this.model.getHeaderRowCount();
   var dropTarget = config.dropTarget == null ? false : config.dropTarget;
   var dragSource = config.dragSource == null ? false : config.dragSource;
   var resizable = config.resizable == null || config.resizable ? true : false;
   var sortable = config.sortable == null || config.sortable ? true : false;
   var multiSelection = config.multiSelection == false ? false : true;
   var hideBorder = config.hideBorder == true ? true : false;
   var selectedRows = new Array(0);
   var tableContainerBoder = 1;
   var tableHeight = config.tableHeight ? config.tableHeight : -1;
   var current = 0;
   var tableObj;
   var scrollBar;
   var scrollBarBox;
   var scrollHeight = -1;
   var currKey;
   var gridWidth;
   var allowDOMContents = config.allowDOMContents == true ? true : false;
   var resizeObj = new Object();

   /**
    * Initialize grid. Please note that it must append grid to parent div and
    * set it's size with conrete number before initializing grid.
    * @public.
    */
   this.init = function() {
      initGridView();
      initGridListener();
      populateGridData();
   }

   /**
    * Get selected row.
    */
   this.getSelectedRow = function() {
      if(selectedRows.length > 0) {
         return selectedRows[0];
      }

      return -1;
   }

   /**
    * Get selected rows.
    */
   this.getSelectedRows = function() {
      var result = [];

      for(var i = 0; i < selectedRows.length; i++) {
         result.push(selectedRows[i]);
      }

      result.sort(sortNumber);

      return result;
   }

   function sortNumber(a, b) {
      return a - b;
   }

   /**
    * Add before selecting row listener.
    */
   this.addRowWillSelectListener = function(func, obj) {
      _this.addEventListener("row_will_select", func, obj);
   }

   /**
    * Remove before selecting row listener.
    */
   this.removeRowWillSelectListener = function(func, obj) {
      _this.removeEventListener("row_will_select", func, obj);
   }

   /**
    * Add selecting row listener.
    */
   this.addRowSelectedListener = function(func, obj) {
      _this.addEventListener("row_selected", func, obj);
   }

   /**
    * Remove selecting row listener.
    */
   this.removeRowSelectedListener = function(func, obj) {
      _this.removeEventListener("row_selected", func, obj);
   }

   /**
    * Init grid view.
    * @private.
    */
   function initGridView() {
      gridWidth = _this.rootDiv.style.width;
      gridWidth = gridWidth.indexOf("%") != -1 ? 600 : gridWidth;
      var gridViewBox = Element.create("DIV", "gridViewBox", true, document);
      var containerBox = Element.create("DIV", "containerBox", true, document);
      containerBox.style.width = calculateLength(gridWidth, "-", 18);

      var container = Element.create("TABLE", "tableContainer", true,
                                     document);
      container.style.width = "100%";
      container.style.border = tableContainerBoder + "px gray solid";

      scrollBarBox = Element.create("DIV", "scrollBarBox", false, document);

      var r0 = container.insertRow(0);
      r0.insertCell(0);
      container.cellPadding = 0;
      container.cellSpacing = 0;
      var gridDataBox = Element.create("DIV", "gridDataBox", true, document);
      gridDataBox.style.overflow = "auto";
      gridDataBox.style.width = Element.toPixel(_this.getSize().width - 20);
      gridDataBox.style.overflowY = "hidden";

      tableObj = Element.create("TABLE", "tableObj", true, document);
      tableObj.style.tableLayout  = "fixed";

      // if currect browser is firefox, set it can't select only at his own
      // element
      var _root = getRoot(self);

      /* bug1255329684275 select cell
      if(_root.is_ie) {
         tableObj.setAttribute("unselectable", true);
      }

      if(_root.is_gecko) {
         tableObj.style.setProperty("-moz-user-select", "-moz-none", "");
      }*/

      tableObj.style.width = extractAbsoluteValue(gridWidth) - 20;

      if(tableHeight >= 0) {
         tableObj.style.height = tableHeight;
      }

      tableObj.cellPadding = 0;
      tableObj.cellSpacing = 0;

      if(hideBorder) {
         tableObj.className = "tableObj_hideBorder";
      }

      Element.append(gridDataBox, tableObj);
	   container.rows[0].cells[0].appendChild(gridDataBox);
	   container.rows[0].cells[0].vAlign = "top";
      Element.append(containerBox, container);
      Element.append(gridViewBox, containerBox);
      Element.append(gridViewBox, scrollBarBox);
      Element.append(_this.rootDiv, gridViewBox);
   }

   /**
    * Init grid listener.
    * @private.
    */
   function initGridListener() {
      Element.addEventListener(document, "mousemove", mouseMoveGrid.bind(this));
      Element.addEventListener(document, "mousedown", mouseDownGrid.bind(this));
      Element.addEventListener(document, "mouseup", mouseUpGrid.bind(this));

      Element.addEventListener(tableObj, "click", doClick);
      Element.addEventListener(document, "keydown", keyDown);
      Element.addEventListener(document, "keyup", keyUp);
      _this.model.addCellDataChangedListener(populateGridData, this);
      _this.model.addRowInsertedListener(populateGridData, this);
      _this.model.addRowRemovedListener(populateGridData, this);
      _this.model.addStructureChangedListener(function(msg) {
         resetSelected(true);

         if(msg != null && msg.refreshHeader) {
            _this.headerinited = 0;
         }

         populateGridData();

         if(selectedRows.length == 0) {
            for(var i = tableObj.rows.length - 1; i >= 0; i--) {
               var r = tableObj.rows[i];

               if(!r.empty) {
                  _this.setRowSelected(r.idd, true);
                  _this.dispatchEvent("row_selected",  {"index":r.idd});

                  break;
               }
            }
         }
      });
   }

   function mouseMoveGrid(event) {
      var ievent = new IEvent(event);
      var elem = getCell(ievent);

      if(resizable && !resizeObj.resize) {
         if(resizeObj.resizesrc && (!elem || !elem.header)) {
            window.document.body.style.cursor = "default";
            resizeObj.resizesrc = null;
         }
         else if(elem && elem.header && elem.grid == _this && elem.idd == 0) {
            var found = false;

            if(_root.is_ie || _root.is_khtml) {
               var gap = event.offsetX - elem.offsetWidth;

               if(gap > -15 && gap < 0) {
                  found = true;
               }
            }
            else {
               var w = 0;
               var r = tableObj.rows[elem.idd];

               for(var i = 0; i < r.cells.length; i++) {
                  var c = r.cells[i];

                  if(c.header > elem.header) {
                     break;
                  }

                  w += c.offsetWidth;
               }

               var gap = event.layerX - w;

               if(gap > -15 && gap < 0) {
                  found = true;
               }
            }

            if(found) {
               window.document.body.style.cursor = "w-resize";
               resizeObj.resizesrc = elem;
            }
            else {
               window.document.body.style.cursor = "default";
               resizeObj.resizesrc = null;
            }
         }
      }
   }

   function mouseDownGrid(event) {
      var eve =  new IEvent(event);
      var elem = getCell(eve);

      if(!elem || !elem.grid || elem.grid != _this) {
         return;
      }

      var hcnt =  _this.model.getHeaderRowCount();

      if(resizeObj.resizesrc) {
         resizeObj.resize = true;
         resizeObj.resizex = extractAbsoluteValue(eve.pageX);
      }
      else if(sortable && (elem.header && elem.idd + 1 == hcnt ||
         elem.rowSpan && elem.rowSpan + elem.idd == hcnt))
      {
         _this.model.sortData(elem.header - 1);
      }
   }

   function mouseUpGrid(event) {
      if(resizeObj.resize) {
         var ow = resizeObj.resizesrc.ow ? resizeObj.resizesrc.ow : resizeObj.resizesrc.offsetWidth;
         var w = extractAbsoluteValue(new IEvent(event).pageX);
         w = w - resizeObj.resizex;

         if(w > 2 && _root.is_ie) {
            w = w / 2;
         }
         if(Math.abs(w) > 2) {
            w = w + ow;
            w = Math.max(10, w);
            resizeObj.resizesrc.style.width = Element.toPixel(w);
            resizeObj.resizesrc.ow = w;
            var tw = tableObj.offsetWidth + w - ow;
            tableObj.style.width =  Element.toPixel(tw);
         }

         if(_root.is_ie) {
            document.selection.empty();
         }

         resizeObj.resizesrc = null;
         resizeObj.resize = false;
         resizeObj.resizex = 0;
         window.document.body.style.cursor = "default";

         if(timeId != null) {
            window.clearTimeout(timeId);
         }

         timeId = window.setTimeout("isii_JSGrid.updateScrollBars()", 150);
      }
   }

   function getCell(ievent) {
      var elem = ievent.src;

      while(elem) {
         var oelem = elem;

         try {
            if(!elem.header) {
               elem = elem.offsetParent;
            }
         }
         catch(ex) {
            // ignore
         }

         if(oelem == elem) {
            break;
         }
      }

      return elem;
   }

   /**
    * Process clicking event.
    * @private.
    */
   function doClick(event) {
      var elem = getFirstParentOfType((new IEvent(event)).src, "TR");

      if(!elem) {
         return;
      }

      var idx = elem.empty || isNaN(elem.idd) ? -1 : elem.idd;

      try {
         _this.dispatchEvent("row_will_select",  {"index":idx});

      }
      catch(ex) {
         return;
      }

      if(currKey == null || !multiSelection) {
         _this.clearSelection();
      }

      if(elem) {
         var lastClicked = selectedRows.length > 0 ?
            selectedRows[0] : null;

         if(currKey == null || lastClicked == null) {
            _this.setRowSelected(elem.idd, true);
            _this.dispatchEvent("row_selected",  {"index":idx});
            return;
         }

         if(currKey == "ctrlKey") {
            _this.setRowSelected(elem.idd, true);
            _this.dispatchEvent("row_selected",  {"index":idx});
         }
         else if(currKey == "shiftKey") {
            _this.setEventEnabled(false);
            var lastIndex = lastClicked;
            var currIndex = elem.idd;

            if(currIndex == lastIndex) {
               return;
            }

            _this.clearSelection();

            if(lastIndex < currIndex) {
               for(var i = lastIndex; i <= currIndex; i++) {
                  _this.setRowSelected(i, true);
               }
            }
            else if(lastIndex > currIndex) {
               for(var i = lastIndex; i >= currIndex; i--) {
                  _this.setRowSelected(i, true);
               }
            }
            else {
               return;
            }

            _this.setEventEnabled(true);
            _this.dispatchEvent("row_selected",  {"index":idx});
//            _this.dispatchEvent("row_selected", {"index":elem.idd});
         }
      }
   }

   /**
    * Process key down event.
    * @private.
    */
   function keyDown(event) {
      if(event.ctrlKey) {
         currKey = "ctrlKey";
      }
      else if(event.shiftKey) {
         currKey = "shiftKey";
      }
      else {
         currKey = null;
      }

//      window.status = "currKey down " + currKey;
   }

   /**
    * Process key up event.
    * @private.
    */
   function keyUp(event) {
      currKey = null;
//      window.status = "currKey up " + currKey;
   }

   /**
    * Get the nearest parent of the special type.
    * @param obj the object to get parent.
    * @param tag the specified type.
    * @private.
    */
   function getFirstParentOfType(obj, tag) {
      while(obj && obj.tagName != tag && obj.tagName != "BODY") {
         obj = obj.parentNode;
      }

      return obj;
   }

   this.clearSelection = clearSelectionFunction;
   this.clearSelection._class = this._class;

   function clearSelectionFunction() {
      for(var i = selectedRows.length - 1; i >= 0; i--) {
         _this.setRowSelected(selectedRows[i], false);
      }

      selectedRows = [];
      _this.dispatchEvent("row_selected", {"index":-1});
   }

   /**
    * Set all rows selected.
    * @public.
    */
   this.selectAll = selectAllFunction;
   this.selectAll._class = this._class;

   function selectAllFunction() {
      var dataModel = _this.model;
      var start = dataModel.getHeaderRowCount();
      var end = dataModel.getRowCount();

      if(end - start == 0) {
         return;
      }

      for(var i = start; i < end; i++) {
         this.setRowSelected(i, true);
      }

      _this.dispatchEvent("row_selected",  {"index":i});
   }

   /**
    * Set row selected or unselected.
    * @public.
    */
   this.setRowSelected = setRowSelectedFunction;
   this.setRowSelected._class = this._class;

   function setRowSelectedFunction(row, selected, isScroll) {
      if(isNaN(row) || row < _this.model.getHeaderRowCount() || row >= _this.model.getRowCount()) {
         return;
      }

      for(var i = 0; i < tableObj.rows.length; i++) {
         var r = tableObj.rows[i];

         if(!r.empty && r.idd == row) {
            r.className = selected ? "rowselected" : "row";
            break;
         }
      }

      if(selected && selectedRows.indexOf(row) == -1) {
         selectedRows.push(row);
         selectedRows.sort(sortNumber);
      }

      if(!selected && selectedRows.indexOf(row) != -1) {
         selectedRows.splice(selectedRows.indexOf(row), 1);
      }

      if(selected && isScroll && scrollBar) {
         var aval = row - visibleAmount + 1;
         scrollBar.setValue(Math.max(0, aval));
      }
   }

   this.refresh = function(params, spath) {
      var oasync = _this.model.async;
      _this.model.async = false;
      _this.model.refresh(params);
      _this.model.async = oasync;

      if(spath && spath.length >= 1) {
         for(var i = 0; i < _this.model.getRowCount(); i++) {
            var found = true;

            for(var j = 0; j < spath.length; j++) {
               if(_this.model.getData(i, spath[j][0]) != spath[j][1]) {
                  found = false;
                  break;
               }
            }

            if(found) {
               _this.clearSelection();
               _this.setRowSelected(i, true, true);
               _this.dispatchEvent("row_selected",  {"index":i});
               break;
            }
         }
      }
   }

   function resetSelected(reset, isScroll) {
      if(reset) {
         // @by tonyy, to avoid a endless loop
         for(var i = selectedRows.length - 1; i >= 0; i--) {
            var rowIndex = parseInt(selectedRows[i]);

            if(rowIndex >= _this.model.getRowCount()) {
               selectedRows.splice(i, 1);
            }
         }

         _this.dispatchEvent("row_selected", {"index":-1});
      }
      else {
         for(var i = 0; i < selectedRows.length; i++) {
            var rowIndex = parseInt(selectedRows[i]);
            _this.setRowSelected(rowIndex, true);
         }

         if(selectedRows.length > 0 && !isScroll) {
            _this.dispatchEvent("row_selected",
               {"index":selectedRows[selectedRows.length - 1]});
         }
      }
   }

   /**
    * Populate grid detail content.
    */
   function populateGridData(isScroll) {
      var hcnt = _this.model.getHeaderRowCount();
      var ccnt = _this.model.getColCount();
      var inited = _this.headerinited == ccnt;

      if(!inited) {
         current = 0;

         for(var i = tableObj.rows.length - 1; i >= 0; i--) {
            for(var j = tableObj.rows[i].cells.length - 1; j >= 0; j--) {
               tableObj.rows[i].deleteCell(j);
            }

            tableObj.deleteRow(i);
         }

         for(var i = 0; i < hcnt; i++) {
            var r = tableObj.insertRow(i);
            r.className = "tableHeader";
            var cellIndex = 0;

            for(var j = 0; j < ccnt; j++) {
               var cellData = _this.model.getData(i, j);

               if(cellData) {
                  var c = r.insertCell(cellIndex++);
                  var valueObj = {row:i, col:j, cell:c};
                  c.innerHTML = _this.cellRenderer.getGridCellRendererString(_this,valueObj);

                  c.style.overflow = "hidden";
                  c.style.whiteSpace="nowrap";
                  c.colSpan = _this.model.getHeaderSpan(i, j);
   	       c.rowSpan = _this.model.getHeaderRowSpan(i, j);
                  c.header = j + 1;
                  c.idd = i;
                  c.grid = _this;

                  if(_root.is_ie) {
                     c.setAttribute("unselectable", true);
                  }
                  else if(_root.is_gecko) {
                     c.style.setProperty("-moz-user-select", "-moz-none", "");
                  }
               }
            }
         }

         for(var i = hcnt; i < visibleAmount; i++) {
            var r = tableObj.insertRow(i);

            for(var j = 0; j < ccnt; j++) {
               var c = r.insertCell(j);
               c.style.overflow = "hidden";
               c.style.whiteSpace="nowrap";
               c.innerHTML = "&nbsp;";
            }
         }
      }

      if(current + visibleAmount > _this.model.getRowCount()) {
         current = 0;
      }

      for(var i = 0; i < tableObj.rows.length; i++) {
         var empty = i + current >= _this.model.getRowCount();
         var r = tableObj.rows[i];
         r.className = r.className == "rowselected" ? "row" : r.className;
         r.idd = i < hcnt ? i : i + current;
         r.empty = empty || i < hcnt;

         if(i < hcnt && _this.headerinited) {
            continue;
         }


         for(var j = 0; j < r.cells.length; j++) {
            var c = r.cells[j];
            var cidx = j;

            if(c.header) {
               cidx = c.header - 1;
            }

            var owidth = inited ?  c.style.width : null;
            var valueObj = {row:r.idd, col:cidx, cell:c};
            c.innerHTML = ""; // empty the old value
            var content = empty ? "&nbsp;" :
               _this.cellRenderer.getGridCellRendererString(_this, valueObj);

            var str = "" + content;
            var idx = str.indexOf("<input type='text' class='gridText'");

            if(idx != -1) {
               var idx1 = str.indexOf("value=", idx);
               var idx2 = str.indexOf("style=", idx1);
               var idx3 = str.indexOf(">", idx2);
               content = str.substring(0, idx);
               content += str.substring(idx1 + 7, idx2 - 2);
               content += str.substring(idx3 + 1);
            }

            // @by jasons, if allowDOMContents is turned on and no content is
            // returned by the cell renderer, the contents have been added
            // directly to the DOM by the renderer, so don't set the innerHTML
            // property
            if(!allowDOMContents || content) {
               if(_root && _root.is_ie) {
                  c.innerHTML = "<nobr>" + content + "</nobr>";
               }
               else {
                  c.innerHTML = content;
               }
            }

            var found = false;

            if(!owidth && c.iwidth) {
               var str = "" + c.iwidth;

               if(str.indexOf("%") != -1) {
                  found = true;
                  str = str.substring(0, str.length -1);

                  c.iwidth = Element.toPixel((tableObj.offsetWidth - 20 * r.cells.length) *
                     parseFloat(str) / 100);
               }
            }

            c.style.width = owidth ? owidth : c.iwidth && (!found || _root.is_ie || found && j != r.cells.length - 1) ? c.iwidth : c.style.width;

            var tip = empty || !_this.cellRenderer.getTip ? null :
               _this.cellRenderer.getTip(_this, valueObj);

            if(tip == null && content && content != "&nbsp;" &&
               ("" + content).indexOf("<img") == -1 &&
               ("" + content).indexOf("<select") == -1 &&
               ("" + content).indexOf("<input") == -1)
            {
               tip = content;
            }

            tipManager.deregisterTip(c);

            if(tip != null) {
               tipManager.registerTip(c, tip);
            }

            if(dragSource) {
               dndManager.registerDragSource(c, _this);
            }

            if(dropTarget) {
               dndManager.registerDropTarget(c, _this);
            }
         }
      }

      if(_this.model.osortedCol >= 0) {
         var c = tableObj.rows[0].cells[_this.model.osortedCol];
         var valueObj = {row:c.idd, col:c.header - 1, cell:c};
         var content = _this.cellRenderer.getGridCellRendererString(_this, valueObj);

         if(_root && _root.is_ie) {
            c.innerHTML = "<nobr>" + content + "</nobr>";
         }
         else {
            c.innerHTML = content;
         }

         _this.model.osortedCol = -1;
      }

      if(_this.model.sortedCol >= 0) {
         var c = tableObj.rows[0].cells[_this.model.sortedCol];
         var valueObj = {row:c.idd, col:c.header - 1, cell:c};
         var content = _this.cellRenderer.getGridCellRendererString(_this, valueObj);
         content = (_this.model.sortedAsc ? "  &#8593" : "  &#8595") + " " + content;

         if(_root && _root.is_ie) {
            c.innerHTML = "<nobr>" + content + "</nobr>";
         }
         else {
            c.innerHTML = content;
         }
      }

      _this.headerinited = _this.model.getColCount()
      resetSelected(false, isScroll);

      if(timeId != null) {
         window.clearTimeout(timeId);
      }

      window.setTimeout("isii_JSGrid.updateScrollBars()", 150);
   }

   this.updateScrollBar = updateScrollBarFunction;
   this.updateScrollBar._class = this._class;

   /**
    * Function to update the size of the scroll bar. This method needs to be
    * called outside the flow of any thread that modifies the DOM tree, i.e.,
    * whenever table rows or cells are added or removed. This is because
    * Firefox does not update the offsetHeight property of elements added to
    * the DOM tree until control has been given back to the browser.
    */
   function updateScrollBarFunction() {
      if(!tableObj || tableObj.offsetHeight <= 0) {
         return;
      }

      var scrollHeight = tableObj.offsetHeight + 2 * tableContainerBoder;

      if(!scrollBar) {
         scrollBarBox.style.width = Element.toPixel(17);
         scrollBarBox.style.height = Element.toPixel(scrollHeight);
         createScrollBar(scrollBarBox);
      }

      var max = Math.max(_this.model.getRowCount(), visibleAmount);
      current = Math.min(current, max - visibleAmount);
      scrollBar.setValues(visibleAmount, current, scrollBar.min, max,scrollHeight);
      scrollBar.setDisability(visibleAmount <= _this.model.getHeaderRowCount() ||
         visibleAmount >= _this.model.getRowCount());
      scrollBar.setVisibility(true);
   }

   /**
    * Create scroll bar component.
    * @private.
    */
   function createScrollBar(vparent) {
      var step = 1;
      var max = Math.max(visibleAmount, _this.model.getRowCount());
      scrollBar = new isii_JSScrollBar(vparent, step, visibleAmount,
         0, 0, max);
      scrollBar.setVisibility(true);
      scrollBar.addScrollingListener(scrollData);
   }

   /**
    * Show the scrolled data.
    * @private.
    */
   function scrollData(info) {
      current = info.index;
      populateGridData(true);
   }
}

/**
 * Calls the updateScrollBar() function on all grid components in the current
 * page. See the comments for that method for the reason for doing this.
 */
isii_JSGrid.updateScrollBars = function() {
   for(var i = 0; i < gridSet.length; i++) {
      gridSet[i].updateScrollBar();
   }

   timeId = null;
}
