/**
 * Ingrid : JQuery Datagrid Control
 *
 * Copyright (c) 2009 Matthew Knight (http://www.reconstrukt.com http://slu.sh)
 *                    Patrice Blanchardie (http://www.inisos.fr)
 * 
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * @requires jQuery v1.2+
 * @version 0.9.3
 * @todo load JSON data, etc.
 * 
 * Revision: 0.9.3.0 2009/06/26 Patrice Blanchardie
 * - bug fixes: selection behaviour,
 *              hscroll width,
 *              attribute selector,
 *              result error handler,
 *              header auto-resize
 * - feature: new param: unsortable columns
 * 
 * Revision: 0.9.3.1 2009/06/29 Patrice Blanchardie
 * - bug fixes: unable to clear last selected row from cookie
 *              added onRowSelect callback during pre-selection
 * - feature: new functions : (un)select all
 *            selection is now stored for all pages!
 *            page changed callback
 *
 */


jQuery.fn.ingrid = function(o){


        var cfg = {
                height: 200,                                                    // height of our datagrid (scrolling body area)
                
                savedStateLoad : false,                                 // when Ingrid is initialized, should it load data from a previously saved state?
                initialLoad : false,                                    // when Ingrid is initialized, should it load data immediately?


                colWidths: [225,225,225,225],                   // width of each column
                minColWidth: 60,                                                // minimum column width
                headerHeight: 30,                                               // height of our header
                headerClass: 'grid-header-bg',                  // header bg
                resizableCols: true,                                    // make columns resizable via drag + drop
                
                gridClass: 'datagrid',                                  // class of head & body
                rowClasses: [],                                                 // list of row classes (selected by cursor)
                colClasses: [],                                                 // array of classes : i.e. ['','grid-col-2','','']
                rowHoverClass: 'grid-row-hover',                // hovering over a row? use this class
                rowSelection: true,                                             // allow row selection?
                rowSelectedClass: 'grid-row-sel',               // selecting a row? use this class
                onRowSelect: function(tr, selected){},  // function to call when row is clicked
                
                /* sorting */
                sorting: true,
                colSortParams: [],                                              // value to pass as sort param when header clicked (i.e. '&sort=param') ex: ['col1','col2','col3','col4']
                sortAscParam: 'asc',                                    // param passed on ascending sort (i.e. '&dir=asc)
                sortDescParam: 'desc',                                  // param passed on ascending sort (i.e. '&dir=desc)
                sortedCol: 'col1',                                              // current data's sorted column (can be a key from 'colSortParams', or an int 0-n (for n columns)
                sortedColDir: 'desc',                                   // current data's sorted directions
                sortDefaultDir: 'desc',                                 // on 1st click, sort tihs direction
                sortAscClass: 'grid-sort-asc',                  // class for ascending sorted col
                sortDescClass: 'grid-sort-desc',                // class for descending sorted col
                sortNoneClass: 'grid-sort-none',                // ... not sorted? use this class
                unsortableCols: [],                                             // do not make theses columns sortable
                
                /* paging */
                paging: true,                                                   // create a paging toolbar
                pageNumber: 1,
                recordsPerPage: 0,
                totalRecords: 0,
                pageToolbarHeight: 25,
                pageToolbarClass: 'grid-page-toolbar',
                pageStartClass: 'grid-page-start',
                pagePrevClass: 'grid-page-prev',
                pageInfoClass: 'grid-page-info',
                pageInputClass: 'grid-page-input',
                pageNextClass: 'grid-page-next',
                pageEndClass: 'grid-page-end',
                pageLoadingClass: 'grid-page-loading',
                pageLoadingDoneClass: 'grid-page-loading-done',
                pageViewingRecordsInfoClass: 'grid-page-viewing-records-info',
                pageChanged: function(p){},             // called when page changed (loading finished)


                /* ajax stuff */
                url: 'remote.php',                                              // url to fetch data
                type: 'GET',                                                    // 'POST' or 'GET'
                dataType: 'html',                                               // 'html' or 'json' - expected dataType returned
                extraParams: {},                                                // a map of extra params to send to the server                          
                loadingClass: 'grid-loading',                   // loading modalmask div
                loadingHtml: '<div> </div>',                    
                
                /* should seldom change */
                resizeHandleHtml: '',                                   // resize handle html + css
                resizeHandleClass: 'grid-col-resize',
                scrollbarW: 22,                                                 // width allocated for scrollbar
                columnIDAttr: '_colid',                                 // attribute name used to groups TDs in columns
                ingridIDPrefix: '_ingrid',                              // prefix used to create unique IDs for Ingrid
                
                /* cookie, for saving state */
                cookieExpiresDays: 360,
                cookiePath: '/',
                
                /* not yet implemented */
                minHeight: 100,
                resizableGrid: true,
                dragDropCols: true,
                sortType: 'server|client|none',
                
                /* cfg functions */
                isSortableCol : function(colIndex) {
                        if (cfg.unsortableCols.length==0 || jQuery.inArray(colIndex, cfg.unsortableCols)==-1) {
                                return true;
                        }
                        return false;
                }
                
        };
        jQuery.extend(cfg, o);


        // break into 2 tables: header, body.
        // create header table
        var cols = new Array();
        var h = jQuery('<table cellpadding="0" cellspacing="0"></table>')
                                        .html(this.find('thead'))
                                        .addClass(cfg.gridClass)
                                        .addClass(cfg.headerClass)
                                        .height(cfg.headerHeight)
                                        .extend({
                                                cols : cols
                                        });
        // initialize columns
        h.find('th').each(function(i){
                                                                                                                 
                // init width
                jQuery(this).width(cfg.colWidths[i]);
                
                // put column text in a div, make unselectable
                var col_label = jQuery('<div />')
                                                                                .html(jQuery(this).html())
                                                                                .css({float: 'left', display: 'block'})
                                                                                .css('-moz-user-select', 'none')
                                                                                .css('-khtml-user-select', 'none')
                                                                                .css('user-select', 'none')
                                                                                .attr('unselectable', 'on');


                // column sorting?
                if (cfg.sorting && cfg.isSortableCol(i)) {
                        
                        var key = cfg.colSortParams[i] ? cfg.colSortParams[i] : i;
                        // is this column the default sorted column?
                        var cls = (key == cfg.sortedCol || i == cfg.sortedCol) ? 
                                                                        ( cfg.sortedColDir == cfg.sortAscParam ? cfg.sortAscClass : cfg.sortDescClass ) :
                                                                        ( cfg.sortNoneClass );


                        col_label.addClass(cls).click(function(){
                                var dir = col_label.hasClass(cfg.sortNoneClass) ? 
                                                                                cfg.sortDefaultDir : ( col_label.hasClass(cfg.sortAscClass) ? cfg.sortDescParam : cfg.sortAscParam );


                                var params = { sort : key, dir : dir };                                 
                                if (p) jQuery.extend(params, { page : p.getPage() } );
                                
                                g.load( params, function(){                                             
                                        var cls = col_label.hasClass(cfg.sortNoneClass) ? 
                                                                                        ( cfg.sortDefaultDir == cfg.sortAscParam ? cfg.sortAscClass : cfg.sortDescClass ) :
                                                                                        ( col_label.hasClass(cfg.sortAscClass) ? cfg.sortDescClass : cfg.sortAscClass );


                                        // re-init sortable cols
                                        var i2 = 0;
                                        g.getHeaders(function(col){
                                                col.find('div:first').each(function() {
                                                        if(cfg.isSortableCol(i2++))
                                                                jQuery(this).addClass(cfg.sortNoneClass).removeClass(cfg.sortAscClass).removeClass(cfg.sortDescClass);
                                                });
                                        });
                                        col_label.removeClass(cfg.sortAscClass).removeClass(cfg.sortDescClass).addClass(cls).removeClass(cfg.sortNoneClass);


                                });
                        });
                }
                
                // replace contents of <th>
                jQuery(this).html(col_label);
                
                // bind an event to easily resize columns
                jQuery(this).bind('resizeColumn', {col_num : i}, function(e, w){
                        
                        jQuery(this).width(w);  
                        
                        // auto enlarge while header is > headerHeight
                        while(jQuery(this).parent().height()>cfg.headerHeight) {
                                jQuery(this).width(++w);
                        }
                        
                        // set body cells to this width
                        g.resize();     
                        g.getColumn(e.data.col_num).each(function(){
                                jQuery(this).width(w);
                        });
                });
                
                // append resize handle?
                if (cfg.resizableCols) {
                        // make column headers resizable
                        var handle = jQuery('<div />').html(cfg.resizeHandleHtml == '' ? '-' : cfg.resizeHandleHtml).addClass(cfg.resizeHandleClass);
                        handle.bind('mousedown', function(e){
                                // start resize drag
                                var th          = jQuery(this).parent();
                                var left  = e.clientX;
                                z.resizeStart(th, left);
                        });
                        jQuery(this).append(handle);
                }
        });
        
        // create body table. surround body with container div for scrolling
        // setting width on first row keeps it from "blinking"
        var row = this.find('tr:first')
        jQuery(row).find('td').each(function(i){
                jQuery(this).width( cfg.colWidths[i] )                                                          
        });
        var b = jQuery('<div />')
                                        .html( jQuery('<table cellpadding="0" cellspacing="0"></table>').html( this.find('tbody') ).width( h.width() ).addClass(cfg.gridClass) )
                                        .css('overflow', 'auto')
                                        .height(cfg.height);
                        
        
        // resizable cols?
        // if so create a vertical resize divider, with unique ID
        if (cfg.resizableCols) {
                var z_sel = 'vertical-resize-divider' + new Date().getTime();
                var z   = jQuery('<div id="' + z_sel + '"></div>')
                                                .css({
                                                        backgroundColor: '#ababab', 
                                                        height: (cfg.headerHeight + cfg.height),
                                                        width: '4px',
                                                        position: 'absolute',
                                                        zIndex: '10',
                                                        display: 'block'
                                                })
                                                .extend({
                                                        resizeStart : function(th, eventX){
                                                                // this is fired onmousedown of the column's resize handle                                              
                                                                var pos = th.offset();
                                                                jQuery(this).show().css({
                                                                        top: pos.top,
                                                                        left: eventX
                                                                })
                                                                // when resizing, bind some listeners for mousemove & mouseup events
                                                                jQuery('body').bind('mousemove', {col : th}, function(e){               
                                                                        // on mousemove, move the vertical-resize-divider
                                                                        var th          = e.data.col;
                                                                        var pos         = th.offset();
                                                                        var col_w       = e.clientX - pos.left;
                                                                        // make sure cursor isn't trying to make column smaller than minimum
                                                                        if (col_w > cfg.minColWidth) {
                                                                                jQuery('#' + z_sel).css('left', e.clientX);                                                                             
                                                                        }                                                                                                                                               
                                                                })
                                                                jQuery('body').bind('mouseup', {col : th}, function(e){
                                                                        // on mouseup, 
                                                                        // 1.) unbind resize listener events from body
                                                                        // 2.) hide the vertical-resize-divider
                                                                        // 3.) trigger the resize event on the column
                                                                        jQuery(this).unbind('mousemove').unbind('mouseup');
                                                                        jQuery('#' + z_sel).hide();
                                                                        var th          = e.data.col;
                                                                        var pos         = th.offset();
                                                                        var col_w       = e.clientX - pos.left;
                                                                        if (col_w > cfg.minColWidth) {
                                                                                th.trigger('resizeColumn', [col_w]);
                                                                        } else {
                                                                                th.trigger('resizeColumn', [cfg.minColWidth]);
                                                                        }
                                                                })
                                                        }
                                                });
        }
        // paging?
        // if so create a paging toolbar
        if (cfg.paging) {
        
                // create a paging toolbar
                var totr  = cfg.recordsPerPage > 0 ? cfg.recordsPerPage : b.find('tr').length;
                
                // total records viewing message (if we know total records)
                // total record count might not be passed in config, it's sometimes an expensive hit to the DB
                var pv;
                if (cfg.totalRecords > 0) {
                        pv = jQuery('<div />')
                                                .addClass(cfg.pageViewingRecordsInfoClass)
                                                .extend({
                                                        updateViewInfo : function(loaded_rows, page){
                                                                var _start = ( (loaded_rows * (page - 1) + 1) );
                                                                var _end   = ( (loaded_rows * page) > cfg.totalRecords ? cfg.totalRecords : loaded_rows * page );
                                                                this.html('Viewing Rows ' + _start + ' - ' + _end + ' of ' + cfg.totalRecords);
                                                                return this;
                                                        }
                                                });
                        // update the "viewing x of y" record info
                        pv.updateViewInfo(totr, cfg.pageNumber);
                }
                
                // create our paging control container
                var p           = jQuery('<div />')
                                                                .addClass(cfg.pageToolbarClass)
                                                                .height(cfg.pageToolbarHeight)
                                                                .width(b.width())
                                                                .extend({                                                                               
                                                                                setPage : function(p){
                                                                                        var input = this.find('input.' + cfg.pageInputClass);
                                                                                        pload.removeClass(cfg.pageLoadingDoneClass);
                                                                                        g.load( {page : p}, function(){
                                                                                                input.val(p);
                                                                                                if (cfg.totalRecords > 0) {
                                                                                                        var totr = b.find('tr').length;
                                                                                                        pv.updateViewInfo(totr, p);
                                                                                                        if(cfg.pageChanged)
                                                                                                                cfg.pageChanged(p);
                                                                                                }
                                                                                                pload.addClass(cfg.pageLoadingDoneClass);
                                                                                        });
                                                                                        return this;
                                                                                },
                                                                                getPage : function(){
                                                                                        var p = Number(this.find('input.' + cfg.pageInputClass).val());
                                                                                        return p;
                                                                                }
                                                                });
        
                // start page button
                var pb1         = jQuery('<a href="#">&laquo;</a>').addClass(cfg.pageStartClass).click(function(){
                                                                        p.setPage(1);
                                                                });
        
                // prev page button
                var pb2         = jQuery('<a href="#">&lt;</a>').addClass(cfg.pagePrevClass).click(function(){
                                                                        var _p = p.getPage();                                                                                                                                                                                                                                                   
                                                                        if (_p > 1) {
                                                                                _p--;
                                                                                p.setPage(_p);
                                                                        }                                                                               
                                                                });
        
                // next page button
                if (cfg.totalRecords > 0) {
                        var totp = Math.ceil(cfg.totalRecords / totr);
                }
                var pb3         = jQuery('<a href="#">&gt;</a>').addClass(cfg.pageNextClass).click(function(){
                                                                        var _p = p.getPage(); _p++;
                                                                        if (totp) {
                                                                                if (_p <= totp) p.setPage(_p);
                                                                        } else {
                                                                                p.setPage(_p);
                                                                        }
                                                                });
                
                // loading indicator
                var pload       = jQuery('<div />').addClass(cfg.pageLoadingClass).addClass(cfg.pageLoadingDoneClass);
                
                // page field & form
                var pfld  = jQuery('<input type="text" value="' + cfg.pageNumber + '"/>').addClass(cfg.pageInputClass);
                var pinfo = jQuery('<div />')
                                                                .addClass(cfg.pageInfoClass)
                                                                .append(pfld);
                var pform = jQuery('<form></form>').append(pinfo).submit(function(){
                                                                        var _p = parseInt(p.getPage());
                                                                        if (_p) {
                                                                                if (totp) {
                                                                                        if (_p <= totp) p.setPage(_p);
                                                                                } else if (_p > 0) {
                                                                                        p.setPage(_p);
                                                                                }
                                                                        } else {
                                                                                alert('Please Enter a Valid Page Number.');
                                                                        }
                                                                        return false;
                                                                });
                
                // last page button & info (if we know total records)
                var pb4;
                if (cfg.totalRecords > 0) {
                        pinfo.html('Page ' + pinfo.html() + ' of ' + totp);
                        var pb4         = jQuery('<a href="#">&raquo;</a>').addClass(cfg.pageEndClass).click(function(){
                                                                                var _p = p.getPage(); _p++;
                                                                                if (totp) {
                                                                                         if (_p < totp) p.setPage(totp);
                                                                                }
                                                                        });
                } else {
                        pinfo.html('Page ' + pinfo.html());
                }
                
                p.append(pb1).append(pb2).append(pform).append(pb3).append(pb4).append(pload).append(pv);
        }


        // create a container div to for our main grid object
        // append & extend grid {g} with header {h}, body {b}, paging {p}, resize handle {z}
        var g = jQuery('<div />').append(h).append(b).extend({
                h : h,
                b : b
        });
        if (cfg.paging) {
                g.append(p).extend({ p : p });
        }
        if (cfg.resizableCols) {
                g.append(z.hide()).extend({ z : z });
        }


        // create some other piece-parts, like
        // ...a gap filler to fill gap over scrollport          
        var gap = jQuery('<div />').width(cfg.scrollbarW).addClass(cfg.headerClass).height(cfg.headerHeight).css({
                position: 'absolute',
                zIndex: '0'
        }).appendTo(g);
        // ...a loading modal mask
        var modalmask = jQuery('<div />').html(cfg.loadingHtml).addClass(cfg.loadingClass).css({
                position: 'absolute',           
                zIndex: '1000'
        }).appendTo(g).hide();


        // create methods on our grid object
        g.extend({
                
                selected_ids : [],
                
                load : function(params, cb) {
                        var data = jQuery.extend(cfg.extraParams, params);
                        
                        /*
                        alert(this + ' ...is jQuery')
                        alert(this[0] + ' ...is the div, id="' + this.attr('id') + '"')
                        */
                        
                        // show loading canvas
                        modalmask.width(b.width()).show();
                        
                        // save selected rows
                        g.saveSelectedRows();
                        
                        jQuery.ajax({
                                type: cfg.type.toUpperCase(),
                                url: cfg.url,
                                data: data,
                                success: function(result){
                                        if(result == "") {
                                                alert('Error: Empty result.');
                                                return;
                                        }
                                        // for JSON return type
                                        if (cfg.dataType == 'json') {
                                                var rows  = eval( '(' + result + ')' );
                                                // not supported :)
                                                alert('json = ' + rows);                                                
                                        }
                                        // for HTML (Table) return type
                                        if (cfg.dataType == 'html') {
                                                var $tbl = jQuery(result);
                                                var row  = $tbl.find('tr:first');
                                                if ( jQuery(row).find('td').length == cfg.colWidths.length ) {
                                                        // setting width on first row keeps it from "blinking"
                                                        jQuery(row).find('td').each(function(i){
                                                                // don't use width() - makes column headers jitter                                                                                                                                                                                                       
                                                                // g.getHeader(i).width()
                                                                jQuery(this).width( g.getHeader(i).css('width') )                                                               
                                                        });
                                                        // now swap the tbody's
                                                        b.find('tbody').html($tbl.find('tbody').html());
                                                        g.initStylesAndWidths();
                                                        
                                                        // remember the last loaded state for this grid?
                                                        g.saveState(data);
                                                        
                                                } else if (row.length < 1) {
                                                        // no rows returned
                                                        alert('Error: No Rows Returned.');
                                                } else {
                                                        // inconsistent results... too many (or too few) columns returned
                                                        alert('Error: Total columns returned [' + $tbl.find('tbody tr:first td').length + '] do not match Ingrid ['+ cfg.colWidths.length +'].');
                                                }
                                        }
                                        if (cb) cb();
                                },
                                error: function(){
                                        alert('Error: Could not load "' + cfg.url + '". Please check the URL and try again. ');
                                },
                                complete: function(){
                                        modalmask.hide();
                                }
                        });
                        return this;
                },
                
                // returns JSON
                getState : function() {
                        
                        /*
                        alert(this + ' ...is jQuery')
                        alert(this[0] + ' ...is the div, id="' + this.attr('id') + '"')
                        */
                        var props = {
                                url : 'nothing'                         
                        }
                        return props;
                },
                
                saveState : function(data){


                        // how can we deserialize the 'data' object from JSON, to a string, like: "{page:3}"
                        //   we could then save this JSON string into a cookie, 
                        //   and eval() it back out again when initStylesAndWidths() is called
                        
                        /*
                        I think I need the JSON lib?
                        JSON.toString(json_object)
                        
                        so, like
                        JSON.toString(props)
                        
                        would be nice to combine JSON & jQuery's cookie plugin, call it something like "cache"
                        which would let you serialize JSON objects as strings, for storage in cookies, and eval() them back out from a cookie later
                        
                        so you could call like: 
                        
                                jQuery.toCache(json_object, 'key')
                                json_object = eval( jQuery.fromCache('key') );
                                jQuery.clearCache('key')
                                
                        ...u could get creative and call it "save", "remember", "recall", "read", "store", "forget" or whatever
                        */
                        
                        if (jQuery.cookie) {
                                // save page #, column sort & dir
                                var g_id                = this.attr('id');
                                var param_str = 'page=' + data.page + ',sort=' + data.sort + ',dir=' + data.dir;
                                jQuery.cookie(g_id, param_str, {expires: cfg.cookieExpiresDays, path: cfg.cookiePath});
                        }
                        
                        /*
                        props.url = data;
                        alert( data.toString() );
                        alert( props.toString() );
                        */
                        
                },
                
                saveSelectedRows : function() {
                        if (jQuery.cookie) {
                                //var row_ids           = g.getSelectedRowIds();
                                var row_ids = g.selected_ids;
                                jQuery.cookie( this.attr('id') + '_rows', row_ids.join(','), {expires: cfg.cookieExpiresDays, path: cfg.cookiePath});                           
                        }
                },
                
                // returns <th> els
                getHeaders : function(cb) {
                        var ths = this.find('th');
                        if (cb) {
                                ths.each(function(){
                                        cb(jQuery(this));                                                        
                                });
                                return this;
                        } else {
                                return ths;
                        }
                },
                
                // returns single <th> el
                getHeader : function(i, cb) {
                        var th = this.find('th').slice(i, i+1);
                        if (cb) {
                                cb(jQuery(this));
                                return this;
                        } else {
                                return th;
                        }
                },
                
                // returns <td> els in column i
                getColumn : function(i, cb) {
                        var tds = this.find("tbody td[" + cfg.columnIDAttr + "='" + i + "']");
                        if (cb) {
                                tds.each(function(){
                                        cb(jQuery(this));                                                        
                                });
                                return this;
                        } else {
                                return tds;                                                      
                        }
                },
                
                // returns <tr> els
                getRows : function(cb) {
                        var trs = this.find("tbody tr");
                        if (cb) {
                                trs.each(function(){
                                        cb(jQuery(this));                                                        
                                });
                                return this;
                        } else {
                                return trs;                                                      
                        }
                },
                                
                // returns <tr> els
                getSelectedRows : function() {
                        return this.find("tbody tr[_selected='true']");
                },
                
                unSelectAll : function() {
                        g.getSelectedRows().each(function() {
                                $(this).attr("_selected", "true");
                                $(this).click();
                        });
                },
                
                selectAll : function() {
                        this.find("tbody tr").each(function() {
                                $(this).attr("_selected", "false");
                                $(this).click();
                        });
                },
                
                // returns an array of IDs (current view)
                getSelectedRowIds : function() {
                        var rows                        = g.getSelectedRows();
                        var row_ids             = [];
                        for (i=0; i<rows.length; i++) {
                                if ( jQuery(rows[i]).attr('id') ) row_ids.push( jQuery(rows[i]).attr('id') );
                        }
                        return row_ids;
                },
                
                // returns an array of IDs (saved in cookie)            
                getSavedRowIds : function() {
                        var row_ids = [];
                        if (jQuery.cookie) {
                                var str_ids = jQuery.cookie( this.attr('id') + '_rows' );
                                if (str_ids) row_ids = str_ids.split(',');
                        }                       
                        return row_ids;
                },
                
                resize : function() {
                        // set body table width based on header table 
                        // minimize calls to width() and offset()
                        var outer_w = h.width() + cfg.scrollbarW;
                        b.width(outer_w);


                        if (p) p.width(outer_w);
                        
                        if (gap) {
                                var pos = h.offset();
                                gap.css('left', outer_w - cfg.scrollbarW + pos.left).css('top', pos.top);
                        }
                },
                
                initStylesAndWidths : function() {
                        
                        // alert('setting styles and widths')
                        
                        var colWidths = new Array();
                        this.getHeaders().each(function(i){
                                // don't use width() - makes column headers jitter
                                // colWidths[i] = jQuery(this).width();
                                colWidths[i] = jQuery(this).css('width');
                        });


                        // make one pass of the grid, 
                        // initialize properties on rows & columns
                        
                        // old way to store ids
                        //var str_ids = '|' + g.getSavedRowIds().join('|') + '|';
                        
                        // pre-selected rows
                        g.selected_ids = g.getSavedRowIds();
                        
                        this.getRows().each(function(r){
                                
                                // custom row styles (striping, etc) & hover
                                if (cfg.rowClasses.length > 0) {
                                        var cursor = (r == 0 ? 0 : r % cfg.rowClasses.length);
                                        if (cfg.rowClasses[cursor] != '') {
                                                // custom row class
                                                jQuery(this).addClass(cfg.rowClasses[cursor]);                                                  
                                        }
                                        if (cfg.rowHoverClass != '') {
                                                // hover class
                                                jQuery(this).hover(
                                                        function() { if (jQuery(this).attr('_selected') != 'true') jQuery(this).removeClass(cfg.rowClasses[cursor]).addClass(cfg.rowHoverClass); },
                                                        function() { if (jQuery(this).attr('_selected') != 'true') jQuery(this).removeClass(cfg.rowHoverClass).addClass(cfg.rowClasses[cursor]); }
                                                );
                                        }
                                }
                                
                                // setup column IDs & classes on row's cells
                                jQuery(this).find('td').each(function(i){
                                        // column IDs & width
                                        // wrap the cell text in a div with overflow hidden, so cells aren't stretched wider by long text
                                        var txt = jQuery(this).html();
                                        jQuery(this).attr(cfg.columnIDAttr, i)
                                                                                        .width(colWidths[i])
                                                                                        .html( jQuery('<div />').html(txt).css('overflow', 'hidden') );
                                        // column colors
                                        if (cfg.colClasses.length > 0) {
                                                if (cfg.colClasses[i] != '') {
                                                        jQuery(this).addClass(cfg.colClasses[i]);
                                                }
                                        }
                                });
                                
                                // selection behaviour
                                if (cfg.rowSelection == true) {
                                        jQuery(this).click(function() {
                                                // test array state
                                                isAlreadySelected = jQuery(this).attr('id') != undefined && jQuery.inArray(jQuery(this).attr('id'), g.selected_ids) != -1;
                                                // test view state
                                                if (jQuery(this).attr('_selected') && jQuery(this).attr('_selected') == 'true') {
                                                        // switch to unselected state
                                                        jQuery(this).attr('_selected', 'false').removeClass(cfg.rowSelectedClass);
                                                        // remove from selected_ids array
                                                        if(isAlreadySelected)
                                                                g.selected_ids.splice(jQuery.inArray(jQuery(this).attr('id'), g.selected_ids),1);
                                                } else {
                                                        // switch to selected state
                                                        jQuery(this).attr('_selected', 'true').addClass(cfg.rowSelectedClass);
                                                        // push to selected_ids array
                                                        if(!isAlreadySelected)
                                                                g.selected_ids.push(jQuery(this).attr('id'));
                                                }
                                                
                                                // callback
                                                if (cfg.onRowSelect)
                                                        cfg.onRowSelect(this, (jQuery(this).attr('_selected') == 'true') );
                                        });
                                        
                                        // previously selected rows
                                        // (use table instead of str)
                                        //if (jQuery(this).attr('id') && str_ids.indexOf( '|' + jQuery(this).attr('id') + '|' ) != -1) {
                                        if (jQuery(this).attr('id')!=undefined && jQuery.inArray(jQuery(this).attr('id'), g.selected_ids) != -1) {
                                                // switch to selected state
                                                jQuery(this).attr('_selected', 'true').addClass(cfg.rowSelectedClass);
                                                // push to selected_ids array
                                                if(jQuery(this).attr('id') == undefined || jQuery.inArray(jQuery(this).attr('id'), g.selected_ids) == -1)
                                                        g.selected_ids.push(jQuery(this).attr('id'));
                                                // callback
                                                if (cfg.onRowSelect)
                                                        cfg.onRowSelect(this, true);
                                        }
                                }
                        });
                }                       
        });
        
        // don't break the chain
        // return a modified & extended jQ table object.
        // here,
        //      this     ...is jQuery
        //      this[0]  ...is a table
        
        /*
                        alert(this + ' ...is jQuery')
                        alert(this[0] + ' ...is a table')
                        alert(this.length + ' = total tables matched to selector')
        */


        return this.each(function(tblIter){
                // fires for each table[tblIter].
                // for each one,
                //      this     ...is a table


                /*
                alert(this + ' ...is a table [' + tblIter + '] , id="' + jQuery(this).attr('id') + '"')
                alert(g[0] + ' ...is the grid div html');
                */


                // append to doc
                var g_id =      cfg.ingridIDPrefix + '_' + jQuery( this ).attr('id') + '_' + tblIter;
                g.attr( 'id', g_id );
                jQuery( this ).replaceWith( g[0] )


                // init grid styles, etc
                g.initStylesAndWidths();
                
                // sync grid size to headers
                g.resize();
                
                // place the mask accordingly
                modalmask.width( h.width() + cfg.scrollbarW ).height( b.height()).css({
                        top: b.offset().top,
                        left: b.offset().left
                });


                // load it up?
                if (cfg.savedStateLoad && jQuery.cookie) {
                        var param_str = jQuery.cookie(g_id);
                        if (!param_str) {
                                g.load();
                                cfg.initialLoad = false;
                        } else {
                                // we have a saved state for this grid_id
                                var pairs       = param_str.split(',');
                                var params      = {};
                                var hash        = [];
                                for (i=0; i<pairs.length; i++) {
                                        var prop = pairs[i].split('=');
                                        hash[prop[0]] = prop[1];
                                }
                                if (hash['page'].toLowerCase() != 'undefined' && cfg.paging) {
                                        params.page = hash['page'];
                                        p.find('input.' + cfg.pageInputClass).val(params.page);
                                }
                                if (hash['sort'].toLowerCase() != 'undefined' && 
                                                hash['dir'].toLowerCase() != 'undefined') {
                                        
                                        params.sort = hash['sort'];
                                        params.dir      = hash['dir'];
                                        var colid = params.sort;
                                        // perhaps the sort param is a key, if so, get the id for that key
                                        if (cfg.colSortParams.length > 0) {
                                                for (i=0; i<cfg.colSortParams; i++) {
                                                        if (cfg.colSortParams[i] == params.sort) {
                                                                colid = i;
                                                                break;
                                                        }
                                                }
                                        }
                                        
                                        // set appropriate style on sorted column
                                        // perhaps we should bind an event to the column <th>, like setSort()?
                                        // (re-init sortable cols)
                                        var i2 = 0;
                                        g.getHeaders(function(col){
                                                col.find('div:first').each(function() {
                                                        if(cfg.isSortableCol(i2++))
                                                                g.getHeaders(function(th){
                                                                        jQuery(this).addClass(cfg.sortNoneClass).removeClass(cfg.sortAscClass).removeClass(cfg.sortDescClass);
                                                                })
                                                })
                                        });
                                        // all this prevents the column from being style-less (and blinking)
                                        g.getHeader(parseInt(colid)).find('div:first').addClass(cfg.sortNoneClass).removeClass(cfg.sortAscClass).removeClass(cfg.sortDescClass)
                                                                                                                                                                                          .addClass( params.dir == cfg.sortAscParam ? cfg.sortAscClass : cfg.sortDescClass )
                                                                                                                                                                                          .removeClass(cfg.sortNoneClass);
                                }                                       
                                if ( params.page || params.sort || params.dir ) {
                                        g.load(params);
                                        cfg.initialLoad = false;
                                }
                        }
                }
                
                if (cfg.initialLoad) {
                        g.load();
                }


        }).extend({


                g : g


        });


};
