1

I am trying to filter a table using the text boxes in table footers. When I type a value in these footers I am expecting to invoke filtering of the table. In order to be able to type comparison operators, I pushed a comparison function into the set of datatables filters that will read the text (that can have a comparison operator like > < =) in the corresponding footer textbox and compare the values in that column according to the comparison operator.

The filtering works perfectly as expected the only problem is that I keep getting this error:

Uncaught DOMException: Failed to execute 'appendChild' on 'Node': The node to be removed is no longer a child of this node. Perhaps it was moved in a 'blur' event handler?

After the error the table starts to behave weirdly: the focus goes away from the text box in the footer and the footer sometimes moves to a position under the header.

How can I make the table stable and get rid of this error?

Please pay attention to the function inside $.fn.dataTableExt.afnFiltering.push(:

<!DOCTYPE HTML>
<html lang="en">
<head>
    <title>
    </title>

    <link rel="stylesheet" type="text/css"
          href="https://cdn.datatables.net/v/dt/jq-2.2.4/dt-1.10.13/b-1.2.4/b-html5-1.2.4/fh-3.1.2/datatables.min.css"/>
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.11.1/themes/smoothness/jquery-ui.css"/>

    <script type="text/javascript"
            src="https://cdn.datatables.net/v/dt/jq-2.2.4/dt-1.10.13/b-1.2.4/b-html5-1.2.4/fh-3.1.2/datatables.min.js"></script>
    <script src="http://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script>
    <script type="text/javascript" src="https://cdn.datatables.net/plug-ins/1.10.13/sorting/natural.js"></script>
    <script src="http://www.chartjs.org/dist/2.6.0/Chart.bundle.js"></script>
    <script src="http://www.chartjs.org/samples/latest/utils.js"></script>



</head>
<body>

<img id="loader"
     style="
        width:36px;
        height:36px;
        display: none;
        position:absolute;
        top:50%;
        left:50%;
        margin-top:-18px;
        margin-left:-18px;"/>
<p><a href="/accounts/logout/">Logout</a> | <a href="/accounts/profile/">Home</a></p>

<div id="title">
    <b style="font-size:200%">Optimize proxies<br></b>
</div>
<div id="proxy_history_dialog" title="Proxy history" style="display:none;" font="8">
</div>
<div id="graph_dialog" title="Extraction - daily vs accumulative" style="display:none;" font="8">


</div>
<table id='p_table-id' class="display" cellspacing="0" style="float: left;">
    <thead>
    <tr>

        <th>Site id</th>

        <th>Site name</th>

        <th>Is optimized</th>

        <th>Proxy group</th>

        <th>Price level</th>

        <th>Duration</th>

        <th>Avg extraction score (cached)</th>

        <th>Max extraction in last 3 days (cached)</th>

        <th>Overlap score</th>

        <th>Last status</th>

        <th>Actions</th>
        <th>Change proxy</th>

    </tr>
    </thead>
    <tfoot>
    <tr>

        <th>Site id</th>

        <th>Site name</th>

        <th>Is optimized</th>

        <th>Proxy group</th>

        <th>Price level</th>

        <th>Duration</th>

        <th>Avg extraction score (cached)</th>

        <th>Max extraction in last 3 days (cached)</th>

        <th>Overlap score</th>

        <th>Last status</th>

        <th>Actions</th>
        <th>Change proxy</th>
    </tr>
    </tfoot>
    <tbody>

    <tr>

        <td>-106</td>

        <td>target.com (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>14 days 11:17:57</td>

        <td>83.18</td>

        <td>91.03</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_-106">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>-105</td>

        <td>walmart.com (P)</td>

        <td>True</td>

        <td>us</td>

        <td>2</td>

        <td>27 days 23:58:15</td>

        <td>96.21</td>

        <td>97.28</td>

        <td>None</td>

        <td>failed_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_-105">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>-104</td>

        <td>bestonix (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>14 days 11:17:56</td>

        <td>64.05</td>

        <td>63.91</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_-104">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>209</td>

        <td>rockbottomgolf.com (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>31 days 00:56:48</td>

        <td>95.73</td>

        <td>95.02</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_209">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>210</td>

        <td>golfgalaxy.com (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>14 days 11:17:54</td>

        <td>87.91</td>

        <td>90.53</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_210">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>211</td>

        <td>tgw.com (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>14 days 11:17:53</td>

        <td>60.06</td>

        <td>64.00</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_211">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>244</td>

        <td>amazon_golf (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>53 days 18:23:36</td>

        <td>92.38</td>

        <td>93.20</td>

        <td>None</td>

        <td>None</td>


       <td></td>

        <td>
            Change proxy:
            <select id="proxy_dp_244">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>246</td>

        <td>golfdiscount.com (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>31 days 00:56:46</td>

        <td>85.85</td>

        <td>75.90</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_246">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>248</td>

        <td>globalgolf.com (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>31 days 00:56:45</td>

        <td>61.02</td>

        <td>65.17</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_248">

            </select></option>



        </td>
    </tr>

    <tr>

        <td>658</td>

        <td>sephora_ulta_new (P)</td>

        <td>True</td>

        <td>shader_us</td>

        <td>1</td>

        <td>13 days 11:17:48</td>

        <td>98.55</td>

        <td>99.16</td>

        <td>None</td>

        <td>successful_downgrade</td>


        <td style="position: relative;">



        <td>
            Change proxy:
            <select id="proxy_dp_658">

            </select></option>



        </td>
    </tr>

    </tbody>
</table>

<script>
    function natural_compare(a, b, html) {
        var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?%?$|^0x[0-9a-f]+$|[0-9]+)/gi,
            sre = /(^[ ]*|[ ]*$)/g,
            dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
            hre = /^0x[0-9a-f]+$/i,
            ore = /^0/,
            htmre = /(<([^>]+)>)/ig,
            // convert all to strings and trim()
            x = a.toString().replace(sre, '') || '',
            y = b.toString().replace(sre, '') || '';
        // remove html from strings if desired
        if (!html) {
            x = x.replace(htmre, '');
            y = y.replace(htmre, '');
        }
        // chunk/tokenize
        var xN = x.replace(re, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0'),
            yN = y.replace(re, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0'),
            // numeric, hex or date detection
            xD = parseInt(x.match(hre), 10) || (xN.length !== 1 && x.match(dre) && Date.parse(x)),
            yD = parseInt(y.match(hre), 10) || xD && y.match(dre) && Date.parse(y) || null;

        // first try and sort Hex codes or Dates
        if (yD) {
            if (xD < yD) {
                return -1;
            }
            else if (xD > yD) {
                return 1;
            }
        }

        // natural sorting through split numeric strings and default strings
        for (var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
            // find floats not starting with '0', string or 0 if not defined (Clint Priest)
            var oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc], 10) || xN[cLoc] || 0;
            var oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc], 10) || yN[cLoc] || 0;
            // handle numeric vs string comparison - number < string - (Kyle Adams)
            if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
                return (isNaN(oFxNcL)) ? 1 : -1;
            }
            // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
            else if (typeof oFxNcL !== typeof oFyNcL) {
                oFxNcL += '';
                oFyNcL += '';
            }
            if (oFxNcL < oFyNcL) {
                return -1;
            }
            if (oFxNcL > oFyNcL) {
                return 1;
            }
        }
        return 0;
    }


    $(document).ready(function () {
        ///////////////////////////////////////////////////////////////////////////
        DURATION_COL_IX = 5;
        // Setup column search - add a text input to each footer cell
        $('#p_table-id').find('tfoot th').each(function () {
            var title = $(this).text();
            $(this).html('<input type="text" placeholder="Filter">');
        });
        // DataTable
        var table = $('#p_table-id').DataTable({
            "orderClasses": false,
            lengthChange: false,
            columnDefs: [
                {type: 'natural', targets: '_all'}
            ],
            "dom": '<"top" li>lt<"bottom"i><"clear">',

            fixedHeader: {
                footer: true
            },
            'iDisplayLength': -1,
            "order": [[5, "asc"]]
        });
        // Apply the search todo: delete
        var last_changed_footer_col;
        table.columns().every(function () {
            var col = this;
            $('input', this.footer()).on('keyup change', function () {
                last_changed_footer_col = col.footer().cellIndex;
                table.draw();


            });
        });
        $.fn.dataTableExt.afnFiltering.push(
            function (oSettings, aData, iDataIndex) {


                var column = table.column(last_changed_footer_col);

                var footer_txt = $('input', column.footer()).val().replace(/\s*/g, '');

                var cell_data = aData[last_changed_footer_col];
                if (footer_txt.match(/^[<>=].+/)) {

                    var comparator = footer_txt.match(/^([<>=]).*$/)[1];
                    var val_regex = new RegExp('^' + comparator + '(.*)');
                    var val = footer_txt.match(val_regex)[1];
                    switch (comparator) {
                        case '<':
                            return natural_compare(cell_data, val, false) < 0;
                        case '>':
                            return natural_compare(cell_data, val, false) > 0;
                        case '=':
                            return natural_compare(cell_data, val, false) === 0;
                    }
                } else {
                    //just filter
                    return cell_data.search(footer_txt) >= 0;


                }
            }
        );
        ///////////////////////////////////////////////////////////////////////////////







    });

</script>


</body>
</html>

1 Answer 1

1

Your issue is in this line:

$('input', this.footer()).on('keyup change', function () {

In this way you call twice the search method and so you get the error.

Hence, you need to change it to:

$('input', this.footer()).on('input', function () {

function natural_compare(a, b, html) {
    var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?%?$|^0x[0-9a-f]+$|[0-9]+)/gi,
            sre = /(^[ ]*|[ ]*$)/g,
            dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
            hre = /^0x[0-9a-f]+$/i,
            ore = /^0/,
            htmre = /(<([^>]+)>)/ig,
    // convert all to strings and trim()
            x = a.toString().replace(sre, '') || '',
            y = b.toString().replace(sre, '') || '';
    // remove html from strings if desired
    if (!html) {
        x = x.replace(htmre, '');
        y = y.replace(htmre, '');
    }
    // chunk/tokenize
    var xN = x.replace(re, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0'),
            yN = y.replace(re, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0'),
    // numeric, hex or date detection
            xD = parseInt(x.match(hre), 10) || (xN.length !== 1 && x.match(dre) && Date.parse(x)),
            yD = parseInt(y.match(hre), 10) || xD && y.match(dre) && Date.parse(y) || null;

    // first try and sort Hex codes or Dates
    if (yD) {
        if (xD < yD) {
            return -1;
        }
        else if (xD > yD) {
            return 1;
        }
    }

    // natural sorting through split numeric strings and default strings
    for (var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
        // find floats not starting with '0', string or 0 if not defined (Clint Priest)
        var oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc], 10) || xN[cLoc] || 0;
        var oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc], 10) || yN[cLoc] || 0;
        // handle numeric vs string comparison - number < string - (Kyle Adams)
        if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
            return (isNaN(oFxNcL)) ? 1 : -1;
        }
        // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
        else if (typeof oFxNcL !== typeof oFyNcL) {
            oFxNcL += '';
            oFyNcL += '';
        }
        if (oFxNcL < oFyNcL) {
            return -1;
        }
        if (oFxNcL > oFyNcL) {
            return 1;
        }
    }
    return 0;
}


$(document).ready(function () {
    ///////////////////////////////////////////////////////////////////////////
    DURATION_COL_IX = 5;
    // Setup column search - add a text input to each footer cell
    $('#p_table-id').find('tfoot th').each(function () {
        var title = $(this).text();
        $(this).html('<input type="text" placeholder="Filter">');
    });
    // DataTable
    var table = $('#p_table-id').DataTable({
        "orderClasses": false,
        lengthChange: false,
        columnDefs: [
            {type: 'natural', targets: '_all'}
        ],
        "dom": '<"top" li>lt<"bottom"i><"clear">',
        fixedHeader: {
            footer: true
        },
        'iDisplayLength': -1,
        "order": [[5, "asc"]]
    });
    // Apply the search todo: delete
    var last_changed_footer_col;
    table.columns().every(function () {
        var col = this;
        $('input', this.footer()).on('input', function (e) {
            //substitute input with keyup change events
            // and print to the console.....
            //console.log(e.type);
            last_changed_footer_col = col.footer().cellIndex;
            table.draw(false);
        });
    });
    $.fn.dataTableExt.afnFiltering.push(
            function (oSettings, aData, iDataIndex) {
                var column = table.column(last_changed_footer_col);
                var footer_txt = $('input', column.footer()).val().replace(/\s*/g, '');
                var cell_data = aData[last_changed_footer_col];
                if (footer_txt.match(/^[<>=].+/)) {
                    var comparator = footer_txt.match(/^([<>=]).*$/)[1];
                    var val_regex = new RegExp('^' + comparator + '(.*)');
                    var val = footer_txt.match(val_regex)[1];
                    switch (comparator) {
                        case '<':
                            return natural_compare(cell_data, val, false) < 0;
                        case '>':
                            return natural_compare(cell_data, val, false) > 0;
                        case '=':
                            return natural_compare(cell_data, val, false) === 0;
                    }
                } else {
                    //just filter
                    return cell_data.search(footer_txt) >= 0;
                }
            }
    );
});
<link rel="stylesheet" type="text/css"
href="https://cdn.datatables.net/v/dt/jq-2.2.4/dt-1.10.13/b-1.2.4/b-html5-1.2.4/fh-3.1.2/datatables.min.css"/>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.11.1/themes/smoothness/jquery-ui.css"/>

<script type="text/javascript"
src="https://cdn.datatables.net/v/dt/jq-2.2.4/dt-1.10.13/b-1.2.4/b-html5-1.2.4/fh-3.1.2/datatables.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/plug-ins/1.10.13/sorting/natural.js"></script>


<img id="loader"
     style="
        width:36px;
        height:36px;
        display: none;
        position:absolute;
        top:50%;
        left:50%;
        margin-top:-18px;
        margin-left:-18px;"/>

<p><a href="/accounts/logout/">Logout</a> | <a href="/accounts/profile/">Home</a></p>

<div id="title">
    <b style="font-size:200%">Optimize proxies<br></b>
</div>
<div id="proxy_history_dialog" title="Proxy history" style="display:none;" font="8">
</div>
<div id="graph_dialog" title="Extraction - daily vs accumulative" style="display:none;" font="8">


</div>
<table id='p_table-id' class="display" cellspacing="0" style="float: left;">
    <thead>
    <tr>
        <th>Site id</th>
        <th>Site name</th>
        <th>Is optimized</th>
        <th>Proxy group</th>
        <th>Price level</th>
        <th>Duration</th>
        <th>Avg extraction score (cached)</th>
        <th>Max extraction in last 3 days (cached)</th>
        <th>Overlap score</th>
        <th>Last status</th>
        <th>Actions</th>
        <th>Change proxy</th>
    </tr>
    </thead>
    <tfoot>
    <tr>
        <th>Site id</th>
        <th>Site name</th>
        <th>Is optimized</th>
        <th>Proxy group</th>
        <th>Price level</th>
        <th>Duration</th>
        <th>Avg extraction score (cached)</th>
        <th>Max extraction in last 3 days (cached)</th>
        <th>Overlap score</th>
        <th>Last status</th>
        <th>Actions</th>
        <th>Change proxy</th>
    </tr>
    </tfoot>
    <tbody>
    <tr>
        <td>-106</td>
        <td>target.com (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>14 days 11:17:57</td>
        <td>83.18</td>
        <td>91.03</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_-106">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>-105</td>
        <td>walmart.com (P)</td>
        <td>True</td>
        <td>us</td>
        <td>2</td>
        <td>27 days 23:58:15</td>
        <td>96.21</td>
        <td>97.28</td>
        <td>None</td>
        <td>failed_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_-105">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>-104</td>
        <td>bestonix (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>14 days 11:17:56</td>
        <td>64.05</td>
        <td>63.91</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_-104">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>209</td>
        <td>rockbottomgolf.com (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>31 days 00:56:48</td>
        <td>95.73</td>
        <td>95.02</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_209">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>210</td>
        <td>golfgalaxy.com (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>14 days 11:17:54</td>
        <td>87.91</td>
        <td>90.53</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_210">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>211</td>
        <td>tgw.com (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>14 days 11:17:53</td>
        <td>60.06</td>
        <td>64.00</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_211">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>244</td>
        <td>amazon_golf (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>53 days 18:23:36</td>
        <td>92.38</td>
        <td>93.20</td>
        <td>None</td>
        <td>None</td>
        <td></td>
        <td>
            Change proxy:
            <select id="proxy_dp_244">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>246</td>
        <td>golfdiscount.com (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>31 days 00:56:46</td>
        <td>85.85</td>
        <td>75.90</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_246">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>248</td>
        <td>globalgolf.com (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>31 days 00:56:45</td>
        <td>61.02</td>
        <td>65.17</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_248">
            </select></option>
        </td>
    </tr>
    <tr>
        <td>658</td>
        <td>sephora_ulta_new (P)</td>
        <td>True</td>
        <td>shader_us</td>
        <td>1</td>
        <td>13 days 11:17:48</td>
        <td>98.55</td>
        <td>99.16</td>
        <td>None</td>
        <td>successful_downgrade</td>
        <td style="position: relative;">
        <td>
            Change proxy:
            <select id="proxy_dp_658">
            </select></option>
        </td>
    </tr>
    </tbody>
</table>

Sign up to request clarification or add additional context in comments.

2 Comments

can you share the reason why in first case the search method is executed twice?
@MaxSegal When you press a key in the input search field a keyup event fires. But, when the focus is lost, on search, a change event is fired You may add a console log into $('input', this.footer()).on('input', function (e) {. See my comment, in updated snippet, and test it by yourself. Thanks and let me know

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.