Add datatables-1.9.4 and jquery-1.10.2 libraries
[proteocache.git] / webapp / resources / datatables-1.9.4 / media / src / core / core.draw.js
1 /**
2  * Create a new TR element (and it's TD children) for a row
3  *  @param {object} oSettings dataTables settings object
4  *  @param {int} iRow Row to consider
5  *  @memberof DataTable#oApi
6  */
7 function _fnCreateTr ( oSettings, iRow )
8 {
9         var oData = oSettings.aoData[iRow];
10         var nTd;
11
12         if ( oData.nTr === null )
13         {
14                 oData.nTr = document.createElement('tr');
15
16                 /* Use a private property on the node to allow reserve mapping from the node
17                  * to the aoData array for fast look up
18                  */
19                 oData.nTr._DT_RowIndex = iRow;
20
21                 /* Special parameters can be given by the data source to be used on the row */
22                 if ( oData._aData.DT_RowId )
23                 {
24                         oData.nTr.id = oData._aData.DT_RowId;
25                 }
26
27                 if ( oData._aData.DT_RowClass )
28                 {
29                         oData.nTr.className = oData._aData.DT_RowClass;
30                 }
31
32                 /* Process each column */
33                 for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
34                 {
35                         var oCol = oSettings.aoColumns[i];
36                         nTd = document.createElement( oCol.sCellType );
37
38                         /* Render if needed - if bUseRendered is true then we already have the rendered
39                          * value in the data source - so can just use that
40                          */
41                         nTd.innerHTML = (typeof oCol.fnRender === 'function' && (!oCol.bUseRendered || oCol.mData === null)) ?
42                                 _fnRender( oSettings, iRow, i ) :
43                                 _fnGetCellData( oSettings, iRow, i, 'display' );
44                 
45                         /* Add user defined class */
46                         if ( oCol.sClass !== null )
47                         {
48                                 nTd.className = oCol.sClass;
49                         }
50                         
51                         if ( oCol.bVisible )
52                         {
53                                 oData.nTr.appendChild( nTd );
54                                 oData._anHidden[i] = null;
55                         }
56                         else
57                         {
58                                 oData._anHidden[i] = nTd;
59                         }
60
61                         if ( oCol.fnCreatedCell )
62                         {
63                                 oCol.fnCreatedCell.call( oSettings.oInstance,
64                                         nTd, _fnGetCellData( oSettings, iRow, i, 'display' ), oData._aData, iRow, i
65                                 );
66                         }
67                 }
68
69                 _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, iRow] );
70         }
71 }
72
73
74 /**
75  * Create the HTML header for the table
76  *  @param {object} oSettings dataTables settings object
77  *  @memberof DataTable#oApi
78  */
79 function _fnBuildHead( oSettings )
80 {
81         var i, nTh, iLen, j, jLen;
82         var iThs = $('th, td', oSettings.nTHead).length;
83         var iCorrector = 0;
84         var jqChildren;
85         
86         /* If there is a header in place - then use it - otherwise it's going to get nuked... */
87         if ( iThs !== 0 )
88         {
89                 /* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
90                 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
91                 {
92                         nTh = oSettings.aoColumns[i].nTh;
93                         nTh.setAttribute('role', 'columnheader');
94                         if ( oSettings.aoColumns[i].bSortable )
95                         {
96                                 nTh.setAttribute('tabindex', oSettings.iTabIndex);
97                                 nTh.setAttribute('aria-controls', oSettings.sTableId);
98                         }
99
100                         if ( oSettings.aoColumns[i].sClass !== null )
101                         {
102                                 $(nTh).addClass( oSettings.aoColumns[i].sClass );
103                         }
104                         
105                         /* Set the title of the column if it is user defined (not what was auto detected) */
106                         if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
107                         {
108                                 nTh.innerHTML = oSettings.aoColumns[i].sTitle;
109                         }
110                 }
111         }
112         else
113         {
114                 /* We don't have a header in the DOM - so we are going to have to create one */
115                 var nTr = document.createElement( "tr" );
116                 
117                 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
118                 {
119                         nTh = oSettings.aoColumns[i].nTh;
120                         nTh.innerHTML = oSettings.aoColumns[i].sTitle;
121                         nTh.setAttribute('tabindex', '0');
122                         
123                         if ( oSettings.aoColumns[i].sClass !== null )
124                         {
125                                 $(nTh).addClass( oSettings.aoColumns[i].sClass );
126                         }
127                         
128                         nTr.appendChild( nTh );
129                 }
130                 $(oSettings.nTHead).html( '' )[0].appendChild( nTr );
131                 _fnDetectHeader( oSettings.aoHeader, oSettings.nTHead );
132         }
133         
134         /* ARIA role for the rows */    
135         $(oSettings.nTHead).children('tr').attr('role', 'row');
136         
137         /* Add the extra markup needed by jQuery UI's themes */
138         if ( oSettings.bJUI )
139         {
140                 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
141                 {
142                         nTh = oSettings.aoColumns[i].nTh;
143                         
144                         var nDiv = document.createElement('div');
145                         nDiv.className = oSettings.oClasses.sSortJUIWrapper;
146                         $(nTh).contents().appendTo(nDiv);
147                         
148                         var nSpan = document.createElement('span');
149                         nSpan.className = oSettings.oClasses.sSortIcon;
150                         nDiv.appendChild( nSpan );
151                         nTh.appendChild( nDiv );
152                 }
153         }
154         
155         if ( oSettings.oFeatures.bSort )
156         {
157                 for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
158                 {
159                         if ( oSettings.aoColumns[i].bSortable !== false )
160                         {
161                                 _fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
162                         }
163                         else
164                         {
165                                 $(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone );
166                         }
167                 }
168         }
169         
170         /* Deal with the footer - add classes if required */
171         if ( oSettings.oClasses.sFooterTH !== "" )
172         {
173                 $(oSettings.nTFoot).children('tr').children('th').addClass( oSettings.oClasses.sFooterTH );
174         }
175         
176         /* Cache the footer elements */
177         if ( oSettings.nTFoot !== null )
178         {
179                 var anCells = _fnGetUniqueThs( oSettings, null, oSettings.aoFooter );
180                 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
181                 {
182                         if ( anCells[i] )
183                         {
184                                 oSettings.aoColumns[i].nTf = anCells[i];
185                                 if ( oSettings.aoColumns[i].sClass )
186                                 {
187                                         $(anCells[i]).addClass( oSettings.aoColumns[i].sClass );
188                                 }
189                         }
190                 }
191         }
192 }
193
194
195 /**
196  * Draw the header (or footer) element based on the column visibility states. The
197  * methodology here is to use the layout array from _fnDetectHeader, modified for
198  * the instantaneous column visibility, to construct the new layout. The grid is
199  * traversed over cell at a time in a rows x columns grid fashion, although each 
200  * cell insert can cover multiple elements in the grid - which is tracks using the
201  * aApplied array. Cell inserts in the grid will only occur where there isn't
202  * already a cell in that position.
203  *  @param {object} oSettings dataTables settings object
204  *  @param array {objects} aoSource Layout array from _fnDetectHeader
205  *  @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, 
206  *  @memberof DataTable#oApi
207  */
208 function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
209 {
210         var i, iLen, j, jLen, k, kLen, n, nLocalTr;
211         var aoLocal = [];
212         var aApplied = [];
213         var iColumns = oSettings.aoColumns.length;
214         var iRowspan, iColspan;
215
216         if (  bIncludeHidden === undefined )
217         {
218                 bIncludeHidden = false;
219         }
220
221         /* Make a copy of the master layout array, but without the visible columns in it */
222         for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
223         {
224                 aoLocal[i] = aoSource[i].slice();
225                 aoLocal[i].nTr = aoSource[i].nTr;
226
227                 /* Remove any columns which are currently hidden */
228                 for ( j=iColumns-1 ; j>=0 ; j-- )
229                 {
230                         if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
231                         {
232                                 aoLocal[i].splice( j, 1 );
233                         }
234                 }
235
236                 /* Prep the applied array - it needs an element for each row */
237                 aApplied.push( [] );
238         }
239
240         for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
241         {
242                 nLocalTr = aoLocal[i].nTr;
243                 
244                 /* All cells are going to be replaced, so empty out the row */
245                 if ( nLocalTr )
246                 {
247                         while( (n = nLocalTr.firstChild) )
248                         {
249                                 nLocalTr.removeChild( n );
250                         }
251                 }
252
253                 for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
254                 {
255                         iRowspan = 1;
256                         iColspan = 1;
257
258                         /* Check to see if there is already a cell (row/colspan) covering our target
259                          * insert point. If there is, then there is nothing to do.
260                          */
261                         if ( aApplied[i][j] === undefined )
262                         {
263                                 nLocalTr.appendChild( aoLocal[i][j].cell );
264                                 aApplied[i][j] = 1;
265
266                                 /* Expand the cell to cover as many rows as needed */
267                                 while ( aoLocal[i+iRowspan] !== undefined &&
268                                         aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
269                                 {
270                                         aApplied[i+iRowspan][j] = 1;
271                                         iRowspan++;
272                                 }
273
274                                 /* Expand the cell to cover as many columns as needed */
275                                 while ( aoLocal[i][j+iColspan] !== undefined &&
276                                         aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
277                                 {
278                                         /* Must update the applied array over the rows for the columns */
279                                         for ( k=0 ; k<iRowspan ; k++ )
280                                         {
281                                                 aApplied[i+k][j+iColspan] = 1;
282                                         }
283                                         iColspan++;
284                                 }
285
286                                 /* Do the actual expansion in the DOM */
287                                 aoLocal[i][j].cell.rowSpan = iRowspan;
288                                 aoLocal[i][j].cell.colSpan = iColspan;
289                         }
290                 }
291         }
292 }
293
294
295 /**
296  * Insert the required TR nodes into the table for display
297  *  @param {object} oSettings dataTables settings object
298  *  @memberof DataTable#oApi
299  */
300 function _fnDraw( oSettings )
301 {
302         /* Provide a pre-callback function which can be used to cancel the draw is false is returned */
303         var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
304         if ( $.inArray( false, aPreDraw ) !== -1 )
305         {
306                 _fnProcessingDisplay( oSettings, false );
307                 return;
308         }
309         
310         var i, iLen, n;
311         var anRows = [];
312         var iRowCount = 0;
313         var iStripes = oSettings.asStripeClasses.length;
314         var iOpenRows = oSettings.aoOpenRows.length;
315         
316         oSettings.bDrawing = true;
317         
318         /* Check and see if we have an initial draw position from state saving */
319         if ( oSettings.iInitDisplayStart !== undefined && oSettings.iInitDisplayStart != -1 )
320         {
321                 if ( oSettings.oFeatures.bServerSide )
322                 {
323                         oSettings._iDisplayStart = oSettings.iInitDisplayStart;
324                 }
325                 else
326                 {
327                         oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ?
328                                 0 : oSettings.iInitDisplayStart;
329                 }
330                 oSettings.iInitDisplayStart = -1;
331                 _fnCalculateEnd( oSettings );
332         }
333         
334         /* Server-side processing draw intercept */
335         if ( oSettings.bDeferLoading )
336         {
337                 oSettings.bDeferLoading = false;
338                 oSettings.iDraw++;
339         }
340         else if ( !oSettings.oFeatures.bServerSide )
341         {
342                 oSettings.iDraw++;
343         }
344         else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
345         {
346                 return;
347         }
348         
349         if ( oSettings.aiDisplay.length !== 0 )
350         {
351                 var iStart = oSettings._iDisplayStart;
352                 var iEnd = oSettings._iDisplayEnd;
353                 
354                 if ( oSettings.oFeatures.bServerSide )
355                 {
356                         iStart = 0;
357                         iEnd = oSettings.aoData.length;
358                 }
359                 
360                 for ( var j=iStart ; j<iEnd ; j++ )
361                 {
362                         var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ];
363                         if ( aoData.nTr === null )
364                         {
365                                 _fnCreateTr( oSettings, oSettings.aiDisplay[j] );
366                         }
367
368                         var nRow = aoData.nTr;
369                         
370                         /* Remove the old striping classes and then add the new one */
371                         if ( iStripes !== 0 )
372                         {
373                                 var sStripe = oSettings.asStripeClasses[ iRowCount % iStripes ];
374                                 if ( aoData._sRowStripe != sStripe )
375                                 {
376                                         $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
377                                         aoData._sRowStripe = sStripe;
378                                 }
379                         }
380                         
381                         /* Row callback functions - might want to manipulate the row */
382                         _fnCallbackFire( oSettings, 'aoRowCallback', null, 
383                                 [nRow, oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j] );
384                         
385                         anRows.push( nRow );
386                         iRowCount++;
387                         
388                         /* If there is an open row - and it is attached to this parent - attach it on redraw */
389                         if ( iOpenRows !== 0 )
390                         {
391                                 for ( var k=0 ; k<iOpenRows ; k++ )
392                                 {
393                                         if ( nRow == oSettings.aoOpenRows[k].nParent )
394                                         {
395                                                 anRows.push( oSettings.aoOpenRows[k].nTr );
396                                                 break;
397                                         }
398                                 }
399                         }
400                 }
401         }
402         else
403         {
404                 /* Table is empty - create a row with an empty message in it */
405                 anRows[ 0 ] = document.createElement( 'tr' );
406                 
407                 if ( oSettings.asStripeClasses[0] )
408                 {
409                         anRows[ 0 ].className = oSettings.asStripeClasses[0];
410                 }
411
412                 var oLang = oSettings.oLanguage;
413                 var sZero = oLang.sZeroRecords;
414                 if ( oSettings.iDraw == 1 && oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
415                 {
416                         sZero = oLang.sLoadingRecords;
417                 }
418                 else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
419                 {
420                         sZero = oLang.sEmptyTable;
421                 }
422
423                 var nTd = document.createElement( 'td' );
424                 nTd.setAttribute( 'valign', "top" );
425                 nTd.colSpan = _fnVisbleColumns( oSettings );
426                 nTd.className = oSettings.oClasses.sRowEmpty;
427                 nTd.innerHTML = _fnInfoMacros( oSettings, sZero );
428                 
429                 anRows[ iRowCount ].appendChild( nTd );
430         }
431         
432         /* Header and footer callbacks */
433         _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], 
434                 _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ] );
435         
436         _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], 
437                 _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ] );
438         
439         /* 
440          * Need to remove any old row from the display - note we can't just empty the tbody using
441          * $().html('') since this will unbind the jQuery event handlers (even although the node 
442          * still exists!) - equally we can't use innerHTML, since IE throws an exception.
443          */
444         var
445                 nAddFrag = document.createDocumentFragment(),
446                 nRemoveFrag = document.createDocumentFragment(),
447                 nBodyPar, nTrs;
448         
449         if ( oSettings.nTBody )
450         {
451                 nBodyPar = oSettings.nTBody.parentNode;
452                 nRemoveFrag.appendChild( oSettings.nTBody );
453                 
454                 /* When doing infinite scrolling, only remove child rows when sorting, filtering or start
455                  * up. When not infinite scroll, always do it.
456                  */
457                 if ( !oSettings.oScroll.bInfinite || !oSettings._bInitComplete ||
458                         oSettings.bSorted || oSettings.bFiltered )
459                 {
460                         while( (n = oSettings.nTBody.firstChild) )
461                         {
462                                 oSettings.nTBody.removeChild( n );
463                         }
464                 }
465                 
466                 /* Put the draw table into the dom */
467                 for ( i=0, iLen=anRows.length ; i<iLen ; i++ )
468                 {
469                         nAddFrag.appendChild( anRows[i] );
470                 }
471                 
472                 oSettings.nTBody.appendChild( nAddFrag );
473                 if ( nBodyPar !== null )
474                 {
475                         nBodyPar.appendChild( oSettings.nTBody );
476                 }
477         }
478         
479         /* Call all required callback functions for the end of a draw */
480         _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
481         
482         /* Draw is complete, sorting and filtering must be as well */
483         oSettings.bSorted = false;
484         oSettings.bFiltered = false;
485         oSettings.bDrawing = false;
486         
487         if ( oSettings.oFeatures.bServerSide )
488         {
489                 _fnProcessingDisplay( oSettings, false );
490                 if ( !oSettings._bInitComplete )
491                 {
492                         _fnInitComplete( oSettings );
493                 }
494         }
495 }
496
497
498 /**
499  * Redraw the table - taking account of the various features which are enabled
500  *  @param {object} oSettings dataTables settings object
501  *  @memberof DataTable#oApi
502  */
503 function _fnReDraw( oSettings )
504 {
505         if ( oSettings.oFeatures.bSort )
506         {
507                 /* Sorting will refilter and draw for us */
508                 _fnSort( oSettings, oSettings.oPreviousSearch );
509         }
510         else if ( oSettings.oFeatures.bFilter )
511         {
512                 /* Filtering will redraw for us */
513                 _fnFilterComplete( oSettings, oSettings.oPreviousSearch );
514         }
515         else
516         {
517                 _fnCalculateEnd( oSettings );
518                 _fnDraw( oSettings );
519         }
520 }
521
522
523 /**
524  * Add the options to the page HTML for the table
525  *  @param {object} oSettings dataTables settings object
526  *  @memberof DataTable#oApi
527  */
528 function _fnAddOptionsHtml ( oSettings )
529 {
530         /*
531          * Create a temporary, empty, div which we can later on replace with what we have generated
532          * we do it this way to rendering the 'options' html offline - speed :-)
533          */
534         var nHolding = $('<div></div>')[0];
535         oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
536         
537         /* 
538          * All DataTables are wrapped in a div
539          */
540         oSettings.nTableWrapper = $('<div id="'+oSettings.sTableId+'_wrapper" class="'+oSettings.oClasses.sWrapper+'" role="grid"></div>')[0];
541         oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
542
543         /* Track where we want to insert the option */
544         var nInsertNode = oSettings.nTableWrapper;
545         
546         /* Loop over the user set positioning and place the elements as needed */
547         var aDom = oSettings.sDom.split('');
548         var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j;
549         for ( var i=0 ; i<aDom.length ; i++ )
550         {
551                 iPushFeature = 0;
552                 cOption = aDom[i];
553                 
554                 if ( cOption == '<' )
555                 {
556                         /* New container div */
557                         nNewNode = $('<div></div>')[0];
558                         
559                         /* Check to see if we should append an id and/or a class name to the container */
560                         cNext = aDom[i+1];
561                         if ( cNext == "'" || cNext == '"' )
562                         {
563                                 sAttr = "";
564                                 j = 2;
565                                 while ( aDom[i+j] != cNext )
566                                 {
567                                         sAttr += aDom[i+j];
568                                         j++;
569                                 }
570                                 
571                                 /* Replace jQuery UI constants */
572                                 if ( sAttr == "H" )
573                                 {
574                                         sAttr = oSettings.oClasses.sJUIHeader;
575                                 }
576                                 else if ( sAttr == "F" )
577                                 {
578                                         sAttr = oSettings.oClasses.sJUIFooter;
579                                 }
580                                 
581                                 /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
582                                  * breaks the string into parts and applies them as needed
583                                  */
584                                 if ( sAttr.indexOf('.') != -1 )
585                                 {
586                                         var aSplit = sAttr.split('.');
587                                         nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
588                                         nNewNode.className = aSplit[1];
589                                 }
590                                 else if ( sAttr.charAt(0) == "#" )
591                                 {
592                                         nNewNode.id = sAttr.substr(1, sAttr.length-1);
593                                 }
594                                 else
595                                 {
596                                         nNewNode.className = sAttr;
597                                 }
598                                 
599                                 i += j; /* Move along the position array */
600                         }
601                         
602                         nInsertNode.appendChild( nNewNode );
603                         nInsertNode = nNewNode;
604                 }
605                 else if ( cOption == '>' )
606                 {
607                         /* End container div */
608                         nInsertNode = nInsertNode.parentNode;
609                 }
610                 else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
611                 {
612                         /* Length */
613                         nTmp = _fnFeatureHtmlLength( oSettings );
614                         iPushFeature = 1;
615                 }
616                 else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
617                 {
618                         /* Filter */
619                         nTmp = _fnFeatureHtmlFilter( oSettings );
620                         iPushFeature = 1;
621                 }
622                 else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
623                 {
624                         /* pRocessing */
625                         nTmp = _fnFeatureHtmlProcessing( oSettings );
626                         iPushFeature = 1;
627                 }
628                 else if ( cOption == 't' )
629                 {
630                         /* Table */
631                         nTmp = _fnFeatureHtmlTable( oSettings );
632                         iPushFeature = 1;
633                 }
634                 else if ( cOption ==  'i' && oSettings.oFeatures.bInfo )
635                 {
636                         /* Info */
637                         nTmp = _fnFeatureHtmlInfo( oSettings );
638                         iPushFeature = 1;
639                 }
640                 else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
641                 {
642                         /* Pagination */
643                         nTmp = _fnFeatureHtmlPaginate( oSettings );
644                         iPushFeature = 1;
645                 }
646                 else if ( DataTable.ext.aoFeatures.length !== 0 )
647                 {
648                         /* Plug-in features */
649                         var aoFeatures = DataTable.ext.aoFeatures;
650                         for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
651                         {
652                                 if ( cOption == aoFeatures[k].cFeature )
653                                 {
654                                         nTmp = aoFeatures[k].fnInit( oSettings );
655                                         if ( nTmp )
656                                         {
657                                                 iPushFeature = 1;
658                                         }
659                                         break;
660                                 }
661                         }
662                 }
663                 
664                 /* Add to the 2D features array */
665                 if ( iPushFeature == 1 && nTmp !== null )
666                 {
667                         if ( typeof oSettings.aanFeatures[cOption] !== 'object' )
668                         {
669                                 oSettings.aanFeatures[cOption] = [];
670                         }
671                         oSettings.aanFeatures[cOption].push( nTmp );
672                         nInsertNode.appendChild( nTmp );
673                 }
674         }
675         
676         /* Built our DOM structure - replace the holding div with what we want */
677         nHolding.parentNode.replaceChild( oSettings.nTableWrapper, nHolding );
678 }
679
680
681 /**
682  * Use the DOM source to create up an array of header cells. The idea here is to
683  * create a layout grid (array) of rows x columns, which contains a reference
684  * to the cell that that point in the grid (regardless of col/rowspan), such that
685  * any column / row could be removed and the new grid constructed
686  *  @param array {object} aLayout Array to store the calculated layout in
687  *  @param {node} nThead The header/footer element for the table
688  *  @memberof DataTable#oApi
689  */
690 function _fnDetectHeader ( aLayout, nThead )
691 {
692         var nTrs = $(nThead).children('tr');
693         var nTr, nCell;
694         var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
695         var bUnique;
696         var fnShiftCol = function ( a, i, j ) {
697                 var k = a[i];
698                 while ( k[j] ) {
699                         j++;
700                 }
701                 return j;
702         };
703
704         aLayout.splice( 0, aLayout.length );
705         
706         /* We know how many rows there are in the layout - so prep it */
707         for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
708         {
709                 aLayout.push( [] );
710         }
711         
712         /* Calculate a layout array */
713         for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
714         {
715                 nTr = nTrs[i];
716                 iColumn = 0;
717                 
718                 /* For every cell in the row... */
719                 nCell = nTr.firstChild;
720                 while ( nCell ) {
721                         if ( nCell.nodeName.toUpperCase() == "TD" ||
722                              nCell.nodeName.toUpperCase() == "TH" )
723                         {
724                                 /* Get the col and rowspan attributes from the DOM and sanitise them */
725                                 iColspan = nCell.getAttribute('colspan') * 1;
726                                 iRowspan = nCell.getAttribute('rowspan') * 1;
727                                 iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
728                                 iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
729
730                                 /* There might be colspan cells already in this row, so shift our target 
731                                  * accordingly
732                                  */
733                                 iColShifted = fnShiftCol( aLayout, i, iColumn );
734                                 
735                                 /* Cache calculation for unique columns */
736                                 bUnique = iColspan === 1 ? true : false;
737                                 
738                                 /* If there is col / rowspan, copy the information into the layout grid */
739                                 for ( l=0 ; l<iColspan ; l++ )
740                                 {
741                                         for ( k=0 ; k<iRowspan ; k++ )
742                                         {
743                                                 aLayout[i+k][iColShifted+l] = {
744                                                         "cell": nCell,
745                                                         "unique": bUnique
746                                                 };
747                                                 aLayout[i+k].nTr = nTr;
748                                         }
749                                 }
750                         }
751                         nCell = nCell.nextSibling;
752                 }
753         }
754 }
755
756
757 /**
758  * Get an array of unique th elements, one for each column
759  *  @param {object} oSettings dataTables settings object
760  *  @param {node} nHeader automatically detect the layout from this node - optional
761  *  @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
762  *  @returns array {node} aReturn list of unique th's
763  *  @memberof DataTable#oApi
764  */
765 function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
766 {
767         var aReturn = [];
768         if ( !aLayout )
769         {
770                 aLayout = oSettings.aoHeader;
771                 if ( nHeader )
772                 {
773                         aLayout = [];
774                         _fnDetectHeader( aLayout, nHeader );
775                 }
776         }
777
778         for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
779         {
780                 for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
781                 {
782                         if ( aLayout[i][j].unique && 
783                                  (!aReturn[j] || !oSettings.bSortCellsTop) )
784                         {
785                                 aReturn[j] = aLayout[i][j].cell;
786                         }
787                 }
788         }
789         
790         return aReturn;
791 }
792