/*
 * yui-ext 0.33
 * Copyright(c) 2006, Jack Slocum.
 */

/**
 * @class YAHOO.ext.grid.Grid
 * @extends YAHOO.ext.util.Observable
 * This class represents the primary interface of a component based grid control.
 * <br><br>Usage:<pre><code>
 var grid = new YAHOO.ext.grid.Grid('my-container-id', dataModel, columnModel);
 // set any options
 grid.render();
 // or using a config
 var grid = new YAHOO.ext.grid.Grid('my-container-id', {
     dataModel: myDataModel,
     colModel: myColModel,
     selModel: mySelectionModel,
     autoSizeColumns: true,
     monitorWindowResize: false,
     trackMouseOver: true
 }).render();
 * </code></pre>
 * <b>Common Problems:</b><br/>
 * - Grid does not resize properly when going smaller: Setting overflow hidden on the container 
 * element will correct this<br/>
 * - If you get el.style[camel]= NaNpx or -2px or something related, be certain you have given your container element 
 * dimensions. The grid adapts to your container's size, if your container has no size defined then the results
 * are unpredictable.<br/>
 * - Do not render the grid into an element with display:none. Try using visibility:hidden. Otherwise there is no way for the 
 * grid to calculate dimensions/offsets.<br/>
 * @requires YAHOO.util.Dom
 * @requires YAHOO.util.Event
 * @requires YAHOO.util.CustomEvent 
 * @requires YAHOO.ext.Element
 * @requires YAHOO.ext.util.Browser
 * @requires YAHOO.ext.util.CSS
 * @requires YAHOO.ext.SplitBar 
 * @requires YAHOO.ext.EventObject 
 * @constructor
 * @param {String/HTMLElement/YAHOO.ext.Element} container The element into which this grid will be rendered - 
 * The container MUST have some type of size defined for the grid to fill. The container will be 
 * automatically set to position relative if it isn't already.
 * @param {Object} config A config object that sets properties on this grid OR the data model to bind to
 * @param {Object} colModel (optional) The column model with info about this grid's columns
 * @param {Object} selectionModel (optional) The selection model for this grid (defaults to DefaultSelectionModel)
 */
YAHOO.ext.grid.Grid = function(container, config, colModel, selectionModel){
	/** @private */
	this.container = YAHOO.ext.Element.get(container);
	this.container.update('');
	this.container.setStyle('overflow', 'hidden');
	this.id = this.container.id;
	this.rows = [];
    this.rowCount = 0;
    this.fieldId = null;
    var dataModel = config; // for legacy pre config support
    this.dataModel = dataModel;
    this.colModel = colModel;
    this.selModel = selectionModel;
	this.activeEditor = null;
	this.editingCell = null;
	
	/** The minimum width a column can be resized to. (Defaults to 25)
	 * @type Number */
	this.minColumnWidth = 25;
	
	/** True to automatically resize the columns to fit their content <b>on initial render</b>
	 * @type Boolean */
	this.autoSizeColumns = false;
	
	/** True to measure headers with column data when auto sizing columns
	 * @type Boolean */
	this.autoSizeHeaders = false;
	
	/**
	 * True to autoSize the grid when the window resizes - defaults to true
	 */
	this.monitorWindowResize = true;
	
	/** If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
	 * rows measured to get a columns size - defaults to 0 (all rows).
	 * @type Number */
	this.maxRowsToMeasure = 0;
	
	/** True to highlight rows when the mouse is over (default is false)
	 * @type Boolean */
	this.trackMouseOver = false;
	
	/** True to enable drag and drop of rows
	 * @type Boolean */
	this.enableDragDrop = false;
	
	/** True to stripe the rows (default is true)
	 * @type Boolean */
	this.stripeRows = true;
	/** True to fit the height of the grid container to the height of the data (defaults to false)
	 * @type Boolean */
	this.autoHeight = false;
	
	/** True to fit the width of the grid container to the width of the columns (defaults to false)
	 * @type Boolean */
	this.autoWidth = false;
	
	/**
	 * The view used by the grid. This can be set before a call to render(). 
	 * Defaults to a YAHOO.ext.grid.GridView or PagedGridView depending on the data model.
	 * @type Object
	 */
	this.view = null;
	
	/** A regular expression defining tagNames 
     * allowed to have text selection (Defaults to <code>/INPUT|TEXTAREA|SELECT/i</code>) */
    this.allowTextSelectionPattern = /INPUT|TEXTAREA|SELECT/i;
	
	if(typeof config == 'object' && !config.getRowCount){// must be config object
	    YAHOO.ext.util.Config.apply(this, config);
	}
	
	/** @private */
	this.setValueDelegate = this.setCellValue.createDelegate(this);
	
	/** @private */
	this.events = {
	    // raw events
	    /**
	     * @event click
	     * The raw click event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'click' : true,
	    /**
	     * @event dblclick
	     * The raw dblclick event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'dblclick' : true,
	    /**
	     * @event mousedown
	     * The raw mousedown event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'mousedown' : true,
	    /**
	     * @event mouseup
	     * The raw mouseup event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'mouseup' : true,
	    /**
	     * @event mouseover
	     * The raw mouseover event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'mouseover' : true,
	    /**
	     * @event mouseout
	     * The raw mouseout event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'mouseout' : true,
	    /**
	     * @event keypress
	     * The raw keypress event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'keypress' : true,
	    /**
	     * @event keydown
	     * The raw keydown event for the entire grid.
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'keydown' : true,
	    
	    // custom events
	    
	    /**
	     * @event cellclick
	     * Fires when a cell is clicked
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {Number} columnIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'cellclick' : true,
	    /**
	     * @event celldblclick
	     * Fires when a cell is double clicked
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {Number} columnIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'celldblclick' : true,
	    /**
	     * @event rowclick
	     * Fires when a row is clicked
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'rowclick' : true,
	    /**
	     * @event rowdblclick
	     * Fires when a row is double clicked
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'rowdblclick' : true,
	    /**
	     * @event headerclick
	     * Fires when a header is clicked
	     * @param {Grid} this
	     * @param {Number} columnIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'headerclick' : true,
	    /**
	     * @event rowcontextmenu
	     * Fires when a row is right clicked
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'rowcontextmenu' : true,
	    /**
	     * @event headercontextmenu
	     * Fires when a header is right clicked
	     * @param {Grid} this
	     * @param {Number} columnIndex
	     * @param {YAHOO.ext.EventObject} e
	     */
	    'headercontextmenu' : true,
	    /**
	     * @event beforeedit
	     * Fires before a cell is edited
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {Number} columnIndex
	     */
	    'beforeedit' : true,
	    /**
	     * @event afteredit
	     * Fires after a cell is edited
	     * @param {Grid} this
	     * @param {Number} rowIndex
	     * @param {Number} columnIndex
	     */
	    'afteredit' : true,
	    /**
	     * @event bodyscroll
	     * Fires when the body element is scrolled
	     * @param {Number} scrollLeft
	     * @param {Number} scrollTop
	     */
	    'bodyscroll' : true,
	    /**
	     * @event columnresize
	     * Fires when the user resizes a column
	     * @param {Number} columnIndex
	     * @param {Number} newSize
	     */
	    'columnresize' : true,
	    /**
	     * @event startdrag
	     * Fires when row(s) start being dragged 
	     * @param {Grid} this
	     * @param {YAHOO.ext.GridDD} dd The drag drop object
	     * @param {event} e The raw browser event
	     */
	    'startdrag' : true,
	    /**
	     * @event enddrag
	     * Fires when a drag operation is complete
	     * @param {Grid} this
	     * @param {YAHOO.ext.GridDD} dd The drag drop object
	     * @param {event} e The raw browser event
	     */
	    'enddrag' : true,
	    /**
	     * @event dragdrop
	     * Fires when dragged row(s) are dropped on a valid DD target 
	     * @param {Grid} this
	     * @param {YAHOO.ext.GridDD} dd The drag drop object
	     * @param {String} targetId The target drag drop object
	     * @param {event} e The raw browser event
	     */
	    'dragdrop' : true,
	    /**
	     * @event dragover
	     * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
	     * @param {Grid} this
	     * @param {YAHOO.ext.GridDD} dd The drag drop object
	     * @param {String} targetId The target drag drop object
	     * @param {event} e The raw browser event
	     */
	    'dragover' : true,
	    /**
	     * @event dragenter
	     *  Fires when the dragged row(s) first cross another DD target while being dragged 
	     * @param {Grid} this
	     * @param {YAHOO.ext.GridDD} dd The drag drop object
	     * @param {String} targetId The target drag drop object
	     * @param {event} e The raw browser event
	     */
	    'dragenter' : true,
	    /**
	     * @event dragout
	     * Fires when the dragged row(s) leave another DD target while being dragged 
	     * @param {Grid} this
	     * @param {YAHOO.ext.GridDD} dd The drag drop object
	     * @param {String} targetId The target drag drop object
	     * @param {event} e The raw browser event
	     */
	    'dragout' : true
	};
};

YAHOO.ext.grid.Grid.prototype = { 
    /**
     * Called once after all setup has been completed and the grid is ready to be rendered.
     * @return {YAHOO.ext.grid.Grid} this
     */
    render : function(){
        if((!this.container.dom.offsetHeight || this.container.dom.offsetHeight < 20) 
                || this.container.getStyle('height') == 'auto'){
    	    this.autoHeight = true;   
    	}	       
    	if((!this.container.dom.offsetWidth || this.container.dom.offsetWidth < 20)){
    	    this.autoWidth = true;   
    	}	       
    	if(!this.view){
    	    if(this.dataModel.isPaged()){
    		    this.view = new YAHOO.ext.grid.PagedGridView();
    	    }else{
    	        this.view = new YAHOO.ext.grid.GridView();
    	    }
    	}
    	this.view.init(this);
        this.el = getEl(this.view.render(), true);
        var c = this.container;
        c.mon("click", this.onClick, this, true);
        c.mon("dblclick", this.onDblClick, this, true);
        c.mon("contextmenu", this.onContextMenu, this, true);
        c.mon("selectstart", this.cancelTextSelection, this, true);
        c.mon("mousedown", this.cancelTextSelection, this, true);
        c.mon("mousedown", this.onMouseDown, this, true);
        c.mon("mouseup", this.onMouseUp, this, true);
        if(this.trackMouseOver){
            this.el.mon("mouseover", this.onMouseOver, this, true);
            this.el.mon("mouseout", this.onMouseOut, this, true);
        }
        c.mon("keypress", this.onKeyPress, this, true);
        c.mon("keydown", this.onKeyDown, this, true);
        this.init();
        return this;
    },
    
    init : function(){
        this.rows = this.el.dom.rows;
        if(!this.disableSelection){
	        if(!this.selModel){
	            this.selModel = new YAHOO.ext.grid.DefaultSelectionModel(this);
	        }
	        this.selModel.init(this);
	        this.selModel.onSelectionChange.subscribe(this.updateField, this, true);
        }else{
            this.selModel = new YAHOO.ext.grid.DisableSelectionModel(this);
            this.selModel.init(this);
        }
        
        if(this.enableDragDrop){
            this.dd = new YAHOO.ext.grid.GridDD(this, this.container.dom);
        }
     },   

    /**
     * Resets the grid for use with a new configuration and/or data and column models. After calling this function
     * you will need to call render() again. Any listeners for this grid will be retained.
     * Warning: any listeners manually attached (not through the grid) to the grid's container 
     * element will be removed. 
     * @param {Object} config Standard config object with properties to set on this grid
     * @return {YAHOO.ext.grid.Grid} this
     */
    reset : function(config){
        this.destroy(false, true);
        YAHOO.ext.util.Config.apply(this, config);
        return this;
    },
    
    /**
     * Destroy this grid. 
     * @param {Boolean} removeEl True to remove the element
     */
    destroy : function(removeEl, keepListeners){
        var c = this.container;
        c.removeAllListeners();
        this.view.destroy();
        YAHOO.ext.EventManager.removeResizeListener(this.view.onWindowResize, this.view);
        this.view = null;
        this.colModel.purgeListeners();
        if(!keepListeners){
            this.purgeListeners();
        }
        c.update('');
        if(removeEl === true){
            c.remove();
        }
    },
    
    /**
     * Replace the current data model with a new one (experimental)
     * @param {DataModel} dm The new data model
     * @pram {Boolean} rerender true to render the grid rows from scratch
     */
    setDataModel : function(dm, rerender){
        this.view.unplugDataModel(this.dataModel);
        this.dataModel = dm;
        this.view.plugDataModel(dm);
        if(rerender){
            dm.fireEvent('datachanged');
        }
    },
    
    onMouseDown : function(e){
        this.fireEvent('mousedown', e);
    },
    
    onMouseUp : function(e){
        this.fireEvent('mouseup', e);
    },
    
    onMouseOver : function(e){
        this.fireEvent('mouseover', e);
    },
    
    onMouseOut : function(e){
        this.fireEvent('mouseout', e);
    },
    
    onKeyPress : function(e){
        this.fireEvent('keypress', e);
    },
    
    onKeyDown : function(e){
        this.fireEvent('keydown', e);
    },
    
    fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
    on : YAHOO.ext.util.Observable.prototype.on,
    addListener : YAHOO.ext.util.Observable.prototype.addListener,
    delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
    removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
    purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
    bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
    
    onClick : function(e){
        this.fireEvent('click', e);
        var target = e.getTarget();
        var row = this.getRowFromChild(target);
        var cell = this.getCellFromChild(target);
        var header = this.getHeaderFromChild(target);
        if(row){
            this.fireEvent('rowclick', this, row.rowIndex, e);
        }
        if(cell){
            this.fireEvent('cellclick', this, row.rowIndex, cell.columnIndex, e);
        }
        if(header){
            this.fireEvent('headerclick', this, header.columnIndex, e);
        }
    },

    onContextMenu : function(e){
        var target = e.getTarget();
        var row = this.getRowFromChild(target);
        var header = this.getHeaderFromChild(target);
        if(row){
            this.fireEvent('rowcontextmenu', this, row.rowIndex, e);
        }
        if(header){
            this.fireEvent('headercontextmenu', this, header.columnIndex, e);
        }
        e.preventDefault();
    },

    onDblClick : function(e){
        this.fireEvent('dblclick', e);
        var target = e.getTarget();
        var row = this.getRowFromChild(target);
        var cell = this.getCellFromChild(target);
        if(row){
            this.fireEvent('rowdblclick', this, row.rowIndex, e);
        }
        if(cell){
            this.fireEvent('celldblclick', this, row.rowIndex, cell.columnIndex, e);
        }
    },
    
    /**
     * Starts editing the specified for the specified row/column
     * @param {Number} rowIndex
     * @param {Number} colIndex
     */
    startEditing : function(rowIndex, colIndex){
        var row = this.rows[rowIndex];
        var cell = row.childNodes[colIndex];
        this.stopEditing();
        setTimeout(this.doEdit.createDelegate(this, [row, cell]), 10);
    },
        
    /**
     * Stops any active editing
     */
    stopEditing : function(){
        if(this.activeEditor){
            this.activeEditor.stopEditing();
        }
    },
        
    /** @ignore */
    doEdit : function(row, cell){
        if(!row || !cell) return;
        var cm = this.colModel;
        var dm = this.dataModel;
        var colIndex = cell.columnIndex;
        var rowIndex = row.rowIndex;
        if(cm.isCellEditable(colIndex, rowIndex)){
           var ed = cm.getCellEditor(colIndex, rowIndex);
           if(ed){
               if(this.activeEditor){
                   this.activeEditor.stopEditing();
               }
               this.fireEvent('beforeedit', this, rowIndex, colIndex);
               this.activeEditor = ed;
               this.editingCell = cell;
               this.view.ensureVisible(row, true);
               try{
                   cell.focus();
               }catch(e){}
               ed.init(this, this.el.dom.parentNode, this.setValueDelegate);
               var value = dm.getValueAt(rowIndex, cm.getDataIndex(colIndex));
               // set timeout so firefox stops editing before starting a new edit
               setTimeout(ed.startEditing.createDelegate(ed, [value, row, cell]), 1);
           }   
        }  
    },
    
    setCellValue : function(value, rowIndex, colIndex){
         this.dataModel.setValueAt(value, rowIndex, this.colModel.getDataIndex(colIndex));
         this.fireEvent('afteredit', this, rowIndex, colIndex);
    },
    
    /** @ignore Called when text selection starts or mousedown to prevent default */
    cancelTextSelection : function(e){
        var target = e.getTarget();
        if(target && target != this.el.dom.parentNode && !this.allowTextSelectionPattern.test(target.tagName)){
            e.preventDefault();
        }
    },
    
    /**
     * Causes the grid to manually recalculate it's dimensions. Generally this is done automatically, 
     * but if manual update is required this method will initiate it.
     */
    autoSize : function(){
        this.view.updateWrapHeight();
        this.view.adjustForScroll();
    },
    
    /**
     * Scrolls the grid to the specified row
     * @param {Number/HTMLElement} row The row object or index of the row
     */
    scrollTo : function(row){
        if(typeof row == 'number'){
            row = this.rows[row];
        }
        this.view.ensureVisible(row, true);
    },
    
    /** @private */
    getEditingCell : function(){
        return this.editingCell;    
    },
    
    /**
     * Binds this grid to the field with the specified id. Initially reads and parses the comma 
     * delimited ids in the field and selects those items. All selections made in the grid
     * will be persisted to the field by their ids comma delimited.
     * @param {String} The id of the field to bind to
     */
    bindToField : function(fieldId){
        this.fieldId = fieldId;
        this.readField();
    },
    
    /** @private */
    updateField : function(){
        if(this.fieldId){
            var field = YAHOO.util.Dom.get(this.fieldId);
            field.value = this.getSelectedRowIds().join(',');
        }
    },
    
    /**
     * Causes the grid to read and select the ids from the bound field - See {@link #bindToField}.
     */
    readField : function(){
        if(this.fieldId){
            var field = YAHOO.util.Dom.get(this.fieldId);
            var values = field.value.split(',');
            var rows = this.getRowsById(values);
            this.selModel.selectRows(rows, false);
        }
    },
	
	/**
	 * Returns the table row at the specified index
	 * @param {Number} index
	 * @return {HTMLElement} 
	 */
    getRow : function(index){
        return this.rows[index];
    },
	
	/**
	 * Returns the rows that have the specified id(s). The id value for a row is provided 
	 * by the DataModel. See {@link YAHOO.ext.grid.DefaultDataModel#getRowId}.
	 * @param {String/Array} An id to find or an array of ids
	 * @return {HtmlElement/Array} If one id was passed in, it returns one result. 
	 * If an array of ids was specified, it returns an Array of HTMLElements
	 */
    getRowsById : function(id){
        var dm = this.dataModel;
        if(!(id instanceof Array)){
            for(var i = 0; i < this.rows.length; i++){
                if(dm.getRowId(i) == id){
                    return this.rows[i];
                }
            }
            return null;
        }
        var found = [];
        var re = "^(?:";
        for(var i = 0; i < id.length; i++){
            re += id[i];
            if(i != id.length-1) re += "|";
        }
        var regex = new RegExp(re + ")$");
        for(var i = 0; i < this.rows.length; i++){
            if(regex.test(dm.getRowId(i))){
                found.push(this.rows[i]);
            }
        }
        return found;
    },
    
    /**
	 * Returns the row that comes after the specified row - text nodes are skipped.
	 * @param {HTMLElement} row
	 * @return {HTMLElement} 
	 */
    getRowAfter : function(row){
        return this.getSibling('next', row);
    },
    
    /**
	 * Returns the row that comes before the specified row - text nodes are skipped.
	 * @param {HTMLElement} row
	 * @return {HTMLElement} 
	 */
    getRowBefore : function(row){
        return this.getSibling('previous', row);
    },
    
    /**
	 * Returns the cell that comes after the specified cell - text nodes are skipped.
	 * @param {HTMLElement} cell
	 * @param {Boolean} includeHidden
	 * @return {HTMLElement} 
	 */
    getCellAfter : function(cell, includeHidden){
        var next = this.getSibling('next', cell);
        if(next && !includeHidden && this.colModel.isHidden(next.columnIndex)){
            return this.getCellAfter(next);
        }
        return next;
    },
    
    /**
	 * Returns the cell that comes before the specified cell - text nodes are skipped.
	 * @param {HTMLElement} cell
	 * @param {Boolean} includeHidden
	 * @return {HTMLElement} 
	 */
    getCellBefore : function(cell, includeHidden){
        var prev = this.getSibling('previous', cell);
        if(prev && !includeHidden && this.colModel.isHidden(prev.columnIndex)){
            return this.getCellBefore(prev);
        }
        return prev;
    },
    
    /**
	 * Returns the last cell for the row - text nodes and hidden columns are skipped.
	 * @param {HTMLElement} row
	 * @param {Boolean} includeHidden
	 * @return {HTMLElement} 
	 */
    getLastCell : function(row, includeHidden){
        var cell = this.getElement('previous', row.lastChild);
        if(cell && !includeHidden && this.colModel.isHidden(cell.columnIndex)){
            return this.getCellBefore(cell);
        }
        return cell;
    },
    
    /**
	 * Returns the first cell for the row - text nodes and hidden columns are skipped.
	 * @param {HTMLElement} row
	 * @param {Boolean} includeHidden
	 * @return {HTMLElement} 
	 */
    getFirstCell : function(row, includeHidden){
        var cell = this.getElement('next', row.firstChild);
        if(cell && !includeHidden && this.colModel.isHidden(cell.columnIndex)){
            return this.getCellAfter(cell);
        }
        return cell;
    },
    
    /**
     * @private
     * Gets siblings, skipping text nodes
     * @param {String} type The direction to walk: 'next' or 'previous'
     * @param {HTMLElement} node
	 */
    getSibling : function(type, node){
        if(!node) return null;
        type += 'Sibling';
        var n = node[type];
        while(n && n.nodeType != 1){
            n = n[type];
        }
        return n;
    },
    
    /**
     * Returns node if node is an HTMLElement else walks the siblings in direction looking for 
     * a node that is an element
     * @param {String} direction The direction to walk: 'next' or 'previous'
     * @private
     */
    getElement : function(direction, node){
        if(!node || node.nodeType == 1) return node;
        else return this.getSibling(direction, node);
    },
    
    /**
     * @private
     */
    getElementFromChild : function(childEl, parentClass){
        if(!childEl || (YAHOO.util.Dom.hasClass(childEl, parentClass))){
		    return childEl;
	    }
	    var p = childEl.parentNode;
	    var b = document.body;
	    while(p && p != b){
            if(YAHOO.util.Dom.hasClass(p, parentClass)){
            	return p;
            }
            p = p.parentNode;
        }
	    return null;
    },
    
    /**
	 * Returns the row that contains the specified child element.
	 * @param {HTMLElement} childEl
	 * @return {HTMLElement} 
	 */
    getRowFromChild : function(childEl){
        return this.getElementFromChild(childEl, 'ygrid-row');
    },
    
    /**
	 * Returns the cell that contains the specified child element.
	 * @param {HTMLElement} childEl
	 * @return {HTMLElement} 
	 */
    getCellFromChild : function(childEl){
        return this.getElementFromChild(childEl, 'ygrid-col');
    },
    
    
    /**
     * Returns the header element that contains the specified child element.
     * @param {HTMLElement}  childEl
	 * @return {HTMLElement} 
	 */
     getHeaderFromChild : function(childEl){
        return this.getElementFromChild(childEl, 'ygrid-hd');
    },
    
    /**
     * Convenience method for getSelectionModel().getSelectedRows() - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRows}</small> for more details.
     * @return {Array}
     */
    getSelectedRows : function(){
        return this.selModel.getSelectedRows();
    },
    
    /**
     * Convenience method for getSelectionModel().getSelectedRows()[0] - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRows}</small> for more details.
     * @return {HTMLElement}
     */
    getSelectedRow : function(){
        if(this.selModel.hasSelection()){
            return this.selModel.getSelectedRows()[0];
        }
        return null;
    },
    
    /**
     * Get the selected row indexes
     * @return {Array} Array of indexes
     */
    getSelectedRowIndexes : function(){
        var a = [];
        var rows = this.selModel.getSelectedRows();
        for(var i = 0; i < rows.length; i++) {
        	a[i] = rows[i].rowIndex;
        }
        return a;
    },
    
    /**
     * Gets the first selected row or -1 if none are selected
     * @return {Number}
     */
    getSelectedRowIndex : function(){
        if(this.selModel.hasSelection()){
           return this.selModel.getSelectedRows()[0].rowIndex;
        }
        return -1;
    },
    
    /**
     * Convenience method for getSelectionModel().getSelectedRowIds()[0] - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRowIds}</small> for more details.
     * @return {String}
     */
    getSelectedRowId : function(){
        if(this.selModel.hasSelection()){
           return this.selModel.getSelectedRowIds()[0];
        }
        return null;
    },
    
    /**
     * Convenience method for getSelectionModel().getSelectedRowIds() - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRowIds}</small> for more details.
     * @return {Array}
     */
    getSelectedRowIds : function(){
        return this.selModel.getSelectedRowIds();
    },
    
    /**
     * Convenience method for getSelectionModel().clearSelections() - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#clearSelections}</small> for more details.
     */
    clearSelections : function(){
        this.selModel.clearSelections();
    },
    
        
    /**
     * Convenience method for getSelectionModel().selectAll() - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#selectAll}</small> for more details.
     */
    selectAll : function(){
        this.selModel.selectAll();
    },
    
        
    /**
     * Convenience method for getSelectionModel().getCount() - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getCount}</small> for more details.
     * @return {Number}
     */
    getSelectionCount : function(){
        return this.selModel.getCount();
    },
    
    /**
     * Convenience method for getSelectionModel().hasSelection() - 
     * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#hasSelection}</small> for more details.
     * @return {Boolean}
     */
    hasSelection : function(){
        return this.selModel.hasSelection();
    },
    
    /**
     * Returns the grid's SelectionModel.
     * @return {SelectionModel}
     */
    getSelectionModel : function(){
        if(!this.selModel){
            this.selModel = new DefaultSelectionModel();
        }
        return this.selModel;
    },
    
    /**
     * Returns the grid's DataModel.
     * @return {DataModel}
     */
    getDataModel : function(){
        return this.dataModel;
    },
    
    /**
     * Returns the grid's ColumnModel.
     * @return {ColumnModel}
     */
    getColumnModel : function(){
        return this.colModel;
    },
    
    /**
     * Returns the grid's GridView object.
     * @return {GridView}
     */
    getView : function(){
        return this.view;
    },
    /**
     * Called to get grid's drag proxy text, by default returns this.ddText. 
     * @return {String}
     */
    getDragDropText : function(){
        return this.ddText.replace('%0', this.selModel.getCount());
    }
};
/**
 * Configures the text is the drag proxy (defaults to "%0 selected row(s)"). 
 * %0 is replaced with the number of selected rows.
 * @type String
 */
YAHOO.ext.grid.Grid.prototype.ddText = "%0 selected row(s)";