/*
* File: jquery.dataSlides.js
* Version: 1.5.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-2009 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.dataSlides.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,_fnFeatureHtmlFilter,_fnFilter,_fnSaveState,_fnFilterColumn,_fnEscapeRegex,_fnFilterComplete,_fnFeatureHtmlLength,_fnGetDataMaster,_fnVisibleToColumnIndex,_fnDrawHead,_fnAddData,_fnGetTrNodes,_fnColumnIndexToVisible,_fnCreateCookie,_fnAddOptionsHtml,_fnMap,_fnClearTable,_fnDataToSearch,_fnReOrderIndex,_fnFilterCustom,_fnVisbleColumns,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnColumnOrdering,fnGetMaxLenString*/
(function($) {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* dataSlides variables
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* Variable: dataSlideSettings
* Purpose: Store the settings for each dataSlides instance
* Scope: jQuery.fn
*/
$.fn.dataSlideSettings = [];
/*
* Variable: dataSlideExt
* Purpose: Container for customisable parts of dataSlides
* Scope: jQuery.fn
*/
$.fn.dataSlideExt = {};
var _oExt = $.fn.dataSlideExt; /* Short reference for fast internal lookup */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* dataSlides extensible objects
*
* The _oExt object is used to provide an area where user dfined plugins can be
* added to dataSlides. The following properties of the object are used:
* oApi - Plug-in API functions
* aTypes - Auto-detection of types
* oSort - Sorting functions used by dataSlides (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.dataSlideExt
*/
_oExt.sVersion = "1.5.2";
/*
* Variable: iApiIndex
* Purpose: Index for what 'this' index API functions should use
* Scope: jQuery.fn.dataSlideExt
*/
_oExt.iApiIndex = 0;
/*
* Variable: oApi
* Purpose: Container for plugin API functions
* Scope: jQuery.fn.dataSlideExt
*/
_oExt.oApi = { };
/*
* Variable: aFiltering
* Purpose: Container for plugin filtering functions
* Scope: jQuery.fn.dataSlideExt
*/
_oExt.afnFiltering = [ ];
/*
* Variable: aoFeatures
* Purpose: Container for plugin function functions
* Scope: jQuery.fn.dataSlideExt
* 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.dataSlideExt
* 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: oStdClasses
* Purpose: Storage for the various classes that dataSlides uses
* Scope: jQuery.fn.dataSlideExt
*/
_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",
"sPageButtonStaticActive": "paginate_button",
"sPageFirst": "first",
"sPagePrevious": "previous",
"sPageNext": "next",
"sPageLast": "last",
/* Stripping classes */
"sStripOdd": "odd",
"sStripEven": "even",
/* Empty row */
"sRowEmpty": "dataSlides_empty",
/* Features */
"sWrapper": "dataSlides_wrapper",
"sFilter": "dataSlides_filter",
"sInfo": "dataSlides_info",
"sPaging": "dataSlides_paginate paging_", /* Note that the type is postfixed */
"sLength": "dataSlides_length",
"sProcessing": "dataSlides_processing",
/* Sorting */
"sSortAsc": "sorting_asc",
"sSortDesc": "sorting_desc",
"sSortable": "sorting",
"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
"sSortJUIAsc": "",
"sSortJUIDesc": "",
"sSortJUI": ""
};
/*
* Variable: oJUIClasses
* Purpose: Storage for the various classes that dataSlides uses - jQuery UI suitable
* Scope: jQuery.fn.dataSlideExt
*/
_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",
"sPageButtonStaticActive": "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": "dataSlides_empty",
/* Features */
"sWrapper": "dataSlides_wrapper",
"sFilter": "dataSlides_filter",
"sInfo": "dataSlides_info",
"sPaging": "dataSlides_paginate fg-buttonset fg-buttonset-multi paging_", /* Note that the type is postfixed */
"sLength": "dataSlides_length",
"sProcessing": "dataSlides_processing",
/* Sorting */
"sSortAsc": "ui-state-default",
"sSortDesc": "ui-state-default",
"sSortable": "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-triangle-2-n-s"
};
/*
* Variable: oPagination
* Purpose: Container for the various type of pagination that dataSlides supports
* Scope: jQuery.fn.dataSlideExt
*/
_oExt.oPagination = {
/*
* Variable: two_button
* Purpose: Standard two button (forward/back) pagination
* Scope: jQuery.fn.dataSlideExt.oPagination
*/
"two_button": {
/*
* Function: oPagination.two_button.fnInit
* Purpose: Initalise dom elements required for pagination with forward/back buttons only
* Returns: -
* Inputs: object:oSettings - dataSlides settings object
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnInit": function ( oSettings, fnCallbackDraw )
{
var nPaging = oSettings.anFeatures.p;
/* Store the next and previous elements in the oSettings object as they can be very
* usful for automation - particularly testing
*/
if ( !oSettings.bJUI )
{
oSettings.nPrevious = document.createElement( 'div' );
oSettings.nNext = document.createElement( 'div' );
}
else
{
oSettings.nPrevious = document.createElement( 'a' );
oSettings.nNext = document.createElement( 'a' );
var nNextInner = document.createElement('span');
nNextInner.className = oSettings.oClasses.sPageJUINext;
oSettings.nNext.appendChild( nNextInner );
var nPreviousInner = document.createElement('span');
nPreviousInner.className = oSettings.oClasses.sPageJUIPrev;
oSettings.nPrevious.appendChild( nPreviousInner );
}
if ( oSettings.sTableId !== '' )
{
nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
oSettings.nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
oSettings.nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
}
oSettings.nPrevious.className = oSettings.oClasses.sPagePrevDisabled;
oSettings.nNext.className = oSettings.oClasses.sPageNextDisabled;
oSettings.nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
oSettings.nNext.title = oSettings.oLanguage.oPaginate.sNext;
nPaging.appendChild( oSettings.nPrevious );
nPaging.appendChild( oSettings.nNext );
$(nPaging).insertAfter( oSettings.nTable );
$(oSettings.nPrevious).click( function() {
oSettings._iDisplayStart -= oSettings._iDisplayLength;
/* Correct for underrun */
if ( oSettings._iDisplayStart < 0 )
{
oSettings._iDisplayStart = 0;
}
fnCallbackDraw( oSettings );
} );
$(oSettings.nNext).click( function() {
/* Make sure we are not over running the display array */
if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
{
oSettings._iDisplayStart += oSettings._iDisplayLength;
}
fnCallbackDraw( oSettings );
} );
/* Take the brutal approach to cancelling text selection */
$(oSettings.nPrevious).bind( 'selectstart', function () { return false; } );
$(oSettings.nNext).bind( 'selectstart', function () { return false; } );
},
/*
* Function: oPagination.two_button.fnUpdate
* Purpose: Update the two button pagination at the end of the draw
* Returns: -
* Inputs: object:oSettings - dataSlides settings object
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnUpdate": function ( oSettings, fnCallbackDraw )
{
if ( !oSettings.anFeatures.p )
{
return;
}
oSettings.nPrevious.className =
( oSettings._iDisplayStart === 0 ) ?
oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled;
oSettings.nNext.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.dataSlideExt.oPagination
*/
"iFullNumbersShowPages": 5,
/*
* Variable: full_numbers
* Purpose: Full numbers pagination
* Scope: jQuery.fn.dataSlideExt.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 - dataSlides settings object
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnInit": function ( oSettings, fnCallbackDraw )
{
var nPaging = oSettings.anFeatures.p;
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;
if ( oSettings.sTableId !== '' )
{
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' );
}
nPaging.appendChild( nFirst );
nPaging.appendChild( nPrevious );
nPaging.appendChild( nList );
nPaging.appendChild( nNext );
nPaging.appendChild( nLast );
$(nFirst).click( function () {
oSettings._iDisplayStart = 0;
fnCallbackDraw( oSettings );
} );
$(nPrevious).click( function() {
oSettings._iDisplayStart -= oSettings._iDisplayLength;
/* Correct for underrun */
if ( oSettings._iDisplayStart < 0 )
{
oSettings._iDisplayStart = 0;
}
fnCallbackDraw( oSettings );
} );
$(nNext).click( function() {
/* Make sure we are not over running the display array */
if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
{
oSettings._iDisplayStart += oSettings._iDisplayLength;
}
fnCallbackDraw( oSettings );
} );
$(nLast).click( function() {
var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
fnCallbackDraw( oSettings );
} );
/* Take the brutal approach to cancelling text selection */
$('span', nPaging).bind( 'mousedown', function () { return false; } );
$('span', nPaging).bind( 'selectstart', function () { return false; } );
oSettings.nPaginateList = nList;
},
/*
* Function: oPagination.full_numbers.fnUpdate
* Purpose: Update the list of page buttons shows
* Returns: -
* Inputs: object:oSettings - dataSlides settings object
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnUpdate": function ( oSettings, fnCallbackDraw )
{
if ( !oSettings.anFeatures.p )
{
return;
}
var iPageCount = jQuery.fn.dataSlideExt.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;
var iEndButton;
var oClasses = oSettings.oClasses;
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;
}
}
}
for ( var i=iStartButton ; i<=iEndButton ; i++ )
{
if ( iCurrentPage != i )
{
sList += ''+i+'';
}
else
{
sList += ''+i+'';
}
}
oSettings.nPaginateList.innerHTML = sList;
/* Take the brutal approach to cancelling text selection */
$('span', oSettings.nPaginateList).bind( 'mousedown', function () { return false; } );
$('span', oSettings.nPaginateList).bind( 'selectstart', function () { return false; } );
$('span', oSettings.nPaginateList).click( function() {
var iTarget = (this.innerHTML * 1) - 1;
oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
fnCallbackDraw( oSettings );
return false;
} );
/* Update the 'premanent botton's classes */
var nButtons = $('span', oSettings.anFeatures.p);
var nStatic = [ nButtons[0], nButtons[1], nButtons[nButtons.length-2], nButtons[nButtons.length-1] ];
$(nStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive );
if ( iCurrentPage == 1 )
{
nStatic[0].className += " "+oClasses.sPageButtonStaticActive;
nStatic[1].className += " "+oClasses.sPageButtonStaticActive;
}
else
{
nStatic[0].className += " "+oClasses.sPageButton;
nStatic[1].className += " "+oClasses.sPageButton;
}
if ( iCurrentPage == iPages )
{
nStatic[2].className += " "+oClasses.sPageButtonStaticActive;
nStatic[3].className += " "+oClasses.sPageButtonStaticActive;
}
else
{
nStatic[2].className += " "+oClasses.sPageButton;
nStatic[3].className += " "+oClasses.sPageButton;
}
}
}
};
/*
* Variable: oSort
* Purpose: Wrapper for the sorting functions that can be used in dataSlides
* Scope: jQuery.fn.dataSlideExt
* 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 dataSlides supports
* Scope: jQuery.fn.dataSlideExt
* 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 )
{
/* Snaity 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' - div elements
* '<"class" and '>' - div with a class
* Examples: '<"wrapper"flipt>', 'ip>'
*/
this.sDomPositioning = 'lfrtip';
/*
* Variable: sPaginationType
* Purpose: Note which type of sorting should be used
* Scope: jQuery.dataSlide.classSettings
*/
this.sPaginationType = "two_button";
/*
* Variable: iCookieDuration
* Purpose: The cookie duration (for bStateSave) in seconds - default 2 hours
* Scope: jQuery.dataSlide.classSettings
*/
this.iCookieDuration = 60 * 60 * 2;
/*
* Variable: sAjaxSource
* Purpose: Source url for AJAX data for the table
* Scope: jQuery.dataSlide.classSettings
*/
this.sAjaxSource = null;
/*
* Variable: bAjaxDataGet
* Purpose: Note if draw should be blocked while getting data
* Scope: jQuery.dataSlide.classSettings
*/
this.bAjaxDataGet = true;
/*
* Variable: fnServerData
* Purpose: Function to get the server-side data - can be overruled by the developer
* Scope: jQuery.dataSlide.classSettings
*/
this.fnServerData = $.getJSON;
/*
* Variable: iServerDraw
* Purpose: Counter and tracker for server-side processing draws
* Scope: jQuery.dataSlide.classSettings
*/
this.iServerDraw = 0;
/*
* Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
* Purpose: Display length variables
* Scope: jQuery.dataSlide.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.dataSlide.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.dataSlide.classSettings
*/
this.bJUI = false;
/*
* Variable: bJUI
* Purpose: Should we add the markup needed for jQuery UI theming?
* Scope: jQuery.dataSlide.classSettings
*/
this.oClasses = _oExt.oStdClasses;
}
/*
* Variable: oApi
* Purpose: Container for publicly exposed 'private' functions
* Scope: jQuery.dataSlide
*/
this.oApi = {};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* API functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* Function: fnDraw
* Purpose: Redraw the table
* Returns: -
* Inputs: -
*/
this.fnDraw = function()
{
_fnReDraw( _fnSettingsFromNode( this[_oExt.iApiIndex] ) );
};
/*
* 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: 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: 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 )
{
var aiReturn = [];
var iTest;
if ( typeof bRedraw == 'undefined' )
{
bRedraw = true;
}
/* 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= 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: -
* 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("div");
$(nNewRow).addClass('tr');
var nNewCell = document.createElement("div");
nNewRow.appendChild( nNewCell );
nNewCell.className = sClass;
$(nNewCell).addClass('td')
nNewCell.colSpan = _fnVisbleColumns( oSettings );
nNewCell.innerHTML = sHtml;
$(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
} );
}
};
/*
* 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 ; itd', oSettings.aoData[i].nTr)[j-iCorrector] == nNode )
if ( oSettings.aoData[i] !== null &&
oSettings.aoData[i].nTr.children('.td')[j-iCorrector] == nNode )
{
return [ i, 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
* int:iRow - the row (from aoData) to update
* int:iColumn - the column to update
* bool:bRedraw - redraw the table or not - default true
*/
this.fnUpdate = function( mData, iRow, iColumn, bRedraw )
{
var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
var iVisibleColumn;
var sDisplay;
if ( typeof bRedraw == 'undefined' )
{
bRedraw = true;
}
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
} );
if ( oSettings.aoColumns[iColumn].bUseRendered )
{
oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
}
}
iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn );
if ( iVisibleColumn !== null )
{
oSettings.aoData[iRow].nTr.children('.td')[iVisibleColumn].innerHTML =
sDisplay;
}
}
else
{
if ( mData.length != oSettings.aoColumns.length )
{
alert( '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= _fnVisbleColumns( oSettings ) )
{
nTrHead.appendChild( anTheadTh[iCol] );
if ( nTrFoot )
{
nTrFoot.appendChild( anTfootTh[iCol] );
}
for ( i=0, iLen=oSettings.aoData.length ; i=0 if successful (index of new aoData entry), -1 if failed
* Inputs: object:oSettings - dataSlides 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( "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;
var newTr = document.createElement('div');
$(newTr).addClass('tr');
oSettings.aoData.push( {
"_iId": oSettings.iNextId++,
"_aData": aData.slice(),
"nTr": newTr,
"_anHidden": []
} );
/* Create the cells */
var nTd;
for ( var i=0 ; i.tr', oSettings.nTable).each( function() {
var iThisIndex = oSettings.aoData.length;
oSettings.aoData.push( {
"_iId": oSettings.iNextId++,
"_aData": [],
"nTr": this,
"_anHidden": []
} );
oSettings.aiDisplayMaster.push( iThisIndex );
/* Add the data for this column */
var aLocalData = oSettings.aoData[iThisIndex]._aData;
$('.td', this).each( function( i ) {
aLocalData[i] = this.innerHTML;
} );
} );
}
/*
* Now process by column
*/
var iCorrector = 0;
for ( i=0 ; itr' expression. This basically ensures that we
* only get tr elements of the tbody that the data table has been initialised on. If there
* are nested tables then we don't want to remove those elements.
*/
var nTrs = $('.tbody:eq(0)>.tr', oSettings.nTable);
for ( i=0 ; i' )
{
/* End container div */
nInsertNode = nInsertNode.parentNode;
}
else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
{
/* Length */
nTmp = _fnFeatureHtmlLength( oSettings );
oSettings.anFeatures[cOption] = nTmp;
nInsertNode.appendChild( nTmp );
}
else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
{
/* Filter */
nTmp = _fnFeatureHtmlFilter( oSettings );
oSettings.anFeatures[cOption] = nTmp;
nInsertNode.appendChild( nTmp );
}
else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
{
/* pRocessing */
nTmp = _fnFeatureHtmlProcessing( oSettings );
oSettings.anFeatures[cOption] = nTmp;
nInsertNode.appendChild( nTmp );
}
else if ( cOption == 't' )
{
/* Table */
oSettings.anFeatures[cOption] = oSettings.nTable;
nInsertNode.appendChild( oSettings.nTable );
}
else if ( cOption == 'i' && oSettings.oFeatures.bInfo )
{
/* Info */
nTmp = _fnFeatureHtmlInfo( oSettings );
oSettings.anFeatures[cOption] = nTmp;
nInsertNode.appendChild( nTmp );
}
else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
{
/* Pagination */
nTmp = _fnFeatureHtmlPaginate( oSettings );
oSettings.anFeatures[cOption] = nTmp;
nInsertNode.appendChild( nTmp );
}
else if ( _oExt.aoFeatures.length !== 0 )
{
var aoFeatures = _oExt.aoFeatures;
for ( var k=0, kLen=aoFeatures.length ; k';
var jqFilter = $("input", nFilter);
jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','"') );
jqFilter.keyup( function(e) {
_fnFilterComplete( oSettings, {
"sSearch": this.value,
"bEscapeRegex": oSettings.oPreviousSearch.bEscapeRegex
} );
/* Prevent default */
return false;
} );
return nFilter;
}
/*
* Function: _fnFeatureHtmlInfo
* Purpose: Generate the node required for the info display
* Returns: node
* Inputs: object:oSettings - dataSlides settings object
*/
function _fnFeatureHtmlInfo ( oSettings )
{
var nInfo = document.createElement( 'div' );
if ( oSettings.sTableId !== '' )
{
nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
}
nInfo.className = oSettings.oClasses.sInfo;
return nInfo;
}
/*
* Function: _fnFeatureHtmlPaginate
* Purpose: Generate the node required for default pagination
* Returns: node
* Inputs: object:oSettings - dataSlides settings object
*/
function _fnFeatureHtmlPaginate ( oSettings )
{
var nPaginate = document.createElement( 'div' );
nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
oSettings.anFeatures.p = nPaginate; /* Need this stored in order to call paging plug-ins */
_oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, function( oSettings ) {
_fnCalculateEnd( oSettings );
_fnDraw( oSettings );
} );
return nPaginate;
}
/*
* Function: _fnFeatureHtmlLength
* Purpose: Generate the node required for user display length changing
* Returns: node
* Inputs: object:oSettings - dataSlides 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 =
'';
var nLength = document.createElement( 'div' );
if ( oSettings.sTableId !== '' )
{
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) {
oSettings._iDisplayLength = parseInt($(this).val(), 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;
}
/*
* Function: _fnFeatureHtmlProcessing
* Purpose: Generate the node required for the processing node
* Returns: node
* Inputs: object:oSettings - dataSlides settings object
*/
function _fnFeatureHtmlProcessing ( oSettings )
{
var nProcessing = document.createElement( 'div' );
if ( oSettings.sTableId !== '' )
{
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 - dataSlides settings object
* bool:
* true - show the processing indicator
* false - don't show
*/
function _fnProcessingDisplay ( oSettings, bShow )
{
if ( oSettings.oFeatures.bProcessing )
{
oSettings.anFeatures.r.style.visibility = bShow ? "visible" : "hidden";
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Filtering
*/
/*
* Function: _fnFilterComplete
* Purpose: Filter the table using both the global filter and column based filtering
* Returns: -
* Inputs: object:oSettings - dataSlides 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=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 - dataSlides 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/g, "" );
}
else if ( typeof sData == "string" )
{
return sData.replace(/\n/g," ");
}
return sData;
}
/*
* Function: _fnCalculateEnd
* Purpose: Rcalculate the end point based on the start point
* Returns: -
* Inputs: object:oSettings - dataSlides 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 - dataSlides settings object
*/
function _fnCalculateColumnWidths ( oSettings ){
}
/*
* Function: fnGetMaxLenString
* Purpose: Get the maximum strlen for each data column
* Returns: string: - max strlens for each column
* Inputs: object:oSettings - dataSlides settings object
* int:iCol - column of interest
*/
function fnGetMaxLenString( oSettings, iCol )
{
var iMax = 0;
var iMaxIndex = -1;
for ( var i=0 ; i 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';
}
}
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 );
}
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.
*/
}
/* Add the strip classes now that we know which classes to apply - unless overruled */
if ( typeof oInit == 'undefined' || 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 = $('.thead',this);
var nThs = nThead.length===0 ? null : _fnGetUniqueThs( nThead[0] );
var bUseCols = typeof oInit != 'undefined' && typeof oInit.aoColumns != 'undefined';
for ( i=0, iLen=bUseCols ? oInit.aoColumns.length : nThs.length ; i');
}
if ( $(this).children('.tbody').length === 0 )
{
$(this).append('');
}
/* Check if there is data passing into the constructor */
if ( bUsePassedData )
{
for ( i=0 ; i