/**
* Finetooth DataGrid JavaScript Class
** Copyright (c) 2006, FineTooth
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* 
* *       Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* *       Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* *       Neither the name of the FineTooth nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
**/

/**
* If any arguments are passed they'll be passed to init.
* No arguments and it's assumed that you are subclassing (instantiating to place inside of a prototype).
* @class Base class for the two DataGrids.
* @constructor
* @param {string} container_id to place the grid inside of
* @param {string} url optional url
* @param {int} width optional width in pixels
* @param {int} height optional height in pixels
**/
DataGrid = function( container_id, url, width, height )
{
	/**
   * @private
   **/
	this._id = '';
	/**
   * @private
   **/
	this._container_id = '';
	/**
   * @private
   **/
	this._url = '';
	/**
   * @private
   **/
	this._width = 0;
	/**
   * @private
   **/
	this._height = 0;
	/**
   * @private
   **/
	this._dataheight = 0;
	/**
   * @private
   **/
	this._sort = '';
	/**
   * @private
   **/
	this._order = '';
	/**
   * @private
   **/
	this._group = '';
   
	/**
   * @private
   **/
   this._scrollerWidth = 19;
	
	// sortclicked col tmp holder for onclick
	this.sortClickedCol = false;
	
	// to hold the name of the resizing col
	this.resizingCol = false;
	
	// are the grid columns draggable
	this.colsDraggable = false;
	
	// default for colsDraggable grid is in-place, and delay of .5 sec
	this.dragDelay = 500;
	
	/**
	* If there are more than this number of rows, then after a col drag, 
   * we will go back to the server
	* Unfortunately small due to IE being so damn slow. See for yourself.
	* @private
	*/
	this._rowReloadLimitOnDrag = 201;
	
	// default, to show the resizer graphics on resizable cols
	this.colsResizableInvisible = false;
	
	// allow to be overridden in case grid is embedded inside another app
	this.gridPopupDivId = 'gridPopupDiv';
	
	// for storing the filters
	this.colContextMenuFilters = new Array();
	
	// Timers for the colContextMenu
	this._hideColContextMenuTimer = null;
	this._hideColContextMenuDelay = 1000;
	
	// init
	if ( arguments.length ) this.init( container_id, url, width, height );
   
   return this;
}

/**
* Prototype functions
*/
DataGrid.prototype = {
	
	/**
   * The DOM id of the element that the grid will go inside of.
   * @type string
   **/
	get_id: function() { return this._id },
	
	/**
   * The DOM id of the element that the grid will go inside of.
   * @param {string} x the DOM id
   **/
	set_id: function(x) { this._id = x },
	
	
	/**
   * The Panel id for this Grid.
   * Since multiple grid's will be on the page this is used to make them unique.
   * @type string
   **/
	get_container_id: function() { return this._container_id },
	
	/**
   * The Panel id for this Grid.
   * Since multiple grid's will be on the page this is used to make them unique.
	* We also take advantage of storing some container elements in memory for later use here
   * @param {string} x the DataGrid container's id
   **/
	set_container_id: function(x) { 
		this._container_id = x 
	},
   
   
   /**
   * removeDataTableFromDOM
	*
	* Speed up DOM operations on the gridDataTable by temporarily
   * removing it from the live DOM
	*/
	removeDataTableFromDOM: function() {
		this.gridDataContainer.removeChild( this.gridDataTable );
	},
	
	/**
   * restoreDataTableToDOM
	*
	* Get the gridDataTable back into the live DOM
	*/
	restoreDataTableToDOM: function() {
		this.gridDataContainer.insertBefore( this.gridDataTable, null );
	},
   
	
	/**
	* Store the elements into the object's memory
	* @param container_id container_id
	*/
	get_elements: function( container_id ) {
		this.gridSuperduperContainer = $( 'gridSuperduperContainer_' + container_id );
		this.gridSuperContainer = $( 'gridSuperContainer_' + container_id );
		this.gridContainer = $( 'gridContainer_' + container_id );
		this.gridOverlay = $( 'gridOverlay_' + container_id );
		
		// unique_id and pref_module
		if ( this.gridContainer ) {
			this.unique_id = this.gridContainer.getAttribute( 'unique_id' );
			this.pref_module = this.gridContainer.getAttribute( 'pref_module' );
		}
		
		this.gridHeaderTable = $( 'gridHeaderTable_' + container_id );
		this.gridHeaderRow = $( 'gridHeaderRow_' + container_id );
		this.gridLastHeaderCell = $( 'gridHeaderCell_' + container_id + '_LAST' );
		
		this.gridDataContainer = $( 'gridDataContainer_' + container_id );
		this.gridDataTable = $( 'gridDataTable_' + container_id );
		this.gridDataColgroup = $( 'gridDataColgroup_' + container_id );
		this.gridLastDataCol = $( 'gridDataCol_' + container_id + '_LAST' );
		
		// the scroller pieces
		this.gridScrollContainer = $( 'gridScrollContainer_' + container_id );
		this.gridScroller = $( 'gridScroller_' + container_id );
		this.gridScrollHeight = $( 'gridScrollHeight_' + container_id );
		
		// add data elements
		if ( this.gridDataTable ) {
         
			// for previews
			this.src_module = this.gridDataTable.getAttribute( 'src_module' );
			this.preview_in_iframe = this.gridDataTable.getAttribute( 'preview_in_iframe' );
			
			// resize guides - look for global one, else take unique one
			this.resizeColGuideLeft = $( 'resizeColGuideLeft' ) ? $( 'resizeColGuideLeft' ) : $( 'resizeColGuideLeft_' + container_id );
			this.resizeColGuideRight = $( 'resizeColGuideRight' ) ? $( 'resizeColGuideRight' ) : $( 'resizeColGuideRight_' + container_id );
		}
		
		// attempt to turn on colsDraggable - tests if this.colsDraggable is true
		this.toggleColsDraggable( true );
		
	},
	
	/**
   * The base URL for this grid minus any sorting or grouping.
   * @type string
   **/
	get_url: function()
	{ 
		return this._url 
	},
	/**
   * The base URL for this grid minus any sorting or grouping.
   * @param {string} x the URL minux any ? #TODO fix this ? thing
   **/
	set_url: function(x) { this._url = x.replace( "?","" ) },
	
	/**
   * Return the search_id in the url
   **/
	get_search_id: function()
	{
      var matcharray = /search_id=([^&]+)/i.exec( this.get_url() );
      return matcharray[1];
	},
	
	/**
   * The width of the grid.
   * @type int
   **/
	get_width: function() { return this._width },
	/**
   * The width of the grid.
   * @param {int} x
   **/
	set_width: function(x) { this._width = x },
	
	/**
   * The height of the grid.
   * @type int
   **/
	get_height: function() { return this._height },
	/**
   * The height of the grid.
   * @param {int} x
   **/
	set_height: function(x) { this._height = x },
	
	
	/**
   * Column to sort by.
   * @type string
   **/
	get_sort: function() { return this._sort },
	/**
   * Column to sort by.
   * Setting to false or empty string disables sorting
   * @param {string} x
   **/
	set_sort: function(x) { this._sort = x },
	
	/**
   * Direction to sort
   * @type string
   **/
	get_order: function() { return this._order },
	/**
   * Direction to sort
   * @param {string} x
   **/
	set_order: function(x) { this._order = x },
	
	/**
   * Column to group by.
   * @type string
   **/
	get_group: function() { return this._group },
	/**
   * Column to group by.
   * Set to '' to disable.
   * @param {string} x
   **/
	set_group: function(x) { this._group = x },
	
	
	/**
   * Has the Grid loaded itself yet?
   * When resize is called we don't want to attempt to size things that have yet to show up.
	* And when it is loaded, we want to get dom elements into memory if x is true
   * @param {bool} x optional true || false to set.
   * @type bool
   **/
	loaded: function(x)
	{
		if ( x != undefined ) this._loaded = x;
		if (x)
			this.get_elements( this.get_container_id() );
		return this._loaded;
	},
	
	
	/**
   * Psudo-Constructor that concrete classes call in their conctructor.
   * @param {string} container_id to place the grid inside of
   * @param {string} url
   * @param {int} width optional width in pixels
   * @param {int} height optional height in pixels
   **/
	init: function( container_id, url, width, height )
	{
      if ( !container_id ) {
         alert( "Fatal Error: DataGrid init called without container_id.");
         return;
      }
		this._container_id = container_id;
		this._id = 'DataGrid_' + container_id;
		this._url    = url;
		this._width  = width;
		this._height = height;
		this._loaded = false;
		
		this._activeRow    = '';
		this._selectedRows = [];
		
      // store a reference to this grid in JavaScript global namespace
      // this is for the template mostly
      window[this._id] = this;
      //alert('just set window[' + this._id + '] to:' + window[this._id]);
      
      // if url passed in, then lock n load
		if ( url ) {
         this.set_url( url );
			this.load();
		}
	},
	
	/**
	* Positions the loading animation div based on current container size
	* @return {void}
	*/
	buildLoadingAnimation: function( message )
	{
		// prevents errors on early calls to load
		if ( !$( 'gridHeaderRow_' + this.get_container_id() ) ) return;
		if ( !this.get_height() ) return;
		if ( !this.gridOverlay ) return;
		
      
		// turn on the overlay
		this.gridOverlay.style.display = "block";
		
		// build the loader
		var loadingDiv        = $( this.gridPopupDivId );
		loadingDiv.innerHTML = '';
		loadingDiv.className  = "loadingDiv";
		
		var animation  = document.createElement( 'div' );
		animation.className = 'loadingAnimation';
		loadingDiv.appendChild( animation );
		
		// get to the center of the grid
      var pos = Position.page( $( 'gridHeaderRow_' + this.get_container_id() ) );
		var x = pos[0];
		var y = pos[1];
		x += this.get_width()/2;
		y += this.get_height()/2;
 		
		// turn on and then subtract loading size / 2
		loadingDiv.style.visibility = "hidden";
		loadingDiv.style.display = "block";
		x -= parseInt( loadingDiv.offsetWidth ) / 2;
		y -= parseInt( loadingDiv.offsetHeight ) / 2;
		
		// position and display
		loadingDiv.style.top = y + 'px';
		loadingDiv.style.left = x + 'px';
		loadingDiv.style.visibility = "visible";
	},
	
	/**
	* Turn off the loading animation
	* @return {void}
	*/
	stopLoadingAnimation: function() {
		// make sure the loader's off
		$( this.gridPopupDivId ).style.display = "none";
		
		// turn off the overlay
		this.gridOverlay.style.display = "none";
      return true;
	},
	
   
	/**
   * Resize the grid to the specified width and height.
   * @param {int} w width in pixels
   * @param {int} h height in pixels
   *
   */
   resize: function( w, h )
	{
      if ( w == 0 || h == 0 ) return;
		if ( w )
			this.set_width( w );
		else
			w = this.get_width();
		
		if ( h )
			this.set_height( h );
		else
			h = this.get_height();
		
      //alert('*resize* loaded: ' + this.loaded() + ', uid:' + this.unique_id + ", cid:" + this.get_container_id() + ", id:" + this.get_id() + ", h:" + h + ", w:" + w);
      
		// only resize if we're loaded
		if ( this.loaded() ) {

			// get "actual" container width set in template
			var containerWidth = this.gridContainer.getAttribute( 'containerWidth' );
			
			// set total with on superduper
			this.gridSuperduperContainer.style.width  =  w + "px";
			
			// remove width of a scrollerContainer
			var superWidth = w - this._scrollerWidth;
			if ( superWidth > 0 )
				this.gridSuperContainer.style.width = superWidth + "px";
			
			// test to see if we need to extend our grid last column
			var isBiggerBy = superWidth - containerWidth;
			//alert(this.get_container_id() + ' super bigger by:'+isBiggerBy+', cw:'+containerWidth);
			if (isBiggerBy > 0) {
				
				this.gridContainer.style.width = this.gridHeaderTable.style.width = superWidth + "px";
				this.gridLastHeaderCell.style.width = isBiggerBy + "px";
				
				if ( this.gridDataContainer ) {
					this.gridDataContainer.style.width = this.gridDataTable.style.width = superWidth + "px";
					this.gridLastDataCol.style.width = isBiggerBy + "px";
				}
				
			}
			else {
				this.gridContainer.style.width = this.gridHeaderTable.style.width = containerWidth + "px";
				this.gridLastHeaderCell.style.width = "0px";
				
				if ( this.gridDataTable ) {
               this.gridDataTable.style.width = this.gridDataContainer.style.width = containerWidth + "px";
               this.gridLastDataCol.style.width = "0px";
				}
			}
			
			// set a bunch of heights based on total height which necessarily includes headers
			this.gridSuperduperContainer.style.height = h + "px";
			this.gridSuperContainer.style.height = h + "px";
			this.gridContainer.style.height = h + "px";
         
         // calc the header height and make sure scroller is there
			var headerHeight = this.gridHeaderTable.offsetHeight;
			this.gridScroller.style.top = headerHeight + "px";
         
			// set vscroller height
			this.gridScrollContainer.style.height = this.gridScroller.style.height = h - headerHeight + "px";
			
			// set the data container to the full height
			var containerHeight = h - headerHeight - this._scrollerWidth;
			
			if ( containerHeight > 0 && this.gridDataContainer )
				this.gridDataContainer.style.height = containerHeight + "px";
			
			// assume the existence of a horiz. scroll
			if ( this.gridDataTable )
				this._dataheight = this.gridDataTable.offsetHeight;
         
			this.gridScrollHeight.style.height = this._dataheight + this._scrollerWidth + "px";
			
         
			//alert('container_id:' + this.get_container_id() +", datac:" + this.gridDataContainer.style.height + ", scrollheight:" + this.gridScrollHeight.style.height + ",dataheight:" + this._dataheight + ", scrollconheight:" + this.gridScrollContainer.style.height );
		}
		
		this.onResize();
	},
   
   
	/**
   * Abstract method to be used by subclasses.
   **/
	onResize: function() {},
	
	/**
	* Scroll Handler
	* @param {obj} the gridScroller element
	* @param {string} container_id
	*/
	scrollData: function( gridScroller, container_id )
	{
		this.gridDataContainer.scrollTop = gridScroller.scrollTop;
	},
	
	/**
   * Load the Grid and set the Sort and Group MenuBar Menus to the proper actions.
   **/
	load: function() {
      //alert("*load* cid:" + this.get_container_id() + ", id:" + this.get_id() );
		this.buildLoadingAnimation();
      var uid   = this.unique_id;
      var container_id   = this.get_container_id();
		var url   = this.get_url();
		var sort  = this.get_sort();
		var order = this.get_order();
		var group = this.get_group();
      var parameters = url ? url + "&" : "";
      parameters += "grid_uid=" + uid + "&container_id=" + container_id + "&group="+ group + "&sort=" + sort + "&order=" + order;
      
      new AjaxUpdater( container_id, {
         method: "get",
         parameters: parameters,
         onComplete: this.onLoad.bind( this )
      });
	},
   
   /**
   * onLoad after AjaxUpdater completes
   * @param {obj} request
   */
   onLoad: function( request ) {
      
		// load in DOM elements to memory now and set loaded flag
		this.loaded( true );
      
		// turn off loading
		this.stopLoadingAnimation();
      
		// resize for niceness
		this.resize();
      
		// let everyone know that it's all cool...
		this.callObservers( "onDataGridLoad" );
   },
	
	
	/**
   * Sort the grid.
   **/
	sort: function( sort, order )
	{
		order = (order != undefined) ? order : 'asc';
		this.set_sort( sort );
		this.set_order( order );
		this.load();
		this.callObservers( 'onDataGridSort' );
		this.onSort();
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onSort: function() {
	},
	
	
	
	/**
   * Group the grid.
   **/
	group: function( group )
	{
	   if (!group) group = '';
		this.set_group( group );
		this.load();
		this.callObservers( 'onDataGridGroup' );
		this.onGroup();
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onGroup: function() {},
	
	/**
	* Hides the TBODY for this subset.
	* Triggered by the grouping top TRs.
	* @param {HTMLTableRowElement} row TR element
	* @param {MouseEvent} e mouse event
	**/
	rowSetClick: function( row, e )
	{
		var row_parts = row.id.split('_');
		var tbody_id  = 'gridRowSet_' + row_parts[1] + '_' + row_parts[2];
		var tbody = $( tbody_id );
		
		if ( tbody.isActive === false )
		{
			tbody.isActive = true;
			// turn it on...
			tbody.style.display = '';
		}
		else
		{
			tbody.isActive = false;
			// turn it off...
			tbody.style.display = 'none';
		}
		
	},
	/**
   * Event Handler.
   * Just passes the event on to the callback.
   * @param {HTMLElement} row the TR that received the event
   * @param {MouseEvent} e
   **/
	rowMouseDown: function( row, e )
	{
		e = e ? e : window.event;
		
		// call Left or Right click functions
		if ( Event.isLeftClick( e ) )
			this.rowLeftClick( row, e );
		else if ( Event.isRightClick( e ) )
			this.rowRightClick( row, e );
		
		( e.stopPropagation ) ? e.stopPropagation() : e.cancelBubble = true;
		( e.preventDefault ) ? e.preventDefault() : e.returnValue = false;
		this.onRowMouseDown( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowMouseDown: function( row, e ) {},
	
	/**
   * Event Handler.
   * Just passes the event on to the callback.
   * @param {HTMLElement} row the TR that received the event
   * @param {MouseEvent} e
   **/
	rowMouseUp: function( row, e )
	{
		e = e ? e : window.event;
		this.onRowMouseUp( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowMouseUp: function( row, e ) {},
	
	/**
   * Dispatcher method called when a left click has been fired.
   * figures out if the user wants to select or activate a row.
   * @param {HTMLElement} row the TR that has been clicked
   * @param {MouseEvent} e
   **/
	rowLeftClick: function( row, e )
	{
		e = e ? e : window.event;
		( e.stopPropagation ) ? e.stopPropagation() : e.cancelBubble = true;
		( e.preventDefault ) ? e.preventDefault() : e.returnValue = false;
		
		//alert('rlc');
		
		var row_id = row.id;
		if ( !this.hasActiveRowId() )
		{
			this.activateRow( row_id );        // no active row?
		}
		else if ( this.getActiveRowId() == row_id )
		{
			// commented as this breaks doubleclicking a row sorta
			// this.deactivateRow();
			
		}
		else if ( e.altKey )
		{
			this.toggleSelectRow( row_id );
		} 
		else if ( e.shiftKey )
		{
			this.rangeSelectRow( row_id );
		}
		else
		{
			this.activateRow( row_id );
		}
		
		
		this.onRowLeftClick( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowLeftClick: function( row, e ) {},
	
	
	/**
   * Dispatcher method called when a right click has been fired.
   * figures out if the user wants to select or activate a row.
   * @param {HTMLElement} row the TR that has been clicked
   * @param {MouseEvent} e
   **/
	rowRightClick: function( row, e ) { 
		//alert( 'rowRightClick' ); 
      
		this.onRowRightClick( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowRightClick: function( row, e ) {},
	
	
	
	/**
   * Event Handler.
   * Just passes the event on to the callback.
   * @param {HTMLElement} row the TR that received the event
   * @param {MouseEvent} e
   **/
	rowDblClick: function( row, e )
	{
		e = e ? e : window.event;
		( e.stopPropagation ) ? e.stopPropagation() : e.cancelBubble = true;
		( e.preventDefault ) ? e.preventDefault() : e.returnValue = false;
		this.onRowDblClick( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowDblClick: function( row, e ) {
		this.callObservers( 'onRowDblClick' );
	},
	
	
	
	/**
   * Event Handler.
   * Just passes the event on to the callback.
   * @param {HTMLElement} row the TR that received the event
   * @param {MouseEvent} e
   **/
	rowMouseOver: function( row, e )
	{
		e = e ? e : window.event;
		( e.stopPropagation ) ? e.stopPropagation() : e.cancelBubble = true;
		( e.preventDefault ) ? e.preventDefault() : e.returnValue = false;
		this.onRowMouseOver( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowMouseOver: function( row, e ) {},
	
	/**
   * Event Handler.
   * Just passes the event on to the callback.
   * @param {HTMLElement} row the TR that received the event
   * @param {MouseEvent} e
   **/
	rowMouseOut: function( row, e )
	{
		e = e ? e : window.event;
		( e.stopPropagation ) ? e.stopPropagation() : e.cancelBubble = true;
		( e.preventDefault ) ? e.preventDefault() : e.returnValue = false;
		this.onRowMouseOut( row, e );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRowMouseOut: function( row, e ) {},
	
	
	
	/**
   * Activate a row.
   * Only one row may be active at a time.
   * @param {string} row_id DOM id of the target row.
   **/
	activateRow: function( row_id )
	{
		// clear out selected... and set the row being activated as the first new row selected.
		this.clearSelectedRows();
		
		this.addSelectedRow( row_id );
		
		// set the activated row's classname to be active
		this._setRowClassName( row_id, "active" );
		
		// log the new row as the active one
		this.setActiveRowId( row_id );
		
		// run the callback
		this.onActivateRow( row_id );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onActivateRow: function( row_id ) {},
	
	/**
	*
	*/
	deactivateRow: function( )
	{
		this.clearSelectedRows();
		this.setActiveRowId( null );
	},
	
	/**
   * Select a range of rows from the active_id, to the row_id
   * @param {string} row_id DOM id of the target row.
   **/
	rangeSelectRow: function( row_id ) 
	{
		// is the active row after the one clicked?... if so, then we'll loop ascending..
		var n_start = this._findRowIndex( row_id );
		var n_end   = this._findRowIndex( this.getActiveRowId() );
		var n_asc   = (n_end > n_start);
		
		var rows = this.getRows();
		for ( var i = n_start; i != n_end; (n_asc ? i++ : i--) )
		{
			if ( !this.isSelectedRow( rows[i].id ) ) this.selectRow( rows[i].id );
		}
		this.onRangeSelectRow( row_id );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onRangeSelectRow: function( row_id ) {},
	
	
	
	/**
   * Toggle the selected status of the row_id
   * @param {string} row_id DOM id of the target row.
   **/
	toggleSelectRow: function( row_id )
	{
		if ( this.isSelectedRow( row_id ) )
		{
			this.unselectRow( row_id );
			this.onToggleSelectRow( row_id );
		}
		else
		{
			this.selectRow( row_id );
			this.onToggleSelectRow( row_id );
		}
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onToggleSelectRow: function( row_id ) {},
	
	
	
	/**
   * Select a row by row_id
   * @param {string} row_id DOM id of the target row.
   **/
	selectRow: function( row_id )
	{
		this._setRowClassName( row_id, "selected" );
		this.addSelectedRow( row_id );
		this.onSelectRow( row_id );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onSelectRow: function( row_id ) {},
	
	
	
	/**
   * Unselect a row by row_id
   * @param {string} row_id DOM id of the target row.
   **/
	unselectRow: function( row_id )
	{
		
		this._resetRowClassName( row_id )
		this.removeSelectedRow( row_id );
		this.onUnselectRow( row_id );
	},
	/**
   * Abstract method to be used by subclasses.
   **/
	onUnselectRow: function( row_id ) {},
	
	
	/**
   * Get all the rows in the grid.
   * Makes sure to only return real rows and not include the rows that
   *   make up the grouping top and bottom.
   * @return An array of HTMLTableRowElements
   * @type array
   **/
	getRows: function()
	{
		var rows  = [];
		var table = this.gridDataTable;
		if ( table && table.tBodies ) {
			for ( var i = 0, n = table.tBodies.length; i < n; i++ ) {
				if ( table.tBodies[i].className == "gridRowSet" ) {
					for ( var ii = 0, nn = table.tBodies[i].rows.length; ii < nn; ii++ ) {
						rows.push( table.tBodies[i].rows[ii] );
					}
				}
			}
		}
		
		return rows;
	},
	
	
	/**
   * Select all rows.
   **/
	selectAllRows: function()
	{
		var rows = this.getRows();
		for ( var i = 0, n = rows.length; i < n; i++ )
		{
			if ( !this.isSelectedRow( rows[i].id ) ) this.selectRow( rows[i].id );
		}
	},
	
	
	/**
   * Remove rows from the grid.
   * @type void
   **/
	deleteSelectedRows: function()
	{
		var rows  = this.getSelectedRows();
		var table = this.gridDataTable;
		
		if ( !table ) return;
		
		for ( var i = 0, n = rows.length; i < n; i++ ) {
			var row = $( rows[i] );
			if ( row ) table.removeChild( row );
		}
		
		this.clearSelectedRows();
		this.setActiveRowId( null );
	},
	
	
	
	/**
   * Setter.
   * @param {string} row_id
   **/
	setActiveRowId: function( row_id ) { this._activeRow = row_id; },
	/**
   * Getter.
   * @type string
   **/
	getActiveRowId: function() { return this._activeRow; },
	/**
   * Property exists?
   * @type bool
   **/
	hasActiveRowId: function() { if ( this._activeRow ) return this._activeRow.length > 0; },
	/**
   * Get the element named by ActiveRowId.
   * @type HTMLElement
   **/
	getActiveRow: function() { return $(this._activeRow); },
	
	
	/**
   * Setter.
   * @param {array} rows an array of row ID strings
   **/
	setSelectedRows: function( rows ) { this._selectedRows = rows; },
	/**
   * Getter.
   * @type array
   **/
	getSelectedRows: function() { return this._selectedRows },
   /**
	* Getter
	* @param string attribute
	**/
	getActiveRowAttribute: function ( attribute )
	{
		var activeRow = this.getActiveRow();
		return activeRow.getAttribute( attribute );
	},
	/**
	* Getter
	* @type array
	**/
	getSelectedRowsAttribute: function ( attribute )
	{
		var selectedRowsAttrs = new Array();
		var selectedRows = this.getSelectedRows();
		//alert('sel.length:'+selectedRows.length);
		for ( var i = 0, n = selectedRows.length; i < n; i++ ) {
			var row = $(selectedRows[i]);
			selectedRowsAttrs.push( row.getAttribute( attribute ) );
		}
		return selectedRowsAttrs;
	},
	
	/**
	* Find a row by an attribute and value
	*/
	getRowIdByAttribute: function( attribute, value )
	{
		var rows = this.getRows();
		for ( var i=0, n=rows.length; i<n; i++ ) {
			var row = $(rows[i]);
			if ( row.getAttribute( attribute ) == value )
				return row.id;
		}
		return false;
	},
	
	/**
   * Remove a single row ID from the set.
   * @param {string} row_id row ID to remove
   **/
	removeSelectedRow: function( row_id )
	{
		var index = this._findSelectedRowIndex( row_id );
		if ( index != -1 ) this._selectedRows.splice( index, 1 );
	},
	/**
   * Add a single row ID to the set.
   * @param {string} row_id row ID to add
   **/
	addSelectedRow: function( row_id ) { this._selectedRows.push( row_id ) },
	/**
   * Is this row ID in the set?
   * @type bool
   * @param {string} row_id row ID to query
   **/
	isSelectedRow: function( row_id ) { return ( this._findSelectedRowIndex( row_id ) != -1 ); },
	/**
   * Remove a all row IDs from the set and reset their CSS className.
   **/
	clearSelectedRows: function()
	{
		var rows = this.getSelectedRows();
      var i = rows.length;
		do {
			this._resetRowClassName( rows[i] )
		}
      while (i--);
      
		this.setSelectedRows( [] );
	},
	
	
	/**
   * When someone mousedowns on the sort col header, set col clicked 
	* and for one click disable the context menu
   * @param {string} row_id row ID to query
   **/
	onSortHeaderMouseDown: function( e, col, order ) 
	{
		this.sortClickedCol = col;
		return;
	},
	
	/**
   * When someone mouseups on the sort col header, test for right or left click
   * @param {MouseEvent} e
   * @param {string} column
   * @param {string} column label
   * @param {string} column order for sort
   **/
	onSortHeaderMouseUp: function( e, col, label, order ) 
	{
		if ( Draggables.delayInterval ) Draggables.delayInterval = window.clearInterval( Draggables.delayInterval );
      
		// make sure they really want to sort here, i.e. they clicked and let up on the same col anchor
		if ( this.sortClickedCol != col ) return;
		this.sortClickedCol = false; // reset
		
      // don't activate sort or context menu if they're in the middle of dragging
		if ( !this.draggingColumn ) {
			
			// Left-click == sorting
			if ( Event.isLeftClick( e ) ) {
				this.sort( col, order );
			}
			
			// Right-click == column context menu
			else if ( Event.isRightClick( e ) ) {
				this.openColContextMenu( e, col, label );
			}
		}
		
		// toggle this back to false every time
		this.draggingColumn = false;
		
		return false;
	},
	
	/**
   * When someone mouseups on the sort col header, test for right or left click
   * @param {MouseEvent} e
   * @param {string} column
   * @param {string} column label
   * @param {string} column order for sort
   **/
	onTextHeaderMouseUp: function( e, col, label ) 
	{
		if (Draggables.delayInterval) Draggables.delayInterval = window.clearInterval( Draggables.delayInterval );
      
		
		if ( !this.draggingColumn ) {
			
			// Right-click == column context menu
			if ( Event.isRightClick( e ) ) {
				this.openColContextMenu( e, col, label );
			}
		}
		
		// toggle this back to false every time
		this.draggingColumn = false;
		
		return false;
	},
	
	
	/**
	* Open the column-oriented context menu
   * @param {MouseEvent} e
   * @param {string} column
   * @param {string} column label
	*/
	openColContextMenu: function( e, col, label )
	{
		// start off afresh
		this.clearColContextMenuTimer();
      
		// get the holder div and reset, turn on, and position
		var x = Event.pointerX(e)-2;
      var y = Event.pointerY(e)-2;
      //alert("x:"+x+", y:"+y);
      
		var gridColContextMenu = $( this.gridPopupDivId );
		gridColContextMenu.innerHTML = '';
		gridColContextMenu.className = 'gridColContextMenu';
		gridColContextMenu.style.display = "block";
		gridColContextMenu.style.left = x + 'px';
		gridColContextMenu.style.top = y + 'px';
		gridColContextMenu.onmouseover = this.clearColContextMenuTimer.bind( this );
		gridColContextMenu.onmouseout = this.startColContextMenuTimer.bind( this );
		
		var gridColContextMenuList = gridColContextMenu.appendChild( document.createElement( 'div' ) );
		gridColContextMenuList.className = 'gridColContextMenuList';
		gridColContextMenuList.onmouseover = this.clearColContextMenuTimer.bind( this );
		gridColContextMenuList.onmouseout = this.startColContextMenuTimer.bind( this );
		
      
		// Group By
      if ( $( 'gridHeaderCell_' + this.get_container_id() + '_' + col ).className.match( 'sortable' ) ) {
         var menuItem = $( 'gridColContextMenuItemTemplate' ).cloneNode( true );
         menuItem.id = "";
         
         var imgNode = document.getElementsByClassName( 'menuItemIcon', menuItem )[0];
         imgNode.className += " groupByColumnIcon";
         var labelNode = document.getElementsByClassName( 'menuItemText', menuItem )[0];
         
         menuItem.datagrid = this; // store a reference
			menuItem.col = col; // store a reference
			if ( this.get_group() == col ) {
				menuItem.onclick = function() {
					this.datagrid.deactivateColContextMenu();
					this.datagrid.group( null );
				}
				labelNode.innerHTML = "Ungroup by " + label;
			}
			else {
				menuItem.onclick = function() {
					this.datagrid.deactivateColContextMenu();
					this.datagrid.group( this.col );
				}
				labelNode.innerHTML = "Group by " + label;
			}
         
         gridColContextMenuList.appendChild( menuItem );
      }
      
		// Remove Col
      if ( $( 'gridHeaderCell_' + this.get_container_id() + '_' + col ).className.match( 'removeable' ) ) {
         var menuItem = $( 'gridColContextMenuItemTemplate' ).cloneNode( true );
         menuItem.id = "";
         
         var imgNode = document.getElementsByClassName( 'menuItemIcon', menuItem )[0];
         imgNode.className += " removeColumnIcon";
         var labelNode = document.getElementsByClassName( 'menuItemText', menuItem )[0];
         labelNode.innerHTML = "Remove " + label;
         
         menuItem.datagrid = this; // store a reference
			menuItem.col = col; // store a reference
			menuItem.label = label; // store a reference
			menuItem.onclick = function() {
				this.datagrid.deactivateColContextMenu();
				this.datagrid.removeColumn( this.col, this.label );
			}
         
         gridColContextMenuList.appendChild( menuItem );
      }
		
      // Quicksearch
      if ( $( 'gridHeaderCell_' + this.get_container_id() + '_' + col ).className.match( 'quicksearchable' ) ) {
         var menuItem = $( 'gridColContextMenuItemFilterTemplate' ).cloneNode( true );
         menuItem.id = "";
         
         var filterInput = document.getElementsByClassName( 'filterByColumnInput', menuItem )[0];
         if ( this.colContextMenuFilters[col] )
				filterInput.value = this.colContextMenuFilters[col];
         filterInput.datagrid = this; // store a reference
			filterInput.col = col; // store a reference
         filterInput.onkeyup = function() { 
            this.datagrid.clearColContextMenuTimer();
            this.datagrid.filterRowsByCol( this.col, this.value );
         }
            
         gridColContextMenuList.appendChild( menuItem );
      }
      
     
      
      // after all, if menu is hanging off the screen, move it in
      var pos = Position.page( this.gridContainer )
      var xtotal = pos[0] + this.gridContainer.offsetWidth;
      // and add 15 for padding
      var xtest = parseInt( gridColContextMenu.style.left ) + gridColContextMenuList.offsetWidth + 15;
      var diff = xtest-xtotal;
      if ( diff > 0 )
         gridColContextMenu.style.left = parseInt( gridColContextMenu.style.left ) - diff + "px";
      
	},
	
	/**
	* Hide the Column Context Menu
	*/
	deactivateColContextMenu: function() {
		$( this.gridPopupDivId ).style.display = "none";
	},
	
	
	/**
	* Filter gridDataTable rows 
	* param {string} col 
	* param {string} value to search for in col
	*/
	filterRowsByCol: function( col, value )
	{
		// remember the value
		this.colContextMenuFilters[col] = value;
      
      // get the colIndex
      var colIndex = this.getDataColIndex( col );
      
		// lose the table in DOM for speed
      this.removeDataTableFromDOM();
      
		// loop through the data rows and turn on / off display
      var reg = new RegExp( value, "i" );
      var rows = this.gridDataTable.rows;
		var i = rows.length-1;
		do {
			var row = rows[i];
			if ( value == '' || row.cells[colIndex].innerHTML.match( reg ) )
				row.style.display = "";	
			else
				row.style.display = "none";
		}
      while (i--);
      
		// put it back
      this.restoreDataTableToDOM();
      
		this.resize();
		this.startColContextMenuTimer();
	},
	
	/**
   * Called by menu's mouseout handler.
   **/
	startColContextMenuTimer: function(  )
	{
		this.clearColContextMenuTimer();
		$( this.gridPopupDivId )._hideTimer = window.setTimeout( this.deactivateColContextMenu.bind( this ), this._hideColContextMenuDelay );
	},
	
	/**
   * Called by menu's mouseover handler.
   **/
	clearColContextMenuTimer: function( )
	{
		if ( $( this.gridPopupDivId )._hideTimer ) {
			$( this.gridPopupDivId )._hideTimer = window.clearTimeout( $( this.gridPopupDivId )._hideTimer );
		}
	},
   
	
	
	/**
	* Turn on/off visibility of draggable div
	*/
	toggleColsResizableVisibility: function( bool )
	{
		var visibility = bool ? "visible" : "hidden";
		Element.childrenWithClassName( this.gridHeaderRow, 'gridHeaderCellResizer' ).each( function(remover) { 
         remover.style.visibility = visibility; 
      });
		
		return true;
	},
	
	
	
	/**
	* Turn on/off Scriptaculous header sortability
	* @param bool
	*/
	toggleColsDraggable: function( bool )
	{
		if ( this.colsDraggable ) {
			var container_id = this.get_container_id();
			if ( bool ) {
				Sortable.destroy( "gridHeaderRow_" + container_id );
				Sortable.create( "gridHeaderRow_" + container_id, { tag: 'th', constraint: 'horizontal', only: 'draggable', ghosting: false, scroll: this.gridSuperContainer.id, delay: this.dragDelay, overlap: 'horizontal', ignorePositionY: true } );
				this.sortableObserver = new SortableObserver("gridHeaderRow_" + container_id, this);
				
				// inline functions from observer to datagrid
				this.sortableObserver.datagrid = this; // store a reference
            this.sortableObserver.onActivate = function( eventName, draggable, event ) {
               // we only want to run this once, and Draggables notify all observers
               if ( draggable.element.id.match( this.element.id.replace( /gridHeaderRow_/, '' ) ) )
                  this.datagrid.onColDragActivate( eventName, draggable, event );
            }
            this.sortableObserver.onEnd = function( eventName, draggable, event ) {
               // we only want to run this once, and Draggables notify all observers
               if ( draggable.element.id.match( this.element.id.replace( /gridHeaderRow_/, '' ) ) )
                  this.datagrid.onColDragEnd( eventName, draggable, event );
            }
				Draggables.addObserver( this.sortableObserver );
			}
			else { 
				Sortable.destroy( "gridHeaderRow_" + container_id );
				Draggables.removeObserver( this.sortableObserver );
				this.sortableObserver = {};
			}
		}
	},
	
   /**
   * Callback from Scriptaculous for before a grid has its column moved around
   * this in here refers to the SortableObserver
   * @param {string} eventName
   * @param {obj} draggable
   * @param {obj} event
   */	
   onColDragActivate: function( eventName, draggable, event )
   {
      // set boolean so we know not to sort or do anything else
      this.draggingColumn = true;
      
      // change our cursor so we know we can now move the column
      var draglink = $( draggable.element.id.replace( 'gridHeaderCell', 'gridHeaderSort' ) );
      if ( draglink )
         draglink.style.cursor = "move";
      else
         $( draggable.element.id.replace( 'gridHeaderCell', 'gridText' ) ).style.cursor = "move";
      
   },
   
   /**
   * Callback from Scriptaculous when draggable has stopped
   * @param {string} eventName
   * @param {obj} draggable
   * @param {obj} event
   */	
   onColDragEnd: function( eventName, draggable, event )
   {
      // container_id
      var container_id = this.get_container_id();
      
      // get the col string
      var col = draggable.element.id.replace( 'gridHeaderCell_' + container_id + '_', '' );
      
      // default for prefs
      var reload = false;
      
      // change this cols a tag style back to a link cursor
      var draglink = $( 'gridHeaderSort_' + container_id + '_' + col );
      if ( draglink )
         draglink.style.cursor = "pointer";
      else
         $( draggable.element.id.replace( 'gridHeaderCell', 'gridText' ) ).style.cursor = "";
      
      
      // If we have data rows that are affected
      if ( this.gridDataTable ) {
         
         // if not many rows, we can update prefs and just use DOM for visual
         var rowlength = this.gridDataTable.rows.length;
         if ( rowlength < this._rowReloadLimitOnDrag ) {
            
            
            // Figure out the new position
            var insertBeforeCol;
            var colStates = this.getColStates();
            for (var i=0, n=colStates.length, lastTest=colStates.length-1; i<n; i++) {
               if ( col == colStates[i].split( '|' )[0] ) {
                  if ( i == lastTest )
                     insertBeforeCol = "LAST";
                  else 
                     insertBeforeCol = colStates[i+1].split( '|' )[0];
                  break;
               }
            }
            // spinner
            this.buildLoadingAnimation();
            
            // Move the column - we'll turn off the animation in there
            this.moveColumnTo( col, insertBeforeCol );
         }
         
         // update prefs, and reload the whole grid - too much data to reorg in DOM
         else {
            reload = true;
         }
      }
      
      // now update prefs and reload only for the long data situation
      var colStates = this.getColStates();
      this.updateColStatePrefs( colStates, reload );
   },

	
	/**
	* Take care of swapping order of the columns in DOM for the COLGROUP in gridDataTable
	* as well as in the gridDataTable.rows as well
	* @param int colIndex1 from index
	* @param int colIndex2 to index
	*/
	moveColumnTo: function( col, insertBeforeCol )
	{
      
      //alert('moving:'+col+', to:'+insertBeforeCol+' for container_id:'+this.get_container_id() );
		var colIndex = this.getDataColIndex( col );
      var insertBeforeColIndex = this.getDataColIndex( insertBeforeCol );
      
		// need container_id
		var container_id = this.get_container_id();
		
		// Fix the gridDataTable COLGROUP first
		var gridDataCol =  'gridDataCol_' + container_id + '_' + col;
		
		// test undef previousSibling text node for Moz
		var undefNode = false;
		if ( $( gridDataCol ).previousSibling && $( gridDataCol ).previousSibling.nodeType == 3 ) 
			undefNode = this.gridDataColgroup.removeChild( $( gridDataCol ).previousSibling );
		
		// strip the column now
		var colNode = this.gridDataColgroup.removeChild( $( gridDataCol ) );
		
		// if Moz, put the column and then the blank text node back in
		if ( undefNode ) {
			var newNode = this.gridDataColgroup.insertBefore( colNode, $( 'gridDataCol_' + container_id + '_' + insertBeforeCol ).previousSibling );
			this.gridDataColgroup.insertBefore( undefNode, newNode );
		}
		// just add it back in
		else {
			this.gridDataColgroup.insertBefore( colNode, $( 'gridDataCol_' + container_id + '_' + insertBeforeCol ) );
		}
      
      // lose the table in DOM for speed
      this.removeDataTableFromDOM();
      
		// Fix the gridDataTable rows
		var i = this.gridDataTable.rows.length-1;
      var rows = this.gridDataTable.rows;
		do {
         rows[i].insertBefore( rows[i].cells[colIndex], rows[i].cells[insertBeforeColIndex] );
		}
      while (i--);
      
      // now back in since we're done
      this.restoreDataTableToDOM();
      
      // stop the loading animation in here after we've moved the column
      this.stopLoadingAnimation();
	},
   
   
	
	/**
	* Called when user mousedowns on the column resizer
	* @param {MouseEvent} e event object
	* @param {string} container_id
	* @param {string} key of col to resize
	*/
	startColResize: function( e, container_id, col ) {
		
		this.toggleColsDraggable( false );
		
		// set resizing col for reference
		this.resizingCol = col;
		
		// start watching the mouse and set current position
		document.onmouseup = this.stopColResize.bindAsEventListener(this);
		document.onmousemove = this.colResizing.bindAsEventListener(this);
		this.dragStartPos = Event.pointerX(e);
		
		// store some DOM in memory since we have container_id and col here
		this.resize_sortlink = $( 'gridHeaderSort_' + container_id + '_' + col );
		this.resize_gridtext = $( 'gridText_' + container_id + '_' + col );
		this.resize_header_rowcell = $( 'gridHeaderCell_' + container_id + '_' + col );
		this.resize_data_col = $( 'gridDataCol_' + container_id + '_' + col );
      
		// calc a minwidth based on field header label div which includes potential sort icon +10forfun
		this.resize_minwidth = $( 'gridHeaderLabel_' + container_id + '_' + col ).offsetWidth + 10;
		
		// turn on resize guides 
		if ( this.gridDataContainer ) {
         // give em height
			var h = this.gridDataContainer.offsetHeight;
			this.resizeColGuideLeft.style.display = this.resizeColGuideRight.style.display = "block";
			this.resizeColGuideLeft.style.height = this.resizeColGuideRight.style.height = h + "px";
			
			// position em
         var pos = Position.page( $( 'gridHeaderCell_' + container_id + '_' + col ) );
			var x = pos[0];
			var y = pos[1] + $( 'gridHeaderCell_' + container_id + '_' + col ).offsetHeight;
			
			// adjust x for scrollposition on gridSuperContainer
			x -= this.gridSuperContainer.scrollLeft;
			
			// left guide
			this.resizeColGuideLeft.style.left = x + 'px';
			this.resizeColGuideLeft.style.top = y + 'px';
			
			// right guide
			this.resizeColGuideRight.style.left = x + this.resize_header_rowcell.offsetWidth + 'px';
			this.resizeColGuideRight.style.top = y + 'px';
		}
		
		// disable LAST cell while dragging
		var gridLastHeaderCellWidth = parseInt( this.gridLastHeaderCell.style.width );
		if ( gridLastHeaderCellWidth > 0 ) {
			this.gridLastHeaderCell.style.width = "0px";
			this.gridHeaderTable.style.width = parseInt( this.gridHeaderTable.style.width ) - gridLastHeaderCellWidth + 'px';
         
			//this.gridLastHeaderCell.style.width = "auto";
         //this.gridHeaderTable.style.width = "auto";
			
		}
		
		// make it clear to user that this col box grows/shrinks
		if ( this.resize_sortlink )
			this.resize_sortlink.className = "gridSort gridCellResizing";
		else if ( this.resize_gridtext ) 
			this.resize_gridtext.className = "gridText gridCellResizing";
      
      
		return false;
	},
   
   
	/**
	* Called on mousedrag to resize the column and perhaps its data
	* @param e event object
	*/
	colResizing: function( e ) {
		
		// calc new size
		var dragEndPos = Event.pointerX(e);
		var dragDiff = parseInt(dragEndPos - this.dragStartPos);
		
		// compare orig width to dragdiff to see if we should set stuff
		var origWidth = parseInt( this.resize_header_rowcell.style.width );
		var newColWidth = dragDiff + origWidth;
		
		// test against headerLabel text size
		if (newColWidth >= this.resize_minwidth) {
         
			// set em here for endColResizing
			this.newColWidth = newColWidth;
			this.dragEndPos = dragEndPos;			
			
			// resize the header rowcell
			this.resize_header_rowcell.style.width = this.newColWidth + "px";
			
			// subtract size of resizer+1=5 - for some reason this keeps the resizer from dropping out in IE
			var newInnerWidth = this.newColWidth-5;
			
			// resize sortlink span inside
			if (this.resize_sortlink)
				this.resize_sortlink.style.width =  newInnerWidth + "px";
			
			// resize gridtext inside
			if (this.resize_gridtext)
				this.resize_gridtext.style.width = newInnerWidth + "px";
			
			// calc new width
			this.newContainerWidth = dragDiff + parseInt( this.gridContainer.getAttribute( 'containerWidth' ) );
			// and set the attribute for future window resizes
			this.gridContainer.setAttribute( 'containerWidth', this.newContainerWidth );
         
			// resize grid container
			this.gridContainer.style.width = this.newContainerWidth + "px";
			
			// reset the header container and its table
			this.gridHeaderTable.style.width = this.newContainerWidth + "px";
			
			// resize data stuff and move the right guide
			if ( this.gridDataContainer ) {
            
				this.resizeColGuideRight.style.left = dragDiff + parseInt( this.resizeColGuideRight.style.left ) + 'px';
         }
			
			// store this position
			this.dragStartPos = this.dragEndPos;
			
		}
		return false;
	},
	
	/**
	* Called automatically when the user lets the mouseup after starting a column resize
	*  - also makes a call to update preferences
	* @param e event object
	*/
	stopColResize: function( e ) {
      this.resizingCol = false;
      document.onmousemove = null;
      document.onmouseup = null;
      
      // dataContainer
      if (this.gridDataContainer) {
         this.resizeColGuideLeft.style.display = this.resizeColGuideRight.style.display = "none";
         
         
         // only change widths if we truly dragged
         if ( this.dragEndPos ) {
            
            // lose the table in DOM for speed
            this.removeDataTableFromDOM();
            
            this.gridLastDataCol.style.width = "0px";
            this.resize_data_col.style.width = this.newColWidth + "px";
            this.gridDataContainer.style.width = this.newContainerWidth + "px";
            this.gridDataTable.style.width = this.newContainerWidth + "px";
            
            // put it back
            this.restoreDataTableToDOM();
         }
      }
      
      // style em back to the way they were
      if ( this.resize_sortlink )
         this.resize_sortlink.className = this.resize_sortlink.styleName = "gridSort";
      if ( this.resize_gridtext )
         this.resize_gridtext.className = this.resize_gridtext.styleName = "gridText";
      
      // Update Prefs but don't reload
      if ( this.unique_id != '' && this.pref_module != '' ) {
         var colStates = this.getColStates();
         this.updateColStatePrefs( colStates );
      }

      
      // resize the left column to fix for container width being smaller than super width
      this.resize();
      
      // turn on draggability again
      this.toggleColsDraggable( true );
      
      return false;
      
	},
	
	/**
	* Roll up the col order and sizing from the Grid
	* @param container_id optional container_id
	*/
	getColStates: function()
	{
      var container_id = this.get_container_id();
      var cn = this.gridHeaderRow.cells;
      var colStates = new Array();
      for (var i=0, n=cn.length; i<n; ++i) {
         var colname = cn[i].id.replace( 'gridHeaderCell_' + container_id + '_', '' );
         
         // ignore last spacer
         if ( colname != "LAST" ) {
            var colwidth = parseInt( cn[i].style.width );
            colStates.push( colname + '|' + colwidth );
         }
      }
      return colStates;
	},
   
   /**
	* Returns an array of col indexes ( i.e. names, whatever )
	*/
	getColIndexes: function()
	{
      var container_id = this.get_container_id();
      var cn = this.gridHeaderRow.cells;
      var colIndexes = new Array();
      for (var i=0, n=cn.length; i<n; ++i) {
         var colkey = cn[i].id.replace( 'gridHeaderCell_' + container_id + '_', '' );
         colIndexes.push( colkey );
      }
      return colIndexes;
	},
   
   /**
	* Returns an array of col indexes ( i.e. names, whatever )
	*/
	getColIndex: function( col )
	{
      var container_id = this.get_container_id();
      var colIndexes = this.getColIndexes();
      for (var i=0, n=colIndexes.length; i<n; ++i) {
         if ( colIndexes[i] == col )
           return i;
      }
	},
	
	/**
	* Returns an array of col data indexes ( i.e. names, whatever )
	*/
	getDataColIndexes: function()
	{
      var container_id = this.get_container_id();
      var cn = $( 'gridDataColgroup_' + container_id ).getElementsByTagName( 'col' );
      var colIndexes = new Array();
      for (var i=0, n=cn.length; i<n; ++i) {
         var colkey = cn[i].id.replace( 'gridDataCol_' + container_id + '_', '' );
         colIndexes.push( colkey );
      }
      return colIndexes;
	},
	
   /**
	* Returns an array of col indexes ( i.e. names, whatever )
	*/
	getDataColIndex: function( col )
	{
      var container_id = this.get_container_id();
      var colIndexes = this.getDataColIndexes();
      for (var i=0, n=colIndexes.length; i<n; ++i) {
         if ( colIndexes[i] == col )
           return i;
      }
	},
	
	
	/**
	* Send an update with colStates with blank string - make your prefs_handler understand this
	*/
	resetColumnDefaults: function()
	{
		this.updateColStatePrefs( null, true );
	},
	
	/**
	* Add a column
	* @param {string} foramtted like field|fieldlabel|fieldwidth from a select most likely
	*/
	addColumn: function( data )
	{
		if (!data) return;
		var goods = data.split('|');
		var field = goods[0];
		var fieldlabel = goods[1];
		var fieldwidth = parseInt(goods[2]);
		
		this.buildLoadingAnimation();
      
		var colStates = this.getColStates();
		colStates.push( field + '|' + fieldwidth );
		//alert(colStates.join(",")); return;
		
      // update and reload
		this.updateColStatePrefs( colStates, true );
		
	},
	
	/**
	* Remove a column from the grid
	* @param {string} col
	* @param {string} label
	*/
	removeColumn: function( col, label )
	{
		
		// temporarily disable draggability
		this.toggleColsDraggable( false );
		
		var confirmtest = window.confirm( "Remove the column '" + label + "?" );
		if (confirmtest) {
			// proceed - get colStates and then wipe
			var colStates = this.getColStates();
			this.buildLoadingAnimation();
			for (var i=0, n=colStates.length; i<n; i++) {
				var colinfo = colStates[i].split('|');
				if ( colinfo[0] == col ) {
					colStates.splice( i, 1 );
					break;
				}
			}
			
			// AJAX update
			this.updateColStatePrefs( colStates, true );
		}
		
		// reenable draggability if not reloading
		else {
			this.toggleColsDraggable( true );
		}
		
	},
	
	/**
	* update colStates prefs
   * @param {array} colStates return from getColStates
   * @param {bool} reload afterwards
	*/
	updateColStatePrefs: function( colStates, reload )
	{
      // start workin it
		if ( reload )
			this.buildLoadingAnimation();
      
      // if null, then we're resetting
      if ( !colStates )
         colStates = '';
      // join the colStates array
      else 
         colStates = colStates.join(",");
      
      
      // AJAX
      new AjaxRequest( {
         method: "get",
         parameters: 'module='+this.pref_module + '&grid_uid=' + this.unique_id + '&grid_colstates=' + colStates,
         onComplete: this.onUpdateColStatePrefs.bind( this, reload )
      } );
		
	},
   
   /**
   * onUpdateColStatePrefs - when Ajax.Request onCompletes
   * @param {bool} reload
   * @param {obj} transport
   * @param {obj} json results
   */
   onUpdateColStatePrefs: function( reload, transport, json )
   {
      //alert( reload+":"+transport+":"+json+":"+this.get_container_id() ); return;
      if ( reload ) { 
         this.buildLoadingAnimation();
         this.load();
      }
      this.callObservers( 'onUpdateColStates' );
   },
   
	
	/**
   * @private
   **/
	_setRowClassName: function( row_id, name )
	{
		var element = $( row_id );
		if ( element )
		{
			element.style.backgroundColor = '';
			element.className = name;
		}
	},
	/**
   * @private
   **/
	_resetRowClassName: function( row_id )
	{
		var element = $( row_id );
		if ( element )
		{
			// first clear out any color
			element.style.backgroundColor = '';
			element.className = element.getAttribute( 'stripe' );
		}
	},
	/**
   * @private
   **/
	_findSelectedRowIndex: function( row_id )
	{
		var rows = this.getSelectedRows();
		for ( var i = 0, n = rows.length; i < n; i++ )
		{
			if ( rows[i] == row_id ) return i;
		}
		return -1;
	},
	/**
   * @private
   **/
	_findRowIndex: function( row_id )
	{
		var rows = this.getRows();
		for ( var i = 0, n = rows.length; i < n; i++ )
		{
			if ( rows[i].id == row_id ) return i;
		}
		return -1;
	}
	
}

// EOF


