Alphabet input search - Part III
In the final part of this series of posts that explores some of the search options and APIs for extending DataTables I will detail how to convert the alphabet search that was created in the previous two parts of this series into a feature plug-in for DataTables with API extension methods that can be used to control the new feature that we have created.
The end result is a customisable extension for DataTables that can easily be reused for one or more tables on any web-page using the simple initialisation:
var table = $('#example').DataTable( {
layout: {
top1: 'alphabetSearch'
}
} );
Or if you are using legacy DataTables 1.x:
var table = $('#example').DataTable( {
dom: 'Alfrtip'
} );
Note the use of dom
to specify the table control elements that are used - the A
is our new plug-in. That is all there is to using it!
Before we dive into the code, the end result is now available on the DataTables CDN (Javascript / CSS) and a demonstration of the resulting table is shown below (it is functionality the same as that which was created in part II).
Name | Position | Office | Age | Start date | Salary |
---|---|---|---|---|---|
Tiger Nixon | System Architect | Edinburgh | 61 | 2011-04-25 | $320,800 |
Garrett Winters | Accountant | Tokyo | 63 | 2011-07-25 | $170,750 |
Ashton Cox | Junior Technical Author | San Francisco | 66 | 2009-01-12 | $86,000 |
Cedric Kelly | Senior Javascript Developer | Edinburgh | 22 | 2012-03-29 | $433,060 |
Airi Satou | Accountant | Tokyo | 33 | 2008-11-28 | $162,700 |
Brielle Williamson | Integration Specialist | New York | 61 | 2012-12-02 | $372,000 |
Herrod Chandler | Sales Assistant | San Francisco | 59 | 2012-08-06 | $137,500 |
Rhona Davidson | Integration Specialist | Tokyo | 55 | 2010-10-14 | $327,900 |
Colleen Hurst | Javascript Developer | San Francisco | 39 | 2009-09-15 | $205,500 |
Sonya Frost | Software Engineer | Edinburgh | 23 | 2008-12-13 | $103,600 |
Jena Gaines | Office Manager | London | 30 | 2008-12-19 | $90,560 |
Quinn Flynn | Support Lead | Edinburgh | 22 | 2013-03-03 | $342,000 |
Charde Marshall | Regional Director | San Francisco | 36 | 2008-10-16 | $470,600 |
Haley Kennedy | Senior Marketing Designer | London | 43 | 2012-12-18 | $313,500 |
Tatyana Fitzpatrick | Regional Director | London | 19 | 2010-03-17 | $385,750 |
Michael Silva | Marketing Designer | London | 66 | 2012-11-27 | $198,500 |
Paul Byrd | Chief Financial Officer (CFO) | New York | 64 | 2010-06-09 | $725,000 |
Gloria Little | Systems Administrator | New York | 59 | 2009-04-10 | $237,500 |
Bradley Greer | Software Engineer | London | 41 | 2012-10-13 | $132,000 |
Dai Rios | Personnel Lead | Edinburgh | 35 | 2012-09-26 | $217,500 |
Jenette Caldwell | Development Lead | New York | 30 | 2011-09-03 | $345,000 |
Yuri Berry | Chief Marketing Officer (CMO) | New York | 40 | 2009-06-25 | $675,000 |
Caesar Vance | Pre-Sales Support | New York | 21 | 2011-12-12 | $106,450 |
Doris Wilder | Sales Assistant | Sydney | 23 | 2010-09-20 | $85,600 |
Angelica Ramos | Chief Executive Officer (CEO) | London | 47 | 2009-10-09 | $1,200,000 |
Gavin Joyce | Developer | Edinburgh | 42 | 2010-12-22 | $92,575 |
Jennifer Chang | Regional Director | Singapore | 28 | 2010-11-14 | $357,650 |
Brenden Wagner | Software Engineer | San Francisco | 28 | 2011-06-07 | $206,850 |
Fiona Green | Chief Operating Officer (COO) | San Francisco | 48 | 2010-03-11 | $850,000 |
Shou Itou | Regional Marketing | Tokyo | 20 | 2011-08-14 | $163,000 |
Michelle House | Integration Specialist | Sydney | 37 | 2011-06-02 | $95,400 |
Suki Burks | Developer | London | 53 | 2009-10-22 | $114,500 |
Prescott Bartlett | Technical Author | London | 27 | 2011-05-07 | $145,000 |
Gavin Cortez | Team Leader | San Francisco | 22 | 2008-10-26 | $235,500 |
Martena Mccray | Post-Sales support | Edinburgh | 46 | 2011-03-09 | $324,050 |
Unity Butler | Marketing Designer | San Francisco | 47 | 2009-12-09 | $85,675 |
Howard Hatfield | Office Manager | San Francisco | 51 | 2008-12-16 | $164,500 |
Hope Fuentes | Secretary | San Francisco | 41 | 2010-02-12 | $109,850 |
Vivian Harrell | Financial Controller | San Francisco | 62 | 2009-02-14 | $452,500 |
Timothy Mooney | Office Manager | London | 37 | 2008-12-11 | $136,200 |
Jackson Bradshaw | Director | New York | 65 | 2008-09-26 | $645,750 |
Olivia Liang | Support Engineer | Singapore | 64 | 2011-02-03 | $234,500 |
Bruno Nash | Software Engineer | London | 38 | 2011-05-03 | $163,500 |
Sakura Yamamoto | Support Engineer | Tokyo | 37 | 2009-08-19 | $139,575 |
Thor Walton | Developer | New York | 61 | 2013-08-11 | $98,540 |
Finn Camacho | Support Engineer | San Francisco | 47 | 2009-07-07 | $87,500 |
Serge Baldwin | Data Coordinator | Singapore | 64 | 2012-04-09 | $138,575 |
Zenaida Frank | Software Engineer | New York | 63 | 2010-01-04 | $125,250 |
Zorita Serrano | Software Engineer | San Francisco | 56 | 2012-06-01 | $115,000 |
Jennifer Acosta | Junior Javascript Developer | Edinburgh | 43 | 2013-02-01 | $75,650 |
Cara Stevens | Sales Assistant | New York | 46 | 2011-12-06 | $145,600 |
Hermione Butler | Regional Director | London | 47 | 2011-03-21 | $356,250 |
Lael Greer | Systems Administrator | London | 21 | 2009-02-27 | $103,500 |
Jonas Alexander | Developer | San Francisco | 30 | 2010-07-14 | $86,500 |
Shad Decker | Regional Director | Edinburgh | 51 | 2008-11-13 | $183,000 |
Michael Bruce | Javascript Developer | Singapore | 29 | 2011-06-27 | $183,000 |
Donna Snider | Customer Support | New York | 27 | 2011-01-25 | $112,000 |
Name | Position | Office | Age | Start date | Salary |
It is worth noting that I will assume that you are familiar with the code developed in part I and part II of this series. As the majority of the code required for this feature plug-in has already been developed in those posts I will be shortening those code blocks. This post is simply about rearranging the code into a reusable plug-in for DataTables.
Feature plug-ins
The feature plug-ins section of the manual describes in detail how to create a feature plug-in for DataTables. The basics are that we need to register a new feature plug-in that can be used with dom
through the $.fn.dataTable.ext.feature
array. We provide the letter to be registered and a callback function that is executed when the plug-in is to be used for a DataTable - this callback must return the node that is to be inserted into the document for the control, in this case the alphabet search bar.
$.fn.dataTable.ext.feature.push( {
fnInit: function ( settings ) {
var search = new $.fn.dataTable.AlphabetSearch( settings );
return search.node();
},
cFeature: 'A'
} );
In the above code we register the character A
and create a new instance of $.fn.dataTable.AlphabetSearch
, returning the node from its own API node()
method.
$.fn.dataTable.AlphabetSearch
is new in our code, but it is simply an encapsulation of the code developed previously to create the alphabet search input nodes:
$.fn.dataTable.AlphabetSearch = function ( context ) {
var table = new $.fn.dataTable.Api( context );
var alphabet = $('<div class="alphabet"/>');
// Bin the data and create the alphabet search input element
...
// API method to get the alphabet container node
this.node = function () {
return alphabet;
};
};
Note the addition of the node()
API method that can be used to get the container node, as is done in the registration of the DataTables feature above.
Alternative initialisation
It is worth pointing out that the alphabet search can also be initialised directly use new $.fn.dataTable.AlphabetSearch()
rather than using the dom
option. This can improve flexibility and control over where you insert the alphabet bar if you so wish:
var table = $('#myTable').DataTable();
var search = new $.fn.dataTable.AlphabetSearch();
$( search.node() ).appendTo( ... ); // insert into document
Search plug-in update
Previously the search plug-in used the variable _alphabetSearch
to determine what letter to search for, however that was not isolated to a single table - it would apply to all tables on a page. That is not suitable for a reusable component so we need to use a different method to store what letter to search for.
For this we can use the DataTables settings object which is globally unique for each table on the page (the settings object is referred to as the "context" in much of the API documentation). By simply attaching our parameter to the settings object we can always access it for that table. Consider the following update to the search plug-in that we developed way back in part I:
$.fn.dataTable.ext.search.push( function ( context, searchData ) {
// Ensure that there is a search applied to this table before running it
if ( ! context.alphabetSearch ) {
return true;
}
if ( searchData[0].charAt(0) === context.alphabetSearch ) {
return true;
}
return false;
} );
The change is simply to use context.alphabetSearch
rather than _alphabetSearch
. Now to trigger a search we just need to set that parameter, which is where the API plug-ins come in.
API plug-ins
An API plug-in can extend the default set of API methods that DataTables presents, and in this case we wish to add the ability to set the alphabetSearch
parameter for each table. We can do this very easily using the following:
$.fn.dataTable.Api.register( 'alphabetSearch()', function ( searchTerm ) {
this.iterator( 'table', function ( context ) {
context.alphabetSearch = searchTerm;
} );
return this;
} );
Above the iterator()
method is used to loop over each table in the API instance's context and set the search term. Although our feature plug-in doesn't make use of the multi-table aspect of the DataTables API, it would be quite possible to use $('table.dataTable').DataTable().alphabetSearch( 'A' );
to search for A
in all tables on a page due to the use of iterator()
above.
For completeness lets also create a method that can be used to re-bin the data - updating the counts that are available for each letter as shown on mouse over:
$.fn.dataTable.Api.register( 'alphabetSearch.recalc()', function ( searchTerm ) {
this.iterator( 'table', function ( context ) {
draw(
new $.fn.dataTable.Api( context ),
$('div.alphabet', this.table().container()) );
);
} );
return this;
} );
This uses the draw()
method that is simply a function that encapsulates the draw code developed previously. It is passed in an API instance for the current table and the alphabet search DOM node for the table in question.
Final code
We've reached the end of this foray into the world of DataTables extensions and I hope you've found this to be a useful introduction into how to create reusable plug-ins that customise DataTables to your needs.
As always, feedback is very welcome on this and any other aspect of DataTables!
The final code developed in this series of posts is shown below and is available on the DataTables CDN (Javascript / CSS).
(function(){
// Search function
$.fn.dataTable.Api.register( 'alphabetSearch()', function ( searchTerm ) {
this.iterator( 'table', function ( context ) {
context.alphabetSearch = searchTerm;
} );
return this;
} );
// Recalculate the alphabet display for updated data
$.fn.dataTable.Api.register( 'alphabetSearch.recalc()', function ( searchTerm ) {
this.iterator( 'table', function ( context ) {
draw(
new $.fn.dataTable.Api( context ),
$('div.alphabet', this.table().container())
);
} );
return this;
} );
// Search plug-in
$.fn.dataTable.ext.search.push( function ( context, searchData ) {
// Ensure that there is a search applied to this table before running it
if ( ! context.alphabetSearch ) {
return true;
}
if ( searchData[0].charAt(0) === context.alphabetSearch ) {
return true;
}
return false;
} );
// Private support methods
function bin ( data ) {
var letter, bins = {};
for ( var i=0, ien=data.length ; i<ien ; i++ ) {
letter = data[i].charAt(0).toUpperCase();
if ( bins[letter] ) {
bins[letter]++;
}
else {
bins[letter] = 1;
}
}
return bins;
}
function draw ( table, alphabet )
{
alphabet.empty();
alphabet.append( 'Search: ' );
var columnData = table.column(0).data();
var bins = bin( columnData );
$('<span class="clear active"/>')
.data( 'letter', '' )
.data( 'match-count', columnData.length )
.html( 'None' )
.appendTo( alphabet );
for ( var i=0 ; i<26 ; i++ ) {
var letter = String.fromCharCode( 65 + i );
$('<span/>')
.data( 'letter', letter )
.data( 'match-count', bins[letter] || 0 )
.addClass( ! bins[letter] ? 'empty' : '' )
.html( letter )
.appendTo( alphabet );
}
$('<div class="alphabetInfo"></div>')
.appendTo( alphabet );
}
$.fn.dataTable.AlphabetSearch = function ( context ) {
var table = new $.fn.dataTable.Api( context );
var alphabet = $('<div class="alphabet"/>');
draw( table, alphabet );
// Trigger a search
alphabet.on( 'click', 'span', function () {
alphabet.find( '.active' ).removeClass( 'active' );
$(this).addClass( 'active' );
table
.alphabetSearch( $(this).data('letter') )
.draw();
} );
// Mouse events to show helper information
alphabet
.on( 'mouseenter', 'span', function () {
alphabet
.find('div.alphabetInfo')
.css( {
opacity: 1,
left: $(this).position().left,
width: $(this).width()
} )
.html( $(this).data('match-count') );
} )
.on( 'mouseleave', 'span', function () {
alphabet
.find('div.alphabetInfo')
.css('opacity', 0);
} );
// API method to get the alphabet container node
this.node = function () {
return alphabet;
};
};
$.fn.DataTable.AlphabetSearch = $.fn.dataTable.AlphabetSearch;
// Register a search plug-in
$.fn.dataTable.ext.feature.push( {
fnInit: function ( settings ) {
var search = new $.fn.dataTable.AlphabetSearch( settings );
return search.node();
},
cFeature: 'A'
} );
}());