javascriptasp.net-mvcasp.net-coredatatables

Datatables.js Search two columns - Buttons Stop Working After Filtering - How to Maintain Event Listeners?


I'm using DataTables.js in my ASP.NET Core project to display a list of machines and allow users to filter the data. Each row has a button that, when clicked, toggles the display of additional details in a box below the row. Everything works fine initially, but after applying a filter, the buttons no longer work.

I've simplified the code to focus on the issue:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.5/css/jquery.dataTables.min.css">
    <style>
        
        .table-flexible th, .table-flexible td {
            text-align: center;
            vertical-align: middle;
            padding: 8px 16px;
        }
        .btn {
            font-size: 17px;
            padding: 2px;
        }
        .details-row {
            display: none;
        }
        .details-box {
            padding: 10px;
            background-color: #f9f9f9;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
</head>
<body>

<div class="form-group">
    <label for="machineFilter">Filter by Machine:</label>
    <select id="machineFilter" class="form-control" style="width: 20%; font-weight: normal">
        <option value="">All Machines</option>
        <!-- Example static options -->
        <option value="Machine1">Machine1</option>
        <option value="Machine2">Machine2</option>
    </select>
</div>

<table class="table table-striped table-flexible">
    <thead>
        <tr>
            <th>Machine</th>
            <th>Description</th>
            <th>Action</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Machine1</td>
            <td>Details about Machine1</td>
            <td><button class="btn btn-info toggle-btn" data-target="#details1">Details</button></td>
        </tr>
        <tr id="details1" class="details-row">
            <td colspan="3">
                <div class="details-box">
                    Detailed information about Machine1.
                </div>
            </td>
        </tr>
        <tr>
            <td>Machine2</td>
            <td>Details about Machine2</td>
            <td><button class="btn btn-info toggle-btn" data-target="#details2">Details</button></td>
        </tr>
        <tr id="details2" class="details-row">
            <td colspan="3">
                <div class="details-box">
                    Detailed information about Machine2.
                </div>
            </td>
        </tr>
    </tbody>
</table>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js"></script>
<script>
    $(document).ready(function() {
        // Initialize DataTable
        var table = $('.table-flexible').DataTable({
         columnDefs: [{
                    "defaultContent": "-",
                    "targets": "_all"
                }],
            paging: false,
            ordering: false,
            info: false,
            dom: 'ft'
        });

        // Custom filter for Machine
        $('#machineFilter').on('change', function () {
            var selectedMachine = $(this).val();
            if (selectedMachine) {
                table.column(0).search('^' + selectedMachine + '$', true, false).draw();
            } else {
                table.column(0).search('').draw();
            }
        });

        // Attach event listener to buttons after filtering
        table.on('draw', function() {
            attachToggleEventListeners();
        });

        attachToggleEventListeners();  // Initial attachment of event listeners

        function attachToggleEventListeners() {
            $('.toggle-btn').off('click').on('click', function() {
                var target = $(this).data('target');
                $(target).fadeToggle(500); // Toggle the visibility of the target row
            });
        }
    });
</script>

</body>
</html>

The buttons work perfectly before any filtering is applied. However, after applying a filter using the dropdown, the buttons stop working (i.e., they no longer toggle the visibility of the detail boxes).

How can I ensure that the buttons maintain their functionality even after filtering the DataTable?

Any help or examples is greatly appreciated! Thanks for your time and attention!

UPDATE: The solution provided by Jerdine Sabio works for the code I shared above, but that code does not accurately reflect what I have in my actual project. Here is a further simplification of my real code, which I ran in a .NET project.

@{

// Sample machine names
var MachineNames = new List<string> { "Machine A", "Machine B", "Machine C" };



// Sample requests using anonymous objects
var requests = new[]
{
new { Id = 1, ContactName = "Client A", CompanyName = "Machine A", Status = "Pending", Msg = "Request 1" },
new { Id = 2, ContactName = "Client B", CompanyName = "Machine B", Status = "In Progress", Msg = "Request 2" },
new { Id = 3, ContactName = "Client C", CompanyName = "Machine C", Status = "Completed", Msg = "Request 3" }
};

}


<div class="form-group">
    <label for="machineFilter">Filter by Machine:</label>
    <select id="machineFilter" class="form-control" style="width: 20%; font-weight: normal">
        <option value="">All Machines</option>
        @foreach (var machine in MachineNames)
        {
        <option value="@machine">@machine</option>
        }
    </select>
</div>

<table class="table table-striped table-flexible">
    <thead class="thead-custom">
    <tr>
        <th>Client</th>
        <th>Machine</th>
        <th>Status</th>
        <th>Description</th>
        <th>Actions</th>
    </tr>
    </thead>
    <tbody>
    @foreach (var request in requests)
    {
    <tr>
        <td>@request.ContactName</td>
        <td>@request.CompanyName</td>
        <td>@request.Status</td>
        <td>@request.Msg</td>
        <td>
            <button class="btn btn-info btn-sm toggle-btn" data-target="#detailsCollapse_@request.Id">
                Details
            </button>
        </td>
    </tr>
    <tr id="detailsCollapse_@request.Id" class="details-row" style="display: none;">
        <td colspan="5">
            <div class="card details-card">
                <button class="btn btn-success btn-sm mark-seen-btn" data-id="@request.Id">Mark as Seen</button>
            </div>
        </td>
    </tr>
    }
    </tbody>
</table>


@section Scripts
{
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js"></script>
<script>
    $(document).ready(function() {
        var table = $('.table-flexible').DataTable({
            columnDefs: [{
                "defaultContent": "-",
                "targets": "_all"
            }],
            paging: false,
            ordering: false,
            info: false,
            dom: 'ft'
        });

        // Custom filter for Machine
        $('#machineFilter').on('change', function () {
            var selectedMachine = $(this).val();
            if (selectedMachine) {
                table.column(1).search( selectedMachine, true, false).draw();
            } else {
                table.column(1).search('').draw();
            }
        });

        // Attach event listeners
        function attachEventListeners() {
            $('.toggle-btn').off('click').on('click', function() {
                var target = $(this).data('target');
                $(target).fadeToggle(500);
            });

            $('.mark-seen-btn').off('click').on('click', function() {
                var id = $(this).data('id');
                alert('Marked as seen: ' + id);
            });
        }

        // Reattach event listeners after each table draw
        table.on('draw', function() {
            attachEventListeners();
        });

        // Initial attachment of event listeners
        attachEventListeners();
    });
</script>

}

Solution

  • Here's a fiddle with your updated code; https://dotnetfiddle.net/UlNwXV

    1. Create a custom filter and use it on your on change event;
    $('#machineFilter').on('change', function () {
       var selectedMachine = $(this).val();
       if (selectedMachine) {
          $.fn.dataTable.ext.search.push(
          function( settings, searchData, index) {
    
             // get filter value
             var filter = $('#machineFilter').val();
    
             // get row object from data table
             var getRow = table.row(index).node();
    
             // select row in jquery
             var row = $(getRow);
    
             // get the value from data-id
             var getDataId = row.find('td[data-id]').data('id');
                        
             // if data-id == filter value, display this row            
             return getDataId == filter;
          });
          table.draw();
       } else {
          table.column(1).search('').draw();
       }
    });
    
    1. Add data-id to your td element, I used request.CompanyName since it has the Machine names.
    @foreach (var request in requests)
    {
       <tr>
          <td>@request.ContactName</td>
          <td data-id="@request.CompanyName">@request.CompanyName</td>
          <td>@request.Status</td>
          <td>@request.Msg</td>
          <td>
             <button class="btn btn-info btn-sm toggle-btn" data-target="#detailsCollapse_@request.Id">
                Details
             </button>
          </td>
       </tr>
    
       <tr id="detailsCollapse_@request.Id" class="details-row" style="display: none;">
          <td colspan="5" data-id="@request.CompanyName">
             <div class="card details-card">
                <button class="btn btn-success btn-sm mark-seen-btn" data-id="@request.Id">Mark as Seen</button>
              </div>
             </td>
       </tr>                
    }