/**
 * Copyright (c)2005-2007 Matt Kruse (javascripttoolbox.com)
 * 
 * Dual licensed under the MIT and GPL licenses. 
 * This basically means you can use this code however you want for
 * free, but don't claim to have written it yourself!
 * Donations always accepted: http://www.JavascriptToolbox.com/donate/
 * 
 * Please do not link to the .js files on javascripttoolbox.com from
 * your site. Copy the files locally to your server instead.
 * 
 */
/**
 * Table.js
 * Functions for interactive Tables
 *
 * Copyright (c) 2007 Matt Kruse (javascripttoolbox.com)
 * Dual licensed under the MIT and GPL licenses. 
 *
 * @version 0.981
 *
 * @history 0.981 2007-03-19 Added Sort.numeric_comma, additional date parsing formats
 * @history 0.980 2007-03-18 Release new BETA release pending some testing. Todo: Additional docs, examples, plus jQuery plugin.
 * @history 0.959 2007-03-05 Added more "auto" functionality, couple bug fixes
 * @history 0.958 2007-02-28 Added auto functionality based on class names
 * @history 0.957 2007-02-21 Speed increases, more code cleanup, added Auto Sort functionality
 * @history 0.956 2007-02-16 Cleaned up the code and added Auto Filter functionality.
 * @history 0.950 2006-11-15 First BETA release.
 *
 * @todo Add more date format parsers
 * @todo Add style classes to colgroup tags after sorting/filtering in case the user wants to highlight the whole column
 * @todo Correct for colspans in data rows (this may slow it down)
 * @todo Fix for IE losing form control values after sort?
 */

/**
 * Sort Functions
 */
var Sort = (function(){
       var sort = {};
       // Default alpha-numeric sort
       // --------------------------
       sort.alphanumeric = function(a,b) {
              return (a==b)?0:(a<b)?-1:1;
       };
       sort['default'] = sort.alphanumeric; // IE chokes on sort.default

       // This conversion is generalized to work for either a decimal separator of , or .
       sort.numeric_converter = function(separator) {
              return function(val) {
                     if (typeof(val)=="string") {
                       val = parseFloat(val.replace(",","."));
                            //val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g,"$1").replace(new RegExp("[^\\\d"+separator+"]","g"),'').replace(/,/,'.')) || 0;
                     }
                     return val || 0;
              };
       };

       // Numeric Sort       
       // ------------
       sort.numeric = function(a,b) {
              return sort.numeric.convert(a)-sort.numeric.convert(b);
       };
       sort.numeric.convert = sort.numeric_converter(".");

       // Numeric Sort       - comma decimal separator
       // --------------------------------------
       sort.numeric_comma = function(a,b) {
              return sort.numeric_comma.convert(a)-sort.numeric_comma.convert(b);
       };
       sort.numeric_comma.convert = sort.numeric_converter(",");

       // Case-insensitive Sort
       // ---------------------
       sort.ignorecase = function(a,b) {
              return sort.alphanumeric(sort.ignorecase.convert(a),sort.ignorecase.convert(b));
       };
       sort.ignorecase.convert = function(val) {
              if (val==null) { return ""; }
              return (""+val).toLowerCase();
       };

       // Currency Sort
       // -------------
       sort.currency = sort.numeric; // Just treat it as numeric!
       sort.currency_comma = sort.numeric_comma;

       // Date sort
       // ---------
       sort.date = function(a,b) {
              return sort.numeric(sort.date.convert(a),sort.date.convert(b));
       };
       // Convert 2-digit years to 4
       sort.date.fixYear=function(yr) {
              yr = +yr;
              if (yr<50) { yr += 2000; }
              else if (yr<100) { yr += 1900; }
              return yr;
       };
       sort.date.formats = [
              // YY[YY]-MM-DD
              { re:/(\d{2,4})-(\d{1,2})-(\d{1,2})/ , f:function(x){ return (new Date(sort.date.fixYear(x[1]),+x[2],+x[3])).getTime(); } }
              // MM/DD/YY[YY] or MM-DD-YY[YY]
              ,{ re:/(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})/ , f:function(x){ return (new Date(sort.date.fixYear(x[3]),+x[1],+x[2])).getTime(); } }
              // Any catch-all format that new Date() can handle. This is not reliable except for long formats, for example: 31 Jan 2000 01:23:45 GMT
              ,{ re:/(.*\d{4}.*\d+:\d+\d+.*)/, f:function(x){ var d=new Date(x[1]); if(d){return d.getTime();} } }
       ];
       sort.date.convert = function(val) {
              var m,v, f = sort.date.formats;
              for (var i=0,L=f.length; i<L; i++) {
                     if (m=val.match(f[i].re)) {
                            v=f[i].f(m);
                            if (typeof(v)!="undefined") { return v; }
                     }
              }
              return 9999999999999; // So non-parsed dates will be last, not first
       };

       return sort;
})();

/**
 * The main Table namespace
 */
var Table = (function(){

       /**
        * Determine if a reference is defined
        */
       function def(o) {return (typeof o!="undefined");};

       /**
        * Determine if an object or class string contains a given class.
        */
       function hasClass(o,name) {
              return new RegExp("(^|\\s)"+name+"(\\s|$)").test(o.className);
       };

       /**
        * Add a class to an object
        */
       function addClass(o,name) {
              var c = o.className || "";
              if (def(c) && !hasClass(o,name)) {
                     o.className += (c?" ":"") + name;
              }
       };

       /**
        * Remove a class from an object
        */
       function removeClass(o,name) {
              var c = o.className || "";
              o.className = c.replace(new RegExp("(^|\\s)"+name+"(\\s|$)"),"$1");
       };

       /**
        * For classes that match a given substring, return the rest
        */
       function classValue(o,prefix) {
              var c = o.className;
              if (c.match(new RegExp("(^|\\s)"+prefix+"([^ ]+)"))) {
                     return RegExp.$2;
              }
              return null;
       };

       /**
        * Return true if an object is hidden.
        * This uses the "russian doll" technique to unwrap itself to the most efficient
        * function after the first pass. This avoids repeated feature detection that 
        * would always fall into the same block of code.
        */
        function isHidden(o) {
              if (window.getComputedStyle) {
                     var cs = window.getComputedStyle;
                     return (isHidden = function(o) {
                            return 'none'==cs(o,null).getPropertyValue('display');
                     })(o);
              }
              else if (window.currentStyle) {
                     return(isHidden = function(o) {
                            return 'none'==o.currentStyle['display'];
                     })(o);
              }
              return (isHidden = function(o) {
                     return 'none'==o.style['display'];
              })(o);
       };

       /**
        * Get a parent element by tag name, or the original element if it is of the tag type
        */
       function getParent(o,a,b) {
              if (o!=null && o.nodeName) {
                     if (o.nodeName==a || (b && o.nodeName==b)) {
                            return o;
                     }
                     while (o=o.parentNode) {
                            if (o.nodeName && (o.nodeName==a || (b && o.nodeName==b))) {
                                   return o;
                            }
                     }
              }
              return null;
       };

       /**
        * Utility function to copy properties from one object to another
        */
       function copy(o1,o2) {
              for (var i=2;i<arguments.length; i++) {
                     var a = arguments[i];
                     if (def(o1[a])) {
                            o2[a] = o1[a];
                     }
              }
       }

       // The table object itself
       var table = {
              //Class names used in the code
              AutoStripeClassName:"table-autostripe",
              StripeClassNamePrefix:"table-stripeclass:",

              AutoSortClassName:"table-autosort",
              AutoSortColumnPrefix:"table-autosort:",
              AutoSortTitle:"Kliknij by posortować",
              SortedAscendingClassName:"table-sorted-asc",
              SortedDescendingClassName:"table-sorted-desc",
              SortableClassName:"table-sortable",
              SortableColumnPrefix:"table-sortable:",
              NoSortClassName:"table-nosort",

              AutoFilterClassName:"table-autofilter",
              FilteredClassName:"table-filtered",
              FilterableClassName:"table-filterable",
              FilteredRowcountPrefix:"table-filtered-rowcount:",
              RowcountPrefix:"table-rowcount:",
              FilterAllLabel:"Filter: All",

              AutoPageSizePrefix:"table-autopage:",
              AutoPageJumpPrefix:"table-page:",
              PageNumberPrefix:"table-page-number:",
              PageCountPrefix:"table-page-count:"
       };

       /**
        * A place to store misc table information, rather than in the table objects themselves
        */
       table.tabledata = {};

       /**
        * Resolve a table given an element reference, and make sure it has a unique ID
        */
       table.uniqueId=1;
       table.resolve = function(o,args) {
              if (o!=null && o.nodeName && o.nodeName!="TABLE") {
                     o = getParent(o,"TABLE");
              }
              if (o==null) { return null; }
              if (!o.id) {
                     var id = null;
                     do { var id = "TABLE_"+(table.uniqueId++); } 
                            while (document.getElementById(id)!=null);
                     o.id = id;
              }
              this.tabledata[o.id] = this.tabledata[o.id] || {};
              if (args) {
                     copy(args,this.tabledata[o.id],"stripeclass","ignorehiddenrows","useinnertext","sorttype","col","desc","page","pagesize");
              }
              return o;
       };


       /**
        * Run a function against each cell in a table header or footer, usually 
        * to add or remove css classes based on sorting, filtering, etc.
        */
       table.processTableCells = function(t, type, func, arg) {
              t = this.resolve(t);
              if (t==null) { return; }
              if (type!="TFOOT") {
                     this.processCells(t.tHead, func, arg);
              }
              if (type!="THEAD") {
                     this.processCells(t.tFoot, func, arg);
              }
       };

       /**
        * Internal method used to process an arbitrary collection of cells.
        * Referenced by processTableCells.
        * It's done this way to avoid getElementsByTagName() which would also return nested table cells.
        */
       table.processCells = function(section,func,arg) {
              if (section!=null) {
                     if (section.rows && section.rows.length && section.rows.length>0) { 
                            var rows = section.rows;
                            for (var j=0,L2=rows.length; j<L2; j++) { 
                                   var row = rows[j];
                                   if (row.cells && row.cells.length && row.cells.length>0) {
                                          var cells = row.cells;
                                          for (var k=0,L3=cells.length; k<L3; k++) {
                                                 var cellsK = cells[k];
                                                 func.call(this,cellsK,arg);
                                          }
                                   }
                            }
                     }
              }
       };

       /**
        * Get the cellIndex value for a cell. This is only needed because of a Safari
        * bug that causes cellIndex to exist but always be 0.
        * Rather than feature-detecting each time it is called, the function will
        * re-write itself the first time it is called.
        */
       table.getCellIndex = function(td) {
              var tr = td.parentNode;
              var cells = tr.cells;
              if (cells && cells.length) {
                     if (cells.length>1 && cells[cells.length-1].cellIndex>0) {
                            // Define the new function, overwrite the one we're running now, and then run the new one
                            (this.getCellIndex = function(td) {
                                   return td.cellIndex;
                            })(td);
                     }
                     // Safari will always go through this slower block every time. Oh well.
                     for (var i=0,L=cells.length; i<L; i++) {
                            if (tr.cells[i]==td) {
                                   return i;
                            }
                     }
              }
              return 0;
       };

       /**
        * A map of node names and how to convert them into their "value" for sorting, filtering, etc.
        * These are put here so it is extensible.
        */
       table.nodeValue = {
              'INPUT':function(node) { 
                     if (def(node.value) && node.type && ((node.type!="checkbox" && node.type!="radio") || node.checked)) {
                            return node.value;
                     }
                     return "";
              },
              'SELECT':function(node) {
                     if (node.selectedIndex>=0 && node.options) {
                            // Sort select elements by the visible text
                            return node.options[node.selectedIndex].text;
                     }
                     return "";
              },
              'IMG':function(node) {
                     return node.name || "";
              }
       };

       /**
        * Get the text value of a cell. Only use innerText if explicitly told to, because 
        * otherwise we want to be able to handle sorting on inputs and other types
        */
       table.getCellValue = function(td,useInnerText) {
              if (useInnerText && def(td.innerText)) {
                  
					 return td.innerText;
              }
              if (!td.childNodes) { 
                     return ""; 
              }
              var childNodes=td.childNodes;
              var ret = "";
              for (var i=0,L=childNodes.length; i<L; i++) {
                     var node = childNodes[i];
                     var type = node.nodeType;
                     // In order to get realistic sort results, we need to treat some elements in a special way.
                     // These behaviors are defined in the nodeValue() object, keyed by node name
                     if (type==1) {
                            var nname = node.nodeName;
                            if (this.nodeValue[nname]) {
                                   ret += this.nodeValue[nname](node);
                            }
                            else {
                                   ret += this.getCellValue(node);
                            }
                     }
                     else if (type==3) {
                            if (def(node.innerText)) {
                                   ret += node.innerText;
                            }
                            else if (def(node.nodeValue)) {
                                   ret += node.nodeValue;
                            }
                     }
              }			 
              return ret;
       };

       /**
        * Consider colspan and rowspan values in table header cells to calculate the actual cellIndex
        * of a given cell. This is necessary because if the first cell in row 0 has a rowspan of 2, 
        * then the first cell in row 1 will have a cellIndex of 0 rather than 1, even though it really
        * starts in the second column rather than the first.
        * See: http://www.javascripttoolbox.com/temp/table_cellindex.html
        */
       table.tableHeaderIndexes = {};
       table.getActualCellIndex = function(tableCellObj) {
              if (!def(tableCellObj.cellIndex)) { return null; }
              var tableObj = getParent(tableCellObj,"TABLE");
              var cellCoordinates = tableCellObj.parentNode.rowIndex+"-"+this.getCellIndex(tableCellObj);

              // If it has already been computed, return the answer from the lookup table
              if (def(this.tableHeaderIndexes[tableObj.id])) {
                     return this.tableHeaderIndexes[tableObj.id][cellCoordinates];      
              } 

              var matrix = [];
              this.tableHeaderIndexes[tableObj.id] = {};
              var thead = getParent(tableCellObj,"THEAD");
              var trs = thead.getElementsByTagName('TR');

              // Loop thru every tr and every cell in the tr, building up a 2-d array "grid" that gets
              // populated with an "x" for each space that a cell takes up. If the first cell is colspan
              // 2, it will fill in values [0] and [1] in the first array, so that the second cell will
              // find the first empty cell in the first row (which will be [2]) and know that this is
              // where it sits, rather than its internal .cellIndex value of [1].
              for (var i=0; i<trs.length; i++) {
                     var cells = trs[i].cells;
                     for (var j=0; j<cells.length; j++) {
                            var c = cells[j];
                            var rowIndex = c.parentNode.rowIndex;
                            var cellId = rowIndex+"-"+this.getCellIndex(c);
                            var rowSpan = c.rowSpan || 1;
                            var colSpan = c.colSpan || 1;
                            var firstAvailCol;
                            if(!def(matrix[rowIndex])) { 
                                   matrix[rowIndex] = []; 
                            }
                            var m = matrix[rowIndex];
                            // Find first available column in the first row
                            for (var k=0; k<m.length+1; k++) {
                                   if (!def(m[k])) {
                                          firstAvailCol = k;
                                          break;
                                   }
                            }
                            this.tableHeaderIndexes[tableObj.id][cellId] = firstAvailCol;
                            for (var k=rowIndex; k<rowIndex+rowSpan; k++) {
                                   if(!def(matrix[k])) { 
                                          matrix[k] = []; 
                                   }
                                   var matrixrow = matrix[k];
                                   for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) {
                                          matrixrow[l] = "x";
                                   }
                            }
                     }
              }
              // Store the map so future lookups are fast.
              return this.tableHeaderIndexes[tableObj.id][cellCoordinates];
       };

       /**
        * Sort all rows in each TBODY (tbodies are sorted independent of each other)
        */
       table.sort = function(o,args) {
              var t, tdata, sortconvert=null;
              // Allow for a simple passing of sort type as second parameter
              if (typeof(args)=="function") {
                     args={sorttype:args};
              }
              args = args || {};

              // If no col is specified, deduce it from the object sent in
              if (!def(args.col)) { 
                     args.col = this.getActualCellIndex(o) || 0; 
              }
              // If no sort type is specified, default to the default sort
              args.sorttype = args.sorttype || Sort['default'];

              // Resolve the table
              t = this.resolve(o,args);
              tdata = this.tabledata[t.id];

              // If we are sorting on the same column as last time, flip the sort direction
              if (def(tdata.lastcol) && tdata.lastcol==tdata.col && def(tdata.lastdesc)) {
                     tdata.desc = !tdata.lastdesc;
              }
              else {
                     tdata.desc = !!args.desc;
              }

              // Store the last sorted column so clicking again will reverse the sort order
              tdata.lastcol=tdata.col;
              tdata.lastdesc=!!tdata.desc;

              // If a sort conversion function exists, pre-convert cell values and then use a plain alphanumeric sort
              var sorttype = tdata.sorttype;
              if (typeof(sorttype.convert)=="function") {
                     sortconvert=tdata.sorttype.convert;
                     sorttype=Sort.alphanumeric;
              }

              // Loop through all THEADs and remove sorted class names, then re-add them for the col
              // that is being sorted
              this.processTableCells(t,"THEAD",
                     function(cell) {
                            if (hasClass(cell,this.SortableClassName)) {
                                   removeClass(cell,this.SortedAscendingClassName);
                                   removeClass(cell,this.SortedDescendingClassName);
                                   // If the computed colIndex of the cell equals the sorted colIndex, flag it as sorted
                                   if (tdata.col==table.getActualCellIndex(cell) && (classValue(cell,table.SortableClassName))) {
                                          addClass(cell,tdata.desc?this.SortedAscendingClassName:this.SortedDescendingClassName);
                                   }
                            }
                     }
              );

              // Sort each tbody independently
              var bodies = t.tBodies;
              if (bodies==null || bodies.length==0) { return; }

              // Define a new sort function to be called to consider descending or not
              var newSortFunc = (tdata.desc)?
                     function(a,b){return sorttype(b[0],a[0]);}
                     :function(a,b){return sorttype(a[0],b[0]);};

              var useinnertext=!!tdata.useinnertext;
              var col = tdata.col;

              for (var i=0,L=bodies.length; i<L; i++) {
                     var tb = bodies[i], tbrows = tb.rows, rows = [];

                     // Allow tbodies to request that they not be sorted
                     if(!hasClass(tb,table.NoSortClassName)) {
                            // Create a separate array which will store the converted values and refs to the
                            // actual rows. This is the array that will be sorted.
                            var cRow, cRowIndex=0;
                            if (cRow=tbrows[cRowIndex]){
                                   // Funky loop style because it's considerably faster in IE
                                   do {
                                          if (rowCells = cRow.cells) {
                                                 var cellValue = (col<rowCells.length)?this.getCellValue(rowCells[col],useinnertext):null;
                                                 if (sortconvert) cellValue = sortconvert(cellValue);
                                                 rows[cRowIndex] = [cellValue,tbrows[cRowIndex]];
                                          }
                                   } while (cRow=tbrows[++cRowIndex])
                            }

                            // Do the actual sorting
                            rows.sort(newSortFunc);

                            // Move the rows to the correctly sorted order. Appending an existing DOM object just moves it!
                            cRowIndex=0;
                            var displayedCount=0;
                            var f=[removeClass,addClass];
                            if (cRow=rows[cRowIndex]){
                                   do { 
                                          tb.appendChild(cRow[1]); 
                                   } while (cRow=rows[++cRowIndex])
                            }
                     }
              }

              // If paging is enabled on the table, then we need to re-page because the order of rows has changed!
              if (tdata.pagesize) {
                     this.page(t); // This will internally do the striping
              }
              else {
                     // Re-stripe if a class name was supplied
                     if (tdata.stripeclass) {
                            this.stripe(t,tdata.stripeclass,!!tdata.ignorehiddenrows);
                     }
              }
       };

       /**
       * Apply a filter to rows in a table and hide those that do not match.
       */
       table.filter = function(o,filters,args) {
              var cell;
              args = args || {};

              var t = this.resolve(o,args);
              var tdata = this.tabledata[t.id];

              // If new filters were passed in, apply them to the table's list of filters
              if (!filters) {
                     // If a null or blank value was sent in for 'filters' then that means reset the table to no filters
                     tdata.filters = null;
              }
              else {
                     // Allow for passing a select list in as the filter, since this is common design
                     if (filters.nodeName=="SELECT" && filters.type=="select-one" && filters.selectedIndex>-1) {
                            
                            filters={ 'filter':filters.options[filters.selectedIndex].value };
                            
                     }
                     // Also allow for a regular input
                     if (filters.nodeName=="INPUT" && filters.type=="text") {
                            filters={ 'filter':"/^"+filters.value.toUpperCase()+"/" };
                            
                     }
                     if (filters.nodeName=="INPUT" && filters.type=="hidden") {
                            filters={ 'filter': filters.value.replace(',','.') };
                            
                     }
                     // Force filters to be an array
                     if (typeof(filters)=="object" && !filters.length) {
                            filters = [filters];
                            
                     }

                     // Convert regular expression strings to RegExp objects and function strings to function objects
                     for (var i=0,L=filters.length; i<L; i++) {
                            var filter = filters[i];
                            if (typeof(filter.filter)=="string") {
                                   // If a filter string is like "/expr/" then turn it into a Regex
                                   if (filter.filter.match(/^\/(.*)\/$/)) {
                                          filter.filter = new RegExp(RegExp.$1);
                                          filter.filter.regex=true;
                                          
                                   }
                                   // If filter string is like "function (x) { ... }" then turn it into a function
                                   else if (filter.filter.match(/^function\s*\(([^\)]*)\)\s*\{(.*)}\s*$/)) {
                                          filter.filter = Function(RegExp.$1,RegExp.$2);
                                          
                                   }
                            }
                            // If some non-table object was passed in rather than a 'col' value, resolve it 
                            // and assign it's column index to the filter if it doesn't have one. This way, 
                            // passing in a cell reference or a select object etc instead of a table object 
                            // will automatically set the correct column to filter.
                            if (filter && !def(filter.col) && (cell=getParent(o,"TD","TH"))) {
                                   filter.col = this.getCellIndex(cell);
                            }

                            // Apply the passed-in filters to the existing list of filters for the table, removing those that have a filter of null or ""
                            if ((!filter || !filter.filter) && tdata.filters) {
                                   delete tdata.filters[filter.col];
                            }
                            else {
                                   tdata.filters = tdata.filters || {};
                                   tdata.filters[filter.col] = filter.filter;
                            }
                     }
                     // If no more filters are left, then make sure to empty out the filters object
                     for (var j in tdata.filters) { var keep = true; }
                     if (!keep) {
                            tdata.filters = null;
                     }
              }              
              // Everything's been setup, so now scrape the table rows
              return table.scrape(o);
       };

       /**
        * "Page" a table by showing only a subset of the rows
        */
       table.page = function(t,page,args) {
              args = args || {};
              if (def(page)) { args.page = page; }
              return table.scrape(t,args);
       };

       /**
        * Jump forward or back any number of pages
        */
       table.pageJump = function(t,count,args) {
              t = this.resolve(t,args);
              return this.page(t,(table.tabledata[t.id].page||0)+count,args);
       };

       /**
        * Go to the next page of a paged table
        */       
       table.pageNext = function(t,args) {
              return this.pageJump(t,1,args);
       };

       /**
        * Go to the previous page of a paged table
        */       
       table.pagePrevious = function(t,args) {
              return this.pageJump(t,-1,args);
       };

       /**
       * Scrape a table to either hide or show each row based on filters and paging
       */
       table.scrape = function(o,args) {
              var col,cell,filterList,filterReset=false,filter;
              var page,pagesize,pagestart,pageend;
              var unfilteredrows=[],unfilteredrowcount=0,totalrows=0;
              var t,tdata,row,hideRow;
              args = args || {};

              // Resolve the table object
              t = this.resolve(o,args);
              tdata = this.tabledata[t.id];

              // Setup for Paging
              var page = tdata.page;
              if (def(page)) {
                     // Don't let the page go before the beginning
                     if (page<0) { tdata.page=page=0; }
                     pagesize = tdata.pagesize || 25; // 25=arbitrary default
                     pagestart = page*pagesize+1;
                     pageend = pagestart + pagesize - 1;
              }

              // Scrape each row of each tbody
              var bodies = t.tBodies;
              if (bodies==null || bodies.length==0) { return; }
              for (var i=0,L=bodies.length; i<L; i++) {
                     var tb = bodies[i];
                     for (var j=0,L2=tb.rows.length; j<L2; j++) {
                            row = tb.rows[j];
                            hideRow = false;

                            // Test if filters will hide the row
                            if (tdata.filters && row.cells) {
                                   var cells = row.cells;
                                   var cellsLength = cells.length;
                                   // Test each filter
                                   for (col in tdata.filters) {
                                          if (!hideRow) {
                                                 filter = tdata.filters[col];
                                                 if (filter && col<cellsLength) {
                                                        var val = this.getCellValue(cells[col]).replace(',','.');
                                                        
                                                        if (filter.regex && val.search) {
                                                               hideRow=(val.search(filter)<0);
                                                        }
                                                        else if (typeof(filter)=="function") {
                                                               hideRow=!filter(val,cells[col]);
                                                               
                                                        }
                                                        else {
                                                               hideRow = (val!=filter);
                                                        }
                                                 }
                                          }
                                   }
                            }

                            // Keep track of the total rows scanned and the total runs _not_ filtered out
                            totalrows++;
                            
                            if (!hideRow) {
                                   unfilteredrowcount++;
                                   if (def(page)) {
                                          // Temporarily keep an array of unfiltered rows in case the page we're on goes past
                                          // the last page and we need to back up. Don't want to filter again!
                                          unfilteredrows.push(row);
                                          if (unfilteredrowcount<pagestart || unfilteredrowcount>pageend) {
                                                 hideRow = true;
                                          }
                                   }
                            }

                            row.style.display = hideRow?"none":"";
                     }
              }
              //! MM dodanie komunikatu na dole
              if (unfilteredrowcount == 0) {
                document.getElementById("filter_message").innerHTML = 'Nie odnaleziono żadnej spółki spełniającej wybrane kryteria!';
                document.getElementById("filter_total").innerHTML = '';
              }
              else {
                document.getElementById("filter_message").innerHTML = '';
                document.getElementById("filter_total").innerHTML = 'Liczba znalezionych spółek: ' + unfilteredrowcount;
              }
    //! MM koniec
    
              if (def(page)) {
                     // Check to see if filtering has put us past the requested page index. If it has, 
                     // then go back to the last page and show it.
                     if (pagestart>=unfilteredrowcount) {
                            pagestart = unfilteredrowcount-(unfilteredrowcount%pagesize);
                            tdata.page = page = pagestart/pagesize;
                            for (var i=pagestart,L=unfilteredrows.length; i<L; i++) {
                                   unfilteredrows[i].style.display="";
                            }
                     }
              }

              // Loop through all THEADs and add/remove filtered class names
              this.processTableCells(t,"THEAD",
                     function(c) {
                            ((tdata.filters && def(tdata.filters[table.getCellIndex(c)]) && hasClass(c,table.FilterableClassName))?addClass:removeClass)(c,table.FilteredClassName);
                     }
              );

              // Stripe the table if necessary
              if (tdata.stripeclass) {
                     this.stripe(t);
              }

              // Calculate some values to be returned for info and updating purposes
              var pagecount = Math.floor(unfilteredrowcount/pagesize)+1;
              if (def(page)) {
                     // Update the page number/total containers if they exist
                     if (tdata.container_number) {
                            tdata.container_number.innerHTML = page+1;
                     }
                     if (tdata.container_count) {
                            tdata.container_count.innerHTML = pagecount;
                     }
              }

              // Update the row count containers if they exist
              if (tdata.container_filtered_count) {
                     tdata.container_filtered_count.innerHTML = unfilteredrowcount;
              }
              if (tdata.container_all_count) {
                     tdata.container_all_count.innerHTML = totalrows;
              }
              return { 'data':tdata, 'unfilteredcount':unfilteredrowcount, 'total':totalrows, 'pagecount':pagecount, 'page':page, 'pagesize':pagesize };
       };

       /**
        * Shade alternate rows, aka Stripe the table.
        */
       table.stripe = function(t,className,args) { 
              args = args || {};
              args.stripeclass = className;

              t = this.resolve(t,args);
              var tdata = this.tabledata[t.id];

              var bodies = t.tBodies;
              if (bodies==null || bodies.length==0) { 
                     return; 
              }

              className = tdata.stripeclass;
              // Cache a shorter, quicker reference to either the remove or add class methods
              var f=[removeClass,addClass];
              for (var i=0,L=bodies.length; i<L; i++) {
                     var tb = bodies[i], tbrows = tb.rows, cRowIndex=0, cRow, displayedCount=0;
                     if (cRow=tbrows[cRowIndex]){
                            // The ignorehiddenrows test is pulled out of the loop for a slight speed increase.
                            // Makes a bigger difference in FF than in IE.
                            // In this case, speed always wins over brevity!
                            if (tdata.ignoreHiddenRows) {
                                   do {
                                          f[displayedCount++%2](cRow,className);
                                   } while (cRow=tbrows[++cRowIndex])
                            }
                            else {
                                   do {
                                          if (!isHidden(cRow)) {
                                                 f[displayedCount++%2](cRow,className);
                                          }
                                   } while (cRow=tbrows[++cRowIndex])
                            }
                     }
              }
       };

       /**
        * Build up a list of unique values in a table column
        */
       table.getUniqueColValues = function(t,col) {
              var values={}, bodies = this.resolve(t).tBodies;
              for (var i=0,L=bodies.length; i<L; i++) {
                     var tbody = bodies[i];
                     for (var r=0,L2=tbody.rows.length; r<L2; r++) {
                            values[this.getCellValue(tbody.rows[r].cells[col])] = true;
                     }
              }
              var valArray = [];
              for (var val in values) {
                     valArray.push(val);
              }
              return valArray.sort();
       };

       /**
        * Scan the document on load and add sorting, filtering, paging etc ability automatically
        * based on existence of class names on the table and cells.
        */
       table.auto = function(args) {
              var cells = [], tables = document.getElementsByTagName("TABLE");
              var val,tdata;
              if (tables!=null) {
                     for (var i=0,L=tables.length; i<L; i++) {
                            var t = table.resolve(tables[i]);
                            tdata = table.tabledata[t.id];
                            if (val=classValue(t,table.StripeClassNamePrefix)) {
                                   tdata.stripeclass=val;
                            }
                            // Do auto-filter if necessary
                            if (hasClass(t,table.AutoFilterClassName)) {
                                   table.autofilter(t);
                            }
                            // Do auto-page if necessary
                            if (val = classValue(t,table.AutoPageSizePrefix)) {
                                   table.autopage(t,{'pagesize':+val});
                            }
                            // Do auto-sort if necessary
                            if ((val = classValue(t,table.AutoSortColumnPrefix)) || (hasClass(t,table.AutoSortClassName))) {
                                   table.autosort(t,{'col':(val==null)?null:+val});
                            }
                            // Do auto-stripe if necessary
                            if (tdata.stripeclass && hasClass(t,table.AutoStripeClassName)) {
                                   table.stripe(t);
                            }
                     }
              }
       };

       /**
        * Add sorting functionality to a table header cell
        */
       table.autosort = function(t,args) {
              t = this.resolve(t,args);
              var tdata = this.tabledata[t.id];
              this.processTableCells(t, "THEAD", function(c) {
                     var type = classValue(c,table.SortableColumnPrefix);
                     if (type!=null) {
                            type = type || "default";
                            c.title =c.title || table.AutoSortTitle;
                            addClass(c,table.SortableClassName);
                            c.onclick = Function("","Table.sort(this,{'sorttype':Sort['"+type+"']})");
                            // If we are going to auto sort on a column, we need to keep track of what kind of sort it will be
                            if (args.col!=null) {
                                   if (args.col==table.getActualCellIndex(c)) {
                                          tdata.sorttype=Sort['"+type+"'];
                                   }
                            }
                     }
              } );
              if (args.col!=null) {
                     table.sort(t,args);
              }
       };

       /**
        * Add paging functionality to a table 
        */
       table.autopage = function(t,args) {
              t = this.resolve(t,args);
              var tdata = this.tabledata[t.id];
              if (tdata.pagesize) {
                     this.processTableCells(t, "THEAD,TFOOT", function(c) {
                            var type = classValue(c,table.AutoPageJumpPrefix);
                            if (type=="next") { type = 1; }
                            else if (type=="previous") { type = -1; }
                            if (type!=null) {
                                   c.onclick = Function("","Table.pageJump(this,"+type+")");
                            }
                     } );
                     if (val = classValue(t,table.PageNumberPrefix)) {
                            tdata.container_number = document.getElementById(val);
                     }
                     if (val = classValue(t,table.PageCountPrefix)) {
                            tdata.container_count = document.getElementById(val);
                     }
                     return table.page(t,0,args);
              }
       };

       /**
        * A util function to cancel bubbling of clicks on filter dropdowns
        */
       table.cancelBubble = function(e) {
              e = e || window.event;
              if (typeof(e.stopPropagation)=="function") { e.stopPropagation(); } 
              if (def(e.cancelBubble)) { e.cancelBubble = true; }
       };

       /**
        * Auto-filter a table
        */
       table.autofilter = function(t,args) {
              args = args || {};
              t = this.resolve(t,args);
              var tdata = this.tabledata[t.id],val;
              table.processTableCells(t, "THEAD", function(cell) {
                     if (hasClass(cell,table.FilterableClassName)) {
                            var cellIndex = table.getCellIndex(cell);
                            var colValues = table.getUniqueColValues(t,cellIndex);
                            if (colValues.length>0) {
                                   if (typeof(args.insert)=="function") {
                                          func.insert(cell,colValues);
                                   }
                                   else {
                                          var sel = '<select onchange="Table.filter(this,this)" onclick="Table.cancelBubble(event)" class="'+table.AutoFilterClassName+'"><option value="">'+table.FilterAllLabel+'</option>';
                                          for (var i=0; i<colValues.length; i++) {
                                                 sel += '<option value="'+colValues[i]+'">'+colValues[i]+'</option>';
                                          }
                                          sel += '</select>';
                                          cell.innerHTML += "<br>"+sel;
                                   }
                            }
                     }
              });
              if (val = classValue(t,table.FilteredRowcountPrefix)) {
                     tdata.container_filtered_count = document.getElementById(val);
              }
              if (val = classValue(t,table.RowcountPrefix)) {
                     tdata.container_all_count = document.getElementById(val);
              }
       };

       /**
        * Attach the auto event so it happens on load.
        * use jQuery's ready() function if available
        */
       if (typeof(jQuery)!="undefined") {
              jQuery(table.auto);
       }
       else if (window.addEventListener) {
              window.addEventListener( "load", table.auto, false );
       }
       else if (window.attachEvent) {
              window.attachEvent( "onload", table.auto );
       }

       return table;
})();
