Project

General

Profile

Download (13.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Change the order of the table
3
 *  @param {object} oSettings dataTables settings object
4
 *  @param {bool} bApplyClasses optional - should we apply classes or not
5
 *  @memberof DataTable#oApi
6
 */
7
function _fnSort ( oSettings, bApplyClasses )
8
{
9
	var
10
		i, iLen, j, jLen, k, kLen,
11
		sDataType, nTh,
12
		aaSort = [],
13
	 	aiOrig = [],
14
		oSort = DataTable.ext.oSort,
15
		aoData = oSettings.aoData,
16
		aoColumns = oSettings.aoColumns,
17
		oAria = oSettings.oLanguage.oAria;
18
	
19
	/* No sorting required if server-side or no sorting array */
20
	if ( !oSettings.oFeatures.bServerSide && 
21
		(oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
22
	{
23
		aaSort = ( oSettings.aaSortingFixed !== null ) ?
24
			oSettings.aaSortingFixed.concat( oSettings.aaSorting ) :
25
			oSettings.aaSorting.slice();
26
		
27
		/* If there is a sorting data type, and a function belonging to it, then we need to
28
		 * get the data from the developer's function and apply it for this column
29
		 */
30
		for ( i=0 ; i<aaSort.length ; i++ )
31
		{
32
			var iColumn = aaSort[i][0];
33
			var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
34
			sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
35
			if ( DataTable.ext.afnSortData[sDataType] )
36
			{
37
				var aData = DataTable.ext.afnSortData[sDataType].call( 
38
					oSettings.oInstance, oSettings, iColumn, iVisColumn
39
				);
40
				if ( aData.length === aoData.length )
41
				{
42
					for ( j=0, jLen=aoData.length ; j<jLen ; j++ )
43
					{
44
						_fnSetCellData( oSettings, j, iColumn, aData[j] );
45
					}
46
				}
47
				else
48
				{
49
					_fnLog( oSettings, 0, "Returned data sort array (col "+iColumn+") is the wrong length" );
50
				}
51
			}
52
		}
53
		
54
		/* Create a value - key array of the current row positions such that we can use their
55
		 * current position during the sort, if values match, in order to perform stable sorting
56
		 */
57
		for ( i=0, iLen=oSettings.aiDisplayMaster.length ; i<iLen ; i++ )
58
		{
59
			aiOrig[ oSettings.aiDisplayMaster[i] ] = i;
60
		}
61

    
62
		/* Build an internal data array which is specific to the sort, so we can get and prep
63
		 * the data to be sorted only once, rather than needing to do it every time the sorting
64
		 * function runs. This make the sorting function a very simple comparison
65
		 */
66
		var iSortLen = aaSort.length;
67
		var fnSortFormat, aDataSort;
68
		for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
69
		{
70
			for ( j=0 ; j<iSortLen ; j++ )
71
			{
72
				aDataSort = aoColumns[ aaSort[j][0] ].aDataSort;
73

    
74
				for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
75
				{
76
					sDataType = aoColumns[ aDataSort[k] ].sType;
77
					fnSortFormat = oSort[ (sDataType ? sDataType : 'string')+"-pre" ];
78
					
79
					aoData[i]._aSortData[ aDataSort[k] ] = fnSortFormat ?
80
						fnSortFormat( _fnGetCellData( oSettings, i, aDataSort[k], 'sort' ) ) :
81
						_fnGetCellData( oSettings, i, aDataSort[k], 'sort' );
82
				}
83
			}
84
		}
85
		
86
		/* Do the sort - here we want multi-column sorting based on a given data source (column)
87
		 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
88
		 * follow on it's own, but this is what we want (example two column sorting):
89
		 *  fnLocalSorting = function(a,b){
90
		 *  	var iTest;
91
		 *  	iTest = oSort['string-asc']('data11', 'data12');
92
		 *  	if (iTest !== 0)
93
		 *  		return iTest;
94
		 *    iTest = oSort['numeric-desc']('data21', 'data22');
95
		 *    if (iTest !== 0)
96
		 *  		return iTest;
97
		 *  	return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
98
		 *  }
99
		 * Basically we have a test for each sorting column, if the data in that column is equal,
100
		 * test the next column. If all columns match, then we use a numeric sort on the row 
101
		 * positions in the original data array to provide a stable sort.
102
		 */
103
		oSettings.aiDisplayMaster.sort( function ( a, b ) {
104
			var k, l, lLen, iTest, aDataSort, sDataType;
105
			for ( k=0 ; k<iSortLen ; k++ )
106
			{
107
				aDataSort = aoColumns[ aaSort[k][0] ].aDataSort;
108

    
109
				for ( l=0, lLen=aDataSort.length ; l<lLen ; l++ )
110
				{
111
					sDataType = aoColumns[ aDataSort[l] ].sType;
112
					
113
					iTest = oSort[ (sDataType ? sDataType : 'string')+"-"+aaSort[k][1] ](
114
						aoData[a]._aSortData[ aDataSort[l] ],
115
						aoData[b]._aSortData[ aDataSort[l] ]
116
					);
117
				
118
					if ( iTest !== 0 )
119
					{
120
						return iTest;
121
					}
122
				}
123
			}
124
			
125
			return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
126
		} );
127
	}
128
	
129
	/* Alter the sorting classes to take account of the changes */
130
	if ( (bApplyClasses === undefined || bApplyClasses) && !oSettings.oFeatures.bDeferRender )
131
	{
132
		_fnSortingClasses( oSettings );
133
	}
134

    
135
	for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
136
	{
137
		var sTitle = aoColumns[i].sTitle.replace( /<.*?>/g, "" );
138
		nTh = aoColumns[i].nTh;
139
		nTh.removeAttribute('aria-sort');
140
		nTh.removeAttribute('aria-label');
141
		
142
		/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
143
		if ( aoColumns[i].bSortable )
144
		{
145
			if ( aaSort.length > 0 && aaSort[0][0] == i )
146
			{
147
				nTh.setAttribute('aria-sort', aaSort[0][1]=="asc" ? "ascending" : "descending" );
148
				
149
				var nextSort = (aoColumns[i].asSorting[ aaSort[0][2]+1 ]) ? 
150
					aoColumns[i].asSorting[ aaSort[0][2]+1 ] : aoColumns[i].asSorting[0];
151
				nTh.setAttribute('aria-label', sTitle+
152
					(nextSort=="asc" ? oAria.sSortAscending : oAria.sSortDescending) );
153
			}
154
			else
155
			{
156
				nTh.setAttribute('aria-label', sTitle+
157
					(aoColumns[i].asSorting[0]=="asc" ? oAria.sSortAscending : oAria.sSortDescending) );
158
			}
159
		}
160
		else
161
		{
162
			nTh.setAttribute('aria-label', sTitle);
163
		}
164
	}
165
	
166
	/* Tell the draw function that we have sorted the data */
167
	oSettings.bSorted = true;
168
	$(oSettings.oInstance).trigger('sort', oSettings);
169
	
170
	/* Copy the master data into the draw array and re-draw */
171
	if ( oSettings.oFeatures.bFilter )
172
	{
173
		/* _fnFilter() will redraw the table for us */
174
		_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
175
	}
176
	else
177
	{
178
		oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
179
		oSettings._iDisplayStart = 0; /* reset display back to page 0 */
180
		_fnCalculateEnd( oSettings );
181
		_fnDraw( oSettings );
182
	}
183
}
184

    
185

    
186
/**
187
 * Attach a sort handler (click) to a node
188
 *  @param {object} oSettings dataTables settings object
189
 *  @param {node} nNode node to attach the handler to
190
 *  @param {int} iDataIndex column sorting index
191
 *  @param {function} [fnCallback] callback function
192
 *  @memberof DataTable#oApi
193
 */
194
function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback )
195
{
196
	_fnBindAction( nNode, {}, function (e) {
197
		/* If the column is not sortable - don't to anything */
198
		if ( oSettings.aoColumns[iDataIndex].bSortable === false )
199
		{
200
			return;
201
		}
202
		
203
		/*
204
		 * This is a little bit odd I admit... I declare a temporary function inside the scope of
205
		 * _fnBuildHead and the click handler in order that the code presented here can be used 
206
		 * twice - once for when bProcessing is enabled, and another time for when it is 
207
		 * disabled, as we need to perform slightly different actions.
208
		 *   Basically the issue here is that the Javascript engine in modern browsers don't 
209
		 * appear to allow the rendering engine to update the display while it is still executing
210
		 * it's thread (well - it does but only after long intervals). This means that the 
211
		 * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
212
		 * I force an execution break by using setTimeout - but this breaks the expected 
213
		 * thread continuation for the end-developer's point of view (their code would execute
214
		 * too early), so we only do it when we absolutely have to.
215
		 */
216
		var fnInnerSorting = function () {
217
			var iColumn, iNextSort;
218
			
219
			/* If the shift key is pressed then we are multiple column sorting */
220
			if ( e.shiftKey )
221
			{
222
				/* Are we already doing some kind of sort on this column? */
223
				var bFound = false;
224
				for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
225
				{
226
					if ( oSettings.aaSorting[i][0] == iDataIndex )
227
					{
228
						bFound = true;
229
						iColumn = oSettings.aaSorting[i][0];
230
						iNextSort = oSettings.aaSorting[i][2]+1;
231
						
232
						if ( !oSettings.aoColumns[iColumn].asSorting[iNextSort] )
233
						{
234
							/* Reached the end of the sorting options, remove from multi-col sort */
235
							oSettings.aaSorting.splice( i, 1 );
236
						}
237
						else
238
						{
239
							/* Move onto next sorting direction */
240
							oSettings.aaSorting[i][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
241
							oSettings.aaSorting[i][2] = iNextSort;
242
						}
243
						break;
244
					}
245
				}
246
				
247
				/* No sort yet - add it in */
248
				if ( bFound === false )
249
				{
250
					oSettings.aaSorting.push( [ iDataIndex, 
251
						oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
252
				}
253
			}
254
			else
255
			{
256
				/* If no shift key then single column sort */
257
				if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
258
				{
259
					iColumn = oSettings.aaSorting[0][0];
260
					iNextSort = oSettings.aaSorting[0][2]+1;
261
					if ( !oSettings.aoColumns[iColumn].asSorting[iNextSort] )
262
					{
263
						iNextSort = 0;
264
					}
265
					oSettings.aaSorting[0][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
266
					oSettings.aaSorting[0][2] = iNextSort;
267
				}
268
				else
269
				{
270
					oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
271
					oSettings.aaSorting.push( [ iDataIndex, 
272
						oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
273
				}
274
			}
275
			
276
			/* Run the sort */
277
			_fnSort( oSettings );
278
		}; /* /fnInnerSorting */
279
		
280
		if ( !oSettings.oFeatures.bProcessing )
281
		{
282
			fnInnerSorting();
283
		}
284
		else
285
		{
286
			_fnProcessingDisplay( oSettings, true );
287
			setTimeout( function() {
288
				fnInnerSorting();
289
				if ( !oSettings.oFeatures.bServerSide )
290
				{
291
					_fnProcessingDisplay( oSettings, false );
292
				}
293
			}, 0 );
294
		}
295
		
296
		/* Call the user specified callback function - used for async user interaction */
297
		if ( typeof fnCallback == 'function' )
298
		{
299
			fnCallback( oSettings );
300
		}
301
	} );
302
}
303

    
304

    
305
/**
306
 * Set the sorting classes on the header, Note: it is safe to call this function 
307
 * when bSort and bSortClasses are false
308
 *  @param {object} oSettings dataTables settings object
309
 *  @memberof DataTable#oApi
310
 */
311
function _fnSortingClasses( oSettings )
312
{
313
	var i, iLen, j, jLen, iFound;
314
	var aaSort, sClass;
315
	var iColumns = oSettings.aoColumns.length;
316
	var oClasses = oSettings.oClasses;
317
	
318
	for ( i=0 ; i<iColumns ; i++ )
319
	{
320
		if ( oSettings.aoColumns[i].bSortable )
321
		{
322
			$(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
323
				" "+ oSettings.aoColumns[i].sSortingClass );
324
		}
325
	}
326
	
327
	if ( oSettings.aaSortingFixed !== null )
328
	{
329
		aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
330
	}
331
	else
332
	{
333
		aaSort = oSettings.aaSorting.slice();
334
	}
335
	
336
	/* Apply the required classes to the header */
337
	for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
338
	{
339
		if ( oSettings.aoColumns[i].bSortable )
340
		{
341
			sClass = oSettings.aoColumns[i].sSortingClass;
342
			iFound = -1;
343
			for ( j=0 ; j<aaSort.length ; j++ )
344
			{
345
				if ( aaSort[j][0] == i )
346
				{
347
					sClass = ( aaSort[j][1] == "asc" ) ?
348
						oClasses.sSortAsc : oClasses.sSortDesc;
349
					iFound = j;
350
					break;
351
				}
352
			}
353
			$(oSettings.aoColumns[i].nTh).addClass( sClass );
354
			
355
			if ( oSettings.bJUI )
356
			{
357
				/* jQuery UI uses extra markup */
358
				var jqSpan = $("span."+oClasses.sSortIcon,  oSettings.aoColumns[i].nTh);
359
				jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+ 
360
					oClasses.sSortJUI +" "+ oClasses.sSortJUIAscAllowed +" "+ oClasses.sSortJUIDescAllowed );
361
				
362
				var sSpanClass;
363
				if ( iFound == -1 )
364
				{
365
				 	sSpanClass = oSettings.aoColumns[i].sSortingClassJUI;
366
				}
367
				else if ( aaSort[iFound][1] == "asc" )
368
				{
369
					sSpanClass = oClasses.sSortJUIAsc;
370
				}
371
				else
372
				{
373
					sSpanClass = oClasses.sSortJUIDesc;
374
				}
375
				
376
				jqSpan.addClass( sSpanClass );
377
			}
378
		}
379
		else
380
		{
381
			/* No sorting on this column, so add the base class. This will have been assigned by
382
			 * _fnAddColumn
383
			 */
384
			$(oSettings.aoColumns[i].nTh).addClass( oSettings.aoColumns[i].sSortingClass );
385
		}
386
	}
387
	
388
	/* 
389
	 * Apply the required classes to the table body
390
	 * Note that this is given as a feature switch since it can significantly slow down a sort
391
	 * on large data sets (adding and removing of classes is always slow at the best of times..)
392
	 * Further to this, note that this code is admittedly fairly ugly. It could be made a lot 
393
	 * simpler using jQuery selectors and add/removeClass, but that is significantly slower
394
	 * (on the order of 5 times slower) - hence the direct DOM manipulation here.
395
	 * Note that for deferred drawing we do use jQuery - the reason being that taking the first
396
	 * row found to see if the whole column needs processed can miss classes since the first
397
	 * column might be new.
398
	 */
399
	sClass = oClasses.sSortColumn;
400
	
401
	if ( oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses )
402
	{
403
		var nTds = _fnGetTdNodes( oSettings );
404
		
405
		/* Determine what the sorting class for each column should be */
406
		var iClass, iTargetCol;
407
		var asClasses = [];
408
		for (i = 0; i < iColumns; i++)
409
		{
410
			asClasses.push("");
411
		}
412
		for (i = 0, iClass = 1; i < aaSort.length; i++)
413
		{
414
			iTargetCol = parseInt( aaSort[i][0], 10 );
415
			asClasses[iTargetCol] = sClass + iClass;
416
			
417
			if ( iClass < 3 )
418
			{
419
				iClass++;
420
			}
421
		}
422
		
423
		/* Make changes to the classes for each cell as needed */
424
		var reClass = new RegExp(sClass + "[123]");
425
		var sTmpClass, sCurrentClass, sNewClass;
426
		for ( i=0, iLen=nTds.length; i<iLen; i++ )
427
		{
428
			/* Determine which column we're looking at */
429
			iTargetCol = i % iColumns;
430
			
431
			/* What is the full list of classes now */
432
			sCurrentClass = nTds[i].className;
433
			/* What sorting class should be applied? */
434
			sNewClass = asClasses[iTargetCol];
435
			/* What would the new full list be if we did a replacement? */
436
			sTmpClass = sCurrentClass.replace(reClass, sNewClass);
437
			
438
			if ( sTmpClass != sCurrentClass )
439
			{
440
				/* We changed something */
441
				nTds[i].className = $.trim( sTmpClass );
442
			}
443
			else if ( sNewClass.length > 0 && sCurrentClass.indexOf(sNewClass) == -1 )
444
			{
445
				/* We need to add a class */
446
				nTds[i].className = sCurrentClass + " " + sNewClass;
447
			}
448
		}
449
	}
450
}
451

    
(14-14/16)