-
Notifications
You must be signed in to change notification settings - Fork 34
/
ColumnFilterWidgets.js
354 lines (323 loc) · 12.2 KB
/
ColumnFilterWidgets.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/*
* File: ColumnFilterWidgets.js
* Version: 1.0.4
* Description: Controls for filtering based on unique column values in DataTables
* Author: Dylan Kuhn (www.cyberhobo.net)
* Language: Javascript
* License: GPL v2 or BSD 3 point style
* Contact: cyberhobo@cyberhobo.net
*
* Copyright 2011 Dylan Kuhn (except fnGetColumnData by Benedikt Forchhammer), all rights reserved.
*
* This source file is free software, under either the GPL v2 license or a
* BSD style license, available at:
* http://datatables.net/license_gpl2
* http://datatables.net/license_bsd
*
* Quickly hacked to use new DataTables 1.10 API for extracting unique keys from columns by abs@absd.org
*/
(function($) {
if( $.fn.dataTable.fnVersionCheck( '1.10.0' )) {
$.fn.dataTableExt.oApi.fnGetColumnData = function( oSettings, iColumn ) {
return oSettings.oInstance.api().column( iColumn, { search: 'applied' } ).data().sort().unique();
}
} else {
/*
* Function: fnGetColumnData
* Purpose: Return an array of table values from a particular column.
* Returns: array string: 1d data array
* Inputs: object:oSettings - dataTable settings object. This is always the last argument past to the function
* int:iColumn - the id of the column to extract the data from
* bool:bUnique - optional - if set to false duplicated values are not filtered out
* bool:bFiltered - optional - if set to false all the table data is used (not only the filtered)
* bool:bIgnoreEmpty - optional - if set to false empty values are not filtered from the result array
* Author: Benedikt Forchhammer <b.forchhammer /AT\ mind2.de>
*/
$.fn.dataTableExt.oApi.fnGetColumnData = function ( oSettings, iColumn, bUnique, bFiltered, bIgnoreEmpty ) {
// check that we have a column id
if ( typeof iColumn == "undefined" ) return new Array();
// by default we only wany unique data
if ( typeof bUnique == "undefined" ) bUnique = true;
// by default we do want to only look at filtered data
if ( typeof bFiltered == "undefined" ) bFiltered = true;
// by default we do not wany to include empty values
if ( typeof bIgnoreEmpty == "undefined" ) bIgnoreEmpty = true;
// list of rows which we're going to loop through
var aiRows;
// use only filtered rows
if (bFiltered == true) aiRows = oSettings.aiDisplay;
// use all rows
else aiRows = oSettings.aiDisplayMaster; // all row numbers
// set up data array
var asResultData = new Array();
for (var i=0,c=aiRows.length; i<c; i++) {
var iRow = aiRows[i];
var sValue = this.fnGetData(iRow, iColumn);
// ignore empty values?
if (bIgnoreEmpty == true && sValue.length == 0) continue;
// ignore unique values?
else if (bUnique == true && jQuery.inArray(sValue, asResultData) > -1) continue;
// else push the value onto the result data array
else asResultData.push(sValue);
}
return asResultData;
};
}
/**
* Add backslashes to regular expression symbols in a string.
*
* Allows a regular expression to be constructed to search for
* variable text.
*
* @param string sText The text to escape.
* @return string The escaped string.
*/
var fnRegExpEscape = function( sText ) {
return sText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
/**
* Menu-based filter widgets based on distinct column values for a table.
*
* @class ColumnFilterWidgets
* @constructor
* @param {object} oDataTableSettings Settings for the target table.
*/
var ColumnFilterWidgets = function( oDataTableSettings ) {
var me = this;
var sExcludeList = '';
me.$WidgetContainer = $( '<div class="column-filter-widgets"></div>' );
me.$MenuContainer = me.$WidgetContainer;
me.$TermContainer = null;
me.aoWidgets = [];
me.sSeparator = '';
if ( 'oColumnFilterWidgets' in oDataTableSettings.oInit ) {
if ( 'aiExclude' in oDataTableSettings.oInit.oColumnFilterWidgets ) {
sExcludeList = '|' + oDataTableSettings.oInit.oColumnFilterWidgets.aiExclude.join( '|' ) + '|';
}
if ( 'bGroupTerms' in oDataTableSettings.oInit.oColumnFilterWidgets && oDataTableSettings.oInit.oColumnFilterWidgets.bGroupTerms ) {
me.$MenuContainer = $( '<div class="column-filter-widget-menus"></div>' );
me.$TermContainer = $( '<div class="column-filter-widget-selected-terms"></div>' ).hide();
}
}
// Add a widget for each visible and filtered column
$.each( oDataTableSettings.aoColumns, function ( i, oColumn ) {
var $WidgetElem = $( '<div class="column-filter-widget"></div>' );
if ( sExcludeList.indexOf( '|' + i + '|' ) < 0 ) {
me.aoWidgets.push( new ColumnFilterWidget( $WidgetElem, oDataTableSettings, i, me ) );
me.$MenuContainer.append( $WidgetElem );
}
} );
if ( me.$TermContainer ) {
me.$WidgetContainer.append( me.$MenuContainer );
me.$WidgetContainer.append( me.$TermContainer );
}
oDataTableSettings.aoDrawCallback.push( {
name: 'ColumnFilterWidgets',
fn: function() {
$.each( me.aoWidgets, function( i, oWidget ) {
oWidget.fnDraw();
} );
}
} );
return me;
};
/**
* Get the container node of the column filter widgets.
*
* @method
* @return {Node} The container node.
*/
ColumnFilterWidgets.prototype.getContainer = function() {
return this.$WidgetContainer.get( 0 );
}
/**
* A filter widget based on data in a table column.
*
* @class ColumnFilterWidget
* @constructor
* @param {object} $Container The jQuery object that should contain the widget.
* @param {object} oSettings The target table's settings.
* @param {number} i The numeric index of the target table column.
* @param {object} widgets The ColumnFilterWidgets instance the widget is a member of.
*/
var ColumnFilterWidget = function( $Container, oDataTableSettings, i, widgets ) {
var widget = this, sTargetList;
widget.iColumn = i;
widget.oColumn = oDataTableSettings.aoColumns[i];
widget.$Container = $Container;
widget.oDataTable = oDataTableSettings.oInstance;
widget.asFilters = [];
widget.sSeparator = '';
widget.bSort = true;
widget.iMaxSelections = -1;
if ( 'oColumnFilterWidgets' in oDataTableSettings.oInit ) {
if ( 'sSeparator' in oDataTableSettings.oInit.oColumnFilterWidgets ) {
widget.sSeparator = oDataTableSettings.oInit.oColumnFilterWidgets.sSeparator;
}
if ( 'iMaxSelections' in oDataTableSettings.oInit.oColumnFilterWidgets ) {
widget.iMaxSelections = oDataTableSettings.oInit.oColumnFilterWidgets.iMaxSelections;
}
if ( 'aoColumnDefs' in oDataTableSettings.oInit.oColumnFilterWidgets ) {
$.each( oDataTableSettings.oInit.oColumnFilterWidgets.aoColumnDefs, function( iIndex, oColumnDef ) {
var sTargetList = '|' + oColumnDef.aiTargets.join( '|' ) + '|';
if ( sTargetList.indexOf( '|' + i + '|' ) >= 0 ) {
$.each( oColumnDef, function( sDef, oDef ) {
widget[sDef] = oDef;
} );
}
} );
}
}
widget.$Select = $( '<select></select>' ).addClass('widget-' + widget.iColumn).change( function() {
var sSelected = widget.$Select.val(), sText, $TermLink, $SelectedOption;
if ( '' === sSelected ) {
// The blank option is a default, not a filter, and is re-selected after filtering
return;
}
sText = $( '<div>' + sSelected + '</div>' ).text();
$TermLink = $( '<a class="filter-term" href="#"></a>' )
.addClass( 'filter-term-' + sText.toLowerCase().replace( /\W/g, '' ) )
.text( sText )
.click( function() {
// Remove from current filters array
widget.asFilters = $.grep( widget.asFilters, function( sFilter ) {
return sFilter != sSelected;
} );
$TermLink.remove();
if ( widgets.$TermContainer && 0 === widgets.$TermContainer.find( '.filter-term' ).length ) {
widgets.$TermContainer.hide();
}
// Add it back to the select
widget.$Select.append( $( '<option></option>' ).attr( 'value', sSelected ).text( sText ) );
if ( widget.iMaxSelections > 0 && widget.iMaxSelections > widget.asFilters.length ) {
widget.$Select.attr( 'disabled', false );
}
if ( widget.bSort ) {
widget.fnSortOptions();
}
widget.fnFilter();
return false;
} );
widget.asFilters.push( sSelected );
if ( widgets.$TermContainer ) {
widgets.$TermContainer.show();
widgets.$TermContainer.prepend( $TermLink );
} else {
widget.$Select.after( $TermLink );
}
$SelectedOption = widget.$Select.children( 'option:selected' );
widget.$Select.val( '' );
$SelectedOption.remove();
if ( widget.iMaxSelections > 0 && widget.iMaxSelections <= widget.asFilters.length ) {
widget.$Select.attr( 'disabled', true );
}
widget.fnFilter();
} );
widget.$Container.append( widget.$Select );
widget.fnDraw();
};
/**
* Perform filtering on the target column.
*
* @method fnFilter
*/
ColumnFilterWidget.prototype.fnFilter = function() {
var widget = this;
var asEscapedFilters = [];
var sFilterStart, sFilterEnd;
if ( widget.asFilters.length > 0 ) {
// Filters must have RegExp symbols escaped
$.each( widget.asFilters, function( i, sFilter ) {
asEscapedFilters.push( fnRegExpEscape( sFilter ) );
} );
// This regular expression filters by either whole column values or an item in a comma list
sFilterStart = widget.sSeparator ? '(^|' + widget.sSeparator + ')(' : '^(';
sFilterEnd = widget.sSeparator ? ')(' + widget.sSeparator + '|$)' : ')$';
widget.oDataTable.fnFilter( sFilterStart + asEscapedFilters.join('|') + sFilterEnd, widget.iColumn, true, false );
} else {
// Clear any filters for this column
widget.oDataTable.fnFilter( '', widget.iColumn );
}
};
/**
* Sort the widget menu options, using a custom function if one was supplied.
*
* @method fnSortOptions
*/
ColumnFilterWidget.prototype.fnSortOptions = function() {
var widget = this,
$options = widget.$Select.find( 'option' ).slice( 1 );
function fnSort( a, b ) {
var a_text = $( a ).text(),
b_text = $( b ).text();
if ( widget.hasOwnProperty( 'fnSort' ) ) {
return widget.fnSort( a_text, b_text );
} else if ( a_text < b_text ) {
return -1;
} else if ( a_text == b_text ) {
return 0;
} else {
return 1;
}
}
$options.sort( fnSort );
widget.$Select.append( $options );
};
/**
* On each table draw, update filter menu items as needed. This allows any process to
* update the table's column visiblity and menus will still be accurate.
*
* @method fnDraw
*/
ColumnFilterWidget.prototype.fnDraw = function() {
var widget = this;
var oDistinctOptions = {};
var aDistinctOptions = [];
var aData;
if ( widget.asFilters.length === 0 ) {
// Find distinct column values
aData = widget.oDataTable.fnGetColumnData( widget.iColumn );
$.each( aData, function( i, sValue ) {
var asValues = widget.sSeparator ? sValue.split( new RegExp( widget.sSeparator ) ) : [ sValue ];
$.each( asValues, function( j, sOption ) {
if ( !oDistinctOptions.hasOwnProperty( sOption ) ) {
oDistinctOptions[sOption] = true;
aDistinctOptions.push( sOption );
}
} );
} );
// Build the menu
widget.$Select.empty().append( $( '<option></option>' ).attr( 'value', '' ).text( widget.oColumn.sTitle ) );
$.each( aDistinctOptions, function( i, sOption ) {
var sText;
sText = $( '<div>' + sOption + '</div>' ).text();
widget.$Select.append( $( '<option></option>' ).attr( 'value', sOption ).text( sText ) );
} );
if ( aDistinctOptions.length > 1 ) {
if ( widget.bSort ) {
widget.fnSortOptions();
}
// Enable the menu
widget.$Select.attr( 'disabled', false );
} else {
// One option is not a useful menu, disable it
widget.$Select.attr( 'disabled', true );
}
}
};
/*
* Register a new feature with DataTables
*/
if ( typeof $.fn.dataTable === 'function' && typeof $.fn.dataTableExt.fnVersionCheck === 'function' && $.fn.dataTableExt.fnVersionCheck('1.7.0') ) {
$.fn.dataTableExt.aoFeatures.push( {
'fnInit': function( oDTSettings ) {
var oWidgets = new ColumnFilterWidgets( oDTSettings );
return oWidgets.getContainer();
},
'cFeature': 'W',
'sFeature': 'ColumnFilterWidgets'
} );
} else {
throw 'Warning: ColumnFilterWidgets requires DataTables 1.7 or greater - www.datatables.net/download';
}
}(jQuery));