Search code examples
javascripthtmlasp.net-mvcdatatables

How to filter and display a row when the search input matches the content within a collapsed <tr>in DataTables?


I'm using DataTables to manage a table where certain contents are in collapsible <tr> elements. Initially, these <tr> are hidden (display: none), and they only become visible when their corresponding button (Details) is pressed .

When using the DataTables search filter, I'd like the table to display the main row that contains the matching content that is in the hidden <tr> element, without requiring the user to click the "Details" button first.

Here is my current setup:

Each collapsible row has a unique ID, and I store the content of each row in a dictionary with the following structure: { 'id_of_row': ['content1', 'content2', 'content3', ...] } The goal is to search within the dictionary when the user types in the search box, and if a match is found in any of the dictionary elements, it uses the id_of_row and the corresponding main row with that id should be displayed.

Example Problem:

If I search for the term 'requ', nothing shows up because the corresponding content is inside a collapsed <tr>. However, if I manually expand the <tr> by clicking the "Details" button and make that content visible and if there is a match inside that content it will show up.

What I’ve Tried:

I use a dictionary that maps the main row’s ID to an array of content for that row. When the search input matches an element in the dictionary, I want the corresponding main row to be displayed, even if it’s content its inside a currently hidden element. However, I can’t seem to get this to work as expected. The row is not being displayed when its content matches the search term unless it's manually expanded.

Here is a simplified version of my current code:

@using Newtonsoft.Json
@{
// 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" }
};

// Sample dictionary data for matching the behavior of your original example
var requestDictionary = new Dictionary<int, string[]>
{
{ 1, new[] { "client a", "machine a", "pending", "request 1" } },
{ 2, new[] { "client b", "machine b", "in progress", "request 2" } },
{ 3, new[] { "client c", "machine c", "completed", "request 3" } }
};
}

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Collapsible Table with Custom Filter</title>

    <!-- Include jQuery -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

    <!-- Include DataTables CSS & JS -->
    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css">
    <script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js"></script>

    <style>
        table {
            width: 100%;
            border-collapse: collapse;
        }

        th, td {
            padding: 8px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }

        .show {
            display: table-row;
        }

        .dataTables_filter label {
            font-weight: bold;
            font-size: 1.2em;
        }

        .dataTables_filter input {
            margin-left: 10px;
            padding: 5px;
            border-radius: 5px;
            border: 1px solid #ccc;
        }

        #search-container {
            width: auto;
            padding: 5px;
            margin-bottom: 10px;
        }
    </style>
</head>

<body>
<h1>Collapsible Table with Custom Search Filter</h1>

<!-- Search filter -->
<div id="search-container"></div>

<!-- The table with collapsible rows -->
<table id="myTable" class="display table-flexible">
    <thead>
    <tr>
        <th>Request ID</th>
        <th>Client</th>
        <th>Machine</th>
        <th>Status</th>
        <th>Description</th>
    </tr>
    </thead>
    <tbody>
    @foreach (var request in requests)
    {
    <tr>
        <td>@request.Id</td>
        <td>@request.ContactName</td>
        <td data-id="@request.CompanyName">@request.CompanyName</td>
        <td>@request.Status</td>
        <td>
            <button class="btn btn-info btn-sm toggle-btn" data-target="#[email protected]">Details</button>
        </td>
    </tr>

    <tr id="[email protected]" class="details-row" style="display: none;">
        <td colspan="6">
            <div class="card custom-card-body details-card">
                <div class="detail-item">
                    <strong>Message:</strong>
                    <p class="tw-font-bold">@request.Msg</p>
                </div>
            </div>
        </td>
    </tr>
    }
    </tbody>
</table>

@section Scripts {
<script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js"></script>
<script>
    $(document).ready(function () {
       
        var requestDictionary = @Html.Raw(JsonConvert.SerializeObject(requestDictionary));

        console.log(requestDictionary); // Debugging purposes
        
        // Initialize DataTable
        var table = $('#myTable').DataTable({
            columnDefs: [{
                "defaultContent": "-",
                "targets": "_all"
            }],
            paging: false,
            ordering: false,
            info: false,
            dom: 'ft'
        });

        // Move search box to custom container
        $('#search-container').html($('.dataTables_filter'));

        // Custom search functionality using the requestDictionary
        function filterTable(searchTerm) {
            var rows = table.rows().nodes(); // Fetch all rows
            $(rows).each(function () {
                var $row = $(this); // The `tr` element

                // Extract requestId from the first column (adjust if necessary)
                var requestId = $row.find('td').first().text().trim();

                // Ensure requestId is a valid number
                requestId = parseInt(requestId, 10);
                if (isNaN(requestId)) {
                    return; // Skip rows without a valid requestId
                }

                // Check if requestId exists in the dictionary
                if (!requestDictionary.hasOwnProperty(requestId)) {
                    return;
                }

                var requestDetails = requestDictionary[requestId];
                console.log(requestDetails); // Debugging purposes

                // Check if any of the details contain the search term
                var match = requestDetails.some(function (detail) {
                    return detail.toLowerCase().includes(searchTerm.toLowerCase());
                });

                // Show or hide the row based on the search match
                if (match) {
                    console.log("HIT - Showing row for requestId:", requestId);
                    $row.css("display", "table-row");  // Set display to table-row
                } else {
                    console.log("MISS - Hiding row for requestId:", requestId);
                    $row.css("display", "none");  // Hide row
                }
            });
        }

        // Handle search input
        $('.dataTables_filter input[type="search"]').on('input', function () {
            var searchTerm = $(this).val().trim();
            filterTable(searchTerm);
        });

        // Attach event listeners for toggling rows
        $('.toggle-btn').on('click', function () {
            var target = $(this).data('target');
            $(target).fadeToggle(500);
        });
    });
</script>
}
</body>



Thank you for your time and attention!

UPDATE

Here is a visualization of what I wanted to be displayed when I search something in the filter. I want the red row (main row) to appear and not just the green row (it is the hidden row that shows up when the button is clicked) enter image description here


Solution

  • I think the problem is inside your function. What you are doing is, after match is found you are operating on the same row that is currently processing inside of the loop. Instead, you will need to find the details row using current request id and show that row.

    Here is one of the possible solution that you can try out.

    function filterTable(searchTerm) {
        // First hide all the details row
        $('.details-row').css('display', 'none');
    
        var rows = table.rows().nodes(); // Fetch all rows
        $(rows).each(function() {
          var $row = $(this); // The `tr` element
    
          // Extract requestId from the first column (adjust if necessary)
          var requestId = $row.find('td').first().text().trim();
    
          // Ensure requestId is a valid number
          requestId = parseInt(requestId, 10);
          if (isNaN(requestId)) {
            return; // Skip rows without a valid requestId
          }
    
          // Check if requestId exists in the dictionary
          if (!requestDictionary.hasOwnProperty(requestId)) {
            return;
          }
    
          var requestDetails = requestDictionary[requestId];
          //      console.log(requestDetails); // Debugging purposes
    
          // Check if any of the details contain the search term
          var match = requestDetails.some(function(detail) {
            return detail.toLowerCase().includes(searchTerm.toLowerCase());
          });
    
          // Show the details row based on the search match
          if (match) {
            $('#detailsCollapse_' + requestId).css('display', 'table-row');
          }
        });
      }
    

    UPDATE In the case where you want to show only the main row like you mentioned in the comments, you need to alter some things.

    1. Add your own custom search filter instead of using datatable search filter
    2. Disable search filter for datatable
    3. Comment out the line where it puts datatable search filter in your custom div

    I have prepared below code snippet for you

    $(document).ready(function() {
    
      var requestDictionary = {
        1: ["client a", "machine a", "pending", "request 1"],
        2: ["client b", "machine b", "in progress", "request 2"],
        3: ["client c", "machine c", "completed", "request 3"],
      }
    
      // Initialize DataTable
      var table = $('#myTable').DataTable({
        columnDefs: [{
          "defaultContent": "-",
          "targets": "_all"
        }],
        paging: false,
        ordering: false,
        info: false,
        dom: 'ft',
        searching: false
      });
    
      // Move search box to custom container
      // $('#search-container').html($('.dataTables_filter'));
    
      // Custom search functionality using the requestDictionary
      function filterTable(searchTerm) {
        var rows = table.rows().nodes(); // Fetch all rows
        $(rows).each(function() {
          var $row = $(this); // The `tr` element
    
          // Extract requestId from the first column (adjust if necessary)
          var requestId = $row.find('td').first().text().trim();
    
          // Ensure requestId is a valid number
          requestId = parseInt(requestId, 10);
          if (isNaN(requestId)) {
            return; // Skip rows without a valid requestId
          }
    
          // Check if requestId exists in the dictionary
          if (!requestDictionary.hasOwnProperty(requestId)) {
            return;
          }
    
          var requestDetails = requestDictionary[requestId];
    
          // Check if any of the details contain the search term
          var match = requestDetails.some(function(detail) {
            return detail.toLowerCase().includes(searchTerm.toLowerCase());
          });
    
          // Show or hide the row based on the search match
          if (match) {
            $row.css("display", "table-row"); // Set display to table-row
          } else {
            $row.css("display", "none"); // Hide row
          }
        });
      }
    
      // Handle search input
      $('.dataTables_filter input[type="search"]').on('input', function() {
        var searchTerm = $(this).val().trim();
        filterTable(searchTerm);
      });
    
      // Attach event listeners for toggling rows
      $('.toggle-btn').on('click', function() {
        var target = $(this).data('target');
        $(target).fadeToggle(500);
      });
    });
    table {
                width: 100%;
                border-collapse: collapse;
            }
    
            th, td {
                padding: 8px;
                text-align: left;
                border-bottom: 1px solid #ddd;
            }
    
            .show {
                display: table-row;
            }
    
            .dataTables_filter label {
                font-weight: bold;
                font-size: 1.2em;
            }
    
            .dataTables_filter input {
                margin-left: 10px;
                padding: 5px;
                border-radius: 5px;
                border: 1px solid #ccc;
            }
    
            #search-container {
                width: auto;
                padding: 5px;
                margin-bottom: 10px;
            }
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    
    <!-- Include DataTables CSS & JS -->
    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css">
    <script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js"></script>
    
    
    <h1>Collapsible Table with Custom Search Filter</h1>
    
    
    
    <!-- Search filter -->
    <div id="search-container">
      <div id="myTable_filter" class="dataTables_filter"><label>Search:<input type="search" class="" placeholder="" aria-controls="myTable"></label></div>
    </div>
    
    <!-- The table with collapsible rows -->
    <table id="myTable" class="display table-flexible">
      <thead>
        <tr>
          <th>Request ID</th>
          <th>Client</th>
          <th>Machine</th>
          <th>Status</th>
          <th>Description</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>Client A</td>
          <td data-id="TestComp1">Machine A</td>
          <td>Pending</td>
          <td>
            <button class="btn btn-info btn-sm toggle-btn" data-target="#detailsCollapse_1">Details</button>
          </td>
        </tr>
    
        <tr id="detailsCollapse_1" class="details-row" style="display: none;">
          <td colspan="6">
            <div class="card custom-card-body details-card">
              <div class="detail-item">
                <strong>Message:</strong>
                <p class="tw-font-bold">Request 1</p>
              </div>
            </div>
          </td>
        </tr>
        <tr>
          <td>2</td>
          <td>Client B</td>
          <td data-id="TestComp2">Machine B</td>
          <td>In Progress</td>
          <td>
            <button class="btn btn-info btn-sm toggle-btn" data-target="#detailsCollapse_2">Details</button>
          </td>
        </tr>
    
        <tr id="detailsCollapse_2" class="details-row" style="display: none;">
          <td colspan="6">
            <div class="card custom-card-body details-card">
              <div class="detail-item">
                <strong>Message:</strong>
                <p class="tw-font-bold">Request 2</p>
              </div>
            </div>
          </td>
        </tr>
        <tr>
          <td>3</td>
          <td>Client C</td>
          <td data-id="TestComp3">Machine C</td>
          <td>Completed</td>
          <td>
            <button class="btn btn-info btn-sm toggle-btn" data-target="#detailsCollapse_3">Details</button>
          </td>
        </tr>
    
        <tr id="detailsCollapse_3" class="details-row" style="display: none;">
          <td colspan="6">
            <div class="card custom-card-body details-card">
              <div class="detail-item">
                <strong>Message:</strong>
                <p class="tw-font-bold">Request 3</p>
              </div>
            </div>
          </td>
        </tr>
      </tbody>
    </table>