Bootstrap 3 DataTables Paging Modifications

Update Sept. 18, 2013: I have updated the JavaScript to include the following updates:

  • Adjusted the "sDom" column widths for the record selection/search filter fields
  • The "records per page" will wrap to a new line on extra small devices
  • Fixed an issue where filtered searches were not updating the pagination properly

Thanks to everyone who has provided feedback on these issues!

The following is the JavaScript and CSS I used to create a functional and responsive jQuery DataTable within Bootstrap 3. This code is based on the (DataTables Twitter Bootstrap 2)[http://datatables.net/blog/TwitterBootstrap2) code, except modified to work with Bootstrap 3's CSS and form structures.

JavaScript code:

bootstrap-dataTables-paging.js

/* Set the defaults for DataTables initialisation */
$.extend(true, $.fn.dataTable.defaults, {
  "sDom": "<'row'<'col-xs-5 col-sm-6'l><'col-xs-7 col-sm-6 text-right'f>r>t<'row'<'col-xs-3 col-sm-4 col-md-5'i><'col-xs-9 col-sm-8 col-md-7 text-right'p>>",
  "sPaginationType": "bootstrap",
  "oLanguage": {
    "sLengthMenu": "_MENU_ records per page"
  },
  "fnInitComplete": function (oSettings, json) {
    var currentId = $(this).attr('id');
    console.log(currentId);
    if (currentId) {

      var thisLength = $('#' + currentId + '_length');
      var thisLengthLabel = $('#' + currentId + '_length label');
      var thisLengthSelect = $('#' + currentId + '_length label select');

      var thisFilter = $('#' + currentId + '_filter');
      var thisFilterLabel = $('#' + currentId + '_filter label');
      var thisFilterInput = $('#' + currentId + '_filter label input');

      // Re-arrange the records selection for a form-horizontal layout
      thisLength.addClass('form-group');
      thisLengthLabel.addClass('control-label col-xs-12 col-sm-7 col-md-6').attr('for', currentId + '_length_select').css('text-align', 'left');
      thisLengthSelect.addClass('form-control input-sm').attr('id', currentId + '_length_select');
      thisLengthSelect.prependTo(thisLength).wrap('<div class="col-xs-12 col-sm-5 col-md-6" />');
      // Re-arrange the search input for a form-horizontal layout
      thisFilter.addClass('form-group');
      thisFilterLabel.addClass('control-label col-xs-4 col-sm-3 col-md-3').attr('for', currentId + '_filter_input');
      thisFilterInput.addClass('form-control input-sm').attr('id', currentId + '_filter_input');
      thisFilterInput.appendTo(thisFilter).wrap('<div class="col-xs-8 col-sm-9 col-md-9 " />');
    }
  }
});

$.extend($.fn.dataTableExt.oStdClasses, {
  "sWrapper": "dataTables_wrapper form-horizontal"
});

/* API method to get paging information */
$.fn.dataTableExt.oApi.fnPagingInfo = function (oSettings) {
  return {
    "iStart": oSettings._iDisplayStart,
    "iEnd": oSettings.fnDisplayEnd(),
    "iLength": oSettings._iDisplayLength,
    "iTotal": oSettings.fnRecordsTotal(),
    "iFilteredTotal": oSettings.fnRecordsDisplay(),
    "iPage": oSettings._iDisplayLength === -1 ? 0 : Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength),
    "iTotalPages": oSettings._iDisplayLength === -1 ? 0 : Math.ceil(oSettings.fnRecordsDisplay() / oSettings._iDisplayLength)
  };
};


/* Bootstrap style pagination control */
$.extend($.fn.dataTableExt.oPagination, {
  "bootstrap": {
    "fnInit": function (oSettings, nPaging, fnDraw) {
      var oLang = oSettings.oLanguage.oPaginate;
      var fnClickHandler = function (e) {
        e.preventDefault();
        if (oSettings.oApi._fnPageChange(oSettings, e.data.action)) {
          fnDraw(oSettings);
        }
      };

      $(nPaging).append(
       '<ul class="pagination">' +
        '<li class="first disabled"><a href="#" title="' + oLang.sFirst + '"><span class="glyphicon glyphicon-fast-backward"></span></a></li>' +
        '<li class="prev disabled"><a href="#" title="' + oLang.sPrevious + '"><span class="glyphicon glyphicon-chevron-left"></span></a></li>' +
        '<li class="next disabled"><a href="#" title="' + oLang.sNext + '"><span class="glyphicon glyphicon-chevron-right"></span></a></li>' +
        '<li class="last disabled"><a href="#" title="' + oLang.sLast + '"><span class="glyphicon glyphicon-fast-forward"></span></a></li>' +
       '</ul>'
      );
      var els = $('a', nPaging);
      $(els[0]).bind('click.DT', { action: "first" }, fnClickHandler);
      $(els[1]).bind('click.DT', { action: "previous" }, fnClickHandler);
      $(els[2]).bind('click.DT', { action: "next" }, fnClickHandler);
      $(els[3]).bind('click.DT', { action: "last" }, fnClickHandler);
    },

    "fnUpdate": function (oSettings, fnDraw) {
      var iListLength = 5;
      var oPaging = oSettings.oInstance.fnPagingInfo();
      var an = oSettings.aanFeatures.p;
      var i, j, sClass, iStart, iEnd, iHalf = Math.floor(iListLength / 2);

        if (oPaging.iTotalPages < iListLength) { iStart = 1; iEnd = oPaging.iTotalPages; } else if (oPaging.iPage <= iHalf) { iStart = 1; iEnd = iListLength; } else if (oPaging.iPage >= oPaging.iTotalPages - iHalf) { iStart = oPaging.iTotalPages - iListLength + 1; iEnd = oPaging.iTotalPages; } else { iStart = oPaging.iPage - iHalf + 1; iEnd = iStart + iListLength - 1; }

        for (i = 0, iLen = an.length ; i < iLen ; i++) {
          // Remove the middle elements
          $('li:gt(1)', an[i]).filter(':not(.next,.last)').remove();

          // Add the new list items and their event handlers
          for (j = iStart; j <= iEnd; j++) { sClass = j == oPaging.iPage + 1 ? 'class="active"' : ""; $("<li " + sClass + '><a href="#">' + j + "</a></li>").insertBefore($(".next,.last", an[i])[0]).bind("click", function (a) { a.preventDefault(); oSettings._iDisplayStart = (parseInt($("a", this).text(), 10) - 1) * oPaging.iLength; fnDraw(oSettings) }) }

          // Add / remove disabled classes from the static elements
          if (oPaging.iPage === 0) $(".first,.prev", an[i]).addClass("disabled"); else $(".first,.prev", an[i]).removeClass("disabled")

          if (oPaging.iPage === oPaging.iTotalPages - 1 || oPaging.iTotalPages === 0) $(".next,.last", an[i]).addClass("disabled"); else $(".next,.last", an[i]).removeClass("disabled")
        }
    }
  }
});


/*
* TableTools Bootstrap compatibility
* Required TableTools 2.1+
*/
if ($.fn.DataTable.TableTools) {  
  // Set the classes that TableTools uses to something suitable for Bootstrap
  // Set the classes that TableTools uses to something suitable for Bootstrap
  $.extend(true, $.fn.DataTable.TableTools.classes, {
    "container": "DTTT btn-group",
    "buttons": {
      "normal": "btn btn-default",
      "disabled": "disabled"
    },
    "collection": {
      "container": "DTTT_dropdown dropdown-menu",
      "buttons": {
        "normal": "",
        "disabled": "disabled"
      }
    },
    "print": {
      "info": "DTTT_print_info modal"
    },
    "select": {
      "row": "active"
    }
  });

  // Have the collection use a bootstrap compatible dropdown
  $.extend(true, $.fn.DataTable.TableTools.DEFAULTS.oTags, {
    "collection": {
      "container": "ul",
      "button": "li",
      "liner": "a"
    }
  });
}

// Moved to the bottom.
if ($.fn.DataTable.defaults) {  
  $.extend($.fn.dataTable.defaults, {
    'bAutoWidth': false,
    'aLengthMenu': [[5, 10, 25, 50, 100], [5, 10, 25, 50, 100]],
    'iDisplayLength': 10,
    "bFilter": true
  });
}

CSS code:

bootstrap-dataTables.css

Note: You may have to edit the path to any image files depending on the location of the CSS file.

div.dataTables_length label  
{
  vertical-align: middle;
}

div.dataTables_length select  
{
  min-width: 75px;
}

div.dataTables_filter label  
{
  vertical-align: middle;
}

div.dataTables_filter  
{
  margin-bottom: 5px !important;
}

div.dataTables_length  
{
  margin-bottom: 5px !important;
}


div.dataTables_info  
{
  padding-top: 8px;
}

div.dataTables_paginate  
{
  float: right;
  margin: 0;
}

  div.dataTables_paginate ul.pagination
  {
    margin: 2px;
  }

table.table  
{
  clear: both;
  margin-top: 6px !important;
  margin-bottom: 6px !important;
  max-width: none !important;
}

  table.table thead .sorting,
  table.table thead .sorting_asc,
  table.table thead .sorting_desc,
  table.table thead .sorting_asc_disabled,
  table.table thead .sorting_desc_disabled
  {
    cursor: pointer;
  }

  table.table thead .sorting
  {
    background: url('./DataTables-1.9.4/media/images/sort_both.png') no-repeat center right;
  }

  table.table thead .sorting_asc
  {
    background: url('./DataTables-1.9.4/media/images/sort_asc.png') no-repeat center right;
  }

  table.table thead .sorting_desc
  {
    background: url('./DataTables-1.9.4/media/images/sort_desc.png') no-repeat center right;
  }

  table.table thead .sorting_asc_disabled
  {
    background: url('./DataTables-1.9.4/media/images/sort_asc_disabled.png') no-repeat center right;
  }

  table.table thead .sorting_desc_disabled
  {
    background: url('./DataTables-1.9.4/media/images/sort_desc_disabled.png') no-repeat center right;
  }

  table.table thead .sorting,
  table.table thead .sorting_asc,
  table.table thead .sorting_desc,
  table.table thead .sorting_asc_disabled,
  table.table thead .sorting_desc_disabled
  {
    padding-right: 20px;
  }


table.dataTable th:active  
{
  outline: none;
}

/* Scrolling */
div.dataTables_scrollHead table  
{
  margin-bottom: 0 !important;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

  div.dataTables_scrollHead table thead tr:last-child th:first-child,
  div.dataTables_scrollHead table thead tr:last-child td:first-child
  {
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
  }

div.dataTables_scrollBody table  
{
  border-top: none;
  margin-bottom: 0 !important;
}

div.dataTables_scrollBody tbody tr:first-child th,  
div.dataTables_scrollBody tbody tr:first-child td  
{
  border-top: none;
}

div.dataTables_scrollFoot table  
{
  border-top: none;
}




/*
 * TableTools styles
 */
.table tbody tr.active td,
.table tbody tr.active th
{
  background-color: #08C;
  color: white;
}

.table tbody tr.active:hover td,
.table tbody tr.active:hover th
{
  background-color: #0075b0 !important;
}

.table-striped tbody tr.active:nth-child(odd) td,
.table-striped tbody tr.active:nth-child(odd) th
{
  background-color: #017ebc;
}

table.DTTT_selectable tbody tr  
{
  cursor: pointer;
}

div.DTTT .btn  
{
  color: #333 !important;
  font-size: 12px;
}

  div.DTTT .btn:hover
  {
    text-decoration: none !important;
  }

ul.DTTT_dropdown.dropdown-menu  
{
  z-index: 2003;
}


  ul.DTTT_dropdown.dropdown-menu a
  {
    color: #333 !important; /* needed only when demo_page.css is included */
  }

  ul.DTTT_dropdown.dropdown-menu li
  {
    position: relative;
  }

    ul.DTTT_dropdown.dropdown-menu li:hover a
    {
      background-color: #0088cc;
      color: white !important;
    }

/* TableTools information display */
div.DTTT_print_info.modal  
{
  height: 150px;
  margin-top: -75px;
  text-align: center;
}

div.DTTT_print_info h6  
{
  font-weight: normal;
  font-size: 28px;
  line-height: 28px;
  margin: 1em;
}

div.DTTT_print_info p  
{
  font-size: 14px;
  line-height: 20px;
}



/*
 * FixedColumns styles
 */
div.DTFC_LeftHeadWrapper table,  
div.DTFC_LeftFootWrapper table,  
div.DTFC_RightHeadWrapper table,  
div.DTFC_RightFootWrapper table,  
table.DTFC_Cloned tr.even  
{
  background-color: white;
}

div.DTFC_RightHeadWrapper table,  
div.DTFC_LeftHeadWrapper table  
{
  margin-bottom: 0 !important;
  border-top-right-radius: 0 !important;
  border-bottom-left-radius: 0 !important;
  border-bottom-right-radius: 0 !important;
}

  div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child,
  div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child,
  div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child,
  div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child
  {
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
  }

div.DTFC_RightBodyWrapper table,  
div.DTFC_LeftBodyWrapper table  
{
  border-top: none;
  margin-bottom: 0 !important;
}

div.DTFC_RightBodyWrapper tbody tr:first-child th,  
div.DTFC_RightBodyWrapper tbody tr:first-child td,  
div.DTFC_LeftBodyWrapper tbody tr:first-child th,  
div.DTFC_LeftBodyWrapper tbody tr:first-child td  
{
  border-top: none;
}

div.DTFC_RightFootWrapper table,  
div.DTFC_LeftFootWrapper table  
{
  border-top: none;
}