jQuery DataTables: Row selection using checkboxes

Update
January 15, 2016
See jQuery DataTables Checkboxes plug-in that makes it much easier to add checkboxes and multiple row selection to a table powered by jQuery DataTables. It works in client-side and server-side processing modes, supports alternative styling and other extensions.

This is a follow-up article to jQuery DataTables – How to add a checkbox column describing a simple solution to add checkboxes to a table. However proposed solution worked for a table using client-side processing mode only. This article offers universal solution that would work both in client-side and server-side processing modes.

It is loosely based on DataTables example – Row selection but adds extra functionality such as ability to use checkboxes for row selection and other minor improvements.

Example

Example below shows a data table using client-side processing mode where data is received from the server using Ajax. However the same code could be used if data table is switched into server-side processing mode with 'serverSide': true initialization option.

Name Position Office Extn. Start date Salary
Name Position Office Extn. Start date Salary

Data submitted to the server:


//
// Updates "Select all" control in a data table
//
function updateDataTableSelectAllCtrl(table){
   var $table             = table.table().node();
   var $chkbox_all        = $('tbody input[type="checkbox"]', $table);
   var $chkbox_checked    = $('tbody input[type="checkbox"]:checked', $table);
   var chkbox_select_all  = $('thead input[name="select_all"]', $table).get(0);

   // If none of the checkboxes are checked
   if($chkbox_checked.length === 0){
      chkbox_select_all.checked = false;
      if('indeterminate' in chkbox_select_all){
         chkbox_select_all.indeterminate = false;
      }

   // If all of the checkboxes are checked
   } else if ($chkbox_checked.length === $chkbox_all.length){
      chkbox_select_all.checked = true;
      if('indeterminate' in chkbox_select_all){
         chkbox_select_all.indeterminate = false;
      }

   // If some of the checkboxes are checked
   } else {
      chkbox_select_all.checked = true;
      if('indeterminate' in chkbox_select_all){
         chkbox_select_all.indeterminate = true;
      }
   }
}

$(document).ready(function (){
   // Array holding selected row IDs
   var rows_selected = [];
   var table = $('#example').DataTable({
      'ajax': {
         'url': '/lab/articles/jquery-datatables-checkboxes/ids-arrays.txt' 
      },
      'columnDefs': [{
         'targets': 0,
         'searchable': false,
         'orderable': false,
         'width': '1%',
         'className': 'dt-body-center',
         'render': function (data, type, full, meta){
             return '<input type="checkbox">';
         }
      }],
      'order': [[1, 'asc']],
      'rowCallback': function(row, data, dataIndex){
         // Get row ID
         var rowId = data[0];

         // If row ID is in the list of selected row IDs
         if($.inArray(rowId, rows_selected) !== -1){
            $(row).find('input[type="checkbox"]').prop('checked', true);
            $(row).addClass('selected');
         }
      }
   });

   // Handle click on checkbox
   $('#example tbody').on('click', 'input[type="checkbox"]', function(e){
      var $row = $(this).closest('tr');

      // Get row data
      var data = table.row($row).data();

      // Get row ID
      var rowId = data[0];

      // Determine whether row ID is in the list of selected row IDs 
      var index = $.inArray(rowId, rows_selected);

      // If checkbox is checked and row ID is not in list of selected row IDs
      if(this.checked && index === -1){
         rows_selected.push(rowId);

      // Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
      } else if (!this.checked && index !== -1){
         rows_selected.splice(index, 1);
      }

      if(this.checked){
         $row.addClass('selected');
      } else {
         $row.removeClass('selected');
      }

      // Update state of "Select all" control
      updateDataTableSelectAllCtrl(table);

      // Prevent click event from propagating to parent
      e.stopPropagation();
   });

   // Handle click on table cells with checkboxes
   $('#example').on('click', 'tbody td, thead th:first-child', function(e){
      $(this).parent().find('input[type="checkbox"]').trigger('click');
   });

   // Handle click on "Select all" control
   $('thead input[name="select_all"]', table.table().container()).on('click', function(e){
      if(this.checked){
         $('#example tbody input[type="checkbox"]:not(:checked)').trigger('click');
      } else {
         $('#example tbody input[type="checkbox"]:checked').trigger('click');
      }

      // Prevent click event from propagating to parent
      e.stopPropagation();
   });

   // Handle table draw event
   table.on('draw', function(){
      // Update state of "Select all" control
      updateDataTableSelectAllCtrl(table);
   });

   // Handle form submission event 
   $('#frm-example').on('submit', function(e){
      var form = this;
      
      // Iterate over all selected checkboxes
      $.each(rows_selected, function(index, rowId){
         // Create a hidden element 
         $(form).append(
             $('<input>')
                .attr('type', 'hidden')
                .attr('name', 'id[]')
                .val(rowId)
         );
      });
   });

});

In addition to the above code, the following Javascript library files are loaded for use in this example:

//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js
//cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js
table.dataTable.select tbody tr,
table.dataTable thead th:first-child {
  cursor: pointer;
}

The following CSS library files are loaded for use in this example to provide the styling of the table:

//cdn.datatables.net/1.10.7/css/jquery.dataTables.min.css

Edit on jsFiddle

Other examples

Problem

The problem with handling checkboxes varies based on DataTables initialization settings. In server-side processing mode ('serverSide':true) elements <input type="checkbox"> would exist for current page only. Once page is changed, the checked state of the checkboxes would not be preserved. In client-side processing mode, the checked state of checkbox is preserved, but only current page is accessible in DOM, all other pages has to be accessible through DataTables API.

Solution

The solution is to create a global variable (rows_selected in our example) to store a list of selected row IDs and use it to display checkbox state and highlight selected rows.

Highlights

Javascript

  • Storing selected row IDs

       // Array holding selected row IDs
       var rows_selected = [];

    Define array holding selected row IDs.

  • Columns definition

          'columnDefs': [{
             'targets': 0,
             'searchable': false,
             'orderable': false,
             'width': '1%',
             'className': 'dt-body-center',
             'render': function (data, type, full, meta){
                 return '<input type="checkbox">';
             }
          }],
    

    Option columnsDef is used to define appearance and behavior of the first column ('targets': 0).

    Searching and ordering of the column is not needed so this functionality is disabled with searchable and orderable options.

    To center checkbox in the cell, internal DataTables CSS class dt-body-center is used.

    Option render is used to prepare the checkbox control for being displayed in each cell of the first column.

  • Initial sorting order

          'order': [[1, 'asc']],
    

    By default, DataTables sorts table by first column in ascending order. By using order option we select another column to perform initial sort.

  • Row draw callback

          'rowCallback': function(row, data, dataIndex){
             // Get row ID
             var rowId = data[0];
    
             // If row ID is in the list of selected row IDs
             if($.inArray(rowId, rows_selected) !== -1){
                $(row).find('input[type="checkbox"]').prop('checked', true);
                $(row).addClass('selected');
             }
    

    Callback function rowCallback will be called before each row draw and is useful to indicate the state of the checkbox and row selection. We use internal DataTables CSS class selected.

    Important

    Please note that in the example above row ID is stored as first element of the row data array and is being retrieved by using the following code.

             // Get row ID
             var rowId = data[0];

    If you’re using data structure other than described in the article, adjust this and other similar lines accordingly.

  • Form submission

       // Handle form submission event 
       $('#frm-example').on('submit', function(e){
          var form = this;
          
          // Iterate over all selected checkboxes
          $.each(rows_selected, function(index, rowId){
             // Create a hidden element 
             $(form).append(
                 $('<input>')
                    .attr('type', 'hidden')
                    .attr('name', 'id[]')
                    .val(rowId)
             );
          });
       });
    

    When table is enhanced by jQuery DataTables plug-in, only visible elements exist in DOM. That is why by default form submits checkboxes from current page only.

    To submit selected checkboxes from all pages we need to iterate over rows_selected array and add a hidden <input> element to the form with some name (id[] in our example) and row ID as a value. This will allow all the data to be submitted.

    For more information on submitting the form elements in a table powered by jQuery DataTables, please see jQuery DataTables: How to submit all pages form data.

HTML

<table id="example" class="display select" cellspacing="0" width="100%">

Additional CSS class select is used to change cursor when hovering over table rows for specific tables only.

CSS

table.dataTable.select tbody tr,
table.dataTable thead th:first-child {
  cursor: pointer;
}

Display cursor in the form of a hand for table rows and first cell in the table heading where “Select all” control is located.

You May Also Like

Comments

      1. Thanks for your reply! I am afraid I don’t have a quick demo (as it’s in my admin side of the website) but basically I am using this bit:

        https://www.datatables.net/examples/server_side/simple.html

        and I’ve tried your implementation however when I click on page 2 or other pages all the rows get selected.

        I appreciate you’re using Ajax sourced data in your demo but it’s loaded once and then split pages while in server-side mode you load data on every page if you know what I mean.

        This is a snipped from my code: http://pastie.org/10322051

        Do you have an idea?

  1. Thank you very much for this solution. It worked. But for some reason this is not working when I add ‘Select All’ on the top column, I appreciate if you could help me. Again thank you very much.

      1. Thank you Michael for you response… Nice to hear that you will add a code for select all.. Again thank you very much in advance.

  2. Hi Michael,
    if you use the sScrollX property of the DataTables, the header is duplicated by default and the second header is hidden.
    In this scenario, the checkbox select_all doesn’t work because the click event is attached on the hidden checkbox.

    So, to make it work all again, in the js add to the selector input[name="select_all"] the :first, like this:
    input[name="select_all"]:first

    This tells that the event handler is applied only on the first occurrence of the checkbox.

      1. I am not getting checkbox for every row. I placed input type checkbox for every row then select all check box is not working. If possible please provide me the code in zip file.

      1. Hi Michael,

        The fiddle (http://jsfiddle.net/gyrocode/5bmx7ejw/) you posted does not work the same as the sample in your article.

        In this fiddle when you click the checkbox in the header while on the first page, ALL items in ALL pages are checked, then when you click it again, only the current page is affected.

        I got to your article while looking for a working solution for this problem while using AngularJS together with DataTables and angular-datatables – the ones I was able to find do not work as advertised.

        Any chance of you creating a version for use with angular-datatables? 🙂

  3. Hi Michael,
    How can I achieve the column width as yours. The width of my checkbox column, as well as other column with few words are quite large. Hoping to hear from u. Thanks!

  4. How can I download the source?
    I tried implemented this for my app but the submit is not showing items checked and the click on individual checkbox not working on opera.

    1. Yes, that was intentional, because I was trying to come up with universal solution. For client-side processing mode it’s possible to select checkboxes on all pages with some minor modifications of the code in the article. However the code for server-side processing mode would be completely different. Maybe I will update the article to cover that as well.

  5. Hi Michael,

    Thanks for your very detailed tutorial, it really helped me setup data tables.

    I do have one issue though.
    When submitting the form, I want to append some info in addition to the hidden field.

    However, it seems the index is not properly referenced in the loop:

    $.each rows_selected, (index, rowId) ->
      data = linksTable.row(rowId).data()
      console.log(data[1])
    

    The rowId shows the proper selected rows but the index isn’t.
    I don’t really get what I’m doing wrong (the rest of the code is what you did)

    Thanks!
    – Vincent

    1. You’re trying to retrieve data for the whole row with row().data() API method and then access second column with data[1]. I don’t understand the purpose of your code?

      If you want to access row data later with linksTable.row(rowId).data(), store row index instead of ID, for example var rowId = $('#example').DataTable().row(row).index();.

      Alternatively, you can use filter() API method to locate row by value of one of the columns. For example:

      var data = linksTable.column(0).data().filter(function(value, index){ return value === rowId ? true : false; });
      
  6. Hi Michael,

    Really appreciate your super job with DataTable.

    I have 2 questions need you help.
    1. In ajax option (server side processing case), can we replace the url by calling the custom function which returns set of data as a promise? I am using Angular factory to write a service to get the data by passing paging number and some filter parameters. How can I apply the service call in this case?
    2. Can we make the table become responsive? So that in small device I’d like to hide couple of columns, and showing the details info when clicking the plus(+) icon instead.

    If you have time, could you please help me with a fiddle for the demo? Thanks a lot in advance.

  7. I wanted to implement this library in my project, but I make a mistake I tell my JSON has this structure

    [
        {
            "cycle" : 0000,
            " bouquet " , " 0 "
            "group" : "0 "
            " function ": " 0 "
            "portfolio" : " 1A2B3C4D5G "
        }
     {
            "cycle" : 0001,
            " bouquet " , "1"
            "group" : "2"
            " function ": " 3"
            "portfolio" : " 4A5B6C7D8G "
        }
    ]
    

    SEND ME A MISTAKE BUT NOT UNDERSTAND WHAT CAN BE HAPPENING THE ERROR IS Uncaught TypeError : Can not read property ' length' of undefined or your defeto also sends TypeError : f is undefined.

    How could I resolve this error?

      1. sorry I went brackets to json is so

            {
                "cycle" : 0000,
                " bouquet " , " 0 "
                "group" : "0 "
                " function ": " 0 "
                "portfolio" : " 14HF6MJD8 "
            }
        
  8. Dear Michael,
    Thanks a lot for the great job and sharing your knowledge !!
    I’ve one very simple question … would it be possible to have a multi row selector ?
    Example, I would like to have all the Checkboxes checked from Row 3 to Row 7 (by doing a Multi Select Style: OS and Items: row)
    Best regards,
    David

  9. Hi, I have copied your code and trying to run with .json file in ajax call, i am not getting data columns, only checkbox column is coming. Ajax response is coming but not rendering on UI.

    1. In the example the form is not actually submitted. Submission is prevented for demonstration purposes by e.preventDefault();. In production, you need to specify a path to your actual script that will be processing the request.

  10. Thank you for this great tutorial.

    I have applied your solution in the server processing mode, but I have an issue when I paginate, the previous selected ones become deselected.

    Thoughts?

    Alex

  11. Thank you for this tutorial. Concept well explained. I am not using JQuery DataTables presently but I am using the idea here to implement my solution. I have tested your code, the plugin is awesome. Will use it in another solution.

Leave a Reply

(optional)

This site uses Akismet to reduce spam. Learn how your comment data is processed.