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