/**
* Ingrid : JQuery Datagrid Control
*
* Copyright (c) 2007-2009 Matthew Knight (http://www.reconstrukt.com http://slu.sh)
* 
* 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
*
*/

$.fn.ingrid = function (o) {
    var $ingrid = $(this);
    var _ingrid = this;
    this.generateSearchTable = $ingrid.ingrid.generateSearchTable;
    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: [150, 120, 100, 100, 100, 100, 80, 70, 150, 100, 80, 100, 100], 		// width of each column
        minColWidth: 60,                        // minimum column width
        headerHeight: 30,                       // height of our header
        headerClass: 'grid-header-bg',          // header bg
        resizableCols: false,                    // make columns resizable via drag + drop

        gridClass: 'datagrid',                  // class of head & body
        rowClasses: ['common'],                 // 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: false,                     // 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: ['HotelSort', 'RoomNameSort', 'RoomCategoryNameSort', 'PlacesCountSort', 'PansionNameSort',
        'NightsSort', 'PriceSort', '', 'TurDateSort'],
        // value to pass as sort param when header clicked (i.e. '&sort=param') ex: ['col1','col2','col3','col4']
        sortAscParam: 'Desc',                    // param passed on ascending sort (i.e. '&dir=asc)
        sortDescParam: 'Asc',                  // param passed on ascending sort (i.e. '&dir=desc)
        sortedCol: 6,                      // current data's sorted column (can be a key from 'colSortParams', or an int 0-n (for n columns)
        sortedColDir: '',                   // current data's sorted directions
        sortDefaultDir: 'Asc',                 // on 1st click, sort this 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: [7],                     // do not make theses columns sortable

		scrolling: true,
		
        /* 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>&nbsp;</div>',

        /* should seldom change */
        resizeHandleHtml: '',                   // resize handle html + css
        resizeHandleClass: 'grid-col-resize',
        scrollbarW: 16,                         // 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;
        },

		settings: {}
		
    };
    jQuery.extend(cfg, o);

    // break into 2 tables: header, body.
    // create header table
    var cols = new Array();
    var headerWidth = 0;
    jQuery.each(cfg.colWidths, function (i, v) {
        headerWidth += v;
    });
    var h = jQuery('<table cellpadding="0" cellspacing="0"></table>')
                    .html(this.find('thead'))
                    .addClass(cfg.gridClass)
                    .addClass(cfg.headerClass)
                    .height(cfg.headerHeight)
                    .width(headerWidth)
                    .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);

                cfg.searchFilter.sort = key;
                cfg.searchFilter.sortDir = dir;

                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);

                });
            });
        }
        else {
            var cls = cfg.sortNoneClass;
            col_label.addClass(cls);
        }

        // 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-x', 'hidden').css('overflow-y', cfg.scrolling ? 'scroll': 'none');
                    //.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 () {
                                            if (arguments[0] === true) {
                                                input.val(p);
                                            }
                                            if (cfg.totalRecords > 0) {
                                                var totr = b.find('tr').length;
                                                //pv.updateViewInfo(totr, p);
                                                pv.updateViewInfo(cfg.recordsPerPage, 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 });
    }

	if (cfg.scrolling) {
		// 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();

            var serviceUrl = cfg.url;
            serviceUrl += "?token=1";
            serviceUrl += "&priceKeys=";
            serviceUrl += "&firstItemNumber=" + (data.page - 1) * cfg.searchFilter.psize;
            serviceUrl += "&cbf=IngridResponseHandler";
            serviceUrl += "&jsSearchFilter={'searchFilter':{";
            var searchFilter = cfg.searchFilter;
            for (var key in searchFilter) {
                if (searchFilter.hasOwnProperty(key)) {
                    serviceUrl += "'" + key + "': '" + searchFilter[key] + "', ";
                }
            }
            serviceUrl = serviceUrl.substr(0, serviceUrl.length - 2) + "}}";

            jQuery.ajax({
                type: cfg.type.toUpperCase(),
                url: serviceUrl,
                data: data,
                dataType: cfg.dataType,
                success: function (response) {
                    //                    if (result == "") {
                    //                        alert('Error: Empty result.');
                    //                        return;
                    //                    }
                    // for JSON return type
                    var pageChangeSuccess = false;
                    if (cfg.dataType == 'jsonp') {
                        var searchItems = eval(response.SearchItems);
                        if (searchItems.length > 0) {
                            var $tbl = $(_ingrid.generateSearchTable(searchItems, cfg.settings/*cfg.hash*/));
                            $tbl.find("tr:first").find('td').each(function (i) {
                                $(this).width(g.getHeader(i).css('width'))
                            });

                            g.b.find('tbody').html($tbl.find('tbody').html());
                            g.initStylesAndWidths();
                            pageChangeSuccess = true;
                        }
                        else {
                            pageChangeSuccess = false;
                        }                        
                    }
                    // for HTML (Table) return type
                    if (cfg.dataType == 'html') {
                        var $tbl = jQuery($("string", result).text());
                        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);
                            pageChangeSuccess = true;

                        } else if (row.length < 1) {
                            // no rows returned
                            alert('No more records');
                            pageChangeSuccess = false;
                        } 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 + '].');
                            pageChangeSuccess = false;
                        }
                    }
                    if (cb) cb(pageChangeSuccess);
                },
                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.scrolling ? cfg.scrollbarW : 0);
            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).outerWidth();
            });

            // 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])

		cfg.height = b.height() < cfg.height ? b.height() : cfg.height;
		g.find("table." + cfg.gridClass + ":not(." + cfg.headerClass + ")").closest("div").height(cfg.height);
		
        // 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

    });

};

