0
|
1
|
|
2
|
|
3 /**
|
|
4 * Generate the node required for filtering text
|
|
5 * @returns {node} Filter control element
|
|
6 * @param {object} oSettings dataTables settings object
|
|
7 * @memberof DataTable#oApi
|
|
8 */
|
|
9 function _fnFeatureHtmlFilter ( oSettings )
|
|
10 {
|
|
11 var oPreviousSearch = oSettings.oPreviousSearch;
|
|
12
|
|
13 var sSearchStr = oSettings.oLanguage.sSearch;
|
|
14 sSearchStr = (sSearchStr.indexOf('_INPUT_') !== -1) ?
|
|
15 sSearchStr.replace('_INPUT_', '<input type="text" />') :
|
|
16 sSearchStr==="" ? '<input type="text" />' : sSearchStr+' <input type="text" />';
|
|
17
|
|
18 var nFilter = document.createElement( 'div' );
|
|
19 nFilter.className = oSettings.oClasses.sFilter;
|
|
20 nFilter.innerHTML = '<label>'+sSearchStr+'</label>';
|
|
21 if ( !oSettings.aanFeatures.f )
|
|
22 {
|
|
23 nFilter.id = oSettings.sTableId+'_filter';
|
|
24 }
|
|
25
|
|
26 var jqFilter = $('input[type="text"]', nFilter);
|
|
27
|
|
28 // Store a reference to the input element, so other input elements could be
|
|
29 // added to the filter wrapper if needed (submit button for example)
|
|
30 nFilter._DT_Input = jqFilter[0];
|
|
31
|
|
32 jqFilter.val( oPreviousSearch.sSearch.replace('"','"') );
|
|
33 jqFilter.bind( 'keyup.DT', function(e) {
|
|
34 /* Update all other filter input elements for the new display */
|
|
35 var n = oSettings.aanFeatures.f;
|
|
36 var val = this.value==="" ? "" : this.value; // mental IE8 fix :-(
|
|
37
|
|
38 for ( var i=0, iLen=n.length ; i<iLen ; i++ )
|
|
39 {
|
|
40 if ( n[i] != $(this).parents('div.dataTables_filter')[0] )
|
|
41 {
|
|
42 $(n[i]._DT_Input).val( val );
|
|
43 }
|
|
44 }
|
|
45
|
|
46 /* Now do the filter */
|
|
47 if ( val != oPreviousSearch.sSearch )
|
|
48 {
|
|
49 _fnFilterComplete( oSettings, {
|
|
50 "sSearch": val,
|
|
51 "bRegex": oPreviousSearch.bRegex,
|
|
52 "bSmart": oPreviousSearch.bSmart ,
|
|
53 "bCaseInsensitive": oPreviousSearch.bCaseInsensitive
|
|
54 } );
|
|
55 }
|
|
56 } );
|
|
57
|
|
58 jqFilter
|
|
59 .attr('aria-controls', oSettings.sTableId)
|
|
60 .bind( 'keypress.DT', function(e) {
|
|
61 /* Prevent form submission */
|
|
62 if ( e.keyCode == 13 )
|
|
63 {
|
|
64 return false;
|
|
65 }
|
|
66 }
|
|
67 );
|
|
68
|
|
69 return nFilter;
|
|
70 }
|
|
71
|
|
72
|
|
73 /**
|
|
74 * Filter the table using both the global filter and column based filtering
|
|
75 * @param {object} oSettings dataTables settings object
|
|
76 * @param {object} oSearch search information
|
|
77 * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
|
|
78 * @memberof DataTable#oApi
|
|
79 */
|
|
80 function _fnFilterComplete ( oSettings, oInput, iForce )
|
|
81 {
|
|
82 var oPrevSearch = oSettings.oPreviousSearch;
|
|
83 var aoPrevSearch = oSettings.aoPreSearchCols;
|
|
84 var fnSaveFilter = function ( oFilter ) {
|
|
85 /* Save the filtering values */
|
|
86 oPrevSearch.sSearch = oFilter.sSearch;
|
|
87 oPrevSearch.bRegex = oFilter.bRegex;
|
|
88 oPrevSearch.bSmart = oFilter.bSmart;
|
|
89 oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
|
|
90 };
|
|
91
|
|
92 /* In server-side processing all filtering is done by the server, so no point hanging around here */
|
|
93 if ( !oSettings.oFeatures.bServerSide )
|
|
94 {
|
|
95 /* Global filter */
|
|
96 _fnFilter( oSettings, oInput.sSearch, iForce, oInput.bRegex, oInput.bSmart, oInput.bCaseInsensitive );
|
|
97 fnSaveFilter( oInput );
|
|
98
|
|
99 /* Now do the individual column filter */
|
|
100 for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
|
|
101 {
|
|
102 _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, aoPrevSearch[i].bRegex,
|
|
103 aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
|
|
104 }
|
|
105
|
|
106 /* Custom filtering */
|
|
107 _fnFilterCustom( oSettings );
|
|
108 }
|
|
109 else
|
|
110 {
|
|
111 fnSaveFilter( oInput );
|
|
112 }
|
|
113
|
|
114 /* Tell the draw function we have been filtering */
|
|
115 oSettings.bFiltered = true;
|
|
116 $(oSettings.oInstance).trigger('filter', oSettings);
|
|
117
|
|
118 /* Redraw the table */
|
|
119 oSettings._iDisplayStart = 0;
|
|
120 _fnCalculateEnd( oSettings );
|
|
121 _fnDraw( oSettings );
|
|
122
|
|
123 /* Rebuild search array 'offline' */
|
|
124 _fnBuildSearchArray( oSettings, 0 );
|
|
125 }
|
|
126
|
|
127
|
|
128 /**
|
|
129 * Apply custom filtering functions
|
|
130 * @param {object} oSettings dataTables settings object
|
|
131 * @memberof DataTable#oApi
|
|
132 */
|
|
133 function _fnFilterCustom( oSettings )
|
|
134 {
|
|
135 var afnFilters = DataTable.ext.afnFiltering;
|
|
136 var aiFilterColumns = _fnGetColumns( oSettings, 'bSearchable' );
|
|
137
|
|
138 for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
|
|
139 {
|
|
140 var iCorrector = 0;
|
|
141 for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
|
|
142 {
|
|
143 var iDisIndex = oSettings.aiDisplay[j-iCorrector];
|
|
144 var bTest = afnFilters[i](
|
|
145 oSettings,
|
|
146 _fnGetRowData( oSettings, iDisIndex, 'filter', aiFilterColumns ),
|
|
147 iDisIndex
|
|
148 );
|
|
149
|
|
150 /* Check if we should use this row based on the filtering function */
|
|
151 if ( !bTest )
|
|
152 {
|
|
153 oSettings.aiDisplay.splice( j-iCorrector, 1 );
|
|
154 iCorrector++;
|
|
155 }
|
|
156 }
|
|
157 }
|
|
158 }
|
|
159
|
|
160
|
|
161 /**
|
|
162 * Filter the table on a per-column basis
|
|
163 * @param {object} oSettings dataTables settings object
|
|
164 * @param {string} sInput string to filter on
|
|
165 * @param {int} iColumn column to filter
|
|
166 * @param {bool} bRegex treat search string as a regular expression or not
|
|
167 * @param {bool} bSmart use smart filtering or not
|
|
168 * @param {bool} bCaseInsensitive Do case insenstive matching or not
|
|
169 * @memberof DataTable#oApi
|
|
170 */
|
|
171 function _fnFilterColumn ( oSettings, sInput, iColumn, bRegex, bSmart, bCaseInsensitive )
|
|
172 {
|
|
173 if ( sInput === "" )
|
|
174 {
|
|
175 return;
|
|
176 }
|
|
177
|
|
178 var iIndexCorrector = 0;
|
|
179 var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart, bCaseInsensitive );
|
|
180
|
|
181 for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
|
|
182 {
|
|
183 var sData = _fnDataToSearch( _fnGetCellData( oSettings, oSettings.aiDisplay[i], iColumn, 'filter' ),
|
|
184 oSettings.aoColumns[iColumn].sType );
|
|
185 if ( ! rpSearch.test( sData ) )
|
|
186 {
|
|
187 oSettings.aiDisplay.splice( i, 1 );
|
|
188 iIndexCorrector++;
|
|
189 }
|
|
190 }
|
|
191 }
|
|
192
|
|
193
|
|
194 /**
|
|
195 * Filter the data table based on user input and draw the table
|
|
196 * @param {object} oSettings dataTables settings object
|
|
197 * @param {string} sInput string to filter on
|
|
198 * @param {int} iForce optional - force a research of the master array (1) or not (undefined or 0)
|
|
199 * @param {bool} bRegex treat as a regular expression or not
|
|
200 * @param {bool} bSmart perform smart filtering or not
|
|
201 * @param {bool} bCaseInsensitive Do case insenstive matching or not
|
|
202 * @memberof DataTable#oApi
|
|
203 */
|
|
204 function _fnFilter( oSettings, sInput, iForce, bRegex, bSmart, bCaseInsensitive )
|
|
205 {
|
|
206 var i;
|
|
207 var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart, bCaseInsensitive );
|
|
208 var oPrevSearch = oSettings.oPreviousSearch;
|
|
209
|
|
210 /* Check if we are forcing or not - optional parameter */
|
|
211 if ( !iForce )
|
|
212 {
|
|
213 iForce = 0;
|
|
214 }
|
|
215
|
|
216 /* Need to take account of custom filtering functions - always filter */
|
|
217 if ( DataTable.ext.afnFiltering.length !== 0 )
|
|
218 {
|
|
219 iForce = 1;
|
|
220 }
|
|
221
|
|
222 /*
|
|
223 * If the input is blank - we want the full data set
|
|
224 */
|
|
225 if ( sInput.length <= 0 )
|
|
226 {
|
|
227 oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
|
|
228 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
|
|
229 }
|
|
230 else
|
|
231 {
|
|
232 /*
|
|
233 * We are starting a new search or the new search string is smaller
|
|
234 * then the old one (i.e. delete). Search from the master array
|
|
235 */
|
|
236 if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
|
|
237 oPrevSearch.sSearch.length > sInput.length || iForce == 1 ||
|
|
238 sInput.indexOf(oPrevSearch.sSearch) !== 0 )
|
|
239 {
|
|
240 /* Nuke the old display array - we are going to rebuild it */
|
|
241 oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
|
|
242
|
|
243 /* Force a rebuild of the search array */
|
|
244 _fnBuildSearchArray( oSettings, 1 );
|
|
245
|
|
246 /* Search through all records to populate the search array
|
|
247 * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1
|
|
248 * mapping
|
|
249 */
|
|
250 for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
|
|
251 {
|
|
252 if ( rpSearch.test(oSettings.asDataSearch[i]) )
|
|
253 {
|
|
254 oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
|
|
255 }
|
|
256 }
|
|
257 }
|
|
258 else
|
|
259 {
|
|
260 /* Using old search array - refine it - do it this way for speed
|
|
261 * Don't have to search the whole master array again
|
|
262 */
|
|
263 var iIndexCorrector = 0;
|
|
264
|
|
265 /* Search the current results */
|
|
266 for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
|
|
267 {
|
|
268 if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
|
|
269 {
|
|
270 oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
|
|
271 iIndexCorrector++;
|
|
272 }
|
|
273 }
|
|
274 }
|
|
275 }
|
|
276 }
|
|
277
|
|
278
|
|
279 /**
|
|
280 * Create an array which can be quickly search through
|
|
281 * @param {object} oSettings dataTables settings object
|
|
282 * @param {int} iMaster use the master data array - optional
|
|
283 * @memberof DataTable#oApi
|
|
284 */
|
|
285 function _fnBuildSearchArray ( oSettings, iMaster )
|
|
286 {
|
|
287 if ( !oSettings.oFeatures.bServerSide )
|
|
288 {
|
|
289 /* Clear out the old data */
|
|
290 oSettings.asDataSearch = [];
|
|
291
|
|
292 var aiFilterColumns = _fnGetColumns( oSettings, 'bSearchable' );
|
|
293 var aiIndex = (iMaster===1) ?
|
|
294 oSettings.aiDisplayMaster :
|
|
295 oSettings.aiDisplay;
|
|
296
|
|
297 for ( var i=0, iLen=aiIndex.length ; i<iLen ; i++ )
|
|
298 {
|
|
299 oSettings.asDataSearch[i] = _fnBuildSearchRow(
|
|
300 oSettings,
|
|
301 _fnGetRowData( oSettings, aiIndex[i], 'filter', aiFilterColumns )
|
|
302 );
|
|
303 }
|
|
304 }
|
|
305 }
|
|
306
|
|
307
|
|
308 /**
|
|
309 * Create a searchable string from a single data row
|
|
310 * @param {object} oSettings dataTables settings object
|
|
311 * @param {array} aData Row data array to use for the data to search
|
|
312 * @memberof DataTable#oApi
|
|
313 */
|
|
314 function _fnBuildSearchRow( oSettings, aData )
|
|
315 {
|
|
316 var sSearch = aData.join(' ');
|
|
317
|
|
318 /* If it looks like there is an HTML entity in the string, attempt to decode it */
|
|
319 if ( sSearch.indexOf('&') !== -1 )
|
|
320 {
|
|
321 sSearch = $('<div>').html(sSearch).text();
|
|
322 }
|
|
323
|
|
324 // Strip newline characters
|
|
325 return sSearch.replace( /[\n\r]/g, " " );
|
|
326 }
|
|
327
|
|
328 /**
|
|
329 * Build a regular expression object suitable for searching a table
|
|
330 * @param {string} sSearch string to search for
|
|
331 * @param {bool} bRegex treat as a regular expression or not
|
|
332 * @param {bool} bSmart perform smart filtering or not
|
|
333 * @param {bool} bCaseInsensitive Do case insensitive matching or not
|
|
334 * @returns {RegExp} constructed object
|
|
335 * @memberof DataTable#oApi
|
|
336 */
|
|
337 function _fnFilterCreateSearch( sSearch, bRegex, bSmart, bCaseInsensitive )
|
|
338 {
|
|
339 var asSearch, sRegExpString;
|
|
340
|
|
341 if ( bSmart )
|
|
342 {
|
|
343 /* Generate the regular expression to use. Something along the lines of:
|
|
344 * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
|
|
345 */
|
|
346 asSearch = bRegex ? sSearch.split( ' ' ) : _fnEscapeRegex( sSearch ).split( ' ' );
|
|
347 sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
|
|
348 return new RegExp( sRegExpString, bCaseInsensitive ? "i" : "" );
|
|
349 }
|
|
350 else
|
|
351 {
|
|
352 sSearch = bRegex ? sSearch : _fnEscapeRegex( sSearch );
|
|
353 return new RegExp( sSearch, bCaseInsensitive ? "i" : "" );
|
|
354 }
|
|
355 }
|
|
356
|
|
357
|
|
358 /**
|
|
359 * Convert raw data into something that the user can search on
|
|
360 * @param {string} sData data to be modified
|
|
361 * @param {string} sType data type
|
|
362 * @returns {string} search string
|
|
363 * @memberof DataTable#oApi
|
|
364 */
|
|
365 function _fnDataToSearch ( sData, sType )
|
|
366 {
|
|
367 if ( typeof DataTable.ext.ofnSearch[sType] === "function" )
|
|
368 {
|
|
369 return DataTable.ext.ofnSearch[sType]( sData );
|
|
370 }
|
|
371 else if ( sData === null )
|
|
372 {
|
|
373 return '';
|
|
374 }
|
|
375 else if ( sType == "html" )
|
|
376 {
|
|
377 return sData.replace(/[\r\n]/g," ").replace( /<.*?>/g, "" );
|
|
378 }
|
|
379 else if ( typeof sData === "string" )
|
|
380 {
|
|
381 return sData.replace(/[\r\n]/g," ");
|
|
382 }
|
|
383 return sData;
|
|
384 }
|
|
385
|
|
386
|
|
387 /**
|
|
388 * scape a string such that it can be used in a regular expression
|
|
389 * @param {string} sVal string to escape
|
|
390 * @returns {string} escaped string
|
|
391 * @memberof DataTable#oApi
|
|
392 */
|
|
393 function _fnEscapeRegex ( sVal )
|
|
394 {
|
|
395 var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ];
|
|
396 var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
|
|
397 return sVal.replace(reReplace, '\\$1');
|
|
398 }
|
|
399
|