Mercurial > hg > ChinaGisRestApi
view gis_gui/lib/jquery.dataTables.js @ 256:cb953cc153a6
GIS-Links from MPDL-Documents now served by Mappit-Server
author | fknauft |
---|---|
date | Wed, 28 Sep 2011 18:46:00 +0200 |
parents | 7f008e782563 |
children |
line wrap: on
line source
/* * File: jquery.dataTables.js * Version: 1.6.2 * CVS: $Id$ * Description: Paginate, search and sort HTML tables * Author: Allan Jardine (www.sprymedia.co.uk) * Created: 28/3/2008 * Modified: $Date$ by $Author$ * Language: Javascript * License: GPL v2 or BSD 3 point style * Project: Mtaala * Contact: allan.jardine@sprymedia.co.uk * * Copyright 2008-2010 Allan Jardine, all rights reserved. * * This source file is free software, under either the GPL v2 license or a * BSD style license, as supplied with this software. * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * * For details pleease refer to: http://www.datatables.net */ /* * When considering jsLint, we need to allow eval() as it it is used for reading cookies and * building the dynamic multi-column sort functions. */ /*jslint evil: true, undef: true, browser: true */ /*globals $, jQuery,_fnReadCookie,_fnProcessingDisplay,_fnDraw,_fnSort,_fnReDraw,_fnDetectType,_fnSortingClasses,_fnSettingsFromNode,_fnBuildSearchArray,_fnCalculateEnd,_fnFeatureHtmlProcessing,_fnFeatureHtmlPaginate,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlFilter,_fnFilter,_fnSaveState,_fnFilterColumn,_fnEscapeRegex,_fnFilterComplete,_fnFeatureHtmlLength,_fnGetDataMaster,_fnVisibleToColumnIndex,_fnDrawHead,_fnAddData,_fnGetTrNodes,_fnGetTdNodes,_fnColumnIndexToVisible,_fnCreateCookie,_fnAddOptionsHtml,_fnMap,_fnClearTable,_fnDataToSearch,_fnReOrderIndex,_fnFilterCustom,_fnNodeToDataIndex,_fnVisbleColumns,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnColumnOrdering,fnGetMaxLenString,_fnSortAttachListener,_fnPageChange*/ (function($) { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - DataTables variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * Variable: dataTableSettings * Purpose: Store the settings for each dataTables instance * Scope: jQuery.fn */ $.fn.dataTableSettings = []; var _aoSettings = $.fn.dataTableSettings; /* Short reference for fast internal lookup */ /* * Variable: dataTableExt * Purpose: Container for customisable parts of DataTables * Scope: jQuery.fn */ $.fn.dataTableExt = {}; var _oExt = $.fn.dataTableExt; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - DataTables extensible objects * * The _oExt object is used to provide an area where user dfined plugins can be * added to DataTables. The following properties of the object are used: * oApi - Plug-in API functions * aTypes - Auto-detection of types * oSort - Sorting functions used by DataTables (based on the type) * oPagination - Pagination functions for different input styles * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * Variable: sVersion * Purpose: Version string for plug-ins to check compatibility * Scope: jQuery.fn.dataTableExt * Notes: Allowed format is a.b.c.d.e where: * a:int, b:int, c:int, d:string(dev|beta), e:int. d and e are optional */ _oExt.sVersion = "1.6.2"; /* * Variable: iApiIndex * Purpose: Index for what 'this' index API functions should use * Scope: jQuery.fn.dataTableExt */ _oExt.iApiIndex = 0; /* * Variable: oApi * Purpose: Container for plugin API functions * Scope: jQuery.fn.dataTableExt */ _oExt.oApi = { }; /* * Variable: aFiltering * Purpose: Container for plugin filtering functions * Scope: jQuery.fn.dataTableExt */ _oExt.afnFiltering = [ ]; /* * Variable: aoFeatures * Purpose: Container for plugin function functions * Scope: jQuery.fn.dataTableExt * Notes: Array of objects with the following parameters: * fnInit: Function for initialisation of Feature. Takes oSettings and returns node * cFeature: Character that will be matched in sDom - case sensitive * sFeature: Feature name - just for completeness :-) */ _oExt.aoFeatures = [ ]; /* * Variable: ofnSearch * Purpose: Container for custom filtering functions * Scope: jQuery.fn.dataTableExt * Notes: This is an object (the name should match the type) for custom filtering function, * which can be used for live DOM checking or formatted text filtering */ _oExt.ofnSearch = { }; /* * Variable: afnSortData * Purpose: Container for custom sorting data source functions * Scope: jQuery.fn.dataTableExt * Notes: Array (associative) of functions which is run prior to a column of this * 'SortDataType' being sorted upon. * Function input parameters: * object:oSettings- DataTables settings object * int:iColumn - Target column number * Return value: Array of data which exactly matched the full data set size for the column to * be sorted upon */ _oExt.afnSortData = [ ]; /* * Variable: oStdClasses * Purpose: Storage for the various classes that DataTables uses * Scope: jQuery.fn.dataTableExt */ _oExt.oStdClasses = { /* Two buttons buttons */ "sPagePrevEnabled": "paginate_enabled_previous", "sPagePrevDisabled": "paginate_disabled_previous", "sPageNextEnabled": "paginate_enabled_next", "sPageNextDisabled": "paginate_disabled_next", "sPageJUINext": "", "sPageJUIPrev": "", /* Full numbers paging buttons */ "sPageButton": "paginate_button", "sPageButtonActive": "paginate_active", "sPageButtonStaticDisabled": "paginate_button", "sPageFirst": "first", "sPagePrevious": "previous", "sPageNext": "next", "sPageLast": "last", /* Stripping classes */ "sStripOdd": "odd", "sStripEven": "even", /* Empty row */ "sRowEmpty": "dataTables_empty", /* Features */ "sWrapper": "dataTables_wrapper", "sFilter": "dataTables_filter", "sInfo": "dataTables_info", "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ "sLength": "dataTables_length", "sProcessing": "dataTables_processing", /* Sorting */ "sSortAsc": "sorting_asc", "sSortDesc": "sorting_desc", "sSortable": "sorting", /* Sortable in both directions */ "sSortableAsc": "sorting_asc_disabled", "sSortableDesc": "sorting_desc_disabled", "sSortableNone": "sorting_disabled", "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ "sSortJUIAsc": "", "sSortJUIDesc": "", "sSortJUI": "", "sSortJUIAscAllowed": "", "sSortJUIDescAllowed": "" }; /* * Variable: oJUIClasses * Purpose: Storage for the various classes that DataTables uses - jQuery UI suitable * Scope: jQuery.fn.dataTableExt */ _oExt.oJUIClasses = { /* Two buttons buttons */ "sPagePrevEnabled": "fg-button ui-state-default ui-corner-left", "sPagePrevDisabled": "fg-button ui-state-default ui-corner-left ui-state-disabled", "sPageNextEnabled": "fg-button ui-state-default ui-corner-right", "sPageNextDisabled": "fg-button ui-state-default ui-corner-right ui-state-disabled", "sPageJUINext": "ui-icon ui-icon-circle-arrow-e", "sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w", /* Full numbers paging buttons */ "sPageButton": "fg-button ui-state-default", "sPageButtonActive": "fg-button ui-state-default ui-state-disabled", "sPageButtonStaticDisabled": "fg-button ui-state-default ui-state-disabled", "sPageFirst": "first ui-corner-tl ui-corner-bl", "sPagePrevious": "previous", "sPageNext": "next", "sPageLast": "last ui-corner-tr ui-corner-br", /* Stripping classes */ "sStripOdd": "odd", "sStripEven": "even", /* Empty row */ "sRowEmpty": "dataTables_empty", /* Features */ "sWrapper": "dataTables_wrapper", "sFilter": "dataTables_filter", "sInfo": "dataTables_info", "sPaging": "dataTables_paginate fg-buttonset fg-buttonset-multi paging_", /* Note that the type is postfixed */ "sLength": "dataTables_length", "sProcessing": "dataTables_processing", /* Sorting */ "sSortAsc": "ui-state-default", "sSortDesc": "ui-state-default", "sSortable": "ui-state-default", "sSortableAsc": "ui-state-default", "sSortableDesc": "ui-state-default", "sSortableNone": "ui-state-default", "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ "sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n", "sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s", "sSortJUI": "css_right ui-icon ui-icon-carat-2-n-s", "sSortJUIAscAllowed": "css_right ui-icon ui-icon-carat-1-n", "sSortJUIDescAllowed": "css_right ui-icon ui-icon-carat-1-s" }; /* * Variable: oPagination * Purpose: Container for the various type of pagination that dataTables supports * Scope: jQuery.fn.dataTableExt */ _oExt.oPagination = { /* * Variable: two_button * Purpose: Standard two button (forward/back) pagination * Scope: jQuery.fn.dataTableExt.oPagination */ "two_button": { /* * Function: oPagination.two_button.fnInit * Purpose: Initalise dom elements required for pagination with forward/back buttons only * Returns: - * Inputs: object:oSettings - dataTables settings object * node:nPaging - the DIV which contains this pagination control * function:fnCallbackDraw - draw function which must be called on update */ "fnInit": function ( oSettings, nPaging, fnCallbackDraw ) { var nPrevious, nNext, nPreviousInner, nNextInner; /* Store the next and previous elements in the oSettings object as they can be very * usful for automation - particularly testing */ if ( !oSettings.bJUI ) { nPrevious = document.createElement( 'div' ); nNext = document.createElement( 'div' ); } else { nPrevious = document.createElement( 'a' ); nNext = document.createElement( 'a' ); nNextInner = document.createElement('span'); nNextInner.className = oSettings.oClasses.sPageJUINext; nNext.appendChild( nNextInner ); nPreviousInner = document.createElement('span'); nPreviousInner.className = oSettings.oClasses.sPageJUIPrev; nPrevious.appendChild( nPreviousInner ); } nPrevious.className = oSettings.oClasses.sPagePrevDisabled; nNext.className = oSettings.oClasses.sPageNextDisabled; nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious; nNext.title = oSettings.oLanguage.oPaginate.sNext; nPaging.appendChild( nPrevious ); nPaging.appendChild( nNext ); $(nPrevious).click( function() { if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) ) { /* Only draw when the page has actually changed */ fnCallbackDraw( oSettings ); } } ); $(nNext).click( function() { if ( oSettings.oApi._fnPageChange( oSettings, "next" ) ) { fnCallbackDraw( oSettings ); } } ); /* Take the brutal approach to cancelling text selection */ $(nPrevious).bind( 'selectstart', function () { return false; } ); $(nNext).bind( 'selectstart', function () { return false; } ); /* ID the first elements only */ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" ) { nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' ); nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' ); nNext.setAttribute( 'id', oSettings.sTableId+'_next' ); } }, /* * Function: oPagination.two_button.fnUpdate * Purpose: Update the two button pagination at the end of the draw * Returns: - * Inputs: object:oSettings - dataTables settings object * function:fnCallbackDraw - draw function to call on page change */ "fnUpdate": function ( oSettings, fnCallbackDraw ) { if ( !oSettings.aanFeatures.p ) { return; } /* Loop over each instance of the pager */ var an = oSettings.aanFeatures.p; for ( var i=0, iLen=an.length ; i<iLen ; i++ ) { if ( an[i].childNodes.length !== 0 ) { an[i].childNodes[0].className = ( oSettings._iDisplayStart === 0 ) ? oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled; an[i].childNodes[1].className = ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ? oSettings.oClasses.sPageNextDisabled : oSettings.oClasses.sPageNextEnabled; } } } }, /* * Variable: iFullNumbersShowPages * Purpose: Change the number of pages which can be seen * Scope: jQuery.fn.dataTableExt.oPagination */ "iFullNumbersShowPages": 5, /* * Variable: full_numbers * Purpose: Full numbers pagination * Scope: jQuery.fn.dataTableExt.oPagination */ "full_numbers": { /* * Function: oPagination.full_numbers.fnInit * Purpose: Initalise dom elements required for pagination with a list of the pages * Returns: - * Inputs: object:oSettings - dataTables settings object * node:nPaging - the DIV which contains this pagination control * function:fnCallbackDraw - draw function which must be called on update */ "fnInit": function ( oSettings, nPaging, fnCallbackDraw ) { var nFirst = document.createElement( 'span' ); var nPrevious = document.createElement( 'span' ); var nList = document.createElement( 'span' ); var nNext = document.createElement( 'span' ); var nLast = document.createElement( 'span' ); nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst; nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious; nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext; nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast; var oClasses = oSettings.oClasses; nFirst.className = oClasses.sPageButton+" "+oClasses.sPageFirst; nPrevious.className = oClasses.sPageButton+" "+oClasses.sPagePrevious; nNext.className= oClasses.sPageButton+" "+oClasses.sPageNext; nLast.className = oClasses.sPageButton+" "+oClasses.sPageLast; nPaging.appendChild( nFirst ); nPaging.appendChild( nPrevious ); nPaging.appendChild( nList ); nPaging.appendChild( nNext ); nPaging.appendChild( nLast ); $(nFirst).click( function () { if ( oSettings.oApi._fnPageChange( oSettings, "first" ) ) { fnCallbackDraw( oSettings ); } } ); $(nPrevious).click( function() { if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) ) { fnCallbackDraw( oSettings ); } } ); $(nNext).click( function() { if ( oSettings.oApi._fnPageChange( oSettings, "next" ) ) { fnCallbackDraw( oSettings ); } } ); $(nLast).click( function() { if ( oSettings.oApi._fnPageChange( oSettings, "last" ) ) { fnCallbackDraw( oSettings ); } } ); /* Take the brutal approach to cancelling text selection */ $('span', nPaging) .bind( 'mousedown', function () { return false; } ) .bind( 'selectstart', function () { return false; } ); /* ID the first elements only */ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" ) { nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' ); nFirst.setAttribute( 'id', oSettings.sTableId+'_first' ); nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' ); nNext.setAttribute( 'id', oSettings.sTableId+'_next' ); nLast.setAttribute( 'id', oSettings.sTableId+'_last' ); } }, /* * Function: oPagination.full_numbers.fnUpdate * Purpose: Update the list of page buttons shows * Returns: - * Inputs: object:oSettings - dataTables settings object * function:fnCallbackDraw - draw function to call on page change */ "fnUpdate": function ( oSettings, fnCallbackDraw ) { if ( !oSettings.aanFeatures.p ) { return; } var iPageCount = _oExt.oPagination.iFullNumbersShowPages; var iPageCountHalf = Math.floor(iPageCount / 2); var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength); var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1; var sList = ""; var iStartButton, iEndButton, i, iLen; var oClasses = oSettings.oClasses; /* Pages calculation */ if (iPages < iPageCount) { iStartButton = 1; iEndButton = iPages; } else { if (iCurrentPage <= iPageCountHalf) { iStartButton = 1; iEndButton = iPageCount; } else { if (iCurrentPage >= (iPages - iPageCountHalf)) { iStartButton = iPages - iPageCount + 1; iEndButton = iPages; } else { iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1; iEndButton = iStartButton + iPageCount - 1; } } } /* Build the dynamic list */ for ( i=iStartButton ; i<=iEndButton ; i++ ) { if ( iCurrentPage != i ) { sList += '<span class="'+oClasses.sPageButton+'">'+i+'</span>'; } else { sList += '<span class="'+oClasses.sPageButtonActive+'">'+i+'</span>'; } } /* Loop over each instance of the pager */ var an = oSettings.aanFeatures.p; var anButtons, anStatic, nPaginateList; var fnClick = function() { /* Use the information in the element to jump to the required page */ var iTarget = (this.innerHTML * 1) - 1; oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength; fnCallbackDraw( oSettings ); return false; }; var fnFalse = function () { return false; }; for ( i=0, iLen=an.length ; i<iLen ; i++ ) { if ( an[i].childNodes.length === 0 ) { continue; } /* Build up the dynamic list forst - html and listeners */ nPaginateList = an[i].childNodes[2]; nPaginateList.innerHTML = sList; $('span', nPaginateList).click( fnClick ).bind( 'mousedown', fnFalse ) .bind( 'selectstart', fnFalse ); /* Update the 'premanent botton's classes */ anButtons = an[i].getElementsByTagName('span'); anStatic = [ anButtons[0], anButtons[1], anButtons[anButtons.length-2], anButtons[anButtons.length-1] ]; $(anStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive+" "+oClasses.sPageButtonStaticDisabled ); if ( iCurrentPage == 1 ) { anStatic[0].className += " "+oClasses.sPageButtonStaticDisabled; anStatic[1].className += " "+oClasses.sPageButtonStaticDisabled; } else { anStatic[0].className += " "+oClasses.sPageButton; anStatic[1].className += " "+oClasses.sPageButton; } if ( iPages === 0 || iCurrentPage == iPages || oSettings._iDisplayLength == -1 ) { anStatic[2].className += " "+oClasses.sPageButtonStaticDisabled; anStatic[3].className += " "+oClasses.sPageButtonStaticDisabled; } else { anStatic[2].className += " "+oClasses.sPageButton; anStatic[3].className += " "+oClasses.sPageButton; } } } } }; /* * Variable: oSort * Purpose: Wrapper for the sorting functions that can be used in DataTables * Scope: jQuery.fn.dataTableExt * Notes: The functions provided in this object are basically standard javascript sort * functions - they expect two inputs which they then compare and then return a priority * result. For each sort method added, two functions need to be defined, an ascending sort and * a descending sort. */ _oExt.oSort = { /* * text sorting */ "string-asc": function ( a, b ) { var x = a.toLowerCase(); var y = b.toLowerCase(); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }, "string-desc": function ( a, b ) { var x = a.toLowerCase(); var y = b.toLowerCase(); return ((x < y) ? 1 : ((x > y) ? -1 : 0)); }, /* * html sorting (ignore html tags) */ "html-asc": function ( a, b ) { var x = a.replace( /<.*?>/g, "" ).toLowerCase(); var y = b.replace( /<.*?>/g, "" ).toLowerCase(); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }, "html-desc": function ( a, b ) { var x = a.replace( /<.*?>/g, "" ).toLowerCase(); var y = b.replace( /<.*?>/g, "" ).toLowerCase(); return ((x < y) ? 1 : ((x > y) ? -1 : 0)); }, /* * date sorting */ "date-asc": function ( a, b ) { var x = Date.parse( a ); var y = Date.parse( b ); if ( isNaN( x ) ) { x = Date.parse( "01/01/1970 00:00:00" ); } if ( isNaN( y ) ) { y = Date.parse( "01/01/1970 00:00:00" ); } return x - y; }, "date-desc": function ( a, b ) { var x = Date.parse( a ); var y = Date.parse( b ); if ( isNaN( x ) ) { x = Date.parse( "01/01/1970 00:00:00" ); } if ( isNaN( y ) ) { y = Date.parse( "01/01/1970 00:00:00" ); } return y - x; }, /* * numerical sorting */ "numeric-asc": function ( a, b ) { var x = a == "-" ? 0 : a; var y = b == "-" ? 0 : b; return x - y; }, "numeric-desc": function ( a, b ) { var x = a == "-" ? 0 : a; var y = b == "-" ? 0 : b; return y - x; } }; /* * Variable: aTypes * Purpose: Container for the various type of type detection that dataTables supports * Scope: jQuery.fn.dataTableExt * Notes: The functions in this array are expected to parse a string to see if it is a data * type that it recognises. If so then the function should return the name of the type (a * corresponding sort function should be defined!), if the type is not recognised then the * function should return null such that the parser and move on to check the next type. * Note that ordering is important in this array - the functions are processed linearly, * starting at index 0. */ _oExt.aTypes = [ /* * Function: - * Purpose: Check to see if a string is numeric * Returns: string:'numeric' or null * Inputs: string:sText - string to check */ function ( sData ) { /* Sanity check that we are dealing with a string or quick return for a number */ if ( typeof sData == 'number' ) { return 'numeric'; } else if ( typeof sData.charAt != 'function' ) { return null; } var sValidFirstChars = "0123456789-"; var sValidChars = "0123456789."; var Char; var bDecimal = false; /* Check for a valid first char (no period and allow negatives) */ Char = sData.charAt(0); if (sValidFirstChars.indexOf(Char) == -1) { return null; } /* Check all the other characters are valid */ for ( var i=1 ; i<sData.length ; i++ ) { Char = sData.charAt(i); if (sValidChars.indexOf(Char) == -1) { return null; } /* Only allowed one decimal place... */ if ( Char == "." ) { if ( bDecimal ) { return null; } bDecimal = true; } } return 'numeric'; }, /* * Function: - * Purpose: Check to see if a string is actually a formatted date * Returns: string:'date' or null * Inputs: string:sText - string to check */ function ( sData ) { var iParse = Date.parse(sData); if ( iParse !== null && !isNaN(iParse) ) { return 'date'; } return null; } ]; /* * Variable: _oExternConfig * Purpose: Store information for DataTables to access globally about other instances * Scope: jQuery.fn.dataTableExt */ _oExt._oExternConfig = { /* int:iNextUnique - next unique number for an instance */ "iNextUnique": 0 }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - DataTables prototype * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * Function: dataTable * Purpose: DataTables information * Returns: - * Inputs: object:oInit - initalisation options for the table */ $.fn.dataTable = function( oInit ) { /* * Function: classSettings * Purpose: Settings container function for all 'class' properties which are required * by dataTables * Returns: - * Inputs: - */ function classSettings () { this.fnRecordsTotal = function () { if ( this.oFeatures.bServerSide ) { return this._iRecordsTotal; } else { return this.aiDisplayMaster.length; } }; this.fnRecordsDisplay = function () { if ( this.oFeatures.bServerSide ) { return this._iRecordsDisplay; } else { return this.aiDisplay.length; } }; this.fnDisplayEnd = function () { if ( this.oFeatures.bServerSide ) { return this._iDisplayStart + this.aiDisplay.length; } else { return this._iDisplayEnd; } }; /* * Variable: sInstance * Purpose: Unique idendifier for each instance of the DataTables object * Scope: jQuery.dataTable.classSettings */ this.sInstance = null; /* * Variable: oFeatures * Purpose: Indicate the enablement of key dataTable features * Scope: jQuery.dataTable.classSettings */ this.oFeatures = { "bPaginate": true, "bLengthChange": true, "bFilter": true, "bSort": true, "bInfo": true, "bAutoWidth": true, "bProcessing": false, "bSortClasses": true, "bStateSave": false, "bServerSide": false }; /* * Variable: aanFeatures * Purpose: Array referencing the nodes which are used for the features * Scope: jQuery.dataTable.classSettings * Notes: The parameters of this object match what is allowed by sDom - i.e. * 'l' - Length changing * 'f' - Filtering input * 't' - The table! * 'i' - Information * 'p' - Pagination * 'r' - pRocessing */ this.aanFeatures = []; /* * Variable: oLanguage * Purpose: Store the language strings used by dataTables * Scope: jQuery.dataTable.classSettings * Notes: The words in the format _VAR_ are variables which are dynamically replaced * by javascript */ this.oLanguage = { "sProcessing": "Processing...", "sLengthMenu": "Show _MENU_ entries", "sZeroRecords": "No matching records found", "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", "sInfoEmpty": "Showing 0 to 0 of 0 entries", "sInfoFiltered": "(filtered from _MAX_ total entries)", "sInfoPostFix": "", "sSearch": "Search:", "sUrl": "", "oPaginate": { "sFirst": "First", "sPrevious": "Previous", "sNext": "Next", "sLast": "Last" } }; /* * Variable: aoData * Purpose: Store data information * Scope: jQuery.dataTable.classSettings * Notes: This is an array of objects with the following parameters: * int: _iId - internal id for tracking * array: _aData - internal data - used for sorting / filtering etc * node: nTr - display node * array node: _anHidden - hidden TD nodes * string: _sRowStripe */ this.aoData = []; /* * Variable: aiDisplay * Purpose: Array of indexes which are in the current display (after filtering etc) * Scope: jQuery.dataTable.classSettings */ this.aiDisplay = []; /* * Variable: aiDisplayMaster * Purpose: Array of indexes for display - no filtering * Scope: jQuery.dataTable.classSettings */ this.aiDisplayMaster = []; /* * Variable: aoColumns * Purpose: Store information about each column that is in use * Scope: jQuery.dataTable.classSettings */ this.aoColumns = []; /* * Variable: iNextId * Purpose: Store the next unique id to be used for a new row * Scope: jQuery.dataTable.classSettings */ this.iNextId = 0; /* * Variable: asDataSearch * Purpose: Search data array for regular expression searching * Scope: jQuery.dataTable.classSettings */ this.asDataSearch = []; /* * Variable: oPreviousSearch * Purpose: Store the previous search incase we want to force a re-search * or compare the old search to a new one * Scope: jQuery.dataTable.classSettings */ this.oPreviousSearch = { "sSearch": "", "bEscapeRegex": true }; /* * Variable: aoPreSearchCols * Purpose: Store the previous search for each column * Scope: jQuery.dataTable.classSettings */ this.aoPreSearchCols = []; /* * Variable: aaSorting * Purpose: Sorting information * Scope: jQuery.dataTable.classSettings * Notes: Index 0 - column number * Index 1 - current sorting direction * Index 2 - index of asSorting for this column */ this.aaSorting = [ [0, 'asc', 0] ]; /* * Variable: aaSortingFixed * Purpose: Sorting information that is always applied * Scope: jQuery.dataTable.classSettings */ this.aaSortingFixed = null; /* * Variable: asStripClasses * Purpose: Classes to use for the striping of a table * Scope: jQuery.dataTable.classSettings */ this.asStripClasses = []; /* * Variable: fnRowCallback * Purpose: Call this function every time a row is inserted (draw) * Scope: jQuery.dataTable.classSettings */ this.fnRowCallback = null; /* * Variable: fnHeaderCallback * Purpose: Callback function for the header on each draw * Scope: jQuery.dataTable.classSettings */ this.fnHeaderCallback = null; /* * Variable: fnFooterCallback * Purpose: Callback function for the footer on each draw * Scope: jQuery.dataTable.classSettings */ this.fnFooterCallback = null; /* * Variable: aoDrawCallback * Purpose: Array of callback functions for draw callback functions * Scope: jQuery.dataTable.classSettings * Notes: Each array element is an object with the following parameters: * function:fn - function to call * string:sName - name callback (feature). useful for arranging array */ this.aoDrawCallback = []; /* * Variable: fnInitComplete * Purpose: Callback function for when the table has been initalised * Scope: jQuery.dataTable.classSettings */ this.fnInitComplete = null; /* * Variable: sTableId * Purpose: Cache the table ID for quick access * Scope: jQuery.dataTable.classSettings */ this.sTableId = ""; /* * Variable: nTable * Purpose: Cache the table node for quick access * Scope: jQuery.dataTable.classSettings */ this.nTable = null; /* * Variable: iDefaultSortIndex * Purpose: Sorting index which will be used by default * Scope: jQuery.dataTable.classSettings */ this.iDefaultSortIndex = 0; /* * Variable: bInitialised * Purpose: Indicate if all required information has been read in * Scope: jQuery.dataTable.classSettings */ this.bInitialised = false; /* * Variable: aoOpenRows * Purpose: Information about open rows * Scope: jQuery.dataTable.classSettings * Notes: Has the parameters 'nTr' and 'nParent' */ this.aoOpenRows = []; /* * Variable: sDom * Purpose: Dictate the positioning that the created elements will take * Scope: jQuery.dataTable.classSettings * Notes: * The following options are allowed: * 'l' - Length changing * 'f' - Filtering input * 't' - The table! * 'i' - Information * 'p' - Pagination * 'r' - pRocessing * The following constants are allowed: * 'H' - jQueryUI theme "header" classes * 'F' - jQueryUI theme "footer" classes * The following syntax is expected: * '<' and '>' - div elements * '<"class" and '>' - div with a class * Examples: * '<"wrapper"flipt>', '<lf<t>ip>' */ this.sDom = 'lfrtip'; /* * Variable: sPaginationType * Purpose: Note which type of sorting should be used * Scope: jQuery.dataTable.classSettings */ this.sPaginationType = "two_button"; /* * Variable: iCookieDuration * Purpose: The cookie duration (for bStateSave) in seconds - default 2 hours * Scope: jQuery.dataTable.classSettings */ this.iCookieDuration = 60 * 60 * 2; /* * Variable: sAjaxSource * Purpose: Source url for AJAX data for the table * Scope: jQuery.dataTable.classSettings */ this.sAjaxSource = null; /* * Variable: bAjaxDataGet * Purpose: Note if draw should be blocked while getting data * Scope: jQuery.dataTable.classSettings */ this.bAjaxDataGet = true; /* * Variable: fnServerData * Purpose: Function to get the server-side data - can be overruled by the developer * Scope: jQuery.dataTable.classSettings */ this.fnServerData = $.getJSON; /* * Variable: iServerDraw * Purpose: Counter and tracker for server-side processing draws * Scope: jQuery.dataTable.classSettings */ this.iServerDraw = 0; /* * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd * Purpose: Display length variables * Scope: jQuery.dataTable.classSettings * Notes: These variable must NOT be used externally to get the data length. Rather, use * the fnRecordsTotal() (etc) functions. */ this._iDisplayLength = 10; this._iDisplayStart = 0; this._iDisplayEnd = 10; /* * Variable: _iRecordsTotal, _iRecordsDisplay * Purpose: Display length variables used for server side processing * Scope: jQuery.dataTable.classSettings * Notes: These variable must NOT be used externally to get the data length. Rather, use * the fnRecordsTotal() (etc) functions. */ this._iRecordsTotal = 0; this._iRecordsDisplay = 0; /* * Variable: bJUI * Purpose: Should we add the markup needed for jQuery UI theming? * Scope: jQuery.dataTable.classSettings */ this.bJUI = false; /* * Variable: bJUI * Purpose: Should we add the markup needed for jQuery UI theming? * Scope: jQuery.dataTable.classSettings */ this.oClasses = _oExt.oStdClasses; /* * Variable: bFiltered and bSorted * Purpose: Flag to allow callback functions to see what action has been performed * Scope: jQuery.dataTable.classSettings */ this.bFiltered = false; this.bSorted = false; } /* * Variable: oApi * Purpose: Container for publicly exposed 'private' functions * Scope: jQuery.dataTable */ this.oApi = {}; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - API functions * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * Function: fnDraw * Purpose: Redraw the table * Returns: - * Inputs: bool:bComplete - Refilter and resort (if enabled) the table before the draw. * Optional: default - true */ this.fnDraw = function( bComplete ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); if ( typeof bComplete != 'undefined' && bComplete === false ) { _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } else { _fnReDraw( oSettings ); } }; /* * Function: fnFilter * Purpose: Filter the input based on data * Returns: - * Inputs: string:sInput - string to filter the table on * int:iColumn - optional - column to limit filtering to * bool:bEscapeRegex - optional - escape regex characters or not - default true */ this.fnFilter = function( sInput, iColumn, bEscapeRegex ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); if ( typeof bEscapeRegex == 'undefined' ) { bEscapeRegex = true; } if ( typeof iColumn == "undefined" || iColumn === null ) { /* Global filter */ _fnFilterComplete( oSettings, {"sSearch":sInput, "bEscapeRegex": bEscapeRegex}, 1 ); } else { /* Single column filter */ oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput; oSettings.aoPreSearchCols[ iColumn ].bEscapeRegex = bEscapeRegex; _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 ); } }; /* * Function: fnSettings * Purpose: Get the settings for a particular table for extern. manipulation * Returns: - * Inputs: - */ this.fnSettings = function( nNode ) { return _fnSettingsFromNode( this[_oExt.iApiIndex] ); }; /* * Function: fnVersionCheck * Purpose: Check a version string against this version of DataTables. Useful for plug-ins * Returns: bool:true -this version of DataTables is greater or equal to the required version * false -this version of DataTales is not suitable * Inputs: string:sVersion - the version to check against. May be in the following formats: * "a", "a.b" or "a.b.c" * Notes: This function will only check the first three parts of a version string. It is * assumed that beta and dev versions will meet the requirements. This might change in future */ this.fnVersionCheck = function( sVersion ) { /* This is cheap, but very effective */ var fnZPad = function (Zpad, count) { while(Zpad.length < count) { Zpad += '0'; } return Zpad; }; var aThis = _oExt.sVersion.split('.'); var aThat = sVersion.split('.'); var sThis = '', sThat = ''; for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { sThis += fnZPad( aThis[i], 3 ); sThat += fnZPad( aThat[i], 3 ); } return parseInt(sThis, 10) >= parseInt(sThat, 10); }; /* * Function: fnSort * Purpose: Sort the table by a particular row * Returns: - * Inputs: int:iCol - the data index to sort on. Note that this will * not match the 'display index' if you have hidden data entries */ this.fnSort = function( aaSort ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); oSettings.aaSorting = aaSort; _fnSort( oSettings ); }; /* * Function: fnSortListener * Purpose: Attach a sort listener to an element for a given column * Returns: - * Inputs: node:nNode - the element to attach the sort listener to * int:iColumn - the column that a click on this node will sort on * function:fnCallback - callback function when sort is run - optional */ this.fnSortListener = function( nNode, iColumn, fnCallback ) { _fnSortAttachListener( _fnSettingsFromNode( this[_oExt.iApiIndex] ), nNode, iColumn, fnCallback ); }; /* * Function: fnAddData * Purpose: Add new row(s) into the table * Returns: array int: array of indexes (aoData) which have been added (zero length on error) * Inputs: array:mData - the data to be added. The length must match * the original data from the DOM * or * array array:mData - 2D array of data to be added * bool:bRedraw - redraw the table or not - default true * Notes: Warning - the refilter here will cause the table to redraw * starting at zero * Notes: Thanks to Yekimov Denis for contributing the basis for this function! */ this.fnAddData = function( mData, bRedraw ) { if ( mData.length === 0 ) { return []; } var aiReturn = []; var iTest; /* Find settings from table node */ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); /* Check if we want to add multiple rows or not */ if ( typeof mData[0] == "object" ) { for ( var i=0 ; i<mData.length ; i++ ) { iTest = _fnAddData( oSettings, mData[i] ); if ( iTest == -1 ) { return aiReturn; } aiReturn.push( iTest ); } } else { iTest = _fnAddData( oSettings, mData ); if ( iTest == -1 ) { return aiReturn; } aiReturn.push( iTest ); } oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); /* Rebuild the search */ _fnBuildSearchArray( oSettings, 1 ); if ( typeof bRedraw == 'undefined' || bRedraw ) { _fnReDraw( oSettings ); } return aiReturn; }; /* * Function: fnDeleteRow * Purpose: Remove a row for the table * Returns: array:aReturn - the row that was deleted * Inputs: mixed:mTarget - * int: - index of aoData to be deleted, or * node(TR): - TR element you want to delete * function:fnCallBack - callback function - default null * bool:bNullRow - remove the row information from aoData by setting the value to * null - default false * Notes: This function requires a little explanation - we don't actually delete the data * from aoData - rather we remove it's references from aiDisplayMastr and aiDisplay. This * in effect prevnts DataTables from drawing it (hence deleting it) - it could be restored * if you really wanted. The reason for this is that actually removing the aoData object * would mess up all the subsequent indexes in the display arrays (they could be ajusted - * but this appears to do what is required). */ this.fnDeleteRow = function( mTarget, fnCallBack, bNullRow ) { /* Find settings from table node */ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); var i, iAODataIndex; iAODataIndex = (typeof mTarget == 'object') ? _fnNodeToDataIndex(oSettings, mTarget) : mTarget; /* Delete from the display master */ for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ ) { if ( oSettings.aiDisplayMaster[i] == iAODataIndex ) { oSettings.aiDisplayMaster.splice( i, 1 ); break; } } /* Delete from the current display index */ for ( i=0 ; i<oSettings.aiDisplay.length ; i++ ) { if ( oSettings.aiDisplay[i] == iAODataIndex ) { oSettings.aiDisplay.splice( i, 1 ); break; } } /* Rebuild the search */ _fnBuildSearchArray( oSettings, 1 ); /* If there is a user callback function - call it */ if ( typeof fnCallBack == "function" ) { fnCallBack.call( this ); } /* Check for an 'overflow' they case for dislaying the table */ if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length ) { oSettings._iDisplayStart -= oSettings._iDisplayLength; if ( oSettings._iDisplayStart < 0 ) { oSettings._iDisplayStart = 0; } } _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); /* Return the data array from this row */ var aData = oSettings.aoData[iAODataIndex]._aData.slice(); if ( typeof bNullRow != "undefined" && bNullRow === true ) { oSettings.aoData[iAODataIndex] = null; } return aData; }; /* * Function: fnClearTable * Purpose: Quickly and simply clear a table * Returns: - * Inputs: bool:bRedraw - redraw the table or not - default true * Notes: Thanks to Yekimov Denis for contributing the basis for this function! */ this.fnClearTable = function( bRedraw ) { /* Find settings from table node */ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); _fnClearTable( oSettings ); if ( typeof bRedraw == 'undefined' || bRedraw ) { _fnDraw( oSettings ); } }; /* * Function: fnOpen * Purpose: Open a display row (append a row after the row in question) * Returns: node:nNewRow - the row opened * Inputs: node:nTr - the table row to 'open' * string:sHtml - the HTML to put into the row * string:sClass - class to give the new cell */ this.fnOpen = function( nTr, sHtml, sClass ) { /* Find settings from table node */ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); /* the old open one if there is one */ this.fnClose( nTr ); var nNewRow = document.createElement("tr"); var nNewCell = document.createElement("td"); nNewRow.appendChild( nNewCell ); nNewCell.className = sClass; nNewCell.colSpan = _fnVisbleColumns( oSettings ); nNewCell.innerHTML = sHtml; /* If the nTr isn't on the page at the moment - then we don't insert at the moment */ var nTrs = $('tbody tr', oSettings.nTable); if ( $.inArray(nTr, nTrs) != -1 ) { $(nNewRow).insertAfter(nTr); } /* No point in storing the row if using server-side processing since the nParent will be * nuked on a re-draw anyway */ if ( !oSettings.oFeatures.bServerSide ) { oSettings.aoOpenRows.push( { "nTr": nNewRow, "nParent": nTr } ); } return nNewRow; }; /* * Function: fnClose * Purpose: Close a display row * Returns: int: 0 (success) or 1 (failed) * Inputs: node:nTr - the table row to 'close' */ this.fnClose = function( nTr ) { /* Find settings from table node */ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ ) { if ( oSettings.aoOpenRows[i].nParent == nTr ) { var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode; if ( nTrParent ) { /* Remove it if it is currently on display */ nTrParent.removeChild( oSettings.aoOpenRows[i].nTr ); } oSettings.aoOpenRows.splice( i, 1 ); return 0; } } return 1; }; /* * Function: fnGetData * Purpose: Return an array with the data which is used to make up the table * Returns: array array string: 2d data array ([row][column]) or array string: 1d data array * or * array string (if iRow specified) * Inputs: mixed:mRow - optional - if not present, then the full 2D array for the table * if given then: * int: - return 1D array for aoData entry of this index * node(TR): - return 1D array for this TR element * Inputs: int:iRow - optional - if present then the array returned will be the data for * the row with the index 'iRow' */ this.fnGetData = function( mRow ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); if ( typeof mRow != 'undefined' ) { var iRow = (typeof mRow == 'object') ? _fnNodeToDataIndex(oSettings, mRow) : mRow; return oSettings.aoData[iRow]._aData; } return _fnGetDataMaster( oSettings ); }; /* * Function: fnGetNodes * Purpose: Return an array with the TR nodes used for drawing the table * Returns: array node: TR elements * or * node (if iRow specified) * Inputs: int:iRow - optional - if present then the array returned will be the node for * the row with the index 'iRow' */ this.fnGetNodes = function( iRow ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); if ( typeof iRow != 'undefined' ) { return oSettings.aoData[iRow].nTr; } return _fnGetTrNodes( oSettings ); }; /* * Function: fnGetPosition * Purpose: Get the array indexes of a particular cell from it's DOM element * Returns: int: - row index, or array[ int, int, int ]: - row index, column index (visible) * and column index including hidden columns * Inputs: node:nNode - this can either be a TR or a TD in the table, the return is * dependent on this input */ this.fnGetPosition = function( nNode ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); var i; if ( nNode.nodeName == "TR" ) { return _fnNodeToDataIndex(oSettings, nNode); } else if ( nNode.nodeName == "TD" ) { var iDataIndex = _fnNodeToDataIndex(oSettings, nNode.parentNode); var iCorrector = 0; for ( var j=0 ; j<oSettings.aoColumns.length ; j++ ) { if ( oSettings.aoColumns[j].bVisible ) { if ( oSettings.aoData[iDataIndex].nTr.getElementsByTagName('td')[j-iCorrector] == nNode ) { return [ iDataIndex, j-iCorrector, j ]; } } else { iCorrector++; } } } return null; }; /* * Function: fnUpdate * Purpose: Update a table cell or row * Returns: int: 0 okay, 1 error * Inputs: array string 'or' string:mData - data to update the cell/row with * mixed:mRow - * int: - index of aoData to be updated, or * node(TR): - TR element you want to update * int:iColumn - the column to update - optional (not used of mData is 2D) * bool:bRedraw - redraw the table or not - default true */ this.fnUpdate = function( mData, mRow, iColumn, bRedraw ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); var iVisibleColumn; var sDisplay; var iRow = (typeof mRow == 'object') ? _fnNodeToDataIndex(oSettings, mRow) : mRow; if ( typeof mData != 'object' ) { sDisplay = mData; oSettings.aoData[iRow]._aData[iColumn] = sDisplay; if ( oSettings.aoColumns[iColumn].fnRender !== null ) { sDisplay = oSettings.aoColumns[iColumn].fnRender( { "iDataRow": iRow, "iDataColumn": iColumn, "aData": oSettings.aoData[iRow]._aData, "oSettings": oSettings } ); if ( oSettings.aoColumns[iColumn].bUseRendered ) { oSettings.aoData[iRow]._aData[iColumn] = sDisplay; } } iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn ); if ( iVisibleColumn !== null ) { oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML = sDisplay; } } else { if ( mData.length != oSettings.aoColumns.length ) { alert( 'DataTables warning: An array passed to fnUpdate must have the same number of '+ 'columns as the table in question - in this case '+oSettings.aoColumns.length ); return 1; } for ( var i=0 ; i<mData.length ; i++ ) { sDisplay = mData[i]; oSettings.aoData[iRow]._aData[i] = sDisplay; if ( oSettings.aoColumns[i].fnRender !== null ) { sDisplay = oSettings.aoColumns[i].fnRender( { "iDataRow": iRow, "iDataColumn": i, "aData": oSettings.aoData[iRow]._aData, "oSettings": oSettings } ); if ( oSettings.aoColumns[i].bUseRendered ) { oSettings.aoData[iRow]._aData[i] = sDisplay; } } iVisibleColumn = _fnColumnIndexToVisible( oSettings, i ); if ( iVisibleColumn !== null ) { oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML = sDisplay; } } } /* Update the search array */ _fnBuildSearchArray( oSettings, 1 ); /* Redraw the table */ if ( typeof bRedraw != 'undefined' && bRedraw ) { _fnReDraw( oSettings ); } return 0; }; /* * Function: fnShowColoumn * Purpose: Show a particular column * Returns: - * Inputs: int:iCol - the column whose display should be changed * bool:bShow - show (true) or hide (false) the column */ this.fnSetColumnVis = function ( iCol, bShow ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); var i, iLen; var iColumns = oSettings.aoColumns.length; var nTd, anTds; /* No point in doing anything if we are requesting what is already true */ if ( oSettings.aoColumns[iCol].bVisible == bShow ) { return; } var nTrHead = $('thead:eq(0)>tr', oSettings.nTable)[0]; var nTrFoot = $('tfoot:eq(0)>tr', oSettings.nTable)[0]; var anTheadTh = []; var anTfootTh = []; for ( i=0 ; i<iColumns ; i++ ) { anTheadTh.push( oSettings.aoColumns[i].nTh ); anTfootTh.push( oSettings.aoColumns[i].nTf ); } /* Show the column */ if ( bShow ) { var iInsert = 0; for ( i=0 ; i<iCol ; i++ ) { if ( oSettings.aoColumns[i].bVisible ) { iInsert++; } } /* Need to decide if we should use appendChild or insertBefore */ if ( iInsert >= _fnVisbleColumns( oSettings ) ) { nTrHead.appendChild( anTheadTh[iCol] ); if ( nTrFoot ) { nTrFoot.appendChild( anTfootTh[iCol] ); } for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) { nTd = oSettings.aoData[i]._anHidden[iCol]; oSettings.aoData[i].nTr.appendChild( nTd ); } } else { /* Which coloumn should we be inserting before? */ var iBefore; for ( i=iCol ; i<iColumns ; i++ ) { iBefore = _fnColumnIndexToVisible( oSettings, i ); if ( iBefore !== null ) { break; } } nTrHead.insertBefore( anTheadTh[iCol], nTrHead.getElementsByTagName('th')[iBefore] ); if ( nTrFoot ) { nTrFoot.insertBefore( anTfootTh[iCol], nTrFoot.getElementsByTagName('th')[iBefore] ); } anTds = _fnGetTdNodes( oSettings ); for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) { nTd = oSettings.aoData[i]._anHidden[iCol]; oSettings.aoData[i].nTr.insertBefore( nTd, $('>td:eq('+iBefore+')', oSettings.aoData[i].nTr)[0] ); } } oSettings.aoColumns[iCol].bVisible = true; } else { /* Remove a column from display */ nTrHead.removeChild( anTheadTh[iCol] ); if ( nTrFoot ) { nTrFoot.removeChild( anTfootTh[iCol] ); } anTds = _fnGetTdNodes( oSettings ); for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) { nTd = anTds[ ( i*oSettings.aoColumns.length) + iCol ]; oSettings.aoData[i]._anHidden[iCol] = nTd; nTd.parentNode.removeChild( nTd ); } oSettings.aoColumns[iCol].bVisible = false; } /* If there are any 'open' rows, then we need to alter the colspan for this col change */ for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ ) { oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings ); } /* Since there is no redraw done here, we need to save the state manually */ _fnSaveState( oSettings ); }; /* * Function: fnPageChange * Purpose: Change the pagination * Returns: - * Inputs: string:sAction - paging action to take: "first", "previous", "next" or "last" * bool:bRedraw - redraw the table or not - optional - default true */ this.fnPageChange = function ( sAction, bRedraw ) { var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] ); _fnPageChange( oSettings, sAction ); _fnCalculateEnd( oSettings ); if ( typeof bRedraw == 'undefined' || bRedraw ) { _fnDraw( oSettings ); } }; /* * Plugin API functions * * This call will add the functions which are defined in _oExt.oApi to the * DataTables object, providing a rather nice way to allow plug-in API functions. Note that * this is done here, so that API function can actually override the built in API functions if * required for a particular purpose. */ /* * Function: _fnExternApiFunc * Purpose: Create a wrapper function for exporting an internal func to an external API func * Returns: function: - wrapped function * Inputs: string:sFunc - API function name */ function _fnExternApiFunc (sFunc) { return function() { var aArgs = [_fnSettingsFromNode(this[_oExt.iApiIndex])].concat( Array.prototype.slice.call(arguments) ); return _oExt.oApi[sFunc].apply( this, aArgs ); }; } for ( var sFunc in _oExt.oApi ) { if ( sFunc ) { /* * Function: anon * Purpose: Wrap the plug-in API functions in order to provide the settings as 1st arg * and execute in this scope * Returns: - * Inputs: - */ this[sFunc] = _fnExternApiFunc(sFunc); } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Local functions * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Initalisation */ /* * Function: _fnInitalise * Purpose: Draw the table for the first time, adding all required features * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnInitalise ( oSettings ) { /* Ensure that the table data is fully initialised */ if ( oSettings.bInitialised === false ) { setTimeout( function(){ _fnInitalise( oSettings ); }, 200 ); return; } /* Show the display HTML options */ _fnAddOptionsHtml( oSettings ); /* Draw the headers for the table */ _fnDrawHead( oSettings ); /* If there is default sorting required - let's do it. The sort function * will do the drawing for us. Otherwise we draw the table */ if ( oSettings.oFeatures.bSort ) { _fnSort( oSettings, false ); /* * Add the sorting classes to the header and the body (if needed). * Reason for doing it here after the first draw is to stop classes being applied to the * 'static' table. */ _fnSortingClasses( oSettings ); } else { oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } /* if there is an ajax source */ if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide ) { _fnProcessingDisplay( oSettings, true ); oSettings.fnServerData( oSettings.sAjaxSource, null, function(json) { /* Got the data - add it to the table */ for ( var i=0 ; i<json.aaData.length ; i++ ) { _fnAddData( oSettings, json.aaData[i] ); } /* Reset the init display for cookie saving. We've already done a filter, and * therefore cleared it before. So we need to make it appear 'fresh' */ oSettings.iInitDisplayStart = oSettings._iDisplayStart; if ( oSettings.oFeatures.bSort ) { _fnSort( oSettings ); } else { oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } _fnProcessingDisplay( oSettings, false ); /* Run the init callback if there is one */ if ( typeof oSettings.fnInitComplete == 'function' ) { oSettings.fnInitComplete( oSettings, json ); } } ); return; } /* Run the init callback if there is one */ if ( typeof oSettings.fnInitComplete == 'function' ) { oSettings.fnInitComplete( oSettings ); } if ( !oSettings.oFeatures.bServerSide ) { _fnProcessingDisplay( oSettings, false ); } } /* * Function: _fnLanguageProcess * Purpose: Copy language variables from remote object to a local one * Returns: - * Inputs: object:oSettings - dataTables settings object * object:oLanguage - Language information * bool:bInit - init once complete */ function _fnLanguageProcess( oSettings, oLanguage, bInit ) { _fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' ); _fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' ); _fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' ); _fnMap( oSettings.oLanguage, oLanguage, 'sInfo' ); _fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' ); _fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' ); _fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' ); _fnMap( oSettings.oLanguage, oLanguage, 'sSearch' ); if ( typeof oLanguage.oPaginate != 'undefined' ) { _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' ); _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' ); _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' ); _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' ); } if ( bInit ) { _fnInitalise( oSettings ); } } /* * Function: _fnAddColumn * Purpose: Add a column to the list used for the table * Returns: - * Inputs: object:oSettings - dataTables settings object * object:oOptions - object with sType, bVisible and bSearchable * node:nTh - the th element for this column * Notes: All options in enter column can be over-ridden by the user * initialisation of dataTables */ function _fnAddColumn( oSettings, oOptions, nTh ) { oSettings.aoColumns[ oSettings.aoColumns.length++ ] = { "sType": null, "_bAutoType": true, "bVisible": true, "bSearchable": true, "bSortable": true, "asSorting": [ 'asc', 'desc' ], "sSortingClass": oSettings.oClasses.sSortable, "sSortingClassJUI": oSettings.oClasses.sSortJUI, "sTitle": nTh ? nTh.innerHTML : '', "sName": '', "sWidth": null, "sClass": null, "fnRender": null, "bUseRendered": true, "iDataSort": oSettings.aoColumns.length-1, "sSortDataType": 'std', "nTh": nTh ? nTh : document.createElement('th'), "nTf": null }; var iLength = oSettings.aoColumns.length-1; var oCol = oSettings.aoColumns[ iLength ]; /* User specified column options */ if ( typeof oOptions != 'undefined' && oOptions !== null ) { if ( typeof oOptions.sType != 'undefined' ) { oCol.sType = oOptions.sType; oCol._bAutoType = false; } _fnMap( oCol, oOptions, "bVisible" ); _fnMap( oCol, oOptions, "bSearchable" ); _fnMap( oCol, oOptions, "bSortable" ); _fnMap( oCol, oOptions, "sTitle" ); _fnMap( oCol, oOptions, "sName" ); _fnMap( oCol, oOptions, "sWidth" ); _fnMap( oCol, oOptions, "sClass" ); _fnMap( oCol, oOptions, "fnRender" ); _fnMap( oCol, oOptions, "bUseRendered" ); _fnMap( oCol, oOptions, "iDataSort" ); _fnMap( oCol, oOptions, "asSorting" ); _fnMap( oCol, oOptions, "sSortDataType" ); } /* Feature sorting overrides column specific when off */ if ( !oSettings.oFeatures.bSort ) { oCol.bSortable = false; } /* Check that the class assignment is correct for sorting */ if ( !oCol.bSortable || ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) ) { oCol.sSortingClass = oSettings.oClasses.sSortableNone; oCol.sSortingClassJUI = ""; } else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 ) { oCol.sSortingClass = oSettings.oClasses.sSortableAsc; oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed; } else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 ) { oCol.sSortingClass = oSettings.oClasses.sSortableDesc; oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed; } /* Add a column specific filter */ if ( typeof oSettings.aoPreSearchCols[ iLength ] == 'undefined' || oSettings.aoPreSearchCols[ iLength ] === null ) { oSettings.aoPreSearchCols[ iLength ] = { "sSearch": "", "bEscapeRegex": true }; } else if ( typeof oSettings.aoPreSearchCols[ iLength ].bEscapeRegex == 'undefined' ) { /* Don't require that the user must specify bEscapeRegex */ oSettings.aoPreSearchCols[ iLength ].bEscapeRegex = true; } } /* * Function: _fnAddData * Purpose: Add a data array to the table, creating DOM node etc * Returns: int: - >=0 if successful (index of new aoData entry), -1 if failed * Inputs: object:oSettings - dataTables settings object * array:aData - data array to be added */ function _fnAddData ( oSettings, aData ) { /* Sanity check the length of the new array */ if ( aData.length != oSettings.aoColumns.length ) { alert( "DataTables warning: Added data does not match known number of columns" ); return -1; } /* Create the object for storing information about this new row */ var iThisIndex = oSettings.aoData.length; oSettings.aoData.push( { "nTr": document.createElement('tr'), "_iId": oSettings.iNextId++, "_aData": aData.slice(), "_anHidden": [], "_sRowStripe": '' } ); /* Create the cells */ var nTd, sThisType; for ( var i=0 ; i<aData.length ; i++ ) { nTd = document.createElement('td'); if ( typeof oSettings.aoColumns[i].fnRender == 'function' ) { var sRendered = oSettings.aoColumns[i].fnRender( { "iDataRow": iThisIndex, "iDataColumn": i, "aData": aData, "oSettings": oSettings } ); nTd.innerHTML = sRendered; if ( oSettings.aoColumns[i].bUseRendered ) { /* Use the rendered data for filtering/sorting */ oSettings.aoData[iThisIndex]._aData[i] = sRendered; } } else { nTd.innerHTML = aData[i]; } if ( oSettings.aoColumns[i].sClass !== null ) { nTd.className = oSettings.aoColumns[i].sClass; } /* See if we should auto-detect the column type */ if ( oSettings.aoColumns[i]._bAutoType && oSettings.aoColumns[i].sType != 'string' ) { /* Attempt to auto detect the type - same as _fnGatherData() */ sThisType = _fnDetectType( oSettings.aoData[iThisIndex]._aData[i] ); if ( oSettings.aoColumns[i].sType === null ) { oSettings.aoColumns[i].sType = sThisType; } else if ( oSettings.aoColumns[i].sType != sThisType ) { /* String is always the 'fallback' option */ oSettings.aoColumns[i].sType = 'string'; } } if ( oSettings.aoColumns[i].bVisible ) { oSettings.aoData[iThisIndex].nTr.appendChild( nTd ); } else { oSettings.aoData[iThisIndex]._anHidden[i] = nTd; } } /* Add to the display array */ oSettings.aiDisplayMaster.push( iThisIndex ); return iThisIndex; } /* * Function: _fnGatherData * Purpose: Read in the data from the target table * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnGatherData( oSettings ) { var iLoop, i, iLen, j, jLen, jInner, nTds, nTrs, nTd, aLocalData, iThisIndex, iRow, iRows, iColumn, iColumns; /* * Process by row first * Add the data object for the whole table - storing the tr node. Note - no point in getting * DOM based data if we are going to go and replace it with Ajax source data. */ if ( oSettings.sAjaxSource === null ) { nTrs = oSettings.nTable.getElementsByTagName('tbody')[0].childNodes; for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { if ( nTrs[i].nodeName == "TR" ) { iThisIndex = oSettings.aoData.length; oSettings.aoData.push( { "nTr": nTrs[i], "_iId": oSettings.iNextId++, "_aData": [], "_anHidden": [], "_sRowStripe": '' } ); oSettings.aiDisplayMaster.push( iThisIndex ); aLocalData = oSettings.aoData[iThisIndex]._aData; nTds = nTrs[i].childNodes; jInner = 0; for ( j=0, jLen=nTds.length ; j<jLen ; j++ ) { if ( nTds[j].nodeName == "TD" ) { aLocalData[jInner] = nTds[j].innerHTML; jInner++; } } } } } /* Gather in the TD elements of the Table - note that this is basically the same as * fnGetTdNodes, but that function takes account of hidden columns, which we haven't yet * setup! */ nTrs = _fnGetTrNodes( oSettings ); nTds = []; for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ ) { nTd = nTrs[i].childNodes[j]; if ( nTd.nodeName == "TD" ) { nTds.push( nTd ); } } } /* Sanity check */ if ( nTds.length != nTrs.length * oSettings.aoColumns.length ) { alert( "DataTables warning: Unexpected number of TD elements. Expected "+ (nTrs.length * oSettings.aoColumns.length)+" and got "+nTds.length+". DataTables does "+ "not support rowspan / colspan in the table body, and there must be one cell for each "+ "row/column combination." ); } /* Now process by column */ for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ ) { /* Get the title of the column - unless there is a user set one */ if ( oSettings.aoColumns[iColumn].sTitle === null ) { oSettings.aoColumns[iColumn].sTitle = oSettings.aoColumns[iColumn].nTh.innerHTML; } var bAutoType = oSettings.aoColumns[iColumn]._bAutoType, bRender = typeof oSettings.aoColumns[iColumn].fnRender == 'function', bClass = oSettings.aoColumns[iColumn].sClass !== null, bVisible = oSettings.aoColumns[iColumn].bVisible, nCell, sThisType, sRendered; /* A single loop to rule them all (and be more efficient) */ if ( bAutoType || bRender || bClass || !bVisible ) { for ( iRow=0, iRows=oSettings.aoData.length ; iRow<iRows ; iRow++ ) { nCell = nTds[ (iRow*iColumns) + iColumn ]; /* Type detection */ if ( bAutoType ) { if ( oSettings.aoColumns[iColumn].sType != 'string' ) { sThisType = _fnDetectType( oSettings.aoData[iRow]._aData[iColumn] ); if ( oSettings.aoColumns[iColumn].sType === null ) { oSettings.aoColumns[iColumn].sType = sThisType; } else if ( oSettings.aoColumns[iColumn].sType != sThisType ) { /* String is always the 'fallback' option */ oSettings.aoColumns[iColumn].sType = 'string'; } } } /* Rendering */ if ( bRender ) { sRendered = oSettings.aoColumns[iColumn].fnRender( { "iDataRow": iRow, "iDataColumn": iColumn, "aData": oSettings.aoData[iRow]._aData, "oSettings": oSettings } ); nCell.innerHTML = sRendered; if ( oSettings.aoColumns[iColumn].bUseRendered ) { /* Use the rendered data for filtering/sorting */ oSettings.aoData[iRow]._aData[iColumn] = sRendered; } } /* Classes */ if ( bClass ) { nCell.className += ' '+oSettings.aoColumns[iColumn].sClass; } /* Column visability */ if ( !bVisible ) { oSettings.aoData[iRow]._anHidden[iColumn] = nCell; nCell.parentNode.removeChild( nCell ); } } } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Drawing functions */ /* * Function: _fnDrawHead * Purpose: Create the HTML header for the table * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnDrawHead( oSettings ) { var i, nTh, iLen; var iThs = oSettings.nTable.getElementsByTagName('thead')[0].getElementsByTagName('th').length; var iCorrector = 0; /* If there is a header in place - then use it - otherwise it's going to get nuked... */ if ( iThs !== 0 ) { /* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { //oSettings.aoColumns[i].nTh = nThs[i]; nTh = oSettings.aoColumns[i].nTh; if ( oSettings.aoColumns[i].bVisible ) { /* Set width */ if ( oSettings.aoColumns[i].sWidth !== null ) { nTh.style.width = oSettings.aoColumns[i].sWidth; } /* Set the title of the column if it is user defined (not what was auto detected) */ if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML ) { nTh.innerHTML = oSettings.aoColumns[i].sTitle; } } else { nTh.parentNode.removeChild( nTh ); iCorrector++; } } } else { /* We don't have a header in the DOM - so we are going to have to create one */ var nTr = document.createElement( "tr" ); for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { nTh = oSettings.aoColumns[i].nTh; nTh.innerHTML = oSettings.aoColumns[i].sTitle; if ( oSettings.aoColumns[i].bVisible ) { if ( oSettings.aoColumns[i].sClass !== null ) { nTh.className = oSettings.aoColumns[i].sClass; } if ( oSettings.aoColumns[i].sWidth !== null ) { nTh.style.width = oSettings.aoColumns[i].sWidth; } nTr.appendChild( nTh ); } } $('thead:eq(0)', oSettings.nTable).html( '' )[0].appendChild( nTr ); } /* Add the extra markup needed by jQuery UI's themes */ if ( oSettings.bJUI ) { for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { oSettings.aoColumns[i].nTh.insertBefore( document.createElement('span'), oSettings.aoColumns[i].nTh.firstChild ); } } /* Add sort listener */ if ( oSettings.oFeatures.bSort ) { for ( i=0 ; i<oSettings.aoColumns.length ; i++ ) { if ( oSettings.aoColumns[i].bSortable !== false ) { _fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i ); } else { $(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone ); } } /* Take the brutal approach to cancelling text selection due to the shift key */ $('thead:eq(0) th', oSettings.nTable).mousedown( function (e) { if ( e.shiftKey ) { this.onselectstart = function() { return false; }; return false; } } ); } /* Cache the footer elements */ var nTfoot = oSettings.nTable.getElementsByTagName('tfoot'); if ( nTfoot.length !== 0 ) { iCorrector = 0; var nTfs = nTfoot[0].getElementsByTagName('th'); for ( i=0, iLen=nTfs.length ; i<iLen ; i++ ) { oSettings.aoColumns[i].nTf = nTfs[i-iCorrector]; if ( !oSettings.aoColumns[i].bVisible ) { nTfs[i-iCorrector].parentNode.removeChild( nTfs[i-iCorrector] ); iCorrector++; } } } } /* * Function: _fnDraw * Purpose: Insert the required TR nodes into the table for display * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnDraw( oSettings ) { var i, iLen; var anRows = []; var iRowCount = 0; var bRowError = false; var iStrips = oSettings.asStripClasses.length; var iOpenRows = oSettings.aoOpenRows.length; /* If we are dealing with Ajax - do it here */ if ( oSettings.oFeatures.bServerSide && !_fnAjaxUpdate( oSettings ) ) { return; } /* Check and see if we have an initial draw position from state saving */ if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 ) { oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ? 0 : oSettings.iInitDisplayStart; oSettings.iInitDisplayStart = -1; _fnCalculateEnd( oSettings ); } if ( oSettings.aiDisplay.length !== 0 ) { var iStart = oSettings._iDisplayStart; var iEnd = oSettings._iDisplayEnd; if ( oSettings.oFeatures.bServerSide ) { iStart = 0; iEnd = oSettings.aoData.length; } for ( var j=iStart ; j<iEnd ; j++ ) { var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ]; var nRow = aoData.nTr; /* Remove the old stripping classes and then add the new one */ if ( iStrips !== 0 ) { var sStrip = oSettings.asStripClasses[ iRowCount % iStrips ]; if ( aoData._sRowStripe != sStrip ) { $(nRow).removeClass( aoData._sRowStripe ).addClass( sStrip ); aoData._sRowStripe = sStrip; } } /* Custom row callback function - might want to manipule the row */ if ( typeof oSettings.fnRowCallback == "function" ) { nRow = oSettings.fnRowCallback( nRow, oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j ); if ( !nRow && !bRowError ) { alert( "DataTables warning: A node was not returned by fnRowCallback" ); bRowError = true; } } anRows.push( nRow ); iRowCount++; /* If there is an open row - and it is attached to this parent - attach it on redraw */ if ( iOpenRows !== 0 ) { for ( var k=0 ; k<iOpenRows ; k++ ) { if ( nRow == oSettings.aoOpenRows[k].nParent ) { anRows.push( oSettings.aoOpenRows[k].nTr ); } } } } } else { /* Table is empty - create a row with an empty message in it */ anRows[ 0 ] = document.createElement( 'tr' ); if ( typeof oSettings.asStripClasses[0] != 'undefined' ) { anRows[ 0 ].className = oSettings.asStripClasses[0]; } var nTd = document.createElement( 'td' ); nTd.setAttribute( 'valign', "top" ); nTd.colSpan = oSettings.aoColumns.length; nTd.className = oSettings.oClasses.sRowEmpty; nTd.innerHTML = oSettings.oLanguage.sZeroRecords; anRows[ iRowCount ].appendChild( nTd ); } /* Callback the header and footer custom funcation if there is one */ if ( typeof oSettings.fnHeaderCallback == 'function' ) { oSettings.fnHeaderCallback( $('thead:eq(0)>tr', oSettings.nTable)[0], _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ); } if ( typeof oSettings.fnFooterCallback == 'function' ) { oSettings.fnFooterCallback( $('tfoot:eq(0)>tr', oSettings.nTable)[0], _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ); } /* * Need to remove any old row from the display - note we can't just empty the tbody using * $().html('') since this will unbind the jQuery event handlers (even although the node * still exists!) - equally we can't use innerHTML, since IE throws an exception. */ var nBody = oSettings.nTable.getElementsByTagName('tbody'); if ( nBody[0] ) { var nTrs = nBody[0].childNodes; for ( i=nTrs.length-1 ; i>=0 ; i-- ) { nTrs[i].parentNode.removeChild( nTrs[i] ); } /* Put the draw table into the dom */ for ( i=0, iLen=anRows.length ; i<iLen ; i++ ) { nBody[0].appendChild( anRows[i] ); } } /* Call all required callback functions for the end of a draw */ for ( i=0, iLen=oSettings.aoDrawCallback.length ; i<iLen ; i++ ) { oSettings.aoDrawCallback[i].fn( oSettings ); } /* Draw is complete, sorting and filtering must be as well */ oSettings.bSorted = false; oSettings.bFiltered = false; /* Perform certain DOM operations after the table has been drawn for the first time */ if ( typeof oSettings._bInitComplete == "undefined" ) { oSettings._bInitComplete = true; /* Set an absolute width for the table such that pagination doesn't * cause the table to resize */ if ( oSettings.oFeatures.bAutoWidth && oSettings.nTable.offsetWidth !== 0 ) { oSettings.nTable.style.width = oSettings.nTable.offsetWidth+"px"; } } } /* * Function: _fnReDraw * Purpose: Redraw the table - taking account of the various features which are enabled * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnReDraw( oSettings ) { if ( oSettings.oFeatures.bSort ) { /* Sorting will refilter and draw for us */ _fnSort( oSettings, oSettings.oPreviousSearch ); } else if ( oSettings.oFeatures.bFilter ) { /* Filtering will redraw for us */ _fnFilterComplete( oSettings, oSettings.oPreviousSearch ); } else { _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } } /* * Function: _fnAjaxUpdate * Purpose: Update the table using an Ajax call * Returns: bool: block the table drawing or not * Inputs: object:oSettings - dataTables settings object */ function _fnAjaxUpdate( oSettings ) { if ( oSettings.bAjaxDataGet ) { _fnProcessingDisplay( oSettings, true ); var iColumns = oSettings.aoColumns.length; var aoData = []; var i; /* Paging and general */ oSettings.iServerDraw++; aoData.push( { "name": "sEcho", "value": oSettings.iServerDraw } ); aoData.push( { "name": "iColumns", "value": iColumns } ); aoData.push( { "name": "sColumns", "value": _fnColumnOrdering(oSettings) } ); aoData.push( { "name": "iDisplayStart", "value": oSettings._iDisplayStart } ); aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ? oSettings._iDisplayLength : -1 } ); /* Filtering */ if ( oSettings.oFeatures.bFilter !== false ) { aoData.push( { "name": "sSearch", "value": oSettings.oPreviousSearch.sSearch } ); aoData.push( { "name": "bEscapeRegex", "value": oSettings.oPreviousSearch.bEscapeRegex } ); for ( i=0 ; i<iColumns ; i++ ) { aoData.push( { "name": "sSearch_"+i, "value": oSettings.aoPreSearchCols[i].sSearch } ); aoData.push( { "name": "bEscapeRegex_"+i, "value": oSettings.aoPreSearchCols[i].bEscapeRegex } ); aoData.push( { "name": "bSearchable_"+i, "value": oSettings.aoColumns[i].bSearchable } ); } } /* Sorting */ if ( oSettings.oFeatures.bSort !== false ) { var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0; var iUser = oSettings.aaSorting.length; aoData.push( { "name": "iSortingCols", "value": iFixed+iUser } ); for ( i=0 ; i<iFixed ; i++ ) { aoData.push( { "name": "iSortCol_"+i, "value": oSettings.aaSortingFixed[i][0] } ); aoData.push( { "name": "sSortDir_"+i, "value": oSettings.aaSortingFixed[i][1] } ); } for ( i=0 ; i<iUser ; i++ ) { aoData.push( { "name": "iSortCol_"+(i+iFixed), "value": oSettings.aaSorting[i][0] } ); aoData.push( { "name": "sSortDir_"+(i+iFixed), "value": oSettings.aaSorting[i][1] } ); } for ( i=0 ; i<iColumns ; i++ ) { aoData.push( { "name": "bSortable_"+i, "value": oSettings.aoColumns[i].bSortable } ); } } oSettings.fnServerData( oSettings.sAjaxSource, aoData, function(json) { _fnAjaxUpdateDraw( oSettings, json ); } ); return false; } else { return true; } } /* * Function: _fnAjaxUpdateDraw * Purpose: Data the data from the server (nuking the old) and redraw the table * Returns: - * Inputs: object:oSettings - dataTables settings object * object:json - json data return from the server. * The following must be defined: * iTotalRecords, iTotalDisplayRecords, aaData * The following may be defined: * sColumns */ function _fnAjaxUpdateDraw ( oSettings, json ) { if ( typeof json.sEcho != 'undefined' ) { /* Protect against old returns over-writing a new one. Possible when you get * very fast interaction, and later queires are completed much faster */ if ( json.sEcho*1 < oSettings.iServerDraw ) { return; } else { oSettings.iServerDraw = json.sEcho * 1; } } _fnClearTable( oSettings ); oSettings._iRecordsTotal = json.iTotalRecords; oSettings._iRecordsDisplay = json.iTotalDisplayRecords; /* Determine if reordering is required */ var sOrdering = _fnColumnOrdering(oSettings); var bReOrder = (typeof json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering ); if ( bReOrder ) { var aiIndex = _fnReOrderIndex( oSettings, json.sColumns ); } for ( var i=0, iLen=json.aaData.length ; i<iLen ; i++ ) { if ( bReOrder ) { /* If we need to re-order, then create a new array with the correct order and add it */ var aData = []; for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ ) { aData.push( json.aaData[i][ aiIndex[j] ] ); } _fnAddData( oSettings, aData ); } else { /* No re-order required, sever got it "right" - just straight add */ _fnAddData( oSettings, json.aaData[i] ); } } oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); oSettings.bAjaxDataGet = false; _fnDraw( oSettings ); oSettings.bAjaxDataGet = true; _fnProcessingDisplay( oSettings, false ); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Options (features) HTML */ /* * Function: _fnAddOptionsHtml * Purpose: Add the options to the page HTML for the table * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnAddOptionsHtml ( oSettings ) { /* * Create a temporary, empty, div which we can later on replace with what we have generated * we do it this way to rendering the 'options' html offline - speed :-) */ var nHolding = document.createElement( 'div' ); oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable ); /* * All DataTables are wrapped in a div - this is not currently optional - backwards * compatability. It can be removed if you don't want it. */ var nWrapper = document.createElement( 'div' ); nWrapper.className = oSettings.oClasses.sWrapper; if ( oSettings.sTableId !== '' ) { nWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' ); } /* Track where we want to insert the option */ var nInsertNode = nWrapper; /* Substitute any constants in the dom string */ var sDom = oSettings.sDom.replace( "H", "fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix" ); sDom = sDom.replace( "F", "fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix" ); /* Loop over the user set positioning and place the elements as needed */ var aDom = sDom.split(''); var nTmp, iPushFeature, cOption, nNewNode, cNext, sClass, j; for ( var i=0 ; i<aDom.length ; i++ ) { iPushFeature = 0; cOption = aDom[i]; if ( cOption == '<' ) { /* New container div */ nNewNode = document.createElement( 'div' ); /* Check to see if we should append a class name to the container */ cNext = aDom[i+1]; if ( cNext == "'" || cNext == '"' ) { sClass = ""; j = 2; while ( aDom[i+j] != cNext ) { sClass += aDom[i+j]; j++; } nNewNode.className = sClass; i += j; /* Move along the position array */ } nInsertNode.appendChild( nNewNode ); nInsertNode = nNewNode; } else if ( cOption == '>' ) { /* End container div */ nInsertNode = nInsertNode.parentNode; } else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange ) { /* Length */ nTmp = _fnFeatureHtmlLength( oSettings ); iPushFeature = 1; } else if ( cOption == 'f' && oSettings.oFeatures.bFilter ) { /* Filter */ nTmp = _fnFeatureHtmlFilter( oSettings ); iPushFeature = 1; } else if ( cOption == 'r' && oSettings.oFeatures.bProcessing ) { /* pRocessing */ nTmp = _fnFeatureHtmlProcessing( oSettings ); iPushFeature = 1; } else if ( cOption == 't' ) { /* Table */ nTmp = oSettings.nTable; iPushFeature = 1; } else if ( cOption == 'i' && oSettings.oFeatures.bInfo ) { /* Info */ nTmp = _fnFeatureHtmlInfo( oSettings ); iPushFeature = 1; } else if ( cOption == 'p' && oSettings.oFeatures.bPaginate ) { /* Pagination */ nTmp = _fnFeatureHtmlPaginate( oSettings ); iPushFeature = 1; } else if ( _oExt.aoFeatures.length !== 0 ) { /* Plug-in features */ var aoFeatures = _oExt.aoFeatures; for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ ) { if ( cOption == aoFeatures[k].cFeature ) { nTmp = aoFeatures[k].fnInit( oSettings ); if ( nTmp ) { iPushFeature = 1; } break; } } } /* Add to the 2D features array */ if ( iPushFeature == 1 ) { if ( typeof oSettings.aanFeatures[cOption] != 'object' ) { oSettings.aanFeatures[cOption] = []; } oSettings.aanFeatures[cOption].push( nTmp ); nInsertNode.appendChild( nTmp ); } } /* Built our DOM structure - replace the holding div with what we want */ nHolding.parentNode.replaceChild( nWrapper, nHolding ); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Feature: Filtering */ /* * Function: _fnFeatureHtmlFilter * Purpose: Generate the node required for filtering text * Returns: node * Inputs: object:oSettings - dataTables settings object */ function _fnFeatureHtmlFilter ( oSettings ) { var nFilter = document.createElement( 'div' ); if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.f == "undefined" ) { nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' ); } nFilter.className = oSettings.oClasses.sFilter; var sSpace = oSettings.oLanguage.sSearch==="" ? "" : " "; nFilter.innerHTML = oSettings.oLanguage.sSearch+sSpace+'<input type="text" />'; var jqFilter = $("input", nFilter); jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','"') ); jqFilter.keyup( function(e) { /* Update all other filter input elements for the new display */ var n = oSettings.aanFeatures.f; for ( var i=0, iLen=n.length ; i<iLen ; i++ ) { if ( n[i] != this.parentNode ) { $('input', n[i]).val( this.value ); } } /* Now do the filter */ _fnFilterComplete( oSettings, { "sSearch": this.value, "bEscapeRegex": oSettings.oPreviousSearch.bEscapeRegex } ); } ); jqFilter.keypress( function(e) { /* Prevent default */ if ( e.keyCode == 13 ) { return false; } } ); return nFilter; } /* * Function: _fnFilterComplete * Purpose: Filter the table using both the global filter and column based filtering * Returns: - * Inputs: object:oSettings - dataTables settings object * object:oSearch: search information * int:iForce - optional - force a research of the master array (1) or not (undefined or 0) */ function _fnFilterComplete ( oSettings, oInput, iForce ) { /* Filter on everything */ _fnFilter( oSettings, oInput.sSearch, iForce, oInput.bEscapeRegex ); /* Now do the individual column filter */ for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ ) { _fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i, oSettings.aoPreSearchCols[i].bEscapeRegex ); } /* Custom filtering */ if ( _oExt.afnFiltering.length !== 0 ) { _fnFilterCustom( oSettings ); } /* Tell the draw function we have been filtering */ oSettings.bFiltered = true; /* Redraw the table */ oSettings._iDisplayStart = 0; _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); /* Rebuild search array 'offline' */ _fnBuildSearchArray( oSettings, 0 ); } /* * Function: _fnFilterCustom * Purpose: Apply custom filtering functions * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnFilterCustom( oSettings ) { var afnFilters = _oExt.afnFiltering; for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ ) { var iCorrector = 0; for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ ) { var iDisIndex = oSettings.aiDisplay[j-iCorrector]; /* Check if we should use this row based on the filtering function */ if ( !afnFilters[i]( oSettings, oSettings.aoData[iDisIndex]._aData, iDisIndex ) ) { oSettings.aiDisplay.splice( j-iCorrector, 1 ); iCorrector++; } } } } /* * Function: _fnFilterColumn * Purpose: Filter the table on a per-column basis * Returns: - * Inputs: object:oSettings - dataTables settings object * string:sInput - string to filter on * int:iColumn - column to filter * bool:bEscapeRegex - escape regex or not */ function _fnFilterColumn ( oSettings, sInput, iColumn, bEscapeRegex ) { if ( sInput === "" ) { return; } var iIndexCorrector = 0; var sRegexMatch = bEscapeRegex ? _fnEscapeRegex( sInput ) : sInput; var rpSearch = new RegExp( sRegexMatch, "i" ); for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- ) { var sData = _fnDataToSearch( oSettings.aoData[ oSettings.aiDisplay[i] ]._aData[iColumn], oSettings.aoColumns[iColumn].sType ); if ( ! rpSearch.test( sData ) ) { oSettings.aiDisplay.splice( i, 1 ); iIndexCorrector++; } } } /* * Function: _fnFilter * Purpose: Filter the data table based on user input and draw the table * Returns: - * Inputs: object:oSettings - dataTables settings object * string:sInput - string to filter on * int:iForce - optional - force a research of the master array (1) or not (undefined or 0) * bool:bEscapeRegex - escape regex or not */ function _fnFilter( oSettings, sInput, iForce, bEscapeRegex ) { var i; /* Check if we are forcing or not - optional parameter */ if ( typeof iForce == 'undefined' || iForce === null ) { iForce = 0; } /* Need to take account of custom filtering functions always */ if ( _oExt.afnFiltering.length !== 0 ) { iForce = 1; } /* Generate the regular expression to use. Something along the lines of: * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$ */ var asSearch = bEscapeRegex ? _fnEscapeRegex( sInput ).split( ' ' ) : sInput.split( ' ' ); var sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$'; var rpSearch = new RegExp( sRegExpString, "i" ); /* case insensitive */ /* * If the input is blank - we want the full data set */ if ( sInput.length <= 0 ) { oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length); oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); } else { /* * We are starting a new search or the new search string is smaller * then the old one (i.e. delete). Search from the master array */ if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length || oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 || sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 ) { /* Nuke the old display array - we are going to rebuild it */ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length); /* Force a rebuild of the search array */ _fnBuildSearchArray( oSettings, 1 ); /* Search through all records to populate the search array * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1 * mapping */ for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ ) { if ( rpSearch.test(oSettings.asDataSearch[i]) ) { oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] ); } } } else { /* Using old search array - refine it - do it this way for speed * Don't have to search the whole master array again */ var iIndexCorrector = 0; /* Search the current results */ for ( i=0 ; i<oSettings.asDataSearch.length ; i++ ) { if ( ! rpSearch.test(oSettings.asDataSearch[i]) ) { oSettings.aiDisplay.splice( i-iIndexCorrector, 1 ); iIndexCorrector++; } } } } oSettings.oPreviousSearch.sSearch = sInput; oSettings.oPreviousSearch.bEscapeRegex = bEscapeRegex; } /* * Function: _fnBuildSearchArray * Purpose: Create an array which can be quickly search through * Returns: - * Inputs: object:oSettings - dataTables settings object * int:iMaster - use the master data array - optional */ function _fnBuildSearchArray ( oSettings, iMaster ) { /* Clear out the old data */ oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length ); var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ? oSettings.aiDisplayMaster : oSettings.aiDisplay; for ( var i=0, iLen=aArray.length ; i<iLen ; i++ ) { oSettings.asDataSearch[i] = ''; for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ ) { if ( oSettings.aoColumns[j].bSearchable ) { var sData = oSettings.aoData[ aArray[i] ]._aData[j]; oSettings.asDataSearch[i] += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+' '; } } } } /* * Function: _fnDataToSearch * Purpose: Convert raw data into something that the user can search on * Returns: string: - search string * Inputs: string:sData - data to be modified * string:sType - data type */ function _fnDataToSearch ( sData, sType ) { if ( typeof _oExt.ofnSearch[sType] == "function" ) { return _oExt.ofnSearch[sType]( sData ); } else if ( sType == "html" ) { return sData.replace(/\n/g," ").replace( /<.*?>/g, "" ); } else if ( typeof sData == "string" ) { return sData.replace(/\n/g," "); } return sData; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Feature: Sorting */ /* * Function: _fnSort * Purpose: Change the order of the table * Returns: - * Inputs: object:oSettings - dataTables settings object * bool:bApplyClasses - optional - should we apply classes or not * Notes: We always sort the master array and then apply a filter again * if it is needed. This probably isn't optimal - but atm I can't think * of any other way which is (each has disadvantages). we want to sort aiDisplayMaster - * but according to aoData[]._aData */ function _fnSort ( oSettings, bApplyClasses ) { var aaSort = []; var oSort = _oExt.oSort; var aoData = oSettings.aoData; var iDataSort; var iDataType; var i, j, jLen; /* No sorting required if server-side or no sorting array */ if ( !oSettings.oFeatures.bServerSide && (oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) ) { if ( oSettings.aaSortingFixed !== null ) { aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting ); } else { aaSort = oSettings.aaSorting.slice(); } /* If there is a sorting data type, and a fuction belonging to it, then we need to * get the data from the developer's function and apply it for this column */ for ( i=0 ; i<aaSort.length ; i++ ) { var iColumn = aaSort[i][0]; var sDataType = oSettings.aoColumns[ iColumn ].sSortDataType; if ( typeof _oExt.afnSortData[sDataType] != 'undefined' ) { var iCorrector = 0; var aData = _oExt.afnSortData[sDataType]( oSettings, iColumn ); for ( j=0, jLen=aoData.length ; j<jLen ; j++ ) { if ( aoData[j] !== null ) { aoData[j]._aData[iColumn] = aData[iCorrector]; iCorrector++; } } } } /* DataTables offers two different methods for doing the 2D array sorting over multiple * columns. The first is to construct a function dynamically, and then evaluate and run * the function, while the second has no need for evalulation, but is a little bit slower. * This is used for environments which do not allow eval() for code execuation such as AIR */ if ( !window.runtime ) { /* Dynamically created sorting function. Based on the information that we have, we can * create a sorting function as if it were specifically written for this sort. Here we * want to build a function something like (for two column sorting): * fnLocalSorting = function(a,b){ * var iTest; * iTest = oSort['string-asc']('data11', 'data12'); * if (iTest === 0) * iTest = oSort['numeric-desc']('data21', 'data22'); * if (iTest === 0) * return oSort['numeric-desc'](1,2); * return iTest; * } * So basically we have a test for each column, and if that column matches, test the * next one. If all columns match, then we use a numeric sort on the position the two * row have in the original data array in order to provide a stable sort. */ var fnLocalSorting; var sDynamicSort = "fnLocalSorting = function(a,b){"+ "var iTest;"; for ( i=0 ; i<aaSort.length-1 ; i++ ) { iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort; iDataType = oSettings.aoColumns[ iDataSort ].sType; sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[i][1]+"']"+ "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); if ( iTest === 0 )"; } iDataSort = oSettings.aoColumns[ aaSort[aaSort.length-1][0] ].iDataSort; iDataType = oSettings.aoColumns[ iDataSort ].sType; sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[aaSort.length-1][1]+"']"+ "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] );"+ "if (iTest===0) return oSort['numeric-"+aaSort[aaSort.length-1][1]+"'](a, b); "+ "return iTest;}"; /* The eval has to be done to a variable for IE */ eval( sDynamicSort ); oSettings.aiDisplayMaster.sort( fnLocalSorting ); } else { /* * Non-eval() sorting (AIR and other environments which doesn't allow code in eval() * Note that for reasonable sized data sets this method is around 1.5 times slower than * the eval above (hence why it is not used all the time). Oddly enough, it is ever so * slightly faster for very small sets (presumably the eval has overhead). * Single column (1083 records) - eval: 32mS AIR: 38mS * Two columns (1083 records) - eval: 55mS AIR: 66mS */ /* Build a cached array so the sort doesn't have to process this stuff on every call */ var aAirSort = []; var iLen = aaSort.length; for ( i=0 ; i<iLen ; i++ ) { iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort; aAirSort.push( [ iDataSort, oSettings.aoColumns[ iDataSort ].sType+'-'+aaSort[i][1] ] ); } oSettings.aiDisplayMaster.sort( function (a,b) { var iTest; for ( var i=0 ; i<iLen ; i++ ) { iTest = oSort[ aAirSort[i][1] ]( aoData[a]._aData[aAirSort[i][0]], aoData[b]._aData[aAirSort[i][0]] ); if ( iTest !== 0 ) { return iTest; } } return 0; } ); } } /* Alter the sorting classes to take account of the changes */ if ( typeof bApplyClasses == 'undefined' || bApplyClasses ) { _fnSortingClasses( oSettings ); } /* Tell the draw function that we have sorted the data */ oSettings.bSorted = true; /* Copy the master data into the draw array and re-draw */ if ( oSettings.oFeatures.bFilter ) { /* _fnFilter() will redraw the table for us */ _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 ); } else { oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); oSettings._iDisplayStart = 0; /* reset display back to page 0 */ _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } } /* * Function: _fnSortAttachListener * Purpose: Attach a sort handler (click) to a node * Returns: - * Inputs: object:oSettings - dataTables settings object * node:nNode - node to attach the handler to * int:iDataIndex - column sorting index * function:fnCallback - callback function - optional */ function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback ) { $(nNode).click( function (e) { /* If the column is not sortable - don't to anything */ if ( oSettings.aoColumns[iDataIndex].bSortable === false ) { return; } /* * This is a little bit odd I admit... I declare a temporary function inside the scope of * _fnDrawHead and the click handler in order that the code presented here can be used * twice - once for when bProcessing is enabled, and another time for when it is * disabled, as we need to perform slightly different actions. * Basically the issue here is that the Javascript engine in modern browsers don't * appear to allow the rendering engine to update the display while it is still excuting * it's thread (well - it does but only after long intervals). This means that the * 'processing' display doesn't appear for a table sort. To break the js thread up a bit * I force an execution break by using setTimeout - but this breaks the expected * thread continuation for the end-developer's point of view (their code would execute * too early), so we on;y do it when we absolutely have to. */ var fnInnerSorting = function () { var iColumn, iNextSort; /* If the shift key is pressed then we are multipe column sorting */ if ( e.shiftKey ) { /* Are we already doing some kind of sort on this column? */ var bFound = false; for ( var i=0 ; i<oSettings.aaSorting.length ; i++ ) { if ( oSettings.aaSorting[i][0] == iDataIndex ) { bFound = true; iColumn = oSettings.aaSorting[i][0]; iNextSort = oSettings.aaSorting[i][2]+1; if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' ) { /* Reached the end of the sorting options, remove from multi-col sort */ oSettings.aaSorting.splice( i, 1 ); } else { /* Move onto next sorting direction */ oSettings.aaSorting[i][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort]; oSettings.aaSorting[i][2] = iNextSort; } break; } } /* No sort yet - add it in */ if ( bFound === false ) { oSettings.aaSorting.push( [ iDataIndex, oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] ); } } else { /* If no shift key then single column sort */ if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex ) { iColumn = oSettings.aaSorting[0][0]; iNextSort = oSettings.aaSorting[0][2]+1; if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' ) { iNextSort = 0; } oSettings.aaSorting[0][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort]; oSettings.aaSorting[0][2] = iNextSort; } else { oSettings.aaSorting.splice( 0, oSettings.aaSorting.length ); oSettings.aaSorting.push( [ iDataIndex, oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] ); } } /* Run the sort */ _fnSort( oSettings ); }; /* /fnInnerSorting */ if ( !oSettings.oFeatures.bProcessing ) { fnInnerSorting(); } else { _fnProcessingDisplay( oSettings, true ); setTimeout( function() { fnInnerSorting(); if ( !oSettings.oFeatures.bServerSide ) { _fnProcessingDisplay( oSettings, false ); } }, 0 ); } /* Call the user specified callback function - used for async user interaction */ if ( typeof fnCallback == 'function' ) { fnCallback( oSettings ); } } ); } /* * Function: _fnSortingClasses * Purpose: Set the sortting classes on the header * Returns: - * Inputs: object:oSettings - dataTables settings object * Notes: It is safe to call this function when bSort is false */ function _fnSortingClasses( oSettings ) { var i, iLen, j, jLen, iFound; var aaSort, sClass; var iColumns = oSettings.aoColumns.length; var oClasses = oSettings.oClasses; for ( i=0 ; i<iColumns ; i++ ) { if ( oSettings.aoColumns[i].bSortable ) { $(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc + " "+ oSettings.aoColumns[i].sSortingClass ); } } if ( oSettings.aaSortingFixed !== null ) { aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting ); } else { aaSort = oSettings.aaSorting.slice(); } /* Apply the required classes to the header */ for ( i=0 ; i<oSettings.aoColumns.length ; i++ ) { if ( oSettings.aoColumns[i].bSortable ) { sClass = oSettings.aoColumns[i].sSortingClass; iFound = -1; for ( j=0 ; j<aaSort.length ; j++ ) { if ( aaSort[j][0] == i ) { sClass = ( aaSort[j][1] == "asc" ) ? oClasses.sSortAsc : oClasses.sSortDesc; iFound = j; break; } } $(oSettings.aoColumns[i].nTh).addClass( sClass ); if ( oSettings.bJUI ) { /* jQuery UI uses extra markup */ var jqSpan = $("span", oSettings.aoColumns[i].nTh); jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+ oClasses.sSortJUI +" "+ oClasses.sSortJUIAscAllowed +" "+ oClasses.sSortJUIDescAllowed ); var sSpanClass; if ( iFound == -1 ) { sSpanClass = oSettings.aoColumns[i].sSortingClassJUI; } else if ( aaSort[iFound][1] == "asc" ) { sSpanClass = oClasses.sSortJUIAsc; } else { sSpanClass = oClasses.sSortJUIDesc; } jqSpan.addClass( sSpanClass ); } } else { /* No sorting on this column, so add the base class. This will have been assigned by * _fnAddColumn */ $(oSettings.aoColumns[i].nTh).addClass( oSettings.aoColumns[i].sSortingClass ); } } /* * Apply the required classes to the table body * Note that this is given as a feature switch since it can significantly slow down a sort * on large data sets (adding and removing of classes is always slow at the best of times..) * Further to this, note that this code is admitadly fairly ugly. It could be made a lot * simpiler using jQuery selectors and add/removeClass, but that is significantly slower * (on the order of 5 times slower) - hence the direct DOM manipulation here. */ sClass = oClasses.sSortColumn; if ( oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses ) { var nTds = _fnGetTdNodes( oSettings ); /* Remove the old classes */ if ( nTds.length >= iColumns ) { for ( i=0 ; i<iColumns ; i++ ) { if ( nTds[i].className.indexOf(sClass+"1") != -1 ) { for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ ) { nTds[(iColumns*j)+i].className = nTds[(iColumns*j)+i].className.replace( " "+sClass+"1", "" ); } } else if ( nTds[i].className.indexOf(sClass+"2") != -1 ) { for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ ) { nTds[(iColumns*j)+i].className = nTds[(iColumns*j)+i].className.replace( " "+sClass+"2", "" ); } } else if ( nTds[i].className.indexOf(sClass+"3") != -1 ) { for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ ) { nTds[(iColumns*j)+i].className = nTds[(iColumns*j)+i].className.replace( " "+sClass+"3", "" ); } } } } /* Add the new classes to the table */ var iClass = 1, iTargetCol; for ( i=0 ; i<aaSort.length ; i++ ) { iTargetCol = parseInt( aaSort[i][0], 10 ); for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ ) { nTds[(iColumns*j)+iTargetCol].className += " "+sClass+iClass; } if ( iClass < 3 ) { iClass++; } } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Feature: Pagination. Note that most of the paging logic is done in * _oExt.oPagination */ /* * Function: _fnFeatureHtmlPaginate * Purpose: Generate the node required for default pagination * Returns: node * Inputs: object:oSettings - dataTables settings object */ function _fnFeatureHtmlPaginate ( oSettings ) { var nPaginate = document.createElement( 'div' ); nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType; _oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate, function( oSettings ) { _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } ); /* Add a draw callback for the pagination on first instance, to update the paging display */ if ( typeof oSettings.aanFeatures.p == "undefined" ) { oSettings.aoDrawCallback.push( { "fn": function( oSettings ) { _oExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) { _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } ); }, "sName": "pagination" } ); } return nPaginate; } /* * Function: _fnPageChange * Purpose: Alter the display settings to change the page * Returns: bool:true - page has changed, false - no change (no effect) eg 'first' on page 1 * Inputs: object:oSettings - dataTables settings object * string:sAction - paging action to take: "first", "previous", "next" or "last" */ function _fnPageChange ( oSettings, sAction ) { var iOldStart = oSettings._iDisplayStart; if ( sAction == "first" ) { oSettings._iDisplayStart = 0; } else if ( sAction == "previous" ) { oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ? oSettings._iDisplayStart - oSettings._iDisplayLength : 0; /* Correct for underrun */ if ( oSettings._iDisplayStart < 0 ) { oSettings._iDisplayStart = 0; } } else if ( sAction == "next" ) { if ( oSettings._iDisplayLength >= 0 ) { /* Make sure we are not over running the display array */ if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() ) { oSettings._iDisplayStart += oSettings._iDisplayLength; } } else { oSettings._iDisplayStart = 0; } } else if ( sAction == "last" ) { if ( oSettings._iDisplayLength >= 0 ) { var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1; oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength; } else { oSettings._iDisplayStart = 0; } } else { alert( "DataTables warning: unknown paging action: "+sAction ); } return iOldStart != oSettings._iDisplayStart; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Feature: HTML info */ /* * Function: _fnFeatureHtmlInfo * Purpose: Generate the node required for the info display * Returns: node * Inputs: object:oSettings - dataTables settings object */ function _fnFeatureHtmlInfo ( oSettings ) { var nInfo = document.createElement( 'div' ); nInfo.className = oSettings.oClasses.sInfo; /* Actions that are to be taken once only for this feature */ if ( typeof oSettings.aanFeatures.i == "undefined" ) { /* Add draw callback */ oSettings.aoDrawCallback.push( { "fn": _fnUpdateInfo, "sName": "information" } ); /* Add id */ if ( oSettings.sTableId !== '' ) { nInfo.setAttribute( 'id', oSettings.sTableId+'_info' ); } } return nInfo; } /* * Function: _fnUpdateInfo * Purpose: Update the information elements in the display * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnUpdateInfo ( oSettings ) { /* Show information about the table */ if ( !oSettings.oFeatures.bInfo || oSettings.aanFeatures.i.length === 0 ) { return; } var nFirst = oSettings.aanFeatures.i[0]; if ( oSettings.fnRecordsDisplay() === 0 && oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() ) { /* Empty record set */ nFirst.innerHTML = oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix; } else if ( oSettings.fnRecordsDisplay() === 0 ) { /* Rmpty record set after filtering */ nFirst.innerHTML = oSettings.oLanguage.sInfoEmpty +' '+ oSettings.oLanguage.sInfoFiltered.replace('_MAX_', oSettings.fnRecordsTotal())+ oSettings.oLanguage.sInfoPostFix; } else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() ) { /* Normal record set */ nFirst.innerHTML = oSettings.oLanguage.sInfo. replace('_START_',oSettings._iDisplayStart+1). replace('_END_',oSettings.fnDisplayEnd()). replace('_TOTAL_',oSettings.fnRecordsDisplay())+ oSettings.oLanguage.sInfoPostFix; } else { /* Record set after filtering */ nFirst.innerHTML = oSettings.oLanguage.sInfo. replace('_START_',oSettings._iDisplayStart+1). replace('_END_',oSettings.fnDisplayEnd()). replace('_TOTAL_',oSettings.fnRecordsDisplay()) +' '+ oSettings.oLanguage.sInfoFiltered.replace('_MAX_', oSettings.fnRecordsTotal())+ oSettings.oLanguage.sInfoPostFix; } /* No point in recalculating for the other info elements, just copy the first one in */ var n = oSettings.aanFeatures.i; if ( n.length > 1 ) { var sInfo = nFirst.innerHTML; for ( var i=1, iLen=n.length ; i<iLen ; i++ ) { n[i].innerHTML = sInfo; } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Feature: Length change */ /* * Function: _fnFeatureHtmlLength * Purpose: Generate the node required for user display length changing * Returns: node * Inputs: object:oSettings - dataTables settings object */ function _fnFeatureHtmlLength ( oSettings ) { /* This can be overruled by not using the _MENU_ var/macro in the language variable */ var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"'; var sStdMenu = '<select size="1" '+sName+'>'+ '<option value="10">10</option>'+ '<option value="25">25</option>'+ '<option value="50">50</option>'+ '<option value="100">100</option>'+ '</select>'; var nLength = document.createElement( 'div' ); if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.l == "undefined" ) { nLength.setAttribute( 'id', oSettings.sTableId+'_length' ); } nLength.className = oSettings.oClasses.sLength; nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu ); /* * Set the length to the current display length - thanks to Andrea Pavlovic for this fix, * and Stefan Skopnik for fixing the fix! */ $('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true); $('select', nLength).change( function(e) { var iVal = $(this).val(); /* Update all other length options for the new display */ var n = oSettings.aanFeatures.l; for ( var i=0, iLen=n.length ; i<iLen ; i++ ) { if ( n[i] != this.parentNode ) { $('select', n[i]).val( iVal ); } } /* Redraw the table */ oSettings._iDisplayLength = parseInt(iVal, 10); _fnCalculateEnd( oSettings ); /* If we have space to show extra rows (backing up from the end point - then do so */ if ( oSettings._iDisplayEnd == oSettings.aiDisplay.length ) { oSettings._iDisplayStart = oSettings._iDisplayEnd - oSettings._iDisplayLength; if ( oSettings._iDisplayStart < 0 ) { oSettings._iDisplayStart = 0; } } if ( oSettings._iDisplayLength == -1 ) { oSettings._iDisplayStart = 0; } _fnDraw( oSettings ); } ); return nLength; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Feature: Processing incidator */ /* * Function: _fnFeatureHtmlProcessing * Purpose: Generate the node required for the processing node * Returns: node * Inputs: object:oSettings - dataTables settings object */ function _fnFeatureHtmlProcessing ( oSettings ) { var nProcessing = document.createElement( 'div' ); if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.r == "undefined" ) { nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' ); } nProcessing.innerHTML = oSettings.oLanguage.sProcessing; nProcessing.className = oSettings.oClasses.sProcessing; oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable ); return nProcessing; } /* * Function: _fnProcessingDisplay * Purpose: Display or hide the processing indicator * Returns: - * Inputs: object:oSettings - dataTables settings object * bool: * true - show the processing indicator * false - don't show */ function _fnProcessingDisplay ( oSettings, bShow ) { if ( oSettings.oFeatures.bProcessing ) { var an = oSettings.aanFeatures.r; for ( var i=0, iLen=an.length ; i<iLen ; i++ ) { an[i].style.visibility = bShow ? "visible" : "hidden"; } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Support functions */ /* * Function: _fnVisibleToColumnIndex * Purpose: Covert the index of a visible column to the index in the data array (take account * of hidden columns) * Returns: int:i - the data index * Inputs: object:oSettings - dataTables settings object */ function _fnVisibleToColumnIndex( oSettings, iMatch ) { var iColumn = -1; for ( var i=0 ; i<oSettings.aoColumns.length ; i++ ) { if ( oSettings.aoColumns[i].bVisible === true ) { iColumn++; } if ( iColumn == iMatch ) { return i; } } return null; } /* * Function: _fnColumnIndexToVisible * Purpose: Covert the index of an index in the data array and convert it to the visible * column index (take account of hidden columns) * Returns: int:i - the data index * Inputs: object:oSettings - dataTables settings object */ function _fnColumnIndexToVisible( oSettings, iMatch ) { var iVisible = -1; for ( var i=0 ; i<oSettings.aoColumns.length ; i++ ) { if ( oSettings.aoColumns[i].bVisible === true ) { iVisible++; } if ( i == iMatch ) { return oSettings.aoColumns[i].bVisible === true ? iVisible : null; } } return null; } /* * Function: _fnNodeToDataIndex * Purpose: Take a TR element and convert it to an index in aoData * Returns: int:i - index if found, null if not * Inputs: object:s - dataTables settings object * node:n - the TR element to find */ function _fnNodeToDataIndex( s, n ) { for ( var i=0, iLen=s.aoData.length ; i<iLen ; i++ ) { if ( s.aoData[i] !== null && s.aoData[i].nTr == n ) { return i; } } return null; } /* * Function: _fnVisbleColumns * Purpose: Get the number of visible columns * Returns: int:i - the number of visible columns * Inputs: object:oS - dataTables settings object */ function _fnVisbleColumns( oS ) { var iVis = 0; for ( var i=0 ; i<oS.aoColumns.length ; i++ ) { if ( oS.aoColumns[i].bVisible === true ) { iVis++; } } return iVis; } /* * Function: _fnCalculateEnd * Purpose: Rcalculate the end point based on the start point * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnCalculateEnd( oSettings ) { if ( oSettings.oFeatures.bPaginate === false ) { oSettings._iDisplayEnd = oSettings.aiDisplay.length; } else { /* Set the end point of the display - based on how many elements there are * still to display */ if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length || oSettings._iDisplayLength == -1 ) { oSettings._iDisplayEnd = oSettings.aiDisplay.length; } else { oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength; } } } /* * Function: _fnConvertToWidth * Purpose: Convert a CSS unit width to pixels (e.g. 2em) * Returns: int:iWidth - width in pixels * Inputs: string:sWidth - width to be converted * node:nParent - parent to get the with for (required for * relative widths) - optional */ function _fnConvertToWidth ( sWidth, nParent ) { if ( !sWidth || sWidth === null || sWidth === '' ) { return 0; } if ( typeof nParent == "undefined" ) { nParent = document.getElementsByTagName('body')[0]; } var iWidth; var nTmp = document.createElement( "div" ); nTmp.style.width = sWidth; nParent.appendChild( nTmp ); iWidth = nTmp.offsetWidth; nParent.removeChild( nTmp ); return ( iWidth ); } /* * Function: _fnCalculateColumnWidths * Purpose: Calculate the width of columns for the table * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnCalculateColumnWidths ( oSettings ) { var iTableWidth = oSettings.nTable.offsetWidth; var iTotalUserIpSize = 0; var iTmpWidth; var iVisibleColumns = 0; var iColums = oSettings.aoColumns.length; var i; var oHeaders = $('thead:eq(0)>th', oSettings.nTable); /* Convert any user input sizes into pixel sizes */ for ( i=0 ; i<iColums ; i++ ) { if ( oSettings.aoColumns[i].bVisible ) { iVisibleColumns++; if ( oSettings.aoColumns[i].sWidth !== null ) { iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidth, oSettings.nTable.parentNode ); /* Total up the user defined widths for later calculations */ iTotalUserIpSize += iTmpWidth; oSettings.aoColumns[i].sWidth = iTmpWidth+"px"; } } } /* If the number of columns in the DOM equals the number that we * have to process in dataTables, then we can use the offsets that are * created by the web-browser. No custom sizes can be set in order for * this to happen */ if ( iColums == oHeaders.length && iTotalUserIpSize === 0 && iVisibleColumns == iColums ) { for ( i=0 ; i<oSettings.aoColumns.length ; i++ ) { oSettings.aoColumns[i].sWidth = oHeaders[i].offsetWidth+"px"; } } else { /* Otherwise we are going to have to do some calculations to get * the width of each column. Construct a 1 row table with the maximum * string sizes in the data, and any user defined widths */ var nCalcTmp = oSettings.nTable.cloneNode( false ); nCalcTmp.setAttribute( "id", '' ); var sTableTmp = '<table class="'+nCalcTmp.className+'">'; var sCalcHead = "<tr>"; var sCalcHtml = "<tr>"; /* Construct a tempory table which we will inject (invisibly) into * the dom - to let the browser do all the hard word */ for ( i=0 ; i<iColums ; i++ ) { if ( oSettings.aoColumns[i].bVisible ) { sCalcHead += '<th>'+oSettings.aoColumns[i].sTitle+'</th>'; if ( oSettings.aoColumns[i].sWidth !== null ) { var sWidth = ''; if ( oSettings.aoColumns[i].sWidth !== null ) { sWidth = ' style="width:'+oSettings.aoColumns[i].sWidth+';"'; } sCalcHtml += '<td'+sWidth+' tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>'; } else { sCalcHtml += '<td tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>'; } } } sCalcHead += "</tr>"; sCalcHtml += "</tr>"; /* Create the tmp table node (thank you jQuery) */ nCalcTmp = $( sTableTmp + sCalcHead + sCalcHtml +'</table>' )[0]; nCalcTmp.style.width = iTableWidth + "px"; nCalcTmp.style.visibility = "hidden"; nCalcTmp.style.position = "absolute"; /* Try to aviod scroll bar */ oSettings.nTable.parentNode.appendChild( nCalcTmp ); var oNodes = $("tr:eq(1)>td", nCalcTmp); var iIndex; /* Gather in the browser calculated widths for the rows */ for ( i=0 ; i<oNodes.length ; i++ ) { iIndex = oNodes[i].getAttribute('tag_index'); var iContentWidth = $("td", nCalcTmp).eq(i).width(); var iSetWidth = oSettings.aoColumns[i].sWidth ? oSettings.aoColumns[i].sWidth.slice(0, -2) : 0; oSettings.aoColumns[iIndex].sWidth = Math.max(iContentWidth, iSetWidth) + "px"; } oSettings.nTable.parentNode.removeChild( nCalcTmp ); } } /* * Function: fnGetMaxLenString * Purpose: Get the maximum strlen for each data column * Returns: string: - max strlens for each column * Inputs: object:oSettings - dataTables settings object * int:iCol - column of interest */ function fnGetMaxLenString( oSettings, iCol ) { var iMax = 0; var iMaxIndex = -1; for ( var i=0 ; i<oSettings.aoData.length ; i++ ) { if ( oSettings.aoData[i]._aData[iCol].length > iMax ) { iMax = oSettings.aoData[i]._aData[iCol].length; iMaxIndex = i; } } if ( iMaxIndex >= 0 ) { return oSettings.aoData[iMaxIndex]._aData[iCol]; } return ''; } /* * Function: _fnArrayCmp * Purpose: Compare two arrays * Returns: 0 if match, 1 if length is different, 2 if no match * Inputs: array:aArray1 - first array * array:aArray2 - second array */ function _fnArrayCmp( aArray1, aArray2 ) { if ( aArray1.length != aArray2.length ) { return 1; } for ( var i=0 ; i<aArray1.length ; i++ ) { if ( aArray1[i] != aArray2[i] ) { return 2; } } return 0; } /* * Function: _fnDetectType * Purpose: Get the sort type based on an input string * Returns: string: - type (defaults to 'string' if no type can be detected) * Inputs: string:sData - data we wish to know the type of * Notes: This function makes use of the DataTables plugin objct _oExt * (.aTypes) such that new types can easily be added. */ function _fnDetectType( sData ) { var aTypes = _oExt.aTypes; var iLen = aTypes.length; for ( var i=0 ; i<iLen ; i++ ) { var sType = aTypes[i]( sData ); if ( sType !== null ) { return sType; } } return 'string'; } /* * Function: _fnSettingsFromNode * Purpose: Return the settings object for a particular table * Returns: object: Settings object - or null if not found * Inputs: node:nTable - table we are using as a dataTable */ function _fnSettingsFromNode ( nTable ) { for ( var i=0 ; i<_aoSettings.length ; i++ ) { if ( _aoSettings[i].nTable == nTable ) { return _aoSettings[i]; } } return null; } /* * Function: _fnGetDataMaster * Purpose: Return an array with the full table data * Returns: array array:aData - Master data array * Inputs: object:oSettings - dataTables settings object */ function _fnGetDataMaster ( oSettings ) { var aData = []; var iLen = oSettings.aoData.length; for ( var i=0 ; i<iLen; i++ ) { if ( oSettings.aoData[i] === null ) { aData.push( null ); } else { aData.push( oSettings.aoData[i]._aData ); } } return aData; } /* * Function: _fnGetTrNodes * Purpose: Return an array with the TR nodes for the table * Returns: array: - TR array * Inputs: object:oSettings - dataTables settings object */ function _fnGetTrNodes ( oSettings ) { var aNodes = []; var iLen = oSettings.aoData.length; for ( var i=0 ; i<iLen ; i++ ) { if ( oSettings.aoData[i] === null ) { aNodes.push( null ); } else { aNodes.push( oSettings.aoData[i].nTr ); } } return aNodes; } /* * Function: _fnGetTdNodes * Purpose: Return an array with the TD nodes for the table * Returns: array: - TD array * Inputs: object:oSettings - dataTables settings object */ function _fnGetTdNodes ( oSettings ) { var nTrs = _fnGetTrNodes( oSettings ); var nTds = [], nTd; var anReturn = []; var iCorrector; var iRow, iRows, iColumn, iColumns; for ( iRow=0, iRows=nTrs.length ; iRow<iRows ; iRow++ ) { nTds = []; for ( iColumn=0, iColumns=nTrs[iRow].childNodes.length ; iColumn<iColumns ; iColumn++ ) { nTd = nTrs[iRow].childNodes[iColumn]; if ( nTd.nodeName == "TD" ) { nTds.push( nTd ); } } iCorrector = 0; for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ ) { if ( oSettings.aoColumns[iColumn].bVisible ) { anReturn.push( nTds[iColumn-iCorrector] ); } else { anReturn.push( oSettings.aoData[iRow]._anHidden[iColumn] ); iCorrector++; } } } return anReturn; } /* * Function: _fnEscapeRegex * Purpose: scape a string stuch that it can be used in a regular expression * Returns: string: - escaped string * Inputs: string:sVal - string to escape */ function _fnEscapeRegex ( sVal ) { var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ]; var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' ); return sVal.replace(reReplace, '\\$1'); } /* * Function: _fnReOrderIndex * Purpose: Figure out how to reorder a display list * Returns: array int:aiReturn - index list for reordering * Inputs: object:oSettings - dataTables settings object */ function _fnReOrderIndex ( oSettings, sColumns ) { var aColumns = sColumns.split(','); var aiReturn = []; for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { for ( var j=0 ; j<iLen ; j++ ) { if ( oSettings.aoColumns[i].sName == aColumns[j] ) { aiReturn.push( j ); break; } } } return aiReturn; } /* * Function: _fnColumnOrdering * Purpose: Get the column ordering that DataTables expects * Returns: string: - comma separated list of names * Inputs: object:oSettings - dataTables settings object */ function _fnColumnOrdering ( oSettings ) { var sNames = ''; for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { sNames += oSettings.aoColumns[i].sName+','; } if ( sNames.length == iLen ) { return ""; } return sNames.slice(0, -1); } /* * Function: _fnClearTable * Purpose: Nuke the table * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnClearTable( oSettings ) { oSettings.aoData.length = 0; oSettings.aiDisplayMaster.length = 0; oSettings.aiDisplay.length = 0; _fnCalculateEnd( oSettings ); } /* * Function: _fnSaveState * Purpose: Save the state of a table in a cookie such that the page can be reloaded * Returns: - * Inputs: object:oSettings - dataTables settings object */ function _fnSaveState ( oSettings ) { if ( !oSettings.oFeatures.bStateSave ) { return; } /* Store the interesting variables */ var i; var sValue = "{"; sValue += '"iStart": '+oSettings._iDisplayStart+','; sValue += '"iEnd": '+oSettings._iDisplayEnd+','; sValue += '"iLength": '+oSettings._iDisplayLength+','; sValue += '"sFilter": "'+oSettings.oPreviousSearch.sSearch.replace('"','\\"')+'",'; sValue += '"sFilterEsc": '+oSettings.oPreviousSearch.bEscapeRegex+','; sValue += '"aaSorting": [ '; for ( i=0 ; i<oSettings.aaSorting.length ; i++ ) { sValue += "["+oSettings.aaSorting[i][0]+",'"+oSettings.aaSorting[i][1]+"'],"; } sValue = sValue.substring(0, sValue.length-1); sValue += "],"; sValue += '"aaSearchCols": [ '; for ( i=0 ; i<oSettings.aoPreSearchCols.length ; i++ ) { sValue += "['"+oSettings.aoPreSearchCols[i].sSearch.replace("'","\'")+ "',"+oSettings.aoPreSearchCols[i].bEscapeRegex+"],"; } sValue = sValue.substring(0, sValue.length-1); sValue += "],"; sValue += '"abVisCols": [ '; for ( i=0 ; i<oSettings.aoColumns.length ; i++ ) { sValue += oSettings.aoColumns[i].bVisible+","; } sValue = sValue.substring(0, sValue.length-1); sValue += "]"; sValue += "}"; _fnCreateCookie( "SpryMedia_DataTables_"+oSettings.sInstance, sValue, oSettings.iCookieDuration ); } /* * Function: _fnLoadState * Purpose: Attempt to load a saved table state from a cookie * Returns: - * Inputs: object:oSettings - dataTables settings object * object:oInit - DataTables init object so we can override settings */ function _fnLoadState ( oSettings, oInit ) { if ( !oSettings.oFeatures.bStateSave ) { return; } var oData; var sData = _fnReadCookie( "SpryMedia_DataTables_"+oSettings.sInstance ); if ( sData !== null && sData !== '' ) { /* Try/catch the JSON eval - if it is bad then we ignore it */ try { /* Use the JSON library for safety - if it is available */ if ( typeof JSON == 'object' && typeof JSON.parse == 'function' ) { /* DT 1.4.0 used single quotes for a string - JSON.parse doesn't allow this and throws * an error. So for now we can do this. This can be removed in future it is just to * allow the tranfrer to 1.4.1+ to occur */ oData = JSON.parse( sData.replace(/'/g, '"') ); } else { oData = eval( '('+sData+')' ); } } catch( e ) { return; } /* Restore key features */ oSettings._iDisplayStart = oData.iStart; oSettings.iInitDisplayStart = oData.iStart; oSettings._iDisplayEnd = oData.iEnd; oSettings._iDisplayLength = oData.iLength; oSettings.oPreviousSearch.sSearch = oData.sFilter; oSettings.aaSorting = oData.aaSorting.slice(); oSettings.saved_aaSorting = oData.aaSorting.slice(); /* Search filtering - global reference added in 1.4.1 */ if ( typeof oData.sFilterEsc != 'undefined' ) { oSettings.oPreviousSearch.bEscapeRegex = oData.sFilterEsc; } /* Column filtering - added in 1.5.0 beta 6 */ if ( typeof oData.aaSearchCols != 'undefined' ) { for ( var i=0 ; i<oData.aaSearchCols.length ; i++ ) { oSettings.aoPreSearchCols[i] = { "sSearch": oData.aaSearchCols[i][0], "bEscapeRegex": oData.aaSearchCols[i][1] }; } } /* Column visibility state - added in 1.5.0 beta 10 */ if ( typeof oData.abVisCols != 'undefined' ) { /* Pass back visibiliy settings to the init handler, but to do not here override * the init object that the user might have passed in */ oInit.saved_aoColumns = []; for ( i=0 ; i<oData.abVisCols.length ; i++ ) { oInit.saved_aoColumns[i] = {}; oInit.saved_aoColumns[i].bVisible = oData.abVisCols[i]; } } } } /* * Function: _fnCreateCookie * Purpose: Create a new cookie with a value to store the state of a table * Returns: - * Inputs: string:sName - name of the cookie to create * string:sValue - the value the cookie should take * int:iSecs - duration of the cookie */ function _fnCreateCookie ( sName, sValue, iSecs ) { var date = new Date(); date.setTime( date.getTime()+(iSecs*1000) ); /* * Shocking but true - it would appear IE has major issues with having the path being * set to anything but root. We need the cookie to be available based on the path, so we * have to append the pathname to the cookie name. Appalling. */ sName += '_'+window.location.pathname.replace(/[\/:]/g,"").toLowerCase(); document.cookie = sName+"="+encodeURIComponent(sValue)+ "; expires="+date.toGMTString()+"; path=/"; } /* * Function: _fnReadCookie * Purpose: Read an old cookie to get a cookie with an old table state * Returns: string: - contents of the cookie - or null if no cookie with that name found * Inputs: string:sName - name of the cookie to read */ function _fnReadCookie ( sName ) { var sNameEQ = sName +'_'+ window.location.pathname.replace(/[\/:]/g,"").toLowerCase() + "="; var sCookieContents = document.cookie.split(';'); for( var i=0 ; i<sCookieContents.length ; i++ ) { var c = sCookieContents[i]; while (c.charAt(0)==' ') { c = c.substring(1,c.length); } if (c.indexOf(sNameEQ) === 0) { return decodeURIComponent( c.substring(sNameEQ.length,c.length) ); } } return null; } /* * Function: _fnGetUniqueThs * Purpose: Get an array of unique th elements, one for each column * Returns: array node:aReturn - list of unique ths * Inputs: node:nThead - The thead element for the table */ function _fnGetUniqueThs ( nThead ) { var nTrs = nThead.getElementsByTagName('tr'); /* Nice simple case */ if ( nTrs.length == 1 ) { return nTrs[0].getElementsByTagName('th'); } /* Otherwise we need to figure out the layout array to get the nodes */ var aLayout = [], aReturn = []; var ROWSPAN = 2, COLSPAN = 3, TDELEM = 4; var i, j, k, iLen, jLen, iColumnShifted; var fnShiftCol = function ( a, i, j ) { while ( typeof a[i][j] != 'undefined' ) { j++; } return j; }; var fnAddRow = function ( i ) { if ( typeof aLayout[i] == 'undefined' ) { aLayout[i] = []; } }; /* Calculate a layout array */ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { fnAddRow( i ); var iColumn = 0; var nTds = []; for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ ) { if ( nTrs[i].childNodes[j].nodeName == "TD" || nTrs[i].childNodes[j].nodeName == "TH" ) { nTds.push( nTrs[i].childNodes[j] ); } } for ( j=0, jLen=nTds.length ; j<jLen ; j++ ) { var iColspan = nTds[j].getAttribute('colspan') * 1; var iRowspan = nTds[j].getAttribute('rowspan') * 1; if ( !iColspan || iColspan===0 || iColspan===1 ) { iColumnShifted = fnShiftCol( aLayout, i, iColumn ); aLayout[i][iColumnShifted] = (nTds[j].nodeName=="TD") ? TDELEM : nTds[j]; if ( iRowspan || iRowspan===0 || iRowspan===1 ) { for ( k=1 ; k<iRowspan ; k++ ) { fnAddRow( i+k ); aLayout[i+k][iColumnShifted] = ROWSPAN; } } iColumn++; } else { iColumnShifted = fnShiftCol( aLayout, i, iColumn ); for ( k=0 ; k<iColspan ; k++ ) { aLayout[i][iColumnShifted+k] = COLSPAN; } iColumn += iColspan; } } } /* Convert the layout array into a node array * Note the use of aLayout[0] in the outloop, we want the outer loop to occur the same * number of times as there are columns. Unusual having nested loops this way around * but is what we need here. */ for ( i=0, iLen=aLayout[0].length ; i<iLen ; i++ ) { for ( j=0, jLen=aLayout.length ; j<jLen ; j++ ) { if ( typeof aLayout[j][i] == 'object' ) { aReturn.push( aLayout[j][i] ); } } } return aReturn; } /* * Function: _fnMap * Purpose: See if a property is defined on one object, if so assign it to the other object * Returns: - (done by reference) * Inputs: object:oRet - target object * object:oSrc - source object * string:sName - property * string:sMappedName - name to map too - optional, sName used if not given */ function _fnMap( oRet, oSrc, sName, sMappedName ) { if ( typeof sMappedName == 'undefined' ) { sMappedName = sName; } if ( typeof oSrc[sName] != 'undefined' ) { oRet[sMappedName] = oSrc[sName]; } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - API * * I'm not overly happy with this solution - I'd much rather that there was a way of getting * a list of all the private functions and do what we need to dynamically - but that doesn't * appear to be possible. Bonkers. A better solution would be to provide a 'bind' type object * To do - bind type method in DTs 2.x. */ this.oApi._fnInitalise = _fnInitalise; this.oApi._fnLanguageProcess = _fnLanguageProcess; this.oApi._fnAddColumn = _fnAddColumn; this.oApi._fnAddData = _fnAddData; this.oApi._fnGatherData = _fnGatherData; this.oApi._fnDrawHead = _fnDrawHead; this.oApi._fnDraw = _fnDraw; this.oApi._fnAjaxUpdate = _fnAjaxUpdate; this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml; this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter; this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo; this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate; this.oApi._fnPageChange = _fnPageChange; this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength; this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing; this.oApi._fnProcessingDisplay = _fnProcessingDisplay; this.oApi._fnFilterComplete = _fnFilterComplete; this.oApi._fnFilterColumn = _fnFilterColumn; this.oApi._fnFilter = _fnFilter; this.oApi._fnSortingClasses = _fnSortingClasses; this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex; this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible; this.oApi._fnNodeToDataIndex = _fnNodeToDataIndex; this.oApi._fnVisbleColumns = _fnVisbleColumns; this.oApi._fnBuildSearchArray = _fnBuildSearchArray; this.oApi._fnDataToSearch = _fnDataToSearch; this.oApi._fnCalculateEnd = _fnCalculateEnd; this.oApi._fnConvertToWidth = _fnConvertToWidth; this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths; this.oApi._fnArrayCmp = _fnArrayCmp; this.oApi._fnDetectType = _fnDetectType; this.oApi._fnGetDataMaster = _fnGetDataMaster; this.oApi._fnGetTrNodes = _fnGetTrNodes; this.oApi._fnGetTdNodes = _fnGetTdNodes; this.oApi._fnEscapeRegex = _fnEscapeRegex; this.oApi._fnReOrderIndex = _fnReOrderIndex; this.oApi._fnColumnOrdering = _fnColumnOrdering; this.oApi._fnClearTable = _fnClearTable; this.oApi._fnSaveState = _fnSaveState; this.oApi._fnLoadState = _fnLoadState; this.oApi._fnCreateCookie = _fnCreateCookie; this.oApi._fnReadCookie = _fnReadCookie; this.oApi._fnGetUniqueThs = _fnGetUniqueThs; this.oApi._fnReDraw = _fnReDraw; /* Want to be able to reference "this" inside the this.each function */ var _that = this; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Section - Constructor */ return this.each(function() { var i=0, iLen, j, jLen; /* Sanity check that we are not re-initialising a table - if we are, alert an error */ for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ ) { if ( _aoSettings[i].nTable == this ) { alert( "DataTables warning: Unable to re-initialise DataTable. "+ "Please use the API to make any configuration changes required." ); return _aoSettings[i]; } } /* Make a complete and independent copy of the settings object */ var oSettings = new classSettings(); _aoSettings.push( oSettings ); var bInitHandedOff = false; var bUsePassedData = false; /* Set the id */ var sId = this.getAttribute( 'id' ); if ( sId !== null ) { oSettings.sTableId = sId; oSettings.sInstance = sId; } else { oSettings.sInstance = _oExt._oExternConfig.iNextUnique ++; } /* Set the table node */ oSettings.nTable = this; /* Bind the API functions to the settings, so we can perform actions whenever oSettings is * available */ oSettings.oApi = _that.oApi; /* Store the features that we have available */ if ( typeof oInit != 'undefined' && oInit !== null ) { _fnMap( oSettings.oFeatures, oInit, "bPaginate" ); _fnMap( oSettings.oFeatures, oInit, "bLengthChange" ); _fnMap( oSettings.oFeatures, oInit, "bFilter" ); _fnMap( oSettings.oFeatures, oInit, "bSort" ); _fnMap( oSettings.oFeatures, oInit, "bInfo" ); _fnMap( oSettings.oFeatures, oInit, "bProcessing" ); _fnMap( oSettings.oFeatures, oInit, "bAutoWidth" ); _fnMap( oSettings.oFeatures, oInit, "bSortClasses" ); _fnMap( oSettings.oFeatures, oInit, "bServerSide" ); _fnMap( oSettings, oInit, "asStripClasses" ); _fnMap( oSettings, oInit, "fnRowCallback" ); _fnMap( oSettings, oInit, "fnHeaderCallback" ); _fnMap( oSettings, oInit, "fnFooterCallback" ); _fnMap( oSettings, oInit, "fnInitComplete" ); _fnMap( oSettings, oInit, "fnServerData" ); _fnMap( oSettings, oInit, "aaSorting" ); _fnMap( oSettings, oInit, "aaSortingFixed" ); _fnMap( oSettings, oInit, "sPaginationType" ); _fnMap( oSettings, oInit, "sAjaxSource" ); _fnMap( oSettings, oInit, "iCookieDuration" ); _fnMap( oSettings, oInit, "sDom" ); _fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" ); _fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" ); _fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" ); _fnMap( oSettings, oInit, "bJQueryUI", "bJUI" ); if ( typeof oInit.fnDrawCallback == 'function' ) { /* Add user given callback function to array */ oSettings.aoDrawCallback.push( { "fn": oInit.fnDrawCallback, "sName": "user" } ); } if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses ) { /* Enable sort classes for server-side processing. Safe to do it here, since server-side * processing must be enabled by the developer */ oSettings.aoDrawCallback.push( { "fn": _fnSortingClasses, "sName": "server_side_sort_classes" } ); } if ( typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI ) { /* Use the JUI classes object for display. You could clone the oStdClasses object if * you want to have multiple tables with multiple independent classes */ oSettings.oClasses = _oExt.oJUIClasses; if ( typeof oInit.sDom == 'undefined' ) { /* Set the DOM to use a layout suitable for jQuery UI's theming */ oSettings.sDom = '<"H"lfr>t<"F"ip>'; } } if ( typeof oInit.iDisplayStart != 'undefined' && typeof oSettings.iInitDisplayStart == 'undefined' ) { /* Display start point, taking into account the save saving */ oSettings.iInitDisplayStart = oInit.iDisplayStart; oSettings._iDisplayStart = oInit.iDisplayStart; } /* Must be done after everything which can be overridden by a cookie! */ if ( typeof oInit.bStateSave != 'undefined' ) { oSettings.oFeatures.bStateSave = oInit.bStateSave; _fnLoadState( oSettings, oInit ); oSettings.aoDrawCallback.push( { "fn": _fnSaveState, "sName": "state_save" } ); } if ( typeof oInit.aaData != 'undefined' ) { bUsePassedData = true; } /* Backwards compatability */ /* aoColumns / aoData - remove at some point... */ if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' ) { oInit.aoColumns = oInit.aoData; } /* Language definitions */ if ( typeof oInit.oLanguage != 'undefined' ) { if ( typeof oInit.oLanguage.sUrl != 'undefined' && oInit.oLanguage.sUrl !== "" ) { /* Get the language definitions from a file */ oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl; $.getJSON( oSettings.oLanguage.sUrl, null, function( json ) { _fnLanguageProcess( oSettings, json, true ); } ); bInitHandedOff = true; } else { _fnLanguageProcess( oSettings, oInit.oLanguage, false ); } } /* Warning: The _fnLanguageProcess function is async to the remainder of this function due * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing * below is complete. The reason for spliting it like this is optimisation - we can fire * off the XHR (if needed) and then continue processing the data. */ } else { /* Create a dummy object for quick manipulation later on. */ oInit = {}; } /* Add the strip classes now that we know which classes to apply - unless overruled */ if ( typeof oInit.asStripClasses == 'undefined' ) { oSettings.asStripClasses.push( oSettings.oClasses.sStripOdd ); oSettings.asStripClasses.push( oSettings.oClasses.sStripEven ); } /* See if we should load columns automatically or use defined ones - a bit messy this... */ var nThead = this.getElementsByTagName('thead'); var nThs = nThead.length===0 ? null : _fnGetUniqueThs( nThead[0] ); var bUseCols = typeof oInit.aoColumns != 'undefined'; for ( i=0, iLen=bUseCols ? oInit.aoColumns.length : nThs.length ; i<iLen ; i++ ) { var oCol = bUseCols ? oInit.aoColumns[i] : null; var nTh = nThs ? nThs[i] : null; /* Check if we have column visibilty state to restore, and also that the length of the * state saved columns matches the currently know number of columns */ if ( typeof oInit.saved_aoColumns != 'undefined' && oInit.saved_aoColumns.length == iLen ) { if ( oCol === null ) { oCol = {}; } oCol.bVisible = oInit.saved_aoColumns[i].bVisible; } _fnAddColumn( oSettings, oCol, nTh ); } /* Check the aaSorting array */ for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ ) { var oColumn = oSettings.aoColumns[ oSettings.aaSorting[i][0] ]; /* Add a default sorting index */ if ( typeof oSettings.aaSorting[i][2] == 'undefined' ) { oSettings.aaSorting[i][2] = 0; } /* If aaSorting is not defined, then we use the first indicator in asSorting */ if ( typeof oInit.aaSorting == "undefined" && typeof oSettings.saved_aaSorting == "undefined" ) { oSettings.aaSorting[i][1] = oColumn.asSorting[0]; } /* Set the current sorting index based on aoColumns.asSorting */ for ( j=0, jLen=oColumn.asSorting.length ; j<jLen ; j++ ) { if ( oSettings.aaSorting[i][1] == oColumn.asSorting[j] ) { oSettings.aaSorting[i][2] = j; break; } } } /* Sanity check that there is a thead and tfoot. If not let's just create them */ if ( this.getElementsByTagName('thead').length === 0 ) { this.appendChild( document.createElement( 'thead' ) ); } if ( this.getElementsByTagName('tbody').length === 0 ) { this.appendChild( document.createElement( 'tbody' ) ); } /* Check if there is data passing into the constructor */ if ( bUsePassedData ) { for ( i=0 ; i<oInit.aaData.length ; i++ ) { _fnAddData( oSettings, oInit.aaData[ i ] ); } } else { /* Grab the data from the page */ _fnGatherData( oSettings ); } /* Copy the data index array */ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); /* Calculate sizes for columns */ if ( oSettings.oFeatures.bAutoWidth ) { _fnCalculateColumnWidths( oSettings ); } /* Initialisation complete - table can be drawn */ oSettings.bInitialised = true; /* Check if we need to initialise the table (it might not have been handed off to the * language processor) */ if ( bInitHandedOff === false ) { _fnInitalise( oSettings ); } }); }; })(jQuery);