You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2012/10/25 21:17:39 UTC

svn commit: r1402281 [3/7] - in /incubator/ambari/branches/AMBARI-666: ./ ambari-web/ ambari-web/app/ ambari-web/app/controllers/installer/ ambari-web/app/controllers/main/ ambari-web/app/controllers/main/apps/ ambari-web/app/controllers/main/apps/runs...

Added: incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/jquery.dataTables.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/jquery.dataTables.js?rev=1402281&view=auto
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/jquery.dataTables.js (added)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/jquery.dataTables.js Thu Oct 25 19:17:36 2012
@@ -0,0 +1,11272 @@
+/**
+ * @summary     DataTables
+ * @description Paginate, search and sort HTML tables
+ * @version     1.9.4
+ * @file        jquery.dataTables.js
+ * @author      Allan Jardine (www.sprymedia.co.uk)
+ * @contact     www.sprymedia.co.uk/contact
+ *
+ * @copyright Copyright 2008-2012 Allan Jardine, all rights reserved.
+ *
+ * This source file is free software, under either the GPL v2 license or a
+ * BSD style license, available at:
+ *   http://datatables.net/license_gpl2
+ *   http://datatables.net/license_bsd
+ *
+ * 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 please refer to: http://www.datatables.net
+ */
+
+/*jslint evil: true, undef: true, browser: true */
+/*globals $, jQuery,define,_fnExternApiFunc,_fnInitialise,_fnInitComplete,_fnLanguageCompat,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnCreateTr,_fnGatherData,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnServerParams,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAdjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscap
 eRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnDetectHeader,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap,_fnGetRowData,_fnGetCellData,_fnSetCellData,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnApplyColumnDefs,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnJsonString,_fnRender,_fnNodeToColumnIndex,_fnInfoMacros,_fnBrowserDetect,_fnGetColumns*/
+
+(/** @lends <global> */function (window, document, undefined) {
+
+  (function (factory) {
+    "use strict";
+
+    // Define as an AMD module if possible
+    if (typeof define === 'function' && define.amd) {
+      define(['jquery'], factory);
+    }
+    /* Define using browser globals otherwise
+     * Prevent multiple instantiations if the script is loaded twice
+     */
+    else if (jQuery && !jQuery.fn.dataTable) {
+      factory(jQuery);
+    }
+  }
+    (/** @lends <global> */function ($) {
+      "use strict";
+      /**
+       * DataTables is a plug-in for the jQuery Javascript library. It is a
+       * highly flexible tool, based upon the foundations of progressive
+       * enhancement, which will add advanced interaction controls to any
+       * HTML table. For a full list of features please refer to
+       * <a href="http://datatables.net">DataTables.net</a>.
+       *
+       * Note that the <i>DataTable</i> object is not a global variable but is
+       * aliased to <i>jQuery.fn.DataTable</i> and <i>jQuery.fn.dataTable</i> through which
+       * it may be  accessed.
+       *
+       *  @class
+       *  @param {object} [oInit={}] Configuration object for DataTables. Options
+       *    are defined by {@link DataTable.defaults}
+       *  @requires jQuery 1.3+
+       *
+       *  @example
+       *    // Basic initialisation
+       *    $(document).ready( function {
+	 *      $('#example').dataTable();
+	 *    } );
+       *
+       *  @example
+       *    // Initialisation with configuration options - in this case, disable
+       *    // pagination and sorting.
+       *    $(document).ready( function {
+	 *      $('#example').dataTable( {
+	 *        "bPaginate": false,
+	 *        "bSort": false 
+	 *      } );
+	 *    } );
+       */
+      var DataTable = function (oInit) {
+
+
+        /**
+         * Add a column to the list used for the table with default values
+         *  @param {object} oSettings dataTables settings object
+         *  @param {node} nTh The th element for this column
+         *  @memberof DataTable#oApi
+         */
+        function _fnAddColumn(oSettings, nTh) {
+          var oDefaults = DataTable.defaults.columns;
+          var iCol = oSettings.aoColumns.length;
+          var oCol = $.extend({}, DataTable.models.oColumn, oDefaults, {
+            "sSortingClass":oSettings.oClasses.sSortable,
+            "sSortingClassJUI":oSettings.oClasses.sSortJUI,
+            "nTh":nTh ? nTh : document.createElement('th'),
+            "sTitle":oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '',
+            "aDataSort":oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
+            "mData":oDefaults.mData ? oDefaults.oDefaults : iCol
+          });
+          oSettings.aoColumns.push(oCol);
+
+          /* Add a column specific filter */
+          if (oSettings.aoPreSearchCols[ iCol ] === undefined || oSettings.aoPreSearchCols[ iCol ] === null) {
+            oSettings.aoPreSearchCols[ iCol ] = $.extend({}, DataTable.models.oSearch);
+          }
+          else {
+            var oPre = oSettings.aoPreSearchCols[ iCol ];
+
+            /* Don't require that the user must specify bRegex, bSmart or bCaseInsensitive */
+            if (oPre.bRegex === undefined) {
+              oPre.bRegex = true;
+            }
+
+            if (oPre.bSmart === undefined) {
+              oPre.bSmart = true;
+            }
+
+            if (oPre.bCaseInsensitive === undefined) {
+              oPre.bCaseInsensitive = true;
+            }
+          }
+
+          /* Use the column options function to initialise classes etc */
+          _fnColumnOptions(oSettings, iCol, null);
+        }
+
+
+        /**
+         * Apply options for a column
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iCol column index to consider
+         *  @param {object} oOptions object with sType, bVisible and bSearchable etc
+         *  @memberof DataTable#oApi
+         */
+        function _fnColumnOptions(oSettings, iCol, oOptions) {
+          var oCol = oSettings.aoColumns[ iCol ];
+
+          /* User specified column options */
+          if (oOptions !== undefined && oOptions !== null) {
+            /* Backwards compatibility for mDataProp */
+            if (oOptions.mDataProp && !oOptions.mData) {
+              oOptions.mData = oOptions.mDataProp;
+            }
+
+            if (oOptions.sType !== undefined) {
+              oCol.sType = oOptions.sType;
+              oCol._bAutoType = false;
+            }
+
+            $.extend(oCol, oOptions);
+            _fnMap(oCol, oOptions, "sWidth", "sWidthOrig");
+
+            /* iDataSort to be applied (backwards compatibility), but aDataSort will take
+             * priority if defined
+             */
+            if (oOptions.iDataSort !== undefined) {
+              oCol.aDataSort = [ oOptions.iDataSort ];
+            }
+            _fnMap(oCol, oOptions, "aDataSort");
+          }
+
+          /* Cache the data get and set functions for speed */
+          var mRender = oCol.mRender ? _fnGetObjectDataFn(oCol.mRender) : null;
+          var mData = _fnGetObjectDataFn(oCol.mData);
+
+          oCol.fnGetData = function (oData, sSpecific) {
+            var innerData = mData(oData, sSpecific);
+
+            if (oCol.mRender && (sSpecific && sSpecific !== '')) {
+              return mRender(innerData, sSpecific, oData);
+            }
+            return innerData;
+          };
+          oCol.fnSetData = _fnSetObjectDataFn(oCol.mData);
+
+          /* 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.sSortable;
+            oCol.sSortingClassJUI = oSettings.oClasses.sSortJUI;
+          }
+          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;
+          }
+        }
+
+
+        /**
+         * Adjust the table column widths for new data. Note: you would probably want to
+         * do a redraw after calling this function!
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnAdjustColumnSizing(oSettings) {
+          /* Not interested in doing column width calculation if auto-width is disabled */
+          if (oSettings.oFeatures.bAutoWidth === false) {
+            return false;
+          }
+
+          _fnCalculateColumnWidths(oSettings);
+          for (var i = 0 , iLen = oSettings.aoColumns.length; i < iLen; i++) {
+            oSettings.aoColumns[i].nTh.style.width = oSettings.aoColumns[i].sWidth;
+          }
+        }
+
+
+        /**
+         * Covert the index of a visible column to the index in the data array (take account
+         * of hidden columns)
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iMatch Visible column index to lookup
+         *  @returns {int} i the data index
+         *  @memberof DataTable#oApi
+         */
+        function _fnVisibleToColumnIndex(oSettings, iMatch) {
+          var aiVis = _fnGetColumns(oSettings, 'bVisible');
+
+          return typeof aiVis[iMatch] === 'number' ?
+            aiVis[iMatch] :
+            null;
+        }
+
+
+        /**
+         * Covert the index of an index in the data array and convert it to the visible
+         *   column index (take account of hidden columns)
+         *  @param {int} iMatch Column index to lookup
+         *  @param {object} oSettings dataTables settings object
+         *  @returns {int} i the data index
+         *  @memberof DataTable#oApi
+         */
+        function _fnColumnIndexToVisible(oSettings, iMatch) {
+          var aiVis = _fnGetColumns(oSettings, 'bVisible');
+          var iPos = $.inArray(iMatch, aiVis);
+
+          return iPos !== -1 ? iPos : null;
+        }
+
+
+        /**
+         * Get the number of visible columns
+         *  @param {object} oSettings dataTables settings object
+         *  @returns {int} i the number of visible columns
+         *  @memberof DataTable#oApi
+         */
+        function _fnVisbleColumns(oSettings) {
+          return _fnGetColumns(oSettings, 'bVisible').length;
+        }
+
+
+        /**
+         * Get an array of column indexes that match a given property
+         *  @param {object} oSettings dataTables settings object
+         *  @param {string} sParam Parameter in aoColumns to look for - typically
+         *    bVisible or bSearchable
+         *  @returns {array} Array of indexes with matched properties
+         *  @memberof DataTable#oApi
+         */
+        function _fnGetColumns(oSettings, sParam) {
+          var a = [];
+
+          $.map(oSettings.aoColumns, function (val, i) {
+            if (val[sParam]) {
+              a.push(i);
+            }
+          });
+
+          return a;
+        }
+
+
+        /**
+         * Get the sort type based on an input string
+         *  @param {string} sData data we wish to know the type of
+         *  @returns {string} type (defaults to 'string' if no type can be detected)
+         *  @memberof DataTable#oApi
+         */
+        function _fnDetectType(sData) {
+          var aTypes = DataTable.ext.aTypes;
+          var iLen = aTypes.length;
+
+          for (var i = 0; i < iLen; i++) {
+            var sType = aTypes[i](sData);
+            if (sType !== null) {
+              return sType;
+            }
+          }
+
+          return 'string';
+        }
+
+
+        /**
+         * Figure out how to reorder a display list
+         *  @param {object} oSettings dataTables settings object
+         *  @returns array {int} aiReturn index list for reordering
+         *  @memberof DataTable#oApi
+         */
+        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;
+        }
+
+
+        /**
+         * Get the column ordering that DataTables expects
+         *  @param {object} oSettings dataTables settings object
+         *  @returns {string} comma separated list of names
+         *  @memberof DataTable#oApi
+         */
+        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);
+        }
+
+
+        /**
+         * Take the column definitions and static columns arrays and calculate how
+         * they relate to column indexes. The callback function will then apply the
+         * definition found for a column to a suitable configuration object.
+         *  @param {object} oSettings dataTables settings object
+         *  @param {array} aoColDefs The aoColumnDefs array that is to be applied
+         *  @param {array} aoCols The aoColumns array that defines columns individually
+         *  @param {function} fn Callback function - takes two parameters, the calculated
+         *    column index and the definition for that column.
+         *  @memberof DataTable#oApi
+         */
+        function _fnApplyColumnDefs(oSettings, aoColDefs, aoCols, fn) {
+          var i, iLen, j, jLen, k, kLen;
+
+          // Column definitions with aTargets
+          if (aoColDefs) {
+            /* Loop over the definitions array - loop in reverse so first instance has priority */
+            for (i = aoColDefs.length - 1; i >= 0; i--) {
+              /* Each definition can target multiple columns, as it is an array */
+              var aTargets = aoColDefs[i].aTargets;
+              if (!$.isArray(aTargets)) {
+                _fnLog(oSettings, 1, 'aTargets must be an array of targets, not a ' + (typeof aTargets));
+              }
+
+              for (j = 0, jLen = aTargets.length; j < jLen; j++) {
+                if (typeof aTargets[j] === 'number' && aTargets[j] >= 0) {
+                  /* Add columns that we don't yet know about */
+                  while (oSettings.aoColumns.length <= aTargets[j]) {
+                    _fnAddColumn(oSettings);
+                  }
+
+                  /* Integer, basic index */
+                  fn(aTargets[j], aoColDefs[i]);
+                }
+                else if (typeof aTargets[j] === 'number' && aTargets[j] < 0) {
+                  /* Negative integer, right to left column counting */
+                  fn(oSettings.aoColumns.length + aTargets[j], aoColDefs[i]);
+                }
+                else if (typeof aTargets[j] === 'string') {
+                  /* Class name matching on TH element */
+                  for (k = 0, kLen = oSettings.aoColumns.length; k < kLen; k++) {
+                    if (aTargets[j] == "_all" ||
+                      $(oSettings.aoColumns[k].nTh).hasClass(aTargets[j])) {
+                      fn(k, aoColDefs[i]);
+                    }
+                  }
+                }
+              }
+            }
+          }
+
+          // Statically defined columns array
+          if (aoCols) {
+            for (i = 0, iLen = aoCols.length; i < iLen; i++) {
+              fn(i, aoCols[i]);
+            }
+          }
+        }
+
+        /**
+         * Add a data array to the table, creating DOM node etc. This is the parallel to
+         * _fnGatherData, but for adding rows from a Javascript source, rather than a
+         * DOM source.
+         *  @param {object} oSettings dataTables settings object
+         *  @param {array} aData data array to be added
+         *  @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
+         *  @memberof DataTable#oApi
+         */
+        function _fnAddData(oSettings, aDataSupplied) {
+          var oCol;
+
+          /* Take an independent copy of the data source so we can bash it about as we wish */
+          var aDataIn = ($.isArray(aDataSupplied)) ?
+            aDataSupplied.slice() :
+            $.extend(true, {}, aDataSupplied);
+
+          /* Create the object for storing information about this new row */
+          var iRow = oSettings.aoData.length;
+          var oData = $.extend(true, {}, DataTable.models.oRow);
+          oData._aData = aDataIn;
+          oSettings.aoData.push(oData);
+
+          /* Create the cells */
+          var nTd, sThisType;
+          for (var i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) {
+            oCol = oSettings.aoColumns[i];
+
+            /* Use rendered data for filtering / sorting */
+            if (typeof oCol.fnRender === 'function' && oCol.bUseRendered && oCol.mData !== null) {
+              _fnSetCellData(oSettings, iRow, i, _fnRender(oSettings, iRow, i));
+            }
+            else {
+              _fnSetCellData(oSettings, iRow, i, _fnGetCellData(oSettings, iRow, i));
+            }
+
+            /* See if we should auto-detect the column type */
+            if (oCol._bAutoType && oCol.sType != 'string') {
+              /* Attempt to auto detect the type - same as _fnGatherData() */
+              var sVarType = _fnGetCellData(oSettings, iRow, i, 'type');
+              if (sVarType !== null && sVarType !== '') {
+                sThisType = _fnDetectType(sVarType);
+                if (oCol.sType === null) {
+                  oCol.sType = sThisType;
+                }
+                else if (oCol.sType != sThisType && oCol.sType != "html") {
+                  /* String is always the 'fallback' option */
+                  oCol.sType = 'string';
+                }
+              }
+            }
+          }
+
+          /* Add to the display array */
+          oSettings.aiDisplayMaster.push(iRow);
+
+          /* Create the DOM information */
+          if (!oSettings.oFeatures.bDeferRender) {
+            _fnCreateTr(oSettings, iRow);
+          }
+
+          return iRow;
+        }
+
+
+        /**
+         * Read in the data from the target table from the DOM
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnGatherData(oSettings) {
+          var iLoop, i, iLen, j, jLen, jInner,
+            nTds, nTrs, nTd, nTr, aLocalData, iThisIndex,
+            iRow, iRows, iColumn, iColumns, sNodeName,
+            oCol, oData;
+
+          /*
+           * 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.bDeferLoading || oSettings.sAjaxSource === null) {
+            nTr = oSettings.nTBody.firstChild;
+            while (nTr) {
+              if (nTr.nodeName.toUpperCase() == "TR") {
+                iThisIndex = oSettings.aoData.length;
+                nTr._DT_RowIndex = iThisIndex;
+                oSettings.aoData.push($.extend(true, {}, DataTable.models.oRow, {
+                  "nTr":nTr
+                }));
+
+                oSettings.aiDisplayMaster.push(iThisIndex);
+                nTd = nTr.firstChild;
+                jInner = 0;
+                while (nTd) {
+                  sNodeName = nTd.nodeName.toUpperCase();
+                  if (sNodeName == "TD" || sNodeName == "TH") {
+                    _fnSetCellData(oSettings, iThisIndex, jInner, $.trim(nTd.innerHTML));
+                    jInner++;
+                  }
+                  nTd = nTd.nextSibling;
+                }
+              }
+              nTr = nTr.nextSibling;
+            }
+          }
+
+          /* 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++) {
+            nTd = nTrs[i].firstChild;
+            while (nTd) {
+              sNodeName = nTd.nodeName.toUpperCase();
+              if (sNodeName == "TD" || sNodeName == "TH") {
+                nTds.push(nTd);
+              }
+              nTd = nTd.nextSibling;
+            }
+          }
+
+          /* Now process by column */
+          for (iColumn = 0, iColumns = oSettings.aoColumns.length; iColumn < iColumns; iColumn++) {
+            oCol = oSettings.aoColumns[iColumn];
+
+            /* Get the title of the column - unless there is a user set one */
+            if (oCol.sTitle === null) {
+              oCol.sTitle = oCol.nTh.innerHTML;
+            }
+
+            var
+              bAutoType = oCol._bAutoType,
+              bRender = typeof oCol.fnRender === 'function',
+              bClass = oCol.sClass !== null,
+              bVisible = oCol.bVisible,
+              nCell, sThisType, sRendered, sValType;
+
+            /* 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++) {
+                oData = oSettings.aoData[iRow];
+                nCell = nTds[ (iRow * iColumns) + iColumn ];
+
+                /* Type detection */
+                if (bAutoType && oCol.sType != 'string') {
+                  sValType = _fnGetCellData(oSettings, iRow, iColumn, 'type');
+                  if (sValType !== '') {
+                    sThisType = _fnDetectType(sValType);
+                    if (oCol.sType === null) {
+                      oCol.sType = sThisType;
+                    }
+                    else if (oCol.sType != sThisType &&
+                      oCol.sType != "html") {
+                      /* String is always the 'fallback' option */
+                      oCol.sType = 'string';
+                    }
+                  }
+                }
+
+                if (oCol.mRender) {
+                  // mRender has been defined, so we need to get the value and set it
+                  nCell.innerHTML = _fnGetCellData(oSettings, iRow, iColumn, 'display');
+                }
+                else if (oCol.mData !== iColumn) {
+                  // If mData is not the same as the column number, then we need to
+                  // get the dev set value. If it is the column, no point in wasting
+                  // time setting the value that is already there!
+                  nCell.innerHTML = _fnGetCellData(oSettings, iRow, iColumn, 'display');
+                }
+
+                /* Rendering */
+                if (bRender) {
+                  sRendered = _fnRender(oSettings, iRow, iColumn);
+                  nCell.innerHTML = sRendered;
+                  if (oCol.bUseRendered) {
+                    /* Use the rendered data for filtering / sorting */
+                    _fnSetCellData(oSettings, iRow, iColumn, sRendered);
+                  }
+                }
+
+                /* Classes */
+                if (bClass) {
+                  nCell.className += ' ' + oCol.sClass;
+                }
+
+                /* Column visibility */
+                if (!bVisible) {
+                  oData._anHidden[iColumn] = nCell;
+                  nCell.parentNode.removeChild(nCell);
+                }
+                else {
+                  oData._anHidden[iColumn] = null;
+                }
+
+                if (oCol.fnCreatedCell) {
+                  oCol.fnCreatedCell.call(oSettings.oInstance,
+                    nCell, _fnGetCellData(oSettings, iRow, iColumn, 'display'), oData._aData, iRow, iColumn
+                  );
+                }
+              }
+            }
+          }
+
+          /* Row created callbacks */
+          if (oSettings.aoRowCreatedCallback.length !== 0) {
+            for (i = 0, iLen = oSettings.aoData.length; i < iLen; i++) {
+              oData = oSettings.aoData[i];
+              _fnCallbackFire(oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, i]);
+            }
+          }
+        }
+
+
+        /**
+         * Take a TR element and convert it to an index in aoData
+         *  @param {object} oSettings dataTables settings object
+         *  @param {node} n the TR element to find
+         *  @returns {int} index if the node is found, null if not
+         *  @memberof DataTable#oApi
+         */
+        function _fnNodeToDataIndex(oSettings, n) {
+          return (n._DT_RowIndex !== undefined) ? n._DT_RowIndex : null;
+        }
+
+
+        /**
+         * Take a TD element and convert it into a column data index (not the visible index)
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iRow The row number the TD/TH can be found in
+         *  @param {node} n The TD/TH element to find
+         *  @returns {int} index if the node is found, -1 if not
+         *  @memberof DataTable#oApi
+         */
+        function _fnNodeToColumnIndex(oSettings, iRow, n) {
+          var anCells = _fnGetTdNodes(oSettings, iRow);
+
+          for (var i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) {
+            if (anCells[i] === n) {
+              return i;
+            }
+          }
+          return -1;
+        }
+
+
+        /**
+         * Get an array of data for a given row from the internal data cache
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iRow aoData row id
+         *  @param {string} sSpecific data get type ('type' 'filter' 'sort')
+         *  @param {array} aiColumns Array of column indexes to get data from
+         *  @returns {array} Data array
+         *  @memberof DataTable#oApi
+         */
+        function _fnGetRowData(oSettings, iRow, sSpecific, aiColumns) {
+          var out = [];
+          for (var i = 0, iLen = aiColumns.length; i < iLen; i++) {
+            out.push(_fnGetCellData(oSettings, iRow, aiColumns[i], sSpecific));
+          }
+          return out;
+        }
+
+
+        /**
+         * Get the data for a given cell from the internal cache, taking into account data mapping
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iRow aoData row id
+         *  @param {int} iCol Column index
+         *  @param {string} sSpecific data get type ('display', 'type' 'filter' 'sort')
+         *  @returns {*} Cell data
+         *  @memberof DataTable#oApi
+         */
+        function _fnGetCellData(oSettings, iRow, iCol, sSpecific) {
+          var sData;
+          var oCol = oSettings.aoColumns[iCol];
+          var oData = oSettings.aoData[iRow]._aData;
+
+          if ((sData = oCol.fnGetData(oData, sSpecific)) === undefined) {
+            if (oSettings.iDrawError != oSettings.iDraw && oCol.sDefaultContent === null) {
+              _fnLog(oSettings, 0, "Requested unknown parameter " +
+                (typeof oCol.mData == 'function' ? '{mData function}' : "'" + oCol.mData + "'") +
+                " from the data source for row " + iRow);
+              oSettings.iDrawError = oSettings.iDraw;
+            }
+            return oCol.sDefaultContent;
+          }
+
+          /* When the data source is null, we can use default column data */
+          if (sData === null && oCol.sDefaultContent !== null) {
+            sData = oCol.sDefaultContent;
+          }
+          else if (typeof sData === 'function') {
+            /* If the data source is a function, then we run it and use the return */
+            return sData();
+          }
+
+          if (sSpecific == 'display' && sData === null) {
+            return '';
+          }
+          return sData;
+        }
+
+
+        /**
+         * Set the value for a specific cell, into the internal data cache
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iRow aoData row id
+         *  @param {int} iCol Column index
+         *  @param {*} val Value to set
+         *  @memberof DataTable#oApi
+         */
+        function _fnSetCellData(oSettings, iRow, iCol, val) {
+          var oCol = oSettings.aoColumns[iCol];
+          var oData = oSettings.aoData[iRow]._aData;
+
+          oCol.fnSetData(oData, val);
+        }
+
+
+        // Private variable that is used to match array syntax in the data property object
+        var __reArray = /\[.*?\]$/;
+
+        /**
+         * Return a function that can be used to get data from a source object, taking
+         * into account the ability to use nested objects as a source
+         *  @param {string|int|function} mSource The data source for the object
+         *  @returns {function} Data get function
+         *  @memberof DataTable#oApi
+         */
+        function _fnGetObjectDataFn(mSource) {
+          if (mSource === null) {
+            /* Give an empty string for rendering / sorting etc */
+            return function (data, type) {
+              return null;
+            };
+          }
+          else if (typeof mSource === 'function') {
+            return function (data, type, extra) {
+              return mSource(data, type, extra);
+            };
+          }
+          else if (typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1)) {
+            /* If there is a . in the source string then the data source is in a 
+             * nested object so we loop over the data for each level to get the next
+             * level down. On each loop we test for undefined, and if found immediately
+             * return. This allows entire objects to be missing and sDefaultContent to
+             * be used if defined, rather than throwing an error
+             */
+            var fetchData = function (data, type, src) {
+              var a = src.split('.');
+              var arrayNotation, out, innerSrc;
+
+              if (src !== "") {
+                for (var i = 0, iLen = a.length; i < iLen; i++) {
+                  // Check if we are dealing with an array notation request
+                  arrayNotation = a[i].match(__reArray);
+
+                  if (arrayNotation) {
+                    a[i] = a[i].replace(__reArray, '');
+
+                    // Condition allows simply [] to be passed in
+                    if (a[i] !== "") {
+                      data = data[ a[i] ];
+                    }
+                    out = [];
+
+                    // Get the remainder of the nested object to get
+                    a.splice(0, i + 1);
+                    innerSrc = a.join('.');
+
+                    // Traverse each entry in the array getting the properties requested
+                    for (var j = 0, jLen = data.length; j < jLen; j++) {
+                      out.push(fetchData(data[j], type, innerSrc));
+                    }
+
+                    // If a string is given in between the array notation indicators, that
+                    // is used to join the strings together, otherwise an array is returned
+                    var join = arrayNotation[0].substring(1, arrayNotation[0].length - 1);
+                    data = (join === "") ? out : out.join(join);
+
+                    // The inner call to fetchData has already traversed through the remainder
+                    // of the source requested, so we exit from the loop
+                    break;
+                  }
+
+                  if (data === null || data[ a[i] ] === undefined) {
+                    return undefined;
+                  }
+                  data = data[ a[i] ];
+                }
+              }
+
+              return data;
+            };
+
+            return function (data, type) {
+              return fetchData(data, type, mSource);
+            };
+          }
+          else {
+            /* Array or flat object mapping */
+            return function (data, type) {
+              return data[mSource];
+            };
+          }
+        }
+
+
+        /**
+         * Return a function that can be used to set data from a source object, taking
+         * into account the ability to use nested objects as a source
+         *  @param {string|int|function} mSource The data source for the object
+         *  @returns {function} Data set function
+         *  @memberof DataTable#oApi
+         */
+        function _fnSetObjectDataFn(mSource) {
+          if (mSource === null) {
+            /* Nothing to do when the data source is null */
+            return function (data, val) {
+            };
+          }
+          else if (typeof mSource === 'function') {
+            return function (data, val) {
+              mSource(data, 'set', val);
+            };
+          }
+          else if (typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1)) {
+            /* Like the get, we need to get data from a nested object */
+            var setData = function (data, val, src) {
+              var a = src.split('.'), b;
+              var arrayNotation, o, innerSrc;
+
+              for (var i = 0, iLen = a.length - 1; i < iLen; i++) {
+                // Check if we are dealing with an array notation request
+                arrayNotation = a[i].match(__reArray);
+
+                if (arrayNotation) {
+                  a[i] = a[i].replace(__reArray, '');
+                  data[ a[i] ] = [];
+
+                  // Get the remainder of the nested object to set so we can recurse
+                  b = a.slice();
+                  b.splice(0, i + 1);
+                  innerSrc = b.join('.');
+
+                  // Traverse each entry in the array setting the properties requested
+                  for (var j = 0, jLen = val.length; j < jLen; j++) {
+                    o = {};
+                    setData(o, val[j], innerSrc);
+                    data[ a[i] ].push(o);
+                  }
+
+                  // The inner call to setData has already traversed through the remainder
+                  // of the source and has set the data, thus we can exit here
+                  return;
+                }
+
+                // If the nested object doesn't currently exist - since we are
+                // trying to set the value - create it
+                if (data[ a[i] ] === null || data[ a[i] ] === undefined) {
+                  data[ a[i] ] = {};
+                }
+                data = data[ a[i] ];
+              }
+
+              // If array notation is used, we just want to strip it and use the property name
+              // and assign the value. If it isn't used, then we get the result we want anyway
+              data[ a[a.length - 1].replace(__reArray, '') ] = val;
+            };
+
+            return function (data, val) {
+              return setData(data, val, mSource);
+            };
+          }
+          else {
+            /* Array or flat object mapping */
+            return function (data, val) {
+              data[mSource] = val;
+            };
+          }
+        }
+
+
+        /**
+         * Return an array with the full table data
+         *  @param {object} oSettings dataTables settings object
+         *  @returns array {array} aData Master data array
+         *  @memberof DataTable#oApi
+         */
+        function _fnGetDataMaster(oSettings) {
+          var aData = [];
+          var iLen = oSettings.aoData.length;
+          for (var i = 0; i < iLen; i++) {
+            aData.push(oSettings.aoData[i]._aData);
+          }
+          return aData;
+        }
+
+
+        /**
+         * Nuke the table
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnClearTable(oSettings) {
+          oSettings.aoData.splice(0, oSettings.aoData.length);
+          oSettings.aiDisplayMaster.splice(0, oSettings.aiDisplayMaster.length);
+          oSettings.aiDisplay.splice(0, oSettings.aiDisplay.length);
+          _fnCalculateEnd(oSettings);
+        }
+
+
+        /**
+         * Take an array of integers (index array) and remove a target integer (value - not
+         * the key!)
+         *  @param {array} a Index array to target
+         *  @param {int} iTarget value to find
+         *  @memberof DataTable#oApi
+         */
+        function _fnDeleteIndex(a, iTarget) {
+          var iTargetIndex = -1;
+
+          for (var i = 0, iLen = a.length; i < iLen; i++) {
+            if (a[i] == iTarget) {
+              iTargetIndex = i;
+            }
+            else if (a[i] > iTarget) {
+              a[i]--;
+            }
+          }
+
+          if (iTargetIndex != -1) {
+            a.splice(iTargetIndex, 1);
+          }
+        }
+
+
+        /**
+         * Call the developer defined fnRender function for a given cell (row/column) with
+         * the required parameters and return the result.
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iRow aoData index for the row
+         *  @param {int} iCol aoColumns index for the column
+         *  @returns {*} Return of the developer's fnRender function
+         *  @memberof DataTable#oApi
+         */
+        function _fnRender(oSettings, iRow, iCol) {
+          var oCol = oSettings.aoColumns[iCol];
+
+          return oCol.fnRender({
+            "iDataRow":iRow,
+            "iDataColumn":iCol,
+            "oSettings":oSettings,
+            "aData":oSettings.aoData[iRow]._aData,
+            "mDataProp":oCol.mData
+          }, _fnGetCellData(oSettings, iRow, iCol, 'display'));
+        }
+
+        /**
+         * Create a new TR element (and it's TD children) for a row
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iRow Row to consider
+         *  @memberof DataTable#oApi
+         */
+        function _fnCreateTr(oSettings, iRow) {
+          var oData = oSettings.aoData[iRow];
+          var nTd;
+
+          if (oData.nTr === null) {
+            oData.nTr = document.createElement('tr');
+
+            /* Use a private property on the node to allow reserve mapping from the node
+             * to the aoData array for fast look up
+             */
+            oData.nTr._DT_RowIndex = iRow;
+
+            /* Special parameters can be given by the data source to be used on the row */
+            if (oData._aData.DT_RowId) {
+              oData.nTr.id = oData._aData.DT_RowId;
+            }
+
+            if (oData._aData.DT_RowClass) {
+              oData.nTr.className = oData._aData.DT_RowClass;
+            }
+
+            /* Process each column */
+            for (var i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) {
+              var oCol = oSettings.aoColumns[i];
+              nTd = document.createElement(oCol.sCellType);
+
+              /* Render if needed - if bUseRendered is true then we already have the rendered
+               * value in the data source - so can just use that
+               */
+              nTd.innerHTML = (typeof oCol.fnRender === 'function' && (!oCol.bUseRendered || oCol.mData === null)) ?
+                _fnRender(oSettings, iRow, i) :
+                _fnGetCellData(oSettings, iRow, i, 'display');
+
+              /* Add user defined class */
+              if (oCol.sClass !== null) {
+                nTd.className = oCol.sClass;
+              }
+
+              if (oCol.bVisible) {
+                oData.nTr.appendChild(nTd);
+                oData._anHidden[i] = null;
+              }
+              else {
+                oData._anHidden[i] = nTd;
+              }
+
+              if (oCol.fnCreatedCell) {
+                oCol.fnCreatedCell.call(oSettings.oInstance,
+                  nTd, _fnGetCellData(oSettings, iRow, i, 'display'), oData._aData, iRow, i
+                );
+              }
+            }
+
+            _fnCallbackFire(oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, iRow]);
+          }
+        }
+
+
+        /**
+         * Create the HTML header for the table
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnBuildHead(oSettings) {
+          var i, nTh, iLen, j, jLen;
+          var iThs = $('th, td', oSettings.nTHead).length;
+          var iCorrector = 0;
+          var jqChildren;
+
+          /* 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++) {
+              nTh = oSettings.aoColumns[i].nTh;
+              nTh.setAttribute('role', 'columnheader');
+              if (oSettings.aoColumns[i].bSortable) {
+                nTh.setAttribute('tabindex', oSettings.iTabIndex);
+                nTh.setAttribute('aria-controls', oSettings.sTableId);
+              }
+
+              if (oSettings.aoColumns[i].sClass !== null) {
+                $(nTh).addClass(oSettings.aoColumns[i].sClass);
+              }
+
+              /* 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 {
+            /* 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;
+              nTh.setAttribute('tabindex', '0');
+
+              if (oSettings.aoColumns[i].sClass !== null) {
+                $(nTh).addClass(oSettings.aoColumns[i].sClass);
+              }
+
+              nTr.appendChild(nTh);
+            }
+            $(oSettings.nTHead).html('')[0].appendChild(nTr);
+            _fnDetectHeader(oSettings.aoHeader, oSettings.nTHead);
+          }
+
+          /* ARIA role for the rows */
+          $(oSettings.nTHead).children('tr').attr('role', 'row');
+
+          /* Add the extra markup needed by jQuery UI's themes */
+          if (oSettings.bJUI) {
+            for (i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) {
+              nTh = oSettings.aoColumns[i].nTh;
+
+              var nDiv = document.createElement('div');
+              nDiv.className = oSettings.oClasses.sSortJUIWrapper;
+              $(nTh).contents().appendTo(nDiv);
+
+              var nSpan = document.createElement('span');
+              nSpan.className = oSettings.oClasses.sSortIcon;
+              nDiv.appendChild(nSpan);
+              nTh.appendChild(nDiv);
+            }
+          }
+
+          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);
+              }
+            }
+          }
+
+          /* Deal with the footer - add classes if required */
+          if (oSettings.oClasses.sFooterTH !== "") {
+            $(oSettings.nTFoot).children('tr').children('th').addClass(oSettings.oClasses.sFooterTH);
+          }
+
+          /* Cache the footer elements */
+          if (oSettings.nTFoot !== null) {
+            var anCells = _fnGetUniqueThs(oSettings, null, oSettings.aoFooter);
+            for (i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) {
+              if (anCells[i]) {
+                oSettings.aoColumns[i].nTf = anCells[i];
+                if (oSettings.aoColumns[i].sClass) {
+                  $(anCells[i]).addClass(oSettings.aoColumns[i].sClass);
+                }
+              }
+            }
+          }
+        }
+
+
+        /**
+         * Draw the header (or footer) element based on the column visibility states. The
+         * methodology here is to use the layout array from _fnDetectHeader, modified for
+         * the instantaneous column visibility, to construct the new layout. The grid is
+         * traversed over cell at a time in a rows x columns grid fashion, although each
+         * cell insert can cover multiple elements in the grid - which is tracks using the
+         * aApplied array. Cell inserts in the grid will only occur where there isn't
+         * already a cell in that position.
+         *  @param {object} oSettings dataTables settings object
+         *  @param array {objects} aoSource Layout array from _fnDetectHeader
+         *  @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc,
+         *  @memberof DataTable#oApi
+         */
+        function _fnDrawHead(oSettings, aoSource, bIncludeHidden) {
+          var i, iLen, j, jLen, k, kLen, n, nLocalTr;
+          var aoLocal = [];
+          var aApplied = [];
+          var iColumns = oSettings.aoColumns.length;
+          var iRowspan, iColspan;
+
+          if (bIncludeHidden === undefined) {
+            bIncludeHidden = false;
+          }
+
+          /* Make a copy of the master layout array, but without the visible columns in it */
+          for (i = 0, iLen = aoSource.length; i < iLen; i++) {
+            aoLocal[i] = aoSource[i].slice();
+            aoLocal[i].nTr = aoSource[i].nTr;
+
+            /* Remove any columns which are currently hidden */
+            for (j = iColumns - 1; j >= 0; j--) {
+              if (!oSettings.aoColumns[j].bVisible && !bIncludeHidden) {
+                aoLocal[i].splice(j, 1);
+              }
+            }
+
+            /* Prep the applied array - it needs an element for each row */
+            aApplied.push([]);
+          }
+
+          for (i = 0, iLen = aoLocal.length; i < iLen; i++) {
+            nLocalTr = aoLocal[i].nTr;
+
+            /* All cells are going to be replaced, so empty out the row */
+            if (nLocalTr) {
+              while ((n = nLocalTr.firstChild)) {
+                nLocalTr.removeChild(n);
+              }
+            }
+
+            for (j = 0, jLen = aoLocal[i].length; j < jLen; j++) {
+              iRowspan = 1;
+              iColspan = 1;
+
+              /* Check to see if there is already a cell (row/colspan) covering our target
+               * insert point. If there is, then there is nothing to do.
+               */
+              if (aApplied[i][j] === undefined) {
+                nLocalTr.appendChild(aoLocal[i][j].cell);
+                aApplied[i][j] = 1;
+
+                /* Expand the cell to cover as many rows as needed */
+                while (aoLocal[i + iRowspan] !== undefined &&
+                  aoLocal[i][j].cell == aoLocal[i + iRowspan][j].cell) {
+                  aApplied[i + iRowspan][j] = 1;
+                  iRowspan++;
+                }
+
+                /* Expand the cell to cover as many columns as needed */
+                while (aoLocal[i][j + iColspan] !== undefined &&
+                  aoLocal[i][j].cell == aoLocal[i][j + iColspan].cell) {
+                  /* Must update the applied array over the rows for the columns */
+                  for (k = 0; k < iRowspan; k++) {
+                    aApplied[i + k][j + iColspan] = 1;
+                  }
+                  iColspan++;
+                }
+
+                /* Do the actual expansion in the DOM */
+                aoLocal[i][j].cell.rowSpan = iRowspan;
+                aoLocal[i][j].cell.colSpan = iColspan;
+              }
+            }
+          }
+        }
+
+
+        /**
+         * Insert the required TR nodes into the table for display
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnDraw(oSettings) {
+          /* Provide a pre-callback function which can be used to cancel the draw is false is returned */
+          var aPreDraw = _fnCallbackFire(oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings]);
+          if ($.inArray(false, aPreDraw) !== -1) {
+            _fnProcessingDisplay(oSettings, false);
+            return;
+          }
+
+          var i, iLen, n;
+          var anRows = [];
+          var iRowCount = 0;
+          var iStripes = oSettings.asStripeClasses.length;
+          var iOpenRows = oSettings.aoOpenRows.length;
+
+          oSettings.bDrawing = true;
+
+          /* Check and see if we have an initial draw position from state saving */
+          if (oSettings.iInitDisplayStart !== undefined && oSettings.iInitDisplayStart != -1) {
+            if (oSettings.oFeatures.bServerSide) {
+              oSettings._iDisplayStart = oSettings.iInitDisplayStart;
+            }
+            else {
+              oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ?
+                0 : oSettings.iInitDisplayStart;
+            }
+            oSettings.iInitDisplayStart = -1;
+            _fnCalculateEnd(oSettings);
+          }
+
+          /* Server-side processing draw intercept */
+          if (oSettings.bDeferLoading) {
+            oSettings.bDeferLoading = false;
+            oSettings.iDraw++;
+          }
+          else if (!oSettings.oFeatures.bServerSide) {
+            oSettings.iDraw++;
+          }
+          else if (!oSettings.bDestroying && !_fnAjaxUpdate(oSettings)) {
+            return;
+          }
+
+          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] ];
+              if (aoData.nTr === null) {
+                _fnCreateTr(oSettings, oSettings.aiDisplay[j]);
+              }
+
+              var nRow = aoData.nTr;
+
+              /* Remove the old striping classes and then add the new one */
+              if (iStripes !== 0) {
+                var sStripe = oSettings.asStripeClasses[ iRowCount % iStripes ];
+                if (aoData._sRowStripe != sStripe) {
+                  $(nRow).removeClass(aoData._sRowStripe).addClass(sStripe);
+                  aoData._sRowStripe = sStripe;
+                }
+              }
+
+              /* Row callback functions - might want to manipulate the row */
+              _fnCallbackFire(oSettings, 'aoRowCallback', null,
+                [nRow, oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j]);
+
+              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);
+                    break;
+                  }
+                }
+              }
+            }
+          }
+          else {
+            /* Table is empty - create a row with an empty message in it */
+            anRows[ 0 ] = document.createElement('tr');
+
+            if (oSettings.asStripeClasses[0]) {
+              anRows[ 0 ].className = oSettings.asStripeClasses[0];
+            }
+
+            var oLang = oSettings.oLanguage;
+            var sZero = oLang.sZeroRecords;
+            if (oSettings.iDraw == 1 && oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide) {
+              sZero = oLang.sLoadingRecords;
+            }
+            else if (oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0) {
+              sZero = oLang.sEmptyTable;
+            }
+
+            var nTd = document.createElement('td');
+            nTd.setAttribute('valign', "top");
+            nTd.colSpan = _fnVisbleColumns(oSettings);
+            nTd.className = oSettings.oClasses.sRowEmpty;
+            nTd.innerHTML = _fnInfoMacros(oSettings, sZero);
+
+            anRows[ iRowCount ].appendChild(nTd);
+          }
+
+          /* Header and footer callbacks */
+          _fnCallbackFire(oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
+            _fnGetDataMaster(oSettings), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ]);
+
+          _fnCallbackFire(oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[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
+            nAddFrag = document.createDocumentFragment(),
+            nRemoveFrag = document.createDocumentFragment(),
+            nBodyPar, nTrs;
+
+          if (oSettings.nTBody) {
+            nBodyPar = oSettings.nTBody.parentNode;
+            nRemoveFrag.appendChild(oSettings.nTBody);
+
+            /* When doing infinite scrolling, only remove child rows when sorting, filtering or start
+             * up. When not infinite scroll, always do it.
+             */
+            if (!oSettings.oScroll.bInfinite || !oSettings._bInitComplete ||
+              oSettings.bSorted || oSettings.bFiltered) {
+              while ((n = oSettings.nTBody.firstChild)) {
+                oSettings.nTBody.removeChild(n);
+              }
+            }
+
+            /* Put the draw table into the dom */
+            for (i = 0, iLen = anRows.length; i < iLen; i++) {
+              nAddFrag.appendChild(anRows[i]);
+            }
+
+            oSettings.nTBody.appendChild(nAddFrag);
+            if (nBodyPar !== null) {
+              nBodyPar.appendChild(oSettings.nTBody);
+            }
+          }
+
+          /* Call all required callback functions for the end of a draw */
+          _fnCallbackFire(oSettings, 'aoDrawCallback', 'draw', [oSettings]);
+
+          /* Draw is complete, sorting and filtering must be as well */
+          oSettings.bSorted = false;
+          oSettings.bFiltered = false;
+          oSettings.bDrawing = false;
+
+          if (oSettings.oFeatures.bServerSide) {
+            _fnProcessingDisplay(oSettings, false);
+            if (!oSettings._bInitComplete) {
+              _fnInitComplete(oSettings);
+            }
+          }
+        }
+
+
+        /**
+         * Redraw the table - taking account of the various features which are enabled
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        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);
+          }
+        }
+
+
+        /**
+         * Add the options to the page HTML for the table
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        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 = $('<div></div>')[0];
+          oSettings.nTable.parentNode.insertBefore(nHolding, oSettings.nTable);
+
+          /* 
+           * All DataTables are wrapped in a div
+           */
+          oSettings.nTableWrapper = $('<div id="' + oSettings.sTableId + '_wrapper" class="' + oSettings.oClasses.sWrapper + '" role="grid"></div>')[0];
+          oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
+
+          /* Track where we want to insert the option */
+          var nInsertNode = oSettings.nTableWrapper;
+
+          /* Loop over the user set positioning and place the elements as needed */
+          var aDom = oSettings.sDom.split('');
+          var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j;
+          for (var i = 0; i < aDom.length; i++) {
+            iPushFeature = 0;
+            cOption = aDom[i];
+
+            if (cOption == '<') {
+              /* New container div */
+              nNewNode = $('<div></div>')[0];
+
+              /* Check to see if we should append an id and/or a class name to the container */
+              cNext = aDom[i + 1];
+              if (cNext == "'" || cNext == '"') {
+                sAttr = "";
+                j = 2;
+                while (aDom[i + j] != cNext) {
+                  sAttr += aDom[i + j];
+                  j++;
+                }
+
+                /* Replace jQuery UI constants */
+                if (sAttr == "H") {
+                  sAttr = oSettings.oClasses.sJUIHeader;
+                }
+                else if (sAttr == "F") {
+                  sAttr = oSettings.oClasses.sJUIFooter;
+                }
+
+                /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
+                 * breaks the string into parts and applies them as needed
+                 */
+                if (sAttr.indexOf('.') != -1) {
+                  var aSplit = sAttr.split('.');
+                  nNewNode.id = aSplit[0].substr(1, aSplit[0].length - 1);
+                  nNewNode.className = aSplit[1];
+                }
+                else if (sAttr.charAt(0) == "#") {
+                  nNewNode.id = sAttr.substr(1, sAttr.length - 1);
+                }
+                else {
+                  nNewNode.className = sAttr;
+                }
+
+                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 = _fnFeatureHtmlTable(oSettings);
+              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 (DataTable.ext.aoFeatures.length !== 0) {
+              /* Plug-in features */
+              var aoFeatures = DataTable.ext.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 && nTmp !== null) {
+              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(oSettings.nTableWrapper, nHolding);
+        }
+
+
+        /**
+         * Use the DOM source to create up an array of header cells. The idea here is to
+         * create a layout grid (array) of rows x columns, which contains a reference
+         * to the cell that that point in the grid (regardless of col/rowspan), such that
+         * any column / row could be removed and the new grid constructed
+         *  @param array {object} aLayout Array to store the calculated layout in
+         *  @param {node} nThead The header/footer element for the table
+         *  @memberof DataTable#oApi
+         */
+        function _fnDetectHeader(aLayout, nThead) {
+          var nTrs = $(nThead).children('tr');
+          var nTr, nCell;
+          var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
+          var bUnique;
+          var fnShiftCol = function (a, i, j) {
+            var k = a[i];
+            while (k[j]) {
+              j++;
+            }
+            return j;
+          };
+
+          aLayout.splice(0, aLayout.length);
+
+          /* We know how many rows there are in the layout - so prep it */
+          for (i = 0, iLen = nTrs.length; i < iLen; i++) {
+            aLayout.push([]);
+          }
+
+          /* Calculate a layout array */
+          for (i = 0, iLen = nTrs.length; i < iLen; i++) {
+            nTr = nTrs[i];
+            iColumn = 0;
+
+            /* For every cell in the row... */
+            nCell = nTr.firstChild;
+            while (nCell) {
+              if (nCell.nodeName.toUpperCase() == "TD" ||
+                nCell.nodeName.toUpperCase() == "TH") {
+                /* Get the col and rowspan attributes from the DOM and sanitise them */
+                iColspan = nCell.getAttribute('colspan') * 1;
+                iRowspan = nCell.getAttribute('rowspan') * 1;
+                iColspan = (!iColspan || iColspan === 0 || iColspan === 1) ? 1 : iColspan;
+                iRowspan = (!iRowspan || iRowspan === 0 || iRowspan === 1) ? 1 : iRowspan;
+
+                /* There might be colspan cells already in this row, so shift our target 
+                 * accordingly
+                 */
+                iColShifted = fnShiftCol(aLayout, i, iColumn);
+
+                /* Cache calculation for unique columns */
+                bUnique = iColspan === 1 ? true : false;
+
+                /* If there is col / rowspan, copy the information into the layout grid */
+                for (l = 0; l < iColspan; l++) {
+                  for (k = 0; k < iRowspan; k++) {
+                    aLayout[i + k][iColShifted + l] = {
+                      "cell":nCell,
+                      "unique":bUnique
+                    };
+                    aLayout[i + k].nTr = nTr;
+                  }
+                }
+              }
+              nCell = nCell.nextSibling;
+            }
+          }
+        }
+
+
+        /**
+         * Get an array of unique th elements, one for each column
+         *  @param {object} oSettings dataTables settings object
+         *  @param {node} nHeader automatically detect the layout from this node - optional
+         *  @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
+         *  @returns array {node} aReturn list of unique th's
+         *  @memberof DataTable#oApi
+         */
+        function _fnGetUniqueThs(oSettings, nHeader, aLayout) {
+          var aReturn = [];
+          if (!aLayout) {
+            aLayout = oSettings.aoHeader;
+            if (nHeader) {
+              aLayout = [];
+              _fnDetectHeader(aLayout, nHeader);
+            }
+          }
+
+          for (var i = 0, iLen = aLayout.length; i < iLen; i++) {
+            for (var j = 0, jLen = aLayout[i].length; j < jLen; j++) {
+              if (aLayout[i][j].unique &&
+                (!aReturn[j] || !oSettings.bSortCellsTop)) {
+                aReturn[j] = aLayout[i][j].cell;
+              }
+            }
+          }
+
+          return aReturn;
+        }
+
+
+        /**
+         * Update the table using an Ajax call
+         *  @param {object} oSettings dataTables settings object
+         *  @returns {boolean} Block the table drawing or not
+         *  @memberof DataTable#oApi
+         */
+        function _fnAjaxUpdate(oSettings) {
+          if (oSettings.bAjaxDataGet) {
+            oSettings.iDraw++;
+            _fnProcessingDisplay(oSettings, true);
+            var iColumns = oSettings.aoColumns.length;
+            var aoData = _fnAjaxParameters(oSettings);
+            _fnServerParams(oSettings, aoData);
+
+            oSettings.fnServerData.call(oSettings.oInstance, oSettings.sAjaxSource, aoData,
+              function (json) {
+                _fnAjaxUpdateDraw(oSettings, json);
+              }, oSettings);
+            return false;
+          }
+          else {
+            return true;
+          }
+        }
+
+
+        /**
+         * Build up the parameters in an object needed for a server-side processing request
+         *  @param {object} oSettings dataTables settings object
+         *  @returns {bool} block the table drawing or not
+         *  @memberof DataTable#oApi
+         */
+        function _fnAjaxParameters(oSettings) {
+          var iColumns = oSettings.aoColumns.length;
+          var aoData = [], mDataProp, aaSort, aDataSort;
+          var i, j;
+
+          aoData.push({ "name":"sEcho", "value":oSettings.iDraw });
+          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 });
+
+          for (i = 0; i < iColumns; i++) {
+            mDataProp = oSettings.aoColumns[i].mData;
+            aoData.push({ "name":"mDataProp_" + i, "value":typeof(mDataProp) === "function" ? 'function' : mDataProp });
+          }
+
+          /* Filtering */
+          if (oSettings.oFeatures.bFilter !== false) {
+            aoData.push({ "name":"sSearch", "value":oSettings.oPreviousSearch.sSearch });
+            aoData.push({ "name":"bRegex", "value":oSettings.oPreviousSearch.bRegex });
+            for (i = 0; i < iColumns; i++) {
+              aoData.push({ "name":"sSearch_" + i, "value":oSettings.aoPreSearchCols[i].sSearch });
+              aoData.push({ "name":"bRegex_" + i, "value":oSettings.aoPreSearchCols[i].bRegex });
+              aoData.push({ "name":"bSearchable_" + i, "value":oSettings.aoColumns[i].bSearchable });
+            }
+          }
+
+          /* Sorting */
+          if (oSettings.oFeatures.bSort !== false) {
+            var iCounter = 0;
+
+            aaSort = ( oSettings.aaSortingFixed !== null ) ?
+              oSettings.aaSortingFixed.concat(oSettings.aaSorting) :
+              oSettings.aaSorting.slice();
+
+            for (i = 0; i < aaSort.length; i++) {
+              aDataSort = oSettings.aoColumns[ aaSort[i][0] ].aDataSort;
+
+              for (j = 0; j < aDataSort.length; j++) {
+                aoData.push({ "name":"iSortCol_" + iCounter, "value":aDataSort[j] });
+                aoData.push({ "name":"sSortDir_" + iCounter, "value":aaSort[i][1] });
+                iCounter++;
+              }
+            }
+            aoData.push({ "name":"iSortingCols", "value":iCounter });
+
+            for (i = 0; i < iColumns; i++) {
+              aoData.push({ "name":"bSortable_" + i, "value":oSettings.aoColumns[i].bSortable });
+            }
+          }
+
+          return aoData;
+        }
+
+
+        /**
+         * Add Ajax parameters from plug-ins
+         *  @param {object} oSettings dataTables settings object
+         *  @param array {objects} aoData name/value pairs to send to the server
+         *  @memberof DataTable#oApi
+         */
+        function _fnServerParams(oSettings, aoData) {
+          _fnCallbackFire(oSettings, 'aoServerParams', 'serverParams', [aoData]);
+        }
+
+
+        /**
+         * Data the data from the server (nuking the old) and redraw the table
+         *  @param {object} oSettings dataTables settings object
+         *  @param {object} json json data return from the server.
+         *  @param {string} json.sEcho Tracking flag for DataTables to match requests
+         *  @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
+         *  @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
+         *  @param {array} json.aaData The data to display on this page
+         *  @param {string} [json.sColumns] Column ordering (sName, comma separated)
+         *  @memberof DataTable#oApi
+         */
+        function _fnAjaxUpdateDraw(oSettings, json) {
+          if (json.sEcho !== undefined) {
+            /* Protect against old returns over-writing a new one. Possible when you get
+             * very fast interaction, and later queries are completed much faster
+             */
+            if (json.sEcho * 1 < oSettings.iDraw) {
+              return;
+            }
+            else {
+              oSettings.iDraw = json.sEcho * 1;
+            }
+          }
+
+          if (!oSettings.oScroll.bInfinite ||
+            (oSettings.oScroll.bInfinite && (oSettings.bSorted || oSettings.bFiltered))) {
+            _fnClearTable(oSettings);
+          }
+          oSettings._iRecordsTotal = parseInt(json.iTotalRecords, 10);
+          oSettings._iRecordsDisplay = parseInt(json.iTotalDisplayRecords, 10);
+
+          /* Determine if reordering is required */
+          var sOrdering = _fnColumnOrdering(oSettings);
+          var bReOrder = (json.sColumns !== undefined && sOrdering !== "" && json.sColumns != sOrdering );
+          var aiIndex;
+          if (bReOrder) {
+            aiIndex = _fnReOrderIndex(oSettings, json.sColumns);
+          }
+
+          var aData = _fnGetObjectDataFn(oSettings.sAjaxDataProp)(json);
+          for (var i = 0, iLen = aData.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 aDataSorted = [];
+              for (var j = 0, jLen = oSettings.aoColumns.length; j < jLen; j++) {
+                aDataSorted.push(aData[i][ aiIndex[j] ]);
+              }
+              _fnAddData(oSettings, aDataSorted);
+            }
+            else {
+              /* No re-order required, sever got it "right" - just straight add */
+              _fnAddData(oSettings, aData[i]);
+            }
+          }
+          oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+
+          oSettings.bAjaxDataGet = false;
+          _fnDraw(oSettings);
+          oSettings.bAjaxDataGet = true;
+          _fnProcessingDisplay(oSettings, false);
+        }
+
+
+        /**
+         * Generate the node required for filtering text
+         *  @returns {node} Filter control element
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnFeatureHtmlFilter(oSettings) {
+          var oPreviousSearch = oSettings.oPreviousSearch;
+
+          var sSearchStr = oSettings.oLanguage.sSearch;
+          sSearchStr = (sSearchStr.indexOf('_INPUT_') !== -1) ?
+            sSearchStr.replace('_INPUT_', '<input type="text" />') :
+            sSearchStr === "" ? '<input type="text" />' : sSearchStr + ' <input type="text" />';
+
+          var nFilter = document.createElement('div');
+          nFilter.className = oSettings.oClasses.sFilter;
+          nFilter.innerHTML = '<label>' + sSearchStr + '</label>';
+          if (!oSettings.aanFeatures.f) {
+            nFilter.id = oSettings.sTableId + '_filter';
+          }
+
+          var jqFilter = $('input[type="text"]', nFilter);
+
+          // Store a reference to the input element, so other input elements could be
+          // added to the filter wrapper if needed (submit button for example)
+          nFilter._DT_Input = jqFilter[0];
+
+          jqFilter.val(oPreviousSearch.sSearch.replace('"', '&quot;'));
+          jqFilter.bind('keyup.DT', function (e) {
+            /* Update all other filter input elements for the new display */
+            var n = oSettings.aanFeatures.f;
+            var val = this.value === "" ? "" : this.value; // mental IE8 fix :-(
+
+            for (var i = 0, iLen = n.length; i < iLen; i++) {
+              if (n[i] != $(this).parents('div.dataTables_filter')[0]) {
+                $(n[i]._DT_Input).val(val);
+              }
+            }
+
+            /* Now do the filter */
+            if (val != oPreviousSearch.sSearch) {
+              _fnFilterComplete(oSettings, {
+                "sSearch":val,
+                "bRegex":oPreviousSearch.bRegex,
+                "bSmart":oPreviousSearch.bSmart,
+                "bCaseInsensitive":oPreviousSearch.bCaseInsensitive
+              });
+            }
+          });
+
+          jqFilter
+            .attr('aria-controls', oSettings.sTableId)
+            .bind('keypress.DT', function (e) {
+              /* Prevent form submission */
+              if (e.keyCode == 13) {
+                return false;
+              }
+            }
+          );
+
+          return nFilter;
+        }
+
+
+        /**
+         * Filter the table using both the global filter and column based filtering
+         *  @param {object} oSettings dataTables settings object
+         *  @param {object} oSearch search information
+         *  @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
+         *  @memberof DataTable#oApi
+         */
+        function _fnFilterComplete(oSettings, oInput, iForce) {
+          var oPrevSearch = oSettings.oPreviousSearch;
+          var aoPrevSearch = oSettings.aoPreSearchCols;
+          var fnSaveFilter = function (oFilter) {
+            /* Save the filtering values */
+            oPrevSearch.sSearch = oFilter.sSearch;
+            oPrevSearch.bRegex = oFilter.bRegex;
+            oPrevSearch.bSmart = oFilter.bSmart;
+            oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
+          };
+
+          /* In server-side processing all filtering is done by the server, so no point hanging around here */
+          if (!oSettings.oFeatures.bServerSide) {
+            /* Global filter */
+            _fnFilter(oSettings, oInput.sSearch, iForce, oInput.bRegex, oInput.bSmart, oInput.bCaseInsensitive);
+            fnSaveFilter(oInput);
+
+            /* Now do the individual column filter */
+            for (var i = 0; i < oSettings.aoPreSearchCols.length; i++) {
+              _fnFilterColumn(oSettings, aoPrevSearch[i].sSearch, i, aoPrevSearch[i].bRegex,
+                aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive);
+            }
+
+            /* Custom filtering */
+            _fnFilterCustom(oSettings);
+          }
+          else {
+            fnSaveFilter(oInput);
+          }
+
+          /* Tell the draw function we have been filtering */
+          oSettings.bFiltered = true;
+          $(oSettings.oInstance).trigger('filter', oSettings);
+
+          /* Redraw the table */
+          oSettings._iDisplayStart = 0;
+          _fnCalculateEnd(oSettings);
+          _fnDraw(oSettings);
+
+          /* Rebuild search array 'offline' */
+          _fnBuildSearchArray(oSettings, 0);
+        }
+
+
+        /**
+         * Apply custom filtering functions
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnFilterCustom(oSettings) {
+          var afnFilters = DataTable.ext.afnFiltering;
+          var aiFilterColumns = _fnGetColumns(oSettings, 'bSearchable');
+
+          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];
+              var bTest = afnFilters[i](
+                oSettings,
+                _fnGetRowData(oSettings, iDisIndex, 'filter', aiFilterColumns),
+                iDisIndex
+              );
+
+              /* Check if we should use this row based on the filtering function */
+              if (!bTest) {
+                oSettings.aiDisplay.splice(j - iCorrector, 1);
+                iCorrector++;
+              }
+            }
+          }
+        }
+
+
+        /**
+         * Filter the table on a per-column basis
+         *  @param {object} oSettings dataTables settings object
+         *  @param {string} sInput string to filter on
+         *  @param {int} iColumn column to filter
+         *  @param {bool} bRegex treat search string as a regular expression or not
+         *  @param {bool} bSmart use smart filtering or not
+         *  @param {bool} bCaseInsensitive Do case insenstive matching or not
+         *  @memberof DataTable#oApi
+         */
+        function _fnFilterColumn(oSettings, sInput, iColumn, bRegex, bSmart, bCaseInsensitive) {
+          if (sInput === "") {
+            return;
+          }
+
+          var iIndexCorrector = 0;
+          var rpSearch = _fnFilterCreateSearch(sInput, bRegex, bSmart, bCaseInsensitive);
+
+          for (var i = oSettings.aiDisplay.length - 1; i >= 0; i--) {
+            var sData = _fnDataToSearch(_fnGetCellData(oSettings, oSettings.aiDisplay[i], iColumn, 'filter'),
+              oSettings.aoColumns[iColumn].sType);
+            if (!rpSearch.test(sData)) {
+              oSettings.aiDisplay.splice(i, 1);
+              iIndexCorrector++;
+            }
+          }
+        }
+
+
+        /**
+         * Filter the data table based on user input and draw the table
+         *  @param {object} oSettings dataTables settings object
+         *  @param {string} sInput string to filter on
+         *  @param {int} iForce optional - force a research of the master array (1) or not (undefined or 0)
+         *  @param {bool} bRegex treat as a regular expression or not
+         *  @param {bool} bSmart perform smart filtering or not
+         *  @param {bool} bCaseInsensitive Do case insenstive matching or not
+         *  @memberof DataTable#oApi
+         */
+        function _fnFilter(oSettings, sInput, iForce, bRegex, bSmart, bCaseInsensitive) {
+          var i;
+          var rpSearch = _fnFilterCreateSearch(sInput, bRegex, bSmart, bCaseInsensitive);
+          var oPrevSearch = oSettings.oPreviousSearch;
+
+          /* Check if we are forcing or not - optional parameter */
+          if (!iForce) {
+            iForce = 0;
+          }
+
+          /* Need to take account of custom filtering functions - always filter */
+          if (DataTable.ext.afnFiltering.length !== 0) {
+            iForce = 1;
+          }
+
+          /*
+           * 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 ||
+              oPrevSearch.sSearch.length > sInput.length || iForce == 1 ||
+              sInput.indexOf(oPrevSearch.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++;
+                }
+              }
+            }
+          }
+        }
+
+
+        /**
+         * Create an array which can be quickly search through
+         *  @param {object} oSettings dataTables settings object
+         *  @param {int} iMaster use the master data array - optional
+         *  @memberof DataTable#oApi
+         */
+        function _fnBuildSearchArray(oSettings, iMaster) {
+          if (!oSettings.oFeatures.bServerSide) {
+            /* Clear out the old data */
+            oSettings.asDataSearch = [];
+
+            var aiFilterColumns = _fnGetColumns(oSettings, 'bSearchable');
+            var aiIndex = (iMaster === 1) ?
+              oSettings.aiDisplayMaster :
+              oSettings.aiDisplay;
+
+            for (var i = 0, iLen = aiIndex.length; i < iLen; i++) {
+              oSettings.asDataSearch[i] = _fnBuildSearchRow(
+                oSettings,
+                _fnGetRowData(oSettings, aiIndex[i], 'filter', aiFilterColumns)
+              );
+            }
+          }
+        }
+
+
+        /**
+         * Create a searchable string from a single data row
+         *  @param {object} oSettings dataTables settings object
+         *  @param {array} aData Row data array to use for the data to search
+         *  @memberof DataTable#oApi
+         */
+        function _fnBuildSearchRow(oSettings, aData) {
+          var sSearch = aData.join('  ');
+
+          /* If it looks like there is an HTML entity in the string, attempt to decode it */
+          if (sSearch.indexOf('&') !== -1) {
+            sSearch = $('<div>').html(sSearch).text();
+          }
+
+          // Strip newline characters
+          return sSearch.replace(/[\n\r]/g, " ");
+        }
+
+        /**
+         * Build a regular expression object suitable for searching a table
+         *  @param {string} sSearch string to search for
+         *  @param {bool} bRegex treat as a regular expression or not
+         *  @param {bool} bSmart perform smart filtering or not
+         *  @param {bool} bCaseInsensitive Do case insensitive matching or not
+         *  @returns {RegExp} constructed object
+         *  @memberof DataTable#oApi
+         */
+        function _fnFilterCreateSearch(sSearch, bRegex, bSmart, bCaseInsensitive) {
+          var asSearch, sRegExpString;
+
+          if (bSmart) {
+            /* Generate the regular expression to use. Something along the lines of:
+             * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
+             */
+            asSearch = bRegex ? sSearch.split(' ') : _fnEscapeRegex(sSearch).split(' ');
+            sRegExpString = '^(?=.*?' + asSearch.join(')(?=.*?') + ').*$';
+            return new RegExp(sRegExpString, bCaseInsensitive ? "i" : "");
+          }
+          else {
+            sSearch = bRegex ? sSearch : _fnEscapeRegex(sSearch);
+            return new RegExp(sSearch, bCaseInsensitive ? "i" : "");
+          }
+        }
+
+
+        /**
+         * Convert raw data into something that the user can search on
+         *  @param {string} sData data to be modified
+         *  @param {string} sType data type
+         *  @returns {string} search string
+         *  @memberof DataTable#oApi
+         */
+        function _fnDataToSearch(sData, sType) {
+          if (typeof DataTable.ext.ofnSearch[sType] === "function") {
+            return DataTable.ext.ofnSearch[sType](sData);
+          }
+          else if (sData === null) {
+            return '';
+          }
+          else if (sType == "html") {
+            return sData.replace(/[\r\n]/g, " ").replace(/<.*?>/g, "");
+          }
+          else if (typeof sData === "string") {
+            return sData.replace(/[\r\n]/g, " ");
+          }
+          return sData;
+        }
+
+
+        /**
+         * scape a string such that it can be used in a regular expression
+         *  @param {string} sVal string to escape
+         *  @returns {string} escaped string
+         *  @memberof DataTable#oApi
+         */
+        function _fnEscapeRegex(sVal) {
+          var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ];
+          var reReplace = new RegExp('(\\' + acEscape.join('|\\') + ')', 'g');
+          return sVal.replace(reReplace, '\\$1');
+        }
+
+
+        /**
+         * Generate the node required for the info display
+         *  @param {object} oSettings dataTables settings object
+         *  @returns {node} Information element
+         *  @memberof DataTable#oApi
+         */
+        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 (!oSettings.aanFeatures.i) {
+            /* Add draw callback */
+            oSettings.aoDrawCallback.push({
+              "fn":_fnUpdateInfo,
+              "sName":"information"
+            });
+
+            /* Add id */
+            nInfo.id = oSettings.sTableId + '_info';
+          }
+          oSettings.nTable.setAttribute('aria-describedby', oSettings.sTableId + '_info');
+
+          return nInfo;
+        }
+
+
+        /**
+         * Update the information elements in the display
+         *  @param {object} oSettings dataTables settings object
+         *  @memberof DataTable#oApi
+         */
+        function _fnUpdateInfo(oSettings) {
+          /* Show information about the table */

[... 9074 lines stripped ...]