Mottie / tablesorter

Github fork of Christian Bach's tablesorter plugin + awesomeness ~
https://mottie.github.io/tablesorter/docs/
2.61k stars 754 forks source link

ajax pager and filters issue #934

Closed DaveGahan closed 9 years ago

DaveGahan commented 9 years ago

Hi, firstable thank you very much for this great job.

I use tablesorter together with pager and filters and it works great except rendering time becoming too slow due to growing amount of datas.

So I'm modifying it in order to make the job done by the server using ajaxurl.

It seems that filter_functions does not work together with ajax.

I took the ajax demo and I try to add a filter_functions clause but it seems to be ignored.

Do you have any idea ?

modified example-pager-ajax.html :

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
    <title>jQuery plugin: Tablesorter 2.0 - Pager plugin - Ajax</title>

    <!-- jQuery -->
    <script src="js/jquery-latest.min.js"></script>

    <!-- Demo stuff -->
    <link class="ui-theme" rel="stylesheet" href="css/jquery-ui.min.css">
    <link href="css/jq.css" rel="stylesheet">
    <link href="css/prettify.css" rel="stylesheet">
    <script src="js/jquery-ui.min.js"></script>
    <script src="js/prettify.js"></script>
    <script src="js/docs.js"></script>

    <!-- Tablesorter: required -->
    <link rel="stylesheet" href="../css/theme.blue.css">
    <script src="../js/jquery.tablesorter.js"></script>

    <!-- Tablesorter pager: required -->
    <link rel="stylesheet" href="../addons/pager/jquery.tablesorter.pager.css">
    <script src="../addons/pager/jquery.tablesorter.pager.js"></script>

    <!-- Tablesorter: optional -->
    <script src="../js/jquery.tablesorter.widgets.js"></script>

    <script id="js">$(function(){

    // Initialize tablesorter
    // ***********************
    $("table")
        .tablesorter({
            theme: 'blue',
            widthFixed: true,
            sortLocaleCompare: true, // needed for accented characters in the data
            sortList: [ [0,1] ],
            widgets: ['zebra', 'filter'],
                        widgetOptions : {
                        filter_cssFilter   : '',
                        // If there are child rows in the table (rows with class name from "cssChildRow" option)
                        // and this option is true and a match is found anywhere in the child row, then it will make that row
                        // visible; default is false
                        filter_childRows   : false,

                        // if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
                        // below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
                        filter_hideFilters : false,

                        // Set this option to false to make the searches case sensitive
                        filter_ignoreCase  : true,

                        // jQuery selector string of an element used to reset the filters
                        filter_reset : '.reset',

                        // Use the $.tablesorter.storage utility to save the most recent filters
                        filter_saveFilters : true,

                        // Delay in milliseconds before the filter widget starts searching; This option prevents searching for
                        // every character while typing and should make searching large tables faster.
                        filter_searchDelay : 300,

                        // Set this option to true to use the filter to find text from the start of the column
                        // So typing in "a" will find "albert" but not "frank", both have a's; default is false
                        filter_startsWith  : false,
                        filter_functions: {
                              2 : {
                                "A - D" : function(e, n, f, i, $r, c, data) { return /^[A-D]/.test(e); },
                                "E - H" : function(e, n, f, i, $r, c, data) { return /^[E-H]/.test(e); },
                                "I - L" : function(e, n, f, i, $r, c, data) { return /^[I-L]/.test(e); },
                                "M - P" : function(e, n, f, i, $r, c, data) { return /^[M-P]/.test(e); },
                                "Q - T" : function(e, n, f, i, $r, c, data) { return /^[Q-T]/.test(e); },
                                "U - X" : function(e, n, f, i, $r, c, data) { return /^[U-X]/.test(e); },
                                "Y - Z" : function(e, n, f, i, $r, c, data) { return /^[Y-Z]/.test(e); }
                              }
                            }
                        }
        })

        // initialize the pager plugin
        // ****************************
        .tablesorterPager({

            // **********************************
            //  Description of ALL pager options
            // **********************************

            // target the pager markup - see the HTML block below
            container: $(".pager"),

            // use this format: "http:/mydatabase.com?page={page}&size={size}&{sortList:col}"
            // where {page} is replaced by the page number (or use {page+1} to get a one-based index),
            // {size} is replaced by the number of records to show,
            // {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds
            // the filterList to the url into an "fcol" array.
            // So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url
            // and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url
            ajaxUrl : 'assets/City{page}.json?{filterList:filter}&{sortList:column}',

            // modify the url after all processing has been applied
            customAjaxUrl: function(table, url) {
                    // manipulate the url string as you desire
                    // url += '&cPage=' + window.location.pathname;
                    // trigger my custom event
                    $(table).trigger('changingUrl', url);
                    // send the server the current page
                    return url;
            },

            // add more ajax settings here
            // see http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
            ajaxObject: {
                dataType: 'json'
            },

            // process ajax so that the following information is returned:
            // [ total_rows (number), rows (array of arrays), headers (array; optional) ]
            // example:
            // [
            //   100,  // total rows
            //   [
            //     [ "row1cell1", "row1cell2", ... "row1cellN" ],
            //     [ "row2cell1", "row2cell2", ... "row2cellN" ],
            //     ...
            //     [ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
            //   ],
            //   [ "header1", "header2", ... "headerN" ] // optional
            // ]
            // OR
            // return [ total_rows, $rows (jQuery object; optional), headers (array; optional) ]
            ajaxProcessing: function(data){
                if (data && data.hasOwnProperty('rows')) {
                    var indx, r, row, c, d = data.rows,
                    // total number of rows (required)
                    total = data.total_rows,
                    // array of header names (optional)
                    headers = data.headers,
                    // cross-reference to match JSON key within data (no spaces)
                    headerXref = headers.join(',').replace(/\s+/g,'').split(','),
                    // all rows: array of arrays; each internal array has the table cell data for that row
                    rows = [],
                    // len should match pager set size (c.size)
                    len = d.length;
                    // this will depend on how the json is set up - see City0.json
                    // rows
                    for ( r=0; r < len; r++ ) {
                        row = []; // new row array
                        // cells
                        for ( c in d[r] ) {
                            if (typeof(c) === "string") {
                                // match the key with the header to get the proper column index
                                indx = $.inArray( c, headerXref );
                                // add each table cell data to row array
                                if (indx >= 0) {
                                    row[indx] = d[r][c];
                                }
                            }
                        }
                        rows.push(row); // add new row array to rows array
                    }
                    // in version 2.10, you can optionally return $(rows) a set of table rows within a jQuery object
                    return [ total, rows, headers ];
                }
            },

            // Set this option to false if your table data is preloaded into the table, but you are still using ajax
            processAjaxOnInit: true,

            // output string - default is '{page}/{totalPages}';
            // possible variables: {page}, {totalPages}, {startRow}, {endRow} and {totalRows}
            output: '{startRow} to {endRow} ({totalRows})',

            // apply disabled classname (cssDisabled option) to the pager arrows when the rows
            // are at either extreme is visible; default is true
            updateArrows: true,

            // starting page of the pager (zero based index)
            page: 0,

            // Number of visible rows - default is 10
            size: 25,

            // Saves the current pager page size and number (requires storage widget)
            savePages: true,
            // Saves tablesorter paging to custom key if defined.
            // Key parameter name used by the $.tablesorter.storage function.
            // Useful if you have multiple tables defined
            storageKey: 'tablesorter-pager',

            // Reset pager to this page after filtering; set to desired page number (zero-based index),
            // or false to not change page at filter start
            pageReset: 0,

            // if true, the table will remain the same height no matter how many records are displayed.
            // The space is made up by an empty table row set to a height to compensate; default is false
            fixedHeight: false,

            // remove rows from the table to speed up the sort of large tables.
            // setting this to false, only hides the non-visible rows; needed if you plan to
            // add/remove rows with the pager enabled.
            removeRows: false,

            // If true, child rows will be counted towards the pager set size
            countChildRows: false,

            // css class names of pager arrows
            cssNext        : '.next',  // next page arrow
            cssPrev        : '.prev',  // previous page arrow
            cssFirst       : '.first', // go to first page arrow
            cssLast        : '.last',  // go to last page arrow
            cssGoto        : '.gotoPage', // page select dropdown - select dropdown that set the "page" option

            cssPageDisplay : '.pagedisplay', // location of where the "output" is displayed
            cssPageSize    : '.pagesize', // page size selector - select dropdown that sets the "size" option

            // class added to arrows when at the extremes; see the "updateArrows" option
            // (i.e. prev/first arrows are "disabled" when on the first page)
            cssDisabled    : 'disabled', // Note there is no period "." in front of this class name
            cssErrorRow    : 'tablesorter-errorRow' // error information row

        });

});</script>

</head>
<body id="pager-demo">
    <div id="banner">
        <h1>table<em>sorter</em></h1>
        <h2>Pager plugin - Ajax</h2>
        <h3>Flexible client-side table sorting</h3>
        <a href="index.html">Back to documentation</a>
    </div>

    <div id="main">

    <p></p>
    <br>
    <div class="accordion">

        <h3><a href="#">Notes</a></h3>
        <div>
            <ul>
                <li>In <span class="version">v2.13.3</span>, the "ID" column has a default filter setting of "&gt;30" and a descending sort, but neither is applied as this demo is not connected a server (just a basic JSON file).</li>
                <li>In <span class="version">v2.11</span>, the pager now stores any object returned by the `ajaxProcessing` function in `table.config.pager.ajaxData` (see the ajaxProcessing section below for more details).</li>
                <li>In <span class="version updated">v2.10</span>, the <code>ajaxProcessing</code> function was updated to only require a total number of rows to be returned, also instead of returning an array of table rows, you can build the table yourself and just return the jQuery object containing those rows. The addon triggers an update.</li>
                <li><code>{filterList:fcol}</code> was added to the <code>ajaxUrl</code> in version 2.6.</li>
                <li><code>{sortList:col}</code> was added to the <code>ajaxUrl</code> in version 2.4.5.</li>
                <li>This update to the pager plugin that interacts with a database via ajax was added in version 2.0.32 and can be applied to the original tablesorter.</li>
                <li>The <code>ajaxUrl</code> and <code>ajaxProcessing</code> function are both required options for this interaction to work properly.</li>
                <li>The <code>ajaxUrl</code> contains a replaceable string to send the requested page (<code>{page}</code>), block size (<code>{size}</code>) or sort order (<code>{sortList:name}</code>).</li>
                <li>The table header and footer text will be updated to match the JSON "header column #" text; but there is an issue with the table rendering improperly if the number of columns in the HTML and the number of columns in the JSON don't match.</li>
                <li><strong>Limitations of this demo</strong>:
                    <ul>
                        <li>This demo will not work when viewing it locally (except in Firefox) due to communication restrictions between the browser and your desktop.</li>
                        <li>The record size is limited to 25 records because this demo is not interacting with an actual database, but with four JSON files containing 25 records each.</li>
                        <li>Sorting of columns is disabled in this demo because no table data is cached when <code>serverSideSorting</code> is enabled. <del>Sorting of columns is enabled in this demo by setting the <code>serverSideSorting</code> option to <code>false</code> after initialization. It only sorts the current table contents because there is no server to return sorted data</del>.</li>
                        <li>The filters will only update the "Current Ajax url" because again, we're just working with JSON files here.</li>
                    </ul>
                </li>
            </ul>
            <p class="small">* If you have a different JSON format and need help with the processing code, please ask the question on <a href="http://stackoverflow.com/questions/tagged/tablesorter">StackOverflow</a> or message me directly (gmail; wowmotty). Please don't open an issue for help with code.</p>
        </div>

        <h3><a href="#">ajaxProcessing</a></h3>
        <div>
            The <code>ajaxProcessing</code> function is needed to convert your custom JSON format into an array usable by the pager plugin (modified in 2.1.3)<br>
            <br>
            So, given this custom JSON format (this is only an example):
            <pre class="prettyprint lang-javascript">{
  "total_rows": 80,

  "headers" : [
    "ID", "Name", "Country Code", "District", "Population"
  ],

  "rows" : [{
    "ID": 1,
    "Name": "Kabul",
    "CountryCode": "AFG",
    "District": "Kabol",
    "Population": 1780000
  }, {
    "ID": 2,
    "Name": "Qandahar",
    "CountryCode": "AFG",
    "District": "Qandahar",
    "Population": 237500
  }, {
    ...
  }, {
    "ID": 25,
    "Name": "Haarlemmermeer",
    "CountryCode": "NLD",
    "District": "Noord-Holland",
    "Population": 110722
  }]
}</pre>
        <h3>OBJECT returned</h3>
        <ul>
            <li>In <span class="version">v2.11</span>, the <code>ajaxProcessing</code> can just return the above object directly (<strong>or don't even bother setting an <code>ajaxProcessing</code> function</strong>).
                So, if an object is returned by the <code>ajaxProcessing</code> function, the data is stored in <code>table.config.pager.ajaxData</code>:
                <ul>
                    <li>The object should contain attributes for <code>total</code> (numeric), <code>headers</code> (array) and <code>rows</code> (array of arrays).</li>
                    <li>A replacement <code>output</code> option can also be loaded via this method and must be included in the <code>output</code> attribute (i.e. <code>ajaxData.output</code>).</li>
                    <li>Additional attributes are also available to the output display by using the attribute key wrapped in curly brackets (e.g. <code>{extra}</code> from <code>ajaxData.extra</code>).</li>
                    <li>Additional attributes can also be objects or arrays and can be accessed via the output string as <code>{extra:0}</code> (for arrays) or <code>{extra:key}</code> for objects.</li>
                    <li>The page number is processed first, so it would be possible to use this string <code>{extra:{page}}</code> (<code>{page}</code> is a one-based index), or if you need a different value use <code>{page+1}</code> (zero-based index plus any number), or <code>{page-1}</code> (zero-based index minus any number).</li>
                    <li>For more details, please see <a href="https://github.com/Mottie/tablesorter/issues/326">issue #326</a>.</li>
                </ul>
            </li>
        </ul>
        <h3>ARRAY returned</h3>
        <ul>
            <li>The <code>ajaxProcessing</code> function can return the data in the following format <code>[ total, rows, headers (optional) ]</code>,
                <br>or in version 2.9+ <code>[ rows, total, headers (optional) ]</code>,
                <br>or in <span class="version">v2.10</span>, return a jQuery object within the array <code>[ total, $rows ]</code>, or just <code>[ total ]</code>:
                    <pre class="prettyprint lang-javascript">[
  // total # rows contained in the database
  80,
  // row data: array of arrays; each internal array has the table cell data for that row
  [
    [ 1, "Kabul", "AFG", "Kabol", 1780000 ],                 // [ "row1cell1", "row1cell2", ... "row1cellN" ],
    [ 2, "Qandahar", "AFG", "Qandahar", 237500 ],            // [ "row2cell1", "row2cell2", ... "row2cellN" ],
     ...
    [ 25, "Haarlemmermeer", "NLD", "Noord-Holland", 110722 ] // [ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
  ],
  [ "ID", "Name", "Country Code", "District", "Population" ] // [ "Header1", "Header2", ... "HeaderN" ] (optional)
]</pre></li>
            </ul>
        </div>

    </div>

    <h1>Demo</h1>
Original Ajax url: <span id="origurl"></span><br>
Current Ajax url: <span id="url"></span>
<br>
<table class="tablesorter">
    <thead>
        <tr>
            <td class="pager sorter-false" colspan="5">
                <img src="../addons/pager/icons/first.png" class="first" alt="First" />
                <img src="../addons/pager/icons/prev.png" class="prev" alt="Prev" />
                <span class="pagedisplay"></span> <!-- this can be any element, including an input -->
                <img src="../addons/pager/icons/next.png" class="next" alt="Next" />
                <img src="../addons/pager/icons/last.png" class="last" alt="Last" />
                <select class="pagesize">
                    <option value="25">25</option>
                </select>
            </td>
        </tr>
        <tr>
            <th data-value="&gt;30">1</th>
            <th>2</th>
            <th>3</th>
            <th>4</th>
            <th>5</th>
        </tr>
    </thead>
    <tfoot>
        <tr>
            <th>1</th>
            <th>2</th>
            <th>3</th>
            <th>4</th>
            <th>5</th>
        </tr>
        <tr>
            <td class="pager" colspan="5">
                <img src="../addons/pager/icons/first.png" class="first" alt="First" />
                <img src="../addons/pager/icons/prev.png" class="prev" alt="Prev" />
                <span class="pagedisplay"></span> <!-- this can be any element, including an input -->
                <img src="../addons/pager/icons/next.png" class="next" alt="Next" />
                <img src="../addons/pager/icons/last.png" class="last" alt="Last" />
                <select class="pagesize">
                    <option value="25">25</option>
                </select>
            </td>
        </tr>
    </tfoot>
    <tbody>
    </tbody>
</table>

    <h1>Javascript</h1>
    <div id="javascript">
        <pre class="prettyprint lang-javascript"></pre>
    </div>

    <h1>CSS</h1>
    <div>
        <pre class="prettyprint lang-css">/* pager wrapper, div */
.pager {
  padding: 5px;
}
/* pager wrapper, in thead/tfoot */
td.pager {
  background-color: #e6eeee;
}
/* pager navigation arrows */
.pager img {
  vertical-align: middle;
  margin-right: 2px;
}
/* pager output text */
.pager .pagedisplay {
  font-size: 11px;
  padding: 0 5px 0 5px;
  width: 50px;
  text-align: center;
}

/*** loading ajax indeterminate progress indicator ***/
#tablesorterPagerLoading {
  background: rgba(255,255,255,0.8) url(icons/loading.gif) center center no-repeat;
  position: absolute;
  z-index: 1000;
}

/*** css used when "updateArrows" option is true ***/
/* the pager itself gets a disabled class when the number of rows is less than the size */
.pager.disabled {
  display: none;
}
/* hide or fade out pager arrows when the first or last row is visible */
.pager img.disabled {
  /* visibility: hidden */
  opacity: 0.5;
  filter: alpha(opacity=50);
}</pre>
    </div>

    <h1>HTML</h1>
    <div id="html">
        <pre class="prettyprint lang-html">&lt;table class=&quot;tablesorter&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;td class=&quot;pager&quot; colspan=&quot;5&quot;&gt;
        &lt;img src=&quot;../addons/pager/icons/first.png&quot; class=&quot;first&quot;/&gt;
        &lt;img src=&quot;../addons/pager/icons/prev.png&quot; class=&quot;prev&quot;/&gt;
        &lt;span class=&quot;pagedisplay&quot;&gt;&lt;/span&gt; &lt;!-- this can be any element, including an input --&gt;
        &lt;img src=&quot;../addons/pager/icons/next.png&quot; class=&quot;next&quot;/&gt;
        &lt;img src=&quot;../addons/pager/icons/last.png&quot; class=&quot;last&quot;/&gt;
        &lt;select class=&quot;pagesize&quot;&gt;
          &lt;option value=&quot;25&quot;&gt;25&lt;/option&gt;
        &lt;/select&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;1 data-value=&quot;&amp;gt;30&quot;&lt;/th&gt; &lt;!-- thead text will be updated from the JSON; make sure the number of columns matches the JSON data --&gt;
      &lt;th&gt;2&lt;/th&gt;
      &lt;th&gt;3&lt;/th&gt;
      &lt;th&gt;4&lt;/th&gt;
      &lt;th&gt;5&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tfoot&gt;
    &lt;tr&gt;
      &lt;th&gt;1&lt;/th&gt; &lt;!-- tfoot text will be updated at the same time as the thead --&gt;
      &lt;th&gt;2&lt;/th&gt;
      &lt;th&gt;3&lt;/th&gt;
      &lt;th&gt;4&lt;/th&gt;
      &lt;th&gt;5&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;pager&quot; colspan=&quot;5&quot;&gt;
        &lt;img src=&quot;../addons/pager/icons/first.png&quot; class=&quot;first&quot;/&gt;
        &lt;img src=&quot;../addons/pager/icons/prev.png&quot; class=&quot;prev&quot;/&gt;
        &lt;span class=&quot;pagedisplay&quot;&gt;&lt;/span&gt; &lt;!-- this can be any element, including an input --&gt;
        &lt;img src=&quot;../addons/pager/icons/next.png&quot; class=&quot;next&quot;/&gt;
        &lt;img src=&quot;../addons/pager/icons/last.png&quot; class=&quot;last&quot;/&gt;
        &lt;select class=&quot;pagesize&quot;&gt;
          &lt;option value=&quot;25&quot;&gt;25&lt;/option&gt;
        &lt;/select&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tfoot&gt;
  &lt;tbody&gt; &lt;!-- tbody will be loaded via JSON --&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</pre>

        </div>

<div class="next-up">
    <hr />
    Next up: <a href="example-pager-filtered.html">Pager plugin + filter widget &rsaquo;&rsaquo;</a>
</div>

</div>

<script>
var $url = $('#url');
$('table')

    // show current URL for the DEMO ONLY
    .on('changingUrl', function(e, url){
        $url.html(url);
    })

    .on('pagerInitialized', function(){
        // allow THIS demo to sort the content; this variable is automatically set to true when ajax
        // is used as there isn't any way to sort the server side data from the client side.
        this.config.serverSideSorting = false;
        // show original highlighted URL
        $('#origurl').html( this.config.pager.ajaxUrl.replace(/(\{.*?\})/g, '<span class="results">$1</span>') );
    });
</script>

</body>
</html>
TheSin- commented 9 years ago

you need to modify your server side scripts to use the values from fcol and make the filters work server side.

there is no need for a jsfiddle for this.

DaveGahan commented 9 years ago

Thanks a lot for the reply.

I did it but the problem is that using "filter_functions" together with "ajaxurl" does not make any select box appear. The field remains a classic input box, not a select box.

The only way to make it a select box is to set something like this into html th :

class="filter-select" data-placeholder="Select a name"

But it only populates the select box with current page values, so I really need to set up all possible values by myself using "filter_functions". And this function is not recognized.

Note that, despite above html entry works, this does not work neither :

filter_functions : {

'.first-name' : true

}

I hope I'm a bit clearer (sorry if it's not the case).

Mottie commented 9 years ago

Hi @DaveGahan!

If you want a select to be populated by custom options, go ahead and add the "filter-select" class to the header. Then use the filter_selectSource option to add your more extensive list of options.

DaveGahan commented 9 years ago

Hi Mottie and thank you for this great job.

I tried but it does not work.

I took example-pager-ajax.html and I modified these lines :

2015-06-07 21_43_38-tablesorter-master - netbeans ide 8 0 2

2015-06-07 21_44_23-tablesorter-master - netbeans ide 8 0 2

Firstable, note that init of tablesorter does not seem to complete :

2015-06-07 21_45_32-jquery plugin_ tablesorter 2 0 - pager plugin - ajax

Making any filter/sorter change will do the job but only with cells content :

2015-06-07 21_46_07-jquery plugin_ tablesorter 2 0 - pager plugin - ajax

Thanks a lot for your help.

thezoggy commented 9 years ago

#any is an id, not a class...

Mottie commented 9 years ago

Hey @thezoggy! That shouldn't matter, you can target a column using either a class or id.

I see the problem... because the table is empty, no parsers are set so the filter select code is trying to parse the values and getting a javascript error. I'll try to fix it soon.


Derp, yeah, I see the class should be used in the above case... I was making my own demo and found the error.

Mottie commented 9 years ago

Check out this demo.

The fix is available in the master branch of the repository.

DaveGahan commented 9 years ago

@thezoggy Sorry I made a mistake by using the wrong screenshot among many possibilities ;-)

@Mottie Thank you very much, I'll check it as soon as possible today. You were really fast, thanks a lot :-)

DaveGahan commented 9 years ago

Problems solved ! \o/

Thanks a lot for help.