/* Minification failed. Returning unminified contents.
(3894,17-18): run-time warning JS1004: Expected ';': r
(3895,17-18): run-time warning JS1004: Expected ';': c
(8566,17-18): run-time warning JS1004: Expected ';': i
(15909,116-117): run-time warning JS1100: Expected ',': =
(15976,112-113): run-time warning JS1100: Expected ',': =
(16015,35-36): run-time warning JS1195: Expected expression: .
(16015,45-46): run-time warning JS1195: Expected expression: )
(17407,6-7): run-time warning JS1004: Expected ';': t
(17408,6-7): run-time warning JS1004: Expected ';': h
(17409,6-7): run-time warning JS1004: Expected ';': h
(3894,13-16): run-time error JS1137: 'let' is a new reserved word and should not be used as an identifier: let
(3894,17-25): run-time error JS1300: Strict-mode does not allow assignment to undefined variables: 'rowModal': rowModal
(3895,13-16): run-time error JS1137: 'let' is a new reserved word and should not be used as an identifier: let
(3895,17-24): run-time error JS1300: Strict-mode does not allow assignment to undefined variables: 'columns': columns
(8566,13-16): run-time error JS1137: 'let' is a new reserved word and should not be used as an identifier: let
(8566,17-21): run-time error JS1300: Strict-mode does not allow assignment to undefined variables: 'inst': inst
(13942,35-41): run-time error JS1137: 'return' is a new reserved word and should not be used as an identifier: return
 */
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../base/MicrosoftAjax.debug.js" />
/// <reference path="../SysControls/SysElement.js" />
/// <reference path="../SysGrid/Matrix.js" />

function matrixRowTotalCell(me) {
	var field = me.id; 										//mtx_r1_rt
	this.matrix = field.substring(0, field.indexOf("_")); 	//mtx
	field = field.substring(this.matrix.length + 1);
	this.row = field.substring(0, field.indexOf("_")); 		//r1
}

function AdvancedBudgetMatrixTotalize(me) {
	var cell = new matrixCell(me);

	var delta = AdvancedBudgetMatrixCalcRowTotal(cell);
	if (delta == null) {
		SysMatrixCalcColumnTotal(cell);
	}
	else {
		SysMatrixSetColumnTotal(cell, delta);
		SysMatrixSetGrandTotal(cell, delta);
	}
}

function AdvancedBudgetMatrixCalcRowTotal(cell) {
	/// <summary>Calculates row total</summary>
	/// <returns>Difference between new total and old total</returns>
	/// <remarks>Handles readonly cells correctly</remarks>
	var el = SysGetElement(cell.matrix + "_" + cell.row + "_rt");
	if (el == null) return null;
	var oldValue = SysUnFormatNumber(el.value);
	var newValue = SysMatrixCalcTotal("#" + cell.matrix + " tr[id^='" + cell.matrix + "_r']:not(:hidden) input[id^='" + cell.matrix + "_" + cell.row + "_'][id$='_" + cell.field + "']");
	newValue += SysMatrixCalcTotalReadonly("#" + cell.matrix + " tr[id^='" + cell.matrix + "_r']:not(:hidden) span[id^='" + cell.matrix + "_" + cell.row + "_'][id$='_" + cell.field + "']");
	el.value = SysFormatNumber(newValue);
	return newValue - oldValue;
}

function AdvancedBudgetMatrixChangeRowTotal(me, cellCount) {
	/// <summary>Divide the value of the total cell over the individual cells</summary>
	var totalCell = new matrixRowTotalCell(me);
	var value = SysUnFormatNumber(me.value);
	var cellValue = value / cellCount;
	AdvancedBudgetMatrixChangeCellValue("#" + totalCell.matrix + " tr[id^='" + totalCell.matrix + "_r']:not(:hidden) input[id^='" + totalCell.matrix + "_" + totalCell.row + "_c']", cellValue);
	// Handle rounding differences
	me.value = AdvancedBudgetMatrixHandleRoundingDifferenceRowTotal(cellValue, cellCount);
}

function AdvancedBudgetMatrixChangeCellValue(selector, newValue) {
	els = $(selector).each(
		function () {
			var oldValue = SysUnFormatNumber(this.value);
			this.value = SysFormatNumber(newValue);
			var cell = new matrixCell(this);
			var delta = newValue - oldValue;
			SysMatrixSetColumnTotal(cell, delta);
			SysMatrixSetGrandTotal(cell, delta);
		}
	);
}

function AdvancedBudgetMatrixHandleRoundingDifferenceRowTotal(cellValue, cellCount) {
	var formattedValue = SysFormatNumber(cellValue);
	var unformattedValue = SysUnFormatNumber(formattedValue);
	return SysFormatNumber(cellCount * unformattedValue);
}

function OnGenerateBudget(e) {
	var re = /^mtx_r([0-9]+).*$/;
	var row = 'mtx_r' + $(e).attr('id').match(re)[1];
	var rowNumber = row.split('_')[1].substring(1);
	
	var item = SysGetElement(row + '_ItemCell');
	var itemValue = (item != null) ? item.value || null : null;
	var costUnit = SysGetElement(row + '_CostunitCell');
	var costUnitValue = (costUnit != null) ? costUnit.value || null : null;
	var costCenter = SysGetElement(row + '_CostcenterCell');
	var costCenterValue = (costCenter != null) ? costCenter.value || null : null;

	var url = new URL(window.location);
	var classID = url.searchParams.get("ClassID");

	var budgetLine =
	{
		"Item": itemValue,
		"CostUnit": costUnitValue,
		"CostCenter": costCenterValue};

	switch (parseInt(classID)) {
		case BudgetGroupBys.GLAccount:
			// set glAccount id
			budgetLine["GLAccount"] = SysGet(row + '_GLAccountCell');
			budgetLine["GLClassification"] = null;
			budgetLine["GLAccountType"] = null;
			break;
		case BudgetGroupBys.GLClassification:
			// set classification id
			budgetLine["GLAccount"] = null;
			budgetLine["GLClassification"] = SysGet(row + "_GLAccountClassificationID");
			budgetLine["GLAccountType"] = null;
			break;
		default:
			// set type 
			budgetLine["GLAccount"] = null;
			budgetLine["GLClassification"] = null;
			budgetLine["GLAccountType"] = SysGet(row + "_GLAccountTypeCell");
	}

	// all generate buttons should be disabled because of the generate budget background task is going to be run.
	$("img[id*='_rgb']").css({ "pointerEvents":"none"});

	SysSet("SelectedRowNumber", rowNumber);
	GenerateBudgetPage(budgetLine, classID)
}

function GenerateBudgetPage(selectedRow, classID) {
	var budgetScenarioInfo = {
		"BudgetLineGroups": [selectedRow],
		"BudgetScenario": SysGet("BudgetScenario"),
		"Year": SysGet("ReportingYear"),
		"GLScheme": SysGet("GLSchemeID"),
		"PeriodFrom": SysGet("Period_From"),
		"PeriodTo": SysGet("Period_To"),
		"GroupBy": classID,
		"GenerateModes": BudgetGenerateModes.CalculateBudgetLine
	};

	var url = new SysUrlBuilder("FinGenerateBudget.aspx");
	url.Add("ReportingYear", SysGet("ReportingYear"));
	url.Add("BudgetScenario", SysGet("BudgetScenario"));
	url.Add("BudgetGenerateMode", BudgetGenerateModes.CalculateBudgetLine);
	url.Add("SelectedRow", JSON.stringify(budgetScenarioInfo));

	var dlg = new Dialog({
		autoShow: true,
		fullScreen: false,
		resizable: true,
		width: 500,
		height: 300,
		contentsPage: url,
		returnFocus: document.activeElement,
		handler: function () {
			HandelGenerateResult(dlg.returnValue);
		},
		onClose: function () {
			HandelGenerateResult();
		}
	});
}

function HandelGenerateResult(result) {
	// the generate budget page is closed (with or without result), so all generate buttons should be enabled again.
	$("img[id*='_rgb']").css({ "pointerEvents": "" });

	if (result != null) {
		var periodAmount = JSON.parse(result);
		var selectedRow = SysGet("SelectedRowNumber");
		for (var i = 0; i < periodAmount.length; i++) {

			var period = periodAmount[i].Period;
			var amount = periodAmount[i].Amount;
			var rowId = 'mtx_r' + selectedRow + "_c" + period + "_amt";

			SysSet(rowId, amount);
			var rowElement = SysGetElement(rowId);
			AdvancedBudgetMatrixTotalize(rowElement);
		}
	}
};
/**
               _ _____           _          _     _      
              | |  __ \         (_)        | |   | |     
      ___ ___ | | |__) |___  ___ _ ______ _| |__ | | ___ 
     / __/ _ \| |  _  // _ \/ __| |_  / _` | '_ \| |/ _ \
    | (_| (_) | | | \ \  __/\__ \ |/ / (_| | |_) | |  __/
     \___\___/|_|_|  \_\___||___/_/___\__,_|_.__/|_|\___|
	 
	v1.6 - jQuery plugin created by Alvaro Prieto Lauroba
	
	Licences: MIT & GPL
	Feel free to use or modify this plugin as far as my full name is kept	
	
	If you are going to use this plug-in in production environments it is 
	strongly recommended to use its minified version: colResizable.min.js

*/

; (function ($) {

	var d = $(document); 		//window object
	var h = $("head");			//head object
	var drag = null;			//reference to the current grip that is being dragged
	var tables = {};			//object of the already processed tables (table.id as key)
	var count = 0;				//internal count to create unique IDs when needed.	
	var hasResizableColumnsUserSettings = 0;
	var userResizedColumns = 0;

	//common strings for packing
	var ID = "id";
	var PX = "px";
	var SIGNATURE = "JColResizer";
	var FLEX = "JCLRFlex";

	//short-cuts
	var I = parseInt;
	var M = Math;
	var ie = navigator.userAgent.indexOf('Trident/4.0') > 0;
	var S;
	try { S = sessionStorage; } catch (e) { }	//Firefox crashes when executed as local file system

	//append required CSS rules  
	//## Notes: ## .JColResizer{table-layout:fixed;}
	//-- To set the table-layout to fixed, each of the column is able to partial or fully overlap with each other.
	//   However, this is affecting the table column style due to some column will not able to see fully since the width is auto determine by the table.
	h.append("<style type='text/css'>.JColResizer > tbody > tr > td, .JColResizer > tbody > tr > th{overflow:hidden;}  .JCLRgrips{ height:0px; position:relative;} .JCLRgrip{margin-left:-5px; position:absolute; z-index:5; width: 1px; margin-left: 3px;filter:alpha(opacity=0.5);opacity:0.5;} .JCLRgrip:not(:last-child):hover {border-right: 1px dotted #808080;} .JCLRgrip .JColResizer{position:absolute;filter:alpha(opacity=1);opacity:0;width:10px;height:100%;cursor: e-resize;top:0px} .JCLRLastGrip{position:absolute; width:1px; } .JCLRgripDrag{ border-left:1px dotted black;	} .JCLRFlex{width:auto!important;} .JCLRgrip.JCLRdisabledGrip .JColResizer{cursor:default; display:none;}</style>");


	/**
	 * Function to allow column resizing for table objects. It is the starting point to apply the plugin.
	 * @param {DOM node} tb - reference to the DOM table object to be enhanced
	 * @param {Object} options	- some customization values
	 */
	var init = function (tb, options) {
		//The setting of style margin to 0 currently applies to FinInvoiceEntry.aspx target
		//due to a trick done on document object element style to prevent jumping of layout
		//found in scripts\FinEntryNew\css3-mediaquerries.js where they set the document style marginLeft to -32767px. 
		//If margin is not set to 0, the grid will be rendered by default with a very large width (32767 + screen resolution) for pages using the FinEntryNew css 
		if (document.documentElement.style.marginLeft !== "") document.documentElement.style.marginLeft = 0;
		bindWindowEvents();
		var t = $(tb);				    //the table object is wrapped
		t.opt = options;                //each table has its own options available at anytime
		t.mode = options.resizeMode;    //shortcuts
		t.dc = t.opt.disabledColumns;
		if (t.opt.disable) return destroy(t);				//the user is asking to destroy a previously colResized table
		var id = t.id = t.attr(ID) || SIGNATURE + count++;	//its id is obtained, if null new one is generated		
		hasResizableColumnsUserSettings = ($('#' + id + '_ResizableColumnsCtl').val() !== "");
		t.p = t.opt.postbackSafe; 							//short-cut to detect postback safe 		
		if (!t.is("table") || tables[id] && !t.opt.partialRefresh) return; 		//if the object is not a table or if it was already processed then it is ignored.
		if (t.opt.hoverCursor !== 'e-resize') h.append("<style type='text/css'>.JCLRgrip .JColResizer:hover{cursor:" + t.opt.hoverCursor + "!important}</style>");  //if hoverCursor has been set, append the style
		t.addClass(SIGNATURE).attr(ID, id).before('<div class="JCLRgrips"/>');	//the grips container object is added. Signature class forces table rendering in fixed-layout mode to prevent column's min-width
		t.g = []; t.c = []; t.w = t.width(); t.gc = t.prev(); t.f = t.opt.fixed;	//t.c and t.g are arrays of columns and grips respectively				
		if (options.marginLeft) t.gc.css("marginLeft", options.marginLeft);  	//if the table contains margins, it must be specified
		if (options.marginRight) t.gc.css("marginRight", options.marginRight);  	//since there is no (direct) way to obtain margin values in its original units (%, em, ...)
		t.cs = I(ie ? tb.cellSpacing || tb.currentStyle.borderSpacing : t.css('border-spacing')) || 2;	//table cellspacing (not even jQuery is fully cross-browser)
		t.b = I(ie ? tb.border || tb.currentStyle.borderLeftWidth : t.css('border-left-width')) || 1;	//outer border width (again cross-browser issues)
		// if(!(tb.style.width || tb.width)) t.width(t.width()); //I am not an IE fan at all, but it is a pity that only IE has the currentStyle attribute working as expected. For this reason I can not check easily if the table has an explicit width or if it is rendered as "auto"
		tables[id] = t; 	//the table object is stored using its id as key	
		createGrips(t);		//grips are created 
		var cb = t.opt.onResize;
		if (cb) { cb(); }	//necessary to call during init to reallign the extra table after adding new fields to the extended grid tables
	};

	var bindWindowEvents = function () {
		//bind resize event, to update grips position 
		$(window).bind('resize.' + SIGNATURE, onResize);

		//We need to call this function to recalculate the table width (especially table wrapped using responsive div container) 
		//to ensure the draggable div is able to wrap the table correctly.
		$(window).load(function () {
			onResize();
		});
	};


	/**
	 * This function allows to remove any enhancements performed by this plugin on a previously processed table.
	 * @param {jQuery ref} t - table object
	 */
	var destroy = function (t) {
		var id = t.attr(ID), t = tables[id];		//its table object is found
		if (!t || !t.is("table")) return;			//if none, then it wasn't processed	 
		t.removeClass(SIGNATURE + " " + FLEX).gc.remove();	//class and grips are removed
		delete tables[id];						//clean up data
	};


	/**
	 * Function to create all the grips associated with the table given by parameters 
	 * @param {jQuery ref} t - table object
	 */
	var createGrips = function (t) {

		var th = t.find(">thead>tr:first>th.resizable-col,>thead>tr:first>td.resizable-col"); //table headers are obtained
		if (!th.length) th = t.find(">tbody>tr:first>th.resizable-col,>tr:first>th.resizable-col,>tbody>tr:first>td.resizable-col, >tr:first>td.resizable-col");	 //but headers can also be included in different ways
		th = th.filter(":visible");					//filter invisible columns
		t.cg = t.find("col.resizable-col");			//a table can also contain a colgroup with col elements
		t.ln = th.length;							//table length is stored	

		userResizedColumns = hasDefaultResizedColumnData(t.id);
		updateResizableColumnJSONData(t.id, t, th);

		var sessionStoreExist = (t.p && S && S[getResizableSessionStorageKeyBy(t.id)]);
		if (sessionStoreExist) {
			memento(t, th);		//if 'postbackSafe' is enabled and there is data for the current table, its coloumn layout is restored
		}

		th.each(function (i) {						//iterate through the table column headers						
			var c = $(this); 						//jquery wrap for the current column
			var dc = t.dc.indexOf(i) != -1;           //is this a disabled column?
			var g = $(t.gc.append('<div class="JCLRgrip"></div>')[0].lastChild); //add the visual node to be used as grip
			g.append(dc ? "" : t.opt.gripInnerHtml).append('<div class="' + SIGNATURE + '"></div>');
			if (i == t.ln - 1) {                        //if the current grip is the las one 
				g.addClass("JCLRLastGrip");         //add a different css class to stlye it in a different way if needed
				if (t.f) g.html("");                 //if the table resizing mode is set to fixed, the last grip is removed since table with can not change
			}
			g.bind('touchstart mousedown', onGripMouseDown); //bind the mousedown event to start dragging 

			if (!dc) {
				//if normal column bind the mousedown event to start dragging, if disabled then apply its css class
				g.removeClass('JCLRdisabledGrip').bind('touchstart mousedown', onGripMouseDown);
			} else {
				g.addClass('JCLRdisabledGrip');
			}

			g.t = t; g.i = i; g.c = c; c.w = c.width();		//some values are stored in the grip's node data as shortcut
			t.g.push(g); t.c.push(c);						//the current grip and column are added to its table object
			c.width(c.w).removeAttr("width");				//the width of the column is converted into pixel-based measurements
			g.data(SIGNATURE, { i: i, t: t.attr(ID), last: i == t.ln - 1 });	 //grip index and its table name are stored in the HTML 												
		});
		t.cg.removeAttr("width");	//remove the width attribute from elements in the colgroup 

		t.find("td.resizable-col, th.resizable-col").not(th).not("table th.resizable-col, table td.resizable-col").each(function () {
			$(this).removeAttr('width');	//the width attribute is removed from all table cells which are not nested in other tables and dont belong to the header
		});
		if (!t.f) {
			t.removeAttr('width').addClass(FLEX); //if not fixed, let the table grow as needed
		}
		syncGrips(t); 				//the grips are positioned according to the current table layout			
		//there is a small problem, some cells in the table could contain dimension values interfering with the 
		//width value set by this plugin. Those values are removed

	};


	/**
	 * Function to allow the persistence of columns dimensions after a browser postback. It is based in
	 * the HTML5 sessionStorage object, which can be emulated for older browsers using sessionstorage.js
	 * @param {jQuery ref} t - table object
	 * @param {jQuery ref} th - reference to the first row elements (only set in deserialization)
	 */
	var memento = function (t, th) {
		var w, m = 0, i = 0, aux = [], tw;
		if (th) {										//in deserialization mode (after a postback)			
			//Triggered when page is refresh and session store is exist
			updateResizableColumnJSONData(t.id, t, th);

			t.cg.removeAttr("width");
			if (t.opt.flush) { S[getResizableSessionStorageKeyBy(t.id)] = ""; return; } 	//if flush is activated, stored data is removed

			var totalPixelUsedByColumns = resizableColumnJSONData["totalPixelUsedByColumns"];
			tw = resizableColumnJSONData["totalPixelUsedByColumns"];
			if (!t.f && tw) {							//if not fixed and table width data available its size is restored								
				t.width(tw *= 1);
				if (t.opt.overflow) {				//if overfolw flag is set, restore table width also as table min-width
					t.css('min-width', tw + PX);
					t.w = tw;
				}
			}
			for (i = 0; i < t.ln; i++) {						//for each visible column
				var columnId = th.eq(i).attr("id");
				var columnWidth = resizableColumnJSONData[columnId];
				//width is stored in an array since it will be required again a couple of lines ahead
				//aux.push(100 * columnWidth / totalPixelUsedByColumns + "%"); // after resizing, table is rendered fit to the screen when the page is reloaded
				if (!(hasResizableColumnsUserSettings || userResizedColumns)) {
					columnWidth += 12; //each column has 6px of margin on each side thus the value 12. The margin pixel count is lost when the table is rendered the 1st time.
				}
				aux.push(columnWidth + "PX"); //if a table is resized smaller than the page, it should be rendered with the same width next time (not fit to screen)
				th.eq(i).css("width", aux[i]); 	//each column width in % is restored
			}
			for (i = 0; i < t.ln; i++) {
				t.cg.eq(i).css("width", aux[i]);	//this code is required in order to create an inline CSS rule with higher precedence than an existing CSS class in the "col" elements
			}

		} else {							//in serialization mode (after resizing a column)
			//Triggered when column is resizable

			for (i = 0; i < t.c.length; i++) {	//iterate through columns
				var columnId = t.c[i].attr('id');
				//w = t.c[i].width();		//width is obtained
				w = t.c[i][0].clientWidth;	//width including the padding size is obtained

				//Set Minimum width to ensure column width will not be too small.
				if (w <= 0) {
					w = 30;
				}
				if (columnId !== undefined) {
					saveResizableColumnJSONData(t.id, columnId, w);
				}

				m += w; //carriage is updated to obtain the full size used by columns
			}

			saveResizableColumnJSONData(t.id, "totalPixelUsedByColumns", m);

			//if not fixed, table width is stored			
			if (!t.f) {
				saveResizableColumnJSONData(t.id, "tableWidth", t.width);
			}
		}
	};


	/**
	 * Function that places each grip in the correct position according to the current table layout	 
	 * @param {jQuery ref} t - table object
	 */
	var syncGrips = function (t) {
		t.gc.width(t.w);			//the grip's container width is updated				
		for (var i = 0; i < t.ln; i++) {	//for each column
			var c = t.c[i];
			
			var pagingRowHeight = 0;
			var pagingButton = t.find("tr.Header:visible .pgButton");
			if (pagingButton.length) {
				var pagingHeader = pagingButton.closest("tr.Header");
				pagingRowHeight = pagingHeader ? $(pagingHeader).outerHeight() : 0;
			}

			var footerHeight = 0;
			$.each(t.find("tr.Footer:visible"), function (index, value) {
				footerHeight += $(value).outerHeight();
			});

			//height and position of the grip is updated according to the table layout
			//if there is a footer and paging, exclude it in the height of the grip.
			t.g[i].css({			
				left: c.offset().left - t.offset().left + c.outerWidth(false) + t.cs / 2 + PX,
				height: t.opt.headerOnly ? t.c[0].outerHeight(false) : t.outerHeight(false) - pagingRowHeight - footerHeight
			});
		}
	};



	/**
	* This function updates column's width according to the horizontal position increment of the grip being
	* dragged. The function can be called while dragging if liveDragging is enabled and also from the onGripDragOver
	* event handler to synchronize grip's position with their related columns.
	* @param {jQuery ref} t - table object
	* @param {number} i - index of the grip being dragged
	* @param {bool} isOver - to identify when the function is being called from the onGripDragOver event	
	*/
	var syncCols = function (t, i, isOver) {
		var inc = drag.x - drag.l, c = t.c[i], c2 = t.c[i + 1];
		var w = c.w + inc;
		var w2 = 0;	//their new width is obtained					
		if (c2 != undefined) w2 = c2.w - inc;
		c.width(w + PX);
		t.cg.eq(i).width(w + PX);
		if (t.f) { //if fixed mode
			c2.width(w2 + PX);
			t.cg.eq(i + 1).width(w2 + PX);
		} else if (t.opt.overflow) {				//if overflow is set, incriment min-width to force overflow
			t.css('min-width', t.w + inc);
			reAssignColumnWidth(t, i);
		}
		if (isOver) {
			c.w = w;
			c2.w = t.f ? w2 : c2.w;
		}
	};


	//as the drag action happens, loop thru each grid column, 
	//get the column widths and reassign back so that they do not adjust
	var reAssignColumnWidth = function (t, i) {
		for (count = 0; count < t.ln; count++) {
			if (count != i) { //if current index is not the dragged grip index
				t.cg.eq(count).width(t.c[count].w + PX);
			}
			else {
				//change the width of the column to the left of the grip 
				//being dragged
				var draggedLength = drag.x - drag.l;
				var columnLeft = t.c[i];
				var columnLeftNewWidth = columnLeft.w + draggedLength;

				columnLeft.width(columnLeftNewWidth + PX);
				t.cg.eq(i).width(columnLeftNewWidth + PX);
			}
		}
	};

	/**
	* This function updates all columns width according to its real width. It must be taken into account that the 
	* sum of all columns can exceed the table width in some cases (if fixed is set to false and table has some kind 
	* of max-width).
	* @param {jQuery ref} t - table object	
	*/
	var applyBounds = function (t) {
		var w = $.map(t.c, function (c) {			//obtain real widths
			return c.width();
		});
		t.width(t.w = t.width()).removeClass(FLEX);	//prevent table width changes
		$.each(t.c, function (i, c) {
			c.width(w[i]).w = w[i];				//set column widths applying bounds (table's max-width)
		});
		t.addClass(FLEX);						//allow table width changes
	};


	/**
	 * Event handler used while dragging a grip. It checks if the next grip's position is valid and updates it. 
	 * @param {event} e - mousemove event binded to the window object
	 */
	var onGripDrag = function (e) {
		if (!drag) return;
		var t = drag.t;		//table object reference 
		var oe = e.originalEvent.touches;
		var ox = oe ? oe[0].pageX : e.pageX;    //original position (touch or mouse)
		var x = ox - drag.ox + drag.l;	        //next position according to horizontal mouse position increment
		var mw = t.opt.minWidth, i = drag.i;	//cell's min width
		var l = t.cs * 1.5 + mw + t.b;
		var last = i == t.ln - 1;                 			//check if it is the last column's grip (usually hidden)
		var min = i ? t.g[i - 1].position().left + t.cs + mw : l;	//min position according to the contiguous cells
		var max = t.f ? 	//fixed mode?
			i == t.ln - 1 ?
				t.w - l :
				t.g[i + 1].position().left - t.cs - mw :
			Infinity; 								//max position according to the contiguous cells 
		x = M.max(min, M.min(max, x));				//apply bounding		
		drag.x = x; drag.css("left", x + PX); 	//apply position increment	
		if (last) {									//if it is the last grip
			var c = t.c[drag.i];					//width of the last column is obtained
			drag.w = c.w + x - drag.l;
		}
		if (t.opt.liveDrag) { 			//if liveDrag is enabled
			if (last) {
				c.width(drag.w);
				if (!t.f && t.opt.overflow) {			//if overflow is set, incriment min-width to force overflow
					t.css('min-width', t.w + x - drag.l);
				} else {
					t.w = t.width();
				}
			}
			syncCols(t, i); 			//columns are synchronized
			syncGrips(t);
			var cb = t.opt.onDrag;							//check if there is an onDrag callback
			if (cb) { e.currentTarget = t[0]; cb(e); }		//if any, it is fired			
		}
		return false; 	//prevent text selection while dragging				
	};


	/**
	 * Event handler fired when the dragging is over, updating table layout
     * @param {event} e - grip's drag over event
	 */
	var onGripDragOver = function (e) {

		d.unbind('touchend.' + SIGNATURE + ' mouseup.' + SIGNATURE).unbind('touchmove.' + SIGNATURE + ' mousemove.' + SIGNATURE);
		$("head :last-child").remove(); 				//remove the dragging cursor style	
		if (!drag) return;
		drag.removeClass(drag.t.opt.draggingClass);		//remove the grip's dragging css-class
		if (!(drag.x - drag.l == 0)) {
			var t = drag.t;
			var cb = t.opt.onResize; 	    //get some values	
			var i = drag.i;                 //column index
			var last = i == t.ln - 1;         //check if it is the last column's grip (usually hidden)
			var c = t.g[i].c;               //the column being dragged
			if (last) {
				c.width(drag.w);
				c.w = drag.w;
			} else {
				syncCols(t, i, true);	//the columns are updated
			}
			if (!t.f) applyBounds(t);	//if not fixed mode, then apply bounds to obtain real width values
			syncGrips(t);				//the grips are updated
			if (cb) { e.currentTarget = t[0]; cb(e); }	//if there is a callback function, it is fired
			if (t.p && S) memento(t); 	//if postbackSafe is enabled and there is sessionStorage support, the new layout is serialized and stored
		}
		drag = null;   //since the grip's dragging is over									
	};


	/**
	 * Event handler fired when the grip's dragging is about to start. Its main goal is to set up events 
	 * and store some values used while dragging.
	 * @param {event} e - grip's mousedown event
	 */
	var onGripMouseDown = function (e) {
		var o = $(this).data(SIGNATURE);			//retrieve grip's data
		var t = tables[o.t], g = t.g[o.i];			//shortcuts for the table and grip objects
		var oe = e.originalEvent.touches;           //touch or mouse event?
		g.ox = oe ? oe[0].pageX : e.pageX;            //the initial position is kept
		g.l = g.position().left;
		g.x = g.l;

		d.bind('touchmove.' + SIGNATURE + ' mousemove.' + SIGNATURE, onGripDrag).bind('touchend.' + SIGNATURE + ' mouseup.' + SIGNATURE, onGripDragOver);	//mousemove and mouseup events are bound
		h.append("<style type='text/css'>*{cursor:" + t.opt.dragCursor + "!important}</style>"); 	//change the mouse cursor
		g.addClass(t.opt.draggingClass); 	//add the dragging class (to allow some visual feedback)				
		drag = g;							//the current grip is stored as the current dragging object
		if (t.c[o.i].l) for (var i = 0, c; i < t.ln; i++) { c = t.c[i]; c.l = false; c.w = c.width(); } 	//if the colum is locked (after browser resize), then c.w must be updated		
		return false; 	//prevent text selection
	};


	/**
	 * Event handler fired when the browser is resized. The main purpose of this function is to update
	 * table layout according to the browser's size synchronizing related grips 
	 */
	var onResize = function () {
		for (var t in tables) {
			if (tables.hasOwnProperty(t)) {
				t = tables[t];
				var i, mw = 0;
				t.removeClass(SIGNATURE);   //firefox doesn't like layout-fixed in some cases
				if (t.f) {                  //in fixed mode
					t.w = t.width();        //its new width is kept
					for (i = 0; i < t.ln; i++) mw += t.c[i].w;
					//cell rendering is not as trivial as it might seem, and it is slightly different for
					//each browser. In the beginning i had a big switch for each browser, but since the code
					//was extremely ugly now I use a different approach with several re-flows. This works 
					//pretty well but it's a bit slower. For now, lets keep things simple...   
					for (i = 0; i < t.ln; i++) t.c[i].css("width", M.round(1024 * t.c[i].w / mw) / 10 + "%").l = true;
					//c.l locks the column, telling us that its c.w is outdated									
				} else {     //in non fixed-sized tables
					applyBounds(t);         //apply the new bounds 
					if (t.mode == 'flex' && t.p && S) {   //if postbackSafe is enabled and there is sessionStorage support,
						memento(t);                     //the new layout is serialized and stored for 'flex' tables
					}
				}
				syncGrips(t.addClass(SIGNATURE));
			}
		}

	};

	var getResizableSessionStorageKeyBy = function (tableId) {
		//SysGetApplicationName function is generated on server side. 
		return "resizable_" + SysGetApplicationName() + "_" + SysDivision() + "_" + tableId;
	};

	var getAllTableResizableSessionStorageKey = function () {
		return "resizable_" + SysGetApplicationName() + "_" + SysDivision() + "_AllTables";
	};

	var allTableResizableColumnJSONData = {}; //To hold all table resizable JSON data. The page can contains more than 1 table.
	var resizableColumnJSONData = {}; //To hold 1 table resizable JSON data. 

	var updateResizableColumnsHiddenFieldAndSessionStorage = function (tableId) {
		allTableResizableColumnJSONData[tableId] = resizableColumnJSONData;

		var stringifyResizableColumnJSONData = JSON.stringify(resizableColumnJSONData);

		$('#' + tableId + '_ResizableColumnsCtl').val(stringifyResizableColumnJSONData);

		S[getResizableSessionStorageKeyBy(tableId)] = stringifyResizableColumnJSONData;
		S[getAllTableResizableSessionStorageKey()] = JSON.stringify(allTableResizableColumnJSONData);
	};

	var setResizableColumnJSONData = function (tableId) {
		if (S[getAllTableResizableSessionStorageKey()]) {
			allTableResizableColumnJSONData = JSON.parse(S[getAllTableResizableSessionStorageKey()]);
		}

		//When we have more than 1 table in the page, retrieve table resizable JSON data based on table id. 
		resizableColumnJSONData = allTableResizableColumnJSONData[tableId];

		if (resizableColumnJSONData === undefined) {
			resizableColumnJSONData = {};
		}
	};

	// Triggered when column is resized.
	var saveResizableColumnJSONData = function (tableId, columnId, columnWidth) {
		setResizableColumnJSONData(tableId);

		resizableColumnJSONData[columnId] = columnWidth;

		updateResizableColumnsHiddenFieldAndSessionStorage(tableId);
	};

	// Triggered when page is initialized and refresh mode.
	var updateResizableColumnJSONData = function (tableId, t, th) {
		//Update resizable column JSON based on latest updates on the grid visible columns
		//-- Column already exists with the customized width, we need to restore it instead of overwrite the column width value.
		//-- New column that just added to the grid will used the default column width assigned to the column header (th)
		var totalPixelUsedByColumns = 0;
		var updatedResizableColumnJSONData = {};

		setResizableColumnJSONData(tableId);

		//Load resizable column based on hidden field value.
		var isEmptyObject = $.isEmptyObject(resizableColumnJSONData);

		var resizableColumnsHiddenField = $('#' + tableId + '_ResizableColumnsCtl');
		if (resizableColumnsHiddenField && resizableColumnsHiddenField.val() !== "" && isEmptyObject) {
			resizableColumnJSONData = JSON.parse(resizableColumnsHiddenField.val());
		}

		//Re-populate and massage resizable column json data based on latest table changes (Customization)
		for (i = 0; i < t.ln; i++) { //iterate through columns
			var columnId = th.eq(i).attr("id");
			var thWidth = th.eq(i).width();
			var newAddedColumn = (resizableColumnJSONData[columnId] === undefined);

			if (newAddedColumn) {
				//Set newly added column with default width.
				updatedResizableColumnJSONData[columnId] = thWidth;
			} else {
				//set existing column with resized width
				thWidth = resizableColumnJSONData[columnId];
				updatedResizableColumnJSONData[columnId] = thWidth;
			}
			totalPixelUsedByColumns += thWidth;
		}
		updatedResizableColumnJSONData["totalPixelUsedByColumns"] = totalPixelUsedByColumns;

		//Set the table resizable column JSON data
		resizableColumnJSONData = updatedResizableColumnJSONData;

		updateResizableColumnsHiddenFieldAndSessionStorage(tableId);
	};

	var hasDefaultResizedColumnData = function (tableId) {
		if (S[getAllTableResizableSessionStorageKey()]) {
			allTableResizableColumnJSONData = JSON.parse(S[getAllTableResizableSessionStorageKey()]);
			return (allTableResizableColumnJSONData[tableId] !== undefined);
		}
		else return 0;
	};

	/**
	 * The plugin is added to the jQuery library
	 * @param {Object} options -  an object that holds some basic customization values 
	 */
	$.fn.extend({
		colResizable: function (options) {
			var defaults = {

				//attributes:

				resizeMode: 'fit',                    //mode can be 'fit', 'flex' or 'overflow'
				draggingClass: 'JCLRgripDrag',	//css-class used when a grip is being dragged (for visual feedback purposes)
				gripInnerHtml: '',				//if it is required to use a custom grip it can be done using some custom HTML				
				liveDrag: false,				//enables table-layout updating while dragging	
				minWidth: 15, 					//minimum width value in pixels allowed for a column 
				headerOnly: false,				//specifies that the size of the the column resizing anchors will be bounded to the size of the first row 
				hoverCursor: "e-resize",  		//cursor to be used on grip hover
				dragCursor: "e-resize",  		//cursor to be used while dragging
				postbackSafe: false, 			//when it is enabled, table layout can persist after postback or page refresh. It requires browsers with sessionStorage support (it can be emulated with sessionStorage.js). 
				flush: false, 					//when postbakSafe is enabled, and it is required to prevent layout restoration after postback, 'flush' will remove its associated layout data 
				marginLeft: null,				//in case the table contains any margins, colResizable needs to know the values used, e.g. "10%", "15em", "5px" ...
				marginRight: null, 				//in case the table contains any margins, colResizable needs to know the values used, e.g. "10%", "15em", "5px" ...
				disable: false,					//disables all the enhancements performed in a previously colResized table	
				partialRefresh: false,			//can be used in combination with postbackSafe when the table is inside of an updatePanel,
				disabledColumns: [],			//column indexes to be excluded

				//events:
				onDrag: null, 					//callback function to be fired during the column resizing process if liveDrag is enabled
				onResize: null					//callback function fired when the dragging process is over
			}
			var options = $.extend(defaults, options);

			//since now there are 3 different ways of resizing columns, I changed the external interface to make it clear
			//calling it 'resizeMode' but also to remove the "fixed" attribute which was confusing for many people
			options.fixed = true;
			options.overflow = false;
			switch (options.resizeMode) {
				case 'flex': options.fixed = false; break;
				case 'overflow': options.fixed = false; options.overflow = true; break;
			}

			return this.each(function () {
				init(this, options);
			});
		}
	});
})(jQuery);

;
(function(){
	
	// Apply $.DataTable to the default jQuery instance;
	// pass through to the specific versioned jQuery instance, because datatable doesn't support jQuery v1.5.
	// Prepare BCMatrix table before applying DataTable component.
	$.fn.extend({
		DataTableModified: function (options) {
			var tbl = $(this);
			
			prepareBcMatrixTable(tbl);
			moveHiddenInputs(tbl);

			return jQuery(tbl.get(0)).DataTable(options);
		}
	});

	// Move hidden inputs to the last column, so they won't get cloned.
	// Note: don't use non-hidden input fields in cloned columns, as they can't be moved.
	function moveHiddenInputs(tbl) {
		tbl.find('tbody tr input:hidden').each(function () {
			$(this).appendTo($(this).closest('tr').children('td:last-child'));
		});
	}
	
	// Fix DOM issues with BCMatrix before passing it on to the datatables component
	function prepareBcMatrixTable(tbl) {
		
		// rowtotals=false hides the column instead of not rendering, fix this.
		tbl.find('tr td.RowNumber:hidden:not(:first-child)').remove();

		var tblBody = tbl.children('tbody').first();
		var bcMatrixRows = tblBody.children('tr');
		var bcMatrixHeaderRows = bcMatrixRows.filter('.Header');
		if (bcMatrixHeaderRows.length)
		{
			var dataRows = bcMatrixRows.not('.Header');
			var firstDataRowIndex = bcMatrixRows.index(dataRows.first());
			var headers = bcMatrixRows.slice(0, firstDataRowIndex);
			var footers = bcMatrixHeaderRows.not(headers);

			// datatables fixedcolumn fixes thead and tfoot
			// move headers into a thead tag
			if (headers.length && !tbl.children('thead').length)
				headers.wrapAll('<thead />').parent().insertBefore(tblBody);
			// don't move footers, as the total field update doesn't apply to the moved footer.

			// Place double BCMatrix headers on a single row
			if (headers.length == 2 && headers.children('.CellColumnHeader').length) {
				var topHeader = headers.eq(0), subHeader = headers.eq(1);
				var headerTitles = topHeader.children(':not(.CellColumnHeader)');
				subHeader.children().slice(0, headerTitles.length).remove();
				subHeader.prepend(headerTitles);
				topHeader.remove();
			}
		}
		
	}

})();;
/*
 * DataTables supports jQuery 1.7+;
 * [$, jQuery] currently run v1.5 and an alias [jQuery_v1_11] runs on v1.11.0.
 * Remapping $ and jQuery to jQuery_v1_11 within this current scope
 * to have datatables defined on the newer version instance of jQuery.
 */
(function($, jQuery){

    /*
     * This combined file was created by the DataTables downloader builder:
     *   https://datatables.net/download
     *
     * To rebuild or modify this file with the latest versions of the included
     * software please visit:
     *   https://datatables.net/download/#dt/dt-2.1.5/fc-5.0.1
     *
     * Included libraries:
     *   DataTables 2.1.5, FixedColumns 5.0.1
     */
    
    /*! DataTables 2.1.5
     * © SpryMedia Ltd - datatables.net/license
     */
    
    /**
     * @summary     DataTables
     * @description Paginate, search and order HTML tables
     * @version     2.1.5
     * @author      SpryMedia Ltd
     * @contact     www.datatables.net
     * @copyright   SpryMedia Ltd.
     *
     * This source file is free software, available under the following license:
     *   MIT license - https://datatables.net/license
     *
     * 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: https://www.datatables.net
     */
    
    (function( factory ) {
        "use strict";
    
        if ( typeof define === 'function' && define.amd ) {
            // AMD
            define( ['jquery'], function ( $ ) {
                return factory( $, window, document );
            } );
        }
        else if ( typeof exports === 'object' ) {
            // CommonJS
            // jQuery's factory checks for a global window - if it isn't present then it
            // returns a factory function that expects the window object
            var jq = require('jquery');
    
            if (typeof window === 'undefined') {
                module.exports = function (root, $) {
                    if ( ! root ) {
                        // CommonJS environments without a window global must pass a
                        // root. This will give an error otherwise
                        root = window;
                    }
    
                    if ( ! $ ) {
                        $ = jq( root );
                    }
    
                    return factory( $, root, root.document );
                };
            }
            else {
                module.exports = factory( jq, window, window.document );
            }
        }
        else {
            // Browser
            window.DataTable = factory( jQuery, window, document );
        }
    }(function( $, window, document ) {
        "use strict";
    
        
        var DataTable = function ( selector, options )
        {
            // Check if called with a window or jQuery object for DOM less applications
            // This is for backwards compatibility
            if (DataTable.factory(selector, options)) {
                return DataTable;
            }
        
            // When creating with `new`, create a new DataTable, returning the API instance
            if (this instanceof DataTable) {
                return $(selector).DataTable(options);
            }
            else {
                // Argument switching
                options = selector;
            }
        
            var _that = this;
            var emptyInit = options === undefined;
            var len = this.length;
        
            if ( emptyInit ) {
                options = {};
            }
        
            // Method to get DT API instance from jQuery object
            this.api = function ()
            {
                return new _Api( this );
            };
        
            this.each(function() {
                // For each initialisation we want to give it a clean initialisation
                // object that can be bashed around
                var o = {};
                var oInit = len > 1 ? // optimisation for single table case
                    _fnExtend( o, options, true ) :
                    options;
        
                
                var i=0, iLen;
                var sId = this.getAttribute( 'id' );
                var defaults = DataTable.defaults;
                var $this = $(this);
                
                
                /* Sanity check */
                if ( this.nodeName.toLowerCase() != 'table' )
                {
                    _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
                    return;
                }
                
                $(this).trigger( 'options.dt', oInit );
                
                /* Backwards compatibility for the defaults */
                _fnCompatOpts( defaults );
                _fnCompatCols( defaults.column );
                
                /* Convert the camel-case defaults to Hungarian */
                _fnCamelToHungarian( defaults, defaults, true );
                _fnCamelToHungarian( defaults.column, defaults.column, true );
                
                /* Setting up the initialisation object */
                _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true );
                
                
                
                /* Check to see if we are re-initialising a table */
                var allSettings = DataTable.settings;
                for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
                {
                    var s = allSettings[i];
                
                    /* Base check on table node */
                    if (
                        s.nTable == this ||
                        (s.nTHead && s.nTHead.parentNode == this) ||
                        (s.nTFoot && s.nTFoot.parentNode == this)
                    ) {
                        var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
                        var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
                
                        if ( emptyInit || bRetrieve )
                        {
                            return s.oInstance;
                        }
                        else if ( bDestroy )
                        {
                            new DataTable.Api(s).destroy();
                            break;
                        }
                        else
                        {
                            _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
                            return;
                        }
                    }
                
                    /* If the element we are initialising has the same ID as a table which was previously
                     * initialised, but the table nodes don't match (from before) then we destroy the old
                     * instance by simply deleting it. This is under the assumption that the table has been
                     * destroyed by other methods. Anyone using non-id selectors will need to do this manually
                     */
                    if ( s.sTableId == this.id )
                    {
                        allSettings.splice( i, 1 );
                        break;
                    }
                }
                
                /* Ensure the table has an ID - required for accessibility */
                if ( sId === null || sId === "" )
                {
                    sId = "DataTables_Table_"+(DataTable.ext._unique++);
                    this.id = sId;
                }
                
                /* Create the settings object for this table and set some of the default parameters */
                var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
                    "sDestroyWidth": $this[0].style.width,
                    "sInstance":     sId,
                    "sTableId":      sId,
                    colgroup: $('<colgroup>').prependTo(this),
                    fastData: function (row, column, type) {
                        return _fnGetCellData(oSettings, row, column, type);
                    }
                } );
                oSettings.nTable = this;
                oSettings.oInit  = oInit;
                
                allSettings.push( oSettings );
                
                // Make a single API instance available for internal handling
                oSettings.api = new _Api( oSettings );
                
                // Need to add the instance after the instance after the settings object has been added
                // to the settings array, so we can self reference the table instance if more than one
                oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
                
                // Backwards compatibility, before we apply all the defaults
                _fnCompatOpts( oInit );
                
                // If the length menu is given, but the init display length is not, use the length menu
                if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
                {
                    oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0])
                        ? oInit.aLengthMenu[0][0]
                        : $.isPlainObject( oInit.aLengthMenu[0] )
                            ? oInit.aLengthMenu[0].value
                            : oInit.aLengthMenu[0];
                }
                
                // Apply the defaults and init options to make a single init object will all
                // options defined from defaults and instance options.
                oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
                
                
                // Map the initialisation options onto the settings object
                _fnMap( oSettings.oFeatures, oInit, [
                    "bPaginate",
                    "bLengthChange",
                    "bFilter",
                    "bSort",
                    "bSortMulti",
                    "bInfo",
                    "bProcessing",
                    "bAutoWidth",
                    "bSortClasses",
                    "bServerSide",
                    "bDeferRender"
                ] );
                _fnMap( oSettings, oInit, [
                    "ajax",
                    "fnFormatNumber",
                    "sServerMethod",
                    "aaSorting",
                    "aaSortingFixed",
                    "aLengthMenu",
                    "sPaginationType",
                    "iStateDuration",
                    "bSortCellsTop",
                    "iTabIndex",
                    "sDom",
                    "fnStateLoadCallback",
                    "fnStateSaveCallback",
                    "renderer",
                    "searchDelay",
                    "rowId",
                    "caption",
                    "layout",
                    "orderDescReverse",
                    [ "iCookieDuration", "iStateDuration" ], // backwards compat
                    [ "oSearch", "oPreviousSearch" ],
                    [ "aoSearchCols", "aoPreSearchCols" ],
                    [ "iDisplayLength", "_iDisplayLength" ]
                ] );
                _fnMap( oSettings.oScroll, oInit, [
                    [ "sScrollX", "sX" ],
                    [ "sScrollXInner", "sXInner" ],
                    [ "sScrollY", "sY" ],
                    [ "bScrollCollapse", "bCollapse" ]
                ] );
                _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
                
                /* Callback functions which are array driven */
                _fnCallbackReg( oSettings, 'aoDrawCallback',       oInit.fnDrawCallback );
                _fnCallbackReg( oSettings, 'aoStateSaveParams',    oInit.fnStateSaveParams );
                _fnCallbackReg( oSettings, 'aoStateLoadParams',    oInit.fnStateLoadParams );
                _fnCallbackReg( oSettings, 'aoStateLoaded',        oInit.fnStateLoaded );
                _fnCallbackReg( oSettings, 'aoRowCallback',        oInit.fnRowCallback );
                _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow );
                _fnCallbackReg( oSettings, 'aoHeaderCallback',     oInit.fnHeaderCallback );
                _fnCallbackReg( oSettings, 'aoFooterCallback',     oInit.fnFooterCallback );
                _fnCallbackReg( oSettings, 'aoInitComplete',       oInit.fnInitComplete );
                _fnCallbackReg( oSettings, 'aoPreDrawCallback',    oInit.fnPreDrawCallback );
                
                oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
                
                /* Browser support detection */
                _fnBrowserDetect( oSettings );
                
                var oClasses = oSettings.oClasses;
                
                $.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
                $this.addClass( oClasses.table );
                
                if (! oSettings.oFeatures.bPaginate) {
                    oInit.iDisplayStart = 0;
                }
                
                if ( oSettings.iInitDisplayStart === undefined )
                {
                    /* Display start point, taking into account the save saving */
                    oSettings.iInitDisplayStart = oInit.iDisplayStart;
                    oSettings._iDisplayStart = oInit.iDisplayStart;
                }
                
                var defer = oInit.iDeferLoading;
                if ( defer !== null )
                {
                    oSettings.deferLoading = true;
                
                    var tmp = Array.isArray(defer);
                    oSettings._iRecordsDisplay = tmp ? defer[0] : defer;
                    oSettings._iRecordsTotal = tmp ? defer[1] : defer;
                }
                
                /*
                 * Columns
                 * See if we should load columns automatically or use defined ones
                 */
                var columnsInit = [];
                var thead = this.getElementsByTagName('thead');
                var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] );
                
                // If we don't have a columns array, then generate one with nulls
                if ( oInit.aoColumns ) {
                    columnsInit = oInit.aoColumns;
                }
                else if ( initHeaderLayout.length ) {
                    for ( i=0, iLen=initHeaderLayout[0].length ; i<iLen ; i++ ) {
                        columnsInit.push( null );
                    }
                }
                
                // Add the columns
                for ( i=0, iLen=columnsInit.length ; i<iLen ; i++ ) {
                    _fnAddColumn( oSettings );
                }
                
                // Apply the column definitions
                _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, columnsInit, initHeaderLayout, function (iCol, oDef) {
                    _fnColumnOptions( oSettings, iCol, oDef );
                } );
                
                /* HTML5 attribute detection - build an mData object automatically if the
                 * attributes are found
                 */
                var rowOne = $this.children('tbody').find('tr').eq(0);
                
                if ( rowOne.length ) {
                    var a = function ( cell, name ) {
                        return cell.getAttribute( 'data-'+name ) !== null ? name : null;
                    };
                
                    $( rowOne[0] ).children('th, td').each( function (i, cell) {
                        var col = oSettings.aoColumns[i];
                
                        if (! col) {
                            _fnLog( oSettings, 0, 'Incorrect column count', 18 );
                        }
                
                        if ( col.mData === i ) {
                            var sort = a( cell, 'sort' ) || a( cell, 'order' );
                            var filter = a( cell, 'filter' ) || a( cell, 'search' );
                
                            if ( sort !== null || filter !== null ) {
                                col.mData = {
                                    _:      i+'.display',
                                    sort:   sort !== null   ? i+'.@data-'+sort   : undefined,
                                    type:   sort !== null   ? i+'.@data-'+sort   : undefined,
                                    filter: filter !== null ? i+'.@data-'+filter : undefined
                                };
                                col._isArrayHost = true;
                
                                _fnColumnOptions( oSettings, i );
                            }
                        }
                    } );
                }
                
                // Must be done after everything which can be overridden by the state saving!
                _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
                
                var features = oSettings.oFeatures;
                if ( oInit.bStateSave )
                {
                    features.bStateSave = true;
                }
                
                // If aaSorting is not defined, then we use the first indicator in asSorting
                // in case that has been altered, so the default sort reflects that option
                if ( oInit.aaSorting === undefined ) {
                    var sorting = oSettings.aaSorting;
                    for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
                        sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
                    }
                }
                
                // Do a first pass on the sorting classes (allows any size changes to be taken into
                // account, and also will apply sorting disabled classes if disabled
                _fnSortingClasses( oSettings );
                
                _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
                    if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
                        _fnSortingClasses( oSettings );
                    }
                } );
                
                
                /*
                 * Table HTML init
                 * Cache the header, body and footer as required, creating them if needed
                 */
                var caption = $this.children('caption');
                
                if ( oSettings.caption ) {
                    if ( caption.length === 0 ) {
                        caption = $('<caption/>').appendTo( $this );
                    }
                
                    caption.html( oSettings.caption );
                }
                
                // Store the caption side, so we can remove the element from the document
                // when creating the element
                if (caption.length) {
                    caption[0]._captionSide = caption.css('caption-side');
                    oSettings.captionNode = caption[0];
                }
                
                if ( thead.length === 0 ) {
                    thead = $('<thead/>').appendTo($this);
                }
                oSettings.nTHead = thead[0];
                $('tr', thead).addClass(oClasses.thead.row);
                
                var tbody = $this.children('tbody');
                if ( tbody.length === 0 ) {
                    tbody = $('<tbody/>').insertAfter(thead);
                }
                oSettings.nTBody = tbody[0];
                
                var tfoot = $this.children('tfoot');
                if ( tfoot.length === 0 ) {
                    // If we are a scrolling table, and no footer has been given, then we need to create
                    // a tfoot element for the caption element to be appended to
                    tfoot = $('<tfoot/>').appendTo($this);
                }
                oSettings.nTFoot = tfoot[0];
                $('tr', tfoot).addClass(oClasses.tfoot.row);
                
                // Copy the data index array
                oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
                
                // Initialisation complete - table can be drawn
                oSettings.bInitialised = true;
                
                // Language definitions
                var oLanguage = oSettings.oLanguage;
                $.extend( true, oLanguage, oInit.oLanguage );
                
                if ( oLanguage.sUrl ) {
                    // Get the language definitions from a file
                    $.ajax( {
                        dataType: 'json',
                        url: oLanguage.sUrl,
                        success: function ( json ) {
                            _fnCamelToHungarian( defaults.oLanguage, json );
                            $.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
                
                            _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
                            _fnInitialise( oSettings );
                        },
                        error: function () {
                            // Error occurred loading language file
                            _fnLog( oSettings, 0, 'i18n file loading error', 21 );
                
                            // Continue on as best we can
                            _fnInitialise( oSettings );
                        }
                    } );
                }
                else {
                    _fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
                    _fnInitialise( oSettings );
                }
            } );
            _that = null;
            return this;
        };
        
        
        
        /**
         * DataTables extensions
         * 
         * This namespace acts as a collection area for plug-ins that can be used to
         * extend DataTables capabilities. Indeed many of the build in methods
         * use this method to provide their own capabilities (sorting methods for
         * example).
         *
         * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
         * reasons
         *
         *  @namespace
         */
        DataTable.ext = _ext = {
            /**
             * Buttons. For use with the Buttons extension for DataTables. This is
             * defined here so other extensions can define buttons regardless of load
             * order. It is _not_ used by DataTables core.
             *
             *  @type object
             *  @default {}
             */
            buttons: {},
        
        
            /**
             * Element class names
             *
             *  @type object
             *  @default {}
             */
            classes: {},
        
        
            /**
             * DataTables build type (expanded by the download builder)
             *
             *  @type string
             */
            builder: "dt/dt-2.1.5/fc-5.0.1",
        
        
            /**
             * Error reporting.
             * 
             * How should DataTables report an error. Can take the value 'alert',
             * 'throw', 'none' or a function.
             *
             *  @type string|function
             *  @default alert
             */
            errMode: "alert",
        
        
            /**
             * Legacy so v1 plug-ins don't throw js errors on load
             */
            feature: [],
        
            /**
             * Feature plug-ins.
             * 
             * This is an object of callbacks which provide the features for DataTables
             * to be initialised via the `layout` option.
             */
            features: {},
        
        
            /**
             * Row searching.
             * 
             * This method of searching is complimentary to the default type based
             * searching, and a lot more comprehensive as it allows you complete control
             * over the searching logic. Each element in this array is a function
             * (parameters described below) that is called for every row in the table,
             * and your logic decides if it should be included in the searching data set
             * or not.
             *
             * Searching functions have the following input parameters:
             *
             * 1. `{object}` DataTables settings object: see
             *    {@link DataTable.models.oSettings}
             * 2. `{array|object}` Data for the row to be processed (same as the
             *    original format that was passed in as the data source, or an array
             *    from a DOM data source
             * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
             *    can be useful to retrieve the `TR` element if you need DOM interaction.
             *
             * And the following return is expected:
             *
             * * {boolean} Include the row in the searched result set (true) or not
             *   (false)
             *
             * Note that as with the main search ability in DataTables, technically this
             * is "filtering", since it is subtractive. However, for consistency in
             * naming we call it searching here.
             *
             *  @type array
             *  @default []
             *
             *  @example
             *    // The following example shows custom search being applied to the
             *    // fourth column (i.e. the data[3] index) based on two input values
             *    // from the end-user, matching the data in a certain range.
             *    $.fn.dataTable.ext.search.push(
             *      function( settings, data, dataIndex ) {
             *        var min = document.getElementById('min').value * 1;
             *        var max = document.getElementById('max').value * 1;
             *        var version = data[3] == "-" ? 0 : data[3]*1;
             *
             *        if ( min == "" && max == "" ) {
             *          return true;
             *        }
             *        else if ( min == "" && version < max ) {
             *          return true;
             *        }
             *        else if ( min < version && "" == max ) {
             *          return true;
             *        }
             *        else if ( min < version && version < max ) {
             *          return true;
             *        }
             *        return false;
             *      }
             *    );
             */
            search: [],
        
        
            /**
             * Selector extensions
             *
             * The `selector` option can be used to extend the options available for the
             * selector modifier options (`selector-modifier` object data type) that
             * each of the three built in selector types offer (row, column and cell +
             * their plural counterparts). For example the Select extension uses this
             * mechanism to provide an option to select only rows, columns and cells
             * that have been marked as selected by the end user (`{selected: true}`),
             * which can be used in conjunction with the existing built in selector
             * options.
             *
             * Each property is an array to which functions can be pushed. The functions
             * take three attributes:
             *
             * * Settings object for the host table
             * * Options object (`selector-modifier` object type)
             * * Array of selected item indexes
             *
             * The return is an array of the resulting item indexes after the custom
             * selector has been applied.
             *
             *  @type object
             */
            selector: {
                cell: [],
                column: [],
                row: []
            },
        
        
            /**
             * Legacy configuration options. Enable and disable legacy options that
             * are available in DataTables.
             *
             *  @type object
             */
            legacy: {
                /**
                 * Enable / disable DataTables 1.9 compatible server-side processing
                 * requests
                 *
                 *  @type boolean
                 *  @default null
                 */
                ajax: null
            },
        
        
            /**
             * Pagination plug-in methods.
             * 
             * Each entry in this object is a function and defines which buttons should
             * be shown by the pagination rendering method that is used for the table:
             * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
             * buttons are displayed in the document, while the functions here tell it
             * what buttons to display. This is done by returning an array of button
             * descriptions (what each button will do).
             *
             * Pagination types (the four built in options and any additional plug-in
             * options defined here) can be used through the `paginationType`
             * initialisation parameter.
             *
             * The functions defined take two parameters:
             *
             * 1. `{int} page` The current page index
             * 2. `{int} pages` The number of pages in the table
             *
             * Each function is expected to return an array where each element of the
             * array can be one of:
             *
             * * `first` - Jump to first page when activated
             * * `last` - Jump to last page when activated
             * * `previous` - Show previous page when activated
             * * `next` - Show next page when activated
             * * `{int}` - Show page of the index given
             * * `{array}` - A nested array containing the above elements to add a
             *   containing 'DIV' element (might be useful for styling).
             *
             * Note that DataTables v1.9- used this object slightly differently whereby
             * an object with two functions would be defined for each plug-in. That
             * ability is still supported by DataTables 1.10+ to provide backwards
             * compatibility, but this option of use is now decremented and no longer
             * documented in DataTables 1.10+.
             *
             *  @type object
             *  @default {}
             *
             *  @example
             *    // Show previous, next and current page buttons only
             *    $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
             *      return [ 'previous', page, 'next' ];
             *    };
             */
            pager: {},
        
        
            renderer: {
                pageButton: {},
                header: {}
            },
        
        
            /**
             * Ordering plug-ins - custom data source
             * 
             * The extension options for ordering of data available here is complimentary
             * to the default type based ordering that DataTables typically uses. It
             * allows much greater control over the the data that is being used to
             * order a column, but is necessarily therefore more complex.
             * 
             * This type of ordering is useful if you want to do ordering based on data
             * live from the DOM (for example the contents of an 'input' element) rather
             * than just the static string that DataTables knows of.
             * 
             * The way these plug-ins work is that you create an array of the values you
             * wish to be ordering for the column in question and then return that
             * array. The data in the array much be in the index order of the rows in
             * the table (not the currently ordering order!). Which order data gathering
             * function is run here depends on the `dt-init columns.orderDataType`
             * parameter that is used for the column (if any).
             *
             * The functions defined take two parameters:
             *
             * 1. `{object}` DataTables settings object: see
             *    {@link DataTable.models.oSettings}
             * 2. `{int}` Target column index
             *
             * Each function is expected to return an array:
             *
             * * `{array}` Data for the column to be ordering upon
             *
             *  @type array
             *
             *  @example
             *    // Ordering using `input` node values
             *    $.fn.dataTable.ext.order['dom-text'] = function  ( settings, col )
             *    {
             *      return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
             *        return $('input', td).val();
             *      } );
             *    }
             */
            order: {},
        
        
            /**
             * Type based plug-ins.
             *
             * Each column in DataTables has a type assigned to it, either by automatic
             * detection or by direct assignment using the `type` option for the column.
             * The type of a column will effect how it is ordering and search (plug-ins
             * can also make use of the column type if required).
             *
             * @namespace
             */
            type: {
                /**
                 * Automatic column class assignment
                 */
                className: {},
        
                /**
                 * Type detection functions.
                 *
                 * The functions defined in this object are used to automatically detect
                 * a column's type, making initialisation of DataTables super easy, even
                 * when complex data is in the table.
                 *
                 * The functions defined take two parameters:
                 *
                 *  1. `{*}` Data from the column cell to be analysed
                 *  2. `{settings}` DataTables settings object. This can be used to
                 *     perform context specific type detection - for example detection
                 *     based on language settings such as using a comma for a decimal
                 *     place. Generally speaking the options from the settings will not
                 *     be required
                 *
                 * Each function is expected to return:
                 *
                 * * `{string|null}` Data type detected, or null if unknown (and thus
                 *   pass it on to the other type detection functions.
                 *
                 *  @type array
                 *
                 *  @example
                 *    // Currency type detection plug-in:
                 *    $.fn.dataTable.ext.type.detect.push(
                 *      function ( data, settings ) {
                 *        // Check the numeric part
                 *        if ( ! data.substring(1).match(/[0-9]/) ) {
                 *          return null;
                 *        }
                 *
                 *        // Check prefixed by currency
                 *        if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
                 *          return 'currency';
                 *        }
                 *        return null;
                 *      }
                 *    );
                 */
                detect: [],
        
                /**
                 * Automatic renderer assignment
                 */
                render: {},
        
        
                /**
                 * Type based search formatting.
                 *
                 * The type based searching functions can be used to pre-format the
                 * data to be search on. For example, it can be used to strip HTML
                 * tags or to de-format telephone numbers for numeric only searching.
                 *
                 * Note that is a search is not defined for a column of a given type,
                 * no search formatting will be performed.
                 * 
                 * Pre-processing of searching data plug-ins - When you assign the sType
                 * for a column (or have it automatically detected for you by DataTables
                 * or a type detection plug-in), you will typically be using this for
                 * custom sorting, but it can also be used to provide custom searching
                 * by allowing you to pre-processing the data and returning the data in
                 * the format that should be searched upon. This is done by adding
                 * functions this object with a parameter name which matches the sType
                 * for that target column. This is the corollary of <i>afnSortData</i>
                 * for searching data.
                 *
                 * The functions defined take a single parameter:
                 *
                 *  1. `{*}` Data from the column cell to be prepared for searching
                 *
                 * Each function is expected to return:
                 *
                 * * `{string|null}` Formatted string that will be used for the searching.
                 *
                 *  @type object
                 *  @default {}
                 *
                 *  @example
                 *    $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
                 *      return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
                 *    }
                 */
                search: {},
        
        
                /**
                 * Type based ordering.
                 *
                 * The column type tells DataTables what ordering to apply to the table
                 * when a column is sorted upon. The order for each type that is defined,
                 * is defined by the functions available in this object.
                 *
                 * Each ordering option can be described by three properties added to
                 * this object:
                 *
                 * * `{type}-pre` - Pre-formatting function
                 * * `{type}-asc` - Ascending order function
                 * * `{type}-desc` - Descending order function
                 *
                 * All three can be used together, only `{type}-pre` or only
                 * `{type}-asc` and `{type}-desc` together. It is generally recommended
                 * that only `{type}-pre` is used, as this provides the optimal
                 * implementation in terms of speed, although the others are provided
                 * for compatibility with existing Javascript sort functions.
                 *
                 * `{type}-pre`: Functions defined take a single parameter:
                 *
                 *  1. `{*}` Data from the column cell to be prepared for ordering
                 *
                 * And return:
                 *
                 * * `{*}` Data to be sorted upon
                 *
                 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
                 * functions, taking two parameters:
                 *
                 *  1. `{*}` Data to compare to the second parameter
                 *  2. `{*}` Data to compare to the first parameter
                 *
                 * And returning:
                 *
                 * * `{*}` Ordering match: <0 if first parameter should be sorted lower
                 *   than the second parameter, ===0 if the two parameters are equal and
                 *   >0 if the first parameter should be sorted height than the second
                 *   parameter.
                 * 
                 *  @type object
                 *  @default {}
                 *
                 *  @example
                 *    // Numeric ordering of formatted numbers with a pre-formatter
                 *    $.extend( $.fn.dataTable.ext.type.order, {
                 *      "string-pre": function(x) {
                 *        a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
                 *        return parseFloat( a );
                 *      }
                 *    } );
                 *
                 *  @example
                 *    // Case-sensitive string ordering, with no pre-formatting method
                 *    $.extend( $.fn.dataTable.ext.order, {
                 *      "string-case-asc": function(x,y) {
                 *        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
                 *      },
                 *      "string-case-desc": function(x,y) {
                 *        return ((x < y) ? 1 : ((x > y) ? -1 : 0));
                 *      }
                 *    } );
                 */
                order: {}
            },
        
            /**
             * Unique DataTables instance counter
             *
             * @type int
             * @private
             */
            _unique: 0,
        
        
            //
            // Depreciated
            // The following properties are retained for backwards compatibility only.
            // The should not be used in new projects and will be removed in a future
            // version
            //
        
            /**
             * Version check function.
             *  @type function
             *  @depreciated Since 1.10
             */
            fnVersionCheck: DataTable.fnVersionCheck,
        
        
            /**
             * Index for what 'this' index API functions should use
             *  @type int
             *  @deprecated Since v1.10
             */
            iApiIndex: 0,
        
        
            /**
             * Software version
             *  @type string
             *  @deprecated Since v1.10
             */
            sVersion: DataTable.version
        };
        
        
        //
        // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
        //
        $.extend( _ext, {
            afnFiltering: _ext.search,
            aTypes:       _ext.type.detect,
            ofnSearch:    _ext.type.search,
            oSort:        _ext.type.order,
            afnSortData:  _ext.order,
            aoFeatures:   _ext.feature,
            oStdClasses:  _ext.classes,
            oPagination:  _ext.pager
        } );
        
        
        $.extend( DataTable.ext.classes, {
            container: 'dt-container',
            empty: {
                row: 'dt-empty'
            },
            info: {
                container: 'dt-info'
            },
            layout: {
                row: 'dt-layout-row',
                cell: 'dt-layout-cell',
                tableRow: 'dt-layout-table',
                tableCell: '',
                start: 'dt-layout-start',
                end: 'dt-layout-end',
                full: 'dt-layout-full'
            },
            length: {
                container: 'dt-length',
                select: 'dt-input'
            },
            order: {
                canAsc: 'dt-orderable-asc',
                canDesc: 'dt-orderable-desc',
                isAsc: 'dt-ordering-asc',
                isDesc: 'dt-ordering-desc',
                none: 'dt-orderable-none',
                position: 'sorting_'
            },
            processing: {
                container: 'dt-processing'
            },
            scrolling: {
                body: 'dt-scroll-body',
                container: 'dt-scroll',
                footer: {
                    self: 'dt-scroll-foot',
                    inner: 'dt-scroll-footInner'
                },
                header: {
                    self: 'dt-scroll-head',
                    inner: 'dt-scroll-headInner'
                }
            },
            search: {
                container: 'dt-search',
                input: 'dt-input'
            },
            table: 'dataTable',	
            tbody: {
                cell: '',
                row: ''
            },
            thead: {
                cell: '',
                row: ''
            },
            tfoot: {
                cell: '',
                row: ''
            },
            paging: {
                active: 'current',
                button: 'dt-paging-button',
                container: 'dt-paging',
                disabled: 'disabled',
                nav: ''
            }
        } );
        
        
        /*
         * It is useful to have variables which are scoped locally so only the
         * DataTables functions can access them and they don't leak into global space.
         * At the same time these functions are often useful over multiple files in the
         * core and API, so we list, or at least document, all variables which are used
         * by DataTables as private variables here. This also ensures that there is no
         * clashing of variable names and that they can easily referenced for reuse.
         */
        
        
        // Defined else where
        //  _selector_run
        //  _selector_opts
        //  _selector_row_indexes
        
        var _ext; // DataTable.ext
        var _Api; // DataTable.Api
        var _api_register; // DataTable.Api.register
        var _api_registerPlural; // DataTable.Api.registerPlural
        
        var _re_dic = {};
        var _re_new_lines = /[\r\n\u2028]/g;
        var _re_html = /<([^>]*>)/g;
        var _max_str_len = Math.pow(2, 28);
        
        // This is not strict ISO8601 - Date.parse() is quite lax, although
        // implementations differ between browsers.
        var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/;
        
        // Escape regular expression special characters
        var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
        
        // https://en.wikipedia.org/wiki/Foreign_exchange_market
        // - \u20BD - Russian ruble.
        // - \u20a9 - South Korean Won
        // - \u20BA - Turkish Lira
        // - \u20B9 - Indian Rupee
        // - R - Brazil (R$) and South Africa
        // - fr - Swiss Franc
        // - kr - Swedish krona, Norwegian krone and Danish krone
        // - \u2009 is thin space and \u202F is narrow no-break space, both used in many
        // - Ƀ - Bitcoin
        // - Ξ - Ethereum
        //   standards as thousands separators.
        var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi;
        
        
        var _empty = function ( d ) {
            return !d || d === true || d === '-' ? true : false;
        };
        
        
        var _intVal = function ( s ) {
            var integer = parseInt( s, 10 );
            return !isNaN(integer) && isFinite(s) ? integer : null;
        };
        
        // Convert from a formatted number with characters other than `.` as the
        // decimal place, to a Javascript number
        var _numToDecimal = function ( num, decimalPoint ) {
            // Cache created regular expressions for speed as this function is called often
            if ( ! _re_dic[ decimalPoint ] ) {
                _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
            }
            return typeof num === 'string' && decimalPoint !== '.' ?
                num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
                num;
        };
        
        
        var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) {
            var type = typeof d;
            var strType = type === 'string';
        
            if ( type === 'number' || type === 'bigint') {
                return true;
            }
        
            // If empty return immediately so there must be a number if it is a
            // formatted string (this stops the string "k", or "kr", etc being detected
            // as a formatted number for currency
            if ( allowEmpty && _empty( d ) ) {
                return true;
            }
        
            if ( decimalPoint && strType ) {
                d = _numToDecimal( d, decimalPoint );
            }
        
            if ( formatted && strType ) {
                d = d.replace( _re_formatted_numeric, '' );
            }
        
            return !isNaN( parseFloat(d) ) && isFinite( d );
        };
        
        
        // A string without HTML in it can be considered to be HTML still
        var _isHtml = function ( d ) {
            return _empty( d ) || typeof d === 'string';
        };
        
        // Is a string a number surrounded by HTML?
        var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) {
            if ( allowEmpty && _empty( d ) ) {
                return true;
            }
        
            // input and select strings mean that this isn't just a number
            if (typeof d === 'string' && d.match(/<(input|select)/i)) {
                return null;
            }
        
            var html = _isHtml( d );
            return ! html ?
                null :
                _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ?
                    true :
                    null;
        };
        
        
        var _pluck = function ( a, prop, prop2 ) {
            var out = [];
            var i=0, ien=a.length;
        
            // Could have the test in the loop for slightly smaller code, but speed
            // is essential here
            if ( prop2 !== undefined ) {
                for ( ; i<ien ; i++ ) {
                    if ( a[i] && a[i][ prop ] ) {
                        out.push( a[i][ prop ][ prop2 ] );
                    }
                }
            }
            else {
                for ( ; i<ien ; i++ ) {
                    if ( a[i] ) {
                        out.push( a[i][ prop ] );
                    }
                }
            }
        
            return out;
        };
        
        
        // Basically the same as _pluck, but rather than looping over `a` we use `order`
        // as the indexes to pick from `a`
        var _pluck_order = function ( a, order, prop, prop2 )
        {
            var out = [];
            var i=0, ien=order.length;
        
            // Could have the test in the loop for slightly smaller code, but speed
            // is essential here
            if ( prop2 !== undefined ) {
                for ( ; i<ien ; i++ ) {
                    if ( a[ order[i] ][ prop ] ) {
                        out.push( a[ order[i] ][ prop ][ prop2 ] );
                    }
                }
            }
            else {
                for ( ; i<ien ; i++ ) {
                    if ( a[ order[i] ] ) {
                        out.push( a[ order[i] ][ prop ] );
                    }
                }
            }
        
            return out;
        };
        
        
        var _range = function ( len, start )
        {
            var out = [];
            var end;
        
            if ( start === undefined ) {
                start = 0;
                end = len;
            }
            else {
                end = start;
                start = len;
            }
        
            for ( var i=start ; i<end ; i++ ) {
                out.push( i );
            }
        
            return out;
        };
        
        
        var _removeEmpty = function ( a )
        {
            var out = [];
        
            for ( var i=0, ien=a.length ; i<ien ; i++ ) {
                if ( a[i] ) { // careful - will remove all falsy values!
                    out.push( a[i] );
                }
            }
        
            return out;
        };
        
        // Replaceable function in api.util
        var _stripHtml = function (input) {
            if (! input || typeof input !== 'string') {
                return input;
            }
        
            // Irrelevant check to workaround CodeQL's false positive on the regex
            if (input.length > _max_str_len) {
                throw new Error('Exceeded max str len');
            }
        
            var previous;
        
            input = input.replace(_re_html, ''); // Complete tags
        
            // Safety for incomplete script tag - use do / while to ensure that
            // we get all instances
            do {
                previous = input;
                input = input.replace(/<script/i, '');
            } while (input !== previous);
        
            return previous;
        };
        
        // Replaceable function in api.util
        var _escapeHtml = function ( d ) {
            if (Array.isArray(d)) {
                d = d.join(',');
            }
        
            return typeof d === 'string' ?
                d
                    .replace(/&/g, '&amp;')
                    .replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;')
                    .replace(/"/g, '&quot;') :
                d;
        };
        
        // Remove diacritics from a string by decomposing it and then removing
        // non-ascii characters
        var _normalize = function (str, both) {
            if (typeof str !== 'string') {
                return str;
            }
        
            // It is faster to just run `normalize` than it is to check if
            // we need to with a regex!
            var res = str.normalize("NFD");
        
            // Equally, here we check if a regex is needed or not
            return res.length !== str.length
                ? (both === true ? str + ' ' : '' ) + res.replace(/[\u0300-\u036f]/g, "")
                : res;
        }
        
        /**
         * Determine if all values in the array are unique. This means we can short
         * cut the _unique method at the cost of a single loop. A sorted array is used
         * to easily check the values.
         *
         * @param  {array} src Source array
         * @return {boolean} true if all unique, false otherwise
         * @ignore
         */
        var _areAllUnique = function ( src ) {
            if ( src.length < 2 ) {
                return true;
            }
        
            var sorted = src.slice().sort();
            var last = sorted[0];
        
            for ( var i=1, ien=sorted.length ; i<ien ; i++ ) {
                if ( sorted[i] === last ) {
                    return false;
                }
        
                last = sorted[i];
            }
        
            return true;
        };
        
        
        /**
         * Find the unique elements in a source array.
         *
         * @param  {array} src Source array
         * @return {array} Array of unique items
         * @ignore
         */
        var _unique = function ( src )
        {
            if (Array.from && Set) {
                return Array.from(new Set(src));
            }
        
            if ( _areAllUnique( src ) ) {
                return src.slice();
            }
        
            // A faster unique method is to use object keys to identify used values,
            // but this doesn't work with arrays or objects, which we must also
            // consider. See jsperf.app/compare-array-unique-versions/4 for more
            // information.
            var
                out = [],
                val,
                i, ien=src.length,
                j, k=0;
        
            again: for ( i=0 ; i<ien ; i++ ) {
                val = src[i];
        
                for ( j=0 ; j<k ; j++ ) {
                    if ( out[j] === val ) {
                        continue again;
                    }
                }
        
                out.push( val );
                k++;
            }
        
            return out;
        };
        
        // Surprisingly this is faster than [].concat.apply
        // https://jsperf.com/flatten-an-array-loop-vs-reduce/2
        var _flatten = function (out, val) {
            if (Array.isArray(val)) {
                for (var i=0 ; i<val.length ; i++) {
                    _flatten(out, val[i]);
                }
            }
            else {
                out.push(val);
            }
        
            return out;
        }
        
        // Similar to jQuery's addClass, but use classList.add
        function _addClass(el, name) {
            if (name) {
                name.split(' ').forEach(function (n) {
                    if (n) {
                        // `add` does deduplication, so no need to check `contains`
                        el.classList.add(n);
                    }
                });
            }
        }
        
        /**
         * DataTables utility methods
         * 
         * This namespace provides helper methods that DataTables uses internally to
         * create a DataTable, but which are not exclusively used only for DataTables.
         * These methods can be used by extension authors to save the duplication of
         * code.
         *
         *  @namespace
         */
        DataTable.util = {
            /**
             * Return a string with diacritic characters decomposed
             * @param {*} mixed Function or string to normalize
             * @param {*} both Return original string and the normalized string
             * @returns String or undefined
             */
            diacritics: function (mixed, both) {
                var type = typeof mixed;
        
                if (type !== 'function') {
                    return _normalize(mixed, both);
                }
                _normalize = mixed;
            },
        
            /**
             * Debounce a function
             *
             * @param {function} fn Function to be called
             * @param {integer} freq Call frequency in mS
             * @return {function} Wrapped function
             */
            debounce: function ( fn, timeout ) {
                var timer;
        
                return function () {
                    var that = this;
                    var args = arguments;
        
                    clearTimeout(timer);
        
                    timer = setTimeout( function () {
                        fn.apply(that, args);
                    }, timeout || 250 );
                };
            },
        
            /**
             * Throttle the calls to a function. Arguments and context are maintained
             * for the throttled function.
             *
             * @param {function} fn Function to be called
             * @param {integer} freq Call frequency in mS
             * @return {function} Wrapped function
             */
            throttle: function ( fn, freq ) {
                var
                    frequency = freq !== undefined ? freq : 200,
                    last,
                    timer;
        
                return function () {
                    var
                        that = this,
                        now  = +new Date(),
                        args = arguments;
        
                    if ( last && now < last + frequency ) {
                        clearTimeout( timer );
        
                        timer = setTimeout( function () {
                            last = undefined;
                            fn.apply( that, args );
                        }, frequency );
                    }
                    else {
                        last = now;
                        fn.apply( that, args );
                    }
                };
            },
        
            /**
             * Escape a string such that it can be used in a regular expression
             *
             *  @param {string} val string to escape
             *  @returns {string} escaped string
             */
            escapeRegex: function ( val ) {
                return val.replace( _re_escape_regex, '\\$1' );
            },
        
            /**
             * Create a function that will write to a nested object or array
             * @param {*} source JSON notation string
             * @returns Write function
             */
            set: function ( source ) {
                if ( $.isPlainObject( source ) ) {
                    /* Unlike get, only the underscore (global) option is used for for
                     * setting data since we don't know the type here. This is why an object
                     * option is not documented for `mData` (which is read/write), but it is
                     * for `mRender` which is read only.
                     */
                    return DataTable.util.set( source._ );
                }
                else if ( source === null ) {
                    // Nothing to do when the data source is null
                    return function () {};
                }
                else if ( typeof source === 'function' ) {
                    return function (data, val, meta) {
                        source( data, 'set', val, meta );
                    };
                }
                else if (
                    typeof source === 'string' && (source.indexOf('.') !== -1 ||
                    source.indexOf('[') !== -1 || source.indexOf('(') !== -1)
                ) {
                    // Like the get, we need to get data from a nested object
                    var setData = function (data, val, src) {
                        var a = _fnSplitObjNotation( src ), b;
                        var aLast = a[a.length-1];
                        var arrayNotation, funcNotation, o, innerSrc;
            
                        for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) {
                            // Protect against prototype pollution
                            if (a[i] === '__proto__' || a[i] === 'constructor') {
                                throw new Error('Cannot set prototype values');
                            }
            
                            // Check if we are dealing with an array notation request
                            arrayNotation = a[i].match(__reArray);
                            funcNotation = a[i].match(__reFn);
            
                            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
                                if ( Array.isArray( val ) ) {
                                    for ( var j=0, jLen=val.length ; j<jLen ; j++ ) {
                                        o = {};
                                        setData( o, val[j], innerSrc );
                                        data[ a[i] ].push( o );
                                    }
                                }
                                else {
                                    // We've been asked to save data to an array, but it
                                    // isn't array data to be saved. Best that can be done
                                    // is to just save the value.
                                    data[ a[i] ] = val;
                                }
            
                                // 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;
                            }
                            else if ( funcNotation ) {
                                // Function call
                                a[i] = a[i].replace(__reFn, '');
                                data = data[ a[i] ]( val );
                            }
            
                            // 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] ];
                        }
            
                        // Last item in the input - i.e, the actual set
                        if ( aLast.match(__reFn ) ) {
                            // Function call
                            data = data[ aLast.replace(__reFn, '') ]( val );
                        }
                        else {
                            // 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[ aLast.replace(__reArray, '') ] = val;
                        }
                    };
            
                    return function (data, val) { // meta is also passed in, but not used
                        return setData( data, val, source );
                    };
                }
                else {
                    // Array or flat object mapping
                    return function (data, val) { // meta is also passed in, but not used
                        data[source] = val;
                    };
                }
            },
        
            /**
             * Create a function that will read nested objects from arrays, based on JSON notation
             * @param {*} source JSON notation string
             * @returns Value read
             */
            get: function ( source ) {
                if ( $.isPlainObject( source ) ) {
                    // Build an object of get functions, and wrap them in a single call
                    var o = {};
                    $.each( source, function (key, val) {
                        if ( val ) {
                            o[key] = DataTable.util.get( val );
                        }
                    } );
            
                    return function (data, type, row, meta) {
                        var t = o[type] || o._;
                        return t !== undefined ?
                            t(data, type, row, meta) :
                            data;
                    };
                }
                else if ( source === null ) {
                    // Give an empty string for rendering / sorting etc
                    return function (data) { // type, row and meta also passed, but not used
                        return data;
                    };
                }
                else if ( typeof source === 'function' ) {
                    return function (data, type, row, meta) {
                        return source( data, type, row, meta );
                    };
                }
                else if (
                    typeof source === 'string' && (source.indexOf('.') !== -1 ||
                    source.indexOf('[') !== -1 || source.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 arrayNotation, funcNotation, out, innerSrc;
            
                        if ( src !== "" ) {
                            var a = _fnSplitObjNotation( src );
            
                            for ( var i=0, iLen=a.length ; i<iLen ; i++ ) {
                                // Check if we are dealing with special notation
                                arrayNotation = a[i].match(__reArray);
                                funcNotation = a[i].match(__reFn);
            
                                if ( arrayNotation ) {
                                    // Array notation
                                    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
                                    if ( Array.isArray( data ) ) {
                                        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;
                                }
                                else if ( funcNotation ) {
                                    // Function call
                                    a[i] = a[i].replace(__reFn, '');
                                    data = data[ a[i] ]();
                                    continue;
                                }
            
                                if (data === null || data[ a[i] ] === null) {
                                    return null;
                                }
                                else if ( data === undefined || data[ a[i] ] === undefined ) {
                                    return undefined;
                                }
        
                                data = data[ a[i] ];
                            }
                        }
            
                        return data;
                    };
            
                    return function (data, type) { // row and meta also passed, but not used
                        return fetchData( data, type, source );
                    };
                }
                else {
                    // Array or flat object mapping
                    return function (data) { // row and meta also passed, but not used
                        return data[source];
                    };
                }
            },
        
            stripHtml: function (mixed) {
                var type = typeof mixed;
        
                if (type === 'function') {
                    _stripHtml = mixed;
                    return;
                }
                else if (type === 'string') {
                    return _stripHtml(mixed);
                }
                return mixed;
            },
        
            escapeHtml: function (mixed) {
                var type = typeof mixed;
        
                if (type === 'function') {
                    _escapeHtml = mixed;
                    return;
                }
                else if (type === 'string' || Array.isArray(mixed)) {
                    return _escapeHtml(mixed);
                }
                return mixed;
            },
        
            unique: _unique
        };
        
        
        
        /**
         * Create a mapping object that allows camel case parameters to be looked up
         * for their Hungarian counterparts. The mapping is stored in a private
         * parameter called `_hungarianMap` which can be accessed on the source object.
         *  @param {object} o
         *  @memberof DataTable#oApi
         */
        function _fnHungarianMap ( o )
        {
            var
                hungarian = 'a aa ai ao as b fn i m o s ',
                match,
                newKey,
                map = {};
        
            $.each( o, function (key) {
                match = key.match(/^([^A-Z]+?)([A-Z])/);
        
                if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
                {
                    newKey = key.replace( match[0], match[2].toLowerCase() );
                    map[ newKey ] = key;
        
                    if ( match[1] === 'o' )
                    {
                        _fnHungarianMap( o[key] );
                    }
                }
            } );
        
            o._hungarianMap = map;
        }
        
        
        /**
         * Convert from camel case parameters to Hungarian, based on a Hungarian map
         * created by _fnHungarianMap.
         *  @param {object} src The model object which holds all parameters that can be
         *    mapped.
         *  @param {object} user The object to convert from camel case to Hungarian.
         *  @param {boolean} force When set to `true`, properties which already have a
         *    Hungarian value in the `user` object will be overwritten. Otherwise they
         *    won't be.
         *  @memberof DataTable#oApi
         */
        function _fnCamelToHungarian ( src, user, force )
        {
            if ( ! src._hungarianMap ) {
                _fnHungarianMap( src );
            }
        
            var hungarianKey;
        
            $.each( user, function (key) {
                hungarianKey = src._hungarianMap[ key ];
        
                if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
                {
                    // For objects, we need to buzz down into the object to copy parameters
                    if ( hungarianKey.charAt(0) === 'o' )
                    {
                        // Copy the camelCase options over to the hungarian
                        if ( ! user[ hungarianKey ] ) {
                            user[ hungarianKey ] = {};
                        }
                        $.extend( true, user[hungarianKey], user[key] );
        
                        _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force );
                    }
                    else {
                        user[hungarianKey] = user[ key ];
                    }
                }
            } );
        }
        
        /**
         * Map one parameter onto another
         *  @param {object} o Object to map
         *  @param {*} knew The new parameter name
         *  @param {*} old The old parameter name
         */
        var _fnCompatMap = function ( o, knew, old ) {
            if ( o[ knew ] !== undefined ) {
                o[ old ] = o[ knew ];
            }
        };
        
        
        /**
         * Provide backwards compatibility for the main DT options. Note that the new
         * options are mapped onto the old parameters, so this is an external interface
         * change only.
         *  @param {object} init Object to map
         */
        function _fnCompatOpts ( init )
        {
            _fnCompatMap( init, 'ordering',      'bSort' );
            _fnCompatMap( init, 'orderMulti',    'bSortMulti' );
            _fnCompatMap( init, 'orderClasses',  'bSortClasses' );
            _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' );
            _fnCompatMap( init, 'order',         'aaSorting' );
            _fnCompatMap( init, 'orderFixed',    'aaSortingFixed' );
            _fnCompatMap( init, 'paging',        'bPaginate' );
            _fnCompatMap( init, 'pagingType',    'sPaginationType' );
            _fnCompatMap( init, 'pageLength',    'iDisplayLength' );
            _fnCompatMap( init, 'searching',     'bFilter' );
        
            // Boolean initialisation of x-scrolling
            if ( typeof init.sScrollX === 'boolean' ) {
                init.sScrollX = init.sScrollX ? '100%' : '';
            }
            if ( typeof init.scrollX === 'boolean' ) {
                init.scrollX = init.scrollX ? '100%' : '';
            }
        
            // Column search objects are in an array, so it needs to be converted
            // element by element
            var searchCols = init.aoSearchCols;
        
            if ( searchCols ) {
                for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) {
                    if ( searchCols[i] ) {
                        _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] );
                    }
                }
            }
        
            // Enable search delay if server-side processing is enabled
            if (init.serverSide && ! init.searchDelay) {
                init.searchDelay = 400;
            }
        }
        
        
        /**
         * Provide backwards compatibility for column options. Note that the new options
         * are mapped onto the old parameters, so this is an external interface change
         * only.
         *  @param {object} init Object to map
         */
        function _fnCompatCols ( init )
        {
            _fnCompatMap( init, 'orderable',     'bSortable' );
            _fnCompatMap( init, 'orderData',     'aDataSort' );
            _fnCompatMap( init, 'orderSequence', 'asSorting' );
            _fnCompatMap( init, 'orderDataType', 'sortDataType' );
        
            // orderData can be given as an integer
            var dataSort = init.aDataSort;
            if ( typeof dataSort === 'number' && ! Array.isArray( dataSort ) ) {
                init.aDataSort = [ dataSort ];
            }
        }
        
        
        /**
         * Browser feature detection for capabilities, quirks
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnBrowserDetect( settings )
        {
            // We don't need to do this every time DataTables is constructed, the values
            // calculated are specific to the browser and OS configuration which we
            // don't expect to change between initialisations
            if ( ! DataTable.__browser ) {
                var browser = {};
                DataTable.__browser = browser;
        
                // Scrolling feature / quirks detection
                var n = $('<div/>')
                    .css( {
                        position: 'fixed',
                        top: 0,
                        left: -1 * window.pageXOffset, // allow for scrolling
                        height: 1,
                        width: 1,
                        overflow: 'hidden'
                    } )
                    .append(
                        $('<div/>')
                            .css( {
                                position: 'absolute',
                                top: 1,
                                left: 1,
                                width: 100,
                                overflow: 'scroll'
                            } )
                            .append(
                                $('<div/>')
                                    .css( {
                                        width: '100%',
                                        height: 10
                                    } )
                            )
                    )
                    .appendTo( 'body' );
        
                var outer = n.children();
                var inner = outer.children();
        
                // Get scrollbar width
                browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
        
                // In rtl text layout, some browsers (most, but not all) will place the
                // scrollbar on the left, rather than the right.
                browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
        
                n.remove();
            }
        
            $.extend( settings.oBrowser, DataTable.__browser );
            settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
        }
        
        /**
         * Add a column to the list used for the table with default values
         *  @param {object} oSettings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnAddColumn( oSettings )
        {
            // Add column to aoColumns array
            var oDefaults = DataTable.defaults.column;
            var iCol = oSettings.aoColumns.length;
            var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
                "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
                "mData": oDefaults.mData ? oDefaults.mData : iCol,
                idx: iCol,
                searchFixed: {},
                colEl: $('<col>').attr('data-dt-column', iCol)
            } );
            oSettings.aoColumns.push( oCol );
        
            // Add search object for column specific search. Note that the `searchCols[ iCol ]`
            // passed into extend can be undefined. This allows the user to give a default
            // with only some of the parameters defined, and also not give a default
            var searchCols = oSettings.aoPreSearchCols;
            searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
        }
        
        
        /**
         * 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
                _fnCompatCols( oOptions );
        
                // Map camel case parameters to their Hungarian counterparts
                _fnCamelToHungarian( DataTable.defaults.column, oOptions, true );
        
                /* Backwards compatibility for mDataProp */
                if ( oOptions.mDataProp !== undefined && !oOptions.mData )
                {
                    oOptions.mData = oOptions.mDataProp;
                }
        
                if ( oOptions.sType )
                {
                    oCol._sManualType = oOptions.sType;
                }
            
                // `class` is a reserved word in Javascript, so we need to provide
                // the ability to use a valid name for the camel case input
                if ( oOptions.className && ! oOptions.sClass )
                {
                    oOptions.sClass = oOptions.className;
                }
        
                var origClass = oCol.sClass;
        
                $.extend( oCol, oOptions );
                _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
        
                // Merge class from previously defined classes with this one, rather than just
                // overwriting it in the extend above
                if (origClass !== oCol.sClass) {
                    oCol.sClass = origClass + ' ' + oCol.sClass;
                }
        
                /* 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 mDataSrc = oCol.mData;
            var mData = _fnGetObjectDataFn( mDataSrc );
        
            // The `render` option can be given as an array to access the helper rendering methods.
            // The first element is the rendering method to use, the rest are the parameters to pass
            if ( oCol.mRender && Array.isArray( oCol.mRender ) ) {
                var copy = oCol.mRender.slice();
                var name = copy.shift();
        
                oCol.mRender = DataTable.render[name].apply(window, copy);
            }
        
            oCol._render = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
        
            var attrTest = function( src ) {
                return typeof src === 'string' && src.indexOf('@') !== -1;
            };
            oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
                attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
            );
            oCol._setter = null;
        
            oCol.fnGetData = function (rowData, type, meta) {
                var innerData = mData( rowData, type, undefined, meta );
        
                return oCol._render && type ?
                    oCol._render( innerData, type, rowData, meta ) :
                    innerData;
            };
            oCol.fnSetData = function ( rowData, val, meta ) {
                return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
            };
        
            // Indicate if DataTables should read DOM data as an object or array
            // Used in _fnGetRowElements
            if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) {
                oSettings._rowReadObject = true;
            }
        
            /* Feature sorting overrides column specific when off */
            if ( !oSettings.oFeatures.bSort )
            {
                oCol.bSortable = false;
            }
        }
        
        
        /**
         * Adjust the table column widths for new data. Note: you would probably want to
         * do a redraw after calling this function!
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnAdjustColumnSizing ( settings )
        {
            _fnCalculateColumnWidths( settings );
            _fnColumnSizes( settings );
        
            var scroll = settings.oScroll;
            if ( scroll.sY !== '' || scroll.sX !== '') {
                _fnScrollDraw( settings );
            }
        
            _fnCallbackFire( settings, null, 'column-sizing', [settings] );
        }
        
        /**
         * Apply column sizes
         *
         * @param {*} settings DataTables settings object
         */
        function _fnColumnSizes ( settings )
        {
            var cols = settings.aoColumns;
        
            for (var i=0 ; i<cols.length ; i++) {
                var width = _fnColumnsSumWidth(settings, [i], false, false);
        
                // Need to set the min-width, otherwise the browser might try to collapse
                // it further
                cols[i].colEl
                    .css('width', width)
                    .css('min-width', width);
            }
        }
        
        
        /**
         * Convert 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;
        }
        
        
        /**
         * Convert 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 = aiVis.indexOf(iMatch);
        
            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( settings )
        {
            var layout = settings.aoHeader;
            var columns = settings.aoColumns;
            var vis = 0;
        
            if ( layout.length ) {
                for ( var i=0, ien=layout[0].length ; i<ien ; i++ ) {
                    if ( columns[i].bVisible && $(layout[0][i].cell).css('display') !== 'none' ) {
                        vis++;
                    }
                }
            }
        
            return vis;
        }
        
        
        /**
         * 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 = [];
        
            oSettings.aoColumns.map( function(val, i) {
                if ( val[sParam] ) {
                    a.push( i );
                }
            } );
        
            return a;
        }
        
        /**
         * Allow the result from a type detection function to be `true` while
         * translating that into a string. Old type detection functions will
         * return the type name if it passes. An obect store would be better,
         * but not backwards compatible.
         *
         * @param {*} typeDetect Object or function for type detection
         * @param {*} res Result from the type detection function
         * @returns Type name or false
         */
        function _typeResult (typeDetect, res) {
            return res === true
                ? typeDetect.name
                : res;
        }
        
        /**
         * Calculate the 'type' of a column
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnColumnTypes ( settings )
        {
            var columns = settings.aoColumns;
            var data = settings.aoData;
            var types = DataTable.ext.type.detect;
            var i, ien, j, jen, k, ken;
            var col, detectedType, cache;
        
            // If SSP then we don't have the full data set, so any type detection would be
            // unreliable and error prone
            if (_fnDataSource( settings ) === 'ssp') {
                return;
            }
        
            // For each column, spin over the data type detection functions, seeing if one matches
            for ( i=0, ien=columns.length ; i<ien ; i++ ) {
                col = columns[i];
                cache = [];
        
                if ( ! col.sType && col._sManualType ) {
                    col.sType = col._sManualType;
                }
                else if ( ! col.sType ) {
                    for ( j=0, jen=types.length ; j<jen ; j++ ) {
                        var typeDetect = types[j];
        
                        // There can be either one, or three type detection functions
                        var oneOf = typeDetect.oneOf;
                        var allOf = typeDetect.allOf || typeDetect;
                        var init = typeDetect.init;
                        var one = false;
        
                        detectedType = null;
        
                        // Fast detect based on column assignment
                        if (init) {
                            detectedType = _typeResult(typeDetect, init(settings, col, i));
        
                            if (detectedType) {
                                col.sType = detectedType;
                                break;
                            }
                        }
        
                        for ( k=0, ken=data.length ; k<ken ; k++ ) {
                            if (! data[k]) {
                                continue;
                            }
        
                            // Use a cache array so we only need to get the type data
                            // from the formatter once (when using multiple detectors)
                            if ( cache[k] === undefined ) {
                                cache[k] = _fnGetCellData( settings, k, i, 'type' );
                            }
        
                            // Only one data point in the column needs to match this function
                            if (oneOf && ! one) {
                                one = _typeResult(typeDetect, oneOf( cache[k], settings ));
                            }
        
                            // All data points need to match this function
                            detectedType = _typeResult(typeDetect, allOf( cache[k], settings ));
        
                            // If null, then this type can't apply to this column, so
                            // rather than testing all cells, break out. There is an
                            // exception for the last type which is `html`. We need to
                            // scan all rows since it is possible to mix string and HTML
                            // types
                            if ( ! detectedType && j !== types.length-3 ) {
                                break;
                            }
        
                            // Only a single match is needed for html type since it is
                            // bottom of the pile and very similar to string - but it
                            // must not be empty
                            if ( detectedType === 'html' && ! _empty(cache[k]) ) {
                                break;
                            }
                        }
        
                        // Type is valid for all data points in the column - use this
                        // type
                        if ( (oneOf && one && detectedType) || (!oneOf && detectedType) ) {
                            col.sType = detectedType;
                            break;
                        }
                    }
        
                    // Fall back - if no type was detected, always use string
                    if ( ! col.sType ) {
                        col.sType = 'string';
                    }
                }
        
                // Set class names for header / footer for auto type classes
                var autoClass = _ext.type.className[col.sType];
        
                if (autoClass) {
                    _columnAutoClass(settings.aoHeader, i, autoClass);
                    _columnAutoClass(settings.aoFooter, i, autoClass);
                }
        
                var renderer = _ext.type.render[col.sType];
        
                // This can only happen once! There is no way to remove
                // a renderer. After the first time the renderer has
                // already been set so createTr will run the renderer itself.
                if (renderer && ! col._render) {
                    col._render = DataTable.util.get(renderer);
        
                    _columnAutoRender(settings, i);
                }
            }
        }
        
        /**
         * Apply an auto detected renderer to data which doesn't yet have
         * a renderer
         */
        function _columnAutoRender(settings, colIdx) {
            var data = settings.aoData;
        
            for (var i=0 ; i<data.length ; i++) {
                if (data[i].nTr) {
                    // We have to update the display here since there is no
                    // invalidation check for the data
                    var display = _fnGetCellData( settings, i, colIdx, 'display' );
        
                    data[i].displayData[colIdx] = display;
                    _fnWriteCell(data[i].anCells[colIdx], display);
        
                    // No need to update sort / filter data since it has
                    // been invalidated and will be re-read with the
                    // renderer now applied
                }
            }
        }
        
        /**
         * Apply a class name to a column's header cells
         */
        function _columnAutoClass(container, colIdx, className) {
            container.forEach(function (row) {
                if (row[colIdx] && row[colIdx].unique) {
                    _addClass(row[colIdx].cell, className);
                }
            });
        }
        
        /**
         * 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 {array} headerLayout Layout for header as it was loaded
         *  @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, headerLayout, fn )
        {
            var i, iLen, j, jLen, k, kLen, def;
            var columns = oSettings.aoColumns;
        
            if ( aoCols ) {
                for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) {
                    if (aoCols[i] && aoCols[i].name) {
                        columns[i].sName = aoCols[i].name;
                    }
                }
            }
        
            // 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-- )
                {
                    def = aoColDefs[i];
        
                    /* Each definition can target multiple columns, as it is an array */
                    var aTargets = def.target !== undefined
                        ? def.target
                        : def.targets !== undefined
                            ? def.targets
                            : def.aTargets;
        
                    if ( ! Array.isArray( aTargets ) )
                    {
                        aTargets = [ aTargets ];
                    }
        
                    for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
                    {
                        var target = aTargets[j];
        
                        if ( typeof target === 'number' && target >= 0 )
                        {
                            /* Add columns that we don't yet know about */
                            while( columns.length <= target )
                            {
                                _fnAddColumn( oSettings );
                            }
        
                            /* Integer, basic index */
                            fn( target, def );
                        }
                        else if ( typeof target === 'number' && target < 0 )
                        {
                            /* Negative integer, right to left column counting */
                            fn( columns.length+target, def );
                        }
                        else if ( typeof target === 'string' )
                        {
                            for ( k=0, kLen=columns.length ; k<kLen ; k++ ) {
                                if (target === '_all') {
                                    // Apply to all columns
                                    fn( k, def );
                                }
                                else if (target.indexOf(':name') !== -1) {
                                    // Column selector
                                    if (columns[k].sName === target.replace(':name', '')) {
                                        fn( k, def );
                                    }
                                }
                                else {
                                    // Cell selector
                                    headerLayout.forEach(function (row) {
                                        if (row[k]) {
                                            var cell = $(row[k].cell);
        
                                            // Legacy support. Note that it means that we don't support
                                            // an element name selector only, since they are treated as
                                            // class names for 1.x compat.
                                            if (target.match(/^[a-z][\w-]*$/i)) {
                                                target = '.' + target;
                                            }
        
                                            if (cell.is( target )) {
                                                fn( k, def );
                                            }
                                        }
                                    });
                                }
                            }
                        }
                    }
                }
            }
        
            // Statically defined columns array
            if ( aoCols ) {
                for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) {
                    fn( i, aoCols[i] );
                }
            }
        }
        
        
        /**
         * Get the width for a given set of columns
         *
         * @param {*} settings DataTables settings object
         * @param {*} targets Columns - comma separated string or array of numbers
         * @param {*} original Use the original width (true) or calculated (false)
         * @param {*} incVisible Include visible columns (true) or not (false)
         * @returns Combined CSS value
         */
        function _fnColumnsSumWidth( settings, targets, original, incVisible ) {
            if ( ! Array.isArray( targets ) ) {
                targets = _fnColumnsFromHeader( targets );
            }
        
            var sum = 0;
            var unit;
            var columns = settings.aoColumns;
            
            for ( var i=0, ien=targets.length ; i<ien ; i++ ) {
                var column = columns[ targets[i] ];
                var definedWidth = original ?
                    column.sWidthOrig :
                    column.sWidth;
        
                if ( ! incVisible && column.bVisible === false ) {
                    continue;
                }
        
                if ( definedWidth === null || definedWidth === undefined ) {
                    return null; // can't determine a defined width - browser defined
                }
                else if ( typeof definedWidth === 'number' ) {
                    unit = 'px';
                    sum += definedWidth;
                }
                else {
                    var matched = definedWidth.match(/([\d\.]+)([^\d]*)/);
        
                    if ( matched ) {
                        sum += matched[1] * 1;
                        unit = matched.length === 3 ?
                            matched[2] :
                            'px';
                    }
                }
            }
        
            return sum + unit;
        }
        
        function _fnColumnsFromHeader( cell )
        {
            var attr = $(cell).closest('[data-dt-column]').attr('data-dt-column');
        
            if ( ! attr ) {
                return [];
            }
        
            return attr.split(',').map( function (val) {
                return val * 1;
            } );
        }
        /**
         * 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} settings dataTables settings object
         *  @param {array} data data array to be added
         *  @param {node} [tr] TR element to add to the table - optional. If not given,
         *    DataTables will create a row automatically
         *  @param {array} [tds] Array of TD|TH elements for the row - must be given
         *    if nTr is.
         *  @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
         *  @memberof DataTable#oApi
         */
        function _fnAddData ( settings, dataIn, tr, tds )
        {
            /* Create the object for storing information about this new row */
            var rowIdx = settings.aoData.length;
            var rowModel = $.extend( true, {}, DataTable.models.oRow, {
                src: tr ? 'dom' : 'data',
                idx: rowIdx
            } );
        
            rowModel._aData = dataIn;
            settings.aoData.push( rowModel );
        
            var columns = settings.aoColumns;
        
            for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
            {
                // Invalidate the column types as the new data needs to be revalidated
                columns[i].sType = null;
            }
        
            /* Add to the display array */
            settings.aiDisplayMaster.push( rowIdx );
        
            var id = settings.rowIdFn( dataIn );
            if ( id !== undefined ) {
                settings.aIds[ id ] = rowModel;
            }
        
            /* Create the DOM information, or register it if already present */
            if ( tr || ! settings.oFeatures.bDeferRender )
            {
                _fnCreateTr( settings, rowIdx, tr, tds );
            }
        
            return rowIdx;
        }
        
        
        /**
         * Add one or more TR elements to the table. Generally we'd expect to
         * use this for reading data from a DOM sourced table, but it could be
         * used for an TR element. Note that if a TR is given, it is used (i.e.
         * it is not cloned).
         *  @param {object} settings dataTables settings object
         *  @param {array|node|jQuery} trs The TR element(s) to add to the table
         *  @returns {array} Array of indexes for the added rows
         *  @memberof DataTable#oApi
         */
        function _fnAddTr( settings, trs )
        {
            var row;
        
            // Allow an individual node to be passed in
            if ( ! (trs instanceof $) ) {
                trs = $(trs);
            }
        
            return trs.map( function (i, el) {
                row = _fnGetRowElements( settings, el );
                return _fnAddData( settings, row.data, el, row.cells );
            } );
        }
        
        
        /**
         * Get the data for a given cell from the internal cache, taking into account data mapping
         *  @param {object} settings dataTables settings object
         *  @param {int} rowIdx aoData row id
         *  @param {int} colIdx Column index
         *  @param {string} type data get type ('display', 'type' 'filter|search' 'sort|order')
         *  @returns {*} Cell data
         *  @memberof DataTable#oApi
         */
        function _fnGetCellData( settings, rowIdx, colIdx, type )
        {
            if (type === 'search') {
                type = 'filter';
            }
            else if (type === 'order') {
                type = 'sort';
            }
        
            var row = settings.aoData[rowIdx];
        
            if (! row) {
                return undefined;
            }
        
            var draw           = settings.iDraw;
            var col            = settings.aoColumns[colIdx];
            var rowData        = row._aData;
            var defaultContent = col.sDefaultContent;
            var cellData       = col.fnGetData( rowData, type, {
                settings: settings,
                row:      rowIdx,
                col:      colIdx
            } );
        
            // Allow for a node being returned for non-display types
            if (type !== 'display' && cellData && typeof cellData === 'object' && cellData.nodeName) {
                cellData = cellData.innerHTML;
            }
        
            if ( cellData === undefined ) {
                if ( settings.iDrawError != draw && defaultContent === null ) {
                    _fnLog( settings, 0, "Requested unknown parameter "+
                        (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+
                        " for row "+rowIdx+", column "+colIdx, 4 );
                    settings.iDrawError = draw;
                }
                return defaultContent;
            }
        
            // When the data source is null and a specific data type is requested (i.e.
            // not the original data), we can use default column data
            if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) {
                cellData = defaultContent;
            }
            else if ( typeof cellData === 'function' ) {
                // If the data source is a function, then we run it and use the return,
                // executing in the scope of the data object (for instances)
                return cellData.call( rowData );
            }
        
            if ( cellData === null && type === 'display' ) {
                return '';
            }
        
            if ( type === 'filter' ) {
                var fomatters = DataTable.ext.type.search;
        
                if ( fomatters[ col.sType ] ) {
                    cellData = fomatters[ col.sType ]( cellData );
                }
            }
        
            return cellData;
        }
        
        
        /**
         * Set the value for a specific cell, into the internal data cache
         *  @param {object} settings dataTables settings object
         *  @param {int} rowIdx aoData row id
         *  @param {int} colIdx Column index
         *  @param {*} val Value to set
         *  @memberof DataTable#oApi
         */
        function _fnSetCellData( settings, rowIdx, colIdx, val )
        {
            var col     = settings.aoColumns[colIdx];
            var rowData = settings.aoData[rowIdx]._aData;
        
            col.fnSetData( rowData, val, {
                settings: settings,
                row:      rowIdx,
                col:      colIdx
            }  );
        }
        
        /**
         * Write a value to a cell
         * @param {*} td Cell
         * @param {*} val Value
         */
        function _fnWriteCell(td, val)
        {
            if (val && typeof val === 'object' && val.nodeName) {
                $(td)
                    .empty()
                    .append(val);
            }
            else {
                td.innerHTML = val;
            }
        }
        
        
        // Private variable that is used to match action syntax in the data property object
        var __reArray = /\[.*?\]$/;
        var __reFn = /\(\)$/;
        
        /**
         * Split string on periods, taking into account escaped periods
         * @param  {string} str String to split
         * @return {array} Split string
         */
        function _fnSplitObjNotation( str )
        {
            var parts = str.match(/(\\.|[^.])+/g) || [''];
        
            return parts.map( function ( s ) {
                return s.replace(/\\\./g, '.');
            } );
        }
        
        
        /**
         * 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
         */
        var _fnGetObjectDataFn = DataTable.util.get;
        
        
        /**
         * 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
         */
        var _fnSetObjectDataFn = DataTable.util.set;
        
        
        /**
         * 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 ( settings )
        {
            return _pluck( settings.aoData, '_aData' );
        }
        
        
        /**
         * Nuke the table
         *  @param {object} oSettings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnClearTable( settings )
        {
            settings.aoData.length = 0;
            settings.aiDisplayMaster.length = 0;
            settings.aiDisplay.length = 0;
            settings.aIds = {};
        }
        
        
        /**
         * Mark cached data as invalid such that a re-read of the data will occur when
         * the cached data is next requested. Also update from the data source object.
         *
         * @param {object} settings DataTables settings object
         * @param {int}    rowIdx   Row index to invalidate
         * @param {string} [src]    Source to invalidate from: undefined, 'auto', 'dom'
         *     or 'data'
         * @param {int}    [colIdx] Column index to invalidate. If undefined the whole
         *     row will be invalidated
         * @memberof DataTable#oApi
         *
         * @todo For the modularisation of v1.11 this will need to become a callback, so
         *   the sort and filter methods can subscribe to it. That will required
         *   initialisation options for sorting, which is why it is not already baked in
         */
        function _fnInvalidate( settings, rowIdx, src, colIdx )
        {
            var row = settings.aoData[ rowIdx ];
            var i, ien;
        
            // Remove the cached data for the row
            row._aSortData = null;
            row._aFilterData = null;
            row.displayData = null;
        
            // Are we reading last data from DOM or the data object?
            if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
                // Read the data from the DOM
                row._aData = _fnGetRowElements(
                        settings, row, colIdx, colIdx === undefined ? undefined : row._aData
                    )
                    .data;
            }
            else {
                // Reading from data object, update the DOM
                var cells = row.anCells;
                var display = _fnGetRowDisplay(settings, rowIdx);
        
                if ( cells ) {
                    if ( colIdx !== undefined ) {
                        _fnWriteCell(cells[colIdx], display[colIdx]);
                    }
                    else {
                        for ( i=0, ien=cells.length ; i<ien ; i++ ) {
                            _fnWriteCell(cells[i], display[i]);
                        }
                    }
                }
            }
        
            // Column specific invalidation
            var cols = settings.aoColumns;
            if ( colIdx !== undefined ) {
                // Type - the data might have changed
                cols[ colIdx ].sType = null;
        
                // Max length string. Its a fairly cheep recalculation, so not worth
                // something more complicated
                cols[ colIdx ].maxLenString = null;
            }
            else {
                for ( i=0, ien=cols.length ; i<ien ; i++ ) {
                    cols[i].sType = null;
                    cols[i].maxLenString = null;
                }
        
                // Update DataTables special `DT_*` attributes for the row
                _fnRowAttributes( settings, row );
            }
        }
        
        
        /**
         * Build a data source object from an HTML row, reading the contents of the
         * cells that are in the row.
         *
         * @param {object} settings DataTables settings object
         * @param {node|object} TR element from which to read data or existing row
         *   object from which to re-read the data from the cells
         * @param {int} [colIdx] Optional column index
         * @param {array|object} [d] Data source object. If `colIdx` is given then this
         *   parameter should also be given and will be used to write the data into.
         *   Only the column in question will be written
         * @returns {object} Object with two parameters: `data` the data read, in
         *   document order, and `cells` and array of nodes (they can be useful to the
         *   caller, so rather than needing a second traversal to get them, just return
         *   them from here).
         * @memberof DataTable#oApi
         */
        function _fnGetRowElements( settings, row, colIdx, d )
        {
            var
                tds = [],
                td = row.firstChild,
                name, col, i=0, contents,
                columns = settings.aoColumns,
                objectRead = settings._rowReadObject;
        
            // Allow the data object to be passed in, or construct
            d = d !== undefined ?
                d :
                objectRead ?
                    {} :
                    [];
        
            var attr = function ( str, td  ) {
                if ( typeof str === 'string' ) {
                    var idx = str.indexOf('@');
        
                    if ( idx !== -1 ) {
                        var attr = str.substring( idx+1 );
                        var setter = _fnSetObjectDataFn( str );
                        setter( d, td.getAttribute( attr ) );
                    }
                }
            };
        
            // Read data from a cell and store into the data object
            var cellProcess = function ( cell ) {
                if ( colIdx === undefined || colIdx === i ) {
                    col = columns[i];
                    contents = (cell.innerHTML).trim();
        
                    if ( col && col._bAttrSrc ) {
                        var setter = _fnSetObjectDataFn( col.mData._ );
                        setter( d, contents );
        
                        attr( col.mData.sort, cell );
                        attr( col.mData.type, cell );
                        attr( col.mData.filter, cell );
                    }
                    else {
                        // Depending on the `data` option for the columns the data can
                        // be read to either an object or an array.
                        if ( objectRead ) {
                            if ( ! col._setter ) {
                                // Cache the setter function
                                col._setter = _fnSetObjectDataFn( col.mData );
                            }
                            col._setter( d, contents );
                        }
                        else {
                            d[i] = contents;
                        }
                    }
                }
        
                i++;
            };
        
            if ( td ) {
                // `tr` element was passed in
                while ( td ) {
                    name = td.nodeName.toUpperCase();
        
                    if ( name == "TD" || name == "TH" ) {
                        cellProcess( td );
                        tds.push( td );
                    }
        
                    td = td.nextSibling;
                }
            }
            else {
                // Existing row object passed in
                tds = row.anCells;
        
                for ( var j=0, jen=tds.length ; j<jen ; j++ ) {
                    cellProcess( tds[j] );
                }
            }
        
            // Read the ID from the DOM if present
            var rowNode = row.firstChild ? row : row.nTr;
        
            if ( rowNode ) {
                var id = rowNode.getAttribute( 'id' );
        
                if ( id ) {
                    _fnSetObjectDataFn( settings.rowId )( d, id );
                }
            }
        
            return {
                data: d,
                cells: tds
            };
        }
        
        /**
         * Render and cache a row's display data for the columns, if required
         * @returns 
         */
        function _fnGetRowDisplay (settings, rowIdx) {
            let rowModal = settings.aoData[rowIdx];
            let columns = settings.aoColumns;
        
            if (! rowModal.displayData) {
                // Need to render and cache
                rowModal.displayData = [];
            
                for ( var colIdx=0, len=columns.length ; colIdx<len ; colIdx++ ) {
                    rowModal.displayData.push(
                        _fnGetCellData( settings, rowIdx, colIdx, 'display' )
                    );
                }
            }
        
            return rowModal.displayData;
        }
        
        /**
         * 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
         *  @param {node} [nTrIn] TR element to add to the table - optional. If not given,
         *    DataTables will create a row automatically
         *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
         *    if nTr is.
         *  @memberof DataTable#oApi
         */
        function _fnCreateTr ( oSettings, iRow, nTrIn, anTds )
        {
            var
                row = oSettings.aoData[iRow],
                rowData = row._aData,
                cells = [],
                nTr, nTd, oCol,
                i, iLen, create,
                trClass = oSettings.oClasses.tbody.row;
        
            if ( row.nTr === null )
            {
                nTr = nTrIn || document.createElement('tr');
        
                row.nTr = nTr;
                row.anCells = cells;
        
                _addClass(nTr, trClass);
        
                /* Use a private property on the node to allow reserve mapping from the node
                 * to the aoData array for fast look up
                 */
                nTr._DT_RowIndex = iRow;
        
                /* Special parameters can be given by the data source to be used on the row */
                _fnRowAttributes( oSettings, row );
        
                /* Process each column */
                for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
                {
                    oCol = oSettings.aoColumns[i];
                    create = nTrIn && anTds[i] ? false : true;
        
                    nTd = create ? document.createElement( oCol.sCellType ) : anTds[i];
        
                    if (! nTd) {
                        _fnLog( oSettings, 0, 'Incorrect column count', 18 );
                    }
        
                    nTd._DT_CellIndex = {
                        row: iRow,
                        column: i
                    };
                    
                    cells.push( nTd );
                    
                    var display = _fnGetRowDisplay(oSettings, iRow);
        
                    // Need to create the HTML if new, or if a rendering function is defined
                    if (
                        create ||
                        (
                            (oCol.mRender || oCol.mData !== i) &&
                            (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
                        )
                    ) {
                        _fnWriteCell(nTd, display[i]);
                    }
        
                    // column class
                    _addClass(nTd, oCol.sClass);
        
                    // Visibility - add or remove as required
                    if ( oCol.bVisible && create )
                    {
                        nTr.appendChild( nTd );
                    }
                    else if ( ! oCol.bVisible && ! create )
                    {
                        nTd.parentNode.removeChild( nTd );
                    }
        
                    if ( oCol.fnCreatedCell )
                    {
                        oCol.fnCreatedCell.call( oSettings.oInstance,
                            nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i
                        );
                    }
                }
        
                _fnCallbackFire( oSettings, 'aoRowCreatedCallback', 'row-created', [nTr, rowData, iRow, cells] );
            }
            else {
                _addClass(row.nTr, trClass);
            }
        }
        
        
        /**
         * Add attributes to a row based on the special `DT_*` parameters in a data
         * source object.
         *  @param {object} settings DataTables settings object
         *  @param {object} DataTables row object for the row to be modified
         *  @memberof DataTable#oApi
         */
        function _fnRowAttributes( settings, row )
        {
            var tr = row.nTr;
            var data = row._aData;
        
            if ( tr ) {
                var id = settings.rowIdFn( data );
        
                if ( id ) {
                    tr.id = id;
                }
        
                if ( data.DT_RowClass ) {
                    // Remove any classes added by DT_RowClass before
                    var a = data.DT_RowClass.split(' ');
                    row.__rowc = row.__rowc ?
                        _unique( row.__rowc.concat( a ) ) :
                        a;
        
                    $(tr)
                        .removeClass( row.__rowc.join(' ') )
                        .addClass( data.DT_RowClass );
                }
        
                if ( data.DT_RowAttr ) {
                    $(tr).attr( data.DT_RowAttr );
                }
        
                if ( data.DT_RowData ) {
                    $(tr).data( data.DT_RowData );
                }
            }
        }
        
        
        /**
         * Create the HTML header for the table
         *  @param {object} oSettings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnBuildHead( settings, side )
        {
            var classes = settings.oClasses;
            var columns = settings.aoColumns;
            var i, ien, row;
            var target = side === 'header'
                ? settings.nTHead
                : settings.nTFoot;
            var titleProp = side === 'header' ? 'sTitle' : side;
        
            // Footer might be defined
            if (! target) {
                return;
            }
        
            // If no cells yet and we have content for them, then create
            if (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) {
                row = $('tr', target);
        
                // Add a row if needed
                if (! row.length) {
                    row = $('<tr/>').appendTo(target)
                }
        
                // Add the number of cells needed to make up to the number of columns
                if (row.length === 1) {
                    var cells = $('td, th', row);
        
                    for ( i=cells.length, ien=columns.length ; i<ien ; i++ ) {
                        $('<th/>')
                            .html( columns[i][titleProp] || '' )
                            .appendTo( row );
                    }
                }
            }
        
            var detected = _fnDetectHeader( settings, target, true );
        
            if (side === 'header') {
                settings.aoHeader = detected;
            }
            else {
                settings.aoFooter = detected;
            }
        
            // ARIA role for the rows
            $(target).children('tr').attr('role', 'row');
        
            // Every cell needs to be passed through the renderer
            $(target).children('tr').children('th, td')
                .each( function () {
                    _fnRenderer( settings, side )(
                        settings, $(this), classes
                    );
                } );
        }
        
        /**
         * Build a layout structure for a header or footer
         *
         * @param {*} settings DataTables settings
         * @param {*} source Source layout array
         * @param {*} incColumns What columns should be included
         * @returns Layout array
         */
        function _fnHeaderLayout( settings, source, incColumns )
        {
            var row, column, cell;
            var local = [];
            var structure = [];
            var columns = settings.aoColumns;
            var columnCount = columns.length;
            var rowspan, colspan;
        
            if ( ! source ) {
                return;
            }
        
            // Default is to work on only visible columns
            if ( ! incColumns ) {
                incColumns = _range(columnCount)
                    .filter(function (idx) {
                        return columns[idx].bVisible;
                    });
            }
        
            // Make a copy of the master layout array, but with only the columns we want
            for ( row=0 ; row<source.length ; row++ ) {
                // Remove any columns we haven't selected
                local[row] = source[row].slice().filter(function (cell, i) {
                    return incColumns.includes(i);
                });
        
                // Prep the structure array - it needs an element for each row
                structure.push( [] );
            }
        
            for ( row=0 ; row<local.length ; row++ ) {
                for ( column=0 ; column<local[row].length ; column++ ) {
                    rowspan = 1;
                    colspan = 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 ( structure[row][column] === undefined ) {
                        cell = local[row][column].cell;
        
                        // Expand for rowspan
                        while (
                            local[row+rowspan] !== undefined &&
                            local[row][column].cell == local[row+rowspan][column].cell
                        ) {
                            structure[row+rowspan][column] = null;
                            rowspan++;
                        }
        
                        // And for colspan
                        while (
                            local[row][column+colspan] !== undefined &&
                            local[row][column].cell == local[row][column+colspan].cell
                        ) {
                            // Which also needs to go over rows
                            for ( var k=0 ; k<rowspan ; k++ ) {
                                structure[row+k][column+colspan] = null;
                            }
        
                            colspan++;
                        }
        
                        var titleSpan = $('span.dt-column-title', cell);
        
                        structure[row][column] = {
                            cell: cell,
                            colspan: colspan,
                            rowspan: rowspan,
                            title: titleSpan.length
                                ? titleSpan.html()
                                : $(cell).html()
                        };
                    }
                }
            }
        
            return structure;
        }
        
        
        /**
         * Draw the header (or footer) element based on the column visibility states.
         *
         *  @param object oSettings dataTables settings object
         *  @param array aoSource Layout array from _fnDetectHeader
         *  @memberof DataTable#oApi
         */
        function _fnDrawHead( settings, source )
        {
            var layout = _fnHeaderLayout(settings, source);
            var tr, n;
        
            for ( var row=0 ; row<source.length ; row++ ) {
                tr = source[row].row;
        
                // All cells are going to be replaced, so empty out the row
                // Can't use $().empty() as that kills event handlers
                if (tr) {
                    while( (n = tr.firstChild) ) {
                        tr.removeChild( n );
                    }
                }
        
                for ( var column=0 ; column<layout[row].length ; column++ ) {
                    var point = layout[row][column];
        
                    if (point) {
                        $(point.cell)
                            .appendTo(tr)
                            .attr('rowspan', point.rowspan)
                            .attr('colspan', point.colspan);
                    }
                }
            }
        }
        
        
        /**
         * Insert the required TR nodes into the table for display
         *  @param {object} oSettings dataTables settings object
         *  @param ajaxComplete true after ajax call to complete rendering
         *  @memberof DataTable#oApi
         */
        function _fnDraw( oSettings, ajaxComplete )
        {
            // Allow for state saving and a custom start position
            _fnStart( 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 ( aPreDraw.indexOf(false) !== -1 )
            {
                _fnProcessingDisplay( oSettings, false );
                return;
            }
        
            var anRows = [];
            var iRowCount = 0;
            var bServerSide = _fnDataSource( oSettings ) == 'ssp';
            var aiDisplay = oSettings.aiDisplay;
            var iDisplayStart = oSettings._iDisplayStart;
            var iDisplayEnd = oSettings.fnDisplayEnd();
            var columns = oSettings.aoColumns;
            var body = $(oSettings.nTBody);
        
            oSettings.bDrawing = true;
        
            /* Server-side processing draw intercept */
            if ( oSettings.deferLoading )
            {
                oSettings.deferLoading = false;
                oSettings.iDraw++;
                _fnProcessingDisplay( oSettings, false );
            }
            else if ( !bServerSide )
            {
                oSettings.iDraw++;
            }
            else if ( !oSettings.bDestroying && !ajaxComplete)
            {
                // Show loading message for server-side processing
                if (oSettings.iDraw === 0) {
                    body.empty().append(_emptyRow(oSettings));
                }
        
                _fnAjaxUpdate( oSettings );
                return;
            }
        
            if ( aiDisplay.length !== 0 )
            {
                var iStart = bServerSide ? 0 : iDisplayStart;
                var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
        
                for ( var j=iStart ; j<iEnd ; j++ )
                {
                    var iDataIndex = aiDisplay[j];
                    var aoData = oSettings.aoData[ iDataIndex ];
                    if ( aoData.nTr === null )
                    {
                        _fnCreateTr( oSettings, iDataIndex );
                    }
        
                    var nRow = aoData.nTr;
        
                    // Add various classes as needed
                    for (var i=0 ; i<columns.length ; i++) {
                        var col = columns[i];
                        var td = aoData.anCells[i];
        
                        _addClass(td, _ext.type.className[col.sType]); // auto class
                        _addClass(td, oSettings.oClasses.tbody.cell); // all cells
                    }
        
                    // Row callback functions - might want to manipulate the row
                    // iRowCount and j are not currently documented. Are they at all
                    // useful?
                    _fnCallbackFire( oSettings, 'aoRowCallback', null,
                        [nRow, aoData._aData, iRowCount, j, iDataIndex] );
        
                    anRows.push( nRow );
                    iRowCount++;
                }
            }
            else
            {
                anRows[ 0 ] = _emptyRow(oSettings);
            }
        
            /* Header and footer callbacks */
            _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
                _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
        
            _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
                _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
        
            // replaceChildren is faster, but only became widespread in 2020,
            // so a fall back in jQuery is provided for older browsers.
            if (body[0].replaceChildren) {
                body[0].replaceChildren.apply(body[0], anRows);
            }
            else {
                body.children().detach();
                body.append( $(anRows) );
            }
        
            // Empty table needs a specific class
            $(oSettings.nTableWrapper).toggleClass('dt-empty-footer', $('tr', oSettings.nTFoot).length === 0);
        
            /* Call all required callback functions for the end of a draw */
            _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings], true );
        
            /* Draw is complete, sorting and filtering must be as well */
            oSettings.bSorted = false;
            oSettings.bFiltered = false;
            oSettings.bDrawing = false;
        }
        
        
        /**
         * Redraw the table - taking account of the various features which are enabled
         *  @param {object} oSettings dataTables settings object
         *  @param {boolean} [holdPosition] Keep the current paging position. By default
         *    the paging is reset to the first page
         *  @memberof DataTable#oApi
         */
        function _fnReDraw( settings, holdPosition, recompute )
        {
            var
                features = settings.oFeatures,
                sort     = features.bSort,
                filter   = features.bFilter;
        
            if (recompute === undefined || recompute === true) {
                // Resolve any column types that are unknown due to addition or invalidation
                _fnColumnTypes( settings );
        
                if ( sort ) {
                    _fnSort( settings );
                }
        
                if ( filter ) {
                    _fnFilterComplete( settings, settings.oPreviousSearch );
                }
                else {
                    // No filtering, so we want to just use the display master
                    settings.aiDisplay = settings.aiDisplayMaster.slice();
                }
            }
        
            if ( holdPosition !== true ) {
                settings._iDisplayStart = 0;
            }
        
            // Let any modules know about the draw hold position state (used by
            // scrolling internally)
            settings._drawHold = holdPosition;
        
            _fnDraw( settings );
        
            settings._drawHold = false;
        }
        
        
        /*
         * Table is empty - create a row with an empty message in it
         */
        function _emptyRow ( settings ) {
            var oLang = settings.oLanguage;
            var zero = oLang.sZeroRecords;
            var dataSrc = _fnDataSource( settings );
        
            if (
                (settings.iDraw < 1 && dataSrc === 'ssp') ||
                (settings.iDraw <= 1 && dataSrc === 'ajax')
            ) {
                zero = oLang.sLoadingRecords;
            }
            else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 )
            {
                zero = oLang.sEmptyTable;
            }
        
            return $( '<tr/>' )
                .append( $('<td />', {
                    'colSpan': _fnVisbleColumns( settings ),
                    'class':   settings.oClasses.empty.row
                } ).html( zero ) )[0];
        }
        
        
        /**
         * Expand the layout items into an object for the rendering function
         */
        function _layoutItems (row, align, items) {
            if ( Array.isArray(items)) {
                for (var i=0 ; i<items.length ; i++) {
                    _layoutItems(row, align, items[i]);
                }
        
                return;
            }
        
            var rowCell = row[align];
        
            // If it is an object, then there can be multiple features contained in it
            if ( $.isPlainObject( items ) ) {
                // A feature plugin cannot be named "features" due to this check
                if (items.features) {
                    if (items.rowId) {
                        row.id = items.rowId;
                    }
                    if (items.rowClass) {
                        row.className = items.rowClass;
                    }
        
                    rowCell.id = items.id;
                    rowCell.className = items.className;
        
                    _layoutItems(row, align, items.features);
                }
                else {
                    Object.keys(items).map(function (key) {
                        rowCell.contents.push( {
                            feature: key,
                            opts: items[key]
                        });
                    });
                }
            }
            else {
                rowCell.contents.push(items);
            }
        }
        
        /**
         * Find, or create a layout row
         */
        function _layoutGetRow(rows, rowNum, align) {
            var row;
        
            // Find existing rows
            for (var i=0; i<rows.length; i++) {
                row = rows[i];
        
                if (row.rowNum === rowNum) {
                    // full is on its own, but start and end share a row
                    if (
                        (align === 'full' && row.full) ||
                        ((align === 'start' || align === 'end') && (row.start || row.end))
                    ) {
                        if (! row[align]) {
                            row[align] = {
                                contents: []
                            };
                        }
        
                        return row;
                    }
                }
            }
        
            // If we get this far, then there was no match, create a new row
            row = {
                rowNum: rowNum	
            };
        
            row[align] = {
                contents: []
            };
        
            rows.push(row);
        
            return row;
        }
        
        /**
         * Convert a `layout` object given by a user to the object structure needed
         * for the renderer. This is done twice, once for above and once for below
         * the table. Ordering must also be considered.
         *
         * @param {*} settings DataTables settings object
         * @param {*} layout Layout object to convert
         * @param {string} side `top` or `bottom`
         * @returns Converted array structure - one item for each row.
         */
        function _layoutArray ( settings, layout, side ) {
            var rows = [];
            
            // Split out into an array
            $.each( layout, function ( pos, items ) {
                if (items === null) {
                    return;
                }
        
                var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/);
                var rowNum = parts[2]
                    ? parts[2] * 1
                    : 0;
                var align = parts[3]
                    ? parts[3].toLowerCase()
                    : 'full';
        
                // Filter out the side we aren't interested in
                if (parts[1] !== side) {
                    return;
                }
        
                // Get or create the row we should attach to
                var row = _layoutGetRow(rows, rowNum, align);
        
                _layoutItems(row, align, items);
            });
        
            // Order by item identifier
            rows.sort( function ( a, b ) {
                var order1 = a.rowNum;
                var order2 = b.rowNum;
        
                // If both in the same row, then the row with `full` comes first
                if (order1 === order2) {
                    var ret = a.full && ! b.full ? -1 : 1;
        
                    return side === 'bottom'
                        ? ret * -1
                        : ret;
                }
        
                return order2 - order1;
            } );
        
            // Invert for below the table
            if ( side === 'bottom' ) {
                rows.reverse();
            }
        
            for (var row = 0; row<rows.length; row++) {
                delete rows[row].rowNum;
        
                _layoutResolve(settings, rows[row]);
            }
        
            return rows;
        }
        
        
        /**
         * Convert the contents of a row's layout object to nodes that can be inserted
         * into the document by a renderer. Execute functions, look up plug-ins, etc.
         *
         * @param {*} settings DataTables settings object
         * @param {*} row Layout object for this row
         */
        function _layoutResolve( settings, row ) {
            var getFeature = function (feature, opts) {
                if ( ! _ext.features[ feature ] ) {
                    _fnLog( settings, 0, 'Unknown feature: '+ feature );
                }
        
                return _ext.features[ feature ].apply( this, [settings, opts] );
            };
        
            var resolve = function ( item ) {
                if (! row[ item ]) {
                    return;
                }
        
                var line = row[ item ].contents;
        
                for ( var i=0, ien=line.length ; i<ien ; i++ ) {
                    if ( ! line[i] ) {
                        continue;
                    }
                    else if ( typeof line[i] === 'string' ) {
                        line[i] = getFeature( line[i], null );
                    }
                    else if ( $.isPlainObject(line[i]) ) {
                        // If it's an object, it just has feature and opts properties from
                        // the transform in _layoutArray
                        line[i] = getFeature(line[i].feature, line[i].opts);
                    }
                    else if ( typeof line[i].node === 'function' ) {
                        line[i] = line[i].node( settings );
                    }
                    else if ( typeof line[i] === 'function' ) {
                        var inst = line[i]( settings );
        
                        line[i] = typeof inst.node === 'function' ?
                            inst.node() :
                            inst;
                    }
                }
            };
        
            resolve('start');
            resolve('end');
            resolve('full');
        }
        
        
        /**
         * Add the options to the page HTML for the table
         *  @param {object} settings DataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnAddOptionsHtml ( settings )
        {
            var classes = settings.oClasses;
            var table = $(settings.nTable);
        
            // Wrapper div around everything DataTables controls
            var insert = $('<div/>')
                .attr({
                    id:      settings.sTableId+'_wrapper',
                    'class': classes.container
                })
                .insertBefore(table);
        
            settings.nTableWrapper = insert[0];
        
            if (settings.sDom) {
                // Legacy
                _fnLayoutDom(settings, settings.sDom, insert);
            }
            else {
                var top = _layoutArray( settings, settings.layout, 'top' );
                var bottom = _layoutArray( settings, settings.layout, 'bottom' );
                var renderer = _fnRenderer( settings, 'layout' );
            
                // Everything above - the renderer will actually insert the contents into the document
                top.forEach(function (item) {
                    renderer( settings, insert, item );
                });
        
                // The table - always the center of attention
                renderer( settings, insert, {
                    full: {
                        table: true,
                        contents: [ _fnFeatureHtmlTable(settings) ]
                    }
                } );
        
                // Everything below
                bottom.forEach(function (item) {
                    renderer( settings, insert, item );
                });
            }
        
            // Processing floats on top, so it isn't an inserted feature
            _processingHtml( settings );
        }
        
        /**
         * Draw the table with the legacy DOM property
         * @param {*} settings DT settings object
         * @param {*} dom DOM string
         * @param {*} insert Insert point
         */
        function _fnLayoutDom( settings, dom, insert )
        {
            var parts = dom.match(/(".*?")|('.*?')|./g);
            var featureNode, option, newNode, next, attr;
        
            for ( var i=0 ; i<parts.length ; i++ ) {
                featureNode = null;
                option = parts[i];
        
                if ( option == '<' ) {
                    // New container div
                    newNode = $('<div/>');
        
                    // Check to see if we should append an id and/or a class name to the container
                    next = parts[i+1];
        
                    if ( next[0] == "'" || next[0] == '"' ) {
                        attr = next.replace(/['"]/g, '');
        
                        var id = '', className;
        
                        /* 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 ( attr.indexOf('.') != -1 ) {
                            var split = attr.split('.');
        
                            id = split[0];
                            className = split[1];
                        }
                        else if ( attr[0] == "#" ) {
                            id = attr;
                        }
                        else {
                            className = attr;
                        }
        
                        newNode
                            .attr('id', id.substring(1))
                            .addClass(className);
        
                        i++; // Move along the position array
                    }
        
                    insert.append( newNode );
                    insert = newNode;
                }
                else if ( option == '>' ) {
                    // End container div
                    insert = insert.parent();
                }
                else if ( option == 't' ) {
                    // Table
                    featureNode = _fnFeatureHtmlTable( settings );
                }
                else
                {
                    DataTable.ext.feature.forEach(function(feature) {
                        if ( option == feature.cFeature ) {
                            featureNode = feature.fnInit( settings );
                        }
                    });
                }
        
                // Add to the display
                if ( featureNode ) {
                    insert.append( featureNode );
                }
            }
        }
        
        
        /**
         * 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 {node} thead The header/footer element for the table
         *  @returns {array} Calculated layout array
         *  @memberof DataTable#oApi
         */
        function _fnDetectHeader ( settings, thead, write )
        {
            var columns = settings.aoColumns;
            var rows = $(thead).children('tr');
            var row, cell;
            var i, k, l, iLen, shifted, column, colspan, rowspan;
            var isHeader = thead && thead.nodeName.toLowerCase() === 'thead';
            var layout = [];
            var unique;
            var shift = function ( a, i, j ) {
                var k = a[i];
                while ( k[j] ) {
                    j++;
                }
                return j;
            };
        
            // We know how many rows there are in the layout - so prep it
            for ( i=0, iLen=rows.length ; i<iLen ; i++ ) {
                layout.push( [] );
            }
        
            for ( i=0, iLen=rows.length ; i<iLen ; i++ ) {
                row = rows[i];
                column = 0;
        
                // For every cell in the row..
                cell = row.firstChild;
                while ( cell ) {
                    if (
                        cell.nodeName.toUpperCase() == 'TD' ||
                        cell.nodeName.toUpperCase() == 'TH'
                    ) {
                        var cols = [];
        
                        // Get the col and rowspan attributes from the DOM and sanitise them
                        colspan = cell.getAttribute('colspan') * 1;
                        rowspan = cell.getAttribute('rowspan') * 1;
                        colspan = (!colspan || colspan===0 || colspan===1) ? 1 : colspan;
                        rowspan = (!rowspan || rowspan===0 || rowspan===1) ? 1 : rowspan;
        
                        // There might be colspan cells already in this row, so shift our target
                        // accordingly
                        shifted = shift( layout, i, column );
        
                        // Cache calculation for unique columns
                        unique = colspan === 1 ?
                            true :
                            false;
                        
                        // Perform header setup
                        if ( write ) {
                            if (unique) {
                                // Allow column options to be set from HTML attributes
                                _fnColumnOptions( settings, shifted, $(cell).data() );
                                
                                // Get the width for the column. This can be defined from the
                                // width attribute, style attribute or `columns.width` option
                                var columnDef = columns[shifted];
                                var width = cell.getAttribute('width') || null;
                                var t = cell.style.width.match(/width:\s*(\d+[pxem%]+)/);
                                if ( t ) {
                                    width = t[1];
                                }
        
                                columnDef.sWidthOrig = columnDef.sWidth || width;
        
                                if (isHeader) {
                                    // Column title handling - can be user set, or read from the DOM
                                    // This happens before the render, so the original is still in place
                                    if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) {
                                        cell.innerHTML = columnDef.sTitle;
                                    }
        
                                    if (! columnDef.sTitle && unique) {
                                        columnDef.sTitle = _stripHtml(cell.innerHTML);
                                        columnDef.autoTitle = true;
                                    }
                                }
                                else {
                                    // Footer specific operations
                                    if (columnDef.footer) {
                                        cell.innerHTML = columnDef.footer;
                                    }
                                }
        
                                // Fall back to the aria-label attribute on the table header if no ariaTitle is
                                // provided.
                                if (! columnDef.ariaTitle) {
                                    columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle;
                                }
        
                                // Column specific class names
                                if ( columnDef.className ) {
                                    $(cell).addClass( columnDef.className );
                                }
                            }
        
                            // Wrap the column title so we can write to it in future
                            if ( $('span.dt-column-title', cell).length === 0) {
                                $('<span>')
                                    .addClass('dt-column-title')
                                    .append(cell.childNodes)
                                    .appendTo(cell);
                            }
        
                            if ( isHeader && $('span.dt-column-order', cell).length === 0) {
                                $('<span>')
                                    .addClass('dt-column-order')
                                    .appendTo(cell);
                            }
                        }
        
                        // If there is col / rowspan, copy the information into the layout grid
                        for ( l=0 ; l<colspan ; l++ ) {
                            for ( k=0 ; k<rowspan ; k++ ) {
                                layout[i+k][shifted+l] = {
                                    cell: cell,
                                    unique: unique
                                };
        
                                layout[i+k].row = row;
                            }
        
                            cols.push( shifted+l );
                        }
        
                        // Assign an attribute so spanning cells can still be identified
                        // as belonging to a column
                        cell.setAttribute('data-dt-column', _unique(cols).join(','));
                    }
        
                    cell = cell.nextSibling;
                }
            }
        
            return layout;
        }
        
        /**
         * Set the start position for draw
         *  @param {object} oSettings dataTables settings object
         */
        function _fnStart( oSettings )
        {
            var bServerSide = _fnDataSource( oSettings ) == 'ssp';
            var iInitDisplayStart = oSettings.iInitDisplayStart;
        
            // Check and see if we have an initial draw position from state saving
            if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
            {
                oSettings._iDisplayStart = bServerSide ?
                    iInitDisplayStart :
                    iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
                        0 :
                        iInitDisplayStart;
        
                oSettings.iInitDisplayStart = -1;
            }
        }
        
        /**
         * Create an Ajax call based on the table's settings, taking into account that
         * parameters can have multiple forms, and backwards compatibility.
         *
         * @param {object} oSettings dataTables settings object
         * @param {array} data Data to send to the server, required by
         *     DataTables - may be augmented by developer callbacks
         * @param {function} fn Callback function to run when data is obtained
         */
        function _fnBuildAjax( oSettings, data, fn )
        {
            var ajaxData;
            var ajax = oSettings.ajax;
            var instance = oSettings.oInstance;
            var callback = function ( json ) {
                var status = oSettings.jqXHR
                    ? oSettings.jqXHR.status
                    : null;
        
                if ( json === null || (typeof status === 'number' && status == 204 ) ) {
                    json = {};
                    _fnAjaxDataSrc( oSettings, json, [] );
                }
        
                var error = json.error || json.sError;
                if ( error ) {
                    _fnLog( oSettings, 0, error );
                }
        
                // Microsoft often wrap JSON as a string in another JSON object
                // Let's handle that automatically
                if (json.d && typeof json.d === 'string') {
                    try {
                        json = JSON.parse(json.d);
                    }
                    catch (e) {
                        // noop
                    }
                }
        
                oSettings.json = json;
        
                _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true );
                fn( json );
            };
        
            if ( $.isPlainObject( ajax ) && ajax.data )
            {
                ajaxData = ajax.data;
        
                var newData = typeof ajaxData === 'function' ?
                    ajaxData( data, oSettings ) :  // fn can manipulate data or return
                    ajaxData;                      // an object object or array to merge
        
                // If the function returned something, use that alone
                data = typeof ajaxData === 'function' && newData ?
                    newData :
                    $.extend( true, data, newData );
        
                // Remove the data property as we've resolved it already and don't want
                // jQuery to do it again (it is restored at the end of the function)
                delete ajax.data;
            }
        
            var baseAjax = {
                "url": typeof ajax === 'string' ?
                    ajax :
                    '',
                "data": data,
                "success": callback,
                "dataType": "json",
                "cache": false,
                "type": oSettings.sServerMethod,
                "error": function (xhr, error) {
                    var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR], true );
        
                    if ( ret.indexOf(true) === -1 ) {
                        if ( error == "parsererror" ) {
                            _fnLog( oSettings, 0, 'Invalid JSON response', 1 );
                        }
                        else if ( xhr.readyState === 4 ) {
                            _fnLog( oSettings, 0, 'Ajax error', 7 );
                        }
                    }
        
                    _fnProcessingDisplay( oSettings, false );
                }
            };
        
            // If `ajax` option is an object, extend and override our default base
            if ( $.isPlainObject( ajax ) ) {
                $.extend( baseAjax, ajax )
            }
        
            // Store the data submitted for the API
            oSettings.oAjaxData = data;
        
            // Allow plug-ins and external processes to modify the data
            _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true );
        
            if ( typeof ajax === 'function' )
            {
                // Is a function - let the caller define what needs to be done
                oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
            }
            else if (ajax.url === '') {
                // No url, so don't load any data. Just apply an empty data array
                // to the object for the callback.
                var empty = {};
        
                DataTable.util.set(ajax.dataSrc)(empty, []);
                callback(empty);
            }
            else {
                // Object to extend the base settings
                oSettings.jqXHR = $.ajax( baseAjax );
            }
        
            // Restore for next time around
            if ( ajaxData ) {
                ajax.data = ajaxData;
            }
        }
        
        
        /**
         * Update the table using an Ajax call
         *  @param {object} settings dataTables settings object
         *  @returns {boolean} Block the table drawing or not
         *  @memberof DataTable#oApi
         */
        function _fnAjaxUpdate( settings )
        {
            settings.iDraw++;
            _fnProcessingDisplay( settings, true );
        
            _fnBuildAjax(
                settings,
                _fnAjaxParameters( settings ),
                function(json) {
                    _fnAjaxUpdateDraw( settings, json );
                }
            );
        }
        
        
        /**
         * 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( settings )
        {
            var
                columns = settings.aoColumns,
                features = settings.oFeatures,
                preSearch = settings.oPreviousSearch,
                preColSearch = settings.aoPreSearchCols,
                colData = function ( idx, prop ) {
                    return typeof columns[idx][prop] === 'function' ?
                        'function' :
                        columns[idx][prop];
                };
        
            return {
                draw: settings.iDraw,
                columns: columns.map( function ( column, i ) {
                    return {
                        data: colData(i, 'mData'),
                        name: column.sName,
                        searchable: column.bSearchable,
                        orderable: column.bSortable,
                        search: {
                            value: preColSearch[i].search,
                            regex: preColSearch[i].regex,
                            fixed: Object.keys(column.searchFixed).map( function(name) {
                                return {
                                    name: name,
                                    term: column.searchFixed[name].toString()
                                }
                            })
                        }
                    };
                } ),
                order: _fnSortFlatten( settings ).map( function ( val ) {
                    return {
                        column: val.col,
                        dir: val.dir,
                        name: colData(val.col, 'sName')
                    };
                } ),
                start: settings._iDisplayStart,
                length: features.bPaginate ?
                    settings._iDisplayLength :
                    -1,
                search: {
                    value: preSearch.search,
                    regex: preSearch.regex,
                    fixed: Object.keys(settings.searchFixed).map( function(name) {
                        return {
                            name: name,
                            term: settings.searchFixed[name].toString()
                        }
                    })
                }
            };
        }
        
        
        /**
         * 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 ( settings, json )
        {
            var data = _fnAjaxDataSrc(settings, json);
            var draw = _fnAjaxDataSrcParam(settings, 'draw', json);
            var recordsTotal = _fnAjaxDataSrcParam(settings, 'recordsTotal', json);
            var recordsFiltered = _fnAjaxDataSrcParam(settings, 'recordsFiltered', json);
        
            if ( draw !== undefined ) {
                // Protect against out of sequence returns
                if ( draw*1 < settings.iDraw ) {
                    return;
                }
                settings.iDraw = draw * 1;
            }
        
            // No data in returned object, so rather than an array, we show an empty table
            if ( ! data ) {
                data = [];
            }
        
            _fnClearTable( settings );
            settings._iRecordsTotal   = parseInt(recordsTotal, 10);
            settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
        
            for ( var i=0, ien=data.length ; i<ien ; i++ ) {
                _fnAddData( settings, data[i] );
            }
            settings.aiDisplay = settings.aiDisplayMaster.slice();
        
            _fnDraw( settings, true );
            _fnInitComplete( settings );
            _fnProcessingDisplay( settings, false );
        }
        
        
        /**
         * Get the data from the JSON data source to use for drawing a table. Using
         * `_fnGetObjectDataFn` allows the data to be sourced from a property of the
         * source object, or from a processing function.
         *  @param {object} settings dataTables settings object
         *  @param  {object} json Data source object / array from the server
         *  @return {array} Array of data to use
         */
        function _fnAjaxDataSrc ( settings, json, write )
        {
            var dataProp = 'data';
        
            if ($.isPlainObject( settings.ajax ) && settings.ajax.dataSrc !== undefined) {
                // Could in inside a `dataSrc` object, or not!
                var dataSrc = settings.ajax.dataSrc;
        
                // string, function and object are valid types
                if (typeof dataSrc === 'string' || typeof dataSrc === 'function') {
                    dataProp = dataSrc;
                }
                else if (dataSrc.data !== undefined) {
                    dataProp = dataSrc.data;
                }
            }
        
            if ( ! write ) {
                if ( dataProp === 'data' ) {
                    // If the default, then we still want to support the old style, and safely ignore
                    // it if possible
                    return json.aaData || json[dataProp];
                }
        
                return dataProp !== "" ?
                    _fnGetObjectDataFn( dataProp )( json ) :
                    json;
            }
            
            // set
            _fnSetObjectDataFn( dataProp )( json, write );
        }
        
        /**
         * Very similar to _fnAjaxDataSrc, but for the other SSP properties
         * @param {*} settings DataTables settings object
         * @param {*} param Target parameter
         * @param {*} json JSON data
         * @returns Resolved value
         */
        function _fnAjaxDataSrcParam (settings, param, json) {
            var dataSrc = $.isPlainObject( settings.ajax )
                ? settings.ajax.dataSrc
                : null;
        
            if (dataSrc && dataSrc[param]) {
                // Get from custom location
                return _fnGetObjectDataFn( dataSrc[param] )( json );
            }
        
            // else - Default behaviour
            var old = '';
        
            // Legacy support
            if (param === 'draw') {
                old = 'sEcho';
            }
            else if (param === 'recordsTotal') {
                old = 'iTotalRecords';
            }
            else if (param === 'recordsFiltered') {
                old = 'iTotalDisplayRecords';
            }
        
            return json[old] !== undefined
                ? json[old]
                : json[param];
        }
        
        
        /**
         * Filter the table using both the global filter and column based filtering
         *  @param {object} settings dataTables settings object
         *  @param {object} input search information
         *  @memberof DataTable#oApi
         */
        function _fnFilterComplete ( settings, input )
        {
            var columnsSearch = settings.aoPreSearchCols;
        
            // In server-side processing all filtering is done by the server, so no point hanging around here
            if ( _fnDataSource( settings ) != 'ssp' )
            {
                // Check if any of the rows were invalidated
                _fnFilterData( settings );
        
                // Start from the full data set
                settings.aiDisplay = settings.aiDisplayMaster.slice();
        
                // Global filter first
                _fnFilter( settings.aiDisplay, settings, input.search, input );
        
                $.each(settings.searchFixed, function (name, term) {
                    _fnFilter(settings.aiDisplay, settings, term, {});
                });
        
                // Then individual column filters
                for ( var i=0 ; i<columnsSearch.length ; i++ )
                {
                    var col = columnsSearch[i];
        
                    _fnFilter(
                        settings.aiDisplay,
                        settings,
                        col.search,
                        col,
                        i
                    );
        
                    $.each(settings.aoColumns[i].searchFixed, function (name, term) {
                        _fnFilter(settings.aiDisplay, settings, term, {}, i);
                    });
                }
        
                // And finally global filtering
                _fnFilterCustom( settings );
            }
        
            // Tell the draw function we have been filtering
            settings.bFiltered = true;
        
            _fnCallbackFire( settings, null, 'search', [settings] );
        }
        
        
        /**
         * Apply custom filtering functions
         * 
         * This is legacy now that we have named functions, but it is widely used
         * from 1.x, so it is not yet deprecated.
         *  @param {object} oSettings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnFilterCustom( settings )
        {
            var filters = DataTable.ext.search;
            var displayRows = settings.aiDisplay;
            var row, rowIdx;
        
            for ( var i=0, ien=filters.length ; i<ien ; i++ ) {
                var rows = [];
        
                // Loop over each row and see if it should be included
                for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) {
                    rowIdx = displayRows[ j ];
                    row = settings.aoData[ rowIdx ];
        
                    if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) {
                        rows.push( rowIdx );
                    }
                }
        
                // So the array reference doesn't break set the results into the
                // existing array
                displayRows.length = 0;
                displayRows.push.apply(displayRows, rows);
            }
        }
        
        
        /**
         * Filter the data table based on user input and draw the table
         */
        function _fnFilter( searchRows, settings, input, options, column )
        {
            if ( input === '' ) {
                return;
            }
        
            var i = 0;
            var matched = [];
        
            // Search term can be a function, regex or string - if a string we apply our
            // smart filtering regex (assuming the options require that)
            var searchFunc = typeof input === 'function' ? input : null;
            var rpSearch = input instanceof RegExp
                ? input
                : searchFunc
                    ? null
                    : _fnFilterCreateSearch( input, options );
        
            // Then for each row, does the test pass. If not, lop the row from the array
            for (i=0 ; i<searchRows.length ; i++) {
                var row = settings.aoData[ searchRows[i] ];
                var data = column === undefined
                    ? row._sFilterRow
                    : row._aFilterData[ column ];
        
                if ( (searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data)) ) {
                    matched.push(searchRows[i]);
                }
            }
        
            // Mutate the searchRows array
            searchRows.length = matched.length;
        
            for (i=0 ; i<matched.length ; i++) {
                searchRows[i] = matched[i];
            }
        }
        
        
        /**
         * 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( search, inOpts )
        {
            var not = [];
            var options = $.extend({}, {
                boundary: false,
                caseInsensitive: true,
                exact: false,
                regex: false,
                smart: true
            }, inOpts);
        
            if (typeof search !== 'string') {
                search = search.toString();
            }
        
            // Remove diacritics if normalize is set up to do so
            search = _normalize(search);
        
            if (options.exact) {
                return new RegExp(
                    '^'+_fnEscapeRegex(search)+'$',
                    options.caseInsensitive ? 'i' : ''
                );
            }
        
            search = options.regex ?
                search :
                _fnEscapeRegex( search );
            
            if ( options.smart ) {
                /* For smart filtering we want to allow the search to work regardless of
                 * word order. We also want double quoted text to be preserved, so word
                 * order is important - a la google. And a negative look around for
                 * finding rows which don't contain a given string.
                 * 
                 * So this is the sort of thing we want to generate:
                 * 
                 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
                 */
                var parts = search.match( /!?["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || [''];
                var a = parts.map( function ( word ) {
                    var negative = false;
                    var m;
        
                    // Determine if it is a "does not include"
                    if ( word.charAt(0) === '!' ) {
                        negative = true;
                        word = word.substring(1);
                    }
        
                    // Strip the quotes from around matched phrases
                    if ( word.charAt(0) === '"' ) {
                        m = word.match( /^"(.*)"$/ );
                        word = m ? m[1] : word;
                    }
                    else if ( word.charAt(0) === '\u201C' ) {
                        // Smart quote match (iPhone users)
                        m = word.match( /^\u201C(.*)\u201D$/ );
                        word = m ? m[1] : word;
                    }
        
                    // For our "not" case, we need to modify the string that is
                    // allowed to match at the end of the expression.
                    if (negative) {
                        if (word.length > 1) {
                            not.push('(?!'+word+')');
                        }
        
                        word = '';
                    }
        
                    return word.replace(/"/g, '');
                } );
        
                var match = not.length
                    ? not.join('')
                    : '';
        
                var boundary = options.boundary
                    ? '\\b'
                    : '';
        
                search = '^(?=.*?'+boundary+a.join( ')(?=.*?'+boundary )+')('+match+'.)*$';
            }
        
            return new RegExp( search, options.caseInsensitive ? 'i' : '' );
        }
        
        
        /**
         * Escape 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
         */
        var _fnEscapeRegex = DataTable.util.escapeRegex;
        
        var __filter_div = $('<div>')[0];
        var __filter_div_textContent = __filter_div.textContent !== undefined;
        
        // Update the filtering data for each row if needed (by invalidation or first run)
        function _fnFilterData ( settings )
        {
            var columns = settings.aoColumns;
            var data = settings.aoData;
            var column;
            var j, jen, filterData, cellData, row;
            var wasInvalidated = false;
        
            for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) {
                if (! data[rowIdx]) {
                    continue;
                }
        
                row = data[rowIdx];
        
                if ( ! row._aFilterData ) {
                    filterData = [];
        
                    for ( j=0, jen=columns.length ; j<jen ; j++ ) {
                        column = columns[j];
        
                        if ( column.bSearchable ) {
                            cellData = _fnGetCellData( settings, rowIdx, j, 'filter' );
        
                            // Search in DataTables is string based
                            if ( cellData === null ) {
                                cellData = '';
                            }
        
                            if ( typeof cellData !== 'string' && cellData.toString ) {
                                cellData = cellData.toString();
                            }
                        }
                        else {
                            cellData = '';
                        }
        
                        // If it looks like there is an HTML entity in the string,
                        // attempt to decode it so sorting works as expected. Note that
                        // we could use a single line of jQuery to do this, but the DOM
                        // method used here is much faster https://jsperf.com/html-decode
                        if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
                            __filter_div.innerHTML = cellData;
                            cellData = __filter_div_textContent ?
                                __filter_div.textContent :
                                __filter_div.innerText;
                        }
        
                        if ( cellData.replace ) {
                            cellData = cellData.replace(/[\r\n\u2028]/g, '');
                        }
        
                        filterData.push( cellData );
                    }
        
                    row._aFilterData = filterData;
                    row._sFilterRow = filterData.join('  ');
                    wasInvalidated = true;
                }
            }
        
            return wasInvalidated;
        }
        
        
        /**
         * Draw the table for the first time, adding all required features
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnInitialise ( settings )
        {
            var i;
            var init = settings.oInit;
            var deferLoading = settings.deferLoading;
            var dataSrc = _fnDataSource( settings );
        
            // Ensure that the table data is fully initialised
            if ( ! settings.bInitialised ) {
                setTimeout( function(){ _fnInitialise( settings ); }, 200 );
                return;
            }
        
            // Build the header / footer for the table
            _fnBuildHead( settings, 'header' );
            _fnBuildHead( settings, 'footer' );
        
            // Load the table's state (if needed) and then render around it and draw
            _fnLoadState( settings, init, function () {
                // Then draw the header / footer
                _fnDrawHead( settings, settings.aoHeader );
                _fnDrawHead( settings, settings.aoFooter );
        
                // Cache the paging start point, as the first redraw will reset it
                var iAjaxStart = settings.iInitDisplayStart
        
                // Local data load
                // Check if there is data passing into the constructor
                if ( init.aaData ) {
                    for ( i=0 ; i<init.aaData.length ; i++ ) {
                        _fnAddData( settings, init.aaData[ i ] );
                    }
                }
                else if ( deferLoading || dataSrc == 'dom' ) {
                    // Grab the data from the page
                    _fnAddTr( settings, $(settings.nTBody).children('tr') );
                }
        
                // Filter not yet applied - copy the display master
                settings.aiDisplay = settings.aiDisplayMaster.slice();
        
                // Enable features
                _fnAddOptionsHtml( settings );
                _fnSortInit( settings );
        
                _colGroup( settings );
        
                /* Okay to show that something is going on now */
                _fnProcessingDisplay( settings, true );
        
                _fnCallbackFire( settings, null, 'preInit', [settings], true );
        
                // If there is default sorting required - let's do it. The sort function
                // will do the drawing for us. Otherwise we draw the table regardless of the
                // Ajax source - this allows the table to look initialised for Ajax sourcing
                // data (show 'loading' message possibly)
                _fnReDraw( settings );
        
                // Server-side processing init complete is done by _fnAjaxUpdateDraw
                if ( dataSrc != 'ssp' || deferLoading ) {
                    // if there is an ajax source load the data
                    if ( dataSrc == 'ajax' ) {
                        _fnBuildAjax( settings, {}, function(json) {
                            var aData = _fnAjaxDataSrc( settings, json );
        
                            // Got the data - add it to the table
                            for ( i=0 ; i<aData.length ; i++ ) {
                                _fnAddData( settings, aData[i] );
                            }
        
                            // Reset the init display for cookie saving. We've already done
                            // a filter, and therefore cleared it before. So we need to make
                            // it appear 'fresh'
                            settings.iInitDisplayStart = iAjaxStart;
        
                            _fnReDraw( settings );
                            _fnProcessingDisplay( settings, false );
                            _fnInitComplete( settings );
                        }, settings );
                    }
                    else {
                        _fnInitComplete( settings );
                        _fnProcessingDisplay( settings, false );
                    }
                }
            } );
        }
        
        
        /**
         * Draw the table for the first time, adding all required features
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnInitComplete ( settings )
        {
            if (settings._bInitComplete) {
                return;
            }
        
            var args = [settings, settings.json];
        
            settings._bInitComplete = true;
        
            // Table is fully set up and we have data, so calculate the
            // column widths
            _fnAdjustColumnSizing( settings );
        
            _fnCallbackFire( settings, null, 'plugin-init', args, true );
            _fnCallbackFire( settings, 'aoInitComplete', 'init', args, true );
        }
        
        function _fnLengthChange ( settings, val )
        {
            var len = parseInt( val, 10 );
            settings._iDisplayLength = len;
        
            _fnLengthOverflow( settings );
        
            // Fire length change event
            _fnCallbackFire( settings, null, 'length', [settings, len] );
        }
        
        /**
         * Alter the display settings to change the page
         *  @param {object} settings DataTables settings object
         *  @param {string|int} action Paging action to take: "first", "previous",
         *    "next" or "last" or page number to jump to (integer)
         *  @param [bool] redraw Automatically draw the update or not
         *  @returns {bool} true page has changed, false - no change
         *  @memberof DataTable#oApi
         */
        function _fnPageChange ( settings, action, redraw )
        {
            var
                start     = settings._iDisplayStart,
                len       = settings._iDisplayLength,
                records   = settings.fnRecordsDisplay();
        
            if ( records === 0 || len === -1 )
            {
                start = 0;
            }
            else if ( typeof action === "number" )
            {
                start = action * len;
        
                if ( start > records )
                {
                    start = 0;
                }
            }
            else if ( action == "first" )
            {
                start = 0;
            }
            else if ( action == "previous" )
            {
                start = len >= 0 ?
                    start - len :
                    0;
        
                if ( start < 0 )
                {
                    start = 0;
                }
            }
            else if ( action == "next" )
            {
                if ( start + len < records )
                {
                    start += len;
                }
            }
            else if ( action == "last" )
            {
                start = Math.floor( (records-1) / len) * len;
            }
            else if ( action === 'ellipsis' )
            {
                return;
            }
            else
            {
                _fnLog( settings, 0, "Unknown paging action: "+action, 5 );
            }
        
            var changed = settings._iDisplayStart !== start;
            settings._iDisplayStart = start;
        
            _fnCallbackFire( settings, null, changed ? 'page' : 'page-nc', [settings] );
        
            if ( changed && redraw ) {
                _fnDraw( settings );
            }
        
            return changed;
        }
        
        
        /**
         * Generate the node required for the processing node
         *  @param {object} settings DataTables settings object
         */
        function _processingHtml ( settings )
        {
            var table = settings.nTable;
            var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== '';
        
            if ( settings.oFeatures.bProcessing ) {
                var n = $('<div/>', {
                        'id': settings.sTableId + '_processing',
                        'class': settings.oClasses.processing.container,
                        'role': 'status'
                    } )
                    .html( settings.oLanguage.sProcessing )
                    .append('<div><div></div><div></div><div></div><div></div></div>');
        
                // Different positioning depending on if scrolling is enabled or not
                if (scrolling) {
                    n.prependTo( $('div.dt-scroll', settings.nTableWrapper) );
                }
                else {
                    n.insertBefore( table );
                }
        
                $(table).on( 'processing.dt.DT', function (e, s, show) {
                    n.css( 'display', show ? 'block' : 'none' );
                } );
            }
        }
        
        
        /**
         * Display or hide the processing indicator
         *  @param {object} settings DataTables settings object
         *  @param {bool} show Show the processing indicator (true) or not (false)
         */
        function _fnProcessingDisplay ( settings, show )
        {
            // Ignore cases when we are still redrawing
            if (settings.bDrawing && show === false) {
                return;
            }
        
            _fnCallbackFire( settings, null, 'processing', [settings, show] );
        }
        
        /**
         * Show the processing element if an action takes longer than a given time
         *
         * @param {*} settings DataTables settings object
         * @param {*} enable Do (true) or not (false) async processing (local feature enablement)
         * @param {*} run Function to run
         */
        function _fnProcessingRun( settings, enable, run ) {
            if (! enable) {
                // Immediate execution, synchronous
                run();
            }
            else {
                _fnProcessingDisplay(settings, true);
                
                // Allow the processing display to show if needed
                setTimeout(function () {
                    run();
        
                    _fnProcessingDisplay(settings, false);
                }, 0);
            }
        }
        /**
         * Add any control elements for the table - specifically scrolling
         *  @param {object} settings dataTables settings object
         *  @returns {node} Node to add to the DOM
         *  @memberof DataTable#oApi
         */
        function _fnFeatureHtmlTable ( settings )
        {
            var table = $(settings.nTable);
        
            // Scrolling from here on in
            var scroll = settings.oScroll;
        
            if ( scroll.sX === '' && scroll.sY === '' ) {
                return settings.nTable;
            }
        
            var scrollX = scroll.sX;
            var scrollY = scroll.sY;
            var classes = settings.oClasses.scrolling;
            var caption = settings.captionNode;
            var captionSide = caption ? caption._captionSide : null;
            var headerClone = $( table[0].cloneNode(false) );
            var footerClone = $( table[0].cloneNode(false) );
            var footer = table.children('tfoot');
            var _div = '<div/>';
            var size = function ( s ) {
                return !s ? null : _fnStringToCss( s );
            };
        
            if ( ! footer.length ) {
                footer = null;
            }
        
            /*
             * The HTML structure that we want to generate in this function is:
             *  div - scroller
             *    div - scroll head
             *      div - scroll head inner
             *        table - scroll head table
             *          thead - thead
             *    div - scroll body
             *      table - table (master table)
             *        thead - thead clone for sizing
             *        tbody - tbody
             *    div - scroll foot
             *      div - scroll foot inner
             *        table - scroll foot table
             *          tfoot - tfoot
             */
            var scroller = $( _div, { 'class': classes.container } )
                .append(
                    $(_div, { 'class': classes.header.self } )
                        .css( {
                            overflow: 'hidden',
                            position: 'relative',
                            border: 0,
                            width: scrollX ? size(scrollX) : '100%'
                        } )
                        .append(
                            $(_div, { 'class': classes.header.inner } )
                                .css( {
                                    'box-sizing': 'content-box',
                                    width: scroll.sXInner || '100%'
                                } )
                                .append(
                                    headerClone
                                        .removeAttr('id')
                                        .css( 'margin-left', 0 )
                                        .append( captionSide === 'top' ? caption : null )
                                        .append(
                                            table.children('thead')
                                        )
                                )
                        )
                )
                .append(
                    $(_div, { 'class': classes.body } )
                        .css( {
                            position: 'relative',
                            overflow: 'auto',
                            width: size( scrollX )
                        } )
                        .append( table )
                );
        
            if ( footer ) {
                scroller.append(
                    $(_div, { 'class': classes.footer.self } )
                        .css( {
                            overflow: 'hidden',
                            border: 0,
                            width: scrollX ? size(scrollX) : '100%'
                        } )
                        .append(
                            $(_div, { 'class': classes.footer.inner } )
                                .append(
                                    footerClone
                                        .removeAttr('id')
                                        .css( 'margin-left', 0 )
                                        .append( captionSide === 'bottom' ? caption : null )
                                        .append(
                                            table.children('tfoot')
                                        )
                                )
                        )
                );
            }
        
            var children = scroller.children();
            var scrollHead = children[0];
            var scrollBody = children[1];
            var scrollFoot = footer ? children[2] : null;
        
            // When the body is scrolled, then we also want to scroll the headers
            $(scrollBody).on( 'scroll.DT', function () {
                var scrollLeft = this.scrollLeft;
        
                scrollHead.scrollLeft = scrollLeft;
        
                if ( footer ) {
                    scrollFoot.scrollLeft = scrollLeft;
                }
            } );
        
            // When focus is put on the header cells, we might need to scroll the body
            $('th, td', scrollHead).on('focus', function () {
                var scrollLeft = scrollHead.scrollLeft;
        
                scrollBody.scrollLeft = scrollLeft;
        
                if ( footer ) {
                    scrollBody.scrollLeft = scrollLeft;
                }
            });
        
            $(scrollBody).css('max-height', scrollY);
            if (! scroll.bCollapse) {
                $(scrollBody).css('height', scrollY);
            }
        
            settings.nScrollHead = scrollHead;
            settings.nScrollBody = scrollBody;
            settings.nScrollFoot = scrollFoot;
        
            // On redraw - align columns
            settings.aoDrawCallback.push(_fnScrollDraw);
        
            return scroller[0];
        }
        
        
        
        /**
         * Update the header, footer and body tables for resizing - i.e. column
         * alignment.
         *
         * Welcome to the most horrible function DataTables. The process that this
         * function follows is basically:
         *   1. Re-create the table inside the scrolling div
         *   2. Correct colgroup > col values if needed
         *   3. Copy colgroup > col over to header and footer
         *   4. Clean up
         *
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnScrollDraw ( settings )
        {
            // Given that this is such a monster function, a lot of variables are use
            // to try and keep the minimised size as small as possible
            var
                scroll         = settings.oScroll,
                barWidth       = scroll.iBarWidth,
                divHeader      = $(settings.nScrollHead),
                divHeaderInner = divHeader.children('div'),
                divHeaderTable = divHeaderInner.children('table'),
                divBodyEl      = settings.nScrollBody,
                divBody        = $(divBodyEl),
                divFooter      = $(settings.nScrollFoot),
                divFooterInner = divFooter.children('div'),
                divFooterTable = divFooterInner.children('table'),
                header         = $(settings.nTHead),
                table          = $(settings.nTable),
                footer         = settings.nTFoot && $('th, td', settings.nTFoot).length ? $(settings.nTFoot) : null,
                browser        = settings.oBrowser,
                headerCopy, footerCopy;
        
            // If the scrollbar visibility has changed from the last draw, we need to
            // adjust the column sizes as the table width will have changed to account
            // for the scrollbar
            var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight;
            
            if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) {
                settings.scrollBarVis = scrollBarVis;
                _fnAdjustColumnSizing( settings );
                return; // adjust column sizing will call this function again
            }
            else {
                settings.scrollBarVis = scrollBarVis;
            }
        
            // 1. Re-create the table inside the scrolling div
            // Remove the old minimised thead and tfoot elements in the inner table
            table.children('thead, tfoot').remove();
        
            // Clone the current header and footer elements and then place it into the inner table
            headerCopy = header.clone().prependTo( table );
            headerCopy.find('th, td').removeAttr('tabindex');
            headerCopy.find('[id]').removeAttr('id');
        
            if ( footer ) {
                footerCopy = footer.clone().prependTo( table );
                footerCopy.find('[id]').removeAttr('id');
            }
        
            // 2. Correct colgroup > col values if needed
            // It is possible that the cell sizes are smaller than the content, so we need to
            // correct colgroup>col for such cases. This can happen if the auto width detection
            // uses a cell which has a longer string, but isn't the widest! For example 
            // "Chief Executive Officer (CEO)" is the longest string in the demo, but
            // "Systems Administrator" is actually the widest string since it doesn't collapse.
            // Note the use of translating into a column index to get the `col` element. This
            // is because of Responsive which might remove `col` elements, knocking the alignment
            // of the indexes out.
            if (settings.aiDisplay.length) {
                // Get the column sizes from the first row in the table. This should really be a
                // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year
                // browser support
                var firstTr = null;
        
                for (i=0 ; i<settings.aiDisplay.length ; i++) {
                    var idx = settings.aiDisplay[i];
                    var tr = settings.aoData[idx].nTr;
        
                    if (tr) {
                        firstTr = tr;
                        break;
                    }
                }
        
                if (firstTr) {
                    var colSizes = $(firstTr).children('th, td').map(function (vis) {
                        return {
                            idx: _fnVisibleToColumnIndex(settings, vis),
                            width: $(this).outerWidth()
                        }
                    });
        
                    // Check against what the colgroup > col is set to and correct if needed
                    for (var i=0 ; i<colSizes.length ; i++) {
                        var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0];
                        var colWidth = colEl.style.width.replace('px', '');
        
                        if (colWidth !== colSizes[i].width) {
                            colEl.style.width = colSizes[i].width + 'px';
                        }
                    }
                }
            }
        
            // 3. Copy the colgroup over to the header and footer
            divHeaderTable
                .find('colgroup')
                .remove();
        
            divHeaderTable.append(settings.colgroup.clone());
        
            if ( footer ) {
                divFooterTable
                    .find('colgroup')
                    .remove();
        
                divFooterTable.append(settings.colgroup.clone());
            }
        
            // "Hide" the header and footer that we used for the sizing. We need to keep
            // the content of the cell so that the width applied to the header and body
            // both match, but we want to hide it completely.
            $('th, td', headerCopy).each(function () {
                $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
            });
        
            if ( footer ) {
                $('th, td', footerCopy).each(function () {
                    $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
                });
            }
        
            // 4. Clean up
            // Figure out if there are scrollbar present - if so then we need a the header and footer to
            // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
            var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
            var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
        
            // Set the width's of the header and footer tables
            var outerWidth = table.outerWidth();
        
            divHeaderTable.css('width', _fnStringToCss( outerWidth ));
            divHeaderInner
                .css('width', _fnStringToCss( outerWidth ))
                .css(paddingSide, isScrolling ? barWidth+"px" : "0px");
        
            if ( footer ) {
                divFooterTable.css('width', _fnStringToCss( outerWidth ));
                divFooterInner
                    .css('width', _fnStringToCss( outerWidth ))
                    .css(paddingSide, isScrolling ? barWidth+"px" : "0px");
            }
        
            // Correct DOM ordering for colgroup - comes before the thead
            table.children('colgroup').prependTo(table);
        
            // Adjust the position of the header in case we loose the y-scrollbar
            divBody.trigger('scroll');
        
            // If sorting or filtering has occurred, jump the scrolling back to the top
            // only if we aren't holding the position
            if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
                divBodyEl.scrollTop = 0;
            }
        }
        
        /**
         * Calculate the width of columns for the table
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnCalculateColumnWidths ( settings )
        {
            // Not interested in doing column width calculation if auto-width is disabled
            if (! settings.oFeatures.bAutoWidth) {
                return;
            }
        
            var
                table = settings.nTable,
                columns = settings.aoColumns,
                scroll = settings.oScroll,
                scrollY = scroll.sY,
                scrollX = scroll.sX,
                scrollXInner = scroll.sXInner,
                visibleColumns = _fnGetColumns( settings, 'bVisible' ),
                tableWidthAttr = table.getAttribute('width'), // from DOM element
                tableContainer = table.parentNode,
                i, column, columnIdx;
                
            var styleWidth = table.style.width;
        
            // If there is no width applied as a CSS style or as an attribute, we assume that
            // the width is intended to be 100%, which is usually is in CSS, but it is very
            // difficult to correctly parse the rules to get the final result.
            if ( ! styleWidth && ! tableWidthAttr) {
                table.style.width = '100%';
                styleWidth = '100%';
            }
        
            if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
                tableWidthAttr = styleWidth;
            }
        
            // Let plug-ins know that we are doing a recalc, in case they have changed any of the
            // visible columns their own way (e.g. Responsive uses display:none).
            _fnCallbackFire(
                settings,
                null,
                'column-calc',
                {visible: visibleColumns},
                false
            );
        
            // Construct a single row, worst case, table with the widest
            // node in the data, assign any user defined widths, then insert it into
            // the DOM and allow the browser to do all the hard work of calculating
            // table widths
            var tmpTable = $(table.cloneNode())
                .css( 'visibility', 'hidden' )
                .removeAttr( 'id' );
        
            // Clean up the table body
            tmpTable.append('<tbody>')
            var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
        
            // Clone the table header and footer - we can't use the header / footer
            // from the cloned table, since if scrolling is active, the table's
            // real header and footer are contained in different table tags
            tmpTable
                .append( $(settings.nTHead).clone() )
                .append( $(settings.nTFoot).clone() );
        
            // Remove any assigned widths from the footer (from scrolling)
            tmpTable.find('tfoot th, tfoot td').css('width', '');
        
            // Apply custom sizing to the cloned header
            tmpTable.find('thead th, thead td').each( function () {
                // Get the `width` from the header layout
                var width = _fnColumnsSumWidth( settings, this, true, false );
        
                if ( width ) {
                    // Need to set the width and min-width, otherwise the browser
                    // will attempt to collapse the table beyond want might have
                    // been specified
                    this.style.width = width;
                    this.style.minWidth = width;
        
                    // For scrollX we need to force the column width otherwise the
                    // browser will collapse it. If this width is smaller than the
                    // width the column requires, then it will have no effect
                    if ( scrollX ) {
                        $( this ).append( $('<div/>').css( {
                            width: width,
                            margin: 0,
                            padding: 0,
                            border: 0,
                            height: 1
                        } ) );
                    }
                }
                else {
                    this.style.width = '';
                }
            } );
        
            // Find the widest piece of data for each column and put it into the table
            for ( i=0 ; i<visibleColumns.length ; i++ ) {
                columnIdx = visibleColumns[i];
                column = columns[ columnIdx ];
        
                var longest = _fnGetMaxLenString(settings, columnIdx);
                var autoClass = _ext.type.className[column.sType];
                var text = longest + column.sContentPadding;
                var insert = longest.indexOf('<') === -1
                    ? document.createTextNode(text)
                    : text
                
                $('<td/>')
                    .addClass(autoClass)
                    .addClass(column.sClass)
                    .append(insert)
                    .appendTo(tr);
            }
        
            // Tidy the temporary table - remove name attributes so there aren't
            // duplicated in the dom (radio elements for example)
            $('[name]', tmpTable).removeAttr('name');
        
            // Table has been built, attach to the document so we can work with it.
            // A holding element is used, positioned at the top of the container
            // with minimal height, so it has no effect on if the container scrolls
            // or not. Otherwise it might trigger scrolling when it actually isn't
            // needed
            var holder = $('<div/>').css( scrollX || scrollY ?
                    {
                        position: 'absolute',
                        top: 0,
                        left: 0,
                        height: 1,
                        right: 0,
                        overflow: 'hidden'
                    } :
                    {}
                )
                .append( tmpTable )
                .appendTo( tableContainer );
        
            // When scrolling (X or Y) we want to set the width of the table as 
            // appropriate. However, when not scrolling leave the table width as it
            // is. This results in slightly different, but I think correct behaviour
            if ( scrollX && scrollXInner ) {
                tmpTable.width( scrollXInner );
            }
            else if ( scrollX ) {
                tmpTable.css( 'width', 'auto' );
                tmpTable.removeAttr('width');
        
                // If there is no width attribute or style, then allow the table to
                // collapse
                if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
                    tmpTable.width( tableContainer.clientWidth );
                }
            }
            else if ( scrollY ) {
                tmpTable.width( tableContainer.clientWidth );
            }
            else if ( tableWidthAttr ) {
                tmpTable.width( tableWidthAttr );
            }
        
            // Get the width of each column in the constructed table
            var total = 0;
            var bodyCells = tmpTable.find('tbody tr').eq(0).children();
        
            for ( i=0 ; i<visibleColumns.length ; i++ ) {
                // Use getBounding for sub-pixel accuracy, which we then want to round up!
                var bounding = bodyCells[i].getBoundingClientRect().width;
        
                // Total is tracked to remove any sub-pixel errors as the outerWidth
                // of the table might not equal the total given here
                total += bounding;
        
                // Width for each column to use
                columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding );
            }
        
            table.style.width = _fnStringToCss( total );
        
            // Finished with the table - ditch it
            holder.remove();
        
            // If there is a width attr, we want to attach an event listener which
            // allows the table sizing to automatically adjust when the window is
            // resized. Use the width attr rather than CSS, since we can't know if the
            // CSS is a relative value or absolute - DOM read is always px.
            if ( tableWidthAttr ) {
                table.style.width = _fnStringToCss( tableWidthAttr );
            }
        
            if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) {
                var bindResize = function () {
                    $(window).on('resize.DT-'+settings.sInstance, DataTable.util.throttle( function () {
                        if (! settings.bDestroying) {
                            _fnAdjustColumnSizing( settings );
                        }
                    } ) );
                };
        
                bindResize();
        
                settings._reszEvt = true;
            }
        }
        
        
        /**
         * Get the maximum strlen for each data column
         *  @param {object} settings dataTables settings object
         *  @param {int} colIdx column of interest
         *  @returns {string} string of the max length
         *  @memberof DataTable#oApi
         */
        function _fnGetMaxLenString( settings, colIdx )
        {
            var column = settings.aoColumns[colIdx];
        
            if (! column.maxLenString) {
                var s, max='', maxLen = -1;
            
                for ( var i=0, ien=settings.aiDisplayMaster.length ; i<ien ; i++ ) {
                    var rowIdx = settings.aiDisplayMaster[i];
                    var data = _fnGetRowDisplay(settings, rowIdx)[colIdx];
        
                    var cellString = data && typeof data === 'object' && data.nodeType
                        ? data.innerHTML
                        : data+'';
        
                    // Remove id / name attributes from elements so they
                    // don't interfere with existing elements
                    cellString = cellString
                        .replace(/id=".*?"/g, '')
                        .replace(/name=".*?"/g, '');
        
                    s = _stripHtml(cellString)
                        .replace( /&nbsp;/g, ' ' );
            
                    if ( s.length > maxLen ) {
                        // We want the HTML in the string, but the length that
                        // is important is the stripped string
                        max = cellString;
                        maxLen = s.length;
                    }
                }
        
                column.maxLenString = max;
            }
        
            return column.maxLenString;
        }
        
        
        /**
         * Append a CSS unit (only if required) to a string
         *  @param {string} value to css-ify
         *  @returns {string} value with css unit
         *  @memberof DataTable#oApi
         */
        function _fnStringToCss( s )
        {
            if ( s === null ) {
                return '0px';
            }
        
            if ( typeof s == 'number' ) {
                return s < 0 ?
                    '0px' :
                    s+'px';
            }
        
            // Check it has a unit character already
            return s.match(/\d$/) ?
                s+'px' :
                s;
        }
        
        /**
         * Re-insert the `col` elements for current visibility
         *
         * @param {*} settings DT settings
         */
        function _colGroup( settings ) {
            var cols = settings.aoColumns;
        
            settings.colgroup.empty();
        
            for (i=0 ; i<cols.length ; i++) {
                if (cols[i].bVisible) {
                    settings.colgroup.append(cols[i].colEl);
                }
            }
        }
        
        
        function _fnSortInit( settings ) {
            var target = settings.nTHead;
            var headerRows = target.querySelectorAll('tr');
            var legacyTop = settings.bSortCellsTop;
            var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])';
            
            // Legacy support for `orderCellsTop`
            if (legacyTop === true) {
                target = headerRows[0];
            }
            else if (legacyTop === false) {
                target = headerRows[ headerRows.length - 1 ];
            }
        
            _fnSortAttachListener(
                settings,
                target,
                target === settings.nTHead
                    ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector
                    : 'th'+notSelector+', td'+notSelector
            );
        
            // Need to resolve the user input array into our internal structure
            var order = [];
            _fnSortResolve( settings, order, settings.aaSorting );
        
            settings.aaSorting = order;
        }
        
        
        function _fnSortAttachListener(settings, node, selector, column, callback) {
            _fnBindAction( node, selector, function (e) {
                var run = false;
                var columns = column === undefined
                    ? _fnColumnsFromHeader( e.target )
                    : [column];
        
                if ( columns.length ) {
                    for ( var i=0, ien=columns.length ; i<ien ; i++ ) {
                        var ret = _fnSortAdd( settings, columns[i], i, e.shiftKey );
        
                        if (ret !== false) {
                            run = true;
                        }					
        
                        // If the first entry is no sort, then subsequent
                        // sort columns are ignored
                        if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === '') {
                            break;
                        }
                    }
        
                    if (run) {
                        _fnProcessingRun(settings, true, function () {
                            _fnSort( settings );
                            _fnSortDisplay( settings, settings.aiDisplay );
        
                            _fnReDraw( settings, false, false );
        
                            if (callback) {
                                callback();
                            }
                        });
                    }
                }
            } );
        }
        
        /**
         * Sort the display array to match the master's order
         * @param {*} settings
         */
        function _fnSortDisplay(settings, display) {
            if (display.length < 2) {
                return;
            }
        
            var master = settings.aiDisplayMaster;
            var masterMap = {};
            var map = {};
            var i;
        
            // Rather than needing an `indexOf` on master array, we can create a map
            for (i=0 ; i<master.length ; i++) {
                masterMap[master[i]] = i;
            }
        
            // And then cache what would be the indexOf fom the display
            for (i=0 ; i<display.length ; i++) {
                map[display[i]] = masterMap[display[i]];
            }
        
            display.sort(function(a, b){
                // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);`
                return map[a] - map[b];
            });
        }
        
        
        function _fnSortResolve (settings, nestedSort, sort) {
            var push = function ( a ) {
                if ($.isPlainObject(a)) {
                    if (a.idx !== undefined) {
                        // Index based ordering
                        nestedSort.push([a.idx, a.dir]);
                    }
                    else if (a.name) {
                        // Name based ordering
                        var cols = _pluck( settings.aoColumns, 'sName');
                        var idx = cols.indexOf(a.name);
        
                        if (idx !== -1) {
                            nestedSort.push([idx, a.dir]);
                        }
                    }
                }
                else {
                    // Plain column index and direction pair
                    nestedSort.push(a);
                }
            };
        
            if ( $.isPlainObject(sort) ) {
                // Object
                push(sort);
            }
            else if ( sort.length && typeof sort[0] === 'number' ) {
                // 1D array
                push(sort);
            }
            else if ( sort.length ) {
                // 2D array
                for (var z=0; z<sort.length; z++) {
                    push(sort[z]); // Object or array
                }
            }
        }
        
        
        function _fnSortFlatten ( settings )
        {
            var
                i, k, kLen,
                aSort = [],
                extSort = DataTable.ext.type.order,
                aoColumns = settings.aoColumns,
                aDataSort, iCol, sType, srcCol,
                fixed = settings.aaSortingFixed,
                fixedObj = $.isPlainObject( fixed ),
                nestedSort = [];
            
            if ( ! settings.oFeatures.bSort ) {
                return aSort;
            }
        
            // Build the sort array, with pre-fix and post-fix options if they have been
            // specified
            if ( Array.isArray( fixed ) ) {
                _fnSortResolve( settings, nestedSort, fixed );
            }
        
            if ( fixedObj && fixed.pre ) {
                _fnSortResolve( settings, nestedSort, fixed.pre );
            }
        
            _fnSortResolve( settings, nestedSort, settings.aaSorting );
        
            if (fixedObj && fixed.post ) {
                _fnSortResolve( settings, nestedSort, fixed.post );
            }
        
            for ( i=0 ; i<nestedSort.length ; i++ )
            {
                srcCol = nestedSort[i][0];
        
                if ( aoColumns[ srcCol ] ) {
                    aDataSort = aoColumns[ srcCol ].aDataSort;
        
                    for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
                    {
                        iCol = aDataSort[k];
                        sType = aoColumns[ iCol ].sType || 'string';
        
                        if ( nestedSort[i]._idx === undefined ) {
                            nestedSort[i]._idx = aoColumns[iCol].asSorting.indexOf(nestedSort[i][1]);
                        }
        
                        if ( nestedSort[i][1] ) {
                            aSort.push( {
                                src:       srcCol,
                                col:       iCol,
                                dir:       nestedSort[i][1],
                                index:     nestedSort[i]._idx,
                                type:      sType,
                                formatter: extSort[ sType+"-pre" ],
                                sorter:    extSort[ sType+"-"+nestedSort[i][1] ]
                            } );
                        }
                    }
                }
            }
        
            return aSort;
        }
        
        /**
         * Change the order of the table
         *  @param {object} oSettings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnSort ( oSettings, col, dir )
        {
            var
                i, ien, iLen,
                aiOrig = [],
                extSort = DataTable.ext.type.order,
                aoData = oSettings.aoData,
                sortCol,
                displayMaster = oSettings.aiDisplayMaster,
                aSort;
        
            // Allow a specific column to be sorted, which will _not_ alter the display
            // master
            if (col !== undefined) {
                var srcCol = oSettings.aoColumns[col];
                aSort = [{
                    src:       col,
                    col:       col,
                    dir:       dir,
                    index:     0,
                    type:      srcCol.sType,
                    formatter: extSort[ srcCol.sType+"-pre" ],
                    sorter:    extSort[ srcCol.sType+"-"+dir ]
                }];
                displayMaster = displayMaster.slice();
            }
            else {
                aSort = _fnSortFlatten( oSettings );
            }
        
            for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
                sortCol = aSort[i];
        
                // Load the data needed for the sort, for each cell
                _fnSortData( oSettings, sortCol.col );
            }
        
            /* No sorting required if server-side or no sorting array */
            if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
            {
                // Reset the initial positions on each pass so we get a stable sort
                for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
                    aiOrig[ i ] = i;
                }
        
                // If the first sort is desc, then reverse the array to preserve original
                // order, just in reverse
                if (aSort.length && aSort[0].dir === 'desc' && oSettings.orderDescReverse) {
                    aiOrig.reverse();
                }
        
                /* Do the sort - here we want multi-column sorting based on a given data source (column)
                 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
                 * follow on it's own, but this is what we want (example two column sorting):
                 *  fnLocalSorting = function(a,b){
                 *    var test;
                 *    test = oSort['string-asc']('data11', 'data12');
                 *      if (test !== 0)
                 *        return test;
                 *    test = oSort['numeric-desc']('data21', 'data22');
                 *    if (test !== 0)
                 *      return test;
                 *    return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
                 *  }
                 * Basically we have a test for each sorting column, if the data in that column is equal,
                 * test the next column. If all columns match, then we use a numeric sort on the row
                 * positions in the original data array to provide a stable sort.
                 */
                displayMaster.sort( function ( a, b ) {
                    var
                        x, y, k, test, sort,
                        len=aSort.length,
                        dataA = aoData[a]._aSortData,
                        dataB = aoData[b]._aSortData;
        
                    for ( k=0 ; k<len ; k++ ) {
                        sort = aSort[k];
        
                        // Data, which may have already been through a `-pre` function
                        x = dataA[ sort.col ];
                        y = dataB[ sort.col ];
        
                        if (sort.sorter) {
                            // If there is a custom sorter (`-asc` or `-desc`) for this
                            // data type, use it
                            test = sort.sorter(x, y);
        
                            if ( test !== 0 ) {
                                return test;
                            }
                        }
                        else {
                            // Otherwise, use generic sorting
                            test = x<y ? -1 : x>y ? 1 : 0;
        
                            if ( test !== 0 ) {
                                return sort.dir === 'asc' ? test : -test;
                            }
                        }
                    }
        
                    x = aiOrig[a];
                    y = aiOrig[b];
        
                    return x<y ? -1 : x>y ? 1 : 0;
                } );
            }
            else if ( aSort.length === 0 ) {
                // Apply index order
                displayMaster.sort(function (x, y) {
                    return x<y ? -1 : x>y ? 1 : 0;
                });
            }
        
            if (col === undefined) {
                // Tell the draw function that we have sorted the data
                oSettings.bSorted = true;
                oSettings.sortDetails = aSort;
        
                _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] );
            }
        
            return displayMaster;
        }
        
        
        /**
         * Function to run on user sort request
         *  @param {object} settings dataTables settings object
         *  @param {node} attachTo node to attach the handler to
         *  @param {int} colIdx column sorting index
         *  @param {int} addIndex Counter
         *  @param {boolean} [shift=false] Shift click add
         *  @param {function} [callback] callback function
         *  @memberof DataTable#oApi
         */
        function _fnSortAdd ( settings, colIdx, addIndex, shift )
        {
            var col = settings.aoColumns[ colIdx ];
            var sorting = settings.aaSorting;
            var asSorting = col.asSorting;
            var nextSortIdx;
            var next = function ( a, overflow ) {
                var idx = a._idx;
                if ( idx === undefined ) {
                    idx = asSorting.indexOf(a[1]);
                }
        
                return idx+1 < asSorting.length ?
                    idx+1 :
                    overflow ?
                        null :
                        0;
            };
        
            if ( ! col.bSortable ) {
                return false;
            }
        
            // Convert to 2D array if needed
            if ( typeof sorting[0] === 'number' ) {
                sorting = settings.aaSorting = [ sorting ];
            }
        
            // If appending the sort then we are multi-column sorting
            if ( (shift || addIndex) && settings.oFeatures.bSortMulti ) {
                // Are we already doing some kind of sort on this column?
                var sortIdx = _pluck(sorting, '0').indexOf(colIdx);
        
                if ( sortIdx !== -1 ) {
                    // Yes, modify the sort
                    nextSortIdx = next( sorting[sortIdx], true );
        
                    if ( nextSortIdx === null && sorting.length === 1 ) {
                        nextSortIdx = 0; // can't remove sorting completely
                    }
        
                    if ( nextSortIdx === null ) {
                        sorting.splice( sortIdx, 1 );
                    }
                    else {
                        sorting[sortIdx][1] = asSorting[ nextSortIdx ];
                        sorting[sortIdx]._idx = nextSortIdx;
                    }
                }
                else if (shift) {
                    // No sort on this column yet, being added by shift click
                    // add it as itself
                    sorting.push( [ colIdx, asSorting[0], 0 ] );
                    sorting[sorting.length-1]._idx = 0;
                }
                else {
                    // No sort on this column yet, being added from a colspan
                    // so add with same direction as first column
                    sorting.push( [ colIdx, sorting[0][1], 0 ] );
                    sorting[sorting.length-1]._idx = 0;
                }
            }
            else if ( sorting.length && sorting[0][0] == colIdx ) {
                // Single column - already sorting on this column, modify the sort
                nextSortIdx = next( sorting[0] );
        
                sorting.length = 1;
                sorting[0][1] = asSorting[ nextSortIdx ];
                sorting[0]._idx = nextSortIdx;
            }
            else {
                // Single column - sort only on this column
                sorting.length = 0;
                sorting.push( [ colIdx, asSorting[0] ] );
                sorting[0]._idx = 0;
            }
        }
        
        
        /**
         * Set the sorting classes on table's body, Note: it is safe to call this function
         * when bSort and bSortClasses are false
         *  @param {object} oSettings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnSortingClasses( settings )
        {
            var oldSort = settings.aLastSort;
            var sortClass = settings.oClasses.order.position;
            var sort = _fnSortFlatten( settings );
            var features = settings.oFeatures;
            var i, ien, colIdx;
        
            if ( features.bSort && features.bSortClasses ) {
                // Remove old sorting classes
                for ( i=0, ien=oldSort.length ; i<ien ; i++ ) {
                    colIdx = oldSort[i].src;
        
                    // Remove column sorting
                    $( _pluck( settings.aoData, 'anCells', colIdx ) )
                        .removeClass( sortClass + (i<2 ? i+1 : 3) );
                }
        
                // Add new column sorting
                for ( i=0, ien=sort.length ; i<ien ; i++ ) {
                    colIdx = sort[i].src;
        
                    $( _pluck( settings.aoData, 'anCells', colIdx ) )
                        .addClass( sortClass + (i<2 ? i+1 : 3) );
                }
            }
        
            settings.aLastSort = sort;
        }
        
        
        // Get the data to sort a column, be it from cache, fresh (populating the
        // cache), or from a sort formatter
        function _fnSortData( settings, colIdx )
        {
            // Custom sorting function - provided by the sort data type
            var column = settings.aoColumns[ colIdx ];
            var customSort = DataTable.ext.order[ column.sSortDataType ];
            var customData;
        
            if ( customSort ) {
                customData = customSort.call( settings.oInstance, settings, colIdx,
                    _fnColumnIndexToVisible( settings, colIdx )
                );
            }
        
            // Use / populate cache
            var row, cellData;
            var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
            var data = settings.aoData;
        
            for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) {
                // Sparse array
                if (! data[rowIdx]) {
                    continue;
                }
        
                row = data[rowIdx];
        
                if ( ! row._aSortData ) {
                    row._aSortData = [];
                }
        
                if ( ! row._aSortData[colIdx] || customSort ) {
                    cellData = customSort ?
                        customData[rowIdx] : // If there was a custom sort function, use data from there
                        _fnGetCellData( settings, rowIdx, colIdx, 'sort' );
        
                    row._aSortData[ colIdx ] = formatter ?
                        formatter( cellData, settings ) :
                        cellData;
                }
            }
        }
        
        
        /**
         * State information for a table
         *
         * @param {*} settings
         * @returns State object
         */
        function _fnSaveState ( settings )
        {
            if (settings._bLoadingState) {
                return;
            }
        
            /* Store the interesting variables */
            var state = {
                time:    +new Date(),
                start:   settings._iDisplayStart,
                length:  settings._iDisplayLength,
                order:   $.extend( true, [], settings.aaSorting ),
                search:  $.extend({}, settings.oPreviousSearch),
                columns: settings.aoColumns.map( function ( col, i ) {
                    return {
                        visible: col.bVisible,
                        search: $.extend({}, settings.aoPreSearchCols[i])
                    };
                } )
            };
        
            settings.oSavedState = state;
            _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] );
            
            if ( settings.oFeatures.bStateSave && !settings.bDestroying )
            {
                settings.fnStateSaveCallback.call( settings.oInstance, settings, state );
            }	
        }
        
        
        /**
         * Attempt to load a saved table state
         *  @param {object} oSettings dataTables settings object
         *  @param {object} oInit DataTables init object so we can override settings
         *  @param {function} callback Callback to execute when the state has been loaded
         *  @memberof DataTable#oApi
         */
        function _fnLoadState ( settings, init, callback )
        {
            if ( ! settings.oFeatures.bStateSave ) {
                callback();
                return;
            }
        
            var loaded = function(state) {
                _fnImplementState(settings, state, callback);
            }
        
            var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded );
        
            if ( state !== undefined ) {
                _fnImplementState( settings, state, callback );
            }
            // otherwise, wait for the loaded callback to be executed
        
            return true;
        }
        
        function _fnImplementState ( settings, s, callback) {
            var i, ien;
            var columns = settings.aoColumns;
            settings._bLoadingState = true;
        
            // When StateRestore was introduced the state could now be implemented at any time
            // Not just initialisation. To do this an api instance is required in some places
            var api = settings._bInitComplete ? new DataTable.Api(settings) : null;
        
            if ( ! s || ! s.time ) {
                settings._bLoadingState = false;
                callback();
                return;
            }
        
            // Reject old data
            var duration = settings.iStateDuration;
            if ( duration > 0 && s.time < +new Date() - (duration*1000) ) {
                settings._bLoadingState = false;
                callback();
                return;
            }
        
            // Allow custom and plug-in manipulation functions to alter the saved data set and
            // cancelling of loading by returning false
            var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] );
            if ( abStateLoad.indexOf(false) !== -1 ) {
                settings._bLoadingState = false;
                callback();
                return;
            }
        
            // Number of columns have changed - all bets are off, no restore of settings
            if ( s.columns && columns.length !== s.columns.length ) {
                settings._bLoadingState = false;
                callback();
                return;
            }
        
            // Store the saved state so it might be accessed at any time
            settings.oLoadedState = $.extend( true, {}, s );
        
            // This is needed for ColReorder, which has to happen first to allow all
            // the stored indexes to be usable. It is not publicly documented.
            _fnCallbackFire( settings, null, 'stateLoadInit', [settings, s], true );
        
            // Page Length
            if ( s.length !== undefined ) {
                // If already initialised just set the value directly so that the select element is also updated
                if (api) {
                    api.page.len(s.length)
                }
                else {
                    settings._iDisplayLength   = s.length;
                }
            }
        
            // Restore key features
            if ( s.start !== undefined ) {
                if(api === null) {
                    settings._iDisplayStart    = s.start;
                    settings.iInitDisplayStart = s.start;
                }
                else {
                    _fnPageChange(settings, s.start/settings._iDisplayLength);
                }
            }
        
            // Order
            if ( s.order !== undefined ) {
                settings.aaSorting = [];
                $.each( s.order, function ( i, col ) {
                    settings.aaSorting.push( col[0] >= columns.length ?
                        [ 0, col[1] ] :
                        col
                    );
                } );
            }
        
            // Search
            if ( s.search !== undefined ) {
                $.extend( settings.oPreviousSearch, s.search );
            }
        
            // Columns
            if ( s.columns ) {
                for ( i=0, ien=s.columns.length ; i<ien ; i++ ) {
                    var col = s.columns[i];
        
                    // Visibility
                    if ( col.visible !== undefined ) {
                        // If the api is defined, the table has been initialised so we need to use it rather than internal settings
                        if (api) {
                            // Don't redraw the columns on every iteration of this loop, we will do this at the end instead
                            api.column(i).visible(col.visible, false);
                        }
                        else {
                            columns[i].bVisible = col.visible;
                        }
                    }
        
                    // Search
                    if ( col.search !== undefined ) {
                        $.extend( settings.aoPreSearchCols[i], col.search );
                    }
                }
                
                // If the api is defined then we need to adjust the columns once the visibility has been changed
                if (api) {
                    api.columns.adjust();
                }
            }
        
            settings._bLoadingState = false;
            _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] );
            callback();
        }
        
        /**
         * Log an error message
         *  @param {object} settings dataTables settings object
         *  @param {int} level log error messages, or display them to the user
         *  @param {string} msg error message
         *  @param {int} tn Technical note id to get more information about the error.
         *  @memberof DataTable#oApi
         */
        function _fnLog( settings, level, msg, tn )
        {
            msg = 'DataTables warning: '+
                (settings ? 'table id='+settings.sTableId+' - ' : '')+msg;
        
            if ( tn ) {
                msg += '. For more information about this error, please see '+
                'https://datatables.net/tn/'+tn;
            }
        
            if ( ! level  ) {
                // Backwards compatibility pre 1.10
                var ext = DataTable.ext;
                var type = ext.sErrMode || ext.errMode;
        
                if ( settings ) {
                    _fnCallbackFire( settings, null, 'dt-error', [ settings, tn, msg ], true );
                }
        
                if ( type == 'alert' ) {
                    alert( msg );
                }
                else if ( type == 'throw' ) {
                    throw new Error(msg);
                }
                else if ( typeof type == 'function' ) {
                    type( settings, tn, msg );
                }
            }
            else if ( window.console && console.log ) {
                console.log( msg );
            }
        }
        
        
        /**
         * See if a property is defined on one object, if so assign it to the other object
         *  @param {object} ret target object
         *  @param {object} src source object
         *  @param {string} name property
         *  @param {string} [mappedName] name to map too - optional, name used if not given
         *  @memberof DataTable#oApi
         */
        function _fnMap( ret, src, name, mappedName )
        {
            if ( Array.isArray( name ) ) {
                $.each( name, function (i, val) {
                    if ( Array.isArray( val ) ) {
                        _fnMap( ret, src, val[0], val[1] );
                    }
                    else {
                        _fnMap( ret, src, val );
                    }
                } );
        
                return;
            }
        
            if ( mappedName === undefined ) {
                mappedName = name;
            }
        
            if ( src[name] !== undefined ) {
                ret[mappedName] = src[name];
            }
        }
        
        
        /**
         * Extend objects - very similar to jQuery.extend, but deep copy objects, and
         * shallow copy arrays. The reason we need to do this, is that we don't want to
         * deep copy array init values (such as aaSorting) since the dev wouldn't be
         * able to override them, but we do want to deep copy arrays.
         *  @param {object} out Object to extend
         *  @param {object} extender Object from which the properties will be applied to
         *      out
         *  @param {boolean} breakRefs If true, then arrays will be sliced to take an
         *      independent copy with the exception of the `data` or `aaData` parameters
         *      if they are present. This is so you can pass in a collection to
         *      DataTables and have that used as your data source without breaking the
         *      references
         *  @returns {object} out Reference, just for convenience - out === the return.
         *  @memberof DataTable#oApi
         *  @todo This doesn't take account of arrays inside the deep copied objects.
         */
        function _fnExtend( out, extender, breakRefs )
        {
            var val;
        
            for ( var prop in extender ) {
                if ( Object.prototype.hasOwnProperty.call(extender, prop) ) {
                    val = extender[prop];
        
                    if ( $.isPlainObject( val ) ) {
                        if ( ! $.isPlainObject( out[prop] ) ) {
                            out[prop] = {};
                        }
                        $.extend( true, out[prop], val );
                    }
                    else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) {
                        out[prop] = val.slice();
                    }
                    else {
                        out[prop] = val;
                    }
                }
            }
        
            return out;
        }
        
        
        /**
         * Bind an event handers to allow a click or return key to activate the callback.
         * This is good for accessibility since a return on the keyboard will have the
         * same effect as a click, if the element has focus.
         *  @param {element} n Element to bind the action to
         *  @param {object|string} selector Selector (for delegated events) or data object
         *   to pass to the triggered function
         *  @param {function} fn Callback function for when the event is triggered
         *  @memberof DataTable#oApi
         */
        function _fnBindAction( n, selector, fn )
        {
            $(n)
                .on( 'click.DT', selector, function (e) {
                    fn(e);
                } )
                .on( 'keypress.DT', selector, function (e){
                    if ( e.which === 13 ) {
                        e.preventDefault();
                        fn(e);
                    }
                } )
                .on( 'selectstart.DT', selector, function () {
                    // Don't want a double click resulting in text selection
                    return false;
                } );
        }
        
        
        /**
         * Register a callback function. Easily allows a callback function to be added to
         * an array store of callback functions that can then all be called together.
         *  @param {object} settings dataTables settings object
         *  @param {string} store Name of the array storage for the callbacks in oSettings
         *  @param {function} fn Function to be called back
         *  @memberof DataTable#oApi
         */
        function _fnCallbackReg( settings, store, fn )
        {
            if ( fn ) {
                settings[store].push(fn);
            }
        }
        
        
        /**
         * Fire callback functions and trigger events. Note that the loop over the
         * callback array store is done backwards! Further note that you do not want to
         * fire off triggers in time sensitive applications (for example cell creation)
         * as its slow.
         *  @param {object} settings dataTables settings object
         *  @param {string} callbackArr Name of the array storage for the callbacks in
         *      oSettings
         *  @param {string} eventName Name of the jQuery custom event to trigger. If
         *      null no trigger is fired
         *  @param {array} args Array of arguments to pass to the callback function /
         *      trigger
         *  @param {boolean} [bubbles] True if the event should bubble
         *  @memberof DataTable#oApi
         */
        function _fnCallbackFire( settings, callbackArr, eventName, args, bubbles )
        {
            var ret = [];
        
            if ( callbackArr ) {
                ret = settings[callbackArr].slice().reverse().map( function (val) {
                    return val.apply( settings.oInstance, args );
                } );
            }
        
            if ( eventName !== null) {
                var e = $.Event( eventName+'.dt' );
                var table = $(settings.nTable);
                
                // Expose the DataTables API on the event object for easy access
                e.dt = settings.api;
        
                table[bubbles ?  'trigger' : 'triggerHandler']( e, args );
        
                // If not yet attached to the document, trigger the event
                // on the body directly to sort of simulate the bubble
                if (bubbles && table.parents('body').length === 0) {
                    $('body').trigger( e, args );
                }
        
                ret.push( e.result );
            }
        
            return ret;
        }
        
        
        function _fnLengthOverflow ( settings )
        {
            var
                start = settings._iDisplayStart,
                end = settings.fnDisplayEnd(),
                len = settings._iDisplayLength;
        
            /* If we have space to show extra rows (backing up from the end point - then do so */
            if ( start >= end )
            {
                start = end - len;
            }
        
            // Keep the start record on the current page
            start -= (start % len);
        
            if ( len === -1 || start < 0 )
            {
                start = 0;
            }
        
            settings._iDisplayStart = start;
        }
        
        
        function _fnRenderer( settings, type )
        {
            var renderer = settings.renderer;
            var host = DataTable.ext.renderer[type];
        
            if ( $.isPlainObject( renderer ) && renderer[type] ) {
                // Specific renderer for this type. If available use it, otherwise use
                // the default.
                return host[renderer[type]] || host._;
            }
            else if ( typeof renderer === 'string' ) {
                // Common renderer - if there is one available for this type use it,
                // otherwise use the default
                return host[renderer] || host._;
            }
        
            // Use the default
            return host._;
        }
        
        
        /**
         * Detect the data source being used for the table. Used to simplify the code
         * a little (ajax) and to make it compress a little smaller.
         *
         *  @param {object} settings dataTables settings object
         *  @returns {string} Data source
         *  @memberof DataTable#oApi
         */
        function _fnDataSource ( settings )
        {
            if ( settings.oFeatures.bServerSide ) {
                return 'ssp';
            }
            else if ( settings.ajax ) {
                return 'ajax';
            }
            return 'dom';
        }
        
        /**
         * Common replacement for language strings
         *
         * @param {*} settings DT settings object
         * @param {*} str String with values to replace
         * @param {*} entries Plural number for _ENTRIES_ - can be undefined
         * @returns String
         */
        function _fnMacros ( settings, str, entries )
        {
            // When infinite scrolling, we are always starting at 1. _iDisplayStart is
            // used only internally
            var
                formatter  = settings.fnFormatNumber,
                start      = settings._iDisplayStart+1,
                len        = settings._iDisplayLength,
                vis        = settings.fnRecordsDisplay(),
                max        = settings.fnRecordsTotal(),
                all        = len === -1;
        
            return str.
                replace(/_START_/g, formatter.call( settings, start ) ).
                replace(/_END_/g,   formatter.call( settings, settings.fnDisplayEnd() ) ).
                replace(/_MAX_/g,   formatter.call( settings, max ) ).
                replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
                replace(/_PAGE_/g,  formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
                replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ).
                replace(/_ENTRIES_/g, settings.api.i18n('entries', '', entries) ).
                replace(/_ENTRIES-MAX_/g, settings.api.i18n('entries', '', max) ).
                replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) );
        }
        
        
        
        /**
         * Computed structure of the DataTables API, defined by the options passed to
         * `DataTable.Api.register()` when building the API.
         *
         * The structure is built in order to speed creation and extension of the Api
         * objects since the extensions are effectively pre-parsed.
         *
         * The array is an array of objects with the following structure, where this
         * base array represents the Api prototype base:
         *
         *     [
         *       {
         *         name:      'data'                -- string   - Property name
         *         val:       function () {},       -- function - Api method (or undefined if just an object
         *         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
         *         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
         *       },
         *       {
         *         name:     'row'
         *         val:       {},
         *         methodExt: [ ... ],
         *         propExt:   [
         *           {
         *             name:      'data'
         *             val:       function () {},
         *             methodExt: [ ... ],
         *             propExt:   [ ... ]
         *           },
         *           ...
         *         ]
         *       }
         *     ]
         *
         * @type {Array}
         * @ignore
         */
        var __apiStruct = [];
        
        
        /**
         * `Array.prototype` reference.
         *
         * @type object
         * @ignore
         */
        var __arrayProto = Array.prototype;
        
        
        /**
         * Abstraction for `context` parameter of the `Api` constructor to allow it to
         * take several different forms for ease of use.
         *
         * Each of the input parameter types will be converted to a DataTables settings
         * object where possible.
         *
         * @param  {string|node|jQuery|object} mixed DataTable identifier. Can be one
         *   of:
         *
         *   * `string` - jQuery selector. Any DataTables' matching the given selector
         *     with be found and used.
         *   * `node` - `TABLE` node which has already been formed into a DataTable.
         *   * `jQuery` - A jQuery object of `TABLE` nodes.
         *   * `object` - DataTables settings object
         *   * `DataTables.Api` - API instance
         * @return {array|null} Matching DataTables settings objects. `null` or
         *   `undefined` is returned if no matching DataTable is found.
         * @ignore
         */
        var _toSettings = function ( mixed )
        {
            var idx, jq;
            var settings = DataTable.settings;
            var tables = _pluck(settings, 'nTable');
        
            if ( ! mixed ) {
                return [];
            }
            else if ( mixed.nTable && mixed.oFeatures ) {
                // DataTables settings object
                return [ mixed ];
            }
            else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
                // Table node
                idx = tables.indexOf(mixed);
                return idx !== -1 ? [ settings[idx] ] : null;
            }
            else if ( mixed && typeof mixed.settings === 'function' ) {
                return mixed.settings().toArray();
            }
            else if ( typeof mixed === 'string' ) {
                // jQuery selector
                jq = $(mixed).get();
            }
            else if ( mixed instanceof $ ) {
                // jQuery object (also DataTables instance)
                jq = mixed.get();
            }
        
            if ( jq ) {
                return settings.filter(function (v, idx) {
                    return jq.includes(tables[idx]);
                });
            }
        };
        
        
        /**
         * DataTables API class - used to control and interface with  one or more
         * DataTables enhanced tables.
         *
         * The API class is heavily based on jQuery, presenting a chainable interface
         * that you can use to interact with tables. Each instance of the API class has
         * a "context" - i.e. the tables that it will operate on. This could be a single
         * table, all tables on a page or a sub-set thereof.
         *
         * Additionally the API is designed to allow you to easily work with the data in
         * the tables, retrieving and manipulating it as required. This is done by
         * presenting the API class as an array like interface. The contents of the
         * array depend upon the actions requested by each method (for example
         * `rows().nodes()` will return an array of nodes, while `rows().data()` will
         * return an array of objects or arrays depending upon your table's
         * configuration). The API object has a number of array like methods (`push`,
         * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`,
         * `unique` etc) to assist your working with the data held in a table.
         *
         * Most methods (those which return an Api instance) are chainable, which means
         * the return from a method call also has all of the methods available that the
         * top level object had. For example, these two calls are equivalent:
         *
         *     // Not chained
         *     api.row.add( {...} );
         *     api.draw();
         *
         *     // Chained
         *     api.row.add( {...} ).draw();
         *
         * @class DataTable.Api
         * @param {array|object|string|jQuery} context DataTable identifier. This is
         *   used to define which DataTables enhanced tables this API will operate on.
         *   Can be one of:
         *
         *   * `string` - jQuery selector. Any DataTables' matching the given selector
         *     with be found and used.
         *   * `node` - `TABLE` node which has already been formed into a DataTable.
         *   * `jQuery` - A jQuery object of `TABLE` nodes.
         *   * `object` - DataTables settings object
         * @param {array} [data] Data to initialise the Api instance with.
         *
         * @example
         *   // Direct initialisation during DataTables construction
         *   var api = $('#example').DataTable();
         *
         * @example
         *   // Initialisation using a DataTables jQuery object
         *   var api = $('#example').dataTable().api();
         *
         * @example
         *   // Initialisation as a constructor
         *   var api = new DataTable.Api( 'table.dataTable' );
         */
        _Api = function ( context, data )
        {
            if ( ! (this instanceof _Api) ) {
                return new _Api( context, data );
            }
        
            var i;
            var settings = [];
            var ctxSettings = function ( o ) {
                var a = _toSettings( o );
                if ( a ) {
                    settings.push.apply( settings, a );
                }
            };
        
            if ( Array.isArray( context ) ) {
                for ( i=0 ; i<context.length ; i++ ) {
                    ctxSettings( context[i] );
                }
            }
            else {
                ctxSettings( context );
            }
        
            // Remove duplicates
            this.context = settings.length > 1
                ? _unique( settings )
                : settings;
        
            // Initial data
            if ( data ) {
                // Chrome can throw a max stack error if apply is called with
                // too large an array, but apply is faster.
                if (data.length < 10000) {
                    this.push.apply(this, data);
                }
                else {
                    for (i=0 ; i<data.length ; i++) {
                        this.push(data[i]);
                    }
                }
            }
        
            // selector
            this.selector = {
                rows: null,
                cols: null,
                opts: null
            };
        
            _Api.extend( this, this, __apiStruct );
        };
        
        DataTable.Api = _Api;
        
        // Don't destroy the existing prototype, just extend it. Required for jQuery 2's
        // isPlainObject.
        $.extend( _Api.prototype, {
            any: function ()
            {
                return this.count() !== 0;
            },
        
            context: [], // array of table settings objects
        
            count: function ()
            {
                return this.flatten().length;
            },
        
            each: function ( fn )
            {
                for ( var i=0, ien=this.length ; i<ien; i++ ) {
                    fn.call( this, this[i], i, this );
                }
        
                return this;
            },
        
            eq: function ( idx )
            {
                var ctx = this.context;
        
                return ctx.length > idx ?
                    new _Api( ctx[idx], this[idx] ) :
                    null;
            },
        
            filter: function ( fn )
            {
                var a = __arrayProto.filter.call( this, fn, this );
        
                return new _Api( this.context, a );
            },
        
            flatten: function ()
            {
                var a = [];
        
                return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
            },
        
            get: function ( idx )
            {
                return this[ idx ];
            },
        
            join:    __arrayProto.join,
        
            includes: function ( find ) {
                return this.indexOf( find ) === -1 ? false : true;
            },
        
            indexOf: __arrayProto.indexOf,
        
            iterator: function ( flatten, type, fn, alwaysNew ) {
                var
                    a = [], ret,
                    i, ien, j, jen,
                    context = this.context,
                    rows, items, item,
                    selector = this.selector;
        
                // Argument shifting
                if ( typeof flatten === 'string' ) {
                    alwaysNew = fn;
                    fn = type;
                    type = flatten;
                    flatten = false;
                }
        
                for ( i=0, ien=context.length ; i<ien ; i++ ) {
                    var apiInst = new _Api( context[i] );
        
                    if ( type === 'table' ) {
                        ret = fn.call( apiInst, context[i], i );
        
                        if ( ret !== undefined ) {
                            a.push( ret );
                        }
                    }
                    else if ( type === 'columns' || type === 'rows' ) {
                        // this has same length as context - one entry for each table
                        ret = fn.call( apiInst, context[i], this[i], i );
        
                        if ( ret !== undefined ) {
                            a.push( ret );
                        }
                    }
                    else if ( type === 'every' || type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
                        // columns and rows share the same structure.
                        // 'this' is an array of column indexes for each context
                        items = this[i];
        
                        if ( type === 'column-rows' ) {
                            rows = _selector_row_indexes( context[i], selector.opts );
                        }
        
                        for ( j=0, jen=items.length ; j<jen ; j++ ) {
                            item = items[j];
        
                            if ( type === 'cell' ) {
                                ret = fn.call( apiInst, context[i], item.row, item.column, i, j );
                            }
                            else {
                                ret = fn.call( apiInst, context[i], item, i, j, rows );
                            }
        
                            if ( ret !== undefined ) {
                                a.push( ret );
                            }
                        }
                    }
                }
        
                if ( a.length || alwaysNew ) {
                    var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a );
                    var apiSelector = api.selector;
                    apiSelector.rows = selector.rows;
                    apiSelector.cols = selector.cols;
                    apiSelector.opts = selector.opts;
                    return api;
                }
                return this;
            },
        
            lastIndexOf: __arrayProto.lastIndexOf,
        
            length:  0,
        
            map: function ( fn )
            {
                var a = __arrayProto.map.call( this, fn, this );
        
                return new _Api( this.context, a );
            },
        
            pluck: function ( prop )
            {
                var fn = DataTable.util.get(prop);
        
                return this.map( function ( el ) {
                    return fn(el);
                } );
            },
        
            pop:     __arrayProto.pop,
        
            push:    __arrayProto.push,
        
            reduce: __arrayProto.reduce,
        
            reduceRight: __arrayProto.reduceRight,
        
            reverse: __arrayProto.reverse,
        
            // Object with rows, columns and opts
            selector: null,
        
            shift:   __arrayProto.shift,
        
            slice: function () {
                return new _Api( this.context, this );
            },
        
            sort:    __arrayProto.sort,
        
            splice:  __arrayProto.splice,
        
            toArray: function ()
            {
                return __arrayProto.slice.call( this );
            },
        
            to$: function ()
            {
                return $( this );
            },
        
            toJQuery: function ()
            {
                return $( this );
            },
        
            unique: function ()
            {
                return new _Api( this.context, _unique(this.toArray()) );
            },
        
            unshift: __arrayProto.unshift
        } );
        
        
        function _api_scope( scope, fn, struc ) {
            return function () {
                var ret = fn.apply( scope || this, arguments );
        
                // Method extension
                _Api.extend( ret, ret, struc.methodExt );
                return ret;
            };
        }
        
        function _api_find( src, name ) {
            for ( var i=0, ien=src.length ; i<ien ; i++ ) {
                if ( src[i].name === name ) {
                    return src[i];
                }
            }
            return null;
        }
        
        window.__apiStruct = __apiStruct;
        
        _Api.extend = function ( scope, obj, ext )
        {
            // Only extend API instances and static properties of the API
            if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) {
                return;
            }
        
            var
                i, ien,
                struct;
        
            for ( i=0, ien=ext.length ; i<ien ; i++ ) {
                struct = ext[i];
        
                if (struct.name === '__proto__') {
                    continue;
                }
        
                // Value
                obj[ struct.name ] = struct.type === 'function' ?
                    _api_scope( scope, struct.val, struct ) :
                    struct.type === 'object' ?
                        {} :
                        struct.val;
        
                obj[ struct.name ].__dt_wrapper = true;
        
                // Property extension
                _Api.extend( scope, obj[ struct.name ], struct.propExt );
            }
        };
        
        //     [
        //       {
        //         name:      'data'                -- string   - Property name
        //         val:       function () {},       -- function - Api method (or undefined if just an object
        //         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
        //         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
        //       },
        //       {
        //         name:     'row'
        //         val:       {},
        //         methodExt: [ ... ],
        //         propExt:   [
        //           {
        //             name:      'data'
        //             val:       function () {},
        //             methodExt: [ ... ],
        //             propExt:   [ ... ]
        //           },
        //           ...
        //         ]
        //       }
        //     ]
        
        
        _Api.register = _api_register = function ( name, val )
        {
            if ( Array.isArray( name ) ) {
                for ( var j=0, jen=name.length ; j<jen ; j++ ) {
                    _Api.register( name[j], val );
                }
                return;
            }
        
            var
                i, ien,
                heir = name.split('.'),
                struct = __apiStruct,
                key, method;
        
            for ( i=0, ien=heir.length ; i<ien ; i++ ) {
                method = heir[i].indexOf('()') !== -1;
                key = method ?
                    heir[i].replace('()', '') :
                    heir[i];
        
                var src = _api_find( struct, key );
                if ( ! src ) {
                    src = {
                        name:      key,
                        val:       {},
                        methodExt: [],
                        propExt:   [],
                        type:      'object'
                    };
                    struct.push( src );
                }
        
                if ( i === ien-1 ) {
                    src.val = val;
                    src.type = typeof val === 'function' ?
                        'function' :
                        $.isPlainObject( val ) ?
                            'object' :
                            'other';
                }
                else {
                    struct = method ?
                        src.methodExt :
                        src.propExt;
                }
            }
        };
        
        _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) {
            _Api.register( pluralName, val );
        
            _Api.register( singularName, function () {
                var ret = val.apply( this, arguments );
        
                if ( ret === this ) {
                    // Returned item is the API instance that was passed in, return it
                    return this;
                }
                else if ( ret instanceof _Api ) {
                    // New API instance returned, want the value from the first item
                    // in the returned array for the singular result.
                    return ret.length ?
                        Array.isArray( ret[0] ) ?
                            new _Api( ret.context, ret[0] ) : // Array results are 'enhanced'
                            ret[0] :
                        undefined;
                }
        
                // Non-API return - just fire it back
                return ret;
            } );
        };
        
        
        /**
         * Selector for HTML tables. Apply the given selector to the give array of
         * DataTables settings objects.
         *
         * @param {string|integer} [selector] jQuery selector string or integer
         * @param  {array} Array of DataTables settings objects to be filtered
         * @return {array}
         * @ignore
         */
        var __table_selector = function ( selector, a )
        {
            if ( Array.isArray(selector) ) {
                var result = [];
        
                selector.forEach(function (sel) {
                    var inner = __table_selector(sel, a);
        
                    result.push.apply(result, inner);
                });
        
                return result.filter( function (item) {
                    return item;
                });
            }
        
            // Integer is used to pick out a table by index
            if ( typeof selector === 'number' ) {
                return [ a[ selector ] ];
            }
        
            // Perform a jQuery selector on the table nodes
            var nodes = a.map( function (el) {
                return el.nTable;
            } );
        
            return $(nodes)
                .filter( selector )
                .map( function () {
                    // Need to translate back from the table node to the settings
                    var idx = nodes.indexOf(this);
                    return a[ idx ];
                } )
                .toArray();
        };
        
        
        
        /**
         * Context selector for the API's context (i.e. the tables the API instance
         * refers to.
         *
         * @name    DataTable.Api#tables
         * @param {string|integer} [selector] Selector to pick which tables the iterator
         *   should operate on. If not given, all tables in the current context are
         *   used. This can be given as a jQuery selector (for example `':gt(0)'`) to
         *   select multiple tables or as an integer to select a single table.
         * @returns {DataTable.Api} Returns a new API instance if a selector is given.
         */
        _api_register( 'tables()', function ( selector ) {
            // A new instance is created if there was a selector specified
            return selector !== undefined && selector !== null ?
                new _Api( __table_selector( selector, this.context ) ) :
                this;
        } );
        
        
        _api_register( 'table()', function ( selector ) {
            var tables = this.tables( selector );
            var ctx = tables.context;
        
            // Truncate to the first matched table
            return ctx.length ?
                new _Api( ctx[0] ) :
                tables;
        } );
        
        // Common methods, combined to reduce size
        [
            ['nodes', 'node', 'nTable'],
            ['body', 'body', 'nTBody'],
            ['header', 'header', 'nTHead'],
            ['footer', 'footer', 'nTFoot'],
        ].forEach(function (item) {
            _api_registerPlural(
                'tables().' + item[0] + '()',
                'table().' + item[1] + '()' ,
                function () {
                    return this.iterator( 'table', function ( ctx ) {
                        return ctx[item[2]];
                    }, 1 );
                }
            );
        });
        
        // Structure methods
        [
            ['header', 'aoHeader'],
            ['footer', 'aoFooter'],
        ].forEach(function (item) {
            _api_register( 'table().' + item[0] + '.structure()' , function (selector) {
                var indexes = this.columns(selector).indexes().flatten();
                var ctx = this.context[0];
                
                return _fnHeaderLayout(ctx, ctx[item[1]], indexes);
            } );
        })
        
        
        _api_registerPlural( 'tables().containers()', 'table().container()' , function () {
            return this.iterator( 'table', function ( ctx ) {
                return ctx.nTableWrapper;
            }, 1 );
        } );
        
        _api_register( 'tables().every()', function ( fn ) {
            var that = this;
        
            return this.iterator('table', function (s, i) {
                fn.call(that.table(i), i);
            });
        });
        
        _api_register( 'caption()', function ( value, side ) {
            var context = this.context;
        
            // Getter - return existing node's content
            if ( value === undefined ) {
                var caption = context[0].captionNode;
        
                return caption && context.length ?
                    caption.innerHTML : 
                    null;
            }
        
            return this.iterator( 'table', function ( ctx ) {
                var table = $(ctx.nTable);
                var caption = $(ctx.captionNode);
                var container = $(ctx.nTableWrapper);
        
                // Create the node if it doesn't exist yet
                if ( ! caption.length ) {
                    caption = $('<caption/>').html( value );
                    ctx.captionNode = caption[0];
        
                    // If side isn't set, we need to insert into the document to let the
                    // CSS decide so we can read it back, otherwise there is no way to
                    // know if the CSS would put it top or bottom for scrolling
                    if (! side) {
                        table.prepend(caption);
        
                        side = caption.css('caption-side');
                    }
                }
        
                caption.html( value );
        
                if ( side ) {
                    caption.css( 'caption-side', side );
                    caption[0]._captionSide = side;
                }
        
                if (container.find('div.dataTables_scroll').length) {
                    var selector = (side === 'top' ? 'Head' : 'Foot');
        
                    container.find('div.dataTables_scroll'+ selector +' table').prepend(caption);
                }
                else {
                    table.prepend(caption);
                }
            }, 1 );
        } );
        
        _api_register( 'caption.node()', function () {
            var ctx = this.context;
        
            return ctx.length ? ctx[0].captionNode : null;
        } );
        
        
        /**
         * Redraw the tables in the current context.
         */
        _api_register( 'draw()', function ( paging ) {
            return this.iterator( 'table', function ( settings ) {
                if ( paging === 'page' ) {
                    _fnDraw( settings );
                }
                else {
                    if ( typeof paging === 'string' ) {
                        paging = paging === 'full-hold' ?
                            false :
                            true;
                    }
        
                    _fnReDraw( settings, paging===false );
                }
            } );
        } );
        
        
        
        /**
         * Get the current page index.
         *
         * @return {integer} Current page index (zero based)
         *//**
         * Set the current page.
         *
         * Note that if you attempt to show a page which does not exist, DataTables will
         * not throw an error, but rather reset the paging.
         *
         * @param {integer|string} action The paging action to take. This can be one of:
         *  * `integer` - The page index to jump to
         *  * `string` - An action to take:
         *    * `first` - Jump to first page.
         *    * `next` - Jump to the next page
         *    * `previous` - Jump to previous page
         *    * `last` - Jump to the last page.
         * @returns {DataTables.Api} this
         */
        _api_register( 'page()', function ( action ) {
            if ( action === undefined ) {
                return this.page.info().page; // not an expensive call
            }
        
            // else, have an action to take on all tables
            return this.iterator( 'table', function ( settings ) {
                _fnPageChange( settings, action );
            } );
        } );
        
        
        /**
         * Paging information for the first table in the current context.
         *
         * If you require paging information for another table, use the `table()` method
         * with a suitable selector.
         *
         * @return {object} Object with the following properties set:
         *  * `page` - Current page index (zero based - i.e. the first page is `0`)
         *  * `pages` - Total number of pages
         *  * `start` - Display index for the first record shown on the current page
         *  * `end` - Display index for the last record shown on the current page
         *  * `length` - Display length (number of records). Note that generally `start
         *    + length = end`, but this is not always true, for example if there are
         *    only 2 records to show on the final page, with a length of 10.
         *  * `recordsTotal` - Full data set length
         *  * `recordsDisplay` - Data set length once the current filtering criterion
         *    are applied.
         */
        _api_register( 'page.info()', function () {
            if ( this.context.length === 0 ) {
                return undefined;
            }
        
            var
                settings   = this.context[0],
                start      = settings._iDisplayStart,
                len        = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1,
                visRecords = settings.fnRecordsDisplay(),
                all        = len === -1;
        
            return {
                "page":           all ? 0 : Math.floor( start / len ),
                "pages":          all ? 1 : Math.ceil( visRecords / len ),
                "start":          start,
                "end":            settings.fnDisplayEnd(),
                "length":         len,
                "recordsTotal":   settings.fnRecordsTotal(),
                "recordsDisplay": visRecords,
                "serverSide":     _fnDataSource( settings ) === 'ssp'
            };
        } );
        
        
        /**
         * Get the current page length.
         *
         * @return {integer} Current page length. Note `-1` indicates that all records
         *   are to be shown.
         *//**
         * Set the current page length.
         *
         * @param {integer} Page length to set. Use `-1` to show all records.
         * @returns {DataTables.Api} this
         */
        _api_register( 'page.len()', function ( len ) {
            // Note that we can't call this function 'length()' because `length`
            // is a Javascript property of functions which defines how many arguments
            // the function expects.
            if ( len === undefined ) {
                return this.context.length !== 0 ?
                    this.context[0]._iDisplayLength :
                    undefined;
            }
        
            // else, set the page length
            return this.iterator( 'table', function ( settings ) {
                _fnLengthChange( settings, len );
            } );
        } );
        
        
        
        var __reload = function ( settings, holdPosition, callback ) {
            // Use the draw event to trigger a callback
            if ( callback ) {
                var api = new _Api( settings );
        
                api.one( 'draw', function () {
                    callback( api.ajax.json() );
                } );
            }
        
            if ( _fnDataSource( settings ) == 'ssp' ) {
                _fnReDraw( settings, holdPosition );
            }
            else {
                _fnProcessingDisplay( settings, true );
        
                // Cancel an existing request
                var xhr = settings.jqXHR;
                if ( xhr && xhr.readyState !== 4 ) {
                    xhr.abort();
                }
        
                // Trigger xhr
                _fnBuildAjax( settings, {}, function( json ) {
                    _fnClearTable( settings );
        
                    var data = _fnAjaxDataSrc( settings, json );
                    for ( var i=0, ien=data.length ; i<ien ; i++ ) {
                        _fnAddData( settings, data[i] );
                    }
        
                    _fnReDraw( settings, holdPosition );
                    _fnInitComplete( settings );
                    _fnProcessingDisplay( settings, false );
                } );
            }
        };
        
        
        /**
         * Get the JSON response from the last Ajax request that DataTables made to the
         * server. Note that this returns the JSON from the first table in the current
         * context.
         *
         * @return {object} JSON received from the server.
         */
        _api_register( 'ajax.json()', function () {
            var ctx = this.context;
        
            if ( ctx.length > 0 ) {
                return ctx[0].json;
            }
        
            // else return undefined;
        } );
        
        
        /**
         * Get the data submitted in the last Ajax request
         */
        _api_register( 'ajax.params()', function () {
            var ctx = this.context;
        
            if ( ctx.length > 0 ) {
                return ctx[0].oAjaxData;
            }
        
            // else return undefined;
        } );
        
        
        /**
         * Reload tables from the Ajax data source. Note that this function will
         * automatically re-draw the table when the remote data has been loaded.
         *
         * @param {boolean} [reset=true] Reset (default) or hold the current paging
         *   position. A full re-sort and re-filter is performed when this method is
         *   called, which is why the pagination reset is the default action.
         * @returns {DataTables.Api} this
         */
        _api_register( 'ajax.reload()', function ( callback, resetPaging ) {
            return this.iterator( 'table', function (settings) {
                __reload( settings, resetPaging===false, callback );
            } );
        } );
        
        
        /**
         * Get the current Ajax URL. Note that this returns the URL from the first
         * table in the current context.
         *
         * @return {string} Current Ajax source URL
         *//**
         * Set the Ajax URL. Note that this will set the URL for all tables in the
         * current context.
         *
         * @param {string} url URL to set.
         * @returns {DataTables.Api} this
         */
        _api_register( 'ajax.url()', function ( url ) {
            var ctx = this.context;
        
            if ( url === undefined ) {
                // get
                if ( ctx.length === 0 ) {
                    return undefined;
                }
                ctx = ctx[0];
        
                return $.isPlainObject( ctx.ajax ) ?
                    ctx.ajax.url :
                    ctx.ajax;
            }
        
            // set
            return this.iterator( 'table', function ( settings ) {
                if ( $.isPlainObject( settings.ajax ) ) {
                    settings.ajax.url = url;
                }
                else {
                    settings.ajax = url;
                }
            } );
        } );
        
        
        /**
         * Load data from the newly set Ajax URL. Note that this method is only
         * available when `ajax.url()` is used to set a URL. Additionally, this method
         * has the same effect as calling `ajax.reload()` but is provided for
         * convenience when setting a new URL. Like `ajax.reload()` it will
         * automatically redraw the table once the remote data has been loaded.
         *
         * @returns {DataTables.Api} this
         */
        _api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
            // Same as a reload, but makes sense to present it for easy access after a
            // url change
            return this.iterator( 'table', function ( ctx ) {
                __reload( ctx, resetPaging===false, callback );
            } );
        } );
        
        
        
        
        var _selector_run = function ( type, selector, selectFn, settings, opts )
        {
            var
                out = [], res,
                a, i, ien, j, jen,
                selectorType = typeof selector;
        
            // Can't just check for isArray here, as an API or jQuery instance might be
            // given with their array like look
            if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
                selector = [ selector ];
            }
        
            for ( i=0, ien=selector.length ; i<ien ; i++ ) {
                // Only split on simple strings - complex expressions will be jQuery selectors
                a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ?
                    selector[i].split(',') :
                    [ selector[i] ];
        
                for ( j=0, jen=a.length ; j<jen ; j++ ) {
                    res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] );
        
                    // Remove empty items
                    res = res.filter( function (item) {
                        return item !== null && item !== undefined;
                    });
        
                    if ( res && res.length ) {
                        out = out.concat( res );
                    }
                }
            }
        
            // selector extensions
            var ext = _ext.selector[ type ];
            if ( ext.length ) {
                for ( i=0, ien=ext.length ; i<ien ; i++ ) {
                    out = ext[i]( settings, opts, out );
                }
            }
        
            return _unique( out );
        };
        
        
        var _selector_opts = function ( opts )
        {
            if ( ! opts ) {
                opts = {};
            }
        
            // Backwards compatibility for 1.9- which used the terminology filter rather
            // than search
            if ( opts.filter && opts.search === undefined ) {
                opts.search = opts.filter;
            }
        
            return $.extend( {
                search: 'none',
                order: 'current',
                page: 'all'
            }, opts );
        };
        
        
        // Reduce the API instance to the first item found
        var _selector_first = function ( old )
        {
            let inst = new _Api(old.context[0]);
        
            // Use a push rather than passing to the constructor, since it will
            // merge arrays down automatically, which isn't what is wanted here
            if (old.length) {
                inst.push( old[0] );
            }
        
            inst.selector = old.selector;
        
            // Limit to a single row / column / cell
            if (inst.length && inst[0].length > 1) {
                inst[0].splice(1);
            }
        
            return inst;
        };
        
        
        var _selector_row_indexes = function ( settings, opts )
        {
            var
                i, ien, tmp, a=[],
                displayFiltered = settings.aiDisplay,
                displayMaster = settings.aiDisplayMaster;
        
            var
                search = opts.search,  // none, applied, removed
                order  = opts.order,   // applied, current, index (original - compatibility with 1.9)
                page   = opts.page;    // all, current
        
            if ( _fnDataSource( settings ) == 'ssp' ) {
                // In server-side processing mode, most options are irrelevant since
                // rows not shown don't exist and the index order is the applied order
                // Removed is a special case - for consistency just return an empty
                // array
                return search === 'removed' ?
                    [] :
                    _range( 0, displayMaster.length );
            }
        
            if ( page == 'current' ) {
                // Current page implies that order=current and filter=applied, since it is
                // fairly senseless otherwise, regardless of what order and search actually
                // are
                for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) {
                    a.push( displayFiltered[i] );
                }
            }
            else if ( order == 'current' || order == 'applied' ) {
                if ( search == 'none') {
                    a = displayMaster.slice();
                }
                else if ( search == 'applied' ) {
                    a = displayFiltered.slice();
                }
                else if ( search == 'removed' ) {
                    // O(n+m) solution by creating a hash map
                    var displayFilteredMap = {};
        
                    for ( i=0, ien=displayFiltered.length ; i<ien ; i++ ) {
                        displayFilteredMap[displayFiltered[i]] = null;
                    }
        
                    displayMaster.forEach(function (item) {
                        if (! Object.prototype.hasOwnProperty.call(displayFilteredMap, item)) {
                            a.push(item);
                        }
                    });
                }
            }
            else if ( order == 'index' || order == 'original' ) {
                for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
                    if (! settings.aoData[i]) {
                        continue;
                    }
        
                    if ( search == 'none' ) {
                        a.push( i );
                    }
                    else { // applied | removed
                        tmp = displayFiltered.indexOf(i);
        
                        if ((tmp === -1 && search == 'removed') ||
                            (tmp >= 0   && search == 'applied') )
                        {
                            a.push( i );
                        }
                    }
                }
            }
            else if ( typeof order === 'number' ) {
                // Order the rows by the given column
                var ordered = _fnSort(settings, order, 'asc');
        
                if (search === 'none') {
                    a = ordered;
                }
                else { // applied | removed
                    for (i=0; i<ordered.length; i++) {
                        tmp = displayFiltered.indexOf(ordered[i]);
        
                        if ((tmp === -1 && search == 'removed') ||
                            (tmp >= 0   && search == 'applied') )
                        {
                            a.push( ordered[i] );
                        }
                    }
                }
            }
        
            return a;
        };
        
        
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
         * Rows
         *
         * {}          - no selector - use all available rows
         * {integer}   - row aoData index
         * {node}      - TR node
         * {string}    - jQuery selector to apply to the TR elements
         * {array}     - jQuery array of nodes, or simply an array of TR nodes
         *
         */
        var __row_selector = function ( settings, selector, opts )
        {
            var rows;
            var run = function ( sel ) {
                var selInt = _intVal( sel );
                var aoData = settings.aoData;
        
                // Short cut - selector is a number and no options provided (default is
                // all records, so no need to check if the index is in there, since it
                // must be - dev error if the index doesn't exist).
                if ( selInt !== null && ! opts ) {
                    return [ selInt ];
                }
        
                if ( ! rows ) {
                    rows = _selector_row_indexes( settings, opts );
                }
        
                if ( selInt !== null && rows.indexOf(selInt) !== -1 ) {
                    // Selector - integer
                    return [ selInt ];
                }
                else if ( sel === null || sel === undefined || sel === '' ) {
                    // Selector - none
                    return rows;
                }
        
                // Selector - function
                if ( typeof sel === 'function' ) {
                    return rows.map( function (idx) {
                        var row = aoData[ idx ];
                        return sel( idx, row._aData, row.nTr ) ? idx : null;
                    } );
                }
        
                // Selector - node
                if ( sel.nodeName ) {
                    var rowIdx = sel._DT_RowIndex;  // Property added by DT for fast lookup
                    var cellIdx = sel._DT_CellIndex;
        
                    if ( rowIdx !== undefined ) {
                        // Make sure that the row is actually still present in the table
                        return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ?
                            [ rowIdx ] :
                            [];
                    }
                    else if ( cellIdx ) {
                        return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ?
                            [ cellIdx.row ] :
                            [];
                    }
                    else {
                        var host = $(sel).closest('*[data-dt-row]');
                        return host.length ?
                            [ host.data('dt-row') ] :
                            [];
                    }
                }
        
                // ID selector. Want to always be able to select rows by id, regardless
                // of if the tr element has been created or not, so can't rely upon
                // jQuery here - hence a custom implementation. This does not match
                // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
                // but to select it using a CSS selector engine (like Sizzle or
                // querySelect) it would need to need to be escaped for some characters.
                // DataTables simplifies this for row selectors since you can select
                // only a row. A # indicates an id any anything that follows is the id -
                // unescaped.
                if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
                    // get row index from id
                    var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
                    if ( rowObj !== undefined ) {
                        return [ rowObj.idx ];
                    }
        
                    // need to fall through to jQuery in case there is DOM id that
                    // matches
                }
                
                // Get nodes in the order from the `rows` array with null values removed
                var nodes = _removeEmpty(
                    _pluck_order( settings.aoData, rows, 'nTr' )
                );
        
                // Selector - jQuery selector string, array of nodes or jQuery object/
                // As jQuery's .filter() allows jQuery objects to be passed in filter,
                // it also allows arrays, so this will cope with all three options
                return $(nodes)
                    .filter( sel )
                    .map( function () {
                        return this._DT_RowIndex;
                    } )
                    .toArray();
            };
        
            var matched = _selector_run( 'row', selector, run, settings, opts );
        
            if (opts.order === 'current' || opts.order === 'applied') {
                _fnSortDisplay(settings, matched);
            }
        
            return matched;
        };
        
        
        _api_register( 'rows()', function ( selector, opts ) {
            // argument shifting
            if ( selector === undefined ) {
                selector = '';
            }
            else if ( $.isPlainObject( selector ) ) {
                opts = selector;
                selector = '';
            }
        
            opts = _selector_opts( opts );
        
            var inst = this.iterator( 'table', function ( settings ) {
                return __row_selector( settings, selector, opts );
            }, 1 );
        
            // Want argument shifting here and in __row_selector?
            inst.selector.rows = selector;
            inst.selector.opts = opts;
        
            return inst;
        } );
        
        _api_register( 'rows().nodes()', function () {
            return this.iterator( 'row', function ( settings, row ) {
                return settings.aoData[ row ].nTr || undefined;
            }, 1 );
        } );
        
        _api_register( 'rows().data()', function () {
            return this.iterator( true, 'rows', function ( settings, rows ) {
                return _pluck_order( settings.aoData, rows, '_aData' );
            }, 1 );
        } );
        
        _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
            return this.iterator( 'row', function ( settings, row ) {
                var r = settings.aoData[ row ];
                return type === 'search' ? r._aFilterData : r._aSortData;
            }, 1 );
        } );
        
        _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
            return this.iterator( 'row', function ( settings, row ) {
                _fnInvalidate( settings, row, src );
            } );
        } );
        
        _api_registerPlural( 'rows().indexes()', 'row().index()', function () {
            return this.iterator( 'row', function ( settings, row ) {
                return row;
            }, 1 );
        } );
        
        _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
            var a = [];
            var context = this.context;
        
            // `iterator` will drop undefined values, but in this case we want them
            for ( var i=0, ien=context.length ; i<ien ; i++ ) {
                for ( var j=0, jen=this[i].length ; j<jen ; j++ ) {
                    var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData );
                    a.push( (hash === true ? '#' : '' )+ id );
                }
            }
        
            return new _Api( context, a );
        } );
        
        _api_registerPlural( 'rows().remove()', 'row().remove()', function () {
            this.iterator( 'row', function ( settings, row ) {
                var data = settings.aoData;
                var rowData = data[ row ];
        
                // Delete from the display arrays
                var idx = settings.aiDisplayMaster.indexOf(row);
                if (idx !== -1) {
                    settings.aiDisplayMaster.splice(idx, 1);
                }
        
                // For server-side processing tables - subtract the deleted row from the count
                if ( settings._iRecordsDisplay > 0 ) {
                    settings._iRecordsDisplay--;
                }
        
                // Check for an 'overflow' they case for displaying the table
                _fnLengthOverflow( settings );
        
                // Remove the row's ID reference if there is one
                var id = settings.rowIdFn( rowData._aData );
                if ( id !== undefined ) {
                    delete settings.aIds[ id ];
                }
        
                data[row] = null;
            } );
        
            return this;
        } );
        
        
        _api_register( 'rows.add()', function ( rows ) {
            var newRows = this.iterator( 'table', function ( settings ) {
                    var row, i, ien;
                    var out = [];
        
                    for ( i=0, ien=rows.length ; i<ien ; i++ ) {
                        row = rows[i];
        
                        if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
                            out.push( _fnAddTr( settings, row )[0] );
                        }
                        else {
                            out.push( _fnAddData( settings, row ) );
                        }
                    }
        
                    return out;
                }, 1 );
        
            // Return an Api.rows() extended instance, so rows().nodes() etc can be used
            var modRows = this.rows( -1 );
            modRows.pop();
            modRows.push.apply(modRows, newRows);
        
            return modRows;
        } );
        
        
        
        
        
        /**
         *
         */
        _api_register( 'row()', function ( selector, opts ) {
            return _selector_first( this.rows( selector, opts ) );
        } );
        
        
        _api_register( 'row().data()', function ( data ) {
            var ctx = this.context;
        
            if ( data === undefined ) {
                // Get
                return ctx.length && this.length && this[0].length ?
                    ctx[0].aoData[ this[0] ]._aData :
                    undefined;
            }
        
            // Set
            var row = ctx[0].aoData[ this[0] ];
            row._aData = data;
        
            // If the DOM has an id, and the data source is an array
            if ( Array.isArray( data ) && row.nTr && row.nTr.id ) {
                _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id );
            }
        
            // Automatically invalidate
            _fnInvalidate( ctx[0], this[0], 'data' );
        
            return this;
        } );
        
        
        _api_register( 'row().node()', function () {
            var ctx = this.context;
        
            if (ctx.length && this.length && this[0].length) {
                var row = ctx[0].aoData[ this[0] ];
        
                if (row && row.nTr) {
                    return row.nTr;
                }
            }
        
            return null;
        } );
        
        
        _api_register( 'row.add()', function ( row ) {
            // Allow a jQuery object to be passed in - only a single row is added from
            // it though - the first element in the set
            if ( row instanceof $ && row.length ) {
                row = row[0];
            }
        
            var rows = this.iterator( 'table', function ( settings ) {
                if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
                    return _fnAddTr( settings, row )[0];
                }
                return _fnAddData( settings, row );
            } );
        
            // Return an Api.rows() extended instance, with the newly added row selected
            return this.row( rows[0] );
        } );
        
        
        $(document).on('plugin-init.dt', function (e, context) {
            var api = new _Api( context );
        
            api.on( 'stateSaveParams.DT', function ( e, settings, d ) {
                // This could be more compact with the API, but it is a lot faster as a simple
                // internal loop
                var idFn = settings.rowIdFn;
                var rows = settings.aiDisplayMaster;
                var ids = [];
        
                for (var i=0 ; i<rows.length ; i++) {
                    var rowIdx = rows[i];
                    var data = settings.aoData[rowIdx];
        
                    if (data._detailsShow) {
                        ids.push( '#' + idFn(data._aData) );
                    }
                }
        
                d.childRows = ids;
            });
        
            // For future state loads (e.g. with StateRestore)
            api.on( 'stateLoaded.DT', function (e, settings, state) {
                __details_state_load( api, state );
            });
        
            // And the initial load state
            __details_state_load( api, api.state.loaded() );
        });
        
        var __details_state_load = function (api, state)
        {
            if ( state && state.childRows ) {
                api
                    .rows( state.childRows.map(function (id) {
                        // Escape any `:` characters from the row id. Accounts for
                        // already escaped characters.
                        return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:");
                    }) )
                    .every( function () {
                        _fnCallbackFire( api.settings()[0], null, 'requestChild', [ this ] )
                    });
            }
        }
        
        var __details_add = function ( ctx, row, data, klass )
        {
            // Convert to array of TR elements
            var rows = [];
            var addRow = function ( r, k ) {
                // Recursion to allow for arrays of jQuery objects
                if ( Array.isArray( r ) || r instanceof $ ) {
                    for ( var i=0, ien=r.length ; i<ien ; i++ ) {
                        addRow( r[i], k );
                    }
                    return;
                }
        
                // If we get a TR element, then just add it directly - up to the dev
                // to add the correct number of columns etc
                if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
                    r.setAttribute( 'data-dt-row', row.idx );
                    rows.push( r );
                }
                else {
                    // Otherwise create a row with a wrapper
                    var created = $('<tr><td></td></tr>')
                        .attr( 'data-dt-row', row.idx )
                        .addClass( k );
                    
                    $('td', created)
                        .addClass( k )
                        .html( r )[0].colSpan = _fnVisbleColumns( ctx );
        
                    rows.push( created[0] );
                }
            };
        
            addRow( data, klass );
        
            if ( row._details ) {
                row._details.detach();
            }
        
            row._details = $(rows);
        
            // If the children were already shown, that state should be retained
            if ( row._detailsShow ) {
                row._details.insertAfter( row.nTr );
            }
        };
        
        
        // Make state saving of child row details async to allow them to be batch processed
        var __details_state = DataTable.util.throttle(
            function (ctx) {
                _fnSaveState( ctx[0] )
            },
            500
        );
        
        
        var __details_remove = function ( api, idx )
        {
            var ctx = api.context;
        
            if ( ctx.length ) {
                var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
        
                if ( row && row._details ) {
                    row._details.remove();
        
                    row._detailsShow = undefined;
                    row._details = undefined;
                    $( row.nTr ).removeClass( 'dt-hasChild' );
                    __details_state( ctx );
                }
            }
        };
        
        
        var __details_display = function ( api, show ) {
            var ctx = api.context;
        
            if ( ctx.length && api.length ) {
                var row = ctx[0].aoData[ api[0] ];
        
                if ( row._details ) {
                    row._detailsShow = show;
        
                    if ( show ) {
                        row._details.insertAfter( row.nTr );
                        $( row.nTr ).addClass( 'dt-hasChild' );
                    }
                    else {
                        row._details.detach();
                        $( row.nTr ).removeClass( 'dt-hasChild' );
                    }
        
                    _fnCallbackFire( ctx[0], null, 'childRow', [ show, api.row( api[0] ) ] )
        
                    __details_events( ctx[0] );
                    __details_state( ctx );
                }
            }
        };
        
        
        var __details_events = function ( settings )
        {
            var api = new _Api( settings );
            var namespace = '.dt.DT_details';
            var drawEvent = 'draw'+namespace;
            var colvisEvent = 'column-sizing'+namespace;
            var destroyEvent = 'destroy'+namespace;
            var data = settings.aoData;
        
            api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
        
            if ( _pluck( data, '_details' ).length > 0 ) {
                // On each draw, insert the required elements into the document
                api.on( drawEvent, function ( e, ctx ) {
                    if ( settings !== ctx ) {
                        return;
                    }
        
                    api.rows( {page:'current'} ).eq(0).each( function (idx) {
                        // Internal data grab
                        var row = data[ idx ];
        
                        if ( row._detailsShow ) {
                            row._details.insertAfter( row.nTr );
                        }
                    } );
                } );
        
                // Column visibility change - update the colspan
                api.on( colvisEvent, function ( e, ctx ) {
                    if ( settings !== ctx ) {
                        return;
                    }
        
                    // Update the colspan for the details rows (note, only if it already has
                    // a colspan)
                    var row, visible = _fnVisbleColumns( ctx );
        
                    for ( var i=0, ien=data.length ; i<ien ; i++ ) {
                        row = data[i];
        
                        if ( row && row._details ) {
                            row._details.each(function () {
                                var el = $(this).children('td');
        
                                if (el.length == 1) {
                                    el.attr('colspan', visible);
                                }
                            });
                        }
                    }
                } );
        
                // Table destroyed - nuke any child rows
                api.on( destroyEvent, function ( e, ctx ) {
                    if ( settings !== ctx ) {
                        return;
                    }
        
                    for ( var i=0, ien=data.length ; i<ien ; i++ ) {
                        if ( data[i] && data[i]._details ) {
                            __details_remove( api, i );
                        }
                    }
                } );
            }
        };
        
        // Strings for the method names to help minification
        var _emp = '';
        var _child_obj = _emp+'row().child';
        var _child_mth = _child_obj+'()';
        
        // data can be:
        //  tr
        //  string
        //  jQuery or array of any of the above
        _api_register( _child_mth, function ( data, klass ) {
            var ctx = this.context;
        
            if ( data === undefined ) {
                // get
                return ctx.length && this.length && ctx[0].aoData[ this[0] ]
                    ? ctx[0].aoData[ this[0] ]._details
                    : undefined;
            }
            else if ( data === true ) {
                // show
                this.child.show();
            }
            else if ( data === false ) {
                // remove
                __details_remove( this );
            }
            else if ( ctx.length && this.length ) {
                // set
                __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass );
            }
        
            return this;
        } );
        
        
        _api_register( [
            _child_obj+'.show()',
            _child_mth+'.show()' // only when `child()` was called with parameters (without
        ], function () {         // it returns an object and this method is not executed)
            __details_display( this, true );
            return this;
        } );
        
        
        _api_register( [
            _child_obj+'.hide()',
            _child_mth+'.hide()' // only when `child()` was called with parameters (without
        ], function () {         // it returns an object and this method is not executed)
            __details_display( this, false );
            return this;
        } );
        
        
        _api_register( [
            _child_obj+'.remove()',
            _child_mth+'.remove()' // only when `child()` was called with parameters (without
        ], function () {           // it returns an object and this method is not executed)
            __details_remove( this );
            return this;
        } );
        
        
        _api_register( _child_obj+'.isShown()', function () {
            var ctx = this.context;
        
            if ( ctx.length && this.length && ctx[0].aoData[ this[0] ] ) {
                // _detailsShown as false or undefined will fall through to return false
                return ctx[0].aoData[ this[0] ]._detailsShow || false;
            }
            return false;
        } );
        
        
        
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
         * Columns
         *
         * {integer}           - column index (>=0 count from left, <0 count from right)
         * "{integer}:visIdx"  - visible column index (i.e. translate to column index)  (>=0 count from left, <0 count from right)
         * "{integer}:visible" - alias for {integer}:visIdx  (>=0 count from left, <0 count from right)
         * "{string}:name"     - column name
         * "{string}"          - jQuery selector on column header nodes
         *
         */
        
        // can be an array of these items, comma separated list, or an array of comma
        // separated lists
        
        var __re_column_selector = /^([^:]+)?:(name|title|visIdx|visible)$/;
        
        
        // r1 and r2 are redundant - but it means that the parameters match for the
        // iterator callback in columns().data()
        var __columnData = function ( settings, column, r1, r2, rows, type ) {
            var a = [];
            for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
                a.push( _fnGetCellData( settings, rows[row], column, type ) );
            }
            return a;
        };
        
        
        var __column_header = function ( settings, column, row ) {
            var header = settings.aoHeader;
            var target = row !== undefined
                ? row
                : settings.bSortCellsTop // legacy support
                    ? 0
                    : header.length - 1;
        
            return header[target][column].cell;
        };
        
        var __column_selector = function ( settings, selector, opts )
        {
            var
                columns = settings.aoColumns,
                names = _pluck( columns, 'sName' ),
                titles = _pluck( columns, 'sTitle' ),
                cells = DataTable.util.get('[].[].cell')(settings.aoHeader),
                nodes = _unique( _flatten([], cells) );
            
            var run = function ( s ) {
                var selInt = _intVal( s );
        
                // Selector - all
                if ( s === '' ) {
                    return _range( columns.length );
                }
        
                // Selector - index
                if ( selInt !== null ) {
                    return [ selInt >= 0 ?
                        selInt : // Count from left
                        columns.length + selInt // Count from right (+ because its a negative value)
                    ];
                }
        
                // Selector = function
                if ( typeof s === 'function' ) {
                    var rows = _selector_row_indexes( settings, opts );
        
                    return columns.map(function (col, idx) {
                        return s(
                                idx,
                                __columnData( settings, idx, 0, 0, rows ),
                                __column_header( settings, idx )
                            ) ? idx : null;
                    });
                }
        
                // jQuery or string selector
                var match = typeof s === 'string' ?
                    s.match( __re_column_selector ) :
                    '';
        
                if ( match ) {
                    switch( match[2] ) {
                        case 'visIdx':
                        case 'visible':
                            // Selector is a column index
                            if (match[1] && match[1].match(/^\d+$/)) {
                                var idx = parseInt( match[1], 10 );
        
                                // Visible index given, convert to column index
                                if ( idx < 0 ) {
                                    // Counting from the right
                                    var visColumns = columns.map( function (col,i) {
                                        return col.bVisible ? i : null;
                                    } );
                                    return [ visColumns[ visColumns.length + idx ] ];
                                }
                                // Counting from the left
                                return [ _fnVisibleToColumnIndex( settings, idx ) ];
                            }
                            
                            return columns.map( function (col, idx) {
                                // Not visible, can't match
                                if (! col.bVisible) {
                                    return null;
                                }
        
                                // Selector
                                if (match[1]) {
                                    return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null;
                                }
        
                                // `:visible` on its own
                                return idx;
                            } );
        
                        case 'name':
                            // match by name. `names` is column index complete and in order
                            return names.map( function (name, i) {
                                return name === match[1] ? i : null;
                            } );
        
                        case 'title':
                            // match by column title
                            return titles.map( function (title, i) {
                                return title === match[1] ? i : null;
                            } );
        
                        default:
                            return [];
                    }
                }
        
                // Cell in the table body
                if ( s.nodeName && s._DT_CellIndex ) {
                    return [ s._DT_CellIndex.column ];
                }
        
                // jQuery selector on the TH elements for the columns
                var jqResult = $( nodes )
                    .filter( s )
                    .map( function () {
                        return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order
                    } )
                    .toArray();
        
                if ( jqResult.length || ! s.nodeName ) {
                    return jqResult;
                }
        
                // Otherwise a node which might have a `dt-column` data attribute, or be
                // a child or such an element
                var host = $(s).closest('*[data-dt-column]');
                return host.length ?
                    [ host.data('dt-column') ] :
                    [];
            };
        
            return _selector_run( 'column', selector, run, settings, opts );
        };
        
        
        var __setColumnVis = function ( settings, column, vis ) {
            var
                cols = settings.aoColumns,
                col  = cols[ column ],
                data = settings.aoData,
                cells, i, ien, tr;
        
            // Get
            if ( vis === undefined ) {
                return col.bVisible;
            }
        
            // Set
            // No change
            if ( col.bVisible === vis ) {
                return false;
            }
        
            if ( vis ) {
                // Insert column
                // Need to decide if we should use appendChild or insertBefore
                var insertBefore = _pluck(cols, 'bVisible').indexOf(true, column+1);
        
                for ( i=0, ien=data.length ; i<ien ; i++ ) {
                    if (data[i]) {
                        tr = data[i].nTr;
                        cells = data[i].anCells;
        
                        if ( tr ) {
                            // insertBefore can act like appendChild if 2nd arg is null
                            tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
                        }
                    }
                }
            }
            else {
                // Remove column
                $( _pluck( settings.aoData, 'anCells', column ) ).detach();
            }
        
            // Common actions
            col.bVisible = vis;
        
            _colGroup(settings);
            
            return true;
        };
        
        
        _api_register( 'columns()', function ( selector, opts ) {
            // argument shifting
            if ( selector === undefined ) {
                selector = '';
            }
            else if ( $.isPlainObject( selector ) ) {
                opts = selector;
                selector = '';
            }
        
            opts = _selector_opts( opts );
        
            var inst = this.iterator( 'table', function ( settings ) {
                return __column_selector( settings, selector, opts );
            }, 1 );
        
            // Want argument shifting here and in _row_selector?
            inst.selector.cols = selector;
            inst.selector.opts = opts;
        
            return inst;
        } );
        
        _api_registerPlural( 'columns().header()', 'column().header()', function ( row ) {
            return this.iterator( 'column', function (settings, column) {
                return __column_header(settings, column, row);
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().footer()', 'column().footer()', function ( row ) {
            return this.iterator( 'column', function ( settings, column ) {
                var footer = settings.aoFooter;
        
                if (! footer.length) {
                    return null;
                }
        
                return settings.aoFooter[row !== undefined ? row : 0][column].cell;
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().data()', 'column().data()', function () {
            return this.iterator( 'column-rows', __columnData, 1 );
        } );
        
        _api_registerPlural( 'columns().render()', 'column().render()', function ( type ) {
            return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
                return __columnData( settings, column, i, j, rows, type );
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
            return this.iterator( 'column', function ( settings, column ) {
                return settings.aoColumns[column].mData;
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) {
            return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
                return _pluck_order( settings.aoData, rows,
                    type === 'search' ? '_aFilterData' : '_aSortData', column
                );
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().init()', 'column().init()', function () {
            return this.iterator( 'column', function ( settings, column ) {
                return settings.aoColumns[column];
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
            return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
                return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().titles()', 'column().title()', function (title, row) {
            return this.iterator( 'column', function ( settings, column ) {
                // Argument shifting
                if (typeof title === 'number') {
                    row = title;
                    title = undefined;
                }
        
                var span = $('span.dt-column-title', this.column(column).header(row));
        
                if (title !== undefined) {
                    span.html(title);
                    return this;
                }
        
                return span.html();
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().types()', 'column().type()', function () {
            return this.iterator( 'column', function ( settings, column ) {
                var type = settings.aoColumns[column].sType;
        
                // If the type was invalidated, then resolve it. This actually does
                // all columns at the moment. Would only happen once if getting all
                // column's data types.
                if (! type) {
                    _fnColumnTypes(settings);
                }
        
                return type;
            }, 1 );
        } );
        
        _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
            var that = this;
            var changed = [];
            var ret = this.iterator( 'column', function ( settings, column ) {
                if ( vis === undefined ) {
                    return settings.aoColumns[ column ].bVisible;
                } // else
                
                if (__setColumnVis( settings, column, vis )) {
                    changed.push(column);
                }
            } );
        
            // Group the column visibility changes
            if ( vis !== undefined ) {
                this.iterator( 'table', function ( settings ) {
                    // Redraw the header after changes
                    _fnDrawHead( settings, settings.aoHeader );
                    _fnDrawHead( settings, settings.aoFooter );
            
                    // Update colspan for no records display. Child rows and extensions will use their own
                    // listeners to do this - only need to update the empty table item here
                    if ( ! settings.aiDisplay.length ) {
                        $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings));
                    }
            
                    _fnSaveState( settings );
        
                    // Second loop once the first is done for events
                    that.iterator( 'column', function ( settings, column ) {
                        if (changed.includes(column)) {
                            _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
                        }
                    } );
        
                    if ( changed.length && (calc === undefined || calc) ) {
                        that.columns.adjust();
                    }
                });
            }
        
            return ret;
        } );
        
        _api_registerPlural( 'columns().widths()', 'column().width()', function () {
            // Injects a fake row into the table for just a moment so the widths can
            // be read, regardless of colspan in the header and rows being present in
            // the body
            var columns = this.columns(':visible').count();
            var row = $('<tr>').html('<td>' + Array(columns).join('</td><td>') + '</td>');
        
            $(this.table().body()).append(row);
        
            var widths = row.children().map(function () {
                return $(this).outerWidth();
            });
        
            row.remove();
            
            return this.iterator( 'column', function ( settings, column ) {
                var visIdx = _fnColumnIndexToVisible( settings, column );
        
                return visIdx !== null ? widths[visIdx] : 0;
            }, 1);
        } );
        
        _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
            return this.iterator( 'column', function ( settings, column ) {
                return type === 'visible' ?
                    _fnColumnIndexToVisible( settings, column ) :
                    column;
            }, 1 );
        } );
        
        _api_register( 'columns.adjust()', function () {
            return this.iterator( 'table', function ( settings ) {
                _fnAdjustColumnSizing( settings );
            }, 1 );
        } );
        
        _api_register( 'column.index()', function ( type, idx ) {
            if ( this.context.length !== 0 ) {
                var ctx = this.context[0];
        
                if ( type === 'fromVisible' || type === 'toData' ) {
                    return _fnVisibleToColumnIndex( ctx, idx );
                }
                else if ( type === 'fromData' || type === 'toVisible' ) {
                    return _fnColumnIndexToVisible( ctx, idx );
                }
            }
        } );
        
        _api_register( 'column()', function ( selector, opts ) {
            return _selector_first( this.columns( selector, opts ) );
        } );
        
        var __cell_selector = function ( settings, selector, opts )
        {
            var data = settings.aoData;
            var rows = _selector_row_indexes( settings, opts );
            var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) );
            var allCells = $(_flatten( [], cells ));
            var row;
            var columns = settings.aoColumns.length;
            var a, i, ien, j, o, host;
        
            var run = function ( s ) {
                var fnSelector = typeof s === 'function';
        
                if ( s === null || s === undefined || fnSelector ) {
                    // All cells and function selectors
                    a = [];
        
                    for ( i=0, ien=rows.length ; i<ien ; i++ ) {
                        row = rows[i];
        
                        for ( j=0 ; j<columns ; j++ ) {
                            o = {
                                row: row,
                                column: j
                            };
        
                            if ( fnSelector ) {
                                // Selector - function
                                host = data[ row ];
        
                                if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) {
                                    a.push( o );
                                }
                            }
                            else {
                                // Selector - all
                                a.push( o );
                            }
                        }
                    }
        
                    return a;
                }
                
                // Selector - index
                if ( $.isPlainObject( s ) ) {
                    // Valid cell index and its in the array of selectable rows
                    return s.column !== undefined && s.row !== undefined && rows.indexOf(s.row) !== -1 ?
                        [s] :
                        [];
                }
        
                // Selector - jQuery filtered cells
                var jqResult = allCells
                    .filter( s )
                    .map( function (i, el) {
                        return { // use a new object, in case someone changes the values
                            row:    el._DT_CellIndex.row,
                            column: el._DT_CellIndex.column
                        };
                    } )
                    .toArray();
        
                if ( jqResult.length || ! s.nodeName ) {
                    return jqResult;
                }
        
                // Otherwise the selector is a node, and there is one last option - the
                // element might be a child of an element which has dt-row and dt-column
                // data attributes
                host = $(s).closest('*[data-dt-row]');
                return host.length ?
                    [ {
                        row: host.data('dt-row'),
                        column: host.data('dt-column')
                    } ] :
                    [];
            };
        
            return _selector_run( 'cell', selector, run, settings, opts );
        };
        
        
        
        
        _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) {
            // Argument shifting
            if ( $.isPlainObject( rowSelector ) ) {
                // Indexes
                if ( rowSelector.row === undefined ) {
                    // Selector options in first parameter
                    opts = rowSelector;
                    rowSelector = null;
                }
                else {
                    // Cell index objects in first parameter
                    opts = columnSelector;
                    columnSelector = null;
                }
            }
            if ( $.isPlainObject( columnSelector ) ) {
                opts = columnSelector;
                columnSelector = null;
            }
        
            // Cell selector
            if ( columnSelector === null || columnSelector === undefined ) {
                return this.iterator( 'table', function ( settings ) {
                    return __cell_selector( settings, rowSelector, _selector_opts( opts ) );
                } );
            }
        
            // The default built in options need to apply to row and columns
            var internalOpts = opts ? {
                page: opts.page,
                order: opts.order,
                search: opts.search
            } : {};
        
            // Row + column selector
            var columns = this.columns( columnSelector, internalOpts );
            var rows = this.rows( rowSelector, internalOpts );
            var i, ien, j, jen;
        
            var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) {
                var a = [];
        
                for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) {
                    for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) {
                        a.push( {
                            row:    rows[idx][i],
                            column: columns[idx][j]
                        } );
                    }
                }
        
                return a;
            }, 1 );
        
            // There is currently only one extension which uses a cell selector extension
            // It is a _major_ performance drag to run this if it isn't needed, so this is
            // an extension specific check at the moment
            var cells = opts && opts.selected ?
                this.cells( cellsNoOpts, opts ) :
                cellsNoOpts;
        
            $.extend( cells.selector, {
                cols: columnSelector,
                rows: rowSelector,
                opts: opts
            } );
        
            return cells;
        } );
        
        
        _api_registerPlural( 'cells().nodes()', 'cell().node()', function () {
            return this.iterator( 'cell', function ( settings, row, column ) {
                var data = settings.aoData[ row ];
        
                return data && data.anCells ?
                    data.anCells[ column ] :
                    undefined;
            }, 1 );
        } );
        
        
        _api_register( 'cells().data()', function () {
            return this.iterator( 'cell', function ( settings, row, column ) {
                return _fnGetCellData( settings, row, column );
            }, 1 );
        } );
        
        
        _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) {
            type = type === 'search' ? '_aFilterData' : '_aSortData';
        
            return this.iterator( 'cell', function ( settings, row, column ) {
                return settings.aoData[ row ][ type ][ column ];
            }, 1 );
        } );
        
        
        _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) {
            return this.iterator( 'cell', function ( settings, row, column ) {
                return _fnGetCellData( settings, row, column, type );
            }, 1 );
        } );
        
        
        _api_registerPlural( 'cells().indexes()', 'cell().index()', function () {
            return this.iterator( 'cell', function ( settings, row, column ) {
                return {
                    row: row,
                    column: column,
                    columnVisible: _fnColumnIndexToVisible( settings, column )
                };
            }, 1 );
        } );
        
        
        _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) {
            return this.iterator( 'cell', function ( settings, row, column ) {
                _fnInvalidate( settings, row, src, column );
            } );
        } );
        
        
        
        _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) {
            return _selector_first( this.cells( rowSelector, columnSelector, opts ) );
        } );
        
        
        _api_register( 'cell().data()', function ( data ) {
            var ctx = this.context;
            var cell = this[0];
        
            if ( data === undefined ) {
                // Get
                return ctx.length && cell.length ?
                    _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) :
                    undefined;
            }
        
            // Set
            _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data );
            _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column );
        
            return this;
        } );
        
        
        
        /**
         * Get current ordering (sorting) that has been applied to the table.
         *
         * @returns {array} 2D array containing the sorting information for the first
         *   table in the current context. Each element in the parent array represents
         *   a column being sorted upon (i.e. multi-sorting with two columns would have
         *   2 inner arrays). The inner arrays may have 2 or 3 elements. The first is
         *   the column index that the sorting condition applies to, the second is the
         *   direction of the sort (`desc` or `asc`) and, optionally, the third is the
         *   index of the sorting order from the `column.sorting` initialisation array.
         *//**
         * Set the ordering for the table.
         *
         * @param {integer} order Column index to sort upon.
         * @param {string} direction Direction of the sort to be applied (`asc` or `desc`)
         * @returns {DataTables.Api} this
         *//**
         * Set the ordering for the table.
         *
         * @param {array} order 1D array of sorting information to be applied.
         * @param {array} [...] Optional additional sorting conditions
         * @returns {DataTables.Api} this
         *//**
         * Set the ordering for the table.
         *
         * @param {array} order 2D array of sorting information to be applied.
         * @returns {DataTables.Api} this
         */
        _api_register( 'order()', function ( order, dir ) {
            var ctx = this.context;
            var args = Array.prototype.slice.call( arguments );
        
            if ( order === undefined ) {
                // get
                return ctx.length !== 0 ?
                    ctx[0].aaSorting :
                    undefined;
            }
        
            // set
            if ( typeof order === 'number' ) {
                // Simple column / direction passed in
                order = [ [ order, dir ] ];
            }
            else if ( args.length > 1 ) {
                // Arguments passed in (list of 1D arrays)
                order = args;
            }
            // otherwise a 2D array was passed in
        
            return this.iterator( 'table', function ( settings ) {
                settings.aaSorting = Array.isArray(order) ? order.slice() : order;
            } );
        } );
        
        
        /**
         * Attach a sort listener to an element for a given column
         *
         * @param {node|jQuery|string} node Identifier for the element(s) to attach the
         *   listener to. This can take the form of a single DOM node, a jQuery
         *   collection of nodes or a jQuery selector which will identify the node(s).
         * @param {integer} column the column that a click on this node will sort on
         * @param {function} [callback] callback function when sort is run
         * @returns {DataTables.Api} this
         */
        _api_register( 'order.listener()', function ( node, column, callback ) {
            return this.iterator( 'table', function ( settings ) {
                _fnSortAttachListener(settings, node, {}, column, callback);
            } );
        } );
        
        
        _api_register( 'order.fixed()', function ( set ) {
            if ( ! set ) {
                var ctx = this.context;
                var fixed = ctx.length ?
                    ctx[0].aaSortingFixed :
                    undefined;
        
                return Array.isArray( fixed ) ?
                    { pre: fixed } :
                    fixed;
            }
        
            return this.iterator( 'table', function ( settings ) {
                settings.aaSortingFixed = $.extend( true, {}, set );
            } );
        } );
        
        
        // Order by the selected column(s)
        _api_register( [
            'columns().order()',
            'column().order()'
        ], function ( dir ) {
            var that = this;
        
            if ( ! dir ) {
                return this.iterator( 'column', function ( settings, idx ) {
                    var sort = _fnSortFlatten( settings );
        
                    for ( var i=0, ien=sort.length ; i<ien ; i++ ) {
                        if ( sort[i].col === idx ) {
                            return sort[i].dir;
                        }
                    }
        
                    return null;
                }, 1 );
            }
            else {
                return this.iterator( 'table', function ( settings, i ) {
                    settings.aaSorting = that[i].map( function (col) {
                        return [ col, dir ];
                    } );
                } );
            }
        } );
        
        _api_registerPlural('columns().orderable()', 'column().orderable()', function ( directions ) {
            return this.iterator( 'column', function ( settings, idx ) {
                var col = settings.aoColumns[idx];
        
                return directions ?
                    col.asSorting :
                    col.bSortable;
            }, 1 );
        } );
        
        
        _api_register( 'processing()', function ( show ) {
            return this.iterator( 'table', function ( ctx ) {
                _fnProcessingDisplay( ctx, show );
            } );
        } );
        
        
        _api_register( 'search()', function ( input, regex, smart, caseInsen ) {
            var ctx = this.context;
        
            if ( input === undefined ) {
                // get
                return ctx.length !== 0 ?
                    ctx[0].oPreviousSearch.search :
                    undefined;
            }
        
            // set
            return this.iterator( 'table', function ( settings ) {
                if ( ! settings.oFeatures.bFilter ) {
                    return;
                }
        
                if (typeof regex === 'object') {
                    // New style options to pass to the search builder
                    _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, regex, {
                        search: input
                    } ) );
                }
                else {
                    // Compat for the old options
                    _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, {
                        search: input,
                        regex:  regex === null ? false : regex,
                        smart:  smart === null ? true  : smart,
                        caseInsensitive: caseInsen === null ? true : caseInsen
                    } ) );
                }
            } );
        } );
        
        _api_register( 'search.fixed()', function ( name, search ) {
            var ret = this.iterator( true, 'table', function ( settings ) {
                var fixed = settings.searchFixed;
        
                if (! name) {
                    return Object.keys(fixed)
                }
                else if (search === undefined) {
                    return fixed[name];
                }
                else if (search === null) {
                    delete fixed[name];
                }
                else {
                    fixed[name] = search;
                }
        
                return this;
            } );
        
            return name !== undefined && search === undefined
                ? ret[0]
                : ret;
        } );
        
        _api_registerPlural(
            'columns().search()',
            'column().search()',
            function ( input, regex, smart, caseInsen ) {
                return this.iterator( 'column', function ( settings, column ) {
                    var preSearch = settings.aoPreSearchCols;
        
                    if ( input === undefined ) {
                        // get
                        return preSearch[ column ].search;
                    }
        
                    // set
                    if ( ! settings.oFeatures.bFilter ) {
                        return;
                    }
        
                    if (typeof regex === 'object') {
                        // New style options to pass to the search builder
                        $.extend( preSearch[ column ], regex, {
                            search: input
                        } );
                    }
                    else {
                        // Old style (with not all options available)
                        $.extend( preSearch[ column ], {
                            search: input,
                            regex:  regex === null ? false : regex,
                            smart:  smart === null ? true  : smart,
                            caseInsensitive: caseInsen === null ? true : caseInsen
                        } );
                    }
        
                    _fnFilterComplete( settings, settings.oPreviousSearch );
                } );
            }
        );
        
        _api_register([
                'columns().search.fixed()',
                'column().search.fixed()'
            ],
            function ( name, search ) {
                var ret = this.iterator( true, 'column', function ( settings, colIdx ) {
                    var fixed = settings.aoColumns[colIdx].searchFixed;
        
                    if (! name) {
                        return Object.keys(fixed)
                    }
                    else if (search === undefined) {
                        return fixed[name];
                    }
                    else if (search === null) {
                        delete fixed[name];
                    }
                    else {
                        fixed[name] = search;
                    }
        
                    return this;
                } );
        
                return name !== undefined && search === undefined
                    ? ret[0]
                    : ret;
            }
        );
        /*
         * State API methods
         */
        
        _api_register( 'state()', function ( set, ignoreTime ) {
            // getter
            if ( ! set ) {
                return this.context.length ?
                    this.context[0].oSavedState :
                    null;
            }
        
            var setMutate = $.extend( true, {}, set );
        
            // setter
            return this.iterator( 'table', function ( settings ) {
                if ( ignoreTime !== false ) {
                    setMutate.time = +new Date() + 100;
                }
        
                _fnImplementState( settings, setMutate, function(){} );
            } );
        } );
        
        
        _api_register( 'state.clear()', function () {
            return this.iterator( 'table', function ( settings ) {
                // Save an empty object
                settings.fnStateSaveCallback.call( settings.oInstance, settings, {} );
            } );
        } );
        
        
        _api_register( 'state.loaded()', function () {
            return this.context.length ?
                this.context[0].oLoadedState :
                null;
        } );
        
        
        _api_register( 'state.save()', function () {
            return this.iterator( 'table', function ( settings ) {
                _fnSaveState( settings );
            } );
        } );
        
        /**
         * Set the libraries that DataTables uses, or the global objects.
         * Note that the arguments can be either way around (legacy support)
         * and the second is optional. See docs.
         */
        DataTable.use = function (arg1, arg2) {
            // Reverse arguments for legacy support
            var module = typeof arg1 === 'string'
                ? arg2
                : arg1;
            var type = typeof arg2 === 'string'
                ? arg2
                : arg1;
        
            // Getter
            if (module === undefined && typeof type === 'string') {
                switch (type) {
                    case 'lib':
                    case 'jq':
                        return $;
        
                    case 'win':
                        return window;
        
                    case 'datetime':
                        return DataTable.DateTime;
        
                    case 'luxon':
                        return __luxon;
        
                    case 'moment':
                        return __moment;
        
                    default:
                        return null;
                }
            }
        
            // Setter
            if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) {
                $ = module;
            }
            else if (type == 'win' || (module && module.document)) {
                window = module;
                document = module.document;
            }
            else if (type === 'datetime' || (module && module.type === 'DateTime')) {
                DataTable.DateTime = module;
            }
            else if (type === 'luxon' || (module && module.FixedOffsetZone)) {
                __luxon = module;
            }
            else if (type === 'moment' || (module && module.isMoment)) {
                __moment = module;
            }
        }
        
        /**
         * CommonJS factory function pass through. This will check if the arguments
         * given are a window object or a jQuery object. If so they are set
         * accordingly.
         * @param {*} root Window
         * @param {*} jq jQUery
         * @returns {boolean} Indicator
         */
        DataTable.factory = function (root, jq) {
            var is = false;
        
            // Test if the first parameter is a window object
            if (root && root.document) {
                window = root;
                document = root.document;
            }
        
            // Test if the second parameter is a jQuery object
            if (jq && jq.fn && jq.fn.jquery) {
                $ = jq;
                is = true;
            }
        
            return is;
        }
        
        /**
         * Provide a common method for plug-ins to check the version of DataTables being
         * used, in order to ensure compatibility.
         *
         *  @param {string} version Version string to check for, in the format "X.Y.Z".
         *    Note that the formats "X" and "X.Y" are also acceptable.
         *  @param {string} [version2=current DataTables version] As above, but optional.
         *   If not given the current DataTables version will be used.
         *  @returns {boolean} true if this version of DataTables is greater or equal to
         *    the required version, or false if this version of DataTales is not
         *    suitable
         *  @static
         *  @dtopt API-Static
         *
         *  @example
         *    alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
         */
        DataTable.versionCheck = function( version, version2 )
        {
            var aThis = version2 ?
                version2.split('.') :
                DataTable.version.split('.');
            var aThat = version.split('.');
            var iThis, iThat;
        
            for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) {
                iThis = parseInt( aThis[i], 10 ) || 0;
                iThat = parseInt( aThat[i], 10 ) || 0;
        
                // Parts are the same, keep comparing
                if (iThis === iThat) {
                    continue;
                }
        
                // Parts are different, return immediately
                return iThis > iThat;
            }
        
            return true;
        };
        
        
        /**
         * Check if a `<table>` node is a DataTable table already or not.
         *
         *  @param {node|jquery|string} table Table node, jQuery object or jQuery
         *      selector for the table to test. Note that if more than more than one
         *      table is passed on, only the first will be checked
         *  @returns {boolean} true the table given is a DataTable, or false otherwise
         *  @static
         *  @dtopt API-Static
         *
         *  @example
         *    if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
         *      $('#example').dataTable();
         *    }
         */
        DataTable.isDataTable = function ( table )
        {
            var t = $(table).get(0);
            var is = false;
        
            if ( table instanceof DataTable.Api ) {
                return true;
            }
        
            $.each( DataTable.settings, function (i, o) {
                var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
                var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
        
                if ( o.nTable === t || head === t || foot === t ) {
                    is = true;
                }
            } );
        
            return is;
        };
        
        
        /**
         * Get all DataTable tables that have been initialised - optionally you can
         * select to get only currently visible tables.
         *
         *  @param {boolean} [visible=false] Flag to indicate if you want all (default)
         *    or visible tables only.
         *  @returns {array} Array of `table` nodes (not DataTable instances) which are
         *    DataTables
         *  @static
         *  @dtopt API-Static
         *
         *  @example
         *    $.each( $.fn.dataTable.tables(true), function () {
         *      $(table).DataTable().columns.adjust();
         *    } );
         */
        DataTable.tables = function ( visible )
        {
            var api = false;
        
            if ( $.isPlainObject( visible ) ) {
                api = visible.api;
                visible = visible.visible;
            }
        
            var a = DataTable.settings
                .filter( function (o) {
                    return !visible || (visible && $(o.nTable).is(':visible')) 
                        ? true
                        : false;
                } )
                .map( function (o) {
                    return o.nTable;
                });
        
            return api ?
                new _Api( a ) :
                a;
        };
        
        
        /**
         * Convert from camel case parameters to Hungarian notation. This is made public
         * for the extensions to provide the same ability as DataTables core to accept
         * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
         * parameters.
         *
         *  @param {object} src The model object which holds all parameters that can be
         *    mapped.
         *  @param {object} user The object to convert from camel case to Hungarian.
         *  @param {boolean} force When set to `true`, properties which already have a
         *    Hungarian value in the `user` object will be overwritten. Otherwise they
         *    won't be.
         */
        DataTable.camelToHungarian = _fnCamelToHungarian;
        
        
        
        /**
         *
         */
        _api_register( '$()', function ( selector, opts ) {
            var
                rows   = this.rows( opts ).nodes(), // Get all rows
                jqRows = $(rows);
        
            return $( [].concat(
                jqRows.filter( selector ).toArray(),
                jqRows.find( selector ).toArray()
            ) );
        } );
        
        
        // jQuery functions to operate on the tables
        $.each( [ 'on', 'one', 'off' ], function (i, key) {
            _api_register( key+'()', function ( /* event, handler */ ) {
                var args = Array.prototype.slice.call(arguments);
        
                // Add the `dt` namespace automatically if it isn't already present
                args[0] = args[0].split( /\s/ ).map( function ( e ) {
                    return ! e.match(/\.dt\b/) ?
                        e+'.dt' :
                        e;
                    } ).join( ' ' );
        
                var inst = $( this.tables().nodes() );
                inst[key].apply( inst, args );
                return this;
            } );
        } );
        
        
        _api_register( 'clear()', function () {
            return this.iterator( 'table', function ( settings ) {
                _fnClearTable( settings );
            } );
        } );
        
        
        _api_register( 'error()', function (msg) {
            return this.iterator( 'table', function ( settings ) {
                _fnLog( settings, 0, msg );
            } );
        } );
        
        
        _api_register( 'settings()', function () {
            return new _Api( this.context, this.context );
        } );
        
        
        _api_register( 'init()', function () {
            var ctx = this.context;
            return ctx.length ? ctx[0].oInit : null;
        } );
        
        
        _api_register( 'data()', function () {
            return this.iterator( 'table', function ( settings ) {
                return _pluck( settings.aoData, '_aData' );
            } ).flatten();
        } );
        
        
        _api_register( 'trigger()', function ( name, args, bubbles ) {
            return this.iterator( 'table', function ( settings ) {
                return _fnCallbackFire( settings, null, name, args, bubbles );
            } ).flatten();
        } );
        
        
        _api_register( 'ready()', function ( fn ) {
            var ctx = this.context;
        
            // Get status of first table
            if (! fn) {
                return ctx.length
                    ? (ctx[0]._bInitComplete || false)
                    : null;
            }
        
            // Function to run either once the table becomes ready or
            // immediately if it is already ready.
            return this.tables().every(function () {
                if (this.context[0]._bInitComplete) {
                    fn.call(this);
                }
                else {
                    this.on('init', function () {
                        fn.call(this);
                    });
                }
            } );
        } );
        
        
        _api_register( 'destroy()', function ( remove ) {
            remove = remove || false;
        
            return this.iterator( 'table', function ( settings ) {
                var classes   = settings.oClasses;
                var table     = settings.nTable;
                var tbody     = settings.nTBody;
                var thead     = settings.nTHead;
                var tfoot     = settings.nTFoot;
                var jqTable   = $(table);
                var jqTbody   = $(tbody);
                var jqWrapper = $(settings.nTableWrapper);
                var rows      = settings.aoData.map( function (r) { return r ? r.nTr : null; } );
                var orderClasses = classes.order;
        
                // Flag to note that the table is currently being destroyed - no action
                // should be taken
                settings.bDestroying = true;
        
                // Fire off the destroy callbacks for plug-ins etc
                _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings], true );
        
                // If not being removed from the document, make all columns visible
                if ( ! remove ) {
                    new _Api( settings ).columns().visible( true );
                }
        
                // Blitz all `DT` namespaced events (these are internal events, the
                // lowercase, `dt` events are user subscribed and they are responsible
                // for removing them
                jqWrapper.off('.DT').find(':not(tbody *)').off('.DT');
                $(window).off('.DT-'+settings.sInstance);
        
                // When scrolling we had to break the table up - restore it
                if ( table != thead.parentNode ) {
                    jqTable.children('thead').detach();
                    jqTable.append( thead );
                }
        
                if ( tfoot && table != tfoot.parentNode ) {
                    jqTable.children('tfoot').detach();
                    jqTable.append( tfoot );
                }
        
                settings.colgroup.remove();
        
                settings.aaSorting = [];
                settings.aaSortingFixed = [];
                _fnSortingClasses( settings );
        
                $('th, td', thead)
                    .removeClass(
                        orderClasses.canAsc + ' ' +
                        orderClasses.canDesc + ' ' +
                        orderClasses.isAsc + ' ' +
                        orderClasses.isDesc
                    )
                    .css('width', '');
        
                // Add the TR elements back into the table in their original order
                jqTbody.children().detach();
                jqTbody.append( rows );
        
                var orig = settings.nTableWrapper.parentNode;
                var insertBefore = settings.nTableWrapper.nextSibling;
        
                // Remove the DataTables generated nodes, events and classes
                var removedMethod = remove ? 'remove' : 'detach';
                jqTable[ removedMethod ]();
                jqWrapper[ removedMethod ]();
        
                // If we need to reattach the table to the document
                if ( ! remove && orig ) {
                    // insertBefore acts like appendChild if !arg[1]
                    orig.insertBefore( table, insertBefore );
        
                    // Restore the width of the original table - was read from the style property,
                    // so we can restore directly to that
                    jqTable
                        .css( 'width', settings.sDestroyWidth )
                        .removeClass( classes.table );
                }
        
                /* Remove the settings object from the settings array */
                var idx = DataTable.settings.indexOf(settings);
                if ( idx !== -1 ) {
                    DataTable.settings.splice( idx, 1 );
                }
            } );
        } );
        
        
        // Add the `every()` method for rows, columns and cells in a compact form
        $.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
            _api_register( type+'s().every()', function ( fn ) {
                var opts = this.selector.opts;
                var api = this;
                var inst;
                var counter = 0;
        
                return this.iterator( 'every', function ( settings, selectedIdx, tableIdx ) {
                    inst = api[ type ](selectedIdx, opts);
        
                    if (type === 'cell') {
                        fn.call(inst, inst[0][0].row, inst[0][0].column, tableIdx, counter);
                    }
                    else {
                        fn.call(inst, selectedIdx, tableIdx, counter);
                    }
        
                    counter++;
                } );
            } );
        } );
        
        
        // i18n method for extensions to be able to use the language object from the
        // DataTable
        _api_register( 'i18n()', function ( token, def, plural ) {
            var ctx = this.context[0];
            var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
        
            if ( resolved === undefined ) {
                resolved = def;
            }
        
            if ( $.isPlainObject( resolved ) ) {
                resolved = plural !== undefined && resolved[ plural ] !== undefined ?
                    resolved[ plural ] :
                    resolved._;
            }
        
            return typeof resolved === 'string'
                ? resolved.replace( '%d', plural ) // nb: plural might be undefined,
                : resolved;
        } );
        
        /**
         * Version string for plug-ins to check compatibility. Allowed format is
         * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
         * only for non-release builds. See https://semver.org/ for more information.
         *  @member
         *  @type string
         *  @default Version number
         */
        DataTable.version = "2.1.5";
        
        /**
         * Private data store, containing all of the settings objects that are
         * created for the tables on a given page.
         *
         * Note that the `DataTable.settings` object is aliased to
         * `jQuery.fn.dataTableExt` through which it may be accessed and
         * manipulated, or `jQuery.fn.dataTable.settings`.
         *  @member
         *  @type array
         *  @default []
         *  @private
         */
        DataTable.settings = [];
        
        /**
         * Object models container, for the various models that DataTables has
         * available to it. These models define the objects that are used to hold
         * the active state and configuration of the table.
         *  @namespace
         */
        DataTable.models = {};
        
        
        
        /**
         * Template object for the way in which DataTables holds information about
         * search information for the global filter and individual column filters.
         *  @namespace
         */
        DataTable.models.oSearch = {
            /**
             * Flag to indicate if the filtering should be case insensitive or not
             */
            "caseInsensitive": true,
        
            /**
             * Applied search term
             */
            "search": "",
        
            /**
             * Flag to indicate if the search term should be interpreted as a
             * regular expression (true) or not (false) and therefore and special
             * regex characters escaped.
             */
            "regex": false,
        
            /**
             * Flag to indicate if DataTables is to use its smart filtering or not.
             */
            "smart": true,
        
            /**
             * Flag to indicate if DataTables should only trigger a search when
             * the return key is pressed.
             */
            "return": false
        };
        
        
        
        
        /**
         * Template object for the way in which DataTables holds information about
         * each individual row. This is the object format used for the settings
         * aoData array.
         *  @namespace
         */
        DataTable.models.oRow = {
            /**
             * TR element for the row
             */
            "nTr": null,
        
            /**
             * Array of TD elements for each row. This is null until the row has been
             * created.
             */
            "anCells": null,
        
            /**
             * Data object from the original data source for the row. This is either
             * an array if using the traditional form of DataTables, or an object if
             * using mData options. The exact type will depend on the passed in
             * data from the data source, or will be an array if using DOM a data
             * source.
             */
            "_aData": [],
        
            /**
             * Sorting data cache - this array is ostensibly the same length as the
             * number of columns (although each index is generated only as it is
             * needed), and holds the data that is used for sorting each column in the
             * row. We do this cache generation at the start of the sort in order that
             * the formatting of the sort data need be done only once for each cell
             * per sort. This array should not be read from or written to by anything
             * other than the master sorting methods.
             */
            "_aSortData": null,
        
            /**
             * Per cell filtering data cache. As per the sort data cache, used to
             * increase the performance of the filtering in DataTables
             */
            "_aFilterData": null,
        
            /**
             * Filtering data cache. This is the same as the cell filtering cache, but
             * in this case a string rather than an array. This is easily computed with
             * a join on `_aFilterData`, but is provided as a cache so the join isn't
             * needed on every search (memory traded for performance)
             */
            "_sFilterRow": null,
        
            /**
             * Denote if the original data source was from the DOM, or the data source
             * object. This is used for invalidating data, so DataTables can
             * automatically read data from the original source, unless uninstructed
             * otherwise.
             */
            "src": null,
        
            /**
             * Index in the aoData array. This saves an indexOf lookup when we have the
             * object, but want to know the index
             */
            "idx": -1,
        
            /**
             * Cached display value
             */
            displayData: null
        };
        
        
        /**
         * Template object for the column information object in DataTables. This object
         * is held in the settings aoColumns array and contains all the information that
         * DataTables needs about each individual column.
         *
         * Note that this object is related to {@link DataTable.defaults.column}
         * but this one is the internal data store for DataTables's cache of columns.
         * It should NOT be manipulated outside of DataTables. Any configuration should
         * be done through the initialisation options.
         *  @namespace
         */
        DataTable.models.oColumn = {
            /**
             * Column index.
             */
            "idx": null,
        
            /**
             * A list of the columns that sorting should occur on when this column
             * is sorted. That this property is an array allows multi-column sorting
             * to be defined for a column (for example first name / last name columns
             * would benefit from this). The values are integers pointing to the
             * columns to be sorted on (typically it will be a single integer pointing
             * at itself, but that doesn't need to be the case).
             */
            "aDataSort": null,
        
            /**
             * Define the sorting directions that are applied to the column, in sequence
             * as the column is repeatedly sorted upon - i.e. the first value is used
             * as the sorting direction when the column if first sorted (clicked on).
             * Sort it again (click again) and it will move on to the next index.
             * Repeat until loop.
             */
            "asSorting": null,
        
            /**
             * Flag to indicate if the column is searchable, and thus should be included
             * in the filtering or not.
             */
            "bSearchable": null,
        
            /**
             * Flag to indicate if the column is sortable or not.
             */
            "bSortable": null,
        
            /**
             * Flag to indicate if the column is currently visible in the table or not
             */
            "bVisible": null,
        
            /**
             * Store for manual type assignment using the `column.type` option. This
             * is held in store so we can manipulate the column's `sType` property.
             */
            "_sManualType": null,
        
            /**
             * Flag to indicate if HTML5 data attributes should be used as the data
             * source for filtering or sorting. True is either are.
             */
            "_bAttrSrc": false,
        
            /**
             * Developer definable function that is called whenever a cell is created (Ajax source,
             * etc) or processed for input (DOM source). This can be used as a compliment to mRender
             * allowing you to modify the DOM element (add background colour for example) when the
             * element is available.
             */
            "fnCreatedCell": null,
        
            /**
             * Function to get data from a cell in a column. You should <b>never</b>
             * access data directly through _aData internally in DataTables - always use
             * the method attached to this property. It allows mData to function as
             * required. This function is automatically assigned by the column
             * initialisation method
             */
            "fnGetData": null,
        
            /**
             * Function to set data for a cell in the column. You should <b>never</b>
             * set the data directly to _aData internally in DataTables - always use
             * this method. It allows mData to function as required. This function
             * is automatically assigned by the column initialisation method
             */
            "fnSetData": null,
        
            /**
             * Property to read the value for the cells in the column from the data
             * source array / object. If null, then the default content is used, if a
             * function is given then the return from the function is used.
             */
            "mData": null,
        
            /**
             * Partner property to mData which is used (only when defined) to get
             * the data - i.e. it is basically the same as mData, but without the
             * 'set' option, and also the data fed to it is the result from mData.
             * This is the rendering method to match the data method of mData.
             */
            "mRender": null,
        
            /**
             * The class to apply to all TD elements in the table's TBODY for the column
             */
            "sClass": null,
        
            /**
             * When DataTables calculates the column widths to assign to each column,
             * it finds the longest string in each column and then constructs a
             * temporary table and reads the widths from that. The problem with this
             * is that "mmm" is much wider then "iiii", but the latter is a longer
             * string - thus the calculation can go wrong (doing it properly and putting
             * it into an DOM object and measuring that is horribly(!) slow). Thus as
             * a "work around" we provide this option. It will append its value to the
             * text that is found to be the longest string for the column - i.e. padding.
             */
            "sContentPadding": null,
        
            /**
             * Allows a default value to be given for a column's data, and will be used
             * whenever a null data source is encountered (this can be because mData
             * is set to null, or because the data source itself is null).
             */
            "sDefaultContent": null,
        
            /**
             * Name for the column, allowing reference to the column by name as well as
             * by index (needs a lookup to work by name).
             */
            "sName": null,
        
            /**
             * Custom sorting data type - defines which of the available plug-ins in
             * afnSortData the custom sorting will use - if any is defined.
             */
            "sSortDataType": 'std',
        
            /**
             * Class to be applied to the header element when sorting on this column
             */
            "sSortingClass": null,
        
            /**
             * Title of the column - what is seen in the TH element (nTh).
             */
            "sTitle": null,
        
            /**
             * Column sorting and filtering type
             */
            "sType": null,
        
            /**
             * Width of the column
             */
            "sWidth": null,
        
            /**
             * Width of the column when it was first "encountered"
             */
            "sWidthOrig": null,
        
            /** Cached string which is the longest in the column */
            maxLenString: null,
        
            /**
             * Store for named searches
             */
            searchFixed: null
        };
        
        
        /*
         * Developer note: The properties of the object below are given in Hungarian
         * notation, that was used as the interface for DataTables prior to v1.10, however
         * from v1.10 onwards the primary interface is camel case. In order to avoid
         * breaking backwards compatibility utterly with this change, the Hungarian
         * version is still, internally the primary interface, but is is not documented
         * - hence the @name tags in each doc comment. This allows a Javascript function
         * to create a map from Hungarian notation to camel case (going the other direction
         * would require each property to be listed, which would add around 3K to the size
         * of DataTables, while this method is about a 0.5K hit).
         *
         * Ultimately this does pave the way for Hungarian notation to be dropped
         * completely, but that is a massive amount of work and will break current
         * installs (therefore is on-hold until v2).
         */
        
        /**
         * Initialisation options that can be given to DataTables at initialisation
         * time.
         *  @namespace
         */
        DataTable.defaults = {
            /**
             * An array of data to use for the table, passed in at initialisation which
             * will be used in preference to any data which is already in the DOM. This is
             * particularly useful for constructing tables purely in Javascript, for
             * example with a custom Ajax call.
             */
            "aaData": null,
        
        
            /**
             * If ordering is enabled, then DataTables will perform a first pass sort on
             * initialisation. You can define which column(s) the sort is performed
             * upon, and the sorting direction, with this variable. The `sorting` array
             * should contain an array for each column to be sorted initially containing
             * the column's index and a direction string ('asc' or 'desc').
             */
            "aaSorting": [[0,'asc']],
        
        
            /**
             * This parameter is basically identical to the `sorting` parameter, but
             * cannot be overridden by user interaction with the table. What this means
             * is that you could have a column (visible or hidden) which the sorting
             * will always be forced on first - any sorting after that (from the user)
             * will then be performed as required. This can be useful for grouping rows
             * together.
             */
            "aaSortingFixed": [],
        
        
            /**
             * DataTables can be instructed to load data to display in the table from a
             * Ajax source. This option defines how that Ajax call is made and where to.
             *
             * The `ajax` property has three different modes of operation, depending on
             * how it is defined. These are:
             *
             * * `string` - Set the URL from where the data should be loaded from.
             * * `object` - Define properties for `jQuery.ajax`.
             * * `function` - Custom data get function
             *
             * `string`
             * --------
             *
             * As a string, the `ajax` property simply defines the URL from which
             * DataTables will load data.
             *
             * `object`
             * --------
             *
             * As an object, the parameters in the object are passed to
             * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control
             * of the Ajax request. DataTables has a number of default parameters which
             * you can override using this option. Please refer to the jQuery
             * documentation for a full description of the options available, although
             * the following parameters provide additional options in DataTables or
             * require special consideration:
             *
             * * `data` - As with jQuery, `data` can be provided as an object, but it
             *   can also be used as a function to manipulate the data DataTables sends
             *   to the server. The function takes a single parameter, an object of
             *   parameters with the values that DataTables has readied for sending. An
             *   object may be returned which will be merged into the DataTables
             *   defaults, or you can add the items to the object that was passed in and
             *   not return anything from the function. This supersedes `fnServerParams`
             *   from DataTables 1.9-.
             *
             * * `dataSrc` - By default DataTables will look for the property `data` (or
             *   `aaData` for compatibility with DataTables 1.9-) when obtaining data
             *   from an Ajax source or for server-side processing - this parameter
             *   allows that property to be changed. You can use Javascript dotted
             *   object notation to get a data source for multiple levels of nesting, or
             *   it my be used as a function. As a function it takes a single parameter,
             *   the JSON returned from the server, which can be manipulated as
             *   required, with the returned value being that used by DataTables as the
             *   data source for the table.
             *
             * * `success` - Should not be overridden it is used internally in
             *   DataTables. To manipulate / transform the data returned by the server
             *   use `ajax.dataSrc`, or use `ajax` as a function (see below).
             *
             * `function`
             * ----------
             *
             * As a function, making the Ajax call is left up to yourself allowing
             * complete control of the Ajax request. Indeed, if desired, a method other
             * than Ajax could be used to obtain the required data, such as Web storage
             * or an AIR database.
             *
             * The function is given four parameters and no return is required. The
             * parameters are:
             *
             * 1. _object_ - Data to send to the server
             * 2. _function_ - Callback function that must be executed when the required
             *    data has been obtained. That data should be passed into the callback
             *    as the only parameter
             * 3. _object_ - DataTables settings object for the table
             */
            "ajax": null,
        
        
            /**
             * This parameter allows you to readily specify the entries in the length drop
             * down menu that DataTables shows when pagination is enabled. It can be
             * either a 1D array of options which will be used for both the displayed
             * option and the value, or a 2D array which will use the array in the first
             * position as the value, and the array in the second position as the
             * displayed options (useful for language strings such as 'All').
             *
             * Note that the `pageLength` property will be automatically set to the
             * first value given in this array, unless `pageLength` is also provided.
             */
            "aLengthMenu": [ 10, 25, 50, 100 ],
        
        
            /**
             * The `columns` option in the initialisation parameter allows you to define
             * details about the way individual columns behave. For a full list of
             * column options that can be set, please see
             * {@link DataTable.defaults.column}. Note that if you use `columns` to
             * define your columns, you must have an entry in the array for every single
             * column that you have in your table (these can be null if you don't which
             * to specify any options).
             */
            "aoColumns": null,
        
            /**
             * Very similar to `columns`, `columnDefs` allows you to target a specific
             * column, multiple columns, or all columns, using the `targets` property of
             * each object in the array. This allows great flexibility when creating
             * tables, as the `columnDefs` arrays can be of any length, targeting the
             * columns you specifically want. `columnDefs` may use any of the column
             * options available: {@link DataTable.defaults.column}, but it _must_
             * have `targets` defined in each object in the array. Values in the `targets`
             * array may be:
             *   <ul>
             *     <li>a string - class name will be matched on the TH for the column</li>
             *     <li>0 or a positive integer - column index counting from the left</li>
             *     <li>a negative integer - column index counting from the right</li>
             *     <li>the string "_all" - all columns (i.e. assign a default)</li>
             *   </ul>
             */
            "aoColumnDefs": null,
        
        
            /**
             * Basically the same as `search`, this parameter defines the individual column
             * filtering state at initialisation time. The array must be of the same size
             * as the number of columns, and each element be an object with the parameters
             * `search` and `escapeRegex` (the latter is optional). 'null' is also
             * accepted and the default will be used.
             */
            "aoSearchCols": [],
        
        
            /**
             * Enable or disable automatic column width calculation. This can be disabled
             * as an optimisation (it takes some time to calculate the widths) if the
             * tables widths are passed in using `columns`.
             */
            "bAutoWidth": true,
        
        
            /**
             * Deferred rendering can provide DataTables with a huge speed boost when you
             * are using an Ajax or JS data source for the table. This option, when set to
             * true, will cause DataTables to defer the creation of the table elements for
             * each row until they are needed for a draw - saving a significant amount of
             * time.
             */
            "bDeferRender": true,
        
        
            /**
             * Replace a DataTable which matches the given selector and replace it with
             * one which has the properties of the new initialisation object passed. If no
             * table matches the selector, then the new DataTable will be constructed as
             * per normal.
             */
            "bDestroy": false,
        
        
            /**
             * Enable or disable filtering of data. Filtering in DataTables is "smart" in
             * that it allows the end user to input multiple words (space separated) and
             * will match a row containing those words, even if not in the order that was
             * specified (this allow matching across multiple columns). Note that if you
             * wish to use filtering in DataTables this must remain 'true' - to remove the
             * default filtering input box and retain filtering abilities, please use
             * {@link DataTable.defaults.dom}.
             */
            "bFilter": true,
        
            /**
             * Used only for compatiblity with DT1
             * @deprecated
             */
            "bInfo": true,
        
            /**
             * Used only for compatiblity with DT1
             * @deprecated
             */
            "bLengthChange": true,
        
            /**
             * Enable or disable pagination.
             */
            "bPaginate": true,
        
        
            /**
             * Enable or disable the display of a 'processing' indicator when the table is
             * being processed (e.g. a sort). This is particularly useful for tables with
             * large amounts of data where it can take a noticeable amount of time to sort
             * the entries.
             */
            "bProcessing": false,
        
        
            /**
             * Retrieve the DataTables object for the given selector. Note that if the
             * table has already been initialised, this parameter will cause DataTables
             * to simply return the object that has already been set up - it will not take
             * account of any changes you might have made to the initialisation object
             * passed to DataTables (setting this parameter to true is an acknowledgement
             * that you understand this). `destroy` can be used to reinitialise a table if
             * you need.
             */
            "bRetrieve": false,
        
        
            /**
             * When vertical (y) scrolling is enabled, DataTables will force the height of
             * the table's viewport to the given height at all times (useful for layout).
             * However, this can look odd when filtering data down to a small data set,
             * and the footer is left "floating" further down. This parameter (when
             * enabled) will cause DataTables to collapse the table's viewport down when
             * the result set will fit within the given Y height.
             */
            "bScrollCollapse": false,
        
        
            /**
             * Configure DataTables to use server-side processing. Note that the
             * `ajax` parameter must also be given in order to give DataTables a
             * source to obtain the required data for each draw.
             */
            "bServerSide": false,
        
        
            /**
             * Enable or disable sorting of columns. Sorting of individual columns can be
             * disabled by the `sortable` option for each column.
             */
            "bSort": true,
        
        
            /**
             * Enable or display DataTables' ability to sort multiple columns at the
             * same time (activated by shift-click by the user).
             */
            "bSortMulti": true,
        
        
            /**
             * Allows control over whether DataTables should use the top (true) unique
             * cell that is found for a single column, or the bottom (false - default).
             * This is useful when using complex headers.
             */
            "bSortCellsTop": null,
        
        
            /**
             * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
             * `sorting\_3` to the columns which are currently being sorted on. This is
             * presented as a feature switch as it can increase processing time (while
             * classes are removed and added) so for large data sets you might want to
             * turn this off.
             */
            "bSortClasses": true,
        
        
            /**
             * Enable or disable state saving. When enabled HTML5 `localStorage` will be
             * used to save table display information such as pagination information,
             * display length, filtering and sorting. As such when the end user reloads
             * the page the display display will match what thy had previously set up.
             */
            "bStateSave": false,
        
        
            /**
             * This function is called when a TR element is created (and all TD child
             * elements have been inserted), or registered if using a DOM source, allowing
             * manipulation of the TR element (adding classes etc).
             */
            "fnCreatedRow": null,
        
        
            /**
             * This function is called on every 'draw' event, and allows you to
             * dynamically modify any aspect you want about the created DOM.
             */
            "fnDrawCallback": null,
        
        
            /**
             * Identical to fnHeaderCallback() but for the table footer this function
             * allows you to modify the table footer on every 'draw' event.
             */
            "fnFooterCallback": null,
        
        
            /**
             * When rendering large numbers in the information element for the table
             * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
             * to have a comma separator for the 'thousands' units (e.g. 1 million is
             * rendered as "1,000,000") to help readability for the end user. This
             * function will override the default method DataTables uses.
             */
            "fnFormatNumber": function ( toFormat ) {
                return toFormat.toString().replace(
                    /\B(?=(\d{3})+(?!\d))/g,
                    this.oLanguage.sThousands
                );
            },
        
        
            /**
             * This function is called on every 'draw' event, and allows you to
             * dynamically modify the header row. This can be used to calculate and
             * display useful information about the table.
             */
            "fnHeaderCallback": null,
        
        
            /**
             * The information element can be used to convey information about the current
             * state of the table. Although the internationalisation options presented by
             * DataTables are quite capable of dealing with most customisations, there may
             * be times where you wish to customise the string further. This callback
             * allows you to do exactly that.
             */
            "fnInfoCallback": null,
        
        
            /**
             * Called when the table has been initialised. Normally DataTables will
             * initialise sequentially and there will be no need for this function,
             * however, this does not hold true when using external language information
             * since that is obtained using an async XHR call.
             */
            "fnInitComplete": null,
        
        
            /**
             * Called at the very start of each table draw and can be used to cancel the
             * draw by returning false, any other return (including undefined) results in
             * the full draw occurring).
             */
            "fnPreDrawCallback": null,
        
        
            /**
             * This function allows you to 'post process' each row after it have been
             * generated for each table draw, but before it is rendered on screen. This
             * function might be used for setting the row class name etc.
             */
            "fnRowCallback": null,
        
        
            /**
             * Load the table state. With this function you can define from where, and how, the
             * state of a table is loaded. By default DataTables will load from `localStorage`
             * but you might wish to use a server-side database or cookies.
             */
            "fnStateLoadCallback": function ( settings ) {
                try {
                    return JSON.parse(
                        (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
                            'DataTables_'+settings.sInstance+'_'+location.pathname
                        )
                    );
                } catch (e) {
                    return {};
                }
            },
        
        
            /**
             * Callback which allows modification of the saved state prior to loading that state.
             * This callback is called when the table is loading state from the stored data, but
             * prior to the settings object being modified by the saved state. Note that for
             * plug-in authors, you should use the `stateLoadParams` event to load parameters for
             * a plug-in.
             */
            "fnStateLoadParams": null,
        
        
            /**
             * Callback that is called when the state has been loaded from the state saving method
             * and the DataTables settings object has been modified as a result of the loaded state.
             */
            "fnStateLoaded": null,
        
        
            /**
             * Save the table state. This function allows you to define where and how the state
             * information for the table is stored By default DataTables will use `localStorage`
             * but you might wish to use a server-side database or cookies.
             */
            "fnStateSaveCallback": function ( settings, data ) {
                try {
                    (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
                        'DataTables_'+settings.sInstance+'_'+location.pathname,
                        JSON.stringify( data )
                    );
                } catch (e) {
                    // noop
                }
            },
        
        
            /**
             * Callback which allows modification of the state to be saved. Called when the table
             * has changed state a new state save is required. This method allows modification of
             * the state saving object prior to actually doing the save, including addition or
             * other state properties or modification. Note that for plug-in authors, you should
             * use the `stateSaveParams` event to save parameters for a plug-in.
             */
            "fnStateSaveParams": null,
        
        
            /**
             * Duration for which the saved state information is considered valid. After this period
             * has elapsed the state will be returned to the default.
             * Value is given in seconds.
             */
            "iStateDuration": 7200,
        
        
            /**
             * Number of rows to display on a single page when using pagination. If
             * feature enabled (`lengthChange`) then the end user will be able to override
             * this to a custom setting using a pop-up menu.
             */
            "iDisplayLength": 10,
        
        
            /**
             * Define the starting point for data display when using DataTables with
             * pagination. Note that this parameter is the number of records, rather than
             * the page number, so if you have 10 records per page and want to start on
             * the third page, it should be "20".
             */
            "iDisplayStart": 0,
        
        
            /**
             * By default DataTables allows keyboard navigation of the table (sorting, paging,
             * and filtering) by adding a `tabindex` attribute to the required elements. This
             * allows you to tab through the controls and press the enter key to activate them.
             * The tabindex is default 0, meaning that the tab follows the flow of the document.
             * You can overrule this using this parameter if you wish. Use a value of -1 to
             * disable built-in keyboard navigation.
             */
            "iTabIndex": 0,
        
        
            /**
             * Classes that DataTables assigns to the various components and features
             * that it adds to the HTML table. This allows classes to be configured
             * during initialisation in addition to through the static
             * {@link DataTable.ext.oStdClasses} object).
             */
            "oClasses": {},
        
        
            /**
             * All strings that DataTables uses in the user interface that it creates
             * are defined in this object, allowing you to modified them individually or
             * completely replace them all as required.
             */
            "oLanguage": {
                /**
                 * Strings that are used for WAI-ARIA labels and controls only (these are not
                 * actually visible on the page, but will be read by screenreaders, and thus
                 * must be internationalised as well).
                 */
                "oAria": {
                    /**
                     * ARIA label that is added to the table headers when the column may be sorted
                     */
                    "orderable": ": Activate to sort",
        
                    /**
                     * ARIA label that is added to the table headers when the column is currently being sorted
                     */
                    "orderableReverse": ": Activate to invert sorting",
        
                    /**
                     * ARIA label that is added to the table headers when the column is currently being 
                     * sorted and next step is to remove sorting
                     */
                    "orderableRemove": ": Activate to remove sorting",
        
                    paginate: {
                        first: 'First',
                        last: 'Last',
                        next: 'Next',
                        previous: 'Previous',
                        number: ''
                    }
                },
        
                /**
                 * Pagination string used by DataTables for the built-in pagination
                 * control types.
                 */
                "oPaginate": {
                    /**
                     * Label and character for first page button («)
                     */
                    "sFirst": "\u00AB",
        
                    /**
                     * Last page button (»)
                     */
                    "sLast": "\u00BB",
        
                    /**
                     * Next page button (›)
                     */
                    "sNext": "\u203A",
        
                    /**
                     * Previous page button (‹)
                     */
                    "sPrevious": "\u2039",
                },
        
                /**
                 * Plural object for the data type the table is showing
                 */
                entries: {
                    _: "entries",
                    1: "entry"
                },
        
                /**
                 * This string is shown in preference to `zeroRecords` when the table is
                 * empty of data (regardless of filtering). Note that this is an optional
                 * parameter - if it is not given, the value of `zeroRecords` will be used
                 * instead (either the default or given value).
                 */
                "sEmptyTable": "No data available in table",
        
        
                /**
                 * This string gives information to the end user about the information
                 * that is current on display on the page. The following tokens can be
                 * used in the string and will be dynamically replaced as the table
                 * display updates. This tokens can be placed anywhere in the string, or
                 * removed as needed by the language requires:
                 *
                 * * `\_START\_` - Display index of the first record on the current page
                 * * `\_END\_` - Display index of the last record on the current page
                 * * `\_TOTAL\_` - Number of records in the table after filtering
                 * * `\_MAX\_` - Number of records in the table without filtering
                 * * `\_PAGE\_` - Current page number
                 * * `\_PAGES\_` - Total number of pages of data in the table
                 */
                "sInfo": "Showing _START_ to _END_ of _TOTAL_ _ENTRIES-TOTAL_",
        
        
                /**
                 * Display information string for when the table is empty. Typically the
                 * format of this string should match `info`.
                 */
                "sInfoEmpty": "Showing 0 to 0 of 0 _ENTRIES-TOTAL_",
        
        
                /**
                 * When a user filters the information in a table, this string is appended
                 * to the information (`info`) to give an idea of how strong the filtering
                 * is. The variable _MAX_ is dynamically updated.
                 */
                "sInfoFiltered": "(filtered from _MAX_ total _ENTRIES-MAX_)",
        
        
                /**
                 * If can be useful to append extra information to the info string at times,
                 * and this variable does exactly that. This information will be appended to
                 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
                 * being used) at all times.
                 */
                "sInfoPostFix": "",
        
        
                /**
                 * This decimal place operator is a little different from the other
                 * language options since DataTables doesn't output floating point
                 * numbers, so it won't ever use this for display of a number. Rather,
                 * what this parameter does is modify the sort methods of the table so
                 * that numbers which are in a format which has a character other than
                 * a period (`.`) as a decimal place will be sorted numerically.
                 *
                 * Note that numbers with different decimal places cannot be shown in
                 * the same table and still be sortable, the table must be consistent.
                 * However, multiple different tables on the page can use different
                 * decimal place characters.
                 */
                "sDecimal": "",
        
        
                /**
                 * DataTables has a build in number formatter (`formatNumber`) which is
                 * used to format large numbers that are used in the table information.
                 * By default a comma is used, but this can be trivially changed to any
                 * character you wish with this parameter.
                 */
                "sThousands": ",",
        
        
                /**
                 * Detail the action that will be taken when the drop down menu for the
                 * pagination length option is changed. The '_MENU_' variable is replaced
                 * with a default select list of 10, 25, 50 and 100, and can be replaced
                 * with a custom select box if required.
                 */
                "sLengthMenu": "_MENU_ _ENTRIES_ per page",
        
        
                /**
                 * When using Ajax sourced data and during the first draw when DataTables is
                 * gathering the data, this message is shown in an empty row in the table to
                 * indicate to the end user the the data is being loaded. Note that this
                 * parameter is not used when loading data by server-side processing, just
                 * Ajax sourced data with client-side processing.
                 */
                "sLoadingRecords": "Loading...",
        
        
                /**
                 * Text which is displayed when the table is processing a user action
                 * (usually a sort command or similar).
                 */
                "sProcessing": "",
        
        
                /**
                 * Details the actions that will be taken when the user types into the
                 * filtering input text box. The variable "_INPUT_", if used in the string,
                 * is replaced with the HTML text box for the filtering input allowing
                 * control over where it appears in the string. If "_INPUT_" is not given
                 * then the input box is appended to the string automatically.
                 */
                "sSearch": "Search:",
        
        
                /**
                 * Assign a `placeholder` attribute to the search `input` element
                 *  @type string
                 *  @default 
                 *
                 *  @dtopt Language
                 *  @name DataTable.defaults.language.searchPlaceholder
                 */
                "sSearchPlaceholder": "",
        
        
                /**
                 * All of the language information can be stored in a file on the
                 * server-side, which DataTables will look up if this parameter is passed.
                 * It must store the URL of the language file, which is in a JSON format,
                 * and the object has the same properties as the oLanguage object in the
                 * initialiser object (i.e. the above parameters). Please refer to one of
                 * the example language files to see how this works in action.
                 */
                "sUrl": "",
        
        
                /**
                 * Text shown inside the table records when the is no information to be
                 * displayed after filtering. `emptyTable` is shown when there is simply no
                 * information in the table at all (regardless of filtering).
                 */
                "sZeroRecords": "No matching records found"
            },
        
        
            /** The initial data order is reversed when `desc` ordering */
            orderDescReverse: true,
        
        
            /**
             * This parameter allows you to have define the global filtering state at
             * initialisation time. As an object the `search` parameter must be
             * defined, but all other parameters are optional. When `regex` is true,
             * the search string will be treated as a regular expression, when false
             * (default) it will be treated as a straight string. When `smart`
             * DataTables will use it's smart filtering methods (to word match at
             * any point in the data), when false this will not be done.
             */
            "oSearch": $.extend( {}, DataTable.models.oSearch ),
        
        
            /**
             * Table and control layout. This replaces the legacy `dom` option.
             */
            layout: {
                topStart: 'pageLength',
                topEnd: 'search',
                bottomStart: 'info',
                bottomEnd: 'paging'
            },
        
        
            /**
             * Legacy DOM layout option
             */
            "sDom": null,
        
        
            /**
             * Search delay option. This will throttle full table searches that use the
             * DataTables provided search input element (it does not effect calls to
             * `dt-api search()`, providing a delay before the search is made.
             */
            "searchDelay": null,
        
        
            /**
             * DataTables features six different built-in options for the buttons to
             * display for pagination control:
             *
             * * `numbers` - Page number buttons only
             * * `simple` - 'Previous' and 'Next' buttons only
             * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
             * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
             * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
             * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
             */
            "sPaginationType": "",
        
        
            /**
             * Enable horizontal scrolling. When a table is too wide to fit into a
             * certain layout, or you have a large number of columns in the table, you
             * can enable x-scrolling to show the table in a viewport, which can be
             * scrolled. This property can be `true` which will allow the table to
             * scroll horizontally when needed, or any CSS unit, or a number (in which
             * case it will be treated as a pixel measurement). Setting as simply `true`
             * is recommended.
             */
            "sScrollX": "",
        
        
            /**
             * This property can be used to force a DataTable to use more width than it
             * might otherwise do when x-scrolling is enabled. For example if you have a
             * table which requires to be well spaced, this parameter is useful for
             * "over-sizing" the table, and thus forcing scrolling. This property can by
             * any CSS unit, or a number (in which case it will be treated as a pixel
             * measurement).
             */
            "sScrollXInner": "",
        
        
            /**
             * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
             * to the given height, and enable scrolling for any data which overflows the
             * current viewport. This can be used as an alternative to paging to display
             * a lot of data in a small area (although paging and scrolling can both be
             * enabled at the same time). This property can be any CSS unit, or a number
             * (in which case it will be treated as a pixel measurement).
             */
            "sScrollY": "",
        
        
            /**
             * __Deprecated__ The functionality provided by this parameter has now been
             * superseded by that provided through `ajax`, which should be used instead.
             *
             * Set the HTTP method that is used to make the Ajax call for server-side
             * processing or Ajax sourced data.
             */
            "sServerMethod": "GET",
        
        
            /**
             * DataTables makes use of renderers when displaying HTML elements for
             * a table. These renderers can be added or modified by plug-ins to
             * generate suitable mark-up for a site. For example the Bootstrap
             * integration plug-in for DataTables uses a paging button renderer to
             * display pagination buttons in the mark-up required by Bootstrap.
             *
             * For further information about the renderers available see
             * DataTable.ext.renderer
             */
            "renderer": null,
        
        
            /**
             * Set the data property name that DataTables should use to get a row's id
             * to set as the `id` property in the node.
             */
            "rowId": "DT_RowId",
        
        
            /**
             * Caption value
             */
            "caption": null,
        
        
            /**
             * For server-side processing - use the data from the DOM for the first draw
             */
            iDeferLoading: null
        };
        
        _fnHungarianMap( DataTable.defaults );
        
        
        
        /*
         * Developer note - See note in model.defaults.js about the use of Hungarian
         * notation and camel case.
         */
        
        /**
         * Column options that can be given to DataTables at initialisation time.
         *  @namespace
         */
        DataTable.defaults.column = {
            /**
             * Define which column(s) an order will occur on for this column. This
             * allows a column's ordering to take multiple columns into account when
             * doing a sort or use the data from a different column. For example first
             * name / last name columns make sense to do a multi-column sort over the
             * two columns.
             */
            "aDataSort": null,
            "iDataSort": -1,
        
            ariaTitle: '',
        
        
            /**
             * You can control the default ordering direction, and even alter the
             * behaviour of the sort handler (i.e. only allow ascending ordering etc)
             * using this parameter.
             */
            "asSorting": [ 'asc', 'desc', '' ],
        
        
            /**
             * Enable or disable filtering on the data in this column.
             */
            "bSearchable": true,
        
        
            /**
             * Enable or disable ordering on this column.
             */
            "bSortable": true,
        
        
            /**
             * Enable or disable the display of this column.
             */
            "bVisible": true,
        
        
            /**
             * Developer definable function that is called whenever a cell is created (Ajax source,
             * etc) or processed for input (DOM source). This can be used as a compliment to mRender
             * allowing you to modify the DOM element (add background colour for example) when the
             * element is available.
             */
            "fnCreatedCell": null,
        
        
            /**
             * This property can be used to read data from any data source property,
             * including deeply nested objects / properties. `data` can be given in a
             * number of different ways which effect its behaviour:
             *
             * * `integer` - treated as an array index for the data source. This is the
             *   default that DataTables uses (incrementally increased for each column).
             * * `string` - read an object property from the data source. There are
             *   three 'special' options that can be used in the string to alter how
             *   DataTables reads the data from the source object:
             *    * `.` - Dotted Javascript notation. Just as you use a `.` in
             *      Javascript to read from nested objects, so to can the options
             *      specified in `data`. For example: `browser.version` or
             *      `browser.name`. If your object parameter name contains a period, use
             *      `\\` to escape it - i.e. `first\\.name`.
             *    * `[]` - Array notation. DataTables can automatically combine data
             *      from and array source, joining the data with the characters provided
             *      between the two brackets. For example: `name[, ]` would provide a
             *      comma-space separated list from the source array. If no characters
             *      are provided between the brackets, the original array source is
             *      returned.
             *    * `()` - Function notation. Adding `()` to the end of a parameter will
             *      execute a function of the name given. For example: `browser()` for a
             *      simple function on the data source, `browser.version()` for a
             *      function in a nested property or even `browser().version` to get an
             *      object property if the function called returns an object. Note that
             *      function notation is recommended for use in `render` rather than
             *      `data` as it is much simpler to use as a renderer.
             * * `null` - use the original data source for the row rather than plucking
             *   data directly from it. This action has effects on two other
             *   initialisation options:
             *    * `defaultContent` - When null is given as the `data` option and
             *      `defaultContent` is specified for the column, the value defined by
             *      `defaultContent` will be used for the cell.
             *    * `render` - When null is used for the `data` option and the `render`
             *      option is specified for the column, the whole data source for the
             *      row is used for the renderer.
             * * `function` - the function given will be executed whenever DataTables
             *   needs to set or get the data for a cell in the column. The function
             *   takes three parameters:
             *    * Parameters:
             *      * `{array|object}` The data source for the row
             *      * `{string}` The type call data requested - this will be 'set' when
             *        setting data or 'filter', 'display', 'type', 'sort' or undefined
             *        when gathering data. Note that when `undefined` is given for the
             *        type DataTables expects to get the raw data for the object back<
             *      * `{*}` Data to set when the second parameter is 'set'.
             *    * Return:
             *      * The return value from the function is not required when 'set' is
             *        the type of call, but otherwise the return is what will be used
             *        for the data requested.
             *
             * Note that `data` is a getter and setter option. If you just require
             * formatting of data for output, you will likely want to use `render` which
             * is simply a getter and thus simpler to use.
             *
             * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
             * name change reflects the flexibility of this property and is consistent
             * with the naming of mRender. If 'mDataProp' is given, then it will still
             * be used by DataTables, as it automatically maps the old name to the new
             * if required.
             */
            "mData": null,
        
        
            /**
             * This property is the rendering partner to `data` and it is suggested that
             * when you want to manipulate data for display (including filtering,
             * sorting etc) without altering the underlying data for the table, use this
             * property. `render` can be considered to be the the read only companion to
             * `data` which is read / write (then as such more complex). Like `data`
             * this option can be given in a number of different ways to effect its
             * behaviour:
             *
             * * `integer` - treated as an array index for the data source. This is the
             *   default that DataTables uses (incrementally increased for each column).
             * * `string` - read an object property from the data source. There are
             *   three 'special' options that can be used in the string to alter how
             *   DataTables reads the data from the source object:
             *    * `.` - Dotted Javascript notation. Just as you use a `.` in
             *      Javascript to read from nested objects, so to can the options
             *      specified in `data`. For example: `browser.version` or
             *      `browser.name`. If your object parameter name contains a period, use
             *      `\\` to escape it - i.e. `first\\.name`.
             *    * `[]` - Array notation. DataTables can automatically combine data
             *      from and array source, joining the data with the characters provided
             *      between the two brackets. For example: `name[, ]` would provide a
             *      comma-space separated list from the source array. If no characters
             *      are provided between the brackets, the original array source is
             *      returned.
             *    * `()` - Function notation. Adding `()` to the end of a parameter will
             *      execute a function of the name given. For example: `browser()` for a
             *      simple function on the data source, `browser.version()` for a
             *      function in a nested property or even `browser().version` to get an
             *      object property if the function called returns an object.
             * * `object` - use different data for the different data types requested by
             *   DataTables ('filter', 'display', 'type' or 'sort'). The property names
             *   of the object is the data type the property refers to and the value can
             *   defined using an integer, string or function using the same rules as
             *   `render` normally does. Note that an `_` option _must_ be specified.
             *   This is the default value to use if you haven't specified a value for
             *   the data type requested by DataTables.
             * * `function` - the function given will be executed whenever DataTables
             *   needs to set or get the data for a cell in the column. The function
             *   takes three parameters:
             *    * Parameters:
             *      * {array|object} The data source for the row (based on `data`)
             *      * {string} The type call data requested - this will be 'filter',
             *        'display', 'type' or 'sort'.
             *      * {array|object} The full data source for the row (not based on
             *        `data`)
             *    * Return:
             *      * The return value from the function is what will be used for the
             *        data requested.
             */
            "mRender": null,
        
        
            /**
             * Change the cell type created for the column - either TD cells or TH cells. This
             * can be useful as TH cells have semantic meaning in the table body, allowing them
             * to act as a header for a row (you may wish to add scope='row' to the TH elements).
             */
            "sCellType": "td",
        
        
            /**
             * Class to give to each cell in this column.
             */
            "sClass": "",
        
            /**
             * When DataTables calculates the column widths to assign to each column,
             * it finds the longest string in each column and then constructs a
             * temporary table and reads the widths from that. The problem with this
             * is that "mmm" is much wider then "iiii", but the latter is a longer
             * string - thus the calculation can go wrong (doing it properly and putting
             * it into an DOM object and measuring that is horribly(!) slow). Thus as
             * a "work around" we provide this option. It will append its value to the
             * text that is found to be the longest string for the column - i.e. padding.
             * Generally you shouldn't need this!
             */
            "sContentPadding": "",
        
        
            /**
             * Allows a default value to be given for a column's data, and will be used
             * whenever a null data source is encountered (this can be because `data`
             * is set to null, or because the data source itself is null).
             */
            "sDefaultContent": null,
        
        
            /**
             * This parameter is only used in DataTables' server-side processing. It can
             * be exceptionally useful to know what columns are being displayed on the
             * client side, and to map these to database fields. When defined, the names
             * also allow DataTables to reorder information from the server if it comes
             * back in an unexpected order (i.e. if you switch your columns around on the
             * client-side, your server-side code does not also need updating).
             */
            "sName": "",
        
        
            /**
             * Defines a data source type for the ordering which can be used to read
             * real-time information from the table (updating the internally cached
             * version) prior to ordering. This allows ordering to occur on user
             * editable elements such as form inputs.
             */
            "sSortDataType": "std",
        
        
            /**
             * The title of this column.
             */
            "sTitle": null,
        
        
            /**
             * The type allows you to specify how the data for this column will be
             * ordered. Four types (string, numeric, date and html (which will strip
             * HTML tags before ordering)) are currently available. Note that only date
             * formats understood by Javascript's Date() object will be accepted as type
             * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
             * 'numeric', 'date' or 'html' (by default). Further types can be adding
             * through plug-ins.
             */
            "sType": null,
        
        
            /**
             * Defining the width of the column, this parameter may take any CSS value
             * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
             * been given a specific width through this interface ensuring that the table
             * remains readable.
             */
            "sWidth": null
        };
        
        _fnHungarianMap( DataTable.defaults.column );
        
        
        
        /**
         * DataTables settings object - this holds all the information needed for a
         * given table, including configuration, data and current application of the
         * table options. DataTables does not have a single instance for each DataTable
         * with the settings attached to that instance, but rather instances of the
         * DataTable "class" are created on-the-fly as needed (typically by a
         * $().dataTable() call) and the settings object is then applied to that
         * instance.
         *
         * Note that this object is related to {@link DataTable.defaults} but this
         * one is the internal data store for DataTables's cache of columns. It should
         * NOT be manipulated outside of DataTables. Any configuration should be done
         * through the initialisation options.
         */
        DataTable.models.oSettings = {
            /**
             * Primary features of DataTables and their enablement state.
             */
            "oFeatures": {
        
                /**
                 * Flag to say if DataTables should automatically try to calculate the
                 * optimum table and columns widths (true) or not (false).
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bAutoWidth": null,
        
                /**
                 * Delay the creation of TR and TD elements until they are actually
                 * needed by a driven page draw. This can give a significant speed
                 * increase for Ajax source and Javascript source data, but makes no
                 * difference at all for DOM and server-side processing tables.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bDeferRender": null,
        
                /**
                 * Enable filtering on the table or not. Note that if this is disabled
                 * then there is no filtering at all on the table, including fnFilter.
                 * To just remove the filtering input use sDom and remove the 'f' option.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bFilter": null,
        
                /**
                 * Used only for compatiblity with DT1
                 * @deprecated
                 */
                "bInfo": true,
        
                /**
                 * Used only for compatiblity with DT1
                 * @deprecated
                 */
                "bLengthChange": true,
        
                /**
                 * Pagination enabled or not. Note that if this is disabled then length
                 * changing must also be disabled.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bPaginate": null,
        
                /**
                 * Processing indicator enable flag whenever DataTables is enacting a
                 * user request - typically an Ajax request for server-side processing.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bProcessing": null,
        
                /**
                 * Server-side processing enabled flag - when enabled DataTables will
                 * get all data from the server for every draw - there is no filtering,
                 * sorting or paging done on the client-side.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bServerSide": null,
        
                /**
                 * Sorting enablement flag.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bSort": null,
        
                /**
                 * Multi-column sorting
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bSortMulti": null,
        
                /**
                 * Apply a class to the columns which are being sorted to provide a
                 * visual highlight or not. This can slow things down when enabled since
                 * there is a lot of DOM interaction.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bSortClasses": null,
        
                /**
                 * State saving enablement flag.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bStateSave": null
            },
        
        
            /**
             * Scrolling settings for a table.
             */
            "oScroll": {
                /**
                 * When the table is shorter in height than sScrollY, collapse the
                 * table container down to the height of the table (when true).
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "bCollapse": null,
        
                /**
                 * Width of the scrollbar for the web-browser's platform. Calculated
                 * during table initialisation.
                 */
                "iBarWidth": 0,
        
                /**
                 * Viewport width for horizontal scrolling. Horizontal scrolling is
                 * disabled if an empty string.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "sX": null,
        
                /**
                 * Width to expand the table to when using x-scrolling. Typically you
                 * should not need to use this.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 *  @deprecated
                 */
                "sXInner": null,
        
                /**
                 * Viewport height for vertical scrolling. Vertical scrolling is disabled
                 * if an empty string.
                 * Note that this parameter will be set by the initialisation routine. To
                 * set a default use {@link DataTable.defaults}.
                 */
                "sY": null
            },
        
            /**
             * Language information for the table.
             */
            "oLanguage": {
                /**
                 * Information callback function. See
                 * {@link DataTable.defaults.fnInfoCallback}
                 */
                "fnInfoCallback": null
            },
        
            /**
             * Browser support parameters
             */
            "oBrowser": {
                /**
                 * Determine if the vertical scrollbar is on the right or left of the
                 * scrolling container - needed for rtl language layout, although not
                 * all browsers move the scrollbar (Safari).
                 */
                "bScrollbarLeft": false,
        
                /**
                 * Browser scrollbar width
                 */
                "barWidth": 0
            },
        
        
            "ajax": null,
        
        
            /**
             * Array referencing the nodes which are used for the features. The
             * parameters of this object match what is allowed by sDom - i.e.
             *   <ul>
             *     <li>'l' - Length changing</li>
             *     <li>'f' - Filtering input</li>
             *     <li>'t' - The table!</li>
             *     <li>'i' - Information</li>
             *     <li>'p' - Pagination</li>
             *     <li>'r' - pRocessing</li>
             *   </ul>
             */
            "aanFeatures": [],
        
            /**
             * Store data information - see {@link DataTable.models.oRow} for detailed
             * information.
             */
            "aoData": [],
        
            /**
             * Array of indexes which are in the current display (after filtering etc)
             */
            "aiDisplay": [],
        
            /**
             * Array of indexes for display - no filtering
             */
            "aiDisplayMaster": [],
        
            /**
             * Map of row ids to data indexes
             */
            "aIds": {},
        
            /**
             * Store information about each column that is in use
             */
            "aoColumns": [],
        
            /**
             * Store information about the table's header
             */
            "aoHeader": [],
        
            /**
             * Store information about the table's footer
             */
            "aoFooter": [],
        
            /**
             * Store the applied global search information in case we want to force a
             * research or compare the old search to a new one.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "oPreviousSearch": {},
        
            /**
             * Store for named searches
             */
            searchFixed: {},
        
            /**
             * Store the applied search for each column - see
             * {@link DataTable.models.oSearch} for the format that is used for the
             * filtering information for each column.
             */
            "aoPreSearchCols": [],
        
            /**
             * Sorting that is applied to the table. Note that the inner arrays are
             * used in the following manner:
             * <ul>
             *   <li>Index 0 - column number</li>
             *   <li>Index 1 - current sorting direction</li>
             * </ul>
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "aaSorting": null,
        
            /**
             * Sorting that is always applied to the table (i.e. prefixed in front of
             * aaSorting).
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "aaSortingFixed": [],
        
            /**
             * If restoring a table - we should restore its width
             */
            "sDestroyWidth": 0,
        
            /**
             * Callback functions array for every time a row is inserted (i.e. on a draw).
             */
            "aoRowCallback": [],
        
            /**
             * Callback functions for the header on each draw.
             */
            "aoHeaderCallback": [],
        
            /**
             * Callback function for the footer on each draw.
             */
            "aoFooterCallback": [],
        
            /**
             * Array of callback functions for draw callback functions
             */
            "aoDrawCallback": [],
        
            /**
             * Array of callback functions for row created function
             */
            "aoRowCreatedCallback": [],
        
            /**
             * Callback functions for just before the table is redrawn. A return of
             * false will be used to cancel the draw.
             */
            "aoPreDrawCallback": [],
        
            /**
             * Callback functions for when the table has been initialised.
             */
            "aoInitComplete": [],
        
        
            /**
             * Callbacks for modifying the settings to be stored for state saving, prior to
             * saving state.
             */
            "aoStateSaveParams": [],
        
            /**
             * Callbacks for modifying the settings that have been stored for state saving
             * prior to using the stored values to restore the state.
             */
            "aoStateLoadParams": [],
        
            /**
             * Callbacks for operating on the settings object once the saved state has been
             * loaded
             */
            "aoStateLoaded": [],
        
            /**
             * Cache the table ID for quick access
             */
            "sTableId": "",
        
            /**
             * The TABLE node for the main table
             */
            "nTable": null,
        
            /**
             * Permanent ref to the thead element
             */
            "nTHead": null,
        
            /**
             * Permanent ref to the tfoot element - if it exists
             */
            "nTFoot": null,
        
            /**
             * Permanent ref to the tbody element
             */
            "nTBody": null,
        
            /**
             * Cache the wrapper node (contains all DataTables controlled elements)
             */
            "nTableWrapper": null,
        
            /**
             * Indicate if all required information has been read in
             */
            "bInitialised": false,
        
            /**
             * Information about open rows. Each object in the array has the parameters
             * 'nTr' and 'nParent'
             */
            "aoOpenRows": [],
        
            /**
             * Dictate the positioning of DataTables' control elements - see
             * {@link DataTable.model.oInit.sDom}.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "sDom": null,
        
            /**
             * Search delay (in mS)
             */
            "searchDelay": null,
        
            /**
             * Which type of pagination should be used.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "sPaginationType": "two_button",
        
            /**
             * Number of paging controls on the page. Only used for backwards compatibility
             */
            pagingControls: 0,
        
            /**
             * The state duration (for `stateSave`) in seconds.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "iStateDuration": 0,
        
            /**
             * Array of callback functions for state saving. Each array element is an
             * object with the following parameters:
             *   <ul>
             *     <li>function:fn - function to call. Takes two parameters, oSettings
             *       and the JSON string to save that has been thus far created. Returns
             *       a JSON string to be inserted into a json object
             *       (i.e. '"param": [ 0, 1, 2]')</li>
             *     <li>string:sName - name of callback</li>
             *   </ul>
             */
            "aoStateSave": [],
        
            /**
             * Array of callback functions for state loading. Each array element is an
             * object with the following parameters:
             *   <ul>
             *     <li>function:fn - function to call. Takes two parameters, oSettings
             *       and the object stored. May return false to cancel state loading</li>
             *     <li>string:sName - name of callback</li>
             *   </ul>
             */
            "aoStateLoad": [],
        
            /**
             * State that was saved. Useful for back reference
             */
            "oSavedState": null,
        
            /**
             * State that was loaded. Useful for back reference
             */
            "oLoadedState": null,
        
            /**
             * Note if draw should be blocked while getting data
             */
            "bAjaxDataGet": true,
        
            /**
             * The last jQuery XHR object that was used for server-side data gathering.
             * This can be used for working with the XHR information in one of the
             * callbacks
             */
            "jqXHR": null,
        
            /**
             * JSON returned from the server in the last Ajax request
             */
            "json": undefined,
        
            /**
             * Data submitted as part of the last Ajax request
             */
            "oAjaxData": undefined,
        
            /**
             * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
             * required).
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "sServerMethod": null,
        
            /**
             * Format numbers for display.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "fnFormatNumber": null,
        
            /**
             * List of options that can be used for the user selectable length menu.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "aLengthMenu": null,
        
            /**
             * Counter for the draws that the table does. Also used as a tracker for
             * server-side processing
             */
            "iDraw": 0,
        
            /**
             * Indicate if a redraw is being done - useful for Ajax
             */
            "bDrawing": false,
        
            /**
             * Draw index (iDraw) of the last error when parsing the returned data
             */
            "iDrawError": -1,
        
            /**
             * Paging display length
             */
            "_iDisplayLength": 10,
        
            /**
             * Paging start point - aiDisplay index
             */
            "_iDisplayStart": 0,
        
            /**
             * Server-side processing - number of records in the result set
             * (i.e. before filtering), Use fnRecordsTotal rather than
             * this property to get the value of the number of records, regardless of
             * the server-side processing setting.
             */
            "_iRecordsTotal": 0,
        
            /**
             * Server-side processing - number of records in the current display set
             * (i.e. after filtering). Use fnRecordsDisplay rather than
             * this property to get the value of the number of records, regardless of
             * the server-side processing setting.
             */
            "_iRecordsDisplay": 0,
        
            /**
             * The classes to use for the table
             */
            "oClasses": {},
        
            /**
             * Flag attached to the settings object so you can check in the draw
             * callback if filtering has been done in the draw. Deprecated in favour of
             * events.
             *  @deprecated
             */
            "bFiltered": false,
        
            /**
             * Flag attached to the settings object so you can check in the draw
             * callback if sorting has been done in the draw. Deprecated in favour of
             * events.
             *  @deprecated
             */
            "bSorted": false,
        
            /**
             * Indicate that if multiple rows are in the header and there is more than
             * one unique cell per column, if the top one (true) or bottom one (false)
             * should be used for sorting / title by DataTables.
             * Note that this parameter will be set by the initialisation routine. To
             * set a default use {@link DataTable.defaults}.
             */
            "bSortCellsTop": null,
        
            /**
             * Initialisation object that is used for the table
             */
            "oInit": null,
        
            /**
             * Destroy callback functions - for plug-ins to attach themselves to the
             * destroy so they can clean up markup and events.
             */
            "aoDestroyCallback": [],
        
        
            /**
             * Get the number of records in the current record set, before filtering
             */
            "fnRecordsTotal": function ()
            {
                return _fnDataSource( this ) == 'ssp' ?
                    this._iRecordsTotal * 1 :
                    this.aiDisplayMaster.length;
            },
        
            /**
             * Get the number of records in the current record set, after filtering
             */
            "fnRecordsDisplay": function ()
            {
                return _fnDataSource( this ) == 'ssp' ?
                    this._iRecordsDisplay * 1 :
                    this.aiDisplay.length;
            },
        
            /**
             * Get the display end point - aiDisplay index
             */
            "fnDisplayEnd": function ()
            {
                var
                    len      = this._iDisplayLength,
                    start    = this._iDisplayStart,
                    calc     = start + len,
                    records  = this.aiDisplay.length,
                    features = this.oFeatures,
                    paginate = features.bPaginate;
        
                if ( features.bServerSide ) {
                    return paginate === false || len === -1 ?
                        start + records :
                        Math.min( start+len, this._iRecordsDisplay );
                }
                else {
                    return ! paginate || calc>records || len===-1 ?
                        records :
                        calc;
                }
            },
        
            /**
             * The DataTables object for this table
             */
            "oInstance": null,
        
            /**
             * Unique identifier for each instance of the DataTables object. If there
             * is an ID on the table node, then it takes that value, otherwise an
             * incrementing internal counter is used.
             */
            "sInstance": null,
        
            /**
             * tabindex attribute value that is added to DataTables control elements, allowing
             * keyboard navigation of the table and its controls.
             */
            "iTabIndex": 0,
        
            /**
             * DIV container for the footer scrolling table if scrolling
             */
            "nScrollHead": null,
        
            /**
             * DIV container for the footer scrolling table if scrolling
             */
            "nScrollFoot": null,
        
            /**
             * Last applied sort
             */
            "aLastSort": [],
        
            /**
             * Stored plug-in instances
             */
            "oPlugins": {},
        
            /**
             * Function used to get a row's id from the row's data
             */
            "rowIdFn": null,
        
            /**
             * Data location where to store a row's id
             */
            "rowId": null,
        
            caption: '',
        
            captionNode: null,
        
            colgroup: null,
        
            /** Delay loading of data */
            deferLoading: null
        };
        
        /**
         * Extension object for DataTables that is used to provide all extension
         * options.
         *
         * Note that the `DataTable.ext` object is available through
         * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
         * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
         *  @namespace
         *  @extends DataTable.models.ext
         */
        
        
        var extPagination = DataTable.ext.pager;
        
        // Paging buttons configuration
        $.extend( extPagination, {
            simple: function () {
                return [ 'previous', 'next' ];
            },
        
            full: function () {
                return [ 'first', 'previous', 'next', 'last' ];
            },
        
            numbers: function () {
                return [ 'numbers' ];
            },
        
            simple_numbers: function () {
                return [ 'previous', 'numbers', 'next' ];
            },
        
            full_numbers: function () {
                return [ 'first', 'previous', 'numbers', 'next', 'last' ];
            },
        
            first_last: function () {
                return ['first', 'last'];
            },
        
            first_last_numbers: function () {
                return ['first', 'numbers', 'last'];
            },
        
            // For testing and plug-ins to use
            _numbers: _pagingNumbers,
        
            // Number of number buttons - legacy, use `numbers` option for paging feature
            numbers_length: 7
        } );
        
        
        $.extend( true, DataTable.ext.renderer, {
            pagingButton: {
                _: function (settings, buttonType, content, active, disabled) {
                    var classes = settings.oClasses.paging;
                    var btnClasses = [classes.button];
                    var btn;
        
                    if (active) {
                        btnClasses.push(classes.active);
                    }
        
                    if (disabled) {
                        btnClasses.push(classes.disabled)
                    }
        
                    if (buttonType === 'ellipsis') {
                        btn = $('<span class="ellipsis"></span>').html(content)[0];
                    }
                    else {
                        btn = $('<button>', {
                            class: btnClasses.join(' '),
                            role: 'link',
                            type: 'button'
                        }).html(content);
                    }
        
                    return {
                        display: btn,
                        clicker: btn
                    }
                }
            },
        
            pagingContainer: {
                _: function (settings, buttons) {
                    // No wrapping element - just append directly to the host
                    return buttons;
                }
            }
        } );
        
        // Common function to remove new lines, strip HTML and diacritic control
        var _filterString = function (stripHtml, normalize) {
            return function (str) {
                if (_empty(str) || typeof str !== 'string') {
                    return str;
                }
        
                str = str.replace( _re_new_lines, " " );
        
                if (stripHtml) {
                    str = _stripHtml(str);
                }
        
                if (normalize) {
                    str = _normalize(str, false);
                }
        
                return str;
            };
        }
        
        /*
         * Public helper functions. These aren't used internally by DataTables, or
         * called by any of the options passed into DataTables, but they can be used
         * externally by developers working with DataTables. They are helper functions
         * to make working with DataTables a little bit easier.
         */
        
        /**
         * Common logic for moment, luxon or a date action.
         *
         * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again
         */
        function __mld( dtLib, momentFn, luxonFn, dateFn, arg1 ) {
            if (__moment) {
                return dtLib[momentFn]( arg1 );
            }
            else if (__luxon) {
                return dtLib[luxonFn]( arg1 );
            }
            
            return dateFn ? dtLib[dateFn]( arg1 ) : dtLib;
        }
        
        
        var __mlWarning = false;
        var __luxon; // Can be assigned in DateTeble.use()
        var __moment; // Can be assigned in DateTeble.use()
        
        /**
         * 
         */
        function resolveWindowLibs() {
            if (window.luxon && ! __luxon) {
                __luxon = window.luxon;
            }
            
            if (window.moment && ! __moment) {
                __moment = window.moment;
            }
        }
        
        function __mldObj (d, format, locale) {
            var dt;
        
            resolveWindowLibs();
        
            if (__moment) {
                dt = __moment.utc( d, format, locale, true );
        
                if (! dt.isValid()) {
                    return null;
                }
            }
            else if (__luxon) {
                dt = format && typeof d === 'string'
                    ? __luxon.DateTime.fromFormat( d, format )
                    : __luxon.DateTime.fromISO( d );
        
                if (! dt.isValid) {
                    return null;
                }
        
                dt.setLocale(locale);
            }
            else if (! format) {
                // No format given, must be ISO
                dt = new Date(d);
            }
            else {
                if (! __mlWarning) {
                    alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17');
                }
        
                __mlWarning = true;
            }
        
            return dt;
        }
        
        // Wrapper for date, datetime and time which all operate the same way with the exception of
        // the output string for auto locale support
        function __mlHelper (localeString) {
            return function ( from, to, locale, def ) {
                // Luxon and Moment support
                // Argument shifting
                if ( arguments.length === 0 ) {
                    locale = 'en';
                    to = null; // means toLocaleString
                    from = null; // means iso8601
                }
                else if ( arguments.length === 1 ) {
                    locale = 'en';
                    to = from;
                    from = null;
                }
                else if ( arguments.length === 2 ) {
                    locale = to;
                    to = from;
                    from = null;
                }
        
                var typeName = 'datetime' + (to ? '-' + to : '');
        
                // Add type detection and sorting specific to this date format - we need to be able to identify
                // date type columns as such, rather than as numbers in extensions. Hence the need for this.
                if (! DataTable.ext.type.order[typeName]) {
                    DataTable.type(typeName, {
                        detect: function (d) {
                            // The renderer will give the value to type detect as the type!
                            return d === typeName ? typeName : false;
                        },
                        order: {
                            pre: function (d) {
                                // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a
                                // `valueOf` which gives milliseconds epoch
                                return d.valueOf();
                            }
                        },
                        className: 'dt-right'
                    });
                }
            
                return function ( d, type ) {
                    // Allow for a default value
                    if (d === null || d === undefined) {
                        if (def === '--now') {
                            // We treat everything as UTC further down, so no changes are
                            // made, as such need to get the local date / time as if it were
                            // UTC
                            var local = new Date();
                            d = new Date( Date.UTC(
                                local.getFullYear(), local.getMonth(), local.getDate(),
                                local.getHours(), local.getMinutes(), local.getSeconds()
                            ) );
                        }
                        else {
                            d = '';
                        }
                    }
        
                    if (type === 'type') {
                        // Typing uses the type name for fast matching
                        return typeName;
                    }
        
                    if (d === '') {
                        return type !== 'sort'
                            ? ''
                            : __mldObj('0000-01-01 00:00:00', null, locale);
                    }
        
                    // Shortcut. If `from` and `to` are the same, we are using the renderer to
                    // format for ordering, not display - its already in the display format.
                    if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) {
                        return d;
                    }
        
                    var dt = __mldObj(d, from, locale);
        
                    if (dt === null) {
                        return d;
                    }
        
                    if (type === 'sort') {
                        return dt;
                    }
                    
                    var formatted = to === null
                        ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]()
                        : __mld(dt, 'format', 'toFormat', 'toISOString', to);
        
                    // XSS protection
                    return type === 'display' ?
                        _escapeHtml( formatted ) :
                        formatted;
                };
            }
        }
        
        // Based on locale, determine standard number formatting
        // Fallback for legacy browsers is US English
        var __thousands = ',';
        var __decimal = '.';
        
        if (window.Intl !== undefined) {
            try {
                var num = new Intl.NumberFormat().formatToParts(100000.1);
            
                for (var i=0 ; i<num.length ; i++) {
                    if (num[i].type === 'group') {
                        __thousands = num[i].value;
                    }
                    else if (num[i].type === 'decimal') {
                        __decimal = num[i].value;
                    }
                }
            }
            catch (e) {
                // noop
            }
        }
        
        // Formatted date time detection - use by declaring the formats you are going to use
        DataTable.datetime = function ( format, locale ) {
            var typeName = 'datetime-' + format;
        
            if (! locale) {
                locale = 'en';
            }
        
            if (! DataTable.ext.type.order[typeName]) {
                DataTable.type(typeName, {
                    detect: function (d) {
                        var dt = __mldObj(d, format, locale);
                        return d === '' || dt ? typeName : false;
                    },
                    order: {
                        pre: function (d) {
                            return __mldObj(d, format, locale) || 0;
                        }
                    },
                    className: 'dt-right'
                });
            }
        }
        
        /**
         * Helpers for `columns.render`.
         *
         * The options defined here can be used with the `columns.render` initialisation
         * option to provide a display renderer. The following functions are defined:
         *
         * * `moment` - Uses the MomentJS library to convert from a given format into another.
         * This renderer has three overloads:
         *   * 1 parameter:
         *     * `string` - Format to convert to (assumes input is ISO8601 and locale is `en`)
         *   * 2 parameters:
         *     * `string` - Format to convert from
         *     * `string` - Format to convert to. Assumes `en` locale
         *   * 3 parameters:
         *     * `string` - Format to convert from
         *     * `string` - Format to convert to
         *     * `string` - Locale
         * * `number` - Will format numeric data (defined by `columns.data`) for
         *   display, retaining the original unformatted data for sorting and filtering.
         *   It takes 5 parameters:
         *   * `string` - Thousands grouping separator
         *   * `string` - Decimal point indicator
         *   * `integer` - Number of decimal points to show
         *   * `string` (optional) - Prefix.
         *   * `string` (optional) - Postfix (/suffix).
         * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
         *   parameters.
         *
         * @example
         *   // Column definition using the number renderer
         *   {
         *     data: "salary",
         *     render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
         *   }
         *
         * @namespace
         */
        DataTable.render = {
            date: __mlHelper('toLocaleDateString'),
            datetime: __mlHelper('toLocaleString'),
            time: __mlHelper('toLocaleTimeString'),
            number: function ( thousands, decimal, precision, prefix, postfix ) {
                // Auto locale detection
                if (thousands === null || thousands === undefined) {
                    thousands = __thousands;
                }
        
                if (decimal === null || decimal === undefined) {
                    decimal = __decimal;
                }
        
                return {
                    display: function ( d ) {
                        if ( typeof d !== 'number' && typeof d !== 'string' ) {
                            return d;
                        }
        
                        if (d === '' || d === null) {
                            return d;
                        }
        
                        var negative = d < 0 ? '-' : '';
                        var flo = parseFloat( d );
                        var abs = Math.abs(flo);
        
                        // Scientific notation for large and small numbers
                        if (abs >= 100000000000 || (abs < 0.0001 && abs !== 0) ) {
                            var exp = flo.toExponential(precision).split(/e\+?/);
                            return exp[0] + ' x 10<sup>' + exp[1] + '</sup>';
                        }
        
                        // If NaN then there isn't much formatting that we can do - just
                        // return immediately, escaping any HTML (this was supposed to
                        // be a number after all)
                        if ( isNaN( flo ) ) {
                            return _escapeHtml( d );
                        }
        
                        flo = flo.toFixed( precision );
                        d = Math.abs( flo );
        
                        var intPart = parseInt( d, 10 );
                        var floatPart = precision ?
                            decimal+(d - intPart).toFixed( precision ).substring( 2 ):
                            '';
        
                        // If zero, then can't have a negative prefix
                        if (intPart === 0 && parseFloat(floatPart) === 0) {
                            negative = '';
                        }
        
                        return negative + (prefix||'') +
                            intPart.toString().replace(
                                /\B(?=(\d{3})+(?!\d))/g, thousands
                            ) +
                            floatPart +
                            (postfix||'');
                    }
                };
            },
        
            text: function () {
                return {
                    display: _escapeHtml,
                    filter: _escapeHtml
                };
            }
        };
        
        
        var _extTypes = DataTable.ext.type;
        
        // Get / set type
        DataTable.type = function (name, prop, val) {
            if (! prop) {
                return {
                    className: _extTypes.className[name],
                    detect: _extTypes.detect.find(function (fn) {
                        return fn.name === name;
                    }),
                    order: {
                        pre: _extTypes.order[name + '-pre'],
                        asc: _extTypes.order[name + '-asc'],
                        desc: _extTypes.order[name + '-desc']
                    },
                    render: _extTypes.render[name],
                    search: _extTypes.search[name]
                };
            }
        
            var setProp = function(prop, propVal) {
                _extTypes[prop][name] = propVal;
            };
            var setDetect = function (detect) {
                // `detect` can be a function or an object - we set a name
                // property for either - that is used for the detection
                Object.defineProperty(detect, "name", {value: name});
        
                var idx = _extTypes.detect.findIndex(function (item) {
                    return item.name === name;
                });
        
                if (idx === -1) {
                    _extTypes.detect.unshift(detect);
                }
                else {
                    _extTypes.detect.splice(idx, 1, detect);
                }
            };
            var setOrder = function (obj) {
                _extTypes.order[name + '-pre'] = obj.pre; // can be undefined
                _extTypes.order[name + '-asc'] = obj.asc; // can be undefined
                _extTypes.order[name + '-desc'] = obj.desc; // can be undefined
            };
        
            // prop is optional
            if (val === undefined) {
                val = prop;
                prop = null;
            }
        
            if (prop === 'className') {
                setProp('className', val);
            }
            else if (prop === 'detect') {
                setDetect(val);
            }
            else if (prop === 'order') {
                setOrder(val);
            }
            else if (prop === 'render') {
                setProp('render', val);
            }
            else if (prop === 'search') {
                setProp('search', val);
            }
            else if (! prop) {
                if (val.className) {
                    setProp('className', val.className);
                }
        
                if (val.detect !== undefined) {
                    setDetect(val.detect);
                }
        
                if (val.order) {
                    setOrder(val.order);
                }
        
                if (val.render !== undefined) {
                    setProp('render', val.render);
                }
        
                if (val.search !== undefined) {
                    setProp('search', val.search);
                }
            }
        }
        
        // Get a list of types
        DataTable.types = function () {
            return _extTypes.detect.map(function (fn) {
                return fn.name;
            });
        };
        
        var __diacriticSort = function (a, b) {
            a = a.toString().toLowerCase();
            b = b.toString().toLowerCase();
        
            // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old
            // Safari and thus can disable this check
            // eslint-disable-next-line compat/compat
            return a.localeCompare(b, navigator.languages[0] || navigator.language, {
                numeric: true,
                ignorePunctuation: true,
            });
        }
        
        //
        // Built in data types
        //
        
        DataTable.type('string', {
            detect: function () {
                return 'string';
            },
            order: {
                pre: function ( a ) {
                    // This is a little complex, but faster than always calling toString,
                    // http://jsperf.com/tostring-v-check
                    return _empty(a) && typeof a !== 'boolean' ?
                        '' :
                        typeof a === 'string' ?
                            a.toLowerCase() :
                            ! a.toString ?
                                '' :
                                a.toString();
                }
            },
            search: _filterString(false, true)
        });
        
        DataTable.type('string-utf8', {
            detect: {
                allOf: function ( d ) {
                    return true;
                },
                oneOf: function ( d ) {
                    // At least one data point must contain a non-ASCII character
                    // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-)
                    // this data type won't be supported.
                    // eslint-disable-next-line compat/compat
                    return ! _empty( d ) && navigator.languages && typeof d === 'string' && d.match(/[^\x00-\x7F]/);
                }
            },
            order: {
                asc: __diacriticSort,
                desc: function (a, b) {
                    return __diacriticSort(a, b) * -1;
                }
            },
            search: _filterString(false, true)
        });
        
        
        DataTable.type('html', {
            detect: {
                allOf: function ( d ) {
                    return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
                },
                oneOf: function ( d ) {
                    // At least one data point must contain a `<`
                    return ! _empty( d ) && typeof d === 'string' && d.indexOf('<') !== -1;
                }
            },
            order: {
                pre: function ( a ) {
                    return _empty(a) ?
                        '' :
                        a.replace ?
                            _stripHtml(a).trim().toLowerCase() :
                            a+'';
                }
            },
            search: _filterString(true, true)
        });
        
        
        DataTable.type('date', {
            className: 'dt-type-date',
            detect: {
                allOf: function ( d ) {
                    // V8 tries _very_ hard to make a string passed into `Date.parse()`
                    // valid, so we need to use a regex to restrict date formats. Use a
                    // plug-in for anything other than ISO8601 style strings
                    if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
                        return null;
                    }
                    var parsed = Date.parse(d);
                    return (parsed !== null && !isNaN(parsed)) || _empty(d);
                },
                oneOf: function ( d ) {
                    // At least one entry must be a date or a string with a date
                    return (d instanceof Date) || (typeof d === 'string' && _re_date.test(d));
                }
            },
            order: {
                pre: function ( d ) {
                    var ts = Date.parse( d );
                    return isNaN(ts) ? -Infinity : ts;
                }
            }
        });
        
        
        DataTable.type('html-num-fmt', {
            className: 'dt-type-numeric',
            detect: {
                allOf: function ( d, settings ) {
                    var decimal = settings.oLanguage.sDecimal;
                    return _htmlNumeric( d, decimal, true, false );
                },
                oneOf: function (d, settings) {
                    // At least one data point must contain a numeric value
                    var decimal = settings.oLanguage.sDecimal;
                    return _htmlNumeric( d, decimal, true, false );
                }
            },
            order: {
                pre: function ( d, s ) {
                    var dp = s.oLanguage.sDecimal;
                    return __numericReplace( d, dp, _re_html, _re_formatted_numeric );
                }
            },
            search: _filterString(true, true)
        });
        
        
        DataTable.type('html-num', {
            className: 'dt-type-numeric',
            detect: {
                allOf: function ( d, settings ) {
                    var decimal = settings.oLanguage.sDecimal;
                    return _htmlNumeric( d, decimal, false, true );
                },
                oneOf: function (d, settings) {
                    // At least one data point must contain a numeric value
                    var decimal = settings.oLanguage.sDecimal;
                    return _htmlNumeric( d, decimal, false, false );
                }
            },
            order: {
                pre: function ( d, s ) {
                    var dp = s.oLanguage.sDecimal;
                    return __numericReplace( d, dp, _re_html );
                }
            },
            search: _filterString(true, true)
        });
        
        
        DataTable.type('num-fmt', {
            className: 'dt-type-numeric',
            detect: {
                allOf: function ( d, settings ) {
                    var decimal = settings.oLanguage.sDecimal;
                    return _isNumber( d, decimal, true, true );
                },
                oneOf: function (d, settings) {
                    // At least one data point must contain a numeric value
                    var decimal = settings.oLanguage.sDecimal;
                    return _isNumber( d, decimal, true, false );
                }
            },
            order: {
                pre: function ( d, s ) {
                    var dp = s.oLanguage.sDecimal;
                    return __numericReplace( d, dp, _re_formatted_numeric );
                }
            }
        });
        
        
        DataTable.type('num', {
            className: 'dt-type-numeric',
            detect: {
                allOf: function ( d, settings ) {
                    var decimal = settings.oLanguage.sDecimal;
                    return _isNumber( d, decimal, false, true );
                },
                oneOf: function (d, settings) {
                    // At least one data point must contain a numeric value
                    var decimal = settings.oLanguage.sDecimal;
                    return _isNumber( d, decimal, false, false );
                }
            },
            order: {
                pre: function (d, s) {
                    var dp = s.oLanguage.sDecimal;
                    return __numericReplace( d, dp );
                }
            }
        });
        
        
        
        
        var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
            if ( d !== 0 && (!d || d === '-') ) {
                return -Infinity;
            }
            
            var type = typeof d;
        
            if (type === 'number' || type === 'bigint') {
                return d;
            }
        
            // If a decimal place other than `.` is used, it needs to be given to the
            // function so we can detect it and replace with a `.` which is the only
            // decimal place Javascript recognises - it is not locale aware.
            if ( decimalPlace ) {
                d = _numToDecimal( d, decimalPlace );
            }
        
            if ( d.replace ) {
                if ( re1 ) {
                    d = d.replace( re1, '' );
                }
        
                if ( re2 ) {
                    d = d.replace( re2, '' );
                }
            }
        
            return d * 1;
        };
        
        
        $.extend( true, DataTable.ext.renderer, {
            footer: {
                _: function ( settings, cell, classes ) {
                    cell.addClass(classes.tfoot.cell);
                }
            },
        
            header: {
                _: function ( settings, cell, classes ) {
                    cell.addClass(classes.thead.cell);
        
                    if (! settings.oFeatures.bSort) {
                        cell.addClass(classes.order.none);
                    }
        
                    var legacyTop = settings.bSortCellsTop;
                    var headerRows = cell.closest('thead').find('tr');
                    var rowIdx = cell.parent().index();
        
                    // Conditions to not apply the ordering icons
                    if (
                        // Cells and rows which have the attribute to disable the icons
                        cell.attr('data-dt-order') === 'disable' ||
                        cell.parent().attr('data-dt-order') === 'disable' ||
        
                        // Legacy support for `orderCellsTop`. If it is set, then cells
                        // which are not in the top or bottom row of the header (depending
                        // on the value) do not get the sorting classes applied to them
                        (legacyTop === true && rowIdx !== 0) ||
                        (legacyTop === false && rowIdx !== headerRows.length - 1)
                    ) {
                        return;
                    }
        
                    // No additional mark-up required
                    // Attach a sort listener to update on sort - note that using the
                    // `DT` namespace will allow the event to be removed automatically
                    // on destroy, while the `dt` namespaced event is the one we are
                    // listening for
                    $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) {
                        if ( settings !== ctx ) { // need to check this this is the host
                            return;               // table, not a nested one
                        }
        
                        var sorting = ctx.sortDetails;
        
                        if (! sorting) {
                            return;
                        }
        
                        var i;
                        var orderClasses = classes.order;
                        var columns = ctx.api.columns( cell );
                        var col = settings.aoColumns[columns.flatten()[0]];
                        var orderable = columns.orderable().includes(true);
                        var ariaType = '';
                        var indexes = columns.indexes();
                        var sortDirs = columns.orderable(true).flatten();
                        var orderedColumns = _pluck(sorting, 'col');
        
                        cell
                            .removeClass(
                                orderClasses.isAsc +' '+
                                orderClasses.isDesc
                            )
                            .toggleClass( orderClasses.none, ! orderable )
                            .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
                            .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
        
                        // Determine if all of the columns that this cell covers are included in the
                        // current ordering
                        var isOrdering = true;
                        
                        for (i=0; i<indexes.length; i++) {
                            if (! orderedColumns.includes(indexes[i])) {
                                isOrdering = false;
                            }
                        }
        
                        if ( isOrdering ) {
                            // Get the ordering direction for the columns under this cell
                            // Note that it is possible for a cell to be asc and desc sorting
                            // (column spanning cells)
                            var orderDirs = columns.order();
        
                            cell.addClass(
                                orderDirs.includes('asc') ? orderClasses.isAsc : '' +
                                orderDirs.includes('desc') ? orderClasses.isDesc : ''
                            );
                        }
        
                        // Find the first visible column that has ordering applied to it - it get's
                        // the aria information, as the ARIA spec says that only one column should
                        // be marked with aria-sort
                        var firstVis = -1; // column index
        
                        for (i=0; i<orderedColumns.length; i++) {
                            if (settings.aoColumns[orderedColumns[i]].bVisible) {
                                firstVis = orderedColumns[i];
                                break;
                            }
                        }
        
                        if (indexes[0] == firstVis) {
                            var firstSort = sorting[0];
                            var sortOrder = col.asSorting;
        
                            cell.attr('aria-sort', firstSort.dir === 'asc' ? 'ascending' : 'descending');
        
                            // Determine if the next click will remove sorting or change the sort
                            ariaType = ! sortOrder[firstSort.index + 1] ? 'Remove' : 'Reverse';
                        }
                        else {
                            cell.removeAttr('aria-sort');
                        }
        
                        cell.attr('aria-label', orderable
                            ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
                            : col.ariaTitle
                        );
        
                        // Make the headers tab-able for keyboard navigation
                        if (orderable) {
                            cell.find('.dt-column-title').attr('role', 'button');
                            cell.attr('tabindex', 0)
                        }
                    } );
                }
            },
        
            layout: {
                _: function ( settings, container, items ) {
                    var classes = settings.oClasses.layout;
                    var row = $('<div/>')
                        .attr('id', items.id || null)
                        .addClass(items.className || classes.row)
                        .appendTo( container );
        
                    $.each( items, function (key, val) {
                        if (key === 'id' || key === 'className') {
                            return;
                        }
        
                        var klass = '';
        
                        if (val.table) {
                            row.addClass(classes.tableRow);
                            klass += classes.tableCell + ' ';
                        }
        
                        if (key === 'start') {
                            klass += classes.start;
                        }
                        else if (key === 'end') {
                            klass += classes.end;
                        }
                        else {
                            klass += classes.full;
                        }
        
                        $('<div/>')
                            .attr({
                                id: val.id || null,
                                "class": val.className
                                    ? val.className
                                    : classes.cell + ' ' + klass
                            })
                            .append( val.contents )
                            .appendTo( row );
                    } );
                }
            }
        } );
        
        
        DataTable.feature = {};
        
        // Third parameter is internal only!
        DataTable.feature.register = function ( name, cb, legacy ) {
            DataTable.ext.features[ name ] = cb;
        
            if (legacy) {
                _ext.feature.push({
                    cFeature: legacy,
                    fnInit: cb
                });
            }
        };
        
        function _divProp(el, prop, val) {
            if (val) {
                el[prop] = val;
            }
        }
        
        DataTable.feature.register( 'div', function ( settings, opts ) {
            var n = $('<div>')[0];
        
            if (opts) {
                _divProp(n, 'className', opts.className);
                _divProp(n, 'id', opts.id);
                _divProp(n, 'innerHTML', opts.html);
                _divProp(n, 'textContent', opts.text);
            }
        
            return n;
        } );
        
        DataTable.feature.register( 'info', function ( settings, opts ) {
            // For compatibility with the legacy `info` top level option
            if (! settings.oFeatures.bInfo) {
                return null;
            }
        
            var
                lang  = settings.oLanguage,
                tid = settings.sTableId,
                n = $('<div/>', {
                    'class': settings.oClasses.info.container,
                } );
        
            opts = $.extend({
                callback: lang.fnInfoCallback,
                empty: lang.sInfoEmpty,
                postfix: lang.sInfoPostFix,
                search: lang.sInfoFiltered,
                text: lang.sInfo,
            }, opts);
        
        
            // Update display on each draw
            settings.aoDrawCallback.push(function (s) {
                _fnUpdateInfo(s, opts, n);
            });
        
            // For the first info display in the table, we add a callback and aria information.
            if (! settings._infoEl) {
                n.attr({
                    'aria-live': 'polite',
                    id: tid+'_info',
                    role: 'status'
                });
        
                // Table is described by our info div
                $(settings.nTable).attr( 'aria-describedby', tid+'_info' );
        
                settings._infoEl = n;
            }
        
            return n;
        }, 'i' );
        
        /**
         * Update the information elements in the display
         *  @param {object} settings dataTables settings object
         *  @memberof DataTable#oApi
         */
        function _fnUpdateInfo ( settings, opts, node )
        {
            var
                start = settings._iDisplayStart+1,
                end   = settings.fnDisplayEnd(),
                max   = settings.fnRecordsTotal(),
                total = settings.fnRecordsDisplay(),
                out   = total
                    ? opts.text
                    : opts.empty;
        
            if ( total !== max ) {
                // Record set after filtering
                out += ' ' + opts.search;
            }
        
            // Convert the macros
            out += opts.postfix;
            out = _fnMacros( settings, out );
        
            if ( opts.callback ) {
                out = opts.callback.call( settings.oInstance,
                    settings, start, end, max, total, out
                );
            }
        
            node.html( out );
        
            _fnCallbackFire(settings, null, 'info', [settings, node[0], out]);
        }
        
        var __searchCounter = 0;
        
        // opts
        // - text
        // - placeholder
        DataTable.feature.register( 'search', function ( settings, opts ) {
            // Don't show the input if filtering isn't available on the table
            if (! settings.oFeatures.bFilter) {
                return null;
            }
        
            var classes = settings.oClasses.search;
            var tableId = settings.sTableId;
            var language = settings.oLanguage;
            var previousSearch = settings.oPreviousSearch;
            var input = '<input type="search" class="'+classes.input+'"/>';
        
            opts = $.extend({
                placeholder: language.sSearchPlaceholder,
                processing: false,
                text: language.sSearch
            }, opts);
        
            // The _INPUT_ is optional - is appended if not present
            if (opts.text.indexOf('_INPUT_') === -1) {
                opts.text += '_INPUT_';
            }
        
            opts.text = _fnMacros(settings, opts.text);
        
            // We can put the <input> outside of the label if it is at the start or end
            // which helps improve accessability (not all screen readers like implicit
            // for elements).
            var end = opts.text.match(/_INPUT_$/);
            var start = opts.text.match(/^_INPUT_/);
            var removed = opts.text.replace(/_INPUT_/, '');
            var str = '<label>' + opts.text + '</label>';
        
            if (start) {
                str = '_INPUT_<label>' + removed + '</label>';
            }
            else if (end) {
                str = '<label>' + removed + '</label>_INPUT_';
            }
        
            var filter = $('<div>')
                .addClass(classes.container)
                .append(str.replace(/_INPUT_/, input));
        
            // add for and id to label and input
            filter.find('label').attr('for', 'dt-search-' + __searchCounter);
            filter.find('input').attr('id', 'dt-search-' + __searchCounter);
            __searchCounter++;
        
            var searchFn = function(event) {
                var val = this.value;
        
                if(previousSearch.return && event.key !== "Enter") {
                    return;
                }
        
                /* Now do the filter */
                if ( val != previousSearch.search ) {
                    _fnProcessingRun(settings, opts.processing, function () {
                        previousSearch.search = val;
                
                        _fnFilterComplete( settings, previousSearch );
                
                        // Need to redraw, without resorting
                        settings._iDisplayStart = 0;
                        _fnDraw( settings );
                    });
                }
            };
        
            var searchDelay = settings.searchDelay !== null ?
                settings.searchDelay :
                0;
        
            var jqFilter = $('input', filter)
                .val( previousSearch.search )
                .attr( 'placeholder', opts.placeholder )
                .on(
                    'keyup.DT search.DT input.DT paste.DT cut.DT',
                    searchDelay ?
                        DataTable.util.debounce( searchFn, searchDelay ) :
                        searchFn
                )
                .on( 'mouseup.DT', function(e) {
                    // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking
                    // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn`
                    // checks the value to see if it has changed. In other browsers it won't have.
                    setTimeout( function () {
                        searchFn.call(jqFilter[0], e);
                    }, 10);
                } )
                .on( 'keypress.DT', function(e) {
                    /* Prevent form submission */
                    if ( e.keyCode == 13 ) {
                        return false;
                    }
                } )
                .attr('aria-controls', tableId);
        
            // Update the input elements whenever the table is filtered
            $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
                if ( settings === s && jqFilter[0] !== document.activeElement ) {
                    jqFilter.val( typeof previousSearch.search !== 'function'
                        ? previousSearch.search
                        : ''
                    );
                }
            } );
        
            return filter;
        }, 'f' );
        
        // opts
        // - type - button configuration
        // - buttons - number of buttons to show - must be odd
        DataTable.feature.register( 'paging', function ( settings, opts ) {
            // Don't show the paging input if the table doesn't have paging enabled
            if (! settings.oFeatures.bPaginate) {
                return null;
            }
        
            opts = $.extend({
                buttons: DataTable.ext.pager.numbers_length,
                type: settings.sPaginationType,
                boundaryNumbers: true,
                firstLast: true,
                previousNext: true,
                numbers: true
            }, opts);
        
            var host = $('<div/>')
                .addClass(settings.oClasses.paging.container + (opts.type ? ' paging_' + opts.type : ''))
                .append(
                    $('<nav>')
                        .attr('aria-label', 'pagination')
                        .addClass(settings.oClasses.paging.nav)
                );
            var draw = function () {
                _pagingDraw(settings, host.children(), opts);
            };
        
            settings.aoDrawCallback.push(draw);
        
            // Responsive redraw of paging control
            $(settings.nTable).on('column-sizing.dt.DT', draw);
        
            return host;
        }, 'p' );
        
        /**
         * Dynamically create the button type array based on the configuration options.
         * This will only happen if the paging type is not defined.
         */
        function _pagingDynamic(opts) {
            var out = [];
        
            if (opts.numbers) {
                out.push('numbers');
            }
        
            if (opts.previousNext) {
                out.unshift('previous');
                out.push('next');
            }
        
            if (opts.firstLast) {
                out.unshift('first');
                out.push('last');
            }
        
            return out;
        }
        
        function _pagingDraw(settings, host, opts) {
            if (! settings._bInitComplete) {
                return;
            }
        
            var
                plugin = opts.type
                    ? DataTable.ext.pager[ opts.type ]
                    : _pagingDynamic,
                aria = settings.oLanguage.oAria.paginate || {},
                start      = settings._iDisplayStart,
                len        = settings._iDisplayLength,
                visRecords = settings.fnRecordsDisplay(),
                all        = len === -1,
                page = all ? 0 : Math.ceil( start / len ),
                pages = all ? 1 : Math.ceil( visRecords / len ),
                buttons = plugin(opts)
                    .map(function (val) {
                        return val === 'numbers'
                            ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers)
                            : val;
                    })
                    .flat();
        
            var buttonEls = [];
        
            for (var i=0 ; i<buttons.length ; i++) {
                var button = buttons[i];
        
                var btnInfo = _pagingButtonInfo(settings, button, page, pages);
                var btn = _fnRenderer( settings, 'pagingButton' )(
                    settings,
                    button,
                    btnInfo.display,
                    btnInfo.active,
                    btnInfo.disabled
                );
        
                var ariaLabel = typeof button === 'string'
                    ? aria[ button ]
                    : aria.number
                        ? aria.number + (button+1)
                        : null;
        
                // Common attributes
                $(btn.clicker).attr({
                    'aria-controls': settings.sTableId,
                    'aria-disabled': btnInfo.disabled ? 'true' : null,
                    'aria-current': btnInfo.active ? 'page' : null,
                    'aria-label': ariaLabel,
                    'data-dt-idx': button,
                    'tabIndex': btnInfo.disabled
                        ? -1
                        : settings.iTabIndex
                            ? settings.iTabIndex
                            : null, // `0` doesn't need a tabIndex since it is the default
                });
        
                if (typeof button !== 'number') {
                    $(btn.clicker).addClass(button);
                }
        
                _fnBindAction(
                    btn.clicker, {action: button}, function(e) {
                        e.preventDefault();
        
                        _fnPageChange( settings, e.data.action, true );
                    }
                );
        
                buttonEls.push(btn.display);
            }
        
            var wrapped = _fnRenderer(settings, 'pagingContainer')(
                settings, buttonEls
            );
        
            var activeEl = host.find(document.activeElement).data('dt-idx');
        
            host.empty().append(wrapped);
        
            if ( activeEl !== undefined ) {
                host.find( '[data-dt-idx='+activeEl+']' ).trigger('focus');
            }
        
            // Responsive - check if the buttons are over two lines based on the
            // height of the buttons and the container.
            if (
                buttonEls.length && // any buttons
                opts.buttons > 1 && // prevent infinite
                $(host).height() >= ($(buttonEls[0]).outerHeight() * 2) - 10
            ) {
                _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 }));
            }
        }
        
        /**
         * Get properties for a button based on the current paging state of the table
         *
         * @param {*} settings DT settings object
         * @param {*} button The button type in question
         * @param {*} page Table's current page
         * @param {*} pages Number of pages
         * @returns Info object
         */
        function _pagingButtonInfo(settings, button, page, pages) {
            var lang = settings.oLanguage.oPaginate;
            var o = {
                display: '',
                active: false,
                disabled: false
            };
        
            switch ( button ) {
                case 'ellipsis':
                    o.display = '&#x2026;';
                    o.disabled = true;
                    break;
        
                case 'first':
                    o.display = lang.sFirst;
        
                    if (page === 0) {
                        o.disabled = true;
                    }
                    break;
        
                case 'previous':
                    o.display = lang.sPrevious;
        
                    if ( page === 0 ) {
                        o.disabled = true;
                    }
                    break;
        
                case 'next':
                    o.display = lang.sNext;
        
                    if ( pages === 0 || page === pages-1 ) {
                        o.disabled = true;
                    }
                    break;
        
                case 'last':
                    o.display = lang.sLast;
        
                    if ( pages === 0 || page === pages-1 ) {
                        o.disabled = true;
                    }
                    break;
        
                default:
                    if ( typeof button === 'number' ) {
                        o.display = settings.fnFormatNumber( button + 1 );
                        
                        if (page === button) {
                            o.active = true;
                        }
                    }
                    break;
            }
        
            return o;
        }
        
        /**
         * Compute what number buttons to show in the paging control
         *
         * @param {*} page Current page
         * @param {*} pages Total number of pages
         * @param {*} buttons Target number of number buttons
         * @param {boolean} addFirstLast Indicate if page 1 and end should be included
         * @returns Buttons to show
         */
        function _pagingNumbers ( page, pages, buttons, addFirstLast ) {
            var
                numbers = [],
                half = Math.floor(buttons / 2),
                before = addFirstLast ? 2 : 1,
                after = addFirstLast ? 1 : 0;
        
            if ( pages <= buttons ) {
                numbers = _range(0, pages);
            }
            else if (buttons === 1) {
                // Single button - current page only
                numbers = [page];
            }
            else if (buttons === 3) {
                // Special logic for just three buttons
                if (page <= 1) {
                    numbers = [0, 1, 'ellipsis'];
                }
                else if (page >= pages - 2) {
                    numbers = _range(pages-2, pages);
                    numbers.unshift('ellipsis');
                }
                else {
                    numbers = ['ellipsis', page, 'ellipsis'];
                }
            }
            else if ( page <= half ) {
                numbers = _range(0, buttons-before);
                numbers.push('ellipsis');
        
                if (addFirstLast) {
                    numbers.push(pages-1);
                }
            }
            else if ( page >= pages - 1 - half ) {
                numbers = _range(pages-(buttons-before), pages);
                numbers.unshift('ellipsis');
        
                if (addFirstLast) {
                    numbers.unshift(0);
                }
            }
            else {
                numbers = _range(page-half+before, page+half-after);
                numbers.push('ellipsis');
                numbers.unshift('ellipsis');
        
                if (addFirstLast) {
                    numbers.push(pages-1);
                    numbers.unshift(0);
                }
            }
        
            return numbers;
        }
        
        var __lengthCounter = 0;
        
        // opts
        // - menu
        // - text
        DataTable.feature.register( 'pageLength', function ( settings, opts ) {
            var features = settings.oFeatures;
        
            // For compatibility with the legacy `pageLength` top level option
            if (! features.bPaginate || ! features.bLengthChange) {
                return null;
            }
        
            opts = $.extend({
                menu: settings.aLengthMenu,
                text: settings.oLanguage.sLengthMenu
            }, opts);
        
            var
                classes  = settings.oClasses.length,
                tableId  = settings.sTableId,
                menu     = opts.menu,
                lengths  = [],
                language = [],
                i;
        
            // Options can be given in a number of ways
            if (Array.isArray( menu[0] )) {
                // Old 1.x style - 2D array
                lengths = menu[0];
                language = menu[1];
            }
            else {
                for ( i=0 ; i<menu.length ; i++ ) {
                    // An object with different label and value
                    if ($.isPlainObject(menu[i])) {
                        lengths.push(menu[i].value);
                        language.push(menu[i].label);
                    }
                    else {
                        // Or just a number to display and use
                        lengths.push(menu[i]);
                        language.push(menu[i]);
                    }
                }
            }
        
            // We can put the <select> outside of the label if it is at the start or
            // end which helps improve accessability (not all screen readers like
            // implicit for elements).
            var end = opts.text.match(/_MENU_$/);
            var start = opts.text.match(/^_MENU_/);
            var removed = opts.text.replace(/_MENU_/, '');
            var str = '<label>' + opts.text + '</label>';
        
            if (start) {
                str = '_MENU_<label>' + removed + '</label>';
            }
            else if (end) {
                str = '<label>' + removed + '</label>_MENU_';
            }
        
            // Wrapper element - use a span as a holder for where the select will go
            var tmpId = 'tmp-' + (+new Date())
            var div = $('<div/>')
                .addClass( classes.container )
                .append(
                    str.replace( '_MENU_', '<span id="'+tmpId+'"></span>' )
                );
        
            // Save text node content for macro updating
            var textNodes = [];
            div.find('label')[0].childNodes.forEach(function (el) {
                if (el.nodeType === Node.TEXT_NODE) {
                    textNodes.push({
                        el: el,
                        text: el.textContent
                    });
                }
            })
        
            // Update the label text in case it has an entries value
            var updateEntries = function (len) {
                textNodes.forEach(function (node) {
                    node.el.textContent = _fnMacros(settings, node.text, len);
                });
            }
        
            // Next, the select itself, along with the options
            var select = $('<select/>', {
                'name':          tableId+'_length',
                'aria-controls': tableId,
                'class':         classes.select
            } );
        
            for ( i=0 ; i<lengths.length ; i++ ) {
                select[0][ i ] = new Option(
                    typeof language[i] === 'number' ?
                        settings.fnFormatNumber( language[i] ) :
                        language[i],
                    lengths[i]
                );
            }
        
            // add for and id to label and input
            div.find('label').attr('for', 'dt-length-' + __lengthCounter);
            select.attr('id', 'dt-length-' + __lengthCounter);
            __lengthCounter++;
        
            // Swap in the select list
            div.find('#' + tmpId).replaceWith(select);
        
            // Can't use `select` variable as user might provide their own and the
            // reference is broken by the use of outerHTML
            $('select', div)
                .val( settings._iDisplayLength )
                .on( 'change.DT', function() {
                    _fnLengthChange( settings, $(this).val() );
                    _fnDraw( settings );
                } );
        
            // Update node value whenever anything changes the table's length
            $(settings.nTable).on( 'length.dt.DT', function (e, s, len) {
                if ( settings === s ) {
                    $('select', div).val( len );
        
                    // Resolve plurals in the text for the new length
                    updateEntries(len);
                }
            } );
        
            updateEntries(settings._iDisplayLength);
        
            return div;
        }, 'l' );
        
        // jQuery access
        $.fn.dataTable = DataTable;
        
        // Provide access to the host jQuery object (circular reference)
        DataTable.$ = $;
        
        // Legacy aliases
        $.fn.dataTableSettings = DataTable.settings;
        $.fn.dataTableExt = DataTable.ext;
        
        // With a capital `D` we return a DataTables API instance rather than a
        // jQuery object
        $.fn.DataTable = function ( opts ) {
            return $(this).dataTable( opts ).api();
        };
        
        // All properties that are available to $.fn.dataTable should also be
        // available on $.fn.DataTable
        $.each( DataTable, function ( prop, val ) {
            $.fn.DataTable[ prop ] = val;
        } );
    
        return DataTable;
    }));
    
    
    /*! DataTables styling integration
     * © SpryMedia Ltd - datatables.net/license
     */
    
    (function( factory ){
        if ( typeof define === 'function' && define.amd ) {
            // AMD
            define( ['jquery', 'datatables.net'], function ( $ ) {
                return factory( $, window, document );
            } );
        }
        else if ( typeof exports === 'object' ) {
            // CommonJS
            var jq = require('jquery');
            var cjsRequires = function (root, $) {
                if ( ! $.fn.dataTable ) {
                    require('datatables.net')(root, $);
                }
            };
    
            if (typeof window === 'undefined') {
                module.exports = function (root, $) {
                    if ( ! root ) {
                        // CommonJS environments without a window global must pass a
                        // root. This will give an error otherwise
                        root = window;
                    }
    
                    if ( ! $ ) {
                        $ = jq( root );
                    }
    
                    cjsRequires( root, $ );
                    return factory( $, root, root.document );
                };
            }
            else {
                cjsRequires( window, jq );
                module.exports = factory( jq, window, window.document );
            }
        }
        else {
            // Browser
            factory( jQuery, window, document );
        }
    }(function( $, window, document ) {
    'use strict';
    var DataTable = $.fn.dataTable;
    
    
    
    
    
    return DataTable;
    }));
    
    
    /*! FixedColumns 5.0.1
     * © SpryMedia Ltd - datatables.net/license
     */
    
    (function( factory ){
        if ( typeof define === 'function' && define.amd ) {
            // AMD
            define( ['jquery', 'datatables.net'], function ( $ ) {
                return factory( $, window, document );
            } );
        }
        else if ( typeof exports === 'object' ) {
            // CommonJS
            var jq = require('jquery');
            var cjsRequires = function (root, $) {
                if ( ! $.fn.dataTable ) {
                    require('datatables.net')(root, $);
                }
            };
    
            if (typeof window === 'undefined') {
                module.exports = function (root, $) {
                    if ( ! root ) {
                        // CommonJS environments without a window global must pass a
                        // root. This will give an error otherwise
                        root = window;
                    }
    
                    if ( ! $ ) {
                        $ = jq( root );
                    }
    
                    cjsRequires( root, $ );
                    return factory( $, root, root.document );
                };
            }
            else {
                cjsRequires( window, jq );
                module.exports = factory( jq, window, window.document );
            }
        }
        else {
            // Browser
            factory( jQuery, window, document );
        }
    }(function( $, window, document ) {
    'use strict';
    var DataTable = $.fn.dataTable;
    
    
    (function () {
        'use strict';
    
        var $$1;
        var DataTable$1;
        function setJQuery(jq) {
            $$1 = jq;
            DataTable$1 = $$1.fn.dataTable;
        }
        var FixedColumns = /** @class */ (function () {
            function FixedColumns(settings, opts) {
                var _this = this;
                // Check that the required version of DataTables is included
                if (!DataTable$1 ||
                    !DataTable$1.versionCheck ||
                    !DataTable$1.versionCheck('2')) {
                    throw new Error('FixedColumns requires DataTables 2 or newer');
                }
                var table = new DataTable$1.Api(settings);
                this.classes = $$1.extend(true, {}, FixedColumns.classes);
                // Get options from user
                this.c = $$1.extend(true, {}, FixedColumns.defaults, opts);
                this.s = {
                    dt: table,
                    rtl: $$1(table.table().node()).css('direction') === 'rtl'
                };
                // Backwards compatibility for deprecated options
                if (opts && opts.leftColumns !== undefined) {
                    opts.left = opts.leftColumns;
                }
                if (opts && opts.left !== undefined) {
                    this.c[this.s.rtl ? 'end' : 'start'] = opts.left;
                }
                if (opts && opts.rightColumns !== undefined) {
                    opts.right = opts.rightColumns;
                }
                if (opts && opts.right !== undefined) {
                    this.c[this.s.rtl ? 'start' : 'end'] = opts.right;
                }
                this.dom = {
                    bottomBlocker: $$1('<div>').addClass(this.classes.bottomBlocker),
                    topBlocker: $$1('<div>').addClass(this.classes.topBlocker),
                    scroller: $$1('div.dt-scroll-body', this.s.dt.table().container())
                };
                if (this.s.dt.settings()[0]._bInitComplete) {
                    // Fixed Columns Initialisation
                    this._addStyles();
                    this._setKeyTableListener();
                }
                else {
                    table.one('init.dt.dtfc', function () {
                        // Fixed Columns Initialisation
                        _this._addStyles();
                        _this._setKeyTableListener();
                    });
                }
                // Lots or reasons to redraw the column styles
                table.on('column-sizing.dt.dtfc column-reorder.dt.dtfc draw.dt.dtfc', function () { return _this._addStyles(); });
                // Column visibility can trigger a number of times quickly, so we debounce it
                var debounced = DataTable$1.util.debounce(function () {
                    _this._addStyles();
                }, 50);
                table.on('column-visibility.dt.dtfc', function () {
                    debounced();
                });
                // Add classes to indicate scrolling state for styling
                this.dom.scroller.on('scroll.dtfc', function () { return _this._scroll(); });
                this._scroll();
                // Make class available through dt object
                table.settings()[0]._fixedColumns = this;
                table.on('destroy', function () { return _this._destroy(); });
                return this;
            }
            FixedColumns.prototype.end = function (newVal) {
                // If the value is to change
                if (newVal !== undefined) {
                    if (newVal >= 0 && newVal <= this.s.dt.columns().count()) {
                        // Set the new values and redraw the columns
                        this.c.end = newVal;
                        this._addStyles();
                    }
                    return this;
                }
                return this.c.end;
            };
            /**
             * Left fix - accounting for RTL
             *
             * @param count Columns to fix, or undefined for getter
             */
            FixedColumns.prototype.left = function (count) {
                return this.s.rtl
                    ? this.end(count)
                    : this.start(count);
            };
            /**
             * Right fix - accounting for RTL
             *
             * @param count Columns to fix, or undefined for getter
             */
            FixedColumns.prototype.right = function (count) {
                return this.s.rtl
                    ? this.start(count)
                    : this.end(count);
            };
            FixedColumns.prototype.start = function (newVal) {
                // If the value is to change
                if (newVal !== undefined) {
                    if (newVal >= 0 && newVal <= this.s.dt.columns().count()) {
                        // Set the new values and redraw the columns
                        this.c.start = newVal;
                        this._addStyles();
                    }
                    return this;
                }
                return this.c.start;
            };
            /**
             * Iterates over the columns, fixing the appropriate ones to the left and right
             */
            FixedColumns.prototype._addStyles = function () {
                var dt = this.s.dt;
                var that = this;
                var colCount = this.s.dt.columns(':visible').count();
                var headerStruct = dt.table().header.structure(':visible');
                var footerStruct = dt.table().footer.structure(':visible');
                var widths = dt.columns(':visible').widths().toArray();
                var wrapper = $$1(dt.table().node()).closest('div.dt-scroll');
                var scroller = $$1(dt.table().node()).closest('div.dt-scroll-body')[0];
                var rtl = this.s.rtl;
                var start = this.c.start;
                var end = this.c.end;
                var left = rtl ? end : start;
                var right = rtl ? start : end;
                var barWidth = dt.settings()[0].oBrowser.barWidth; // dt internal
                // Do nothing if no scrolling in the DataTable
                if (wrapper.length === 0) {
                    return this;
                }
                // Bar not needed - no vertical scrolling
                if (scroller.offsetWidth === scroller.clientWidth) {
                    barWidth = 0;
                }
                // Loop over the visible columns, setting their state
                dt.columns().every(function (colIdx) {
                    var visIdx = dt.column.index('toVisible', colIdx);
                    var offset;
                    // Skip the hidden columns
                    if (visIdx === null) {
                        return;
                    }
                    if (visIdx < start) {
                        // Fix to the start
                        offset = that._sum(widths, visIdx);
                        that._fixColumn(visIdx, offset, 'start', headerStruct, footerStruct, barWidth);
                    }
                    else if (visIdx >= colCount - end) {
                        // Fix to the end
                        offset = that._sum(widths, colCount - visIdx - 1, true);
                        that._fixColumn(visIdx, offset, 'end', headerStruct, footerStruct, barWidth);
                    }
                    else {
                        // Release
                        that._fixColumn(visIdx, 0, 'none', headerStruct, footerStruct, barWidth);
                    }
                });
                // Apply classes to table to indicate what state we are in
                $$1(dt.table().node())
                    .toggleClass(that.classes.tableFixedStart, start > 0)
                    .toggleClass(that.classes.tableFixedEnd, end > 0)
                    .toggleClass(that.classes.tableFixedLeft, left > 0)
                    .toggleClass(that.classes.tableFixedRight, right > 0);
                // Blocker elements for when scroll bars are always visible
                var headerEl = dt.table().header();
                var footerEl = dt.table().footer();
                var headerHeight = $$1(headerEl).outerHeight();
                var footerHeight = $$1(footerEl).outerHeight();
                this.dom.topBlocker
                    .appendTo(wrapper)
                    .css('top', 0)
                    .css(this.s.rtl ? 'left' : 'right', 0)
                    .css('height', headerHeight)
                    .css('width', barWidth + 1)
                    .css('display', barWidth ? 'block' : 'none');
                if (footerEl) {
                    this.dom.bottomBlocker
                        .appendTo(wrapper)
                        .css('bottom', 0)
                        .css(this.s.rtl ? 'left' : 'right', 0)
                        .css('height', footerHeight)
                        .css('width', barWidth + 1)
                        .css('display', barWidth ? 'block' : 'none');
                }
            };
            /**
             * Clean up
             */
            FixedColumns.prototype._destroy = function () {
                this.s.dt.off('.dtfc');
                this.dom.scroller.off('.dtfc');
                $$1(this.s.dt.table().node())
                    .removeClass(this.classes.tableScrollingEnd + ' ' +
                    this.classes.tableScrollingLeft + ' ' +
                    this.classes.tableScrollingStart + ' ' +
                    this.classes.tableScrollingRight);
                this.dom.bottomBlocker.remove();
                this.dom.topBlocker.remove();
            };
            /**
             * Fix or unfix a column
             *
             * @param idx Column visible index to operate on
             * @param offset Offset from the start (pixels)
             * @param side start, end or none to unfix a column
             * @param header DT header structure object
             * @param footer DT footer structure object
             */
            FixedColumns.prototype._fixColumn = function (idx, offset, side, header, footer, barWidth) {
                var _this = this;
                var dt = this.s.dt;
                var applyStyles = function (jq, part) {
                    if (side === 'none') {
                        jq.css('position', '')
                            .css('left', '')
                            .css('right', '')
                            .removeClass(_this.classes.fixedEnd + ' ' +
                            _this.classes.fixedLeft + ' ' +
                            _this.classes.fixedRight + ' ' +
                            _this.classes.fixedStart);
                    }
                    else {
                        var positionSide = side === 'start' ? 'left' : 'right';
                        if (_this.s.rtl) {
                            positionSide = side === 'start' ? 'right' : 'left';
                        }
                        var off = offset;
                        if (side === 'end' && (part === 'header' || part === 'footer')) {
                            off += barWidth;
                        }
                        jq.css('position', 'sticky')
                            .css(positionSide, off)
                            .addClass(side === 'start'
                            ? _this.classes.fixedStart
                            : _this.classes.fixedEnd)
                            .addClass(positionSide === 'left'
                            ? _this.classes.fixedLeft
                            : _this.classes.fixedRight);
                    }
                };
                header.forEach(function (row) {
                    if (row[idx]) {
                        applyStyles($$1(row[idx].cell), 'header');
                    }
                });
                applyStyles(dt.column(idx + ':visible', { page: 'current' }).nodes().to$(), 'body');
                if (footer) {
                    footer.forEach(function (row) {
                        if (row[idx]) {
                            applyStyles($$1(row[idx].cell), 'footer');
                        }
                    });
                }
            };
            /**
             * Update classes on the table to indicate if the table is scrolling or not
             */
            FixedColumns.prototype._scroll = function () {
                var scroller = this.dom.scroller[0];
                // Not a scrolling table
                if (!scroller) {
                    return;
                }
                // Need to update the classes on potentially multiple table tags. There is the
                // main one, the scrolling ones and if FixedHeader is active, the holding
                // position ones! jQuery will deduplicate for us.
                var table = $$1(this.s.dt.table().node())
                    .add(this.s.dt.table().header().parentNode)
                    .add(this.s.dt.table().footer().parentNode)
                    .add('div.dt-scroll-headInner table', this.s.dt.table().container())
                    .add('div.dt-scroll-footInner table', this.s.dt.table().container());
                var scrollLeft = scroller.scrollLeft; // 0 when fully scrolled left
                var ltr = !this.s.rtl;
                var scrollStart = scrollLeft !== 0;
                var scrollEnd = scroller.scrollWidth > (scroller.clientWidth + Math.abs(scrollLeft) + 1); // extra 1 for Chrome
                table.toggleClass(this.classes.tableScrollingStart, scrollStart);
                table.toggleClass(this.classes.tableScrollingEnd, scrollEnd);
                table.toggleClass(this.classes.tableScrollingLeft, (scrollStart && ltr) || (scrollEnd && !ltr));
                table.toggleClass(this.classes.tableScrollingRight, (scrollEnd && ltr) || (scrollStart && !ltr));
            };
            FixedColumns.prototype._setKeyTableListener = function () {
                var _this = this;
                this.s.dt.on('key-focus.dt.dtfc', function (e, dt, cell) {
                    var currScroll;
                    var cellPos = $$1(cell.node()).offset();
                    var scroller = _this.dom.scroller[0];
                    var scroll = $$1($$1(_this.s.dt.table().node()).closest('div.dt-scroll-body'));
                    // If there are fixed columns to the left
                    if (_this.c.start > 0) {
                        // Get the rightmost left fixed column header, it's position and it's width
                        var rightMost = $$1(_this.s.dt.column(_this.c.start - 1).header());
                        var rightMostPos = rightMost.offset();
                        var rightMostWidth = rightMost.outerWidth();
                        // If the current highlighted cell is left of the rightmost cell on the screen
                        if ($$1(cell.node()).hasClass(_this.classes.fixedLeft)) {
                            // Fixed columns have the scrollbar at the start, always
                            scroll.scrollLeft(0);
                        }
                        else if (cellPos.left < rightMostPos.left + rightMostWidth) {
                            // Scroll it into view
                            currScroll = scroll.scrollLeft();
                            scroll.scrollLeft(currScroll -
                                (rightMostPos.left + rightMostWidth - cellPos.left));
                        }
                    }
                    // If there are fixed columns to the right
                    if (_this.c.end > 0) {
                        // Get the number of columns and the width of the cell as doing right side calc
                        var numCols = _this.s.dt.columns().data().toArray().length;
                        var cellWidth = $$1(cell.node()).outerWidth();
                        // Get the leftmost right fixed column header and it's position
                        var leftMost = $$1(_this.s.dt.column(numCols - _this.c.end).header());
                        var leftMostPos = leftMost.offset();
                        // If the current highlighted cell is right of the leftmost cell on the screen
                        if ($$1(cell.node()).hasClass(_this.classes.fixedRight)) {
                            scroll.scrollLeft(scroller.scrollWidth - scroller.clientWidth);
                        }
                        else if (cellPos.left + cellWidth > leftMostPos.left) {
                            // Scroll it into view
                            currScroll = scroll.scrollLeft();
                            scroll.scrollLeft(currScroll -
                                (leftMostPos.left - (cellPos.left + cellWidth)));
                        }
                    }
                });
            };
            /**
             * Sum a range of values from an array
             *
             * @param widths
             * @param index
             * @returns
             */
            FixedColumns.prototype._sum = function (widths, index, reverse) {
                if (reverse === void 0) { reverse = false; }
                if (reverse) {
                    widths = widths.slice().reverse();
                }
                return widths.slice(0, index).reduce(function (accum, val) { return accum + val; }, 0);
            };
            FixedColumns.version = '5.0.1';
            FixedColumns.classes = {
                bottomBlocker: 'dtfc-bottom-blocker',
                fixedEnd: 'dtfc-fixed-end',
                fixedLeft: 'dtfc-fixed-left',
                fixedRight: 'dtfc-fixed-right',
                fixedStart: 'dtfc-fixed-start',
                tableFixedEnd: 'dtfc-has-end',
                tableFixedLeft: 'dtfc-has-left',
                tableFixedRight: 'dtfc-has-right',
                tableFixedStart: 'dtfc-has-start',
                tableScrollingEnd: 'dtfc-scrolling-end',
                tableScrollingLeft: 'dtfc-scrolling-left',
                tableScrollingRight: 'dtfc-scrolling-right',
                tableScrollingStart: 'dtfc-scrolling-start',
                topBlocker: 'dtfc-top-blocker'
            };
            FixedColumns.defaults = {
                i18n: {
                    button: 'FixedColumns'
                },
                start: 1,
                end: 0
            };
            return FixedColumns;
        }());
    
        /*! FixedColumns 5.0.1
         * © SpryMedia Ltd - datatables.net/license
         */
        setJQuery($);
        $.fn.dataTable.FixedColumns = FixedColumns;
        $.fn.DataTable.FixedColumns = FixedColumns;
        var apiRegister = DataTable.Api.register;
        apiRegister('fixedColumns()', function () {
            return this;
        });
        apiRegister('fixedColumns().start()', function (newVal) {
            var ctx = this.context[0];
            if (newVal !== undefined) {
                ctx._fixedColumns.start(newVal);
                return this;
            }
            else {
                return ctx._fixedColumns.start();
            }
        });
        apiRegister('fixedColumns().end()', function (newVal) {
            var ctx = this.context[0];
            if (newVal !== undefined) {
                ctx._fixedColumns.end(newVal);
                return this;
            }
            else {
                return ctx._fixedColumns.end();
            }
        });
        apiRegister('fixedColumns().left()', function (newVal) {
            var ctx = this.context[0];
            if (newVal !== undefined) {
                ctx._fixedColumns.left(newVal);
                return this;
            }
            else {
                return ctx._fixedColumns.left();
            }
        });
        apiRegister('fixedColumns().right()', function (newVal) {
            var ctx = this.context[0];
            if (newVal !== undefined) {
                ctx._fixedColumns.right(newVal);
                return this;
            }
            else {
                return ctx._fixedColumns.right();
            }
        });
        DataTable.ext.buttons.fixedColumns = {
            action: function (e, dt, node, config) {
                if ($(node).attr('active')) {
                    $(node).removeAttr('active').removeClass('active');
                    dt.fixedColumns().start(0);
                    dt.fixedColumns().end(0);
                }
                else {
                    $(node).attr('active', 'true').addClass('active');
                    dt.fixedColumns().start(config.config.start);
                    dt.fixedColumns().end(config.config.end);
                }
            },
            config: {
                start: 1,
                end: 0
            },
            init: function (dt, node, config) {
                if (dt.settings()[0]._fixedColumns === undefined) {
                    _init(dt.settings(), config);
                }
                $(node).attr('active', 'true').addClass('active');
                dt.button(node).text(config.text || dt.i18n('buttons.fixedColumns', dt.settings()[0]._fixedColumns.c.i18n.button));
            },
            text: null
        };
        function _init(settings, options) {
            if (options === void 0) { options = null; }
            var api = new DataTable.Api(settings);
            var opts = options
                ? options
                : api.init().fixedColumns || DataTable.defaults.fixedColumns;
            var fixedColumns = new FixedColumns(api, opts);
            return fixedColumns;
        }
        // Attach a listener to the document which listens for DataTables initialisation
        // events so we can automatically initialise
        $(document).on('plugin-init.dt', function (e, settings) {
            if (e.namespace !== 'dt') {
                return;
            }
            if (settings.oInit.fixedColumns ||
                DataTable.defaults.fixedColumns) {
                if (!settings._fixedColumns) {
                    _init(settings, null);
                }
            }
        });
    
    })();
    
    
    return DataTable;
    }));
    
            
    })(jQuery, jQuery);;
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../SysControls/SysElement.js" />
/// <reference path="../SysControls/SysLegacy.js" />
/// <reference path="../SysGrid/ExpandableMatrixRow.js" />

(function () {

	var ExpandableMatrix = (function () {
		// ----- Public interface -----

		ExpandableMatrixDef.prototype = {
			id: null,
			matrix: null,
			rows: null,
			rowPrefix: "",
			expandedRow: null,
			currentRowId: null,

			Expand: function (rowId) {
				/// <summary>Expand the row and move the html elements to a column from different columns.</summary/>
			},

			Collapse: function (rowId) {
				/// <summary>Collapse the row and move the html elements to their original column.</summary/>
			},

			AddCustomizeFeaturesToCollapse: function (rowId) {
				///<summary>Add extra behaviour to collapse mode.</summary>
			},

			AddCustomizeFeaturesToExpand: function (rowId) {
				///<summary>Add extra behaviour to expand mode.</summary>
			},

			Delete: function (rowId) {
				/// <summary>Hide the selected row and delete the values for the row.</summary/>
			},

			AddNewRow: function (addNewButton) {
				/// <summary>Add new row to matrix, and it is expanded row.</summary/>
			},

			AddCustomizeFeaturesToDelete: function (rowId) {
				///<summary>Add extra behaviour to delete function.</summary>
			},

			SetRowReadOnly: function (rowId) {
				/// <summary>Set row to read only.</summary/>
			}
		};

		// ----- Constructor -----

		function ExpandableMatrixDef(matrixId) {
			/// <summary></summary/>
			/// <param name="matrixId" type="String|SysElement" ></param>
			/// <field name="matrix" type="SysElement">The complete matrix</field>
			/// <field name="rows" type="undefined">To be determined</field>

			if (ExpandableMatrixDef._initialized === undefined) {

				// ----- Local interface -----

				ExpandableMatrixDef.prototype._columnControls;
				ExpandableMatrixDef.prototype._addNewButton;

				ExpandableMatrixDef.prototype._Init = function (matrixId) {
					if (matrixId != null) {
						this.matrix = new SysElement(matrixId);
						this.id = matrixId;
						this.rowPrefix = matrixId + "_r";
						this._columnControls = [];
						this._InitializedColumnControls(this.rowPrefix + "0");
						this._addNewButton = new SysElement(this.id + "_addnew");
						this._AddNewButtonOnFocus();
						this.rows = [];

						if (typeof (AddCustomizeFeaturesToCollapse) === "function") {
							this.AddCustomizeFeaturesToCollapse = AddCustomizeFeaturesToCollapse;
						};

						if (typeof (AddCustomizeFeaturesToExpand) === "function") {
							this.AddCustomizeFeaturesToExpand = AddCustomizeFeaturesToExpand;
						};

						if (typeof (AddCustomizeFeaturesToDelete) === "function") {
							this.AddCustomizeFeaturesToDelete = AddCustomizeFeaturesToDelete;
						};
					};
				};

				ExpandableMatrixDef.prototype.Expand = function (rowId) {
					if (this.expandedRow != null) {
						this.Collapse();
					};

					if (rowId != null) {
						for (var row in this.rows) {
							if (this.rows[row].rowId === rowId) {
								this.expandedRow = this.rows[row];
								this.rows[row].Expand();
							};
						}
						this.AddCustomizeFeaturesToExpand(rowId);
						this._SetFocus();
					};
				};

				ExpandableMatrixDef.prototype.Collapse = function () {
					//Move fields back to original positions.
					this.expandedRow.columnControls = this._columnControls.slice();
					this.expandedRow.Collapse();
					this.AddCustomizeFeaturesToCollapse(this.expandedRow.rowId);
					this.expandedRow = null;
				};

				ExpandableMatrixDef.prototype.CollapseAll = function () {
					//Collapse all the rows.
					for (var row in this.rows) {
						this.expandedRow = this.rows[row];
						this.Collapse();
					}
				};

				ExpandableMatrixDef.prototype.Delete = function (deleteButton) {
					if (deleteButton != null) {
						var rowId = SysGridRowID(deleteButton);
						// Hide the row and clear the values when user click on submit/ save button.
						for (var row in this.rows) {
							if (this.rows[row].rowId === rowId) {
								this.rows[row].Delete();
								this.rows.splice(row, 1);
								break;
							};
						}
						this.AddCustomizeFeaturesToDelete(rowId);

						if (this.rows.length === 0) {
							this._addNewButton.FireEvent("click");
						};
					};
				};

				ExpandableMatrixDef.prototype.AddNewRow = function (addNewButton) {
					if (addNewButton != null) {
						SysMatrixAddRows(addNewButton, this.id, true, false);
						var lastID = new Number(SysGet(this.id + "_LastID"));
						var rowId = this.id + "_r" + lastID;
						this.rows.push(new ExpandableMatrixRow(rowId));
						this.Expand(rowId);
					};
				};

				ExpandableMatrixDef.prototype.SetRowReadOnly = function (rowId) {
					if (rowId != null) {
						for (var row in this.rows) {
							if (this.rows[row].rowId === rowId) {
								this.rows[row].SetReadOnly(true);
								break;
							};
						}
					};
				};

				ExpandableMatrixDef.prototype._AddNewButtonOnFocus = function () {
					var me = this;

					if (me._addNewButton.IsVisible()) {
						me._addNewButton.AttachEvent("focus",
							function () {
								if (UserAgent.IsFF() && UserAgent.majorVersion >= 4) {
									window.setTimeout(function () {
										me._addNewButton.FireEvent("click");
									}, 1);
								}
								else {
									me.AddNewRow(me._addNewButton.GetDomElement());
								}
							});
					}
				};

				ExpandableMatrixDef.prototype._InitializedColumnControls = function (rowId) {
					if (rowId != null) {
						var row = new SysElement(rowId).element;
						var columns = row.find("td[class*=ExpandableColumn]:visible");

						for (i = 0; i < columns.length; i++) {
							var column = $(columns[i]);
							var controlColumn;
							var controlsToMove;
							var fieldId = [];
							var columnId = column.attr("id");

							controlsToMove = column.find("div.ExpandableDiv");

							controlsToMove.each(function () {
								var control = $(this);

								controlColumn = control.closest("td");
								fieldId.push(control.attr("id"));
							});

							if (controlColumn != null) {
								this._columnControls.push({
									columnId: columnId,
									fieldId: fieldId
								});
							};
						}
					};
				};

				ExpandableMatrixDef.prototype._SetFocus = function () {
					firstControl = this.expandedRow.row.element.find(":input:not([readonly],input[type=button],button)").filter(":visible:first");
					firstControl.focus();
				};

				ExpandableMatrixDef._initialized = true;
			}

			this._Init(matrixId);
		}

		return ExpandableMatrixDef;
	})();

	window.ExpandableMatrix = ExpandableMatrix;
})();
;
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../SysControls/SysElement.js" />
/// <reference path="../SysControls/SysLegacy.js" />

(function () {

	var ExpandableMatrixRow = (function () {
		// ----- Public interface -----

		ExpandableMatrixRowDef.prototype = {
			rowId: null,
			row: null,
			columnControls: [],

			Expand: function () {
				/// <summary>Move the html elements to a target column.</summary/>
			},

			Collapse: function () {
				/// <summary>Move the html elements to original column.</summary/>
			},

			Delete: function () {
				/// <summary>Hide the selected row and delete the values for the row.</summary/>
			},

			SetReadOnly: function (readOnly) {
				/// <summary>Set row to read only.</summary/>
			}
		};

		// ----- Constructor -----

		function ExpandableMatrixRowDef(rowId) {
			/// <summary></summary/>
			/// <param name="rowId" type="String|SysElement" ></param>
			/// <field name="row" type="SysElement">Selected matrix row</field>

			if (ExpandableMatrixRowDef._initialized === undefined) {

				// ----- Local interface -----
				ExpandableMatrixRowDef.prototype._columns = null;
				ExpandableMatrixRowDef.prototype._targetColumn = null;
				ExpandableMatrixRowDef.prototype._collapseButton = null;
				ExpandableMatrixRowDef.prototype._expandButton = null;
				ExpandableMatrixRowDef.prototype._deleteButton = null;
				ExpandableMatrixRowDef.prototype._isExpand = false;
				ExpandableMatrixRowDef.prototype._favoriteButtonImage = null;
				ExpandableMatrixRowDef.prototype._readOnly = false;

				ExpandableMatrixRowDef.prototype._Init = function (rowId) {
					var rowIdRegExp = new RegExp(/_rr0w/);
					if (!rowIdRegExp.test(rowId)) {
						this.rowId = rowId;
						this.row = new SysElement(this.rowId);
						this._columns = this.row.element.find("td[class*=ExpandableColumn]:visible");
						this._expandButton = new SysElement(this.rowId + "_Expand");
						this._collapseButton = new SysElement(this.rowId + "_Collapse");
						this._InitializeDeletebutton();
						this._InitializeFavoriteButton();
						this._HideDeletedRow();
					}
				};

				ExpandableMatrixRowDef.prototype.Expand = function () {
					// Move next cell in next column to first column after first cell.
					this._expandButton.Hide();
					this._collapseButton.Show();
					this._isExpand = true;
					//Set a css class for expanded row
					new SysElement(this.rowId).AddClass("ExpandedRow");

					for (i = 0; i < this._columns.length; i++) {
						var column = new SysElement(this._columns[i]);
						this._MoveColumnWhenExpanding(column);
					};

					if (this._targetColumn != null) {
						this._targetColumn.Attribute("colspan", this._columns.length);
						this._targetColumn.Show();
					}
				};

				ExpandableMatrixRowDef.prototype.Collapse = function () {
					// Move back all the fields to it's orignal position.
					this._collapseButton.Hide();
					this._expandButton.Show();
					//Set a css class for expanded row
					new SysElement(this.rowId).RemoveClass("ExpandedRow");

					this._MoveColumnWhenCollapse();
				};

				ExpandableMatrixRowDef.prototype.Delete = function () {
					// Hide the row and clear the values when user click on submit/ save button.
					var controlList = this.row.element.find("input");
					var cellIdPrefix = new RegExp(this.rowId + "_c");

					if (controlList.size() > 0) {
						controlList.each(function () {
							var control = this;
							control.value = "";

							if (control.id.match(cellIdPrefix)) {
								SysMatrixTotalize(control);
							}
						});
						new SysElement(this.rowId + "_Deleted").Value(true);
						this.row.Hide();
					}
				};

				ExpandableMatrixRowDef.prototype.SetReadOnly = function (readOnly) {
					this._readOnly = readOnly;
				};

				ExpandableMatrixRowDef.prototype._MoveColumnWhenExpanding = function (column) {
					if (column != null) {
						var me = this;
						var controlColumn;
						var control;

						var columnId = column.Attribute("id");
						var controlsToMove = column.element.find("div.ExpandableDiv");

						if (me._targetColumn === null) {
							me._targetColumn = column;
						};

						controlsToMove.each(function () {
							control = $(this);

							if (control.children().length > 0) {
								controlColumn = control.closest("td");
								me._targetColumn.element.append(control);
								me._ToggleBrowseFields(control);
							};
						});

						if (controlColumn != null) {
							controlColumn.hide();
							controlColumn = null;
						};
					};
				};

				ExpandableMatrixRowDef.prototype._ToggleBrowseFields = function (browseFieldsContainer) {
					var me = this;
					var browseFieldId = "";
					var browseField;

					browseFieldsContainer.find("label").show();

					var browseFields = browseFieldsContainer.find("[id$=_ref]");
					browseFields.each(function () {
						browseFieldId = this.id.replace("_ref", "");
						browseField = new SysBrowser(browseFieldId);
						browseField.input.Show();
						browseField.reference.Show();
						browseField.button.Show();

						if (me._readOnly) {
							browseField.SetReadOnly(true);
						}
					});
				};

				ExpandableMatrixRowDef.prototype._MoveColumnWhenCollapse = function () {
					var column;
					var columnId;
					var matrixId = new SysElement(this.row).Parent("table").Attribute("id");

					for (controlCount = 0; controlCount < this.columnControls.length; controlCount++) {
						columnId = this.columnControls[controlCount].columnId;
						column = this.row.element.find("td[id=" + columnId + "]");

						var fieldIds = this.columnControls[controlCount].fieldId;

						for (fieldCount = 0; fieldCount < fieldIds.length; fieldCount++) {
							var fieldId = fieldIds[fieldCount].replace(matrixId + "_r0", this.rowId);
							var control = new SysElement(fieldId).element;

							control.find("label").hide();
							control.find("input[id*=alt]").hide();
							control.find("button[id^=p" + this.rowId + "]").hide();

							if (this._isExpand && control.closest("td") != column) {
								column.append(control);
								column.attr("colspan", 1);
								column.show();
							};
						}
					}
					this.columnControls = [];
				};

				ExpandableMatrixRowDef.prototype._InitializeDeletebutton = function () {
					this._deleteButton = new SysElement(this.rowId + "_Delete");
					this._deleteButton.Attribute("tabindex", "-1");
				};

				/// <summary>Favorite button image will be hidden by default,
				/// it will only be shown if favorite key exist.</summary/>
				ExpandableMatrixRowDef.prototype._InitializeFavoriteButton = function () {
					this._favoriteButtonImage = new SysElement(this.rowId + "_Favorite_image");

					if (!this._favoriteButtonImage.empty) {
						this._favoriteButtonImage.Hide();
					}
				};

				ExpandableMatrixRowDef.prototype._HideDeletedRow = function () {
					if (Boolean(SysGet(this.rowId + "_Deleted"))) {
						this.row.Hide();
					};
				};

				ExpandableMatrixRowDef._initialized = true;
			}

			this._Init(rowId);
		}

		return ExpandableMatrixRowDef;
	})();

	window.ExpandableMatrixRow = ExpandableMatrixRow;
})();
;
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../base/MicrosoftAjax.debug.js" />
/// <reference path="../SysControls/SysElement.js" />

//Clipboard copy and paste
function SysGridCopyClipboard(gridID) {
	SysCopyClipboard(gridID, 1, 3, 2, 0);
}

function SysMatrixCopyClipboard(matrixID, useTotalColumn, oneFooterRow) {
	var footerColCount = 1;
	var footerRowCount = 2;

	if (!useTotalColumn) {
		footerColCount = 0;
	}

	if (oneFooterRow) {
		footerRowCount = 1
	}

	SysCopyClipboard(matrixID, 2, footerRowCount, 1, footerColCount);
}

function SysCopyClipboard(ID, headerRowCount, footerRowCount, headerColCount, footerColCount) {
	var matrix = SysGetElement(ID);
	var matrixTextData = "";

	for (row = headerRowCount; row < matrix.rows.length - footerRowCount; row++) {
		var tr = matrix.rows[row];

		if (jQuery(tr).is(":not(:hidden)")) {
			for (cell = headerColCount; cell < tr.cells.length - footerColCount; cell++) {
				var td = tr.cells[cell];

				if (jQuery(td).is(":visible")) {
					if (td != null) {
						var el = td.firstChild;
						
					}
					if (el != null) {
						var bRef = false;

						if (el.type == "hidden") {
							bRef = true;
							el = el.nextSibling;
						}
						if (el != null) {
							var v = el.value;

							if (v == null) {
								v = SysGetInnerText(el);
								if (v != null) {
									if (bRef) {
										matrixTextData += v.substring(0, v.indexOf(" - "));
									}
									else {
										matrixTextData += v;
									}
								}
							}
							else {
								matrixTextData += v;
							}
							matrixTextData += "\t";
						}
					}
				}
			}
			matrixTextData += "\n";
		}
	}
	document.addEventListener('copy', function (e) {
		e.clipboardData.setData('text', matrixTextData);
		e.preventDefault();
	});
	document.execCommand('copy');
	sessionStorage.setItem("matrixTextData", matrixTextData);
}

function SysGridPasteClipboard(gridID) {
	SysPasteClipboard(gridID, 1, 3, 2, 0);
}

function SysMatrixPasteClipboard(matrixID, useTotalColumn, oneFooterRow) {
	var footerColCount = 1;
	var footerRowCount = 2;

	if (!useTotalColumn) {
		footerColCount = 0;
	}

	if (oneFooterRow) {
		footerRowCount = 1
	}

	SysPasteClipboard(matrixID, 2, footerRowCount, 1, footerColCount);
}

function SysPasteClipboard(ID, headerRowCount, footerRowCount, headerColCount, footerColCount) {
	var clipboardMatrixData = sessionStorage.getItem("matrixTextData");

	if (clipboardMatrixData === '' || clipboardMatrixData === null) WarningDialog.Show(3749, "Warning", 658545, "Your clipboard is empty", 658546, "Please click the 'Copy' button first."); 
	var lines = clipboardMatrixData.split("\n");
	var lineCount = lines.length;

	if (lines[lineCount - 1] == "") lineCount--;
	var g = SysGetElement(ID);
	var row = -1;

	for (var j = 0; j < lineCount; j++) {
		var tr;

		do {
			row += 1;
			if (g.rows.length - footerRowCount <= row + headerRowCount) {
				var elAddNew = SysGetElement(ID + "_addnew");

				if (elAddNew != null) {
					SysGridAddRows(elAddNew, ID, false, false, false);
				}
			}
			tr = null;
			if (g.rows.length - footerRowCount > row + headerRowCount) {
				tr = g.rows[row + headerRowCount];
			}
		} while (jQuery(tr).is(":hidden"))

		if (tr != null) {
			var values = lines[j].split("\t");
			var col = -1;

			for (var i = 0; i < values.length; i++) {
				var td;

				do {
					col += 1;
					td = null;
					if (tr.cells.length - footerColCount > col + headerColCount) {
						td = tr.cells[col + headerColCount];
					}
				} while (td != null && jQuery(td).is(":hidden"))

				if (td != null) {
					var el = td.firstChild;

					if (el != null) {
						while (el != null && el.type == "hidden") {
							el = el.nextSibling;
						}
						if (!el.disabled && !el.readOnly && el.type != "button" && el.value != values[i]) {
							try {
								el.value = values[i];
								$(el).change();
							}
							catch (ex) { }
						}
					}
				}
			}
		}
	}
	SysGridCheckPaging(ID);
}

//	Grid functions
var sysIsGridDirty = false;
function SysIsGridDirty() {
	return sysIsGridDirty;
}

function SysGridKeyDown(e) {
	/// <summary>Handle the left and right arrow keys inside a keydown event.</summary>
	/// <param name="e" type="DOMEvent"></param>
	/// <returns type="undefined">undefined</returns>
	var el = new SysElement(SysSrcElement(e));
	var sel = new SysSelection(el);
	if (SysIsCancelBubble(e)) {
		return;
	}
	var hdl = new SysHandleKey(e);
	if (hdl.HandleEnter(UserAgent.IsSafari() && hdl.IsTabKey())) {
		// If this is the last element and a browse field, trigger the onchange to make sure the
		// data is retrieve before add a new row. Firefox didn't show this problem and is excluded from the fix.
		if (!UserAgent.IsFF() && el.Is("input[id$='_alt']:visible:enabled")) {
			var $el = el.element;
			var $elLast = $el.closest("tr").find("input:visible:enabled:last");
			if ($el[0] == $elLast[0]) {
				el.element.change();
			}
		}
		return;
	}
	else if (hdl.IsLeftKey()) {
		var t = sel.GetSelection();
		var pos = sel.GetCaretPosition();
		if (t.length > 0) {
			// Other browsers set the cursor just before any selected test.
			if (Sys.Browser.agent === Sys.Browser.Firefox) {
				sel.SetCaretPosition(pos + 1);
			}
			return;
		}
		if (pos == 0) {
			var i;
			var f = document.forms[0];
			var el2 = new SysElement();
			for (i = 0; i < f.elements.length; i++) {
				if (f.elements[i] == el.GetDomElement()) {
					el2.Init(f.elements[i]);
					break;
				}
			}
			if (!el2.empty && i > 0) {
				i--;
				el2.Init(f.elements[i]);
				while (!el2.empty) {
					if (el2.Attribute("tabIndex") >= 0 && !el2.IsDisabled() && el2.IsVisible()) {
						// On timer, because without it only works correctly on IE.
						dummyEl = el2;
						window.setTimeout(function() {
							dummyEl.Focus();
							new SysElement(dummyEl).Select();
						}, 1);
						return;
					}
					i--;
					el2.Init(f.elements[i]);
				}
			}
		}
	}
	else if (hdl.IsRightKey()) {
		var pos2 = sel.GetCaretPosition();
		if (pos2 == el.Value().length) {
			hdl.HandleEnter(true);
		}
	}
}

function SysGridKeyUp(e) {
	/// <summary>Handle the up and down arrow keys inside a keyup event.</summary>
	/// <param name="e" type="DOMEvent"></param>
	/// <returns type="Boolean|undefined">Returns true if the key has been handled</returns>
	/// <remarks>TODO: the return behaviour is inconsistent.</remarks>
	if (SysIsCancelBubble(e)) {
		return;
	}
	var me = SysSrcElement(e);
	if (me.tagName == "SELECT") {
		return;
	}

	var hdl = new SysHandleKey(e);
	if (hdl.IsUpKey() || hdl.IsDownKey()) {
		var srcCell = (me.tagName == "TD") ? me : me.parentNode;
		var row = srcCell.parentNode;
		var tbody = row.parentNode;
		var table = tbody.parentNode;
		var rIndex = row.rowIndex;
		var move = (hdl.IsDownKey() ? 1 : -1);
		var bContinue = true;
		while (bContinue) {
			rIndex += move;
			if (rIndex < 0 || rIndex >= table.rows.length) {
				return;
			}
			var tr = table.rows[rIndex];
			var $tr = $(tr);
			var gridId = $tr.closest(".Grid").attr("id");
			if (SysGridIsAddNewRow(gridId, tr)) {
				SysGridDoAddNewRow(gridId);
				return;
			}
			bContinue = ($tr.is(":hidden") || $tr.hasClass("Header") || $tr.hasClass("Footer"));
			if (!bContinue) {
				var focusCell;
				if (IE_LEGACY) {
					// Hidden cells all get the cellIndex of the last visible cell + 1, ergo this workaround.
					var $cells = $(tr.cells).filter(":visible");
					focusCell = $cells[(srcCell.cellIndex < $cells.length) ? srcCell.cellIndex : $cells.length - 1];
				}
				else {
					focusCell = tr.cells[(srcCell.cellIndex < tr.cells.length) ? srcCell.cellIndex : tr.cells.length - 1];
				}
				if (focusCell != null) {
					var el = focusCell.firstChild;
					if (!SysGridFocusEl(el)) {
						bContinue = true;
					}
				}
			}
		}
		return true;
	}
	return false;
}

function SysGridTotal(grid, field) {
	return SysUnFormatNumber(SysGetInnerTextID(grid + "_vt_" + field));
}

function SysGridVisibleRows(grid) {
	return $("#" + grid + " tr[id^='" + grid + "_r']:not(:hidden)");
}

function SysGridIsAddNewRow(grid, tr) {
	var el = SysGetElement(grid + "_addnew");
	if (el != null) {
		var antr = el.parentNode.parentNode;
	}
	return (antr === tr);
}

function SysGridDoAddNewRow(grid) {
	var el = SysGetElement(grid + "_addnew");
	if (el != null) {
		el.focus();
	}
}

function SysGridTotalize(grid, field, rowId) {
	var st = SysGetElement(grid + "_SubTotal_" + field);
	var t = SysGetElement(grid + "_Total_" + field);
	var vt = SysGetElement(grid + "_vt_" + field);
	var digits = SysGet(grid + '_Digits_' + field);
	if (t == null || st == null || vt == null) {
		return;
	}
	var total = 0;
	var splitTotal = 0;
	var splitKey = SysGridRowSplitKey(grid, rowId);
	//Editable cells
	var els = $("#" + grid + " tr[id^='" + grid + "_r']:not(:hidden) input[id$='_" + field + "']").each(
		function() {
			var td = this.parentNode;
			var tr = td.parentNode;
			if (tr.style.display!="none") {
				var v = this.value;
				v = SysUnFormatNumber(v);
				if (!isNaN(v)) {
					total += v;
					if (splitKey === SysGridRowSplitKey(grid, tr.id)) {
						splitTotal += v;
					}
				}
			}
		});

	//Read-only cells	
	els = $("#" + grid + " tr[id^='" + grid + "_r']:not(:hidden) span[id$='_" + field + "']").each(
		function() {
			var td = this.parentNode;
			var tr = td.parentNode;
			if (tr.style.display!="none") {
				var v = SysGetInnerText(this);
				v = SysUnFormatNumber(v);
				if (!isNaN(v)) {
					total += v;
					if (splitKey === SysGridRowSplitKey(grid, tr.id)) {
						splitTotal += v;
					}
				}
			}
		});

	SysSetInnerText(vt, SysFormatNumber(total - SysUnFormatNumber(st.value) + SysUnFormatNumber(t.value), digits));

	if (splitKey) {
		SysSetInnerTextID(grid + "_st_" + SysGridGuid2Id(splitKey) + '_' + field, SysFormatNumber(splitTotal, digits));
	}
}

function SysGridTotalizeAll(gridId, rowId) {
	var els = $("#" + gridId + " tr[class*='Footer'] td[id^='" + gridId + "_vt_']").each(
		function() {
			var field = this.id.substring(gridId.length + 4);
			SysGridTotalize(gridId, field, rowId);
		}
	)
}

function SysGridCheckPaging(gridID) {
	var grid = SysGetElement(gridID);
	var pagesize = SysGet(gridID + "_PageSize");
	if (pagesize != null && pagesize != -1 && grid.rows.length - 4 > pagesize) {
		//Count the real number of rows, so excluding invisible rows
		var rowCount = 0;
		var trs = SysGridVisibleRows(gridID);
		var rows = trs.length;
		for (var i=0;i<rows;i++) {
			var rowid = SysGridRowID(trs[i]);
			if (!SysGridRowIsDeleted(rowid)) {
				rowCount+=1;
			}
		}

		if (rowCount > pagesize) {
			//Rowcount is really greater than the pagesize
			SysGridPaging(gridID + "_PageCtl", "-1");
		}
	}
}

function SysGridAddRows2(tr, gridID, checkpagesize, setfocus, split, clientCopy, insert, isTotalizeColumnsRequired = true) {
	var table = tr.closest("table");
	var i = SysElement.attr(tr, "rowIndex");

	var el = new SysElement(gridID + "_LastID");
	var lastID = new Number(el.Value()) + 1;
	el.Value(lastID);
	el = new SysElement(gridID + "_Rows");
	var rowCount = new Number(el.Value()) + 1;
	el.Value(rowCount);

	var newRowNumber = 0; //visible rownumber of new row
	var origrowid = SysGridRowID(tr[0]);
	if (origrowid.length > 0 && split === 0) {
		//Row inserted: set new row number
		newRowNumber = parseInt(new Number(SysGridGetRowNumber(tr[0])));
	}

	var trNew = SysGridAllRows(table, gridID, lastID, tr, rowCount, checkpagesize, split, clientCopy, insert);

	var rowids = SysGridGetRowIds(gridID);

	//Increase rownumbers of this row and all subsequent rows
	if (newRowNumber > 0) {
		SysGridSetRowNumber(trNew[0], newRowNumber);
		var splitKey;
		for (var k = Array.indexOf(rowids, trNew[0].id) + 1; k < rowids.length; k++) {
			var row = SysGetElement(rowids[k]);
			if (!(SysGridSplitKeyId(gridID) && splitKey === SysGridRowSplitKey(gridID, row.id))) {
				newRowNumber += 1;
				splitKey = SysGridRowSplitKey(gridID, row.id);
			}
			SysGridSetRowNumber(row, newRowNumber);
		}
	}

	sysIsGridDirty = true;

	if (setfocus == undefined) {
		setfocus = true;
	}
	if (setfocus) {
		SysGridRowFocus(trNew[0]);
	}

	if (isTotalizeColumnsRequired) {
		SysGridTotalizeAll(gridID, trNew[0].id);
	}
	if ($("#" + gridID).hasClass("JColResizer")) {
		SysSetColumnMinimumWidth(gridID);
	}
}

function SysGridGetRowNumber(tr) {
	return $(tr.cells[0]).find('div').html();
}

function SysGridSetRowNumber(tr, rownr) {
	$(tr.cells[0]).find('div').html(rownr);
}

function SysGridSetRowNumbers(trs, rownr) {
	trs.each(function () {
		SysGridSetRowNumber(this, rownr);
	});
}

function SysGridAddRows(me, gridID, checkpagesize, setfocus, clientCopy, shouldBlur, isTotalizeColumnsRequired = true) {
	if (me == null) {
		return;
	}
	if (shouldBlur === true && window.chrome && me instanceof HTMLElement) {
		me.blur();
	}
	sysIsGridDirty = true;

	var params = {};
	if (GridValidationFunctions instanceof Array && GridValidationFunctions.length > 0) {
		var funcs = [];
		for (var i = 0; i < GridValidationFunctions.length; i++) {
			funcs.push(GridValidationFunctions[i]);
		}
		FinExecuteFunctions(funcs, params, AddRows);
	}
	else {
		AddRows();
	}

	function AddRows() {
		SysGridAddRows2($(me).closest("tr"), gridID, checkpagesize, setfocus, 0, clientCopy, false, isTotalizeColumnsRequired);
	}
}

function SysGridRowFocus(tr) {
	for (var c = 0; c < tr.cells.length; c++) {
		var td = tr.cells[c];
		var el = td.firstChild;
		if (SysGridFocusEl(el)) {
			return;
		}
	}
}

function SysGridFocusEl(el) {
	var addNew = $(el).closest(".Grid").attr("id") + "_addnew";

	if (window._ENABLE_EDS_ && $(el)?.is("div")) {
		el = el.querySelector('input[type="text"]');
	}
	
	while(el!=null) {
		$el = $(el);
		if (el.tabIndex >= 0 && ($el.is(":input:visible:enabled") || el.id == addNew)) {
			el.focus();
			$el.select();
			return true;
		}
		el = el.nextSibling;
	}
	return false;
}

function SysGridAddCell(row, html, rowSpan, colSpan, noWrap, txtAlign, rowID, copy, rows, hidden, className, rowCount, tdID) {
	var c = SysInsertCell(row);
	if (rowCount != null) {
		html = html.replace(/_r0wNR/g, rowCount);
	}
	var re = /_rr0w/g;
	html = html.replace(re, "_r" + rowID);

	c.html(html);

	if (tdID != null) {
		tdID = tdID.replace(re, "r" + rowID);
		c.attr("id", tdID);
	}

	c.attr("colspan", colSpan);
	c.attr("rowspan", rowSpan);
	if (noWrap === 1) {
		c.css("white-space", "nowrap");
	}
	switch (txtAlign) {
		case 0:
			if (IE_LEGACY) {
				// in old IE versions the value "inherit" is not supported for style property "text-align"; 
				// set it to default value of this property, i.e. "left"
				c.css("text-align", "left");
			}
			else {
				c.css("text-align", "inherit");
			}
			break;
		case 1:
			c.css("text-align", "left");
			break;
		case 2:
			c.css("text-align", "center");
			break;
		case 3:
			c.css("text-align", "right");
			break;
		case 4:
			c.css("text-align", "justify");
			break;
		default:
			// do nothing
	}
	if (className != null) {
		c.addClass(className);
	}

	SysGridCopy(c[0], rows, copy);

	if (hidden == "1") {
		c.css('display', 'none');
	}
}

function SysGridAddBrowserControl(controlString, rowID) {
	var re = /_rr0w/g;
	new Function(controlString.replace(re, "_r" + rowID))();
}

function SysGridCopy(cT, rows, copy) {	
	var rowidF = SysGridRowID(SysGridGetCopyRow(cT, rows));
	var rowidT = SysGridRowID(cT);
	var childNodes = cT.childNodes;
	if (window._ENABLE_EDS_) {
		childNodes = $(cT).find("INPUT,SELECT,A,SPAN,DIV");
	}
	for (var i = 0; i < childNodes.length; i++) {
		var iT = childNodes[i];
		if (iT.id != null) {
			var id = iT.id.replace(rowidT, rowidF);
			var iF = SysGetElement(id);

			if (iF != null && (iT.tagName == "INPUT" || iT.tagName == "SELECT" || iT.tagName == "A" ||  iT.tagName == "DIV" || iT.tagName == "SPAN")) {
				if (iF.value != null && copy == "1") {
					iT.value = iF.value;
					iT.className = iF.className;
				}
				if (iT.tagName == "A") {
					if (iF.href != null && copy == "1") {
						var text = $("#" + iF.id).text();
						iT.href = iF.href;
						iT.text = text
					}
				}


				if (iT.tagName == "SPAN" && iF.innerText && copy == "1"){				
					var parentDiv = $(iT).closest(".eds-browsefield")[0];				
					if (parentDiv != null && parentDiv.tagName == "DIV") {
						parentDiv.classList.remove('bf-empty');
						iT.innerText = iF.innerText;
					}
				}

				iT.disabled = iF.disabled;
				iT.readOnly = iF.readOnly;
			}
		}
	}
}

function SysGridGetCopyRow(cT, rows) {
	var t = cT.parentNode.parentNode.parentNode;
	var rF = t.rows[cT.parentNode.rowIndex - rows];
	while (SysGridRowIsDeleted(rF) || SysGridRowIsSplitTotal(t.id,rF.id)) {
		rows += 1;
		rF = t.rows[cT.parentNode.rowIndex - rows];
	}
	if (cT.parentNode.rowIndex - rows <= 0) {
		//No row above the new one to copy from: try to copy from a row below this one
		rows = 1;
		rF = t.rows[cT.parentNode.rowIndex + rows];
		while (SysGridRowIsDeleted(rF) || SysGridRowIsSplitTotal(t.id, rF.id)) {
			rows += 1;
			rF = t.rows[cT.parentNode.rowIndex + rows];
		}
	}
	return rF;
}

function SysGridShowDelete(rowid, show) {
	var el = SysGridGetElement(rowid, "delete");
	if (el != null) {
		if (show) {
			$(el).show();
		}
		else {
			$(el).hide();
		}
	}
}

function SysGridSetCellVerticalAlignment(gridid, alignment, className) {
	$("#" + gridid + " .GridRow>td").each(function () {
		$(this).css("vertical-align", alignment);
		$(this).addClass(className);
	});
}

function SysGridDelete(gridid, me, func) {
	sysIsGridDirty = true;
	var el = SysGetElement(me + "_Deleted");
	var tr = el.parentNode.parentNode;

	// only delete one single line
	$(tr).each(function () {
		SysSet(this.id + "_Deleted", "on");
		$(this).hide();
		for (var c = 0; c < this.cells.length; c++) {
			var td = this.cells[c];
			for (var i = 0; i < td.childNodes.length; i++) {
				el = td.childNodes[i];
				if (el != null && el.tabIndex != null) {
					if (el.tagName == "SELECT") {
						$(el).hide();
					}
					else {
					for (var j=0; j < el.childNodes.length; j++) {
							var el2 = el.childNodes[j];
							if (el2 != null && el2.style != null) {
								$(el2).hide();
							}
						}
					}
				}
			}
		}
		if (func != null) {
			var f = new Function("tr", "return " + func + "(tr)");
			f(this);
		}
	});

	if (SysGridAllowSplit(gridid)) {
		var rows = SysGridRows(gridid, tr, false, false);
		if (rows.length > 0) {
			SysGridSetRowSpan(gridid, rows.eq(0), false);
		}
		if (rows.length == 1) {
			var totalRow = $('#' + SysGridRowSplitTotalRowId(gridid, tr.id));
			totalRow.remove();
		}
	}

	SysGridTotalizeAll(gridid, tr.id);

	var table = tr.parentNode.parentNode;
	for (var r1=tr.rowIndex-1; r1>0; r1--)	{
		if (SysGridRowSetFocus(table.rows[r1])) {
			return;
		}
	}
	for (var r2=tr.rowIndex+1; r2<(table.rows.length-2); r2++)	{
		if (SysGridRowSetFocus(table.rows[r2])) {
			return;
		}
	}
	var fa = SysGetElement(gridid + "_addnew");
	if (fa != null) {
		for (var r3=1; r3<(table.rows.length-3); r3++)	{
			if (jQuery(table.rows[r3]).not(".GridRowAdd").is(":not(:hidden)")) {
				return;
			}
		}
		fa.focus();
	}
}

function SysGridRowSetFocus(tr) {
	if (jQuery(tr).is(":not(:hidden)")) {
		for (var c = 0; c < tr.cells.length; c++) {
			var td = tr.cells[c];
			var el = td.firstChild;
			if (el != null && el.id != null && el.id.indexOf("_addnew") == 0 && SysGridFocusEl(el)) {
				return true;
			}
		}
	}
}

function SysGridSetFocus(grd) {
	var t = SysGetElement(grd);
	if (t != null) {
		for (var r = 1; r < t.rows.length - 2; r++) {
			if (SysGridRowSetFocus(t.rows[r])) {
				return;
			}
		}
	}
}

var sysIgnoreFocus = null;
function SysGridOnFocus(el) {
	if (sysIgnoreFocus == el) {
		sysIgnoreFocus = null;
	}
	else if (el.parentNode.tagName == "TD" || el.parentNode.parentNode.parentNode.tagName == "TD") {
		try {
			SysSet("BCField", el.id);
			SysGridHighLight(el.parentNode, true);
		}
		catch (e) { }

		SysSelect(el);

		if (document.activeElement !== el) {
			el.focus();
		}
	}
}

function SysGridOnBlur(el) {
	if (el.parentNode.tagName == "TD") {
		try {
			SysGridHighLight(el.parentNode, false);
		}
		catch (e) { }
	}
	SysChangeOnBlur(el);
}

function SysGridHighLight(td, bHighLight) {
	if (bHighLight) {
		$(td).addClass("Selected");
		var row = new SysElement(td).Parent(".GridRow");
		SysGridHighLightRow(row.id);
	}
	else {
		$(td).removeClass("Selected");
	}
}

function SysGridHighLightRow(rowId) {
	var grid = new SysElement(rowId).Parent(".Grid");
	var rowHighLight = SysGet(grid.id + "_HighLightFocusedRow");
	if (rowHighLight == 0) {
		return;
	}
	var rowIDs = SysGridGetRowIds(grid.id);
	var row;
	for (var i = 0; i < rowIDs.length; i++) {
		row = new SysElement(rowIDs[i]);
		row.RemoveClass("Selected");
	}
	row = new SysElement(rowId);
	row.AddClass("Selected");
}

function SysGridRowID(el) {
	var tr = el;
	while (tr != null && tr.tagName != "TR") {
		tr = tr.parentNode;
	}
	if (tr.tagName == "TR") {
		return tr.id;
	}
	return null;
}

function SysGridRowIsDeleted(rowid) {
	return (SysGridGet(rowid, "Deleted") == "on");
}

function SysGridGetElementID(rowid, ctl) {
	if (rowid.tagName == "TR") {
		rowid = SysGridRowID(rowid);
	}
	if (!isNaN(rowid)) {
		// assume default grid id of 'grd'
		rowid = "grd_r" + rowid;
	}
	return rowid + "_" + ctl;
}

function SysGridGetElement(rowid, ctl) {
	return SysGetElement(SysGridGetElementID(rowid, ctl));
}

function SysGridGet(rowid, ctl) {
	return new SysElement(SysGridGetElementID(rowid, ctl)).Value();
}

function SysGridGetKey(rowid) {
	return SysGridGet(rowid, "K");
}

function SysGridIsNewRow(rowid) {
	/// <summary>Returns true if this is a new, unsaved row, false otherwise</summary>
	/// <remarks>True if there is a state element with value != 1 or else if key value is empty</remarks>
	var rowStateElement = SysGridGetElement(rowid, "S");
	if (rowStateElement != null) {
		return (rowStateElement.value != 1);
	}
	return IsEmpty(SysGridGetKey(rowid));
}

function SysGridGetNumber(rowid, ctl) {
	return SysUnFormatNumber(SysGridGet(rowid, ctl));
}

function SysGridGetBoolean(rowid, ctl) {
	var vValue = SysGridGet(rowid, ctl);
	if (vValue == null || vValue === "") {
		vValue = 0;
	}
	if (vValue == true) {
		vValue = -1
	}
	return (vValue == -1);
}

function SysGridSet(rowid, ctl, value) {
	new SysElement(SysGridGetElementID(rowid, ctl)).Value(value);
}

function SysGridSetNumber(rowid, ctl, value, prec) {
	SysGridSet(rowid, ctl, SysFormatNumber(value, prec));
}

function SysGridSetQuantity(rowId, controlId, quantity) {
	SysGridSet(rowId, controlId, SysFormatQuantity(quantity));
}

function SysGridInsertRow(gridID, rowid) {
	var table = SysGetElement(gridID);
	var tr = SysGetElement(rowid);
	SysGridAddRows2($(tr), gridID, false, true, 0, true, true);
}

//Swap row with the first visible row above it
function SysGridSwapRowsUp(gridID, rowid) {
	var tr1 = SysGetElement(rowid);
	var j = tr1.rowIndex - 1;
	var table = SysGetElement(gridID);
	var tr2 = table.rows[j];

	while (j > 0 && SysGridRowIsDeleted(tr2)) {
		j -= SysGridRows(gridID, tr2, true, true).length;
		tr2 = table.rows[j];
	}
	if (j > 0) {
		SysGridSwapRows(gridID, tr2, tr1);
	}
}

//Swap row with the first visible row below it
function SysGridSwapRowsDown(gridID, rowid) {
	var lastID = new Number(SysGet(gridID + "_LastID")) + 1;
	var tr1 = SysGetElement(rowid);
	var j = tr1.rowIndex + SysGridRows(gridID, tr1, true, true).length;
	var table = SysGetElement(gridID);
	var tr2 = table.rows[j];

	while (tr2.id.length > 0 && SysGridRowIsDeleted(tr2)) {
		j += SysGridRows(gridID, tr2, true, true).length;
		tr2 = table.rows[j];
	}
	if (tr2.id.length > 0) {
		SysGridSwapRows(gridID, tr1, tr2);
	}
}

function SysGridSwapRows(gridID, tr1, tr2) {
	sysIsGridDirty = true;

	var table = SysGetElement(gridID);
	var jtr1 = SysGridRows(gridID, tr1, true, true);
	var jtr2 = SysGridRows(gridID, tr2, true, true);
	jtr1.eq(0).before(jtr2);

	//Swap row numbers
	var rowNumber1 = SysGridGetRowNumber(jtr1[0]);
	var rowNumber2 = SysGridGetRowNumber(jtr2[0]);
	SysGridSetRowNumbers(jtr1, rowNumber2);
	SysGridSetRowNumbers(jtr2, rowNumber1);

	SysGridResetRowIds(gridID);
}

//Returns rowid's as an array
function SysGridGetRowIds(gridID) {
	var rowIDs = SysGet(gridID + "_RowIDs");
	return rowIDs.split(",");
}

//Rebuild rowid's in rowids string
function SysGridResetRowIds(gridID) {
	var el = new SysElement(gridID + "_RowIDs");
	var table = SysGetElement(gridID);
	var newrowids = new Array;
	$(table).find('tr.GridRow').each(function () {
		newrowids.push(this.id);
	});
	el.Value(newrowids.join(','));
	return newrowids;
}

function SysGridNextRowNumber(table) {
	var lastRow = table.find('tr.GridRow:has(td):last');
	if (lastRow.length == 0) {
		return 1;
	}
	return parseInt(SysGridGetRowNumber(lastRow[0])) + 1;
}

function SysGridRows(gridId, tr, includeDeleted, includeTotal) {
	var splitKeyId = SysGridSplitKeyId(gridId);
	if (splitKeyId) {
		// find rows with same split key as current row
		var table = $('#' + gridId);
		var splitKey = SysGridRowSplitKey(gridId, tr.id);
		if (splitKey) {
			var expr = 'tr:has(input[value="' + splitKey + '"])';
			if (!includeDeleted) {
				expr = expr + ':not(:has(input[id^="' + gridId + '_r"][id$="_Deleted"][value="on"]))';
			}
			var rows = table.find(expr);
			if (rows.length > 1 && includeTotal) {
				rows = rows.add($('#' + SysGridRowSplitTotalRowId(gridId, tr.id)));
			}
			return rows;
		}
	}
	return $(tr);
}
function SysGridAllowSplit(gridId) {
	var splitkey = new SysElement(gridId + "_SplitKeyId");
	return !splitkey.IsEmpty();
}
function SysGridRowSplitKey(gridId, rowId) {
	if (rowId) {
		if (SysGridRowIsSplitTotal(gridId,rowId)) {
			// split total row
			return SysGridId2Guid(rowId.substring(gridId.length + 4));
		}
		return $('#' + rowId + '_SK_' + SysGridSplitKeyId(gridId)).val();
	}
	return null;
}
function SysGridRowSplitTotalRowId(gridId, rowId) {
	return gridId + '_st_' + SysGridGuid2Id(SysGridRowSplitKey(gridId, rowId));
}
function SysGridRowSplitTotalColumnId(gridId, rowId, colId) {
	return SysGridRowSplitTotalRowId(gridId, rowId) + '_' + colId;
}
function SysGridRowIsSplitTotal(gridId,rowId) {
	return (rowId.substring(gridId.length, gridId.length + 4) === '_st_');
}
function SysGridSplitRow(gridId, rowId, insert) {
	var splitKey = SysGridRowSplitKey(gridId, rowId);
	if (splitKey != null && splitKey !== '') {
		var tr = new SysElement(rowId);
		SysGridAddRows2(tr.element, gridId, false, true, (insert ? 2 : 1), true, true);
	}
}
function SysGridSplitKeyId(gridId) {
	return SysGet(gridId + "_SplitKeyId");
}
function SysGridCopyData(el) {
	var tr = el.parentNode.parentNode;
	var gridId = $(tr).closest('table')[0].id;
	var rows = SysGridRows(gridId, tr, true, false);
	rows.each(function () {
		if (this.id != tr.id) {
			$('#' + el.id.replace(tr.id,this.id)).val($(el).val());
		}
	});
}
function SysGridGuid2Id(guid) {
	return guid.replace('{', '').replace('}', '');
}
function SysGridId2Guid(id) {
	return '{' + id + '}';
};
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../base/MicrosoftAjax.debug.js" />
/// <reference path="../SysControls/SysElement.js" />

// Public interface

GridObject.prototype = {

	gridversion: "1.0.0",
	srcElement: null,
	grid: null,
	gridId: "",
	table: null,
	rowFirst: -1,
	rowLast: -1,
	row: null,
	rowId: null,
	rowNr: -1,
	pageSize: 0,
	rowPrefix: "",
	empty: true,
	
	Refresh: function() {
		/// <summary>Re-initialised the grid object, which currently should be done after every update of the grid 
		/// (addition / removal of rows) to keep it in sync.</summary>
		/// <returns type="Undefined">.</returns>
	},
	RowNumber: function(id) {
		/// <summary>Extracts the row number from the supplied id.</summary>
		/// <param name="id" type="String" >An id that conforms to the naming conventions expected by the grid.</param>
		/// <returns type="Number|NaN"></returns>
	},
	SetRow: function(row) {
		/// <summary>Set the current row (i.e. deter.</summary>
		/// <param name="row" type="Number|Any" optional="false">Accepts a row number in which case the corresponding
		/// row is searched for, or any type accepted by SysElement.</param>
		/// <returns type="Boolean">If the requested row is not part of the grid false is returned.</returns>
	},
	SetRowNext: function() {
		/// <summary>Set the next row as the current row.</summary>
		/// <returns type="Boolean">Returns false if the next row can not be set or there is no next row.</returns>
	},
	SetRowPrevious: function() {
		/// <summary>Set the previous row as the current row.</summary>
		/// <returns type="Boolean">Returns false if the previous row can not be set or there is no previous row.</returns>
	}
};

// Mimic public enumerators

// Mimic statics

// Constructor

function GridObject(el) {
	/// <summary>Object that stores information on the currently used grid control.</summary>
	/// <param name="el" type="DOMElement">A DOM element upon which the grid is determined, can be any child element
	/// of the grid.</param>
	/// <field name="gridversion", type="String">Version nr of the object.</field>
	/// <field name="srcElement", type="DOMElement">Contains the element upon which the actual grid was determined.
	/// </field>
	/// <field name="gridId" type="String">Holds the id of the grid.</field>
	/// <field name="table" type="jQuery">The actual table element that makes up the grid.</field>
	/// <field name="rowFirst" type="Number">The row number of the first row, which may be needed in case of paging.
	/// </field>
	/// <field name="rowLast" type="Number">The row number of the last row, which may be needed in case of paging.
	/// </field>
	/// <field name="row" type="jQuery">The current row, use SetRow to set a new active row.</field>
	/// <field name="rowId" type="String">The current row's id.</field>
	/// <field name="rowNr" type="Number">The current row's row number.</field>
	/// <field name="pageSize" type="Number">In case of paging, this determines the page size for the grid.</field>
	/// <field name="rowPrefix" type="String">The part of the any row's id excluding its row number part.</field>
	/// <field name="empty" type="Boolean">Determines if the instance does indeed wrap a grid.</field>

	if (GridObject._initialized === undefined) {

		var _rows;

		GridObject.prototype.Refresh = function() {
			this._Init(this.table);
		};
		
		GridObject.prototype.RowNumber = function(id) {
			return Number(id.substr(this.rowPrefix.length));
		};

		GridObject.prototype.SetRow = function(row) {
			var curRow;
			if (typeof row === "number") {
				curRow = _rows.filter("#" + this.rowPrefix + row);
			}
			else {
				var el = new SysElement(row);
				if (el.element.is(":not(tr)")) {
					curRow = el.element.closest("tr", _rows);
				}
				else if (_rows.index(el.element) !== -1) {
					curRow = el.element;
				}
			}

			if (curRow !== undefined && curRow.length > 0) {
				this._SetRow(curRow);
				return true;
			}
			else {
				this.rowId = "";
				this.rowNr = -1;
				return false;
			}

		};

		GridObject.prototype.SetRowNext = function() {
			if (!this.empty) {
				var idx = _rows.index(this.row);
				if (idx !== -1) {
					if (idx < _rows.length - 1) {
						this._SetRow($(_rows[idx + 1]));
						return true;
					}
				}
			}
			return false;
		};

		GridObject.prototype.SetRowPrevious = function() {
			if (!this.empty) {
				var idx = _rows.index(this.row);
				if (idx !== -1) {
					if (idx > 0) {
						this._SetRow($(_rows[idx - 1]));
						return true;
					}
				}
			}
			return false;
		};

		// Local interface
		GridObject.prototype._Init = function(el) {
			var _el = new SysElement(el);

			if (_el.HasClass("Grid")) {
				this.grid = _el;
				_el = null;
			}
			else {
				this.grid = new SysElement(_el.element.closest(".Grid"));
			}
			this.table = this.grid.element;
			
			if (this.table.length > 0) {
				this.srcElement = this.grid.GetDomElement();
				this.gridId = this.table.attr("id");
				this.rowPrefix = this.gridId + "_r";
				_rows = this.table.find("tr[id^=" + this.rowPrefix + "]");
				this.rowFirst = this.RowNumber(_rows[0].id, this.rowPrefix);
				this.rowLast = this.RowNumber(_rows[_rows.length - 1].id, this.rowPrefix);

				if (this.SetRow(_el || this.rowFirst)) {
					this.pageSize = new SysElement(this.gridId + "_PageSize").Value();
					this.empty = false;
				}
			}
		};

		GridObject.prototype._SetRow = function(row) {
			this.row = row;
			this.rowId = this.row.attr("id");
			this.rowNr = this.RowNumber(this.rowId, this.rowPrefix);
		};

		GridObject._initialized = true;
	};

	// GridObject can serve as a base class and must therefore be able to accept an parameterless constructor
	if (SysElement.IsNotNothing(el)) {
		this._Init(el);
	}
}

// private 'static':
;
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../base/MicrosoftAjax.debug.js" />
/// <reference path="../SysControls/SysElement.js" />
/// <reference path="../SysControls/SysEvents.js" />

// List View
// ---------
function SysCheckList(listID, columnId) {
	var f = new Function("return lvcCheckChecked" + listID + "_" + columnId + "()");
	return f();
}

function SysListViewCBXChecked(id) {
	var inps = document.getElementsByName(id);
	if (inps == null || inps.length == 0) {
		return false;
	}
	for (i = 0; i < inps.length; i++) {
		var e = inps[i];
		if (e.type == "checkbox" && e.checked) {
			return true;
		}
	}
	return false;
}

function SysListKD(e) {
	try {
		var hdl = new SysHandleKey(e);
		if (hdl.IsAltKey()) {
			if (hdl.GetKey() === SysHandleKey.Key.pageDown) {
				ListNext();
			}
			else if (hdl.GetKey() === SysHandleKey.Key.pageUp) {
				ListPrev();
			}
		}
	}
	catch (e) { }
}
function SysListAddKD(event) {
	SysAttachEvent(document, "onkeydown", function () { SysListKD(event); });
}
function SysListSort(ctl, column, asc) {
	SysSet(ctl + "_sortcolumn", column);
	SysSet(ctl + "_sortorder", asc ? 1 : 0);

	if(window._ENABLE_EDS_) {
		event.stopPropagation();
	}
}
function SysListToggle(cb, prefix) {
	var checkboxHeader = new SysElement(cb);
	var re = new RegExp(SysReplaceRegEx(prefix), "i");
	$(":checkbox:enabled" + (checkboxHeader.IsChecked() ? ":not(:checked)" : ":checked")).each(function () {
		var checkboxLine = new SysElement(this);
		if (checkboxLine.Attribute("name").search(re) >= 0) {
			if (checkboxLine.IsChecked() != checkboxHeader.IsChecked()) {
				if (UserAgent.IsIE() && UserAgent.version <= 8) {
					$(this).click();
				}
				else {
					checkboxLine.FireEvent("click");
				}
			}
		}
	});
}

function SysListSetHeight(ev, ctlId) {
	/// <summary>This is completely obsolete: any resizing is done by the SysListView object, which in turn is 
	/// automatically created by the listview control.</summary>

	if (SysElement.IsNotNothing(ev)) {
		e = SysEvent(ev);
		e.stopPropagation();
		e.preventDefault();
	}

	var obj = eval("lv" + ctlId);
	if (obj instanceof SysListView) {
		obj.Resize();
	}
}

var sysListPrevRow;
var sysListRuler = true;
var sysUsesOnHoverColumnClass = ".onHoverContainer";
function SysListTable(tr) {
	while (tr != null && tr.tagName != "TABLE") {
		tr = tr.parentNode;
	}
	return tr;
}
function SysListRow(ev) {
	var e = new SysHandleEvent(ev);
	//new SysElement().
	var el = SysSrcElement(ev);
	while (el != null && el.tagName != "TR") {
		el = el.parentNode;
	}
	return el;
}
function SysListOver(ev) {
	if (!sysListRuler) {
		return;
	}
	var el = SysListRow(ev);

	if ($(el).find(sysUsesOnHoverColumnClass) != null && sysListPrevRow != el) {
		SysMenuHide();
	}

	SysListUnSelect(el);
	SysListSelect(el);
}
function SysFindStyleSheetRule(txt) {
	if (document.styleSheets.length == 0) {
		return null;
	}
	var r = (document.styleSheets[0].rules || document.styleSheets[0].cssRules);
	for (var i = 0; i < r.length; i++) {
		if (String(r[i].selectorText).toLowerCase() == txt.toLowerCase()) {
			return r[i];
		}
	}
	return null;
}

var sysListRule = null;
var sysListPrevColor;
function SysListSelect(el) {
	if (el == null) {
		return;
	}
	var e = $(el).filter(".DataDark,.DataLight");
	if (e.length > 0) {
		if (sysListRule === null) {
			sysListRule = SysFindStyleSheetRule("TABLE.ListView TR.Selected");
		}
		sysListPrevRow = el;
		// Should be 'e.css("backgroundColor");', but could not find a location where it actually mattered. The line 
		// below sets sysListPrevColor to an empty string.
		// TODO: clean this up. styling should be moved to CSS file
		sysListPrevColor = el.style.backgroundColor;
		if (sysListRule !== null && sysListRule.style) {
			var clr = sysListRule.style.backgroundColor;
			SysListSetColor(el, clr);

			$(e).find(sysUsesOnHoverColumnClass).addClass('show');
		}
	}
}

function SysListSetColor(el, clr) {
	var e = $(el);
	SysListSetColorEx(e, clr);
	if (el.className == "DataDark" || el.className == "DataLight") {
		SysListSetColorPrev(e, clr, el.className);
		SysListSetColorNext(e, clr, el.className);
	}
}

function SysListSetColorPrev(e, clr, className) {
	e = e.prev("." + className);
	if (e.length) {
		SysListSetColorEx(e, clr);
		SysListSetColorPrev(e, clr, className);
	}
}
function SysListSetColorNext(e, clr, className) {
	e = e.next("." + className);
	if (e.length) {
		SysListSetColorEx(e, clr);
		SysListSetColorNext(e, clr, className);
	}
}
function SysListSetColorEx(e, clr) {
	e.css("backgroundColor", clr);
	e.children('td').css("backgroundColor", clr);
}
function SysListOut(ev) {
	if (!sysListRuler) {
		return;
	}
	var el = SysListRow(ev);

	if ($(el).find(sysUsesOnHoverColumnClass) != null) {
		SysListUnSelect(el);
		SysListSelect(el);
	}	
}
function SysListUnSelect(el) {
	if (el == null) {
		return;
	}
	if (sysListPrevRow != null) {
		SysListSetColor(sysListPrevRow, sysListPrevColor);
		$(sysListPrevRow).find(sysUsesOnHoverColumnClass).removeClass('show');
	}
	sysListPrevRow = null;
}
function SysListDeActivate(e) {
	SysListUnSelect(SysListRow(e));
}
function SysListActivate(e) {
	var tr = SysListRow(e);
	SysListUnSelect(tr);
	SysListSelect(tr);
	var el = SysSrcElement(e);
	if (el != null && el.tagName == "TD") {
		try {
			el.parentNode.getElementsByTagName("A")[0].focus();
		}
		catch (ex) { }
	}
}
function SysListLoad() {
}

function SysListKeyUp(e) {
	if (SysIsCancelBubble(e)) {
		return;
	}
	var me = SysSrcElement(e);
	if (me.tagName == "SELECT") {
		return;
	}

	var hdl = new SysHandleKey(e);
	if (hdl.IsUpKey() || hdl.IsDownKey()) {
		var td = (me.tagName == "TD") ? me : me.parentNode;
		var row = td.parentNode;
		var tbody = row.parentNode;
		var table = tbody.parentNode;
		var rIndex = row.rowIndex;
		// up
		var bContinue = true;
		if (hdl.IsUpKey()) {
			while (bContinue) {
				rIndex--;
				if (rIndex < 0) {
					return;
				}
				bContinue = (jQuery(table.rows[rIndex]).is(":hidden"));
			}
		}
		// down
		else if (hdl.IsDownKey()) {
			bContinue = true;
			while (bContinue) {
				rIndex++;
				if (rIndex >= table.rows.length) {
					return;
				}
				bContinue = (jQuery(table.rows[rIndex]).is(":hidden"));
			}
		}
		row = table.rows[rIndex];
		td = row.cells[(td.cellIndex < row.cells.length) ? td.cellIndex : row.cells.length - 1];
		if (td != null) {
			var el = td.firstChild;
			if ((row.className == "" || row.className.substr(0, 4) == "Data" || row.className == "Selected") && !SysGridFocusEl(el)) {
				td.setActive();
			}
		}
		return true;
	}
	return false;
}
function SysListKeyDown(e) {
	try {
		if (SysListKeyUp(e)) {
			SysCancelBubble(e);
		}
	}
	catch (ex) { }
}
var sysListCol = null;
var sysListColFirst = true;
function SysListColMove(e) {
	e = SysEvent(e);
	var el = $(SysSrcElement(e));
	if (!el.is("th")) {
		el = el.parent("th");
	}
	if (el.length > 0) {
		if (e.offsetX < (el.width() - 5)) {
			el.css("cursor", "auto");
		}
		else {
			el.css("cursor", "col-resize");
			if (e.button == 1) {
				SysListColSetWidths(el);
				sysListCol = el;
				el[0].dragDrop();
			}
		}
	}
}
function SysListColDragStart(e) {
	if (e && e.dataTransfer) {
		e.dataTransfer.effectAllowed = "move";
	}
}
function SysListColDragEnterOver(e) {
	SysStopPropagation(e);
	if (e.dataTransfer) {
		e.dataTransfer.dropEffect = "move";
	}
	SysListColResize(e);
}

function SysListColDrop(e) {
	SysListColResize(e);
}

function SysListColResize(e) {
	e = SysEvent(e);
	if (e.offsetX <= 0) {
		return;
	}

	var el = $(SysSrcElement(e));
	if (!el.is("th")) {
		el = el.parent("th");
	}

	if (sysListCol.length > 0 && el.length > 0) {
		if (sysListCol[0] == el.prev()[0]) {
			SysListColSet(sysListCol, e.offsetX + sysListCol.width());
		}
		else if (sysListCol[0] == el[0]) {
			SysListColSet(sysListCol, e.offsetX);
		}
	}
}

function SysListColSet(c, w) {
	var col = $("#" + "col" + c.attr("id"));
	if (col) {
		var d = col.width() - w;
		col.width(w);
		col = col.next();
		if (col) {
			col.width(col.width() + d);
		}
	}
}

function SysListColDragEnd(e) {
	SysListColGetWidths(sysListCol);
	sysListCol = null;
}

function SysListColSetWidths(c) {
	if (!sysListColFirst) {
		return;
	}
	sysListColFirst = false;
	while (c && c.tagName != "TABLE") {
		c = c.parentNode;
	}
	if (c) {
		var cols = c.getElementsByTagName("COL");
		for (var i = 0; i < cols.length; i++) {
			cols[i].width = cols[i].offsetWidth;
		}
	}
}

function SysListColGetWidths(c) {
	while (c && c.tagName != "TABLE") {
		c = c.parentNode;
	}
	if (c) {
		var w = "";
		var cols = c.getElementsByTagName("COL");
		for (var i = 0; i < cols.length; i++) {
			w += cols[i].id + "," + cols[i].width + ";";
		}
		SysSet(c.id.substring(0, c.id.length - 7) + "_ColumnSizes", w);
		var sn = c.getAttribute("SettingName");
		if (sn) {
			SysCallback("SysCallback.aspx?Action=6&SettingName=" + encodeURIComponent(sn)  // 6 = SysCBAction.ResizeColumn
				+ "&SettingValue=" + encodeURIComponent(w));
		}
	}
}

function SysListRowNr(e, list) {
	var tr = SysSrcElement(e);
	while (tr != null && tr.tagName != 'TR') {
		tr = tr.parentNode;
	}
	if (tr == null) {
		return null;
	}
	if (tr.id.indexOf(list + "_row_") == 0) {
		return tr.id.substring(list.length + 5);
	}
}

function SysListHeader(ctrlId,event) {
	if (event == 'onmouseover') {
		document.getElementById("imgSort_" + ctrlId).style.visibility = "visible";
	} else {
		document.getElementById("imgSort_" + ctrlId).style.visibility = "hidden";
	}
};
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />
/// <reference path="../base/MicrosoftAjax.debug.js" />
/// <reference path="../SysControls/SysElement.js" />

// Matrix functions
function matrixCell(me) {
	var field = me.id; 										//mtx_r1_c1_Amount
	this.matrix = field.substring(0, field.indexOf("_")); 	//mtx
	field = field.substring(this.matrix.length + 1);		//r1_c1_Amount
	this.row = field.substring(0, field.indexOf("_")); 		//r1
	field = field.substring(this.row.length + 1); 			//c1_Amount
	this.column = field.substring(0, field.indexOf("_")); 	//c1
	this.field = field.substring(this.column.length + 1); 	//Amount
}

function SysMatrixTotalize(me) {
	var cell = new matrixCell(me);

	var delta = SysMatrixCalcRowTotal(cell);
	if (delta == null) {
		SysMatrixCalcColumnTotal(cell);
	}
	else {
		SysMatrixSetColumnTotal(cell, delta);
		SysMatrixSetGrandTotal(cell, delta);
	}
}

function SysMatrixCalcRowTotal(cell) {
	/// <summary>Calculates row total</summary>
	/// <returns>Difference between new total and old total</returns>
	/// <remarks>Handles readonly cells correctly</remarks>
	var el = SysGetElement(cell.matrix + "_rt_" + cell.row);
	if (el == null) return null;
	var oldValue = SysUnFormatNumber(SysGetInnerText(el));
	var newValue = SysMatrixCalcTotal("#" + cell.matrix + " tr[id^='" + cell.matrix + "_r']:not(:hidden) input[id^='" + cell.matrix + "_" + cell.row + "_'][id$='_" + cell.field + "']");
	newValue += SysMatrixCalcTotalReadonly("#" + cell.matrix + " tr[id^='" + cell.matrix + "_r']:not(:hidden) span[id^='" + cell.matrix + "_" + cell.row + "_'][id$='_" + cell.field + "']");
	SysSetInnerText(el, SysFormatNumber(newValue));
	return newValue - oldValue;
}

function SysMatrixCalcColumnTotal(cell) {
	/// <summary>Calculates column total</summary>
	/// <returns>Difference between new total and old total</returns>
	/// <remarks>Handles readonly cells correctly</remarks>
	var el = SysGetElement(cell.matrix + "_ct_" + cell.column);
	if (el == null) return null;
	var oldValue = SysUnFormatNumber(SysGetInnerText(el));
	var newValue = SysMatrixCalcTotal("#" + cell.matrix + " tr[id^='" + cell.matrix + "_r']:not(:hidden) input[id$='_" + cell.column + "_" + cell.field + "']");
	newValue += SysMatrixCalcTotalReadonly("#" + cell.matrix + " tr[id^='" + cell.matrix + "_r']:not(:hidden) span[id$='_" + cell.column + "_" + cell.field + "']");
	SysSetInnerText(el, SysFormatNumber(newValue));
	return newValue - oldValue;
}

function SysMatrixCalcTotal(selector) {
	/// <summary>Calculates total of the selected editable input elements</summary>
	var value = 0;

	var els = $(selector).each(
		function () {
			var v = this.value;
			v = SysUnFormatNumber(v);
			if (!isNaN(v)) {
				value += v;
			}
		}
	)

	return value;
}

function SysMatrixCalcTotalReadonly(selector) {
	/// <summary>Calculates total of the selected spans</summary>
	var value = 0;

	els = $(selector).each(
		function () {
			var v = SysGetInnerText(this);
			v = SysUnFormatNumber(v);
			if (!isNaN(v)) {
				value += v;
			}
		}
	)

	return value;
}

function SysMatrixSetColumnTotal(cell, delta) {
	SysMatrixSetTotal(cell.matrix + "_ct_" + cell.column, delta);
}

function SysMatrixSetGrandTotal(cell, delta) {
	SysMatrixSetTotal(cell.matrix + "_gt", delta);
}

function SysMatrixSetTotal(totalCellId, delta) {
	var el = SysGetElement(totalCellId);
	if (el == null) return;
	var oldValue = SysUnFormatNumber(SysGetInnerText(el));
	var value = oldValue + delta;
	SysSetInnerText(el, SysFormatNumber(value));
}

function SysMatrixAddCell(row, html, rowSpan, colSpan, noWrap, txtAlign, rowID, copy, rows, hidden, className, rowCount, tdID, title) {
	var c = SysInsertCell(row);
	if (rowCount != null) {
		html = html.replace(/_r0wNR/g, rowCount);
	}
	var re = /_rr0w/g;
	html = html.replace(re, "_r" + rowID);

	c.html(html);

	if (tdID != null && tdID != "") {
		tdID = tdID.replace(re, "_r" + rowID);
		c.attr("id", tdID);
	}

	c.attr("colspan", colSpan);
	c.attr("rowspan", rowSpan);
	if (noWrap === 1) {
		c.css("white-space", "nowrap");
	}
	switch (txtAlign) {
		case 0:
			c.css("text-align", "inherit");
			break;
		case 1:
			c.css("text-align", "left");
			break;
		case 2:
			c.css("text-align", "center");
			break;
		case 3:
			c.css("text-align", "right");
			break;
		case 4:
			c.css("text-align", "justify");
			break;
		default:
			// do nothing
	}
	if (className != null && className != "") {
		c.addClass(className);
	}

	SysGridCopy(c[0], rows, copy);

	if (hidden == "1") {
		c.hide();
	}

	if (title != null && title != "") {
		c.attr("title", title);
	}
}

function SysMatrixAddBrowserControl(controlString, rowID) {
	var re = /_rr0w/g;
	new Function(controlString.replace(re, "_r" + rowID))();
}

function SysMatrixAddRows(me, matrixId, checkpagesize, setfocus, shouldBlur) {
	if (me == null) {
		return;
	}
	if (shouldBlur === true && window.chrome && me instanceof HTMLElement) {
		me.blur();
	}
	sysIsGridDirty = true;

	SysMatrixAddRows2($(me).closest("tr"), matrixId, checkpagesize, setfocus)
}

function SysMatrixAddRows2(tr, matrixId, checkpagesize, setfocus) {
	var table = tr.closest("table");
	var i = SysElement.attr(tr, "rowIndex");
	var lastID = new Number(SysGet(matrixId + "_LastID")) + 1;
	SysSet(matrixId + "_LastID", lastID);
	var rowCount = new Number(SysGet(matrixId + "_Rows")) + 1;
	SysSet(matrixId + "_Rows", rowCount);
	var rowIDs = SysGet(matrixId + "_RowIDs");
	if (SysElement.IsNothing(rowIDs) || rowIDs === "") {
		SysSet(matrixId + "_RowIDs", matrixId + "_r" + lastID);
	}
	else {
		SysSet(matrixId + "_RowIDs", rowIDs + "," + matrixId + "_r" + lastID);
	}

	var func = window['SysMatrixAllRows_' + matrixId];
	if (func != null) {
		func(table, lastID, tr, rowCount);
	}

	if (setfocus == undefined) {
		setfocus = true;
	}
	if (setfocus) {
		var row = SysElement.attr(table, "rows")[i];
		for (var c = 0; c < row.cells.length; c++) {
			var td = row.cells[c];
			var el = td.firstChild;
			if (SysGridFocusEl(el)) {
				return;
			}
		}
	}
}

function SysMatrixShowFavorite(favoriteButtonId, showAsPinned) {
	var favoriteImage = new SysElement(favoriteButtonId + "_image");

	if (showAsPinned) {
		SysMatrixPinFavorite(favoriteImage);
	}
	else {
		SysMatrixUnpinFavorite(favoriteImage);
	}

	favoriteImage.Show();
}

function SysMatrixPinFavorite(favoriteImage) {
	favoriteImage.Attribute("src", "images/pin_pinnedStar.png");
}

function SysMatrixUnpinFavorite(favoriteImage) {
	favoriteImage.Attribute("src", "images/pin_unpinnedStar.png");
}

function SysMatrixToggleFavoritePin(favoriteButton, callbackFunction) {
	var isPinned = SysMatrixFavoriteIsPinned(favoriteButton.id);
	var favoriteImage = new SysElement(favoriteButton.id + "_image");

	if (isPinned === true) {
		SysMatrixUnpinFavorite(favoriteImage);
	}
	else if (isPinned === false) {
		SysMatrixPinFavorite(favoriteImage);
	}

	if (callbackFunction != null) {
		callbackFunction(favoriteButton.id);
	}
};

function SysMatrixFavoriteIsPinned(favoriteButtonId) {
	var isPinned = null;
	var favoriteImage = new SysElement(favoriteButtonId + "_image").Attribute("src");

	if (favoriteImage.indexOf("pin_pinnedStar.png") != -1) {
		isPinned = true;
	} else if (favoriteImage.indexOf("pin_unpinnedStar.png") != -1) {
		isPinned = false;
	}

	return isPinned;
};
/// <reference path="../base/MicrosoftAjax.debug.js" />
/// <reference path="../base/jquery-1.5.1-vsdoc.js" />

//Column Resizable Functions

function SysSetColumnsResizable(tableID) {
	let tableResizeCallBack = null;
	let hasDiscountTable = $('table.Grid.DiscountTable').length > 0;
	let hasTableTotal = $('#TableTotal').length > 0;
	if (hasDiscountTable && typeof setTotalDiscountFieldWidths === 'function') {
		//setTotalDiscountFieldWidths is a function used in sales entry pages to align the discount table with the respective columns
		tableResizeCallBack = setTotalDiscountFieldWidths;
	}
	else if (hasTableTotal && typeof SetTableTotalWidth === 'function') {
		//SetTableTotalWidth is a function used in purchase order entry page to align the total table with the respective columns
		tableResizeCallBack = SetTableTotalWidth;
	}

	if (sessionStorage === undefined || sessionStorage === null) return;
	$('#' + tableID).colResizable({ liveDrag: true, postbackSafe: true, minWidth: 30, resizeMode: 'overflow', onResize: tableResizeCallBack });
	SysSetColumnMinimumWidth(tableID);
}

//Set min-width for each NOT READONLY input field. The purpose is to restricted user to resize the column too small.
function SysSetColumnMinimumWidth(tableID) {
	$('#' + tableID).find('input[type=text]:not([readonly])').css('min-width', '30px');
};
