/* Minification failed. Returning unminified contents.
(21,3): run-time error CSS1019: Unexpected token, found '('
(21,13): run-time error CSS1031: Expected selector, found '('
(21,13): run-time error CSS1025: Expected comma or open brace, found '('
(177,37): run-time error CSS1002: Unterminated string: '"\s]/g
 */
/*! TableSorter (FORK) v2.27.7 *//*
* Client-side table sorting with ease!
* @requires jQuery v1.2.6+
*
* Copyright (c) 2007 Christian Bach
* fork maintained by Rob Garrison
*
* Examples and original docs at: http://tablesorter.com
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @type jQuery
* @name tablesorter (FORK)
* @cat Plugins/Tablesorter
* @author Christian Bach - christian.bach@polyester.se
* @contributor Rob Garrison - https://github.com/Mottie/tablesorter
* @docs (fork) - https://mottie.github.io/tablesorter/docs/
*/
/*jshint browser:true, jquery:true, unused:false, expr: true */
; (function ($) {
    'use strict';
    var ts = $.tablesorter = {

        version: '2.27.7',

        parsers: [],
        widgets: [],
        defaults: {

            // *** appearance
            theme: 'default',  // adds tablesorter-{theme} to the table for styling
            widthFixed: false,      // adds colgroup to fix widths of columns
            showProcessing: false,      // show an indeterminate timer icon in the header when the table is sorted or filtered.

            headerTemplate: '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> // class from cssIcon
            onRenderTemplate: null,       // function( index, template ){ return template; }, // template is a string
            onRenderHeader: null,       // function( index ){}, // nothing to return

            // *** functionality
            cancelSelection: true,       // prevent text selection in the header
            tabIndex: true,       // add tabindex to header for keyboard accessibility
            dateFormat: 'mmddyyyy', // other options: 'ddmmyyy' or 'yyyymmdd'
            sortMultiSortKey: 'shiftKey', // key used to select additional columns
            sortResetKey: 'ctrlKey',  // key used to remove sorting on a column
            usNumberFormat: true,       // false for German '1.234.567,89' or French '1 234 567,89'
            delayInit: false,      // if false, the parsed table contents will not update until the first sort
            serverSideSorting: false,      // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
            resort: true,       // default setting to trigger a resort after an 'update', 'addRows', 'updateCell', etc has completed

            // *** sort options
            headers: {},         // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
            ignoreCase: true,       // ignore case while sorting
            sortForce: null,       // column(s) first sorted; always applied
            sortList: [],         // Initial sort order; applied initially; updated when manually sorted
            sortAppend: null,       // column(s) sorted last; always applied
            sortStable: false,      // when sorting two rows with exactly the same content, the original sort order is maintained

            sortInitialOrder: 'asc',      // sort direction on first click
            sortLocaleCompare: false,      // replace equivalent character (accented characters)
            sortReset: false,      // third click on the header will reset column to default - unsorted
            sortRestart: false,      // restart sort to 'sortInitialOrder' when clicking on previously unsorted columns

            emptyTo: 'bottom',   // sort empty cell to bottom, top, none, zero, emptyMax, emptyMin
            stringTo: 'max',      // sort strings in numerical column as max, min, top, bottom, zero
            duplicateSpan: true,       // colspan cells in the tbody will have duplicated content in the cache for each spanned column
            textExtraction: 'basic',    // text extraction method/function - function( node, table, cellIndex ){}
            textAttribute: 'data-text',// data-attribute that contains alternate cell text (used in default textExtraction function)
            textSorter: null,       // choose overall or specific column sorter function( a, b, direction, table, columnIndex ) [alt: ts.sortText]
            numberSorter: null,       // choose overall numeric sorter function( a, b, direction, maxColumnValue )

            // *** widget options
            initWidgets: true,       // apply widgets on tablesorter initialization
            widgetClass: 'widget-{name}', // table class name template to match to include a widget
            widgets: [],         // method to add widgets, e.g. widgets: ['zebra']
            widgetOptions: {
                zebra: ['even', 'odd']    // zebra widget alternating row class names
            },

            // *** callbacks
            initialized: null,       // function( table ){},

            // *** extra css class names
            tableClass: '',
            cssAsc: '',
            cssDesc: '',
            cssNone: '',
            cssHeader: '',
            cssHeaderRow: '',
            cssProcessing: '', // processing icon applied to header during sort/filter

            cssChildRow: 'tablesorter-childRow', // class name indiciating that a row is to be attached to its parent
            cssInfoBlock: 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
            cssNoSort: 'tablesorter-noSort',      // class name added to element inside header; clicking on it won't cause a sort
            cssIgnoreRow: 'tablesorter-ignoreRow',   // header row to ignore; cells within this row will not be added to c.$headers

            cssIcon: 'tablesorter-icon', // if this class does not exist, the {icon} will not be added from the headerTemplate
            cssIconNone: '', // class name added to the icon when there is no column sort
            cssIconAsc: '', // class name added to the icon when the column has an ascending sort
            cssIconDesc: '', // class name added to the icon when the column has a descending sort

            // *** events
            pointerClick: 'click',
            pointerDown: 'mousedown',
            pointerUp: 'mouseup',

            // *** selectors
            selectorHeaders: '> thead th, > thead td',
            selectorSort: 'th, td',   // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
            selectorRemove: '.remove-me',

            // *** advanced
            debug: false,

            // *** Internal variables
            headerList: [],
            empties: {},
            strings: {},
            parsers: []

            // removed: widgetZebra: { css: ['even', 'odd'] }

        },

        // internal css classes - these will ALWAYS be added to
        // the table and MUST only contain one class name - fixes #381
        css: {
            table: 'tablesorter',
            cssHasChild: 'tablesorter-hasChildRow',
            childRow: 'tablesorter-childRow',
            colgroup: 'tablesorter-colgroup',
            header: 'tablesorter-header',
            headerRow: 'tablesorter-headerRow',
            headerIn: 'tablesorter-header-inner',
            icon: 'tablesorter-icon',
            processing: 'tablesorter-processing',
            sortAsc: 'tablesorter-headerAsc',
            sortDesc: 'tablesorter-headerDesc',
            sortNone: 'tablesorter-headerUnSorted'
        },

        // labels applied to sortable headers for accessibility (aria) support
        language: {
            sortAsc: 'Ascending sort applied, ',
            sortDesc: 'Descending sort applied, ',
            sortNone: 'No sort applied, ',
            sortDisabled: 'sorting is disabled',
            nextAsc: 'activate to apply an ascending sort',
            nextDesc: 'activate to apply a descending sort',
            nextNone: 'activate to remove the sort'
        },

        regex: {
            templateContent: /\{content\}/g,
            templateIcon: /\{icon\}/g,
            templateName: /\{name\}/i,
            spaces: /\s+/g,
            nonWord: /\W/g,
            formElements: /(input|select|button|textarea)/i,

            // *** sort functions ***
            // regex used in natural sort
            // chunk/tokenize numbers & letters
            chunk: /(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,
            // replace chunks @ ends
            chunks: /(^\\0|\\0$)/,
            hex: /^0x[0-9a-f]+$/i,

            // *** formatFloat ***
            comma: /,/g,
            digitNonUS: /[\s|\.]/g,
            digitNegativeTest: /^\s*\([.\d]+\)/,
            digitNegativeReplace: /^\s*\(([.\d]+)\)/,

            // *** isDigit ***
            digitTest: /^[\-+(]?\d+[)]?$/,
            digitReplace: /[,.'"\s]/g

        },

        // digit sort, text location
        string: {
            max: 1,
            min: -1,
            emptymin: 1,
            emptymax: -1,
            zero: 0,
            none: 0,
            'null': 0,
            top: true,
            bottom: false
        },

        keyCodes: {
            enter: 13
        },

        // placeholder date parser data (globalize)
        dates: {},

        // These methods can be applied on table.config instance
        instanceMethods: {},

        /*
		▄█████ ██████ ██████ ██  ██ █████▄
		▀█▄    ██▄▄     ██   ██  ██ ██▄▄██
		   ▀█▄ ██▀▀     ██   ██  ██ ██▀▀▀
		█████▀ ██████   ██   ▀████▀ ██
		*/

        setup: function (table, c) {
            // if no thead or tbody, or tablesorter is already present, quit
            if (!table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true) {
                if (c.debug) {
                    if (table.hasInitialized) {
                        console.warn('Stopping initialization. Tablesorter has already been initialized');
                    } else {
                        console.error('Stopping initialization! No table, thead or tbody', table);
                    }
                }
                return;
            }

            var tmp = '',
				$table = $(table),
				meta = $.metadata;
            // initialization flag
            table.hasInitialized = false;
            // table is being processed flag
            table.isProcessing = true;
            // make sure to store the config object
            table.config = c;
            // save the settings where they read
            $.data(table, 'tablesorter', c);
            if (c.debug) {
                console[console.group ? 'group' : 'log']('Initializing tablesorter v' + ts.version);
                $.data(table, 'startoveralltimer', new Date());
            }

            // removing this in version 3 (only supports jQuery 1.7+)
            c.supportsDataObject = (function (version) {
                version[0] = parseInt(version[0], 10);
                return (version[0] > 1) || (version[0] === 1 && parseInt(version[1], 10) >= 4);
            })($.fn.jquery.split('.'));
            // ensure case insensitivity
            c.emptyTo = c.emptyTo.toLowerCase();
            c.stringTo = c.stringTo.toLowerCase();
            c.last = { sortList: [], clickedIndex: -1 };
            // add table theme class only if there isn't already one there
            if (!/tablesorter\-/.test($table.attr('class'))) {
                tmp = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
            }
            c.table = table;
            c.$table = $table
				.addClass(ts.css.table + ' ' + c.tableClass + tmp)
				.attr('role', 'grid');
            c.$headers = $table.find(c.selectorHeaders);

            // give the table a unique id, which will be used in namespace binding
            if (!c.namespace) {
                c.namespace = '.tablesorter' + Math.random().toString(16).slice(2);
            } else {
                // make sure namespace starts with a period & doesn't have weird characters
                c.namespace = '.' + c.namespace.replace(ts.regex.nonWord, '');
            }

            c.$table.children().children('tr').attr('role', 'row');
            c.$tbodies = $table.children('tbody:not(.' + c.cssInfoBlock + ')').attr({
                'aria-live': 'polite',
                'aria-relevant': 'all'
            });
            if (c.$table.children('caption').length) {
                tmp = c.$table.children('caption')[0];
                if (!tmp.id) { tmp.id = c.namespace.slice(1) + 'caption'; }
                c.$table.attr('aria-labelledby', tmp.id);
            }
            c.widgetInit = {}; // keep a list of initialized widgets
            // change textExtraction via data-attribute
            c.textExtraction = c.$table.attr('data-text-extraction') || c.textExtraction || 'basic';
            // build headers
            ts.buildHeaders(c);
            // fixate columns if the users supplies the fixedWidth option
            // do this after theme has been applied
            ts.fixColumnWidth(table);
            // add widgets from class name
            ts.addWidgetFromClass(table);
            // add widget options before parsing (e.g. grouping widget has parser settings)
            ts.applyWidgetOptions(table);
            // try to auto detect column type, and store in tables config
            ts.setupParsers(c);
            // start total row count at zero
            c.totalRows = 0;
            // build the cache for the tbody cells
            // delayInit will delay building the cache until the user starts a sort
            if (!c.delayInit) { ts.buildCache(c); }
            // bind all header events and methods
            ts.bindEvents(table, c.$headers, true);
            ts.bindMethods(c);
            // get sort list from jQuery data or metadata
            // in jQuery < 1.4, an error occurs when calling $table.data()
            if (c.supportsDataObject && typeof $table.data().sortlist !== 'undefined') {
                c.sortList = $table.data().sortlist;
            } else if (meta && ($table.metadata() && $table.metadata().sortlist)) {
                c.sortList = $table.metadata().sortlist;
            }
            // apply widget init code
            ts.applyWidget(table, true);
            // if user has supplied a sort list to constructor
            if (c.sortList.length > 0) {
                ts.sortOn(c, c.sortList, {}, !c.initWidgets);
            } else {
                ts.setHeadersCss(c);
                if (c.initWidgets) {
                    // apply widget format
                    ts.applyWidget(table, false);
                }
            }

            // show processesing icon
            if (c.showProcessing) {
                $table
				.unbind('sortBegin' + c.namespace + ' sortEnd' + c.namespace)
				.bind('sortBegin' + c.namespace + ' sortEnd' + c.namespace, function (e) {
				    clearTimeout(c.timerProcessing);
				    ts.isProcessing(table);
				    if (e.type === 'sortBegin') {
				        c.timerProcessing = setTimeout(function () {
				            ts.isProcessing(table, true);
				        }, 500);
				    }
				});
            }

            // initialized
            table.hasInitialized = true;
            table.isProcessing = false;
            if (c.debug) {
                console.log('Overall initialization time:' + ts.benchmark($.data(table, 'startoveralltimer')));
                if (c.debug && console.groupEnd) { console.groupEnd(); }
            }
            $table.triggerHandler('tablesorter-initialized', table);
            if (typeof c.initialized === 'function') {
                c.initialized(table);
            }
        },

        bindMethods: function (c) {
            var $table = c.$table,
				namespace = c.namespace,
				events = ('sortReset update updateRows updateAll updateHeaders addRows updateCell updateComplete ' +
					'sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup ' +
					'mouseleave ').split(' ')
					.join(namespace + ' ');
            // apply easy methods that trigger bound events
            $table
			.unbind(events.replace(ts.regex.spaces, ' '))
			.bind('sortReset' + namespace, function (e, callback) {
			    e.stopPropagation();
			    // using this.config to ensure functions are getting a non-cached version of the config
			    ts.sortReset(this.config, callback);
			})
			.bind('updateAll' + namespace, function (e, resort, callback) {
			    e.stopPropagation();
			    ts.updateAll(this.config, resort, callback);
			})
			.bind('update' + namespace + ' updateRows' + namespace, function (e, resort, callback) {
			    e.stopPropagation();
			    ts.update(this.config, resort, callback);
			})
			.bind('updateHeaders' + namespace, function (e, callback) {
			    e.stopPropagation();
			    ts.updateHeaders(this.config, callback);
			})
			.bind('updateCell' + namespace, function (e, cell, resort, callback) {
			    e.stopPropagation();
			    ts.updateCell(this.config, cell, resort, callback);
			})
			.bind('addRows' + namespace, function (e, $row, resort, callback) {
			    e.stopPropagation();
			    ts.addRows(this.config, $row, resort, callback);
			})
			.bind('updateComplete' + namespace, function () {
			    this.isUpdating = false;
			})
			.bind('sorton' + namespace, function (e, list, callback, init) {
			    e.stopPropagation();
			    ts.sortOn(this.config, list, callback, init);
			})
			.bind('appendCache' + namespace, function (e, callback, init) {
			    e.stopPropagation();
			    ts.appendCache(this.config, init);
			    if ($.isFunction(callback)) {
			        callback(this);
			    }
			})
			// $tbodies variable is used by the tbody sorting widget
			.bind('updateCache' + namespace, function (e, callback, $tbodies) {
			    e.stopPropagation();
			    ts.updateCache(this.config, callback, $tbodies);
			})
			.bind('applyWidgetId' + namespace, function (e, id) {
			    e.stopPropagation();
			    ts.applyWidgetId(this, id);
			})
			.bind('applyWidgets' + namespace, function (e, init) {
			    e.stopPropagation();
			    // apply widgets
			    ts.applyWidget(this, init);
			})
			.bind('refreshWidgets' + namespace, function (e, all, dontapply) {
			    e.stopPropagation();
			    ts.refreshWidgets(this, all, dontapply);
			})
			.bind('removeWidget' + namespace, function (e, name, refreshing) {
			    e.stopPropagation();
			    ts.removeWidget(this, name, refreshing);
			})
			.bind('destroy' + namespace, function (e, removeClasses, callback) {
			    e.stopPropagation();
			    ts.destroy(this, removeClasses, callback);
			})
			.bind('resetToLoadState' + namespace, function (e) {
			    e.stopPropagation();
			    // remove all widgets
			    ts.removeWidget(this, true, false);
			    // restore original settings; this clears out current settings, but does not clear
			    // values saved to storage.
			    c = $.extend(true, ts.defaults, c.originalSettings);
			    this.hasInitialized = false;
			    // setup the entire table again
			    ts.setup(this, c);
			});
        },

        bindEvents: function (table, $headers, core) {
            table = $(table)[0];
            var tmp,
				c = table.config,
				namespace = c.namespace,
				downTarget = null;
            if (core !== true) {
                $headers.addClass(namespace.slice(1) + '_extra_headers');
                tmp = $.fn.closest ? $headers.closest('table')[0] : $headers.parents('table')[0];
                if (tmp && tmp.nodeName === 'TABLE' && tmp !== table) {
                    $(tmp).addClass(namespace.slice(1) + '_extra_table');
                }
            }
            tmp = (c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ')
				.replace(ts.regex.spaces, ' ')
				.split(' ')
				.join(namespace + ' ');
            // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
            $headers
			// http://stackoverflow.com/questions/5312849/jquery-find-self;
			.find(c.selectorSort)
			.add($headers.filter(c.selectorSort))
			.unbind(tmp)
			.bind(tmp, function (e, external) {
			    var $cell, cell, temp,
					$target = $(e.target),
					// wrap event type in spaces, so the match doesn't trigger on inner words
					type = ' ' + e.type + ' ';
			    // only recognize left clicks
			    if (((e.which || e.button) !== 1 && !type.match(' ' + c.pointerClick + ' | sort | keyup ')) ||
			        // allow pressing enter
					(type === ' keyup ' && e.which !== ts.keyCodes.enter) ||
			        // allow triggering a click event (e.which is undefined) & ignore physical clicks
					(type.match(' ' + c.pointerClick + ' ') && typeof e.which !== 'undefined')) {
			        return;
			    }
			    // ignore mouseup if mousedown wasn't on the same target
			    if (type.match(' ' + c.pointerUp + ' ') && downTarget !== e.target && external !== true) {
			        return;
			    }
			    // set target on mousedown
			    if (type.match(' ' + c.pointerDown + ' ')) {
			        downTarget = e.target;
			        // preventDefault needed or jQuery v1.3.2 and older throws an
			        // "Uncaught TypeError: handler.apply is not a function" error
			        temp = $target.jquery.split('.');
			        if (temp[0] === '1' && temp[1] < 4) { e.preventDefault(); }
			        return;
			    }
			    downTarget = null;
			    // prevent sort being triggered on form elements
			    if (ts.regex.formElements.test(e.target.nodeName) ||
			        // nosort class name, or elements within a nosort container
					$target.hasClass(c.cssNoSort) || $target.parents('.' + c.cssNoSort).length > 0 ||
			        // elements within a button
					$target.parents('button').length > 0) {
			        return !c.cancelSelection;
			    }
			    if (c.delayInit && ts.isEmptyObject(c.cache)) {
			        ts.buildCache(c);
			    }
			    // jQuery v1.2.6 doesn't have closest()
			    $cell = $.fn.closest ? $(this).closest('th, td') :
					/TH|TD/.test(this.nodeName) ? $(this) : $(this).parents('th, td');
			    // reference original table headers and find the same cell
			    // don't use $headers or IE8 throws an error - see #987
			    temp = $headers.index($cell);
			    c.last.clickedIndex = (temp < 0) ? $cell.attr('data-column') : temp;
			    // use column index if $headers is undefined
			    cell = c.$headers[c.last.clickedIndex];
			    if (cell && !cell.sortDisabled) {
			        ts.initSort(c, cell, e);
			    }
			});
            if (c.cancelSelection) {
                // cancel selection
                $headers
					.attr('unselectable', 'on')
					.bind('selectstart', false)
					.css({
					    'user-select': 'none',
					    'MozUserSelect': 'none' // not needed for jQuery 1.8+
					});
            }
        },

        buildHeaders: function (c) {
            var $temp, icon, timer, indx;
            c.headerList = [];
            c.headerContent = [];
            c.sortVars = [];
            if (c.debug) {
                timer = new Date();
            }
            // children tr in tfoot - see issue #196 & #547
            // don't pass table.config to computeColumnIndex here - widgets (math) pass it to "quickly" index tbody cells
            c.columns = ts.computeColumnIndex(c.$table.children('thead, tfoot').children('tr'));
            // add icon if cssIcon option exists
            icon = c.cssIcon ?
				'<i class="' + (c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon) + '"></i>' :
				'';
            // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
            c.$headers = $($.map(c.$table.find(c.selectorHeaders), function (elem, index) {
                var configHeaders, header, column, template, tmp,
					$elem = $(elem);
                // ignore cell (don't add it to c.$headers) if row has ignoreRow class
                if ($elem.parent().hasClass(c.cssIgnoreRow)) { return; }
                // make sure to get header cell & not column indexed cell
                configHeaders = ts.getColumnData(c.table, c.headers, index, true);
                // save original header content
                c.headerContent[index] = $elem.html();
                // if headerTemplate is empty, don't reformat the header cell
                if (c.headerTemplate !== '' && !$elem.find('.' + ts.css.headerIn).length) {
                    // set up header template
                    template = c.headerTemplate
						.replace(ts.regex.templateContent, $elem.html())
						.replace(ts.regex.templateIcon, $elem.find('.' + ts.css.icon).length ? '' : icon);
                    if (c.onRenderTemplate) {
                        header = c.onRenderTemplate.apply($elem, [index, template]);
                        // only change t if something is returned
                        if (header && typeof header === 'string') {
                            template = header;
                        }
                    }
                    $elem.html('<div class="' + ts.css.headerIn + '">' + template + '</div>'); // faster than wrapInner
                }
                if (c.onRenderHeader) {
                    c.onRenderHeader.apply($elem, [index, c, c.$table]);
                }
                column = parseInt($elem.attr('data-column'), 10);
                elem.column = column;
                tmp = ts.getOrder(ts.getData($elem, configHeaders, 'sortInitialOrder') || c.sortInitialOrder);
                // this may get updated numerous times if there are multiple rows
                c.sortVars[column] = {
                    count: -1, // set to -1 because clicking on the header automatically adds one
                    order: tmp ?
						(c.sortReset ? [1, 0, 2] : [1, 0]) : // desc, asc, unsorted
						(c.sortReset ? [0, 1, 2] : [0, 1]),  // asc, desc, unsorted
                    lockedOrder: false
                };
                tmp = ts.getData($elem, configHeaders, 'lockedOrder') || false;
                if (typeof tmp !== 'undefined' && tmp !== false) {
                    c.sortVars[column].lockedOrder = true;
                    c.sortVars[column].order = ts.getOrder(tmp) ? [1, 1] : [0, 0];
                }
                // add cell to headerList
                c.headerList[index] = elem;
                // add to parent in case there are multiple rows
                $elem
					.addClass(ts.css.header + ' ' + c.cssHeader)
					.parent()
					.addClass(ts.css.headerRow + ' ' + c.cssHeaderRow)
					.attr('role', 'row');
                // allow keyboard cursor to focus on element
                if (c.tabIndex) {
                    $elem.attr('tabindex', 0);
                }
                return elem;
            }));
            // cache headers per column
            c.$headerIndexed = [];
            for (indx = 0; indx < c.columns; indx++) {
                // colspan in header making a column undefined
                if (ts.isEmptyObject(c.sortVars[indx])) {
                    c.sortVars[indx] = {};
                }
                $temp = c.$headers.filter('[data-column="' + indx + '"]');
                // target sortable column cells, unless there are none, then use non-sortable cells
                // .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6
                c.$headerIndexed[indx] = $temp.length ?
					$temp.not('.sorter-false').length ?
						$temp.not('.sorter-false').filter(':last') :
						$temp.filter(':last') :
					$();
            }
            c.$table.find(c.selectorHeaders).attr({
                scope: 'col',
                role: 'columnheader'
            });
            // enable/disable sorting
            ts.updateHeader(c);
            if (c.debug) {
                console.log('Built headers:' + ts.benchmark(timer));
                console.log(c.$headers);
            }
        },

        // Use it to add a set of methods to table.config which will be available for all tables.
        // This should be done before table initialization
        addInstanceMethods: function (methods) {
            $.extend(ts.instanceMethods, methods);
        },

        /*
		█████▄ ▄████▄ █████▄ ▄█████ ██████ █████▄ ▄█████
		██▄▄██ ██▄▄██ ██▄▄██ ▀█▄    ██▄▄   ██▄▄██ ▀█▄
		██▀▀▀  ██▀▀██ ██▀██     ▀█▄ ██▀▀   ██▀██     ▀█▄
		██     ██  ██ ██  ██ █████▀ ██████ ██  ██ █████▀
		*/
        setupParsers: function (c, $tbodies) {
            var rows, list, span, max, colIndex, indx, header, configHeaders,
				noParser, parser, extractor, time, tbody, len,
				table = c.table,
				tbodyIndex = 0,
				debug = {};
            // update table bodies in case we start with an empty table
            c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')');
            tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies;
            len = tbody.length;
            if (len === 0) {
                return c.debug ? console.warn('Warning: *Empty table!* Not building a parser cache') : '';
            } else if (c.debug) {
                time = new Date();
                console[console.group ? 'group' : 'log']('Detecting parsers for each column');
            }
            list = {
                extractors: [],
                parsers: []
            };
            while (tbodyIndex < len) {
                rows = tbody[tbodyIndex].rows;
                if (rows.length) {
                    colIndex = 0;
                    max = c.columns;
                    for (indx = 0; indx < max; indx++) {
                        header = c.$headerIndexed[colIndex];
                        if (header && header.length) {
                            // get column indexed table cell
                            configHeaders = ts.getColumnData(table, c.headers, colIndex);
                            // get column parser/extractor
                            extractor = ts.getParserById(ts.getData(header, configHeaders, 'extractor'));
                            parser = ts.getParserById(ts.getData(header, configHeaders, 'sorter'));
                            noParser = ts.getData(header, configHeaders, 'parser') === 'false';
                            // empty cells behaviour - keeping emptyToBottom for backwards compatibility
                            c.empties[colIndex] = (
								ts.getData(header, configHeaders, 'empty') ||
								c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top')).toLowerCase();
                            // text strings behaviour in numerical sorts
                            c.strings[colIndex] = (
								ts.getData(header, configHeaders, 'string') ||
								c.stringTo ||
								'max').toLowerCase();
                            if (noParser) {
                                parser = ts.getParserById('no-parser');
                            }
                            if (!extractor) {
                                // For now, maybe detect someday
                                extractor = false;
                            }
                            if (!parser) {
                                parser = ts.detectParserForColumn(c, rows, -1, colIndex);
                            }
                            if (c.debug) {
                                debug['(' + colIndex + ') ' + header.text()] = {
                                    parser: parser.id,
                                    extractor: extractor ? extractor.id : 'none',
                                    string: c.strings[colIndex],
                                    empty: c.empties[colIndex]
                                };
                            }
                            list.parsers[colIndex] = parser;
                            list.extractors[colIndex] = extractor;
                            span = header[0].colSpan - 1;
                            if (span > 0) {
                                colIndex += span;
                                max += span;
                                while (span + 1 > 0) {
                                    // set colspan columns to use the same parsers & extractors
                                    list.parsers[colIndex - span] = parser;
                                    list.extractors[colIndex - span] = extractor;
                                    span--;
                                }
                            }
                        }
                        colIndex++;
                    }
                }
                tbodyIndex += (list.parsers.length) ? len : 1;
            }
            if (c.debug) {
                if (!ts.isEmptyObject(debug)) {
                    console[console.table ? 'table' : 'log'](debug);
                } else {
                    console.warn('  No parsers detected!');
                }
                console.log('Completed detecting parsers' + ts.benchmark(time));
                if (console.groupEnd) { console.groupEnd(); }
            }
            c.parsers = list.parsers;
            c.extractors = list.extractors;
        },

        addParser: function (parser) {
            var indx,
				len = ts.parsers.length,
				add = true;
            for (indx = 0; indx < len; indx++) {
                if (ts.parsers[indx].id.toLowerCase() === parser.id.toLowerCase()) {
                    add = false;
                }
            }
            if (add) {
                ts.parsers[ts.parsers.length] = parser;
            }
        },

        getParserById: function (name) {
            /*jshint eqeqeq:false */
            if (name == 'false') { return false; }
            var indx,
				len = ts.parsers.length;
            for (indx = 0; indx < len; indx++) {
                if (ts.parsers[indx].id.toLowerCase() === (name.toString()).toLowerCase()) {
                    return ts.parsers[indx];
                }
            }
            return false;
        },

        detectParserForColumn: function (c, rows, rowIndex, cellIndex) {
            var cur, $node, row,
				indx = ts.parsers.length,
				node = false,
				nodeValue = '',
				keepLooking = true;
            while (nodeValue === '' && keepLooking) {
                rowIndex++;
                row = rows[rowIndex];
                // stop looking after 50 empty rows
                if (row && rowIndex < 50) {
                    if (row.className.indexOf(ts.cssIgnoreRow) < 0) {
                        node = rows[rowIndex].cells[cellIndex];
                        nodeValue = ts.getElementText(c, node, cellIndex);
                        $node = $(node);
                        if (c.debug) {
                            console.log('Checking if value was empty on row ' + rowIndex + ', column: ' +
								cellIndex + ': "' + nodeValue + '"');
                        }
                    }
                } else {
                    keepLooking = false;
                }
            }
            while (--indx >= 0) {
                cur = ts.parsers[indx];
                // ignore the default text parser because it will always be true
                if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, c.table, node, $node)) {
                    return cur;
                }
            }
            // nothing found, return the generic parser (text)
            return ts.getParserById('text');
        },

        getElementText: function (c, node, cellIndex) {
            if (!node) { return ''; }
            var tmp,
				extract = c.textExtraction || '',
				// node could be a jquery object
				// http://jsperf.com/jquery-vs-instanceof-jquery/2
				$node = node.jquery ? node : $(node);
            if (typeof extract === 'string') {
                // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow!
                // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/
                if (extract === 'basic' && typeof (tmp = $node.attr(c.textAttribute)) !== 'undefined') {
                    return $.trim(tmp);
                }
                return $.trim(node.textContent || $node.text());
            } else {
                if (typeof extract === 'function') {
                    return $.trim(extract($node[0], c.table, cellIndex));
                } else if (typeof (tmp = ts.getColumnData(c.table, extract, cellIndex)) === 'function') {
                    return $.trim(tmp($node[0], c.table, cellIndex));
                }
            }
            // fallback
            return $.trim($node[0].textContent || $node.text());
        },

        // centralized function to extract/parse cell contents
        getParsedText: function (c, cell, colIndex, txt) {
            if (typeof txt === 'undefined') {
                txt = ts.getElementText(c, cell, colIndex);
            }
            // if no parser, make sure to return the txt
            var val = '' + txt,
				parser = c.parsers[colIndex],
				extractor = c.extractors[colIndex];
            if (parser) {
                // do extract before parsing, if there is one
                if (extractor && typeof extractor.format === 'function') {
                    txt = extractor.format(txt, c.table, cell, colIndex);
                }
                // allow parsing if the string is empty, previously parsing would change it to zero,
                // in case the parser needs to extract data from the table cell attributes
                val = parser.id === 'no-parser' ? '' :
					// make sure txt is a string (extractor may have converted it)
					parser.format('' + txt, c.table, cell, colIndex);
                if (c.ignoreCase && typeof val === 'string') {
                    val = val.toLowerCase();
                }
            }
            return val;
        },

        /*
		▄████▄ ▄████▄ ▄████▄ ██  ██ ██████
		██  ▀▀ ██▄▄██ ██  ▀▀ ██▄▄██ ██▄▄
		██  ▄▄ ██▀▀██ ██  ▄▄ ██▀▀██ ██▀▀
		▀████▀ ██  ██ ▀████▀ ██  ██ ██████
		*/
        buildCache: function (c, callback, $tbodies) {
            var cache, val, txt, rowIndex, colIndex, tbodyIndex, $tbody, $row,
				cols, $cells, cell, cacheTime, totalRows, rowData, prevRowData,
				colMax, span, cacheIndex, hasParser, max, len, index,
				table = c.table,
				parsers = c.parsers;
            // update tbody variable
            c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')');
            $tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies,
			c.cache = {};
            c.totalRows = 0;
            // if no parsers found, return - it's an empty table.
            if (!parsers) {
                return c.debug ? console.warn('Warning: *Empty table!* Not building a cache') : '';
            }
            if (c.debug) {
                cacheTime = new Date();
            }
            // processing icon
            if (c.showProcessing) {
                ts.isProcessing(table, true);
            }
            for (tbodyIndex = 0; tbodyIndex < $tbody.length; tbodyIndex++) {
                colMax = []; // column max value per tbody
                cache = c.cache[tbodyIndex] = {
                    normalized: [] // array of normalized row data; last entry contains 'rowData' above
                    // colMax: #   // added at the end
                };

                totalRows = ($tbody[tbodyIndex] && $tbody[tbodyIndex].rows.length) || 0;
                for (rowIndex = 0; rowIndex < totalRows; ++rowIndex) {
                    rowData = {
                        // order: original row order #
                        // $row : jQuery Object[]
                        child: [], // child row text (filter widget)
                        raw: []    // original row text
                    };
                    /** Add the table data to main data array */
                    $row = $($tbody[tbodyIndex].rows[rowIndex]);
                    cols = [];
                    // ignore "remove-me" rows
                    if ($row.hasClass(c.selectorRemove.slice(1))) {
                        continue;
                    }
                    // if this is a child row, add it to the last row's children and continue to the next row
                    // ignore child row class, if it is the first row
                    if ($row.hasClass(c.cssChildRow) && rowIndex !== 0) {
                        len = cache.normalized.length - 1;
                        prevRowData = cache.normalized[len][c.columns];
                        prevRowData.$row = prevRowData.$row.add($row);
                        // add 'hasChild' class name to parent row
                        if (!$row.prev().hasClass(c.cssChildRow)) {
                            $row.prev().addClass(ts.css.cssHasChild);
                        }
                        // save child row content (un-parsed!)
                        $cells = $row.children('th, td');
                        len = prevRowData.child.length;
                        prevRowData.child[len] = [];
                        // child row content does not account for colspans/rowspans; so indexing may be off
                        cacheIndex = 0;
                        max = c.columns;
                        for (colIndex = 0; colIndex < max; colIndex++) {
                            cell = $cells[colIndex];
                            if (cell) {
                                prevRowData.child[len][colIndex] = ts.getParsedText(c, cell, colIndex);
                                span = $cells[colIndex].colSpan - 1;
                                if (span > 0) {
                                    cacheIndex += span;
                                    max += span;
                                }
                            }
                            cacheIndex++;
                        }
                        // go to the next for loop
                        continue;
                    }
                    rowData.$row = $row;
                    rowData.order = rowIndex; // add original row position to rowCache
                    cacheIndex = 0;
                    max = c.columns;
                    for (colIndex = 0; colIndex < max; ++colIndex) {
                        cell = $row[0].cells[colIndex];
                        if (cell && cacheIndex < c.columns) {
                            hasParser = typeof parsers[cacheIndex] !== 'undefined';
                            if (!hasParser && c.debug) {
                                console.warn('No parser found for row: ' + rowIndex + ', column: ' + colIndex +
									'; cell containing: "' + $(cell).text() + '"; does it have a header?');
                            }
                            val = ts.getElementText(c, cell, cacheIndex);
                            rowData.raw[cacheIndex] = val; // save original row text
                            // save raw column text even if there is no parser set
                            txt = ts.getParsedText(c, cell, cacheIndex, val);
                            cols[cacheIndex] = txt;
                            if (hasParser && (parsers[cacheIndex].type || '').toLowerCase() === 'numeric') {
                                // determine column max value (ignore sign)
                                colMax[cacheIndex] = Math.max(Math.abs(txt) || 0, colMax[cacheIndex] || 0);
                            }
                            // allow colSpan in tbody
                            span = cell.colSpan - 1;
                            if (span > 0) {
                                index = 0;
                                while (index <= span) {
                                    // duplicate text (or not) to spanned columns
                                    // instead of setting duplicate span to empty string, use textExtraction to try to get a value
                                    // see http://stackoverflow.com/q/36449711/145346
                                    txt = c.duplicateSpan || index === 0 ?
                                        val :
										typeof c.textExtraction !== 'string' ?
											ts.getElementText(c, cell, cacheIndex + index) || '' :
											'';
                                    rowData.raw[cacheIndex + index] = txt;
                                    cols[cacheIndex + index] = txt;
                                    index++;
                                }
                                cacheIndex += span;
                                max += span;
                            }
                        }
                        cacheIndex++;
                    }
                    // ensure rowData is always in the same location (after the last column)
                    cols[c.columns] = rowData;
                    cache.normalized[cache.normalized.length] = cols;
                }
                cache.colMax = colMax;
                // total up rows, not including child rows
                c.totalRows += cache.normalized.length;

            }
            if (c.showProcessing) {
                ts.isProcessing(table); // remove processing icon
            }
            if (c.debug) {
                len = Math.min(5, c.cache[0].normalized.length);
                console[console.group ? 'group' : 'log']('Building cache for ' + c.totalRows +
					' rows (showing ' + len + ' rows in log) and ' + c.columns + ' columns' +
					ts.benchmark(cacheTime));
                val = {};
                for (colIndex = 0; colIndex < c.columns; colIndex++) {
                    for (cacheIndex = 0; cacheIndex < len; cacheIndex++) {
                        if (!val['row: ' + cacheIndex]) {
                            val['row: ' + cacheIndex] = {};
                        }
                        val['row: ' + cacheIndex][c.$headerIndexed[colIndex].text()] =
							c.cache[0].normalized[cacheIndex][colIndex];
                    }
                }
                console[console.table ? 'table' : 'log'](val);
                if (console.groupEnd) { console.groupEnd(); }
            }
            if ($.isFunction(callback)) {
                callback(table);
            }
        },

        getColumnText: function (table, column, callback, rowFilter) {
            table = $(table)[0];
            var tbodyIndex, rowIndex, cache, row, tbodyLen, rowLen, raw, parsed, $cell, result,
				hasCallback = typeof callback === 'function',
				allColumns = column === 'all',
				data = { raw: [], parsed: [], $cell: [] },
				c = table.config;
            if (ts.isEmptyObject(c)) {
                if (c.debug) {
                    console.warn('No cache found - aborting getColumnText function!');
                }
            } else {
                tbodyLen = c.$tbodies.length;
                for (tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++) {
                    cache = c.cache[tbodyIndex].normalized;
                    rowLen = cache.length;
                    for (rowIndex = 0; rowIndex < rowLen; rowIndex++) {
                        row = cache[rowIndex];
                        if (rowFilter && !row[c.columns].$row.is(rowFilter)) {
                            continue;
                        }
                        result = true;
                        parsed = (allColumns) ? row.slice(0, c.columns) : row[column];
                        row = row[c.columns];
                        raw = (allColumns) ? row.raw : row.raw[column];
                        $cell = (allColumns) ? row.$row.children() : row.$row.children().eq(column);
                        if (hasCallback) {
                            result = callback({
                                tbodyIndex: tbodyIndex,
                                rowIndex: rowIndex,
                                parsed: parsed,
                                raw: raw,
                                $row: row.$row,
                                $cell: $cell
                            });
                        }
                        if (result !== false) {
                            data.parsed[data.parsed.length] = parsed;
                            data.raw[data.raw.length] = raw;
                            data.$cell[data.$cell.length] = $cell;
                        }
                    }
                }
                // return everything
                return data;
            }
        },

        /*
		██  ██ █████▄ █████▄ ▄████▄ ██████ ██████
		██  ██ ██▄▄██ ██  ██ ██▄▄██   ██   ██▄▄
		██  ██ ██▀▀▀  ██  ██ ██▀▀██   ██   ██▀▀
		▀████▀ ██     █████▀ ██  ██   ██   ██████
		*/
        setHeadersCss: function (c) {
            var $sorted, indx, column,
				list = c.sortList,
				len = list.length,
				none = ts.css.sortNone + ' ' + c.cssNone,
				css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
				cssIcon = [c.cssIconAsc, c.cssIconDesc, c.cssIconNone],
				aria = ['ascending', 'descending'],
				// find the footer
				$headers = c.$table
					.find('tfoot tr')
					.children('td, th')
					.add($(c.namespace + '_extra_headers'))
					.removeClass(css.join(' '));
            // remove all header information
            c.$headers
				.removeClass(css.join(' '))
				.addClass(none)
				.attr('aria-sort', 'none')
				.find('.' + ts.css.icon)
				.removeClass(cssIcon.join(' '))
				.addClass(cssIcon[2]);
            for (indx = 0; indx < len; indx++) {
                // direction = 2 means reset!
                if (list[indx][1] !== 2) {
                    // multicolumn sorting updating - see #1005
                    // .not(function(){}) needs jQuery 1.4
                    // filter(function(i, el){}) <- el is undefined in jQuery v1.2.6
                    $sorted = c.$headers.filter(function (i) {
                        // only include headers that are in the sortList (this includes colspans)
                        var include = true,
							$el = c.$headers.eq(i),
							col = parseInt($el.attr('data-column'), 10),
							end = col + c.$headers[i].colSpan;
                        for (; col < end; col++) {
                            include = include ? include || ts.isValueInArray(col, c.sortList) > -1 : false;
                        }
                        return include;
                    });

                    // choose the :last in case there are nested columns
                    $sorted = $sorted
						.not('.sorter-false')
						.filter('[data-column="' + list[indx][0] + '"]' + (len === 1 ? ':last' : ''));
                    if ($sorted.length) {
                        for (column = 0; column < $sorted.length; column++) {
                            if (!$sorted[column].sortDisabled) {
                                $sorted
									.eq(column)
									.removeClass(none)
									.addClass(css[list[indx][1]])
									.attr('aria-sort', aria[list[indx][1]])
									.find('.' + ts.css.icon)
									.removeClass(cssIcon[2])
									.addClass(cssIcon[list[indx][1]]);
                            }
                        }
                        // add sorted class to footer & extra headers, if they exist
                        if ($headers.length) {
                            $headers
								.filter('[data-column="' + list[indx][0] + '"]')
								.removeClass(none)
								.addClass(css[list[indx][1]]);
                        }
                    }
                }
            }
            // add verbose aria labels
            len = c.$headers.length;
            for (indx = 0; indx < len; indx++) {
                ts.setColumnAriaLabel(c, c.$headers.eq(indx));
            }
        },

        // nextSort (optional), lets you disable next sort text
        setColumnAriaLabel: function (c, $header, nextSort) {
            if ($header.length) {
                var column = parseInt($header.attr('data-column'), 10),
					vars = c.sortVars[column],
					tmp = $header.hasClass(ts.css.sortAsc) ?
						'sortAsc' :
						$header.hasClass(ts.css.sortDesc) ? 'sortDesc' : 'sortNone',
					txt = $.trim($header.text()) + ': ' + ts.language[tmp];
                if ($header.hasClass('sorter-false') || nextSort === false) {
                    txt += ts.language.sortDisabled;
                } else {
                    tmp = (vars.count + 1) % vars.order.length;
                    nextSort = vars.order[tmp];
                    // if nextSort
                    txt += ts.language[nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone'];
                }
                $header.attr('aria-label', txt);
            }
        },

        updateHeader: function (c) {
            var index, isDisabled, $header, col,
				table = c.table,
				len = c.$headers.length;
            for (index = 0; index < len; index++) {
                $header = c.$headers.eq(index);
                col = ts.getColumnData(table, c.headers, index, true);
                // add 'sorter-false' class if 'parser-false' is set
                isDisabled = ts.getData($header, col, 'sorter') === 'false' || ts.getData($header, col, 'parser') === 'false';
                ts.setColumnSort(c, $header, isDisabled);
            }
        },

        setColumnSort: function (c, $header, isDisabled) {
            var id = c.table.id;
            $header[0].sortDisabled = isDisabled;
            $header[isDisabled ? 'addClass' : 'removeClass']('sorter-false')
				.attr('aria-disabled', '' + isDisabled);
            // disable tab index on disabled cells
            if (c.tabIndex) {
                if (isDisabled) {
                    $header.removeAttr('tabindex');
                } else {
                    $header.attr('tabindex', '0');
                }
            }
            // aria-controls - requires table ID
            if (id) {
                if (isDisabled) {
                    $header.removeAttr('aria-controls');
                } else {
                    $header.attr('aria-controls', id);
                }
            }
        },

        updateHeaderSortCount: function (c, list) {
            var col, dir, group, indx, primary, temp, val, order,
				sortList = list || c.sortList,
				len = sortList.length;
            c.sortList = [];
            for (indx = 0; indx < len; indx++) {
                val = sortList[indx];
                // ensure all sortList values are numeric - fixes #127
                col = parseInt(val[0], 10);
                // prevents error if sorton array is wrong
                if (col < c.columns) {

                    // set order if not already defined - due to colspan header without associated header cell
                    // adding this check prevents a javascript error
                    if (!c.sortVars[col].order) {
                        if (ts.getOrder(c.sortInitialOrder)) {
                            order = c.sortReset ? [1, 0, 2] : [1, 0];
                        } else {
                            order = c.sortReset ? [0, 1, 2] : [0, 1];
                        }
                        c.sortVars[col].order = order;
                        c.sortVars[col].count = 0;
                    }

                    order = c.sortVars[col].order;
                    dir = ('' + val[1]).match(/^(1|d|s|o|n)/);
                    dir = dir ? dir[0] : '';
                    // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
                    switch (dir) {
                        case '1': case 'd': // descending
                            dir = 1;
                            break;
                        case 's': // same direction (as primary column)
                            // if primary sort is set to 's', make it ascending
                            dir = primary || 0;
                            break;
                        case 'o':
                            temp = order[(primary || 0) % order.length];
                            // opposite of primary column; but resets if primary resets
                            dir = temp === 0 ? 1 : temp === 1 ? 0 : 2;
                            break;
                        case 'n':
                            dir = order[(++c.sortVars[col].count) % order.length];
                            break;
                        default: // ascending
                            dir = 0;
                            break;
                    }
                    primary = indx === 0 ? dir : primary;
                    group = [col, parseInt(dir, 10) || 0];
                    c.sortList[c.sortList.length] = group;
                    dir = $.inArray(group[1], order); // fixes issue #167
                    c.sortVars[col].count = dir >= 0 ? dir : group[1] % order.length;
                }
            }
        },

        updateAll: function (c, resort, callback) {
            var table = c.table;
            table.isUpdating = true;
            ts.refreshWidgets(table, true, true);
            ts.buildHeaders(c);
            ts.bindEvents(table, c.$headers, true);
            ts.bindMethods(c);
            ts.commonUpdate(c, resort, callback);
        },

        update: function (c, resort, callback) {
            var table = c.table;
            table.isUpdating = true;
            // update sorting (if enabled/disabled)
            ts.updateHeader(c);
            ts.commonUpdate(c, resort, callback);
        },

        // simple header update - see #989
        updateHeaders: function (c, callback) {
            c.table.isUpdating = true;
            ts.buildHeaders(c);
            ts.bindEvents(c.table, c.$headers, true);
            ts.resortComplete(c, callback);
        },

        updateCell: function (c, cell, resort, callback) {
            if (ts.isEmptyObject(c.cache)) {
                // empty table, do an update instead - fixes #1099
                ts.updateHeader(c);
                ts.commonUpdate(c, resort, callback);
                return;
            }
            c.table.isUpdating = true;
            c.$table.find(c.selectorRemove).remove();
            // get position from the dom
            var tmp, indx, row, icell, cache, len,
				$tbodies = c.$tbodies,
				$cell = $(cell),
				// update cache - format: function( s, table, cell, cellIndex )
				// no closest in jQuery v1.2.6
				tbodyIndex = $tbodies
					.index($.fn.closest ? $cell.closest('tbody') : $cell.parents('tbody').filter(':first')),
				tbcache = c.cache[tbodyIndex],
				$row = $.fn.closest ? $cell.closest('tr') : $cell.parents('tr').filter(':first');
            cell = $cell[0]; // in case cell is a jQuery object
            // tbody may not exist if update is initialized while tbody is removed for processing
            if ($tbodies.length && tbodyIndex >= 0) {
                row = $tbodies.eq(tbodyIndex).find('tr').index($row);
                cache = tbcache.normalized[row];
                len = $row[0].cells.length;
                if (len !== c.columns) {
                    // colspan in here somewhere!
                    icell = 0;
                    tmp = false;
                    for (indx = 0; indx < len; indx++) {
                        if (!tmp && $row[0].cells[indx] !== cell) {
                            icell += $row[0].cells[indx].colSpan;
                        } else {
                            tmp = true;
                        }
                    }
                } else {
                    icell = $cell.index();
                }
                tmp = ts.getElementText(c, cell, icell); // raw
                cache[c.columns].raw[icell] = tmp;
                tmp = ts.getParsedText(c, cell, icell, tmp);
                cache[icell] = tmp; // parsed
                cache[c.columns].$row = $row;
                if ((c.parsers[icell].type || '').toLowerCase() === 'numeric') {
                    // update column max value (ignore sign)
                    tbcache.colMax[icell] = Math.max(Math.abs(tmp) || 0, tbcache.colMax[icell] || 0);
                }
                tmp = resort !== 'undefined' ? resort : c.resort;
                if (tmp !== false) {
                    // widgets will be reapplied
                    ts.checkResort(c, tmp, callback);
                } else {
                    // don't reapply widgets is resort is false, just in case it causes
                    // problems with element focus
                    ts.resortComplete(c, callback);
                }
            } else {
                if (c.debug) {
                    console.error('updateCell aborted, tbody missing or not within the indicated table');
                }
                c.table.isUpdating = false;
            }
        },

        addRows: function (c, $row, resort, callback) {
            var txt, val, tbodyIndex, rowIndex, rows, cellIndex, len, order,
				cacheIndex, rowData, cells, cell, span,
				// allow passing a row string if only one non-info tbody exists in the table
				valid = typeof $row === 'string' && c.$tbodies.length === 1 && /<tr/.test($row || ''),
				table = c.table;
            if (valid) {
                $row = $($row);
                c.$tbodies.append($row);
            } else if (!$row ||
                // row is a jQuery object?
				!($row instanceof jQuery) ||
                // row contained in the table?
				($.fn.closest ? $row.closest('table')[0] : $row.parents('table')[0]) !== c.table) {
                if (c.debug) {
                    console.error('addRows method requires (1) a jQuery selector reference to rows that have already ' +
						'been added to the table, or (2) row HTML string to be added to a table with only one tbody');
                }
                return false;
            }
            table.isUpdating = true;
            if (ts.isEmptyObject(c.cache)) {
                // empty table, do an update instead - fixes #450
                ts.updateHeader(c);
                ts.commonUpdate(c, resort, callback);
            } else {
                rows = $row.filter('tr').attr('role', 'row').length;
                tbodyIndex = c.$tbodies.index($row.parents('tbody').filter(':first'));
                // fixes adding rows to an empty table - see issue #179
                if (!(c.parsers && c.parsers.length)) {
                    ts.setupParsers(c);
                }
                // add each row
                for (rowIndex = 0; rowIndex < rows; rowIndex++) {
                    cacheIndex = 0;
                    len = $row[rowIndex].cells.length;
                    order = c.cache[tbodyIndex].normalized.length;
                    cells = [];
                    rowData = {
                        child: [],
                        raw: [],
                        $row: $row.eq(rowIndex),
                        order: order
                    };
                    // add each cell
                    for (cellIndex = 0; cellIndex < len; cellIndex++) {
                        cell = $row[rowIndex].cells[cellIndex];
                        txt = ts.getElementText(c, cell, cacheIndex);
                        rowData.raw[cacheIndex] = txt;
                        val = ts.getParsedText(c, cell, cacheIndex, txt);
                        cells[cacheIndex] = val;
                        if ((c.parsers[cacheIndex].type || '').toLowerCase() === 'numeric') {
                            // update column max value (ignore sign)
                            c.cache[tbodyIndex].colMax[cacheIndex] =
								Math.max(Math.abs(val) || 0, c.cache[tbodyIndex].colMax[cacheIndex] || 0);
                        }
                        span = cell.colSpan - 1;
                        if (span > 0) {
                            cacheIndex += span;
                        }
                        cacheIndex++;
                    }
                    // add the row data to the end
                    cells[c.columns] = rowData;
                    // update cache
                    c.cache[tbodyIndex].normalized[order] = cells;
                }
                // resort using current settings
                ts.checkResort(c, resort, callback);
            }
        },

        updateCache: function (c, callback, $tbodies) {
            // rebuild parsers
            if (!(c.parsers && c.parsers.length)) {
                ts.setupParsers(c, $tbodies);
            }
            // rebuild the cache map
            ts.buildCache(c, callback, $tbodies);
        },

        // init flag (true) used by pager plugin to prevent widget application
        // renamed from appendToTable
        appendCache: function (c, init) {
            var parsed, totalRows, $tbody, $curTbody, rowIndex, tbodyIndex, appendTime,
				table = c.table,
				wo = c.widgetOptions,
				$tbodies = c.$tbodies,
				rows = [],
				cache = c.cache;
            // empty table - fixes #206/#346
            if (ts.isEmptyObject(cache)) {
                // run pager appender in case the table was just emptied
                return c.appender ? c.appender(table, rows) :
					table.isUpdating ? c.$table.triggerHandler('updateComplete', table) : ''; // Fixes #532
            }
            if (c.debug) {
                appendTime = new Date();
            }
            for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                $tbody = $tbodies.eq(tbodyIndex);
                if ($tbody.length) {
                    // detach tbody for manipulation
                    $curTbody = ts.processTbody(table, $tbody, true);
                    parsed = cache[tbodyIndex].normalized;
                    totalRows = parsed.length;
                    for (rowIndex = 0; rowIndex < totalRows; rowIndex++) {
                        rows[rows.length] = parsed[rowIndex][c.columns].$row;
                        // removeRows used by the pager plugin; don't render if using ajax - fixes #411
                        if (!c.appender || (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)) {
                            $curTbody.append(parsed[rowIndex][c.columns].$row);
                        }
                    }
                    // restore tbody
                    ts.processTbody(table, $curTbody, false);
                }
            }
            if (c.appender) {
                c.appender(table, rows);
            }
            if (c.debug) {
                console.log('Rebuilt table' + ts.benchmark(appendTime));
            }
            // apply table widgets; but not before ajax completes
            if (!init && !c.appender) {
                ts.applyWidget(table);
            }
            if (table.isUpdating) {
                c.$table.triggerHandler('updateComplete', table);
            }
        },

        commonUpdate: function (c, resort, callback) {
            // remove rows/elements before update
            c.$table.find(c.selectorRemove).remove();
            // rebuild parsers
            ts.setupParsers(c);
            // rebuild the cache map
            ts.buildCache(c);
            ts.checkResort(c, resort, callback);
        },

        /*
		▄█████ ▄████▄ █████▄ ██████ ██ █████▄ ▄████▄
		▀█▄    ██  ██ ██▄▄██   ██   ██ ██  ██ ██ ▄▄▄
		   ▀█▄ ██  ██ ██▀██    ██   ██ ██  ██ ██ ▀██
		█████▀ ▀████▀ ██  ██   ██   ██ ██  ██ ▀████▀
		*/
        initSort: function (c, cell, event) {
            if (c.table.isUpdating) {
                // let any updates complete before initializing a sort
                return setTimeout(function () {
                    ts.initSort(c, cell, event);
                }, 50);
            }

            var arry, indx, headerIndx, dir, temp, tmp, $header,
				notMultiSort = !event[c.sortMultiSortKey],
				table = c.table,
				len = c.$headers.length,
				// get current column index
				col = parseInt($(cell).attr('data-column'), 10),
				order = c.sortVars[col].order;

            // Only call sortStart if sorting is enabled
            c.$table.triggerHandler('sortStart', table);
            // get current column sort order
            tmp = (c.sortVars[col].count + 1) % order.length;
            c.sortVars[col].count = event[c.sortResetKey] ? 2 : tmp;
            // reset all sorts on non-current column - issue #30
            if (c.sortRestart) {
                for (headerIndx = 0; headerIndx < len; headerIndx++) {
                    $header = c.$headers.eq(headerIndx);
                    tmp = parseInt($header.attr('data-column'), 10);
                    // only reset counts on columns that weren't just clicked on and if not included in a multisort
                    if (col !== tmp && (notMultiSort || $header.hasClass(ts.css.sortNone))) {
                        c.sortVars[tmp].count = -1;
                    }
                }
            }
            // user only wants to sort on one column
            if (notMultiSort) {
                // flush the sort list
                c.sortList = [];
                c.last.sortList = [];
                if (c.sortForce !== null) {
                    arry = c.sortForce;
                    for (indx = 0; indx < arry.length; indx++) {
                        if (arry[indx][0] !== col) {
                            c.sortList[c.sortList.length] = arry[indx];
                        }
                    }
                }
                // add column to sort list
                dir = order[c.sortVars[col].count];
                if (dir < 2) {
                    c.sortList[c.sortList.length] = [col, dir];
                    // add other columns if header spans across multiple
                    if (cell.colSpan > 1) {
                        for (indx = 1; indx < cell.colSpan; indx++) {
                            c.sortList[c.sortList.length] = [col + indx, dir];
                            // update count on columns in colSpan
                            c.sortVars[col + indx].count = $.inArray(dir, order);
                        }
                    }
                }
                // multi column sorting
            } else {
                // get rid of the sortAppend before adding more - fixes issue #115 & #523
                c.sortList = $.extend([], c.last.sortList);

                // the user has clicked on an already sorted column
                if (ts.isValueInArray(col, c.sortList) >= 0) {
                    // reverse the sorting direction
                    for (indx = 0; indx < c.sortList.length; indx++) {
                        tmp = c.sortList[indx];
                        if (tmp[0] === col) {
                            // order.count seems to be incorrect when compared to cell.count
                            tmp[1] = order[c.sortVars[col].count];
                            if (tmp[1] === 2) {
                                c.sortList.splice(indx, 1);
                                c.sortVars[col].count = -1;
                            }
                        }
                    }
                } else {
                    // add column to sort list array
                    dir = order[c.sortVars[col].count];
                    if (dir < 2) {
                        c.sortList[c.sortList.length] = [col, dir];
                        // add other columns if header spans across multiple
                        if (cell.colSpan > 1) {
                            for (indx = 1; indx < cell.colSpan; indx++) {
                                c.sortList[c.sortList.length] = [col + indx, dir];
                                // update count on columns in colSpan
                                c.sortVars[col + indx].count = $.inArray(dir, order);
                            }
                        }
                    }
                }
            }
            // save sort before applying sortAppend
            c.last.sortList = $.extend([], c.sortList);
            if (c.sortList.length && c.sortAppend) {
                arry = $.isArray(c.sortAppend) ? c.sortAppend : c.sortAppend[c.sortList[0][0]];
                if (!ts.isEmptyObject(arry)) {
                    for (indx = 0; indx < arry.length; indx++) {
                        if (arry[indx][0] !== col && ts.isValueInArray(arry[indx][0], c.sortList) < 0) {
                            dir = arry[indx][1];
                            temp = ('' + dir).match(/^(a|d|s|o|n)/);
                            if (temp) {
                                tmp = c.sortList[0][1];
                                switch (temp[0]) {
                                    case 'd':
                                        dir = 1;
                                        break;
                                    case 's':
                                        dir = tmp;
                                        break;
                                    case 'o':
                                        dir = tmp === 0 ? 1 : 0;
                                        break;
                                    case 'n':
                                        dir = (tmp + 1) % order.length;
                                        break;
                                    default:
                                        dir = 0;
                                        break;
                                }
                            }
                            c.sortList[c.sortList.length] = [arry[indx][0], dir];
                        }
                    }
                }
            }
            // sortBegin event triggered immediately before the sort
            c.$table.triggerHandler('sortBegin', table);
            // setTimeout needed so the processing icon shows up
            setTimeout(function () {
                // set css for headers
                ts.setHeadersCss(c);
                ts.multisort(c);
                ts.appendCache(c);
                c.$table.triggerHandler('sortBeforeEnd', table);
                c.$table.triggerHandler('sortEnd', table);
            }, 1);
        },

        // sort multiple columns
        multisort: function (c) { /*jshint loopfunc:true */
            var tbodyIndex, sortTime, colMax, rows, tmp,
				table = c.table,
				sorter = [],
				dir = 0,
				textSorter = c.textSorter || '',
				sortList = c.sortList,
				sortLen = sortList.length,
				len = c.$tbodies.length;
            if (c.serverSideSorting || ts.isEmptyObject(c.cache)) {
                // empty table - fixes #206/#346
                return;
            }
            if (c.debug) { sortTime = new Date(); }
            // cache textSorter to optimize speed
            if (typeof textSorter === 'object') {
                colMax = c.columns;
                while (colMax--) {
                    tmp = ts.getColumnData(table, textSorter, colMax);
                    if (typeof tmp === 'function') {
                        sorter[colMax] = tmp;
                    }
                }
            }
            for (tbodyIndex = 0; tbodyIndex < len; tbodyIndex++) {
                colMax = c.cache[tbodyIndex].colMax;
                rows = c.cache[tbodyIndex].normalized;

                rows.sort(function (a, b) {
                    var sortIndex, num, col, order, sort, x, y;
                    // rows is undefined here in IE, so don't use it!
                    for (sortIndex = 0; sortIndex < sortLen; sortIndex++) {
                        col = sortList[sortIndex][0];
                        order = sortList[sortIndex][1];
                        // sort direction, true = asc, false = desc
                        dir = order === 0;

                        if (c.sortStable && a[col] === b[col] && sortLen === 1) {
                            return a[c.columns].order - b[c.columns].order;
                        }

                        // fallback to natural sort since it is more robust
                        num = /n/i.test(ts.getSortType(c.parsers, col));
                        if (num && c.strings[col]) {
                            // sort strings in numerical columns
                            if (typeof (ts.string[c.strings[col]]) === 'boolean') {
                                num = (dir ? 1 : -1) * (ts.string[c.strings[col]] ? -1 : 1);
                            } else {
                                num = (c.strings[col]) ? ts.string[c.strings[col]] || 0 : 0;
                            }
                            // fall back to built-in numeric sort
                            // var sort = $.tablesorter['sort' + s]( a[col], b[col], dir, colMax[col], table );
                            sort = c.numberSorter ? c.numberSorter(a[col], b[col], dir, colMax[col], table) :
								ts['sortNumeric' + (dir ? 'Asc' : 'Desc')](a[col], b[col], num, colMax[col], col, c);
                        } else {
                            // set a & b depending on sort direction
                            x = dir ? a : b;
                            y = dir ? b : a;
                            // text sort function
                            if (typeof textSorter === 'function') {
                                // custom OVERALL text sorter
                                sort = textSorter(x[col], y[col], dir, col, table);
                            } else if (typeof sorter[col] === 'function') {
                                // custom text sorter for a SPECIFIC COLUMN
                                sort = sorter[col](x[col], y[col], dir, col, table);
                            } else {
                                // fall back to natural sort
                                sort = ts['sortNatural' + (dir ? 'Asc' : 'Desc')](a[col], b[col], col, c);
                            }
                        }
                        if (sort) { return sort; }
                    }
                    return a[c.columns].order - b[c.columns].order;
                });
            }
            if (c.debug) {
                console.log('Applying sort ' + sortList.toString() + ts.benchmark(sortTime));
            }
        },

        resortComplete: function (c, callback) {
            if (c.table.isUpdating) {
                c.$table.triggerHandler('updateComplete', c.table);
            }
            if ($.isFunction(callback)) {
                callback(c.table);
            }
        },

        checkResort: function (c, resort, callback) {
            var sortList = $.isArray(resort) ? resort : c.sortList,
				// if no resort parameter is passed, fallback to config.resort (true by default)
				resrt = typeof resort === 'undefined' ? c.resort : resort;
            // don't try to resort if the table is still processing
            // this will catch spamming of the updateCell method
            if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) {
                if (sortList.length) {
                    ts.sortOn(c, sortList, function () {
                        ts.resortComplete(c, callback);
                    }, true);
                } else {
                    ts.sortReset(c, function () {
                        ts.resortComplete(c, callback);
                        ts.applyWidget(c.table, false);
                    });
                }
            } else {
                ts.resortComplete(c, callback);
                ts.applyWidget(c.table, false);
            }
        },

        sortOn: function (c, list, callback, init) {
            var table = c.table;
            c.$table.triggerHandler('sortStart', table);
            // update header count index
            ts.updateHeaderSortCount(c, list);
            // set css for headers
            ts.setHeadersCss(c);
            // fixes #346
            if (c.delayInit && ts.isEmptyObject(c.cache)) {
                ts.buildCache(c);
            }
            c.$table.triggerHandler('sortBegin', table);
            // sort the table and append it to the dom
            ts.multisort(c);
            ts.appendCache(c, init);
            c.$table.triggerHandler('sortBeforeEnd', table);
            c.$table.triggerHandler('sortEnd', table);
            ts.applyWidget(table);
            if ($.isFunction(callback)) {
                callback(table);
            }
        },

        sortReset: function (c, callback) {
            c.sortList = [];
            ts.setHeadersCss(c);
            ts.multisort(c);
            ts.appendCache(c);
            if ($.isFunction(callback)) {
                callback(c.table);
            }
        },

        getSortType: function (parsers, column) {
            return (parsers && parsers[column]) ? parsers[column].type || '' : '';
        },

        getOrder: function (val) {
            // look for 'd' in 'desc' order; return true
            return (/^d/i.test(val) || val === 1);
        },

        // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
        // this function will only accept strings, or you'll see 'TypeError: undefined is not a function'
        // I could add a = a.toString(); b = b.toString(); but it'll slow down the sort overall
        sortNatural: function (a, b) {
            if (a === b) { return 0; }
            var aNum, bNum, aFloat, bFloat, indx, max,
				regex = ts.regex;
            // first try and sort Hex codes
            if (regex.hex.test(b)) {
                aNum = parseInt((a || '').match(regex.hex), 16);
                bNum = parseInt((b || '').match(regex.hex), 16);
                if (aNum < bNum) { return -1; }
                if (aNum > bNum) { return 1; }
            }
            // chunk/tokenize
            aNum = (a || '').replace(regex.chunk, '\\0$1\\0').replace(regex.chunks, '').split('\\0');
            bNum = (b || '').replace(regex.chunk, '\\0$1\\0').replace(regex.chunks, '').split('\\0');
            max = Math.max(aNum.length, bNum.length);
            // natural sorting through split numeric strings and default strings
            for (indx = 0; indx < max; indx++) {
                // find floats not starting with '0', string or 0 if not defined
                aFloat = isNaN(aNum[indx]) ? aNum[indx] || 0 : parseFloat(aNum[indx]) || 0;
                bFloat = isNaN(bNum[indx]) ? bNum[indx] || 0 : parseFloat(bNum[indx]) || 0;
                // handle numeric vs string comparison - number < string - (Kyle Adams)
                if (isNaN(aFloat) !== isNaN(bFloat)) { return isNaN(aFloat) ? 1 : -1; }
                // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
                if (typeof aFloat !== typeof bFloat) {
                    aFloat += '';
                    bFloat += '';
                }
                if (aFloat < bFloat) { return -1; }
                if (aFloat > bFloat) { return 1; }
            }
            return 0;
        },

        sortNaturalAsc: function (a, b, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : -empty || -1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : empty || 1; }
            return ts.sortNatural(a, b);
        },

        sortNaturalDesc: function (a, b, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : empty || 1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : -empty || -1; }
            return ts.sortNatural(b, a);
        },

        // basic alphabetical sort
        sortText: function (a, b) {
            return a > b ? 1 : (a < b ? -1 : 0);
        },

        // return text string value by adding up ascii value
        // so the text is somewhat sorted when using a digital sort
        // this is NOT an alphanumeric sort
        getTextValue: function (val, num, max) {
            if (max) {
                // make sure the text value is greater than the max numerical value (max)
                var indx,
					len = val ? val.length : 0,
					n = max + num;
                for (indx = 0; indx < len; indx++) {
                    n += val.charCodeAt(indx);
                }
                return num * n;
            }
            return 0;
        },

        sortNumericAsc: function (a, b, num, max, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : -empty || -1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : empty || 1; }
            if (isNaN(a)) { a = ts.getTextValue(a, num, max); }
            if (isNaN(b)) { b = ts.getTextValue(b, num, max); }
            return a - b;
        },

        sortNumericDesc: function (a, b, num, max, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : empty || 1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : -empty || -1; }
            if (isNaN(a)) { a = ts.getTextValue(a, num, max); }
            if (isNaN(b)) { b = ts.getTextValue(b, num, max); }
            return b - a;
        },

        sortNumeric: function (a, b) {
            return a - b;
        },

        /*
		██ ██ ██ ██ █████▄ ▄████▄ ██████ ██████ ▄█████
		██ ██ ██ ██ ██  ██ ██ ▄▄▄ ██▄▄     ██   ▀█▄
		██ ██ ██ ██ ██  ██ ██ ▀██ ██▀▀     ██      ▀█▄
		███████▀ ██ █████▀ ▀████▀ ██████   ██   █████▀
		*/
        addWidget: function (widget) {
            if (widget.id && !ts.isEmptyObject(ts.getWidgetById(widget.id))) {
                console.warn('"' + widget.id + '" widget was loaded more than once!');
            }
            ts.widgets[ts.widgets.length] = widget;
        },

        hasWidget: function ($table, name) {
            $table = $($table);
            return $table.length && $table[0].config && $table[0].config.widgetInit[name] || false;
        },

        getWidgetById: function (name) {
            var indx, widget,
				len = ts.widgets.length;
            for (indx = 0; indx < len; indx++) {
                widget = ts.widgets[indx];
                if (widget && widget.id && widget.id.toLowerCase() === name.toLowerCase()) {
                    return widget;
                }
            }
        },

        applyWidgetOptions: function (table) {
            var indx, widget,
				c = table.config,
				len = c.widgets.length;
            if (len) {
                for (indx = 0; indx < len; indx++) {
                    widget = ts.getWidgetById(c.widgets[indx]);
                    if (widget && widget.options) {
                        c.widgetOptions = $.extend(true, {}, widget.options, c.widgetOptions);
                    }
                }
            }
        },

        addWidgetFromClass: function (table) {
            var len, indx,
				c = table.config,
				// look for widgets to apply from table class
				// don't match from 'ui-widget-content'; use \S instead of \w to include widgets
				// with dashes in the name, e.g. "widget-test-2" extracts out "test-2"
				regex = '^' + c.widgetClass.replace(ts.regex.templateName, '(\\S+)+') + '$',
				widgetClass = new RegExp(regex, 'g'),
				// split up table class (widget id's can include dashes) - stop using match
				// otherwise only one widget gets extracted, see #1109
				widgets = (table.className || '').split(ts.regex.spaces);
            if (widgets.length) {
                len = widgets.length;
                for (indx = 0; indx < len; indx++) {
                    if (widgets[indx].match(widgetClass)) {
                        c.widgets[c.widgets.length] = widgets[indx].replace(widgetClass, '$1');
                    }
                }
            }
        },

        applyWidgetId: function (table, id, init) {
            table = $(table)[0];
            var applied, time, name,
				c = table.config,
				wo = c.widgetOptions,
				widget = ts.getWidgetById(id);
            if (widget) {
                name = widget.id;
                applied = false;
                // add widget name to option list so it gets reapplied after sorting, filtering, etc
                if ($.inArray(name, c.widgets) < 0) {
                    c.widgets[c.widgets.length] = name;
                }
                if (c.debug) { time = new Date(); }

                if (init || !(c.widgetInit[name])) {
                    // set init flag first to prevent calling init more than once (e.g. pager)
                    c.widgetInit[name] = true;
                    if (table.hasInitialized) {
                        // don't reapply widget options on tablesorter init
                        ts.applyWidgetOptions(table);
                    }
                    if (typeof widget.init === 'function') {
                        applied = true;
                        if (c.debug) {
                            console[console.group ? 'group' : 'log']('Initializing ' + name + ' widget');
                        }
                        widget.init(table, widget, c, wo);
                    }
                }
                if (!init && typeof widget.format === 'function') {
                    applied = true;
                    if (c.debug) {
                        console[console.group ? 'group' : 'log']('Updating ' + name + ' widget');
                    }
                    widget.format(table, c, wo, false);
                }
                if (c.debug) {
                    if (applied) {
                        console.log('Completed ' + (init ? 'initializing ' : 'applying ') + name + ' widget' + ts.benchmark(time));
                        if (console.groupEnd) { console.groupEnd(); }
                    }
                }
            }
        },

        applyWidget: function (table, init, callback) {
            table = $(table)[0]; // in case this is called externally
            var indx, len, names, widget, time,
				c = table.config,
				widgets = [];
            // prevent numerous consecutive widget applications
            if (init !== false && table.hasInitialized && (table.isApplyingWidgets || table.isUpdating)) {
                return;
            }
            if (c.debug) { time = new Date(); }
            ts.addWidgetFromClass(table);
            // prevent "tablesorter-ready" from firing multiple times in a row
            clearTimeout(c.timerReady);
            if (c.widgets.length) {
                table.isApplyingWidgets = true;
                // ensure unique widget ids
                c.widgets = $.grep(c.widgets, function (val, index) {
                    return $.inArray(val, c.widgets) === index;
                });
                names = c.widgets || [];
                len = names.length;
                // build widget array & add priority as needed
                for (indx = 0; indx < len; indx++) {
                    widget = ts.getWidgetById(names[indx]);
                    if (widget && widget.id) {
                        // set priority to 10 if not defined
                        if (!widget.priority) { widget.priority = 10; }
                        widgets[indx] = widget;
                    } else if (c.debug) {
                        console.warn('"' + names[indx] + '" widget code does not exist!');
                    }
                }
                // sort widgets by priority
                widgets.sort(function (a, b) {
                    return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
                });
                // add/update selected widgets
                len = widgets.length;
                if (c.debug) {
                    console[console.group ? 'group' : 'log']('Start ' + (init ? 'initializing' : 'applying') + ' widgets');
                }
                for (indx = 0; indx < len; indx++) {
                    widget = widgets[indx];
                    if (widget && widget.id) {
                        ts.applyWidgetId(table, widget.id, init);
                    }
                }
                if (c.debug && console.groupEnd) { console.groupEnd(); }
                // callback executed on init only
                if (!init && typeof callback === 'function') {
                    callback(table);
                }
            }
            c.timerReady = setTimeout(function () {
                table.isApplyingWidgets = false;
                $.data(table, 'lastWidgetApplication', new Date());
                c.$table.triggerHandler('tablesorter-ready');
            }, 10);
            if (c.debug) {
                widget = c.widgets.length;
                console.log('Completed ' +
					(init === true ? 'initializing ' : 'applying ') + widget +
					' widget' + (widget !== 1 ? 's' : '') + ts.benchmark(time));
            }
        },

        removeWidget: function (table, name, refreshing) {
            table = $(table)[0];
            var index, widget, indx, len,
				c = table.config;
            // if name === true, add all widgets from $.tablesorter.widgets
            if (name === true) {
                name = [];
                len = ts.widgets.length;
                for (indx = 0; indx < len; indx++) {
                    widget = ts.widgets[indx];
                    if (widget && widget.id) {
                        name[name.length] = widget.id;
                    }
                }
            } else {
                // name can be either an array of widgets names,
                // or a space/comma separated list of widget names
                name = ($.isArray(name) ? name.join(',') : name || '').toLowerCase().split(/[\s,]+/);
            }
            len = name.length;
            for (index = 0; index < len; index++) {
                widget = ts.getWidgetById(name[index]);
                indx = $.inArray(name[index], c.widgets);
                // don't remove the widget from config.widget if refreshing
                if (indx >= 0 && refreshing !== true) {
                    c.widgets.splice(indx, 1);
                }
                if (widget && widget.remove) {
                    if (c.debug) {
                        console.log((refreshing ? 'Refreshing' : 'Removing') + ' "' + name[index] + '" widget');
                    }
                    widget.remove(table, c, c.widgetOptions, refreshing);
                    c.widgetInit[name[index]] = false;
                }
            }
        },

        refreshWidgets: function (table, doAll, dontapply) {
            table = $(table)[0]; // see issue #243
            var indx, widget,
				c = table.config,
				curWidgets = c.widgets,
				widgets = ts.widgets,
				len = widgets.length,
				list = [],
				callback = function (table) {
				    $(table).triggerHandler('refreshComplete');
				};
            // remove widgets not defined in config.widgets, unless doAll is true
            for (indx = 0; indx < len; indx++) {
                widget = widgets[indx];
                if (widget && widget.id && (doAll || $.inArray(widget.id, curWidgets) < 0)) {
                    list[list.length] = widget.id;
                }
            }
            ts.removeWidget(table, list.join(','), true);
            if (dontapply !== true) {
                // call widget init if
                ts.applyWidget(table, doAll || false, callback);
                if (doAll) {
                    // apply widget format
                    ts.applyWidget(table, false, callback);
                }
            } else {
                callback(table);
            }
        },

        /*
		██  ██ ██████ ██ ██     ██ ██████ ██ ██████ ▄█████
		██  ██   ██   ██ ██     ██   ██   ██ ██▄▄   ▀█▄
		██  ██   ██   ██ ██     ██   ██   ██ ██▀▀      ▀█▄
		▀████▀   ██   ██ ██████ ██   ██   ██ ██████ █████▀
		*/
        benchmark: function (diff) {
            return (' (' + (new Date().getTime() - diff.getTime()) + ' ms)');
        },
        // deprecated ts.log
        log: function () {
            console.log(arguments);
        },

        // $.isEmptyObject from jQuery v1.4
        isEmptyObject: function (obj) {
            /*jshint forin: false */
            for (var name in obj) {
                return false;
            }
            return true;
        },

        isValueInArray: function (column, arry) {
            var indx,
				len = arry && arry.length || 0;
            for (indx = 0; indx < len; indx++) {
                if (arry[indx][0] === column) {
                    return indx;
                }
            }
            return -1;
        },

        formatFloat: function (str, table) {
            if (typeof str !== 'string' || str === '') { return str; }
            // allow using formatFloat without a table; defaults to US number format
            var num,
				usFormat = table && table.config ? table.config.usNumberFormat !== false :
					typeof table !== 'undefined' ? table : true;
            if (usFormat) {
                // US Format - 1,234,567.89 -> 1234567.89
                str = str.replace(ts.regex.comma, '');
            } else {
                // German Format = 1.234.567,89 -> 1234567.89
                // French Format = 1 234 567,89 -> 1234567.89
                str = str.replace(ts.regex.digitNonUS, '').replace(ts.regex.comma, '.');
            }
            if (ts.regex.digitNegativeTest.test(str)) {
                // make (#) into a negative number -> (10) = -10
                str = str.replace(ts.regex.digitNegativeReplace, '-$1');
            }
            num = parseFloat(str);
            // return the text instead of zero
            return isNaN(num) ? $.trim(str) : num;
        },

        isDigit: function (str) {
            // replace all unwanted chars and match
            return isNaN(str) ?
				ts.regex.digitTest.test(str.toString().replace(ts.regex.digitReplace, '')) :
				str !== '';
        },

        // computeTableHeaderCellIndexes from:
        // http://www.javascripttoolbox.com/lib/table/examples.php
        // http://www.javascripttoolbox.com/temp/table_cellindex.html
        computeColumnIndex: function ($rows, c) {
            var i, j, k, l, cell, cells, rowIndex, rowSpan, colSpan, firstAvailCol,
				// total columns has been calculated, use it to set the matrixrow
				columns = c && c.columns || 0,
				matrix = [],
				matrixrow = new Array(columns);
            for (i = 0; i < $rows.length; i++) {
                cells = $rows[i].cells;
                for (j = 0; j < cells.length; j++) {
                    cell = cells[j];
                    rowIndex = cell.parentNode.rowIndex;
                    rowSpan = cell.rowSpan || 1;
                    colSpan = cell.colSpan || 1;
                    if (typeof matrix[rowIndex] === 'undefined') {
                        matrix[rowIndex] = [];
                    }
                    // Find first available column in the first row
                    for (k = 0; k < matrix[rowIndex].length + 1; k++) {
                        if (typeof matrix[rowIndex][k] === 'undefined') {
                            firstAvailCol = k;
                            break;
                        }
                    }
                    // jscs:disable disallowEmptyBlocks
                    if (columns && cell.cellIndex === firstAvailCol) {
                        // don't to anything
                    } else if (cell.setAttribute) {
                        // jscs:enable disallowEmptyBlocks
                        // add data-column (setAttribute = IE8+)
                        cell.setAttribute('data-column', firstAvailCol);
                    } else {
                        // remove once we drop support for IE7 - 1/12/2016
                        $(cell).attr('data-column', firstAvailCol);
                    }
                    for (k = rowIndex; k < rowIndex + rowSpan; k++) {
                        if (typeof matrix[k] === 'undefined') {
                            matrix[k] = [];
                        }
                        matrixrow = matrix[k];
                        for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
                            matrixrow[l] = 'x';
                        }
                    }
                }
            }
            return matrixrow.length;
        },

        // automatically add a colgroup with col elements set to a percentage width
        fixColumnWidth: function (table) {
            table = $(table)[0];
            var overallWidth, percent, $tbodies, len, index,
				c = table.config,
				$colgroup = c.$table.children('colgroup');
            // remove plugin-added colgroup, in case we need to refresh the widths
            if ($colgroup.length && $colgroup.hasClass(ts.css.colgroup)) {
                $colgroup.remove();
            }
            if (c.widthFixed && c.$table.children('colgroup').length === 0) {
                $colgroup = $('<colgroup class="' + ts.css.colgroup + '">');
                overallWidth = c.$table.width();
                // only add col for visible columns - fixes #371
                $tbodies = c.$tbodies.find('tr:first').children(':visible');
                len = $tbodies.length;
                for (index = 0; index < len; index++) {
                    percent = parseInt(($tbodies.eq(index).width() / overallWidth) * 1000, 10) / 10 + '%';
                    $colgroup.append($('<col>').css('width', percent));
                }
                c.$table.prepend($colgroup);
            }
        },

        // get sorter, string, empty, etc options for each column from
        // jQuery data, metadata, header option or header class name ('sorter-false')
        // priority = jQuery data > meta > headers option > header class name
        getData: function (header, configHeader, key) {
            var meta, cl4ss,
				val = '',
				$header = $(header);
            if (!$header.length) { return ''; }
            meta = $.metadata ? $header.metadata() : false;
            cl4ss = ' ' + ($header.attr('class') || '');
            if (typeof $header.data(key) !== 'undefined' ||
				typeof $header.data(key.toLowerCase()) !== 'undefined') {
                // 'data-lockedOrder' is assigned to 'lockedorder'; but 'data-locked-order' is assigned to 'lockedOrder'
                // 'data-sort-initial-order' is assigned to 'sortInitialOrder'
                val += $header.data(key) || $header.data(key.toLowerCase());
            } else if (meta && typeof meta[key] !== 'undefined') {
                val += meta[key];
            } else if (configHeader && typeof configHeader[key] !== 'undefined') {
                val += configHeader[key];
            } else if (cl4ss !== ' ' && cl4ss.match(' ' + key + '-')) {
                // include sorter class name 'sorter-text', etc; now works with 'sorter-my-custom-parser'
                val = cl4ss.match(new RegExp('\\s' + key + '-([\\w-]+)'))[1] || '';
            }
            return $.trim(val);
        },

        getColumnData: function (table, obj, indx, getCell, $headers) {
            if (typeof obj === 'undefined' || obj === null) { return; }
            table = $(table)[0];
            var $header, key,
				c = table.config,
				$cells = ($headers || c.$headers),
				// c.$headerIndexed is not defined initially
				$cell = c.$headerIndexed && c.$headerIndexed[indx] ||
					$cells.filter('[data-column="' + indx + '"]:last');
            if (typeof obj[indx] !== 'undefined') {
                return getCell ? obj[indx] : obj[$cells.index($cell)];
            }
            for (key in obj) {
                if (typeof key === 'string') {
                    $header = $cell
						// header cell with class/id
						.filter(key)
						// find elements within the header cell with cell/id
						.add($cell.find(key));
                    if ($header.length) {
                        return obj[key];
                    }
                }
            }
            return;
        },

        // *** Process table ***
        // add processing indicator
        isProcessing: function ($table, toggle, $headers) {
            $table = $($table);
            var c = $table[0].config,
				// default to all headers
				$header = $headers || $table.find('.' + ts.css.header);
            if (toggle) {
                // don't use sortList if custom $headers used
                if (typeof $headers !== 'undefined' && c.sortList.length > 0) {
                    // get headers from the sortList
                    $header = $header.filter(function () {
                        // get data-column from attr to keep compatibility with jQuery 1.2.6
                        return this.sortDisabled ?
							false :
							ts.isValueInArray(parseFloat($(this).attr('data-column')), c.sortList) >= 0;
                    });
                }
                $table.add($header).addClass(ts.css.processing + ' ' + c.cssProcessing);
            } else {
                $table.add($header).removeClass(ts.css.processing + ' ' + c.cssProcessing);
            }
        },

        // detach tbody but save the position
        // don't use tbody because there are portions that look for a tbody index (updateCell)
        processTbody: function (table, $tb, getIt) {
            table = $(table)[0];
            if (getIt) {
                table.isProcessing = true;
                $tb.before('<colgroup class="tablesorter-savemyplace"/>');
                return $.fn.detach ? $tb.detach() : $tb.remove();
            }
            var holdr = $(table).find('colgroup.tablesorter-savemyplace');
            $tb.insertAfter(holdr);
            holdr.remove();
            table.isProcessing = false;
        },

        clearTableBody: function (table) {
            $(table)[0].config.$tbodies.children().detach();
        },

        // used when replacing accented characters during sorting
        characterEquivalents: {
            'a': '\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5', // áàâãäąå
            'A': '\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5', // ÁÀ ÃÄĄÅ
            'c': '\u00e7\u0107\u010d', // çćč
            'C': '\u00c7\u0106\u010c', // ÇĆČ
            'e': '\u00e9\u00e8\u00ea\u00eb\u011b\u0119', // éèêëěę
            'E': '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÉÈÊËĚĘ
            'i': '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // íìİîïı
            'I': '\u00cd\u00cc\u0130\u00ce\u00cf', // ÍÌİÎÏ
            'o': '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöō
            'O': '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÓÒÔÕÖŌ
            'ss': '\u00df', // ß (s sharp)
            'SS': '\u1e9e', // ẞ (Capital sharp s)
            'u': '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů
            'U': '\u00da\u00d9\u00db\u00dc\u016e' // ÚÙÛÜŮ
        },

        replaceAccents: function (str) {
            var chr,
				acc = '[',
				eq = ts.characterEquivalents;
            if (!ts.characterRegex) {
                ts.characterRegexArray = {};
                for (chr in eq) {
                    if (typeof chr === 'string') {
                        acc += eq[chr];
                        ts.characterRegexArray[chr] = new RegExp('[' + eq[chr] + ']', 'g');
                    }
                }
                ts.characterRegex = new RegExp(acc + ']');
            }
            if (ts.characterRegex.test(str)) {
                for (chr in eq) {
                    if (typeof chr === 'string') {
                        str = str.replace(ts.characterRegexArray[chr], chr);
                    }
                }
            }
            return str;
        },

        // restore headers
        restoreHeaders: function (table) {
            var index, $cell,
				c = $(table)[0].config,
				$headers = c.$table.find(c.selectorHeaders),
				len = $headers.length;
            // don't use c.$headers here in case header cells were swapped
            for (index = 0; index < len; index++) {
                $cell = $headers.eq(index);
                // only restore header cells if it is wrapped
                // because this is also used by the updateAll method
                if ($cell.find('.' + ts.css.headerIn).length) {
                    $cell.html(c.headerContent[index]);
                }
            }
        },

        destroy: function (table, removeClasses, callback) {
            table = $(table)[0];
            if (!table.hasInitialized) { return; }
            // remove all widgets
            ts.removeWidget(table, true, false);
            var events,
				$t = $(table),
				c = table.config,
				debug = c.debug,
				$h = $t.find('thead:first'),
				$r = $h.find('tr.' + ts.css.headerRow).removeClass(ts.css.headerRow + ' ' + c.cssHeaderRow),
				$f = $t.find('tfoot:first > tr').children('th, td');
            if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) {
                // reapply uitheme classes, in case we want to maintain appearance
                $t.triggerHandler('applyWidgetId', ['uitheme']);
                $t.triggerHandler('applyWidgetId', ['zebra']);
            }
            // remove widget added rows, just in case
            $h.find('tr').not($r).remove();
            // disable tablesorter - not using .unbind( namespace ) because namespacing was
            // added in jQuery v1.4.3 - see http://api.jquery.com/event.namespace/
            events = 'sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton ' +
				'appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave ' +
				'keypress sortBegin sortEnd resetToLoadState '.split(' ')
				.join(c.namespace + ' ');
            $t
				.removeData('tablesorter')
				.unbind(events.replace(ts.regex.spaces, ' '));
            c.$headers
				.add($f)
				.removeClass([ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' '))
				.removeAttr('data-column')
				.removeAttr('aria-label')
				.attr('aria-disabled', 'true');
            $r
				.find(c.selectorSort)
				.unbind(('mousedown mouseup keypress '.split(' ').join(c.namespace + ' ')).replace(ts.regex.spaces, ' '));
            ts.restoreHeaders(table);
            $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
            // clear flag in case the plugin is initialized again
            table.hasInitialized = false;
            delete table.config.cache;
            if (typeof callback === 'function') {
                callback(table);
            }
            if (debug) {
                console.log('tablesorter has been removed');
            }
        }

    };

    $.fn.tablesorter = function (settings) {
        return this.each(function () {
            var table = this,
			// merge & extend config options
			c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods);
            // save initial settings
            c.originalSettings = settings;
            // create a table from data (build table widget)
            if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') {
                // return the table (in case the original target is the table's container)
                ts.buildTable(table, c);
            } else {
                ts.setup(table, c);
            }
        });
    };

    // set up debug logs
    if (!(window.console && window.console.log)) {
        // access $.tablesorter.logs for browsers that don't have a console...
        ts.logs = [];
        /*jshint -W020 */
        console = {};
        console.log = console.warn = console.error = console.table = function () {
            var arg = arguments.length > 1 ? arguments : arguments[0];
            ts.logs[ts.logs.length] = { date: Date.now(), log: arg };
        };
    }

    // add default parsers
    ts.addParser({
        id: 'no-parser',
        is: function () {
            return false;
        },
        format: function () {
            return '';
        },
        type: 'text'
    });

    ts.addParser({
        id: 'text',
        is: function () {
            return true;
        },
        format: function (str, table) {
            var c = table.config;
            if (str) {
                str = $.trim(c.ignoreCase ? str.toLocaleLowerCase() : str);
                str = c.sortLocaleCompare ? ts.replaceAccents(str) : str;
            }
            return str;
        },
        type: 'text'
    });

    ts.regex.nondigit = /[^\w,. \-()]/g;
    ts.addParser({
        id: 'digit',
        is: function (str) {
            return ts.isDigit(str);
        },
        format: function (str, table) {
            var num = ts.formatFloat((str || '').replace(ts.regex.nondigit, ''), table);
            return str && typeof num === 'number' ? num :
				str ? $.trim(str && table.config.ignoreCase ? str.toLocaleLowerCase() : str) : str;
        },
        type: 'numeric'
    });

    ts.regex.currencyReplace = /[+\-,. ]/g;
    ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/;
    ts.addParser({
        id: 'currency',
        is: function (str) {
            str = (str || '').replace(ts.regex.currencyReplace, '');
            // test for £$€¤¥¢
            return ts.regex.currencyTest.test(str);
        },
        format: function (str, table) {
            var num = ts.formatFloat((str || '').replace(ts.regex.nondigit, ''), table);
            return str && typeof num === 'number' ? num :
				str ? $.trim(str && table.config.ignoreCase ? str.toLocaleLowerCase() : str) : str;
        },
        type: 'numeric'
    });

    // too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme
    // now, this regex can be updated before initialization
    ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//;
    ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\/(www\.)?/;
    ts.addParser({
        id: 'url',
        is: function (str) {
            return ts.regex.urlProtocolTest.test(str);
        },
        format: function (str) {
            return str ? $.trim(str.replace(ts.regex.urlProtocolReplace, '')) : str;
        },
        type: 'text'
    });

    ts.regex.dash = /-/g;
    ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/;
    ts.addParser({
        id: 'isoDate',
        is: function (str) {
            return ts.regex.isoDate.test(str);
        },
        format: function (str, table) {
            var date = str ? new Date(str.replace(ts.regex.dash, '/')) : str;
            return date instanceof Date && isFinite(date) ? date.getTime() : str;
        },
        type: 'numeric'
    });

    ts.regex.percent = /%/g;
    ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/;
    ts.addParser({
        id: 'percent',
        is: function (str) {
            return ts.regex.percentTest.test(str) && str.length < 15;
        },
        format: function (str, table) {
            return str ? ts.formatFloat(str.replace(ts.regex.percent, ''), table) : str;
        },
        type: 'numeric'
    });

    // added image parser to core v2.17.9
    ts.addParser({
        id: 'image',
        is: function (str, table, node, $node) {
            return $node.find('img').length > 0;
        },
        format: function (str, table, cell) {
            return $(cell).find('img').attr(table.config.imgAttr || 'alt') || str;
        },
        parsed: true, // filter widget flag
        type: 'text'
    });

    ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser
    ts.regex.usLongDateTest1 = /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i;
    ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i;
    ts.addParser({
        id: 'usLongDate',
        is: function (str) {
            // two digit years are not allowed cross-browser
            // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
            return ts.regex.usLongDateTest1.test(str) || ts.regex.usLongDateTest2.test(str);
        },
        format: function (str, table) {
            var date = str ? new Date(str.replace(ts.regex.dateReplace, '$1 $2')) : str;
            return date instanceof Date && isFinite(date) ? date.getTime() : str;
        },
        type: 'numeric'
    });

    // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
    ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/;
    // escaped "-" because JSHint in Firefox was showing it as an error
    ts.regex.shortDateReplace = /[\-.,]/g;
    // XXY covers MDY & DMY formats
    ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/;
    ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/;
    ts.convertFormat = function (dateString, format) {
        dateString = (dateString || '')
			.replace(ts.regex.spaces, ' ')
			.replace(ts.regex.shortDateReplace, '/');
        if (format === 'mmddyyyy') {
            dateString = dateString.replace(ts.regex.shortDateXXY, '$3/$1/$2');
        } else if (format === 'ddmmyyyy') {
            dateString = dateString.replace(ts.regex.shortDateXXY, '$3/$2/$1');
        } else if (format === 'yyyymmdd') {
            dateString = dateString.replace(ts.regex.shortDateYMD, '$1/$2/$3');
        }
        var date = new Date(dateString);
        return date instanceof Date && isFinite(date) ? date.getTime() : '';
    };

    ts.addParser({
        id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd'
        is: function (str) {
            str = (str || '').replace(ts.regex.spaces, ' ').replace(ts.regex.shortDateReplace, '/');
            return ts.regex.shortDateTest.test(str);
        },
        format: function (str, table, cell, cellIndex) {
            if (str) {
                var c = table.config,
					$header = c.$headerIndexed[cellIndex],
					format = $header.length && $header.data('dateFormat') ||
						ts.getData($header, ts.getColumnData(table, c.headers, cellIndex), 'dateFormat') ||
						c.dateFormat;
                // save format because getData can be slow...
                if ($header.length) {
                    $header.data('dateFormat', format);
                }
                return ts.convertFormat(str, format) || str;
            }
            return str;
        },
        type: 'numeric'
    });

    // match 24 hour time & 12 hours time + am/pm - see http://regexr.com/3c3tk
    ts.regex.timeTest = /^(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)$|^((?:[01]\d|[2][0-4]):[0-5]\d)$/i;
    ts.regex.timeMatch = /(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)|((?:[01]\d|[2][0-4]):[0-5]\d)/i;
    ts.addParser({
        id: 'time',
        is: function (str) {
            return ts.regex.timeTest.test(str);
        },
        format: function (str, table) {
            // isolate time... ignore month, day and year
            var temp,
				timePart = (str || '').match(ts.regex.timeMatch),
				orig = new Date(str),
				// no time component? default to 00:00 by leaving it out, but only if str is defined
				time = str && (timePart !== null ? timePart[0] : '00:00 AM'),
				date = time ? new Date('2000/01/01 ' + time.replace(ts.regex.dateReplace, '$1 $2')) : time;
            if (date instanceof Date && isFinite(date)) {
                temp = orig instanceof Date && isFinite(orig) ? orig.getTime() : 0;
                // if original string was a valid date, add it to the decimal so the column sorts in some kind of order
                // luckily new Date() ignores the decimals
                return temp ? parseFloat(date.getTime() + '.' + orig.getTime()) : date.getTime();
            }
            return str;
        },
        type: 'numeric'
    });

    ts.addParser({
        id: 'metadata',
        is: function () {
            return false;
        },
        format: function (str, table, cell) {
            var c = table.config,
			p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
            return $(cell).metadata()[p];
        },
        type: 'numeric'
    });

    /*
		██████ ██████ █████▄ █████▄ ▄████▄
		  ▄█▀  ██▄▄   ██▄▄██ ██▄▄██ ██▄▄██
		▄█▀    ██▀▀   ██▀▀██ ██▀▀█  ██▀▀██
		██████ ██████ █████▀ ██  ██ ██  ██
		*/
    // add default widgets
    ts.addWidget({
        id: 'zebra',
        priority: 90,
        format: function (table, c, wo) {
            var $visibleRows, $row, count, isEven, tbodyIndex, rowIndex, len,
				child = new RegExp(c.cssChildRow, 'i'),
				$tbodies = c.$tbodies.add($(c.namespace + '_extra_table').children('tbody:not(.' + c.cssInfoBlock + ')'));
            for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                // loop through the visible rows
                count = 0;
                $visibleRows = $tbodies.eq(tbodyIndex).children('tr:visible').not(c.selectorRemove);
                len = $visibleRows.length;
                for (rowIndex = 0; rowIndex < len; rowIndex++) {
                    $row = $visibleRows.eq(rowIndex);
                    // style child rows the same way the parent row was styled
                    if (!child.test($row[0].className)) { count++; }
                    isEven = (count % 2 === 0);
                    $row
						.removeClass(wo.zebra[isEven ? 1 : 0])
						.addClass(wo.zebra[isEven ? 0 : 1]);
                }
            }
        },
        remove: function (table, c, wo, refreshing) {
            if (refreshing) { return; }
            var tbodyIndex, $tbody,
				$tbodies = c.$tbodies,
				toRemove = (wo.zebra || ['even', 'odd']).join(' ');
            for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
                $tbody.children().removeClass(toRemove);
                ts.processTbody(table, $tbody, false); // restore tbody
            }
        }
    });

})(jQuery);


/*!
* tablesorter (FORK) pager plugin
* updated 9/23/2016 (v2.27.7)
*/
/*jshint browser:true, jquery:true, unused:false */
; (function ($) {
    'use strict';
    /*jshint supernew:true */
    var ts = $.tablesorter;

    $.extend({
        tablesorterPager: new function () {

            this.defaults = {
                // target the pager markup
                container: null,

                // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}"
                // where {page} is replaced by the page number, {size} is replaced by the number of records to show,
                // {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds
                // the filterList to the url into an "fcol" array.
                // So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url
                // and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url
                ajaxUrl: null,

                // modify the url after all processing has been applied
                customAjaxUrl: function (table, url) { return url; },

                // ajax error callback from $.tablesorter.showError function
                // ajaxError: function( config, xhr, settings, exception ){ return exception; };
                // returning false will abort the error message
                ajaxError: null,

                // modify the $.ajax object to allow complete control over your ajax requests
                ajaxObject: {
                    dataType: 'json'
                },

                // set this to false if you want to block ajax loading on init
                processAjaxOnInit: true,

                // process ajax so that the following information is returned:
                // [ total_rows (number), rows (array of arrays), headers (array; optional) ]
                // example:
                // [
                //   100,  // total rows
                //   [
                //     [ "row1cell1", "row1cell2", ... "row1cellN" ],
                //     [ "row2cell1", "row2cell2", ... "row2cellN" ],
                //     ...
                //     [ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
                //   ],
                //   [ "header1", "header2", ... "headerN" ] // optional
                // ]
                ajaxProcessing: function (ajax) { return [0, [], null]; },

                // output default: '{page}/{totalPages}'
                // possible variables: {size}, {page}, {totalPages}, {filteredPages}, {startRow},
                // {endRow}, {filteredRows} and {totalRows}
                output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}'

                // apply disabled classname to the pager arrows when the rows at either extreme is visible
                updateArrows: true,

                // starting page of the pager (zero based index)
                page: 0,

                // reset pager after filtering; set to desired page #
                // set to false to not change page at filter start
                pageReset: 0,

                // Number of visible rows
                size: 10,

                // Number of options to include in the pager number selector
                maxOptionSize: 20,

                // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js)
                savePages: true,

                // defines custom storage key
                storageKey: 'tablesorter-pager',

                // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty
                // table row set to a height to compensate; default is false
                fixedHeight: false,

                // count child rows towards the set page size? (set true if it is a visible table row within the pager)
                // if true, child row(s) may not appear to be attached to its parent row, may be split across pages or
                // may distort the table if rowspan or cellspans are included.
                countChildRows: false,

                // remove rows from the table to speed up the sort of large tables.
                // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled.
                removeRows: false, // removing rows in larger tables speeds up the sort

                // css class names of pager arrows
                cssFirst: '.first', // go to first page arrow
                cssPrev: '.prev', // previous page arrow
                cssNext: '.next', // next page arrow
                cssLast: '.last', // go to last page arrow
                cssGoto: '.gotoPage', // go to page selector - select dropdown that sets the current page
                cssPageDisplay: '.pagedisplay', // location of where the "output" is displayed
                cssPageSize: '.pagesize', // page size selector - select dropdown that sets the "size" option
                cssErrorRow: 'tablesorter-errorRow', // error information row

                // class added to arrows when at the extremes (i.e. prev/first arrows are "disabled" when on the first page)
                cssDisabled: 'disabled', // Note there is no period "." in front of this class name

                // stuff not set by the user
                totalRows: 0,
                totalPages: 0,
                filteredRows: 0,
                filteredPages: 0,
                ajaxCounter: 0,
                currentFilters: [],
                startRow: 0,
                endRow: 0,
                $size: null,
                last: {}

            };

            var pagerEvents = 'filterInit filterStart filterEnd sortEnd disablePager enablePager destroyPager updateComplete ' +
			'pageSize pageSet pageAndSize pagerUpdate refreshComplete ',

			$this = this,

			// hide arrows at extremes
			pagerArrows = function (table, p, disable) {
			    var a = 'addClass',
				r = 'removeClass',
				d = p.cssDisabled,
				dis = !!disable,
				first = (dis || p.page === 0),
				tp = getTotalPages(table, p),
				last = (dis || (p.page === tp - 1) || tp === 0);
			    if (p.updateArrows) {
			        p.$container.find(p.cssFirst + ',' + p.cssPrev)[first ? a : r](d).attr('aria-disabled', first);
			        p.$container.find(p.cssNext + ',' + p.cssLast)[last ? a : r](d).attr('aria-disabled', last);
			    }
			},

			calcFilters = function (table, p) {
			    var normalized, indx, len,
				c = table.config,
				hasFilters = c.$table.hasClass('hasFilters');
			    if (hasFilters && !p.ajax) {
			        if (ts.isEmptyObject(c.cache)) {
			            // delayInit: true so nothing is in the cache
			            p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not(p.countChildRows ? '' : '.' + c.cssChildRow).length;
			        } else {
			            p.filteredRows = 0;
			            normalized = c.cache[0].normalized;
			            len = normalized.length;
			            for (indx = 0; indx < len; indx++) {
			                p.filteredRows += p.regexRows.test(normalized[indx][c.columns].$row[0].className) ? 0 : 1;
			            }
			        }
			    } else if (!hasFilters) {
			        p.filteredRows = p.totalRows;
			    }
			},

			updatePageDisplay = function (table, p, completed) {
			    if (p.initializing) { return; }
			    var s, t, $out, indx, len, options,
				c = table.config,
				namespace = c.namespace + 'pager',
				sz = parsePageSize(p, p.size, 'get'); // don't allow dividing by zero
			    if (sz === 'all') { sz = p.totalRows; }
			    if (p.countChildRows) { t[t.length] = c.cssChildRow; }
			    p.totalPages = Math.ceil(p.totalRows / sz); // needed for "pageSize" method
			    c.totalRows = p.totalRows;
			    parsePageNumber(table, p);
			    calcFilters(table, p);
			    c.filteredRows = p.filteredRows;
			    p.filteredPages = Math.ceil(p.filteredRows / sz) || 0;
			    if (getTotalPages(table, p) >= 0) {
			        t = (sz * p.page > p.filteredRows) && completed;
			        p.page = (t) ? p.pageReset || 0 : p.page;
			        p.startRow = (t) ? sz * p.page + 1 : (p.filteredRows === 0 ? 0 : sz * p.page + 1);
			        p.endRow = Math.min(p.filteredRows, p.totalRows, sz * (p.page + 1));
			        $out = p.$container.find(p.cssPageDisplay);

			        // Output param can be callback for custom rendering or string
			        if (typeof p.output === 'function') {
			            s = p.output(table, p);
			        } else {
			            // form the output string (can now get a new output string from the server)
			            s = (p.ajaxData && p.ajaxData.output ? p.ajaxData.output || p.output : p.output)
							// {page} = one-based index; {page+#} = zero based index +/- value
							.replace(/\{page([\-+]\d+)?\}/gi, function (m, n) {
							    return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0;
							})
							// {totalPages}, {extra}, {extra:0} (array) or {extra : key} (object)
							.replace(/\{\w+(\s*:\s*\w+)?\}/gi, function (m) {
							    var len, indx,
								str = m.replace(/[{}\s]/g, ''),
								extra = str.split(':'),
								data = p.ajaxData,
								// return zero for default page/row numbers
								deflt = /(rows?|pages?)$/i.test(str) ? 0 : '';
							    if (/(startRow|page)/.test(extra[0]) && extra[1] === 'input') {
							        len = ('' + (extra[0] === 'page' ? p.totalPages : p.totalRows)).length;
							        indx = extra[0] === 'page' ? p.page + 1 : p.startRow;
							        return '<input type="text" class="ts-' + extra[0] + '" style="max-width:' + len + 'em" value="' + indx + '"/>';
							    }
							    return extra.length > 1 && data && data[extra[0]] ? data[extra[0]][extra[1]] : p[str] || (data ? data[str] : deflt) || deflt;
							});
			        }
			        if (p.$goto.length) {
			            t = '';
			            options = buildPageSelect(table, p);
			            len = options.length;
			            for (indx = 0; indx < len; indx++) {
			                t += '<option value="' + options[indx] + '">' + options[indx] + '</option>';
			            }
			            // innerHTML doesn't work in IE9 - http://support2.microsoft.com/kb/276228
			            p.$goto.html(t).val(p.page + 1);
			        }
			        if ($out.length) {
			            $out[($out[0].nodeName === 'INPUT') ? 'val' : 'html'](s);
			            // rebind startRow/page inputs
			            $out.find('.ts-startRow, .ts-page').unbind('change' + namespace).bind('change' + namespace, function () {
			                var v = $(this).val(),
							pg = $(this).hasClass('ts-startRow') ? Math.floor(v / sz) + 1 : v;
			                c.$table.triggerHandler('pageSet' + namespace, [pg]);
			            });
			        }
			    }
			    pagerArrows(table, p);
			    fixHeight(table, p);
			    if (p.initialized && completed !== false) {
			        if (c.debug) {
			            console.log('Pager: Triggering pagerComplete');
			        }
			        c.$table.triggerHandler('pagerComplete', p);
			        // save pager info to storage
			        if (p.savePages && ts.storage) {
			            ts.storage(table, p.storageKey, {
			                page: p.page,
			                size: sz === p.totalRows ? 'all' : sz
			            });
			        }
			    }
			},

			buildPageSelect = function (table, p) {
			    // Filter the options page number link array if it's larger than 'maxOptionSize'
			    // as large page set links will slow the browser on large dom inserts
			    var i, central_focus_size, focus_option_pages, insert_index, option_length, focus_length,
				pg = getTotalPages(table, p) || 1,
				// make skip set size multiples of 5
				skip_set_size = Math.ceil((pg / p.maxOptionSize) / 5) * 5,
				large_collection = pg > p.maxOptionSize,
				current_page = p.page + 1,
				start_page = skip_set_size,
				end_page = pg - skip_set_size,
				option_pages = [1],
				// construct default options pages array
				option_pages_start_page = (large_collection) ? skip_set_size : 1;

			    for (i = option_pages_start_page; i <= pg;) {
			        option_pages[option_pages.length] = i;
			        i = i + (large_collection ? skip_set_size : 1);
			    }
			    option_pages[option_pages.length] = pg;
			    if (large_collection) {
			        focus_option_pages = [];
			        // don't allow central focus size to be > 5 on either side of current page
			        central_focus_size = Math.max(Math.floor(p.maxOptionSize / skip_set_size) - 1, 5);

			        start_page = current_page - central_focus_size;
			        if (start_page < 1) { start_page = 1; }
			        end_page = current_page + central_focus_size;
			        if (end_page > pg) { end_page = pg; }
			        // construct an array to get a focus set around the current page
			        for (i = start_page; i <= end_page ; i++) {
			            focus_option_pages[focus_option_pages.length] = i;
			        }

			        // keep unique values
			        option_pages = $.grep(option_pages, function (value, indx) {
			            return $.inArray(value, option_pages) === indx;
			        });

			        option_length = option_pages.length;
			        focus_length = focus_option_pages.length;

			        // make sure at all option_pages aren't replaced
			        if (option_length - focus_length > skip_set_size / 2 && option_length + focus_length > p.maxOptionSize) {
			            insert_index = Math.floor(option_length / 2) - Math.floor(focus_length / 2);
			            Array.prototype.splice.apply(option_pages, [insert_index, focus_length]);
			        }
			        option_pages = option_pages.concat(focus_option_pages);

			    }

			    // keep unique values again
			    option_pages = $.grep(option_pages, function (value, indx) {
			        return $.inArray(value, option_pages) === indx;
			    })
				.sort(function (a, b) { return a - b; });

			    return option_pages;
			},

			fixHeight = function (table, p) {
			    var d, h,
				c = table.config,
				$b = c.$tbodies.eq(0);
			    $b.find('tr.pagerSavedHeightSpacer').remove();
			    if (p.fixedHeight && !p.isDisabled) {
			        h = $.data(table, 'pagerSavedHeight');
			        if (h) {
			            d = h - $b.height();
			            if (d > 5 && $.data(table, 'pagerLastSize') === p.size &&
						$b.children('tr:visible').length < (p.size === 'all' ? p.totalRows : p.size)) {
			                $b.append('<tr class="pagerSavedHeightSpacer ' + c.selectorRemove.slice(1) + '" style="height:' + d + 'px;"></tr>');
			            }
			        }
			    }
			},

			changeHeight = function (table, p) {
			    var h,
				c = table.config,
				$b = c.$tbodies.eq(0);
			    $b.find('tr.pagerSavedHeightSpacer').remove();
			    if (!$b.children('tr:visible').length) {
			        $b.append('<tr class="pagerSavedHeightSpacer ' + c.selectorRemove.slice(1) + '"><td>&nbsp</td></tr>');
			    }
			    h = $b.children('tr').eq(0).height() * (p.size === 'all' ? p.totalRows : p.size);
			    $.data(table, 'pagerSavedHeight', h);
			    fixHeight(table, p);
			    $.data(table, 'pagerLastSize', p.size);
			},

			hideRows = function (table, p) {
			    if (!p.ajaxUrl) {
			        var i,
					lastIndex = 0,
					c = table.config,
					rows = c.$tbodies.eq(0).children('tr'),
					l = rows.length,
					sz = p.size === 'all' ? p.totalRows : p.size,
					s = (p.page * sz),
					e = s + sz,
					last = 0, // for cache indexing
					j = 0; // size counter
			        p.cacheIndex = [];
			        for (i = 0; i < l; i++) {
			            if (!p.regexFiltered.test(rows[i].className)) {
			                if (j === s && rows[i].className.match(c.cssChildRow)) {
			                    // hide child rows @ start of pager (if already visible)
			                    rows[i].style.display = 'none';
			                } else {
			                    rows[i].style.display = (j >= s && j < e) ? '' : 'none';
			                    if (last !== j && j >= s && j < e) {
			                        p.cacheIndex[p.cacheIndex.length] = i;
			                        last = j;
			                    }
			                    // don't count child rows
			                    j += rows[i].className.match(c.cssChildRow + '|' + c.selectorRemove.slice(1)) && !p.countChildRows ? 0 : 1;
			                    if (j === e && rows[i].style.display !== 'none' && rows[i].className.match(ts.css.cssHasChild)) {
			                        lastIndex = i;
			                    }
			                }
			            }
			        }
			        // add any attached child rows to last row of pager. Fixes part of issue #396
			        if (lastIndex > 0 && rows[lastIndex].className.match(ts.css.cssHasChild)) {
			            while (++lastIndex < l && rows[lastIndex].className.match(c.cssChildRow)) {
			                rows[lastIndex].style.display = '';
			            }
			        }
			    }
			},

			hideRowsSetup = function (table, p) {
			    p.size = parsePageSize(p, p.$size.val(), 'get');
			    p.$size.val(p.size);
			    $.data(table, 'pagerLastSize', p.size);
			    pagerArrows(table, p);
			    if (!p.removeRows) {
			        hideRows(table, p);
			        $(table).bind('sortEnd filterEnd '.split(' ').join(table.config.namespace + 'pager '), function () {
			            hideRows(table, p);
			        });
			    }
			},

			renderAjax = function (data, table, p, xhr, settings, exception) {
			    // process data
			    if (typeof p.ajaxProcessing === 'function') {

			        // in case nothing is returned by ajax, empty out the table; see #1032
			        // but do it before calling pager_ajaxProcessing because that function may add content
			        // directly to the table
			        table.config.$tbodies.eq(0).empty();

			        // ajaxProcessing result: [ total, rows, headers ]
			        var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len, sz,
					c = table.config,
					$table = c.$table,
					tds = '',
					result = p.ajaxProcessing(data, table, xhr) || [0, []],
					hl = $table.find('thead th').length;

			        // Clean up any previous error.
			        ts.showError(table);

			        if (exception) {
			            if (c.debug) {
			                console.error('Pager: >> Ajax Error', xhr, settings, exception);
			            }
			            ts.showError(table, xhr, settings, exception);
			            c.$tbodies.eq(0).children('tr').detach();
			            p.totalRows = 0;
			        } else {
			            // process ajax object
			            if (!$.isArray(result)) {
			                p.ajaxData = result;
			                c.totalRows = p.totalRows = result.total;
			                c.filteredRows = p.filteredRows = typeof result.filteredRows !== 'undefined' ? result.filteredRows : result.total;
			                th = result.headers;
			                d = result.rows || [];
			            } else {
			                // allow [ total, rows, headers ]  or [ rows, total, headers ]
			                t = isNaN(result[0]) && !isNaN(result[1]);
			                // ensure a zero returned row count doesn't fail the logical ||
			                rr_count = result[t ? 1 : 0];
			                p.totalRows = isNaN(rr_count) ? p.totalRows || 0 : rr_count;
			                // can't set filtered rows when returning an array
			                c.totalRows = c.filteredRows = p.filteredRows = p.totalRows;
			                // set row data to empty array if nothing found - see http://stackoverflow.com/q/30875583/145346
			                d = p.totalRows === 0 ? [] : result[t ? 0 : 1] || []; // row data
			                th = result[2]; // headers
			            }
			            l = d && d.length;
			            if (d instanceof jQuery) {
			                if (p.processAjaxOnInit) {
			                    // append jQuery object
			                    c.$tbodies.eq(0).empty();
			                    c.$tbodies.eq(0).append(d);
			                }
			            } else if (l) {
			                // build table from array
			                for (i = 0; i < l; i++) {
			                    tds += '<tr>';
			                    for (j = 0; j < d[i].length; j++) {
			                        // build tbody cells; watch for data containing HTML markup - see #434
			                        tds += /^\s*<td/.test(d[i][j]) ? $.trim(d[i][j]) : '<td>' + d[i][j] + '</td>';
			                    }
			                    tds += '</tr>';
			                }
			                // add rows to first tbody
			                if (p.processAjaxOnInit) {
			                    c.$tbodies.eq(0).html(tds);
			                }
			            }
			            p.processAjaxOnInit = true;
			            // only add new header text if the length matches
			            if (th && th.length === hl) {
			                hsh = $table.hasClass('hasStickyHeaders');
			                $sh = hsh ? c.widgetOptions.$sticky.children('thead:first').children('tr').children() : '';
			                $f = $table.find('tfoot tr:first').children();
			                // don't change td headers (may contain pager)
			                $headers = c.$headers.filter('th ');
			                len = $headers.length;
			                for (j = 0; j < len; j++) {
			                    $h = $headers.eq(j);
			                    // add new test within the first span it finds, or just in the header
			                    if ($h.find('.' + ts.css.icon).length) {
			                        icon = $h.find('.' + ts.css.icon).clone(true);
			                        $h.find('.tablesorter-header-inner').html(th[j]).append(icon);
			                        if (hsh && $sh.length) {
			                            icon = $sh.eq(j).find('.' + ts.css.icon).clone(true);
			                            $sh.eq(j).find('.tablesorter-header-inner').html(th[j]).append(icon);
			                        }
			                    } else {
			                        $h.find('.tablesorter-header-inner').html(th[j]);
			                        if (hsh && $sh.length) {
			                            $sh.eq(j).find('.tablesorter-header-inner').html(th[j]);
			                        }
			                    }
			                    $f.eq(j).html(th[j]);
			                }
			            }
			        }
			        if (c.showProcessing) {
			            ts.isProcessing(table); // remove loading icon
			        }
			        sz = parsePageSize(p, p.size, 'get');
			        // make sure last pager settings are saved, prevents multiple server side calls with
			        // the same parameters
			        p.totalPages = sz === 'all' ? 1 : Math.ceil(p.totalRows / sz);
			        p.last.totalRows = p.totalRows;
			        p.last.currentFilters = p.currentFilters;
			        p.last.sortList = (c.sortList || []).join(',');
			        updatePageDisplay(table, p, false);
			        // tablesorter core updateCache (not pager)
			        ts.updateCache(c, function () {
			            if (p.initialized) {
			                // apply widgets after table has rendered & after a delay to prevent
			                // multiple applyWidget blocking code from blocking this trigger
			                setTimeout(function () {
			                    if (c.debug) {
			                        console.log('Pager: Triggering pagerChange');
			                    }
			                    $table.triggerHandler('pagerChange', p);
			                    ts.applyWidget(table);
			                    updatePageDisplay(table, p, true);
			                }, 0);
			            }
			        });

			    }
			    if (!p.initialized) {
			        pagerInitialized(table, p);
			    }
			},

			getAjax = function (table, p) {
			    var url = getAjaxUrl(table, p),
				$doc = $(document),
				counter,
				c = table.config,
				namespace = c.namespace + 'pager';
			    if (url !== '') {
			        if (c.showProcessing) {
			            ts.isProcessing(table, true); // show loading icon
			        }
			        $doc.bind('ajaxError' + namespace, function (e, xhr, settings, exception) {
			            renderAjax(null, table, p, xhr, settings, exception);
			            $doc.unbind('ajaxError' + namespace);
			        });

			        counter = ++p.ajaxCounter;

			        p.last.ajaxUrl = url; // remember processed url
			        p.ajaxObject.url = url; // from the ajaxUrl option and modified by customAjaxUrl
			        p.ajaxObject.success = function (data, status, jqxhr) {
			            // Refuse to process old ajax commands that were overwritten by new ones - see #443
			            if (counter < p.ajaxCounter) {
			                return;
			            }
			            renderAjax(data, table, p, jqxhr);
			            $doc.unbind('ajaxError' + namespace);
			            if (typeof p.oldAjaxSuccess === 'function') {
			                p.oldAjaxSuccess(data);
			            }
			        };
			        if (c.debug) {
			            console.log('Pager: Ajax initialized', p.ajaxObject);
			        }
			        $.ajax(p.ajaxObject);
			    }
			},

			getAjaxUrl = function (table, p) {
			    var indx, len,
				c = table.config,
				url = (p.ajaxUrl) ? p.ajaxUrl
				// allow using "{page+1}" in the url string to switch to a non-zero based index
				.replace(/\{page([\-+]\d+)?\}/, function (s, n) { return p.page + (n ? parseInt(n, 10) : 0); })
				// this will pass "all" to server when size is set to "all"
				.replace(/\{size\}/g, p.size) : '',
				sortList = c.sortList,
				filterList = p.currentFilters || $(table).data('lastSearch') || [],
				sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/),
				filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/),
				arry = [];
			    if (sortCol) {
			        sortCol = sortCol[1];
			        len = sortList.length;
			        for (indx = 0; indx < len; indx++) {
			            arry[arry.length] = sortCol + '[' + sortList[indx][0] + ']=' + sortList[indx][1];
			        }
			        // if the arry is empty, just add the col parameter... "&{sortList:col}" becomes "&col"
			        url = url.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : sortCol);
			        arry = [];
			    }
			    if (filterCol) {
			        filterCol = filterCol[1];
			        len = filterList.length;
			        for (indx = 0; indx < len; indx++) {
			            if (filterList[indx]) {
			                arry[arry.length] = filterCol + '[' + indx + ']=' + encodeURIComponent(filterList[indx]);
			            }
			        }
			        // if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol"
			        url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol);
			        p.currentFilters = filterList;
			    }
			    if (typeof p.customAjaxUrl === 'function') {
			        url = p.customAjaxUrl(table, url);
			    }
			    if (c.debug) {
			        console.log('Pager: Ajax url = ' + url);
			    }
			    return url;
			},

			renderTable = function (table, rows, p) {
			    var $tb, index, count, added,
				$t = $(table),
				c = table.config,
				f = c.$table.hasClass('hasFilters'),
				l = rows && rows.length || 0, // rows may be undefined
				e = p.size === 'all' ? p.totalRows : p.size,
				s = (p.page * e);
			    if (l < 1) {
			        if (c.debug) {
			            console.warn('Pager: >> No rows for pager to render');
			        }
			        // empty table, abort!
			        return;
			    }
			    if (p.page >= p.totalPages) {
			        // lets not render the table more than once
			        moveToLastPage(table, p);
			    }
			    p.cacheIndex = [];
			    p.isDisabled = false; // needed because sorting will change the page and re-enable the pager
			    if (p.initialized) {
			        if (c.debug) {
			            console.log('Pager: Triggering pagerChange');
			        }
			        $t.triggerHandler('pagerChange', p);
			    }
			    if (!p.removeRows) {
			        hideRows(table, p);
			    } else {
			        ts.clearTableBody(table);
			        $tb = ts.processTbody(table, c.$tbodies.eq(0), true);
			        // not filtered, start from the calculated starting point (s)
			        // if filtered, start from zero
			        index = f ? 0 : s;
			        count = f ? 0 : s;
			        added = 0;
			        while (added < e && index < rows.length) {
			            if (!f || !p.regexFiltered.test(rows[index][0].className)) {
			                count++;
			                if (count > s && added <= e) {
			                    added++;
			                    p.cacheIndex[p.cacheIndex.length] = index;
			                    $tb.append(rows[index]);
			                }
			            }
			            index++;
			        }
			        ts.processTbody(table, $tb, false);
			    }
			    updatePageDisplay(table, p);
			    if (table.isUpdating) {
			        if (c.debug) {
			            console.log('Pager: Triggering updateComplete');
			        }
			        $t.triggerHandler('updateComplete', [table, true]);
			    }
			},

			showAllRows = function (table, p) {
			    var index, $controls, len;
			    if (p.ajax) {
			        pagerArrows(table, p, true);
			    } else {
			        $.data(table, 'pagerLastPage', p.page);
			        $.data(table, 'pagerLastSize', p.size);
			        p.page = 0;
			        p.size = 'all';
			        p.totalPages = 1;
			        $(table)
					.addClass('pagerDisabled')
					.removeAttr('aria-describedby')
					.find('tr.pagerSavedHeightSpacer').remove();
			        renderTable(table, table.config.rowsCopy, p);
			        p.isDisabled = true;
			        ts.applyWidget(table);
			        if (table.config.debug) {
			            console.log('Pager: Disabled');
			        }
			    }
			    // disable size selector
			    $controls = p.$size
				.add(p.$goto)
				.add(p.$container.find('.ts-startRow, .ts-page'));
			    len = $controls.length;
			    for (index = 0; index < len; index++) {
			        $controls.eq(index)
					.attr('aria-disabled', 'true')
					.addClass(p.cssDisabled)[0].disabled = true;
			    }
			},

			// updateCache if delayInit: true
			updateCache = function (table) {
			    var c = table.config,
				p = c.pager;
			    // tablesorter core updateCache (not pager)
			    ts.updateCache(c, function () {
			        var i,
					rows = [],
					n = table.config.cache[0].normalized;
			        p.totalRows = n.length;
			        for (i = 0; i < p.totalRows; i++) {
			            rows[rows.length] = n[i][c.columns].$row;
			        }
			        c.rowsCopy = rows;
			        moveToPage(table, p, true);
			    });
			},

			moveToPage = function (table, p, pageMoved) {
			    if (p.isDisabled) { return; }
			    var tmp,
				c = table.config,
				$t = $(table),
				l = p.last;
			    if (pageMoved !== false && p.initialized && ts.isEmptyObject(c.cache)) {
			        return updateCache(table);
			    }
			    // abort page move if the table has filters and has not been initialized
			    if (p.ajax && ts.hasWidget(table, 'filter') && !c.widgetOptions.filter_initialized) { return; }
			    parsePageNumber(table, p);
			    calcFilters(table, p);
			    // fixes issue where one currentFilter is [] and the other is ['','',''],
			    // making the next if comparison think the filters are different (joined by commas). Fixes #202.
			    l.currentFilters = (l.currentFilters || []).join('') === '' ? [] : l.currentFilters;
			    p.currentFilters = (p.currentFilters || []).join('') === '' ? [] : p.currentFilters;
			    // don't allow rendering multiple times on the same page/size/totalRows/filters/sorts
			    if (l.page === p.page && l.size === p.size && l.totalRows === p.totalRows &&
				(l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') &&
			        // check for ajax url changes see #730
				(l.ajaxUrl || '') === (p.ajaxObject.url || '') &&
			        // & ajax url option changes (dynamically add/remove/rename sort & filter parameters)
				(l.optAjaxUrl || '') === (p.ajaxUrl || '') &&
				l.sortList === (c.sortList || []).join(',')) { return; }
			    if (c.debug) {
			        console.log('Pager: Changing to page ' + p.page);
			    }
			    p.last = {
			        page: p.page,
			        size: p.size,
			        // fixes #408; modify sortList otherwise it auto-updates
			        sortList: (c.sortList || []).join(','),
			        totalRows: p.totalRows,
			        currentFilters: p.currentFilters || [],
			        ajaxUrl: p.ajaxObject.url || '',
			        optAjaxUrl: p.ajaxUrl || ''
			    };
			    if (p.ajax) {
			        if (!p.processAjaxOnInit && !ts.isEmptyObject(p.initialRows)) {
			            p.processAjaxOnInit = true;
			            tmp = p.initialRows;
			            p.totalRows = typeof tmp.total !== 'undefined' ? tmp.total :
						(c.debug ? console.error('Pager: no initial total page set!') || 0 : 0);
			            p.filteredRows = typeof tmp.filtered !== 'undefined' ? tmp.filtered :
						(c.debug ? console.error('Pager: no initial filtered page set!') || 0 : 0);
			            pagerInitialized(table, p);
			        } else {
			            getAjax(table, p);
			        }
			    } else if (!p.ajax) {
			        renderTable(table, c.rowsCopy, p);
			    }
			    $.data(table, 'pagerLastPage', p.page);
			    if (p.initialized && pageMoved !== false) {
			        if (c.debug) {
			            console.log('Pager: Triggering pageMoved');
			        }
			        $t.triggerHandler('pageMoved', p);
			        ts.applyWidget(table);
			        if (table.isUpdating) {
			            if (c.debug) {
			                console.log('Pager: Triggering updateComplete');
			            }
			            $t.triggerHandler('updateComplete', [table, true]);
			        }
			    }
			},

			getTotalPages = function (table, p) {
			    return ts.hasWidget(table, 'filter') ? Math.min(p.totalPages, p.filteredPages) : p.totalPages;
			},

			// set to either set or get value
			parsePageSize = function (p, size, mode) {
			    var s = parseInt(size, 10) || p.size || p.settings.size || 10;
			    return p.initialized && (/all/i.test(size) || s === p.totalRows) ?
				// "get" to get `p.size` or "set" to set `p.$size.val()`
				'all' : (mode === 'get' ? s : p.size);
			},

			parsePageNumber = function (table, p) {
			    var min = getTotalPages(table, p) - 1;
			    p.page = parseInt(p.page, 10);
			    if (p.page < 0 || isNaN(p.page)) { p.page = 0; }
			    if (p.page > min && min >= 0) { p.page = min; }
			    return p.page;
			},

			setPageSize = function (table, size, p) {
			    p.size = parsePageSize(p, size, 'get');
			    p.$size.val(parsePageSize(p, p.size, 'set'));
			    $.data(table, 'pagerLastPage', parsePageNumber(table, p));
			    $.data(table, 'pagerLastSize', p.size);
			    p.totalPages = p.size === 'all' ? 1 : Math.ceil(p.totalRows / p.size);
			    p.filteredPages = p.size === 'all' ? 1 : Math.ceil(p.filteredRows / p.size);
			    moveToPage(table, p);
			},

			moveToFirstPage = function (table, p) {
			    p.page = 0;
			    moveToPage(table, p);
			},

			moveToLastPage = function (table, p) {
			    p.page = getTotalPages(table, p) - 1;
			    moveToPage(table, p);
			},

			moveToNextPage = function (table, p) {
			    p.page++;
			    var last = getTotalPages(table, p) - 1;
			    if (p.page >= last) {
			        p.page = last;
			    }
			    moveToPage(table, p);
			},

			moveToPrevPage = function (table, p) {
			    p.page--;
			    if (p.page <= 0) {
			        p.page = 0;
			    }
			    moveToPage(table, p);
			},

			pagerInitialized = function (table, p) {
			    p.initialized = true;
			    p.initializing = false;
			    if (table.config.debug) {
			        console.log('Pager: Triggering pagerInitialized');
			    }
			    $(table).triggerHandler('pagerInitialized', p);
			    ts.applyWidget(table);
			    updatePageDisplay(table, p);
			},

			destroyPager = function (table, p) {
			    var c = table.config,
				namespace = c.namespace + 'pager',
				ctrls = [p.cssFirst, p.cssPrev, p.cssNext, p.cssLast, p.cssGoto, p.cssPageSize].join(',');
			    showAllRows(table, p);
			    p.$container
				// hide pager controls
				.hide()
				// unbind
				.find(ctrls)
				.unbind(namespace);
			    c.appender = null; // remove pager appender function
			    c.$table.unbind(namespace);
			    if (ts.storage) {
			        ts.storage(table, p.storageKey, '');
			    }
			    delete c.pager;
			    delete c.rowsCopy;
			},

			enablePager = function (table, p, triggered) {
			    var info, size, $el,
				c = table.config;
			    p.$size.add(p.$goto).add(p.$container.find('.ts-startRow, .ts-page'))
				.removeClass(p.cssDisabled)
				.removeAttr('disabled')
				.attr('aria-disabled', 'false');
			    p.isDisabled = false;
			    p.page = $.data(table, 'pagerLastPage') || p.page || 0;
			    size = p.$size.find('option[selected]').val();
			    p.size = $.data(table, 'pagerLastSize') || parsePageSize(p, size, 'get');
			    p.$size.val(p.size); // set page size
			    p.totalPages = p.size === 'all' ? 1 : Math.ceil(getTotalPages(table, p) / p.size);
			    // if table id exists, include page display with aria info
			    if (table.id && !c.$table.attr('aria-describedby')) {
			        $el = p.$container.find(p.cssPageDisplay);
			        info = $el.attr('id');
			        if (!info) {
			            // only add pageDisplay id if it doesn't exist - see #1288
			            info = table.id + '_pager_info';
			            $el.attr('id', info);
			        }
			        c.$table.attr('aria-describedby', info);
			    }
			    changeHeight(table, p);
			    if (triggered) {
			        // tablesorter core update table
			        ts.update(c);
			        setPageSize(table, p.size, p);
			        hideRowsSetup(table, p);
			        if (c.debug) {
			            console.log('Pager: Enabled');
			        }
			    }
			};

            $this.appender = function (table, rows) {
                var c = table.config,
				p = c.pager;
                if (!p.ajax) {
                    c.rowsCopy = rows;
                    p.totalRows = p.countChildRows ? c.$tbodies.eq(0).children('tr').length : rows.length;
                    p.size = $.data(table, 'pagerLastSize') || p.size || p.settings.size || 10;
                    p.totalPages = p.size === 'all' ? 1 : Math.ceil(p.totalRows / p.size);
                    renderTable(table, rows, p);
                    // update display here in case all rows are removed
                    updatePageDisplay(table, p, false);
                }
            };

            $this.construct = function (settings) {
                return this.each(function () {
                    // check if tablesorter has initialized
                    if (!(this.config && this.hasInitialized)) { return; }
                    var t, ctrls, fxn, size,
					table = this,
					c = table.config,
					wo = c.widgetOptions,
					p = c.pager = $.extend(true, {}, $.tablesorterPager.defaults, settings),
					$t = c.$table,
					namespace = c.namespace + 'pager',
					// added in case the pager is reinitialized after being destroyed.
					pager = p.$container = $(p.container).addClass('tablesorter-pager').show();
                    // save a copy of the original settings
                    p.settings = $.extend(true, {}, $.tablesorterPager.defaults, settings);
                    if (c.debug) {
                        console.log('Pager: Initializing');
                    }
                    p.oldAjaxSuccess = p.oldAjaxSuccess || p.ajaxObject.success;
                    c.appender = $this.appender;
                    p.initializing = true;
                    if (p.savePages && ts.storage) {
                        t = ts.storage(table, p.storageKey) || {}; // fixes #387
                        p.page = isNaN(t.page) ? p.page : t.page;
                        p.size = t.size === 'all' ? t.size : (isNaN(t.size) ? p.size : t.size) || p.setSize || 10;
                        $.data(table, 'pagerLastSize', p.size);
                        pager.find(p.cssPageSize).val(p.size);
                    }
                    // skipped rows
                    p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.slice(1) + '|' + c.cssChildRow + ')');
                    p.regexFiltered = new RegExp(wo.filter_filteredRow || 'filtered');

                    $t
					// .unbind( namespace ) adding in jQuery 1.4.3 ( I think )
					.unbind(pagerEvents.split(' ').join(namespace + ' ').replace(/\s+/g, ' '))
					.bind('filterInit filterStart '.split(' ').join(namespace + ' '), function (e, filters) {
					    p.currentFilters = $.isArray(filters) ? filters : c.$table.data('lastSearch');
					    // don't change page if filters are the same (pager updating, etc)
					    if (e.type === 'filterStart' && p.pageReset !== false && (c.lastCombinedFilter || '') !== (p.currentFilters || []).join('')) {
					        p.page = p.pageReset; // fixes #456 & #565
					    }
					})
					// update pager after filter widget completes
					.bind('filterEnd sortEnd '.split(' ').join(namespace + ' '), function () {
					    p.currentFilters = c.$table.data('lastSearch');
					    if (p.initialized || p.initializing) {
					        if (c.delayInit && c.rowsCopy && c.rowsCopy.length === 0) {
					            // make sure we have a copy of all table rows once the cache has been built
					            updateCache(table);
					        }
					        updatePageDisplay(table, p, false);
					        moveToPage(table, p, false);
					        ts.applyWidget(table);
					    }
					})
					.bind('disablePager' + namespace, function (e) {
					    e.stopPropagation();
					    showAllRows(table, p);
					})
					.bind('enablePager' + namespace, function (e) {
					    e.stopPropagation();
					    enablePager(table, p, true);
					})
					.bind('destroyPager' + namespace, function (e) {
					    e.stopPropagation();
					    destroyPager(table, p);
					})
					.bind('updateComplete' + namespace, function (e, table, triggered) {
					    e.stopPropagation();
					    // table can be unintentionally undefined in tablesorter v2.17.7 and earlier
					    // don't recalculate total rows/pages if using ajax
					    if (!table || triggered || p.ajax) { return; }
					    var $rows = c.$tbodies.eq(0).children('tr').not(c.selectorRemove);
					    p.totalRows = $rows.length - (p.countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length);
					    p.totalPages = p.size === 'all' ? 1 : Math.ceil(p.totalRows / p.size);
					    if ($rows.length && c.rowsCopy && c.rowsCopy.length === 0) {
					        // make a copy of all table rows once the cache has been built
					        updateCache(table);
					    }
					    if (p.page >= p.totalPages) {
					        moveToLastPage(table, p);
					    }
					    hideRows(table, p);
					    changeHeight(table, p);
					    updatePageDisplay(table, p, true);
					})
					.bind('pageSize refreshComplete '.split(' ').join(namespace + ' '), function (e, size) {
					    e.stopPropagation();
					    setPageSize(table, parsePageSize(p, size, 'get'), p);
					    hideRows(table, p);
					    updatePageDisplay(table, p, false);
					})
					.bind('pageSet pagerUpdate '.split(' ').join(namespace + ' '), function (e, num) {
					    e.stopPropagation();
					    // force pager refresh
					    if (e.type === 'pagerUpdate') {
					        num = typeof num === 'undefined' ? p.page + 1 : num;
					        p.last.page = true;
					    }
					    p.page = (parseInt(num, 10) || 1) - 1;
					    moveToPage(table, p, true);
					    updatePageDisplay(table, p, false);
					})
					.bind('pageAndSize' + namespace, function (e, page, size) {
					    e.stopPropagation();
					    p.page = (parseInt(page, 10) || 1) - 1;
					    setPageSize(table, parsePageSize(p, size, 'get'), p);
					    moveToPage(table, p, true);
					    hideRows(table, p);
					    updatePageDisplay(table, p, false);
					});

                    // clicked controls
                    ctrls = [p.cssFirst, p.cssPrev, p.cssNext, p.cssLast];
                    fxn = [moveToFirstPage, moveToPrevPage, moveToNextPage, moveToLastPage];
                    if (c.debug && !pager.length) {
                        console.warn('Pager: >> Container not found');
                    }
                    pager.find(ctrls.join(','))
					.attr('tabindex', 0)
					.unbind('click' + namespace)
					.bind('click' + namespace, function (e) {
					    e.stopPropagation();
					    var i, $t = $(this), l = ctrls.length;
					    if (!$t.hasClass(p.cssDisabled)) {
					        for (i = 0; i < l; i++) {
					            if ($t.is(ctrls[i])) {
					                fxn[i](table, p);
					                break;
					            }
					        }
					    }
					});

                    // goto selector
                    p.$goto = pager.find(p.cssGoto);
                    if (p.$goto.length) {
                        p.$goto
						.unbind('change' + namespace)
						.bind('change' + namespace, function () {
						    p.page = $(this).val() - 1;
						    moveToPage(table, p, true);
						    updatePageDisplay(table, p, false);
						});
                    } else if (c.debug) {
                        console.warn('Pager: >> Goto selector not found');
                    }
                    // page size selector
                    p.$size = pager.find(p.cssPageSize);
                    if (p.$size.length) {
                        // setting an option as selected appears to cause issues with initial page size
                        p.$size.find('option').removeAttr('selected');
                        p.$size.unbind('change' + namespace).bind('change' + namespace, function () {
                            if (!$(this).hasClass(p.cssDisabled)) {
                                var size = $(this).val();
                                p.$size.val(size); // in case there are more than one pagers
                                setPageSize(table, size, p);
                                changeHeight(table, p);
                            }
                            return false;
                        });
                    } else if (c.debug) {
                        console.warn('Pager: >> Size selector not found');
                    }

                    // clear initialized flag
                    p.initialized = false;
                    // before initialization event
                    $t.triggerHandler('pagerBeforeInitialized', p);

                    enablePager(table, p, false);
                    if (typeof p.ajaxUrl === 'string') {
                        // ajax pager; interact with database
                        p.ajax = true;
                        // When filtering with ajax, allow only custom filtering function, disable default
                        // filtering since it will be done server side.
                        c.widgetOptions.filter_serversideFiltering = true;
                        c.serverSideSorting = true;
                        moveToPage(table, p);
                    } else {
                        p.ajax = false;
                        // Regular pager; all rows stored in memory
                        ts.appendCache(c, true); // true = don't apply widgets
                        hideRowsSetup(table, p);
                    }

                    // pager initialized
                    if (!p.ajax && !p.initialized) {
                        p.initializing = false;
                        p.initialized = true;
                        moveToPage(table, p);
                        if (c.debug) {
                            console.log('Pager: Triggering pagerInitialized');
                        }
                        c.$table.triggerHandler('pagerInitialized', p);
                        if (!(c.widgetOptions.filter_initialized && ts.hasWidget(table, 'filter'))) {
                            updatePageDisplay(table, p, false);
                        }
                    }

                    // make the hasWidget function think that the pager widget is being used
                    c.widgetInit.pager = true;
                });
            };

        }()
    });

    // see #486
    ts.showError = function (table, xhr, settings, exception) {
        var $row,
			$table = $(table),
			c = $table[0].config,
			wo = c && c.widgetOptions,
			errorRow = c.pager && c.pager.cssErrorRow ||
			wo && wo.pager_css && wo.pager_css.errorRow ||
			'tablesorter-errorRow',
			typ = typeof xhr,
			valid = true,
			message = '',
			removeRow = function () {
			    c.$table.find('thead').find('.' + errorRow).remove();
			};

        if (!$table.length) {
            console.error('tablesorter showError: no table parameter passed');
            return;
        }

        // ajaxError callback for plugin or widget - see #992
        if (typeof c.pager.ajaxError === 'function') {
            valid = c.pager.ajaxError(c, xhr, settings, exception);
            if (valid === false) {
                return removeRow();
            } else {
                message = valid;
            }
        } else if (typeof wo.pager_ajaxError === 'function') {
            valid = wo.pager_ajaxError(c, xhr, settings, exception);
            if (valid === false) {
                return removeRow();
            } else {
                message = valid;
            }
        }

        if (message === '') {
            if (typ === 'object') {
                message =
					xhr.status === 0 ? 'Not connected, verify Network' :
					xhr.status === 404 ? 'Requested page not found [404]' :
					xhr.status === 500 ? 'Internal Server Error [500]' :
					exception === 'parsererror' ? 'Requested JSON parse failed' :
					exception === 'timeout' ? 'Time out error' :
					exception === 'abort' ? 'Ajax Request aborted' :
					'Uncaught error: ' + xhr.statusText + ' [' + xhr.status + ']';
            } else if (typ === 'string') {
                // keep backward compatibility (external usage just passes a message string)
                message = xhr;
            } else {
                // remove all error rows
                return removeRow();
            }
        }

        // allow message to include entire row HTML!
        $row = (/tr\>/.test(message) ? $(message) : $('<tr><td colspan="' + c.columns + '">' + message + '</td></tr>'))
			.click(function () {
			    $(this).remove();
			})
			// add error row to thead instead of tbody, or clicking on the header will result in a parser error
			.appendTo(c.$table.find('thead:first'))
			.addClass(errorRow + ' ' + c.selectorRemove.slice(1))
			.attr({
			    role: 'alert',
			    'aria-live': 'assertive'
			});

    };

    // extend plugin scope
    $.fn.extend({
        tablesorterPager: $.tablesorterPager.construct
    });

})(jQuery);


/*** This file is dynamically generated ***
█████▄ ▄████▄   █████▄ ▄████▄ ██████   ███████▄ ▄████▄ █████▄ ██ ██████ ██  ██
██  ██ ██  ██   ██  ██ ██  ██   ██     ██ ██ ██ ██  ██ ██  ██ ██ ██▄▄   ██▄▄██
██  ██ ██  ██   ██  ██ ██  ██   ██     ██ ██ ██ ██  ██ ██  ██ ██ ██▀▀    ▀▀▀██
█████▀ ▀████▀   ██  ██ ▀████▀   ██     ██ ██ ██ ▀████▀ █████▀ ██ ██     █████▀
*/
/*! tablesorter (FORK) - updated 09-23-2016 (v2.27.7)*/
/* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = factory(require('jquery'));
    } else {
        factory(jQuery);
    }
}(function (jQuery) {

    /*! Widget: storage - updated 3/1/2016 (v2.25.5) */
    /*global JSON:false */
    ; (function ($, window, document) {
        'use strict';

        var ts = $.tablesorter || {};
        // *** Store data in local storage, with a cookie fallback ***
        /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
           if you need it, then include https://github.com/douglascrockford/JSON-js
    
           $.parseJSON is not available is jQuery versions older than 1.4.1, using older
           versions will only allow storing information for one page at a time
    
           // *** Save data (JSON format only) ***
           // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid
           var val = { "mywidget" : "data1" }; // valid JSON uses double quotes
           // $.tablesorter.storage(table, key, val);
           $.tablesorter.storage(table, 'tablesorter-mywidget', val);
    
           // *** Get data: $.tablesorter.storage(table, key); ***
           v = $.tablesorter.storage(table, 'tablesorter-mywidget');
           // val may be empty, so also check for your data
           val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : '';
           alert(val); // 'data1' if saved, or '' if not
        */
        ts.storage = function (table, key, value, options) {
            table = $(table)[0];
            var cookieIndex, cookies, date,
                hasStorage = false,
                values = {},
                c = table.config,
                wo = c && c.widgetOptions,
                storageType = (options && options.useSessionStorage) || (wo && wo.storage_useSessionStorage) ?
                    'sessionStorage' : 'localStorage',
                $table = $(table),
                // id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId,
                // (4) table ID, then (5) table index
                id = options && options.id ||
                    $table.attr(options && options.group || wo && wo.storage_group || 'data-table-group') ||
                    wo && wo.storage_tableId || table.id || $('.tablesorter').index($table),
                // url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl,
                // (4) table.config.fixedUrl (deprecated), then (5) window location path
                url = options && options.url ||
                    $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') ||
                    wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname;
            // https://gist.github.com/paulirish/5558557
            if (storageType in window) {
                try {
                    window[storageType].setItem('_tmptest', 'temp');
                    hasStorage = true;
                    window[storageType].removeItem('_tmptest');
                } catch (error) {
                    if (c && c.debug) {
                        console.warn(storageType + ' is not supported in this browser');
                    }
                }
            }
            // *** get value ***
            if ($.parseJSON) {
                if (hasStorage) {
                    values = $.parseJSON(window[storageType][key] || 'null') || {};
                } else {
                    // old browser, using cookies
                    cookies = document.cookie.split(/[;\s|=]/);
                    // add one to get from the key to the value
                    cookieIndex = $.inArray(key, cookies) + 1;
                    values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {};
                }
            }
            // allow value to be an empty string too
            if (typeof value !== 'undefined' && window.JSON && JSON.hasOwnProperty('stringify')) {
                // add unique identifiers = url pathname > table ID/index on page > data
                if (!values[url]) {
                    values[url] = {};
                }
                values[url][id] = value;
                // *** set value ***
                if (hasStorage) {
                    window[storageType][key] = JSON.stringify(values);
                } else {
                    date = new Date();
                    date.setTime(date.getTime() + (31536e+6)); // 365 days
                    document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/';
                }
            } else {
                return values && values[url] ? values[url][id] : '';
            }
        };

    })(jQuery, window, document);

    /*! Widget: uitheme - updated 7/31/2016 (v2.27.0) */
    ; (function ($) {
        'use strict';
        var ts = $.tablesorter || {};

        ts.themes = {
            'bootstrap': {
                table: 'table table-bordered table-striped',
                caption: 'caption',
                // header class names
                header: 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css)
                sortNone: '',
                sortAsc: '',
                sortDesc: '',
                active: '', // applied when column is sorted
                hover: '', // custom css required - a defined bootstrap style may not override other classes
                // icon class names
                icons: '', // add 'icon-white' to make them white; this icon class is added to the <i> in the header
                iconSortNone: 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted
                iconSortAsc: 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
                iconSortDesc: 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
                filterRow: '', // filter row class
                footerRow: '',
                footerCells: '',
                even: '', // even row zebra striping
                odd: ''  // odd row zebra striping
            },
            'jui': {
                table: 'ui-widget ui-widget-content ui-corner-all', // table classes
                caption: 'ui-widget-content',
                // header class names
                header: 'ui-widget-header ui-corner-all ui-state-default', // header classes
                sortNone: '',
                sortAsc: '',
                sortDesc: '',
                active: 'ui-state-active', // applied when column is sorted
                hover: 'ui-state-hover',  // hover class
                // icon class names
                icons: 'ui-icon', // icon class added to the <i> in the header
                iconSortNone: 'ui-icon-carat-2-n-s ui-icon-caret-2-n-s', // class name added to icon when column is not sorted
                iconSortAsc: 'ui-icon-carat-1-n ui-icon-caret-1-n', // class name added to icon when column has ascending sort
                iconSortDesc: 'ui-icon-carat-1-s ui-icon-caret-1-s', // class name added to icon when column has descending sort
                filterRow: '',
                footerRow: '',
                footerCells: '',
                even: 'ui-widget-content', // even row zebra striping
                odd: 'ui-state-default'   // odd row zebra striping
            }
        };

        $.extend(ts.css, {
            wrapper: 'tablesorter-wrapper' // ui theme & resizable
        });

        ts.addWidget({
            id: 'uitheme',
            priority: 10,
            format: function (table, c, wo) {
                var i, tmp, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme,
                    themesAll = ts.themes,
                    $table = c.$table.add($(c.namespace + '_extra_table')),
                    $headers = c.$headers.add($(c.namespace + '_extra_headers')),
                    theme = c.theme || 'jui',
                    themes = themesAll[theme] || {},
                    remove = $.trim([themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active].join(' ')),
                    iconRmv = $.trim([themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc].join(' '));
                if (c.debug) { time = new Date(); }
                // initialization code - run once
                if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) {
                    wo.uitheme_applied = true;
                    oldtheme = themesAll[c.appliedTheme] || {};
                    hasOldTheme = !$.isEmptyObject(oldtheme);
                    oldremove = hasOldTheme ? [oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active].join(' ') : '';
                    oldIconRmv = hasOldTheme ? [oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc].join(' ') : '';
                    if (hasOldTheme) {
                        wo.zebra[0] = $.trim(' ' + wo.zebra[0].replace(' ' + oldtheme.even, ''));
                        wo.zebra[1] = $.trim(' ' + wo.zebra[1].replace(' ' + oldtheme.odd, ''));
                        c.$tbodies.children().removeClass([oldtheme.even, oldtheme.odd].join(' '));
                    }
                    // update zebra stripes
                    if (themes.even) { wo.zebra[0] += ' ' + themes.even; }
                    if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; }
                    // add caption style
                    $table.children('caption')
                        .removeClass(oldtheme.caption || '')
                        .addClass(themes.caption);
                    // add table/footer class names
                    $tfoot = $table
                        // remove other selected themes
                        .removeClass((c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || ''))
                        .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name
                        .children('tfoot');
                    c.appliedTheme = c.theme;

                    if ($tfoot.length) {
                        $tfoot
                            // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed
                            .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow)
                            .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells);
                    }
                    // update header classes
                    $headers
                        .removeClass((hasOldTheme ? [oldtheme.header, oldtheme.hover, oldremove].join(' ') : '') || '')
                        .addClass(themes.header)
                        .not('.sorter-false')
                        .unbind('mouseenter.tsuitheme mouseleave.tsuitheme')
                        .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function (event) {
                            // toggleClass with switch added in jQuery 1.3
                            $(this)[event.type === 'mouseenter' ? 'addClass' : 'removeClass'](themes.hover || '');
                        });

                    $headers.each(function () {
                        var $this = $(this);
                        if (!$this.find('.' + ts.css.wrapper).length) {
                            // Firefox needs this inner div to position the icon & resizer correctly
                            $this.wrapInner('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>');
                        }
                    });
                    if (c.cssIcon) {
                        // if c.cssIcon is '', then no <i> is added to the header
                        $headers
                            .find('.' + ts.css.icon)
                            .removeClass(hasOldTheme ? [oldtheme.icons, oldIconRmv].join(' ') : '')
                            .addClass(themes.icons || '');
                    }
                    // filter widget initializes after uitheme
                    if (c.widgets.indexOf('filter') > -1) {
                        tmp = function () {
                            $table.children('thead').children('.' + ts.css.filterRow)
                                .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '')
                                .addClass(themes.filterRow || '');
                        };
                        if (wo.filter_initialized) {
                            tmp();
                        } else {
                            $table.one('filterInit', function () {
                                tmp();
                            });
                        }
                    }
                }
                for (i = 0; i < c.columns; i++) {
                    $header = c.$headers
                        .add($(c.namespace + '_extra_headers'))
                        .not('.sorter-false')
                        .filter('[data-column="' + i + '"]');
                    $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $();
                    $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last');
                    if ($h.length) {
                        $header.removeClass(remove);
                        $icon.removeClass(iconRmv);
                        if ($h[0].sortDisabled) {
                            // no sort arrows for disabled columns!
                            $icon.removeClass(themes.icons || '');
                        } else {
                            hdr = themes.sortNone;
                            icon = themes.iconSortNone;
                            if ($h.hasClass(ts.css.sortAsc)) {
                                hdr = [themes.sortAsc, themes.active].join(' ');
                                icon = themes.iconSortAsc;
                            } else if ($h.hasClass(ts.css.sortDesc)) {
                                hdr = [themes.sortDesc, themes.active].join(' ');
                                icon = themes.iconSortDesc;
                            }
                            $header.addClass(hdr);
                            $icon.addClass(icon || '');
                        }
                    }
                }
                if (c.debug) {
                    console.log('Applying ' + theme + ' theme' + ts.benchmark(time));
                }
            },
            remove: function (table, c, wo, refreshing) {
                if (!wo.uitheme_applied) { return; }
                var $table = c.$table,
                    theme = c.appliedTheme || 'jui',
                    themes = ts.themes[theme] || ts.themes.jui,
                    $headers = $table.children('thead').children(),
                    remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc,
                    iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc;
                $table.removeClass('tablesorter-' + theme + ' ' + themes.table);
                wo.uitheme_applied = false;
                if (refreshing) { return; }
                $table.find(ts.css.header).removeClass(themes.header);
                $headers
                    .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover
                    .removeClass(themes.hover + ' ' + remove + ' ' + themes.active)
                    .filter('.' + ts.css.filterRow)
                    .removeClass(themes.filterRow);
                $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv);
            }
        });

    })(jQuery);

    /*! Widget: columns */
    ; (function ($) {
        'use strict';
        var ts = $.tablesorter || {};

        ts.addWidget({
            id: 'columns',
            priority: 30,
            options: {
                columns: ['primary', 'secondary', 'tertiary']
            },
            format: function (table, c, wo) {
                var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx,
                $table = c.$table,
                $tbodies = c.$tbodies,
                sortList = c.sortList,
                len = sortList.length,
                // removed c.widgetColumns support
                css = wo && wo.columns || ['primary', 'secondary', 'tertiary'],
                last = css.length - 1;
                remove = css.join(' ');
                // check if there is a sort (on initialization there may not be one)
                for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                    $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody
                    $rows = $tbody.children('tr');
                    // loop through the visible rows
                    $rows.each(function () {
                        $row = $(this);
                        if (this.style.display !== 'none') {
                            // remove all columns class names
                            $cells = $row.children().removeClass(remove);
                            // add appropriate column class names
                            if (sortList && sortList[0]) {
                                // primary sort column class
                                $cells.eq(sortList[0][0]).addClass(css[0]);
                                if (len > 1) {
                                    for (indx = 1; indx < len; indx++) {
                                        // secondary, tertiary, etc sort column classes
                                        $cells.eq(sortList[indx][0]).addClass(css[indx] || css[last]);
                                    }
                                }
                            }
                        }
                    });
                    ts.processTbody(table, $tbody, false);
                }
                // add classes to thead and tfoot
                rows = wo.columns_thead !== false ? ['thead tr'] : [];
                if (wo.columns_tfoot !== false) {
                    rows.push('tfoot tr');
                }
                if (rows.length) {
                    $rows = $table.find(rows.join(',')).children().removeClass(remove);
                    if (len) {
                        for (indx = 0; indx < len; indx++) {
                            // add primary. secondary, tertiary, etc sort column classes
                            $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]);
                        }
                    }
                }
            },
            remove: function (table, c, wo) {
                var tbodyIndex, $tbody,
                    $tbodies = c.$tbodies,
                    remove = (wo.columns || ['primary', 'secondary', 'tertiary']).join(' ');
                c.$headers.removeClass(remove);
                c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove);
                for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                    $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
                    $tbody.children('tr').each(function () {
                        $(this).children().removeClass(remove);
                    });
                    ts.processTbody(table, $tbody, false); // restore tbody
                }
            }
        });

    })(jQuery);

    /*! Widget: filter - updated 9/23/2016 (v2.27.7) *//*
 * Requires tablesorter v2.8+ and jQuery 1.7+
 * by Rob Garrison
 */
    ; (function ($) {
        'use strict';
        var tsf, tsfRegex,
            ts = $.tablesorter || {},
            tscss = ts.css,
            tskeyCodes = ts.keyCodes;

        $.extend(tscss, {
            filterRow: 'tablesorter-filter-row',
            filter: 'tablesorter-filter',
            filterDisabled: 'disabled',
            filterRowHide: 'hideme'
        });

        $.extend(tskeyCodes, {
            backSpace: 8,
            escape: 27,
            space: 32,
            left: 37,
            down: 40
        });

        ts.addWidget({
            id: 'filter',
            priority: 50,
            options: {
                filter_cellFilter: '',    // css class name added to the filter cell ( string or array )
                filter_childRows: false, // if true, filter includes child row content in the search
                filter_childByColumn: false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
                filter_childWithSibs: true,  // if true, include matching child row siblings
                filter_columnAnyMatch: true,  // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
                filter_columnFilters: true,  // if true, a filter will be added to the top of each table column
                filter_cssFilter: '',    // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
                filter_defaultAttrib: 'data-value', // data attribute in the header cell that contains the default filter value
                filter_defaultFilter: {},    // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND.
                filter_excludeFilter: {},    // filters to exclude, per column
                filter_external: '',    // jQuery selector string ( or jQuery object ) of external filters
                filter_filteredRow: 'filtered', // class added to filtered rows; define in css with "display:none" to hide the filtered-out rows
                filter_formatter: null,  // add custom filter elements to the filter row
                filter_functions: null,  // add custom filter functions using this option
                filter_hideEmpty: true,  // hide filter row when table is empty
                filter_hideFilters: false, // collapse filter row when mouse leaves the area
                filter_ignoreCase: true,  // if true, make all searches case-insensitive
                filter_liveSearch: true,  // if true, search column content while the user types ( with a delay )
                filter_matchType: { 'input': 'exact', 'select': 'exact' }, // global query settings ('exact' or 'match'); overridden by "filter-match" or "filter-exact" class
                filter_onlyAvail: 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
                filter_placeholder: { search: '', select: '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
                filter_reset: null,  // jQuery selector string of an element used to reset the filters
                filter_resetOnEsc: true,  // Reset filter input when the user presses escape - normalized across browsers
                filter_saveFilters: false, // Use the $.tablesorter.storage utility to save the most recent filters
                filter_searchDelay: 300,   // typing delay in milliseconds before starting a search
                filter_searchFiltered: true,  // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true
                filter_selectSource: null,  // include a function to return an array of values to be added to the column filter select
                filter_selectSourceSeparator: '|', // filter_selectSource array text left of the separator is added to the option value, right into the option text
                filter_serversideFiltering: false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used.
                filter_startsWith: false, // if true, filter start from the beginning of the cell contents
                filter_useParsedData: false  // filter all data using parsed content
            },
            format: function (table, c, wo) {
                if (!c.$table.hasClass('hasFilters')) {
                    tsf.init(table, c, wo);
                }
            },
            remove: function (table, c, wo, refreshing) {
                var tbodyIndex, $tbody,
                    $table = c.$table,
                    $tbodies = c.$tbodies,
                    events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
                        .split(' ').join(c.namespace + 'filter ');
                $table
                    .removeClass('hasFilters')
                    // add filter namespace to all BUT search
                    .unbind(events.replace(ts.regex.spaces, ' '))
                    // remove the filter row even if refreshing, because the column might have been moved
                    .find('.' + tscss.filterRow).remove();
                wo.filter_initialized = false;
                if (refreshing) { return; }
                for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                    $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
                    $tbody.children().removeClass(wo.filter_filteredRow).show();
                    ts.processTbody(table, $tbody, false); // restore tbody
                }
                if (wo.filter_reset) {
                    $(document).undelegate(wo.filter_reset, 'click' + c.namespace + 'filter');
                }
            }
        });

        tsf = ts.filter = {

            // regex used in filter 'check' functions - not for general use and not documented
            regex: {
                regex: /^\/((?:\\\/|[^\/])+)\/([migyu]{0,5})?$/, // regex to test for regex
                child: /tablesorter-childRow/, // child row class name; this gets updated in the script
                filtered: /filtered/, // filtered (hidden) row class name; updated in the script
                type: /undefined|number/, // check type
                exact: /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
                operators: /[<>=]/g, // replace operators
                query: '(q|query)', // replace filter queries
                wild01: /\?/g, // wild card match 0 or 1
                wild0More: /\*/g, // wild care match 0 or more
                quote: /\"/g,
                isNeg1: /(>=?\s*-\d)/,
                isNeg2: /(<=?\s*\d)/
            },
            // function( c, data ) { }
            // c = table.config
            // data.$row = jQuery object of the row currently being processed
            // data.$cells = jQuery object of all cells within the current row
            // data.filters = array of filters for all columns ( some may be undefined )
            // data.filter = filter for the current column
            // data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true )
            // data.exact = table cell text ( or parsed data if column parser enabled; may be a number & not a string )
            // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true; may be a number & not a string )
            // data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true )
            // data.cacheArray = An array of parsed content from each table cell in the row being processed
            // data.index = column index; table = table element ( DOM )
            // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
            types: {
                or: function (c, data, vars) {
                    // look for "|", but not if it is inside of a regular expression
                    if ((tsfRegex.orTest.test(data.iFilter) || tsfRegex.orSplit.test(data.filter)) &&
                        // this test for regex has potential to slow down the overall search
                        !tsfRegex.regex.test(data.filter)) {
                        var indx, filterMatched, query, regex,
                            // duplicate data but split filter
                            data2 = $.extend({}, data),
                            filter = data.filter.split(tsfRegex.orSplit),
                            iFilter = data.iFilter.split(tsfRegex.orSplit),
                            len = filter.length;
                        for (indx = 0; indx < len; indx++) {
                            data2.nestedFilters = true;
                            data2.filter = '' + (tsf.parseFilter(c, filter[indx], data) || '');
                            data2.iFilter = '' + (tsf.parseFilter(c, iFilter[indx], data) || '');
                            query = '(' + (tsf.parseFilter(c, data2.filter, data) || '') + ')';
                            try {
                                // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search,
                                // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group
                                regex = new RegExp(data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '');
                                // filterMatched = data2.filter === '' && indx > 0 ? true
                                // look for an exact match with the 'or' unless the 'filter-match' class is found
                                filterMatched = regex.test(data2.exact) || tsf.processTypes(c, data2, vars);
                                if (filterMatched) {
                                    return filterMatched;
                                }
                            } catch (error) {
                                return null;
                            }
                        }
                        // may be null from processing types
                        return filterMatched || false;
                    }
                    return null;
                },
                // Look for an AND or && operator ( logical and )
                and: function (c, data, vars) {
                    if (tsfRegex.andTest.test(data.filter)) {
                        var indx, filterMatched, result, query, regex,
                            // duplicate data but split filter
                            data2 = $.extend({}, data),
                            filter = data.filter.split(tsfRegex.andSplit),
                            iFilter = data.iFilter.split(tsfRegex.andSplit),
                            len = filter.length;
                        for (indx = 0; indx < len; indx++) {
                            data2.nestedFilters = true;
                            data2.filter = '' + (tsf.parseFilter(c, filter[indx], data) || '');
                            data2.iFilter = '' + (tsf.parseFilter(c, iFilter[indx], data) || '');
                            query = ('(' + (tsf.parseFilter(c, data2.filter, data) || '') + ')')
                                // replace wild cards since /(a*)/i will match anything
                                .replace(tsfRegex.wild01, '\\S{1}').replace(tsfRegex.wild0More, '\\S*');
                            try {
                                // use try/catch just in case RegExp is invalid
                                regex = new RegExp(data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '');
                                // look for an exact match with the 'and' unless the 'filter-match' class is found
                                result = (regex.test(data2.exact) || tsf.processTypes(c, data2, vars));
                                if (indx === 0) {
                                    filterMatched = result;
                                } else {
                                    filterMatched = filterMatched && result;
                                }
                            } catch (error) {
                                return null;
                            }
                        }
                        // may be null from processing types
                        return filterMatched || false;
                    }
                    return null;
                },
                // Look for regex
                regex: function (c, data) {
                    if (tsfRegex.regex.test(data.filter)) {
                        var matches,
                            // cache regex per column for optimal speed
                            regex = data.filter_regexCache[data.index] || tsfRegex.regex.exec(data.filter),
                            isRegex = regex instanceof RegExp;
                        try {
                            if (!isRegex) {
                                // force case insensitive search if ignoreCase option set?
                                // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
                                data.filter_regexCache[data.index] = regex = new RegExp(regex[1], regex[2]);
                            }
                            matches = regex.test(data.exact);
                        } catch (error) {
                            matches = false;
                        }
                        return matches;
                    }
                    return null;
                },
                // Look for operators >, >=, < or <=
                operators: function (c, data) {
                    // ignore empty strings... because '' < 10 is true
                    if (tsfRegex.operTest.test(data.iFilter) && data.iExact !== '') {
                        var cachedValue, result, txt,
                            table = c.table,
                            parsed = data.parsed[data.index],
                            query = ts.formatFloat(data.iFilter.replace(tsfRegex.operators, ''), table),
                            parser = c.parsers[data.index] || {},
                            savedSearch = query;
                        // parse filter value in case we're comparing numbers ( dates )
                        if (parsed || parser.type === 'numeric') {
                            txt = $.trim('' + data.iFilter.replace(tsfRegex.operators, ''));
                            result = tsf.parseFilter(c, txt, data, true);
                            query = (typeof result === 'number' && result !== '' && !isNaN(result)) ? result : query;
                        }
                        // iExact may be numeric - see issue #149;
                        // check if cached is defined, because sometimes j goes out of range? ( numeric columns )
                        if ((parsed || parser.type === 'numeric') && !isNaN(query) &&
                            typeof data.cache !== 'undefined') {
                            cachedValue = data.cache;
                        } else {
                            txt = isNaN(data.iExact) ? data.iExact.replace(ts.regex.nondigit, '') : data.iExact;
                            cachedValue = ts.formatFloat(txt, table);
                        }
                        if (tsfRegex.gtTest.test(data.iFilter)) {
                            result = tsfRegex.gteTest.test(data.iFilter) ? cachedValue >= query : cachedValue > query;
                        } else if (tsfRegex.ltTest.test(data.iFilter)) {
                            result = tsfRegex.lteTest.test(data.iFilter) ? cachedValue <= query : cachedValue < query;
                        }
                        // keep showing all rows if nothing follows the operator
                        if (!result && savedSearch === '') {
                            result = true;
                        }
                        return result;
                    }
                    return null;
                },
                // Look for a not match
                notMatch: function (c, data) {
                    if (tsfRegex.notTest.test(data.iFilter)) {
                        var indx,
                            txt = data.iFilter.replace('!', ''),
                            filter = tsf.parseFilter(c, txt, data) || '';
                        if (tsfRegex.exact.test(filter)) {
                            // look for exact not matches - see #628
                            filter = filter.replace(tsfRegex.exact, '');
                            return filter === '' ? true : $.trim(filter) !== data.iExact;
                        } else {
                            indx = data.iExact.search($.trim(filter));
                            return filter === '' ? true :
                                // return true if not found
                                data.anyMatch ? indx < 0 :
                                // return false if found
                                !(c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0);
                        }
                    }
                    return null;
                },
                // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
                exact: function (c, data) {
                    /*jshint eqeqeq:false */
                    if (tsfRegex.exact.test(data.iFilter)) {
                        var txt = data.iFilter.replace(tsfRegex.exact, ''),
                            filter = tsf.parseFilter(c, txt, data) || '';
                        return data.anyMatch ? $.inArray(filter, data.rowArray) >= 0 : filter == data.iExact;
                    }
                    return null;
                },
                // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
                range: function (c, data) {
                    if (tsfRegex.toTest.test(data.iFilter)) {
                        var result, tmp, range1, range2,
                            table = c.table,
                            index = data.index,
                            parsed = data.parsed[index],
                            // make sure the dash is for a range and not indicating a negative number
                            query = data.iFilter.split(tsfRegex.toSplit);

                        tmp = query[0].replace(ts.regex.nondigit, '') || '';
                        range1 = ts.formatFloat(tsf.parseFilter(c, tmp, data), table);
                        tmp = query[1].replace(ts.regex.nondigit, '') || '';
                        range2 = ts.formatFloat(tsf.parseFilter(c, tmp, data), table);
                        // parse filter value in case we're comparing numbers ( dates )
                        if (parsed || c.parsers[index].type === 'numeric') {
                            result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
                            range1 = (result !== '' && !isNaN(result)) ? result : range1;
                            result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
                            range2 = (result !== '' && !isNaN(result)) ? result : range2;
                        }
                        if ((parsed || c.parsers[index].type === 'numeric') && !isNaN(range1) && !isNaN(range2)) {
                            result = data.cache;
                        } else {
                            tmp = isNaN(data.iExact) ? data.iExact.replace(ts.regex.nondigit, '') : data.iExact;
                            result = ts.formatFloat(tmp, table);
                        }
                        if (range1 > range2) {
                            tmp = range1; range1 = range2; range2 = tmp; // swap
                        }
                        return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
                    }
                    return null;
                },
                // Look for wild card: ? = single, * = multiple, or | = logical OR
                wild: function (c, data) {
                    if (tsfRegex.wildOrTest.test(data.iFilter)) {
                        var query = '' + (tsf.parseFilter(c, data.iFilter, data) || '');
                        // look for an exact match with the 'or' unless the 'filter-match' class is found
                        if (!tsfRegex.wildTest.test(query) && data.nestedFilters) {
                            query = data.isMatch ? query : '^(' + query + ')$';
                        }
                        // parsing the filter may not work properly when using wildcards =/
                        try {
                            return new RegExp(
                                query.replace(tsfRegex.wild01, '\\S{1}').replace(tsfRegex.wild0More, '\\S*'),
                                c.widgetOptions.filter_ignoreCase ? 'i' : ''
                            )
                            .test(data.exact);
                        } catch (error) {
                            return null;
                        }
                    }
                    return null;
                },
                // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
                fuzzy: function (c, data) {
                    if (tsfRegex.fuzzyTest.test(data.iFilter)) {
                        var indx,
                            patternIndx = 0,
                            len = data.iExact.length,
                            txt = data.iFilter.slice(1),
                            pattern = tsf.parseFilter(c, txt, data) || '';
                        for (indx = 0; indx < len; indx++) {
                            if (data.iExact[indx] === pattern[patternIndx]) {
                                patternIndx += 1;
                            }
                        }
                        return patternIndx === pattern.length;
                    }
                    return null;
                }
            },
            init: function (table) {
                // filter language options
                ts.language = $.extend(true, {}, {
                    to: 'to',
                    or: 'or',
                    and: 'and'
                }, ts.language);

                var options, string, txt, $header, column, val, fxn, noSelect,
                    c = table.config,
                    wo = c.widgetOptions;
                c.$table.addClass('hasFilters');
                c.lastSearch = [];

                // define timers so using clearTimeout won't cause an undefined error
                wo.filter_searchTimer = null;
                wo.filter_initTimer = null;
                wo.filter_formatterCount = 0;
                wo.filter_formatterInit = [];
                wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
                wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';

                val = '\\{' + tsfRegex.query + '\\}';
                $.extend(tsfRegex, {
                    child: new RegExp(c.cssChildRow),
                    filtered: new RegExp(wo.filter_filteredRow),
                    alreadyFiltered: new RegExp('(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i'),
                    toTest: new RegExp('\\s+(-|' + ts.language.to + ')\\s+', 'i'),
                    toSplit: new RegExp('(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi'),
                    andTest: new RegExp('\\s+(' + ts.language.and + '|&&)\\s+', 'i'),
                    andSplit: new RegExp('(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi'),
                    orTest: new RegExp('(\\||\\s+' + ts.language.or + '\\s+)', 'i'),
                    orSplit: new RegExp('(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi'),
                    iQuery: new RegExp(val, 'i'),
                    igQuery: new RegExp(val, 'ig'),
                    operTest: /^[<>]=?/,
                    gtTest: />/,
                    gteTest: />=/,
                    ltTest: /</,
                    lteTest: /<=/,
                    notTest: /^\!/,
                    wildOrTest: /[\?\*\|]/,
                    wildTest: /\?\*/,
                    fuzzyTest: /^~/,
                    exactTest: /[=\"\|!]/
                });

                // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
                // see issue #156
                val = c.$headers.filter('.filter-false, .parser-false').length;
                if (wo.filter_columnFilters !== false && val !== c.$headers.length) {
                    // build filter row
                    tsf.buildRow(table, c, wo);
                }

                txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset ' +
                    'filterResetSaved filterEnd search '.split(' ').join(c.namespace + 'filter ');
                c.$table.bind(txt, function (event, filter) {
                    val = wo.filter_hideEmpty &&
                        $.isEmptyObject(c.cache) &&
                        !(c.delayInit && event.type === 'appendCache');
                    // hide filter row using the 'filtered' class name
                    c.$table.find('.' + tscss.filterRow).toggleClass(wo.filter_filteredRow, val); // fixes #450
                    if (!/(search|filter)/.test(event.type)) {
                        event.stopPropagation();
                        tsf.buildDefault(table, true);
                    }
                    if (event.type === 'filterReset') {
                        c.$table.find('.' + tscss.filter).add(wo.filter_$externalFilters).val('');
                        tsf.searching(table, []);
                    } else if (event.type === 'filterResetSaved') {
                        ts.storage(table, 'tablesorter-filters', '');
                    } else if (event.type === 'filterEnd') {
                        tsf.buildDefault(table, true);
                    } else {
                        // send false argument to force a new search; otherwise if the filter hasn't changed,
                        // it will return
                        filter = event.type === 'search' ? filter :
                            event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
                        if (/(update|add)/.test(event.type) && event.type !== 'updateComplete') {
                            // force a new search since content has changed
                            c.lastCombinedFilter = null;
                            c.lastSearch = [];
                            // update filterFormatters after update (& small delay) - Fixes #1237
                            setTimeout(function () {
                                c.$table.triggerHandler('filterFomatterUpdate');
                            }, 100);
                        }
                        // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
                        // input ensures all inputs are updated when a search is triggered on the table
                        // $( 'table' ).trigger( 'search', [...] );
                        tsf.searching(table, filter, true);
                    }
                    return false;
                });

                // reset button/link
                if (wo.filter_reset) {
                    if (wo.filter_reset instanceof $) {
                        // reset contains a jQuery object, bind to it
                        wo.filter_reset.click(function () {
                            c.$table.triggerHandler('filterReset');
                        });
                    } else if ($(wo.filter_reset).length) {
                        // reset is a jQuery selector, use event delegation
                        $(document)
                            .undelegate(wo.filter_reset, 'click' + c.namespace + 'filter')
                            .delegate(wo.filter_reset, 'click' + c.namespace + 'filter', function () {
                                // trigger a reset event, so other functions ( filter_formatter ) know when to reset
                                c.$table.triggerHandler('filterReset');
                            });
                    }
                }
                if (wo.filter_functions) {
                    for (column = 0; column < c.columns; column++) {
                        fxn = ts.getColumnData(table, wo.filter_functions, column);
                        if (fxn) {
                            // remove 'filter-select' from header otherwise the options added here are replaced with
                            // all options
                            $header = c.$headerIndexed[column].removeClass('filter-select');
                            // don't build select if 'filter-false' or 'parser-false' set
                            noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
                            options = '';
                            if (fxn === true && noSelect) {
                                tsf.buildSelect(table, column);
                            } else if (typeof fxn === 'object' && noSelect) {
                                // add custom drop down list
                                for (string in fxn) {
                                    if (typeof string === 'string') {
                                        options += options === '' ?
                                            '<option value="">' +
                                                ($header.data('placeholder') ||
                                                    $header.attr('data-placeholder') ||
                                                    wo.filter_placeholder.select ||
                                                    ''
                                                ) +
                                            '</option>' : '';
                                        val = string;
                                        txt = string;
                                        if (string.indexOf(wo.filter_selectSourceSeparator) >= 0) {
                                            val = string.split(wo.filter_selectSourceSeparator);
                                            txt = val[1];
                                            val = val[0];
                                        }
                                        options += '<option ' +
                                            (txt === val ? '' : 'data-function-name="' + string + '" ') +
                                            'value="' + val + '">' + txt + '</option>';
                                    }
                                }
                                c.$table
                                    .find('thead')
                                    .find('select.' + tscss.filter + '[data-column="' + column + '"]')
                                    .append(options);
                                txt = wo.filter_selectSource;
                                fxn = typeof txt === 'function' ? true : ts.getColumnData(table, txt, column);
                                if (fxn) {
                                    // updating so the extra options are appended
                                    tsf.buildSelect(c.table, column, '', true, $header.hasClass(wo.filter_onlyAvail));
                                }
                            }
                        }
                    }
                }
                // not really updating, but if the column has both the 'filter-select' class &
                // filter_functions set to true, it would append the same options twice.
                tsf.buildDefault(table, true);

                tsf.bindSearch(table, c.$table.find('.' + tscss.filter), true);
                if (wo.filter_external) {
                    tsf.bindSearch(table, wo.filter_external);
                }

                if (wo.filter_hideFilters) {
                    tsf.hideFilters(c);
                }

                // show processing icon
                if (c.showProcessing) {
                    txt = 'filterStart filterEnd '.split(' ').join(c.namespace + 'filter ');
                    c.$table
                        .unbind(txt.replace(ts.regex.spaces, ' '))
                        .bind(txt, function (event, columns) {
                            // only add processing to certain columns to all columns
                            $header = (columns) ?
                                c.$table
                                    .find('.' + tscss.header)
                                    .filter('[data-column]')
                                    .filter(function () {
                                        return columns[$(this).data('column')] !== '';
                                    }) : '';
                            ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
                        });
                }

                // set filtered rows count ( intially unfiltered )
                c.filteredRows = c.totalRows;

                // add default values
                txt = 'tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter ');
                c.$table
                .unbind(txt.replace(ts.regex.spaces, ' '))
                .bind(txt, function () {
                    tsf.completeInit(this);
                });
                // if filter widget is added after pager has initialized; then set filter init flag
                if (c.pager && c.pager.initialized && !wo.filter_initialized) {
                    c.$table.triggerHandler('filterFomatterUpdate');
                    setTimeout(function () {
                        tsf.filterInitComplete(c);
                    }, 100);
                } else if (!wo.filter_initialized) {
                    tsf.completeInit(table);
                }
            },
            completeInit: function (table) {
                // redefine 'c' & 'wo' so they update properly inside this callback
                var c = table.config,
                    wo = c.widgetOptions,
                    filters = tsf.setDefaults(table, c, wo) || [];
                if (filters.length) {
                    // prevent delayInit from triggering a cache build if filters are empty
                    if (!(c.delayInit && filters.join('') === '')) {
                        ts.setFilters(table, filters, true);
                    }
                }
                c.$table.triggerHandler('filterFomatterUpdate');
                // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
                setTimeout(function () {
                    if (!wo.filter_initialized) {
                        tsf.filterInitComplete(c);
                    }
                }, 100);
            },

            // $cell parameter, but not the config, is passed to the filter_formatters,
            // so we have to work with it instead
            formatterUpdated: function ($cell, column) {
                // prevent error if $cell is undefined - see #1056
                var wo = $cell && $cell.closest('table')[0].config.widgetOptions;
                if (wo && !wo.filter_initialized) {
                    // add updates by column since this function
                    // may be called numerous times before initialization
                    wo.filter_formatterInit[column] = 1;
                }
            },
            filterInitComplete: function (c) {
                var indx, len,
                    wo = c.widgetOptions,
                    count = 0,
                    completed = function () {
                        wo.filter_initialized = true;
                        // update lastSearch - it gets cleared often
                        c.lastSearch = c.$table.data('lastSearch');
                        c.$table.triggerHandler('filterInit', c);
                        tsf.findRows(c.table, c.lastSearch || []);
                    };
                if ($.isEmptyObject(wo.filter_formatter)) {
                    completed();
                } else {
                    len = wo.filter_formatterInit.length;
                    for (indx = 0; indx < len; indx++) {
                        if (wo.filter_formatterInit[indx] === 1) {
                            count++;
                        }
                    }
                    clearTimeout(wo.filter_initTimer);
                    if (!wo.filter_initialized && count === wo.filter_formatterCount) {
                        // filter widget initialized
                        completed();
                    } else if (!wo.filter_initialized) {
                        // fall back in case a filter_formatter doesn't call
                        // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
                        wo.filter_initTimer = setTimeout(function () {
                            completed();
                        }, 500);
                    }
                }
            },
            // encode or decode filters for storage; see #1026
            processFilters: function (filters, encode) {
                var indx,
                    // fixes #1237; previously returning an encoded "filters" value
                    result = [],
                    mode = encode ? encodeURIComponent : decodeURIComponent,
                    len = filters.length;
                for (indx = 0; indx < len; indx++) {
                    if (filters[indx]) {
                        result[indx] = mode(filters[indx]);
                    }
                }
                return result;
            },
            setDefaults: function (table, c, wo) {
                var isArray, saved, indx, col, $filters,
                    // get current ( default ) filters
                    filters = ts.getFilters(table) || [];
                if (wo.filter_saveFilters && ts.storage) {
                    saved = ts.storage(table, 'tablesorter-filters') || [];
                    isArray = $.isArray(saved);
                    // make sure we're not just getting an empty array
                    if (!(isArray && saved.join('') === '' || !isArray)) {
                        filters = tsf.processFilters(saved);
                    }
                }
                // if no filters saved, then check default settings
                if (filters.join('') === '') {
                    // allow adding default setting to external filters
                    $filters = c.$headers.add(wo.filter_$externalFilters)
                        .filter('[' + wo.filter_defaultAttrib + ']');
                    for (indx = 0; indx <= c.columns; indx++) {
                        // include data-column='all' external filters
                        col = indx === c.columns ? 'all' : indx;
                        filters[indx] = $filters
                            .filter('[data-column="' + col + '"]')
                            .attr(wo.filter_defaultAttrib) || filters[indx] || '';
                    }
                }
                c.$table.data('lastSearch', filters);
                return filters;
            },
            parseFilter: function (c, filter, data, parsed) {
                return parsed || data.parsed[data.index] ?
                    c.parsers[data.index].format(filter, c.table, [], data.index) :
                    filter;
            },
            buildRow: function (table, c, wo) {
                var $filter, col, column, $header, makeSelect, disabled, name, ffxn, tmp,
                    // c.columns defined in computeThIndexes()
                    cellFilter = wo.filter_cellFilter,
                    columns = c.columns,
                    arry = $.isArray(cellFilter),
                    buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
                for (column = 0; column < columns; column++) {
                    if (c.$headerIndexed[column].length) {
                        // account for entire column set with colspan. See #1047
                        tmp = c.$headerIndexed[column] && c.$headerIndexed[column][0].colSpan || 0;
                        if (tmp > 1) {
                            buildFilter += '<td data-column="' + column + '-' + (column + tmp - 1) + '" colspan="' + tmp + '"';
                        } else {
                            buildFilter += '<td data-column="' + column + '"';
                        }
                        if (arry) {
                            buildFilter += (cellFilter[column] ? ' class="' + cellFilter[column] + '"' : '');
                        } else {
                            buildFilter += (cellFilter !== '' ? ' class="' + cellFilter + '"' : '');
                        }
                        buildFilter += '></td>';
                    }
                }
                c.$filters = $(buildFilter += '</tr>')
                    .appendTo(c.$table.children('thead').eq(0))
                    .children('td');
                // build each filter input
                for (column = 0; column < columns; column++) {
                    disabled = false;
                    // assuming last cell of a column is the main column
                    $header = c.$headerIndexed[column];
                    if ($header && $header.length) {
                        // $filter = c.$filters.filter( '[data-column="' + column + '"]' );
                        $filter = tsf.getColumnElm(c, c.$filters, column);
                        ffxn = ts.getColumnData(table, wo.filter_functions, column);
                        makeSelect = (wo.filter_functions && ffxn && typeof ffxn !== 'function') ||
                            $header.hasClass('filter-select');
                        // get data from jQuery data, metadata, headers option or header class name
                        col = ts.getColumnData(table, c.headers, column);
                        disabled = ts.getData($header[0], col, 'filter') === 'false' ||
                            ts.getData($header[0], col, 'parser') === 'false';

                        if (makeSelect) {
                            buildFilter = $('<select>').appendTo($filter);
                        } else {
                            ffxn = ts.getColumnData(table, wo.filter_formatter, column);
                            if (ffxn) {
                                wo.filter_formatterCount++;
                                buildFilter = ffxn($filter, column);
                                // no element returned, so lets go find it
                                if (buildFilter && buildFilter.length === 0) {
                                    buildFilter = $filter.children('input');
                                }
                                // element not in DOM, so lets attach it
                                if (buildFilter && (buildFilter.parent().length === 0 ||
                                    (buildFilter.parent().length && buildFilter.parent()[0] !== $filter[0]))) {
                                    $filter.append(buildFilter);
                                }
                            } else {
                                buildFilter = $('<input type="search">').appendTo($filter);
                            }
                            if (buildFilter) {
                                tmp = $header.data('placeholder') ||
                                    $header.attr('data-placeholder') ||
                                    wo.filter_placeholder.search || '';
                                buildFilter.attr('placeholder', tmp);
                            }
                        }
                        if (buildFilter) {
                            // add filter class name
                            name = ($.isArray(wo.filter_cssFilter) ?
                                (typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '') :
                                wo.filter_cssFilter) || '';
                            // copy data-column from table cell (it will include colspan)
                            buildFilter.addClass(tscss.filter + ' ' + name).attr('data-column', $filter.attr('data-column'));
                            if (disabled) {
                                buildFilter.attr('placeholder', '').addClass(tscss.filterDisabled)[0].disabled = true;
                            }
                        }
                    }
                }
            },
            bindSearch: function (table, $el, internal) {
                table = $(table)[0];
                $el = $($el); // allow passing a selector string
                if (!$el.length) { return; }
                var tmp,
                    c = table.config,
                    wo = c.widgetOptions,
                    namespace = c.namespace + 'filter',
                    $ext = wo.filter_$externalFilters;
                if (internal !== true) {
                    // save anyMatch element
                    tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
                    wo.filter_$anyMatch = $el.filter(tmp);
                    if ($ext && $ext.length) {
                        wo.filter_$externalFilters = wo.filter_$externalFilters.add($el);
                    } else {
                        wo.filter_$externalFilters = $el;
                    }
                    // update values ( external filters added after table initialization )
                    ts.setFilters(table, c.$table.data('lastSearch') || [], internal === false);
                }
                // unbind events
                tmp = ('keypress keyup keydown search change input '.split(' ').join(namespace + ' '));
                $el
                // use data attribute instead of jQuery data since the head is cloned without including
                // the data/binding
                .attr('data-lastSearchTime', new Date().getTime())
                .unbind(tmp.replace(ts.regex.spaces, ' '))
                .bind('keydown' + namespace, function (event) {
                    if (event.which === tskeyCodes.escape && !table.config.widgetOptions.filter_resetOnEsc) {
                        // prevent keypress event
                        return false;
                    }
                })
                .bind('keyup' + namespace, function (event) {
                    wo = table.config.widgetOptions; // make sure "wo" isn't cached
                    var column = parseInt($(this).attr('data-column'), 10),
                        liveSearch = typeof wo.filter_liveSearch === 'boolean' ? wo.filter_liveSearch :
                            ts.getColumnData(table, wo.filter_liveSearch, column);
                    if (typeof liveSearch === 'undefined') {
                        liveSearch = wo.filter_liveSearch.fallback || false;
                    }
                    $(this).attr('data-lastSearchTime', new Date().getTime());
                    // emulate what webkit does.... escape clears the filter
                    if (event.which === tskeyCodes.escape) {
                        // make sure to restore the last value on escape
                        this.value = wo.filter_resetOnEsc ? '' : c.lastSearch[column];
                        // live search
                    } else if (liveSearch === false) {
                        return;
                        // don't return if the search value is empty ( all rows need to be revealed )
                    } else if (this.value !== '' && (
                        // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
                        (typeof liveSearch === 'number' && this.value.length < liveSearch) ||
                        // let return & backspace continue on, but ignore arrows & non-valid characters
                        (event.which !== tskeyCodes.enter && event.which !== tskeyCodes.backSpace &&
                            (event.which < tskeyCodes.space || (event.which >= tskeyCodes.left && event.which <= tskeyCodes.down))))) {
                        return;
                    }
                    // change event = no delay; last true flag tells getFilters to skip newest timed input
                    tsf.searching(table, true, true);
                })
                // include change for select - fixes #473
                .bind('search change keypress input '.split(' ').join(namespace + ' '), function (event) {
                    // don't get cached data, in case data-column changes dynamically
                    var column = parseInt($(this).attr('data-column'), 10);
                    // don't allow 'change' event to process if the input value is the same - fixes #685
                    if (table.config.widgetOptions.filter_initialized &&
                        (event.which === tskeyCodes.enter || event.type === 'search' ||
                        (event.type === 'change' || event.type === 'input') &&
                        this.value !== c.lastSearch[column])
                    ) {
                        event.preventDefault();
                        // init search with no delay
                        $(this).attr('data-lastSearchTime', new Date().getTime());
                        tsf.searching(table, event.type !== 'keypress', true, column);
                    }
                });
            },
            searching: function (table, filter, skipFirst, column) {
                var liveSearch,
                    wo = table.config.widgetOptions;
                if (typeof column === 'undefined') {
                    // no delay
                    liveSearch = false;
                } else {
                    liveSearch = typeof wo.filter_liveSearch === 'boolean' ?
                        wo.filter_liveSearch :
                        // get column setting, or set to fallback value, or default to false
                        ts.getColumnData(table, wo.filter_liveSearch, column);
                    if (typeof liveSearch !== 'undefined') {
                        liveSearch = wo.filter_liveSearch.fallback || false;
                    }
                }
                clearTimeout(wo.filter_searchTimer);
                if (typeof filter === 'undefined' || filter === true) {
                    // delay filtering
                    wo.filter_searchTimer = setTimeout(function () {
                        tsf.checkFilters(table, filter, skipFirst);
                    }, liveSearch ? wo.filter_searchDelay : 10);
                } else {
                    // skip delay
                    tsf.checkFilters(table, filter, skipFirst);
                }
            },
            checkFilters: function (table, filter, skipFirst) {
                var c = table.config,
                    wo = c.widgetOptions,
                    filterArray = $.isArray(filter),
                    filters = (filterArray) ? filter : ts.getFilters(table, true),
                    combinedFilters = (filters || []).join(''); // combined filter values
                // prevent errors if delay init is set
                if ($.isEmptyObject(c.cache)) {
                    // update cache if delayInit set & pager has initialized ( after user initiates a search )
                    if (c.delayInit && (!c.pager || c.pager && c.pager.initialized)) {
                        ts.updateCache(c, function () {
                            tsf.checkFilters(table, false, skipFirst);
                        });
                    }
                    return;
                }
                // add filter array back into inputs
                if (filterArray) {
                    ts.setFilters(table, filters, false, skipFirst !== true);
                    if (!wo.filter_initialized) { c.lastCombinedFilter = ''; }
                }
                if (wo.filter_hideFilters) {
                    // show/hide filter row as needed
                    c.$table
                        .find('.' + tscss.filterRow)
                        .triggerHandler(tsf.hideFiltersCheck(c) ? 'mouseleave' : 'mouseenter');
                }
                // return if the last search is the same; but filter === false when updating the search
                // see example-widget-filter.html filter toggle buttons
                if (c.lastCombinedFilter === combinedFilters && filter !== false) {
                    return;
                } else if (filter === false) {
                    // force filter refresh
                    c.lastCombinedFilter = null;
                    c.lastSearch = [];
                }
                // define filter inside it is false
                filters = filters || [];
                // convert filters to strings - see #1070
                filters = Array.prototype.map ?
                    filters.map(String) :
                    // for IE8 & older browsers - maybe not the best method
                    filters.join('\ufffd').split('\ufffd');

                if (wo.filter_initialized) {
                    c.$table.triggerHandler('filterStart', [filters]);
                }
                if (c.showProcessing) {
                    // give it time for the processing icon to kick in
                    setTimeout(function () {
                        tsf.findRows(table, filters, combinedFilters);
                        return false;
                    }, 30);
                } else {
                    tsf.findRows(table, filters, combinedFilters);
                    return false;
                }
            },
            hideFiltersCheck: function (c) {
                if (typeof c.widgetOptions.filter_hideFilters === 'function') {
                    var val = c.widgetOptions.filter_hideFilters(c);
                    if (typeof val === 'boolean') {
                        return val;
                    }
                }
                return ts.getFilters(c.$table).join('') === '';
            },
            hideFilters: function (c, $table) {
                var timer;
                ($table || c.$table)
                    .find('.' + tscss.filterRow)
                    .addClass(tscss.filterRowHide)
                    .bind('mouseenter mouseleave', function (e) {
                        // save event object - http://bugs.jquery.com/ticket/12140
                        var event = e,
                            $row = $(this);
                        clearTimeout(timer);
                        timer = setTimeout(function () {
                            if (/enter|over/.test(event.type)) {
                                $row.removeClass(tscss.filterRowHide);
                            } else {
                                // don't hide if input has focus
                                // $( ':focus' ) needs jQuery 1.6+
                                if ($(document.activeElement).closest('tr')[0] !== $row[0]) {
                                    // don't hide row if any filter has a value
                                    $row.toggleClass(tscss.filterRowHide, tsf.hideFiltersCheck(c));
                                }
                            }
                        }, 200);
                    })
                    .find('input, select').bind('focus blur', function (e) {
                        var event = e,
                            $row = $(this).closest('tr');
                        clearTimeout(timer);
                        timer = setTimeout(function () {
                            clearTimeout(timer);
                            // don't hide row if any filter has a value
                            $row.toggleClass(tscss.filterRowHide, tsf.hideFiltersCheck(c) && event.type !== 'focus');
                        }, 200);
                    });
            },
            defaultFilter: function (filter, mask) {
                if (filter === '') { return filter; }
                var regex = tsfRegex.iQuery,
                    maskLen = mask.match(tsfRegex.igQuery).length,
                    query = maskLen > 1 ? $.trim(filter).split(/\s/) : [$.trim(filter)],
                    len = query.length - 1,
                    indx = 0,
                    val = mask;
                if (len < 1 && maskLen > 1) {
                    // only one 'word' in query but mask has >1 slots
                    query[1] = query[0];
                }
                // replace all {query} with query words...
                // if query = 'Bob', then convert mask from '!{query}' to '!Bob'
                // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
                while (regex.test(val)) {
                    val = val.replace(regex, query[indx++] || '');
                    if (regex.test(val) && indx < len && (query[indx] || '') !== '') {
                        val = mask.replace(regex, val);
                    }
                }
                return val;
            },
            getLatestSearch: function ($input) {
                if ($input) {
                    return $input.sort(function (a, b) {
                        return $(b).attr('data-lastSearchTime') - $(a).attr('data-lastSearchTime');
                    });
                }
                return $input || $();
            },
            findRange: function (c, val, ignoreRanges) {
                // look for multiple columns '1-3,4-6,8' in data-column
                var temp, ranges, range, start, end, singles, i, indx, len,
                    columns = [];
                if (/^[0-9]+$/.test(val)) {
                    // always return an array
                    return [parseInt(val, 10)];
                }
                // process column range
                if (!ignoreRanges && /-/.test(val)) {
                    ranges = val.match(/(\d+)\s*-\s*(\d+)/g);
                    len = ranges ? ranges.length : 0;
                    for (indx = 0; indx < len; indx++) {
                        range = ranges[indx].split(/\s*-\s*/);
                        start = parseInt(range[0], 10) || 0;
                        end = parseInt(range[1], 10) || (c.columns - 1);
                        if (start > end) {
                            temp = start; start = end; end = temp; // swap
                        }
                        if (end >= c.columns) {
                            end = c.columns - 1;
                        }
                        for (; start <= end; start++) {
                            columns[columns.length] = start;
                        }
                        // remove processed range from val
                        val = val.replace(ranges[indx], '');
                    }
                }
                // process single columns
                if (!ignoreRanges && /,/.test(val)) {
                    singles = val.split(/\s*,\s*/);
                    len = singles.length;
                    for (i = 0; i < len; i++) {
                        if (singles[i] !== '') {
                            indx = parseInt(singles[i], 10);
                            if (indx < c.columns) {
                                columns[columns.length] = indx;
                            }
                        }
                    }
                }
                // return all columns
                if (!columns.length) {
                    for (indx = 0; indx < c.columns; indx++) {
                        columns[columns.length] = indx;
                    }
                }
                return columns;
            },
            getColumnElm: function (c, $elements, column) {
                // data-column may contain multiple columns '1-3,5-6,8'
                // replaces: c.$filters.filter( '[data-column="' + column + '"]' );
                return $elements.filter(function () {
                    var cols = tsf.findRange(c, $(this).attr('data-column'));
                    return $.inArray(column, cols) > -1;
                });
            },
            multipleColumns: function (c, $input) {
                // look for multiple columns '1-3,4-6,8' in data-column
                var wo = c.widgetOptions,
                    // only target 'all' column inputs on initialization
                    // & don't target 'all' column inputs if they don't exist
                    targets = wo.filter_initialized || !$input.filter(wo.filter_anyColumnSelector).length,
                    val = $.trim(tsf.getLatestSearch($input).attr('data-column') || '');
                return tsf.findRange(c, val, !targets);
            },
            processTypes: function (c, data, vars) {
                var ffxn,
                    filterMatched = null,
                    matches = null;
                for (ffxn in tsf.types) {
                    if ($.inArray(ffxn, vars.excludeMatch) < 0 && matches === null) {
                        matches = tsf.types[ffxn](c, data, vars);
                        if (matches !== null) {
                            filterMatched = matches;
                        }
                    }
                }
                return filterMatched;
            },
            matchType: function (c, columnIndex) {
                var isMatch,
                    wo = c.widgetOptions,
                    $el = c.$headerIndexed[columnIndex];
                // filter-exact > filter-match > filter_matchType for type
                if ($el.hasClass('filter-exact')) {
                    isMatch = false;
                } else if ($el.hasClass('filter-match')) {
                    isMatch = true;
                } else {
                    // filter-select is not applied when filter_functions are used, so look for a select
                    if (wo.filter_columnFilters) {
                        $el = c.$filters
                            .find('.' + tscss.filter)
                            .add(wo.filter_$externalFilters)
                            .filter('[data-column="' + columnIndex + '"]');
                    } else if (wo.filter_$externalFilters) {
                        $el = wo.filter_$externalFilters.filter('[data-column="' + columnIndex + '"]');
                    }
                    isMatch = $el.length ?
                        c.widgetOptions.filter_matchType[($el[0].nodeName || '').toLowerCase()] === 'match' :
                        // default to exact, if no inputs found
                        false;
                }
                return isMatch;
            },
            processRow: function (c, data, vars) {
                var result, filterMatched,
                    fxn, ffxn, txt,
                    wo = c.widgetOptions,
                    showRow = true,
                    hasAnyMatchInput = wo.filter_$anyMatch && wo.filter_$anyMatch.length,

                    // if wo.filter_$anyMatch data-column attribute is changed dynamically
                    // we don't want to do an "anyMatch" search on one column using data
                    // for the entire row - see #998
                    columnIndex = wo.filter_$anyMatch && wo.filter_$anyMatch.length ?
                        // look for multiple columns '1-3,4-6,8'
                        tsf.multipleColumns(c, wo.filter_$anyMatch) :
                        [];
                data.$cells = data.$row.children();
                if (data.anyMatchFlag && columnIndex.length > 1 || (data.anyMatchFilter && !hasAnyMatchInput)) {
                    data.anyMatch = true;
                    data.isMatch = true;
                    data.rowArray = data.$cells.map(function (i) {
                        if ($.inArray(i, columnIndex) > -1 || (data.anyMatchFilter && !hasAnyMatchInput)) {
                            if (data.parsed[i]) {
                                txt = data.cacheArray[i];
                            } else {
                                txt = data.rawArray[i];
                                txt = $.trim(wo.filter_ignoreCase ? txt.toLowerCase() : txt);
                                if (c.sortLocaleCompare) {
                                    txt = ts.replaceAccents(txt);
                                }
                            }
                            return txt;
                        }
                    }).get();
                    data.filter = data.anyMatchFilter;
                    data.iFilter = data.iAnyMatchFilter;
                    data.exact = data.rowArray.join(' ');
                    data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
                    data.cache = data.cacheArray.slice(0, -1).join(' ');
                    vars.excludeMatch = vars.noAnyMatch;
                    filterMatched = tsf.processTypes(c, data, vars);
                    if (filterMatched !== null) {
                        showRow = filterMatched;
                    } else {
                        if (wo.filter_startsWith) {
                            showRow = false;
                            // data.rowArray may not contain all columns
                            columnIndex = Math.min(c.columns, data.rowArray.length);
                            while (!showRow && columnIndex > 0) {
                                columnIndex--;
                                showRow = showRow || data.rowArray[columnIndex].indexOf(data.iFilter) === 0;
                            }
                        } else {
                            showRow = (data.iExact + data.childRowText).indexOf(data.iFilter) >= 0;
                        }
                    }
                    data.anyMatch = false;
                    // no other filters to process
                    if (data.filters.join('') === data.filter) {
                        return showRow;
                    }
                }

                for (columnIndex = 0; columnIndex < c.columns; columnIndex++) {
                    data.filter = data.filters[columnIndex];
                    data.index = columnIndex;

                    // filter types to exclude, per column
                    vars.excludeMatch = vars.excludeFilter[columnIndex];

                    // ignore if filter is empty or disabled
                    if (data.filter) {
                        data.cache = data.cacheArray[columnIndex];
                        result = data.parsed[columnIndex] ? data.cache : data.rawArray[columnIndex] || '';
                        data.exact = c.sortLocaleCompare ? ts.replaceAccents(result) : result; // issue #405
                        data.iExact = !tsfRegex.type.test(typeof data.exact) && wo.filter_ignoreCase ?
                            data.exact.toLowerCase() : data.exact;
                        data.isMatch = tsf.matchType(c, columnIndex);

                        result = showRow; // if showRow is true, show that row

                        // in case select filter option has a different value vs text 'a - z|A through Z'
                        ffxn = wo.filter_columnFilters ?
                            c.$filters.add(wo.filter_$externalFilters)
                                .filter('[data-column="' + columnIndex + '"]')
                                .find('select option:selected')
                                .attr('data-function-name') || '' : '';
                        // replace accents - see #357
                        if (c.sortLocaleCompare) {
                            data.filter = ts.replaceAccents(data.filter);
                        }

                        // replace column specific default filters - see #1088
                        if (wo.filter_defaultFilter && tsfRegex.iQuery.test(vars.defaultColFilter[columnIndex])) {
                            data.filter = tsf.defaultFilter(data.filter, vars.defaultColFilter[columnIndex]);
                        }

                        // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
                        // data.filter = case sensitive
                        data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLowerCase() : data.filter;
                        fxn = vars.functions[columnIndex];
                        filterMatched = null;
                        if (fxn) {
                            if (fxn === true) {
                                // default selector uses exact match unless 'filter-match' class is found
                                filterMatched = data.isMatch ?
                                    // data.iExact may be a number
                                    ('' + data.iExact).search(data.iFilter) >= 0 :
                                    data.filter === data.exact;
                            } else if (typeof fxn === 'function') {
                                // filter callback( exact cell content, parser normalized content,
                                // filter input value, column index, jQuery row object )
                                filterMatched = fxn(data.exact, data.cache, data.filter, columnIndex, data.$row, c, data);
                            } else if (typeof fxn[ffxn || data.filter] === 'function') {
                                // selector option function
                                txt = ffxn || data.filter;
                                filterMatched =
                                    fxn[txt](data.exact, data.cache, data.filter, columnIndex, data.$row, c, data);
                            }
                        }
                        if (filterMatched === null) {
                            // cycle through the different filters
                            // filters return a boolean or null if nothing matches
                            filterMatched = tsf.processTypes(c, data, vars);
                            if (filterMatched !== null) {
                                result = filterMatched;
                                // Look for match, and add child row data for matching
                            } else {
                                txt = (data.iExact + data.childRowText).indexOf(tsf.parseFilter(c, data.iFilter, data));
                                result = ((!wo.filter_startsWith && txt >= 0) || (wo.filter_startsWith && txt === 0));
                            }
                        } else {
                            result = filterMatched;
                        }
                        showRow = (result) ? showRow : false;
                    }
                }
                return showRow;
            },
            findRows: function (table, filters, combinedFilters) {
                if (table.config.lastCombinedFilter === combinedFilters ||
                    !table.config.widgetOptions.filter_initialized) {
                    return;
                }
                var len, norm_rows, rowData, $rows, $row, rowIndex, tbodyIndex, $tbody, columnIndex,
                    isChild, childRow, lastSearch, showRow, showParent, time, val, indx,
                    notFiltered, searchFiltered, query, injected, res, id, txt,
                    storedFilters = $.extend([], filters),
                    c = table.config,
                    wo = c.widgetOptions,
                    // data object passed to filters; anyMatch is a flag for the filters
                    data = {
                        anyMatch: false,
                        filters: filters,
                        // regex filter type cache
                        filter_regexCache: []
                    },
                    vars = {
                        // anyMatch really screws up with these types of filters
                        noAnyMatch: ['range', 'operators'],
                        // cache filter variables that use ts.getColumnData in the main loop
                        functions: [],
                        excludeFilter: [],
                        defaultColFilter: [],
                        defaultAnyFilter: ts.getColumnData(table, wo.filter_defaultFilter, c.columns, true) || ''
                    };

                // parse columns after formatter, in case the class is added at that point
                data.parsed = [];
                for (columnIndex = 0; columnIndex < c.columns; columnIndex++) {
                    data.parsed[columnIndex] = wo.filter_useParsedData ||
                        // parser has a "parsed" parameter
                        (c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
                        // getData may not return 'parsed' if other 'filter-' class names exist
                        // ( e.g. <th class="filter-select filter-parsed"> )
                        ts.getData && ts.getData(c.$headerIndexed[columnIndex],
                            ts.getColumnData(table, c.headers, columnIndex), 'filter') === 'parsed' ||
                        c.$headerIndexed[columnIndex].hasClass('filter-parsed'));

                    vars.functions[columnIndex] =
                        ts.getColumnData(table, wo.filter_functions, columnIndex) ||
                        c.$headerIndexed[columnIndex].hasClass('filter-select');
                    vars.defaultColFilter[columnIndex] =
                        ts.getColumnData(table, wo.filter_defaultFilter, columnIndex) || '';
                    vars.excludeFilter[columnIndex] =
                        (ts.getColumnData(table, wo.filter_excludeFilter, columnIndex, true) || '').split(/\s+/);
                }

                if (c.debug) {
                    console.log('Filter: Starting filter widget search', filters);
                    time = new Date();
                }
                // filtered rows count
                c.filteredRows = 0;
                c.totalRows = 0;
                // combindedFilters are undefined on init
                combinedFilters = (storedFilters || []).join('');

                for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) {
                    $tbody = ts.processTbody(table, c.$tbodies.eq(tbodyIndex), true);
                    // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
                    // $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
                    columnIndex = c.columns;
                    // convert stored rows into a jQuery object
                    norm_rows = c.cache[tbodyIndex].normalized;
                    $rows = $($.map(norm_rows, function (el) {
                        return el[columnIndex].$row.get();
                    }));

                    if (combinedFilters === '' || wo.filter_serversideFiltering) {
                        $rows
                            .removeClass(wo.filter_filteredRow)
                            .not('.' + c.cssChildRow)
                            .css('display', '');
                    } else {
                        // filter out child rows
                        $rows = $rows.not('.' + c.cssChildRow);
                        len = $rows.length;

                        if ((wo.filter_$anyMatch && wo.filter_$anyMatch.length) ||
                            typeof filters[c.columns] !== 'undefined') {
                            data.anyMatchFlag = true;
                            data.anyMatchFilter = '' + (
                                filters[c.columns] ||
                                wo.filter_$anyMatch && tsf.getLatestSearch(wo.filter_$anyMatch).val() ||
                                ''
                            );
                            if (wo.filter_columnAnyMatch) {
                                // specific columns search
                                query = data.anyMatchFilter.split(tsfRegex.andSplit);
                                injected = false;
                                for (indx = 0; indx < query.length; indx++) {
                                    res = query[indx].split(':');
                                    if (res.length > 1) {
                                        // make the column a one-based index ( non-developers start counting from one :P )
                                        if (isNaN(res[0])) {
                                            $.each(c.headerContent, function (i, txt) {
                                                // multiple matches are possible
                                                if (txt.toLowerCase().indexOf(res[0]) > -1) {
                                                    id = i;
                                                    filters[id] = res[1];
                                                }
                                            });
                                        } else {
                                            id = parseInt(res[0], 10) - 1;
                                        }
                                        if (id >= 0 && id < c.columns) { // if id is an integer
                                            filters[id] = res[1];
                                            query.splice(indx, 1);
                                            indx--;
                                            injected = true;
                                        }
                                    }
                                }
                                if (injected) {
                                    data.anyMatchFilter = query.join(' && ');
                                }
                            }
                        }

                        // optimize searching only through already filtered rows - see #313
                        searchFiltered = wo.filter_searchFiltered;
                        lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
                        if (searchFiltered) {
                            // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
                            for (indx = 0; indx < columnIndex + 1; indx++) {
                                val = filters[indx] || '';
                                // break out of loop if we've already determined not to search filtered rows
                                if (!searchFiltered) { indx = columnIndex; }
                                // search already filtered rows if...
                                searchFiltered = searchFiltered && lastSearch.length &&
                                    // there are no changes from beginning of filter
                                    val.indexOf(lastSearch[indx] || '') === 0 &&
                                    // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
                                    !tsfRegex.alreadyFiltered.test(val) &&
                                    // if we are not doing exact matches, using '|' ( logical or ) or not '!'
                                    !tsfRegex.exactTest.test(val) &&
                                    // don't search only filtered if the value is negative
                                    // ( '> -10' => '> -100' will ignore hidden rows )
                                    !(tsfRegex.isNeg1.test(val) || tsfRegex.isNeg2.test(val)) &&
                                    // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
                                    !(val !== '' && c.$filters && c.$filters.filter('[data-column="' + indx + '"]').find('select').length &&
                                        !tsf.matchType(c, indx));
                            }
                        }
                        notFiltered = $rows.not('.' + wo.filter_filteredRow).length;
                        // can't search when all rows are hidden - this happens when looking for exact matches
                        if (searchFiltered && notFiltered === 0) { searchFiltered = false; }
                        if (c.debug) {
                            console.log('Filter: Searching through ' +
                                (searchFiltered && notFiltered < len ? notFiltered : 'all') + ' rows');
                        }
                        if (data.anyMatchFlag) {
                            if (c.sortLocaleCompare) {
                                // replace accents
                                data.anyMatchFilter = ts.replaceAccents(data.anyMatchFilter);
                            }
                            if (wo.filter_defaultFilter && tsfRegex.iQuery.test(vars.defaultAnyFilter)) {
                                data.anyMatchFilter = tsf.defaultFilter(data.anyMatchFilter, vars.defaultAnyFilter);
                                // clear search filtered flag because default filters are not saved to the last search
                                searchFiltered = false;
                            }
                            // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
                            // when c.ignoreCase is true, the cache contains all lower case data
                            data.iAnyMatchFilter = !(wo.filter_ignoreCase && c.ignoreCase) ?
                                data.anyMatchFilter :
                                data.anyMatchFilter.toLowerCase();
                        }

                        // loop through the rows
                        for (rowIndex = 0; rowIndex < len; rowIndex++) {

                            txt = $rows[rowIndex].className;
                            // the first row can never be a child row
                            isChild = rowIndex && tsfRegex.child.test(txt);
                            // skip child rows & already filtered rows
                            if (isChild || (searchFiltered && tsfRegex.filtered.test(txt))) {
                                continue;
                            }

                            data.$row = $rows.eq(rowIndex);
                            data.rowIndex = rowIndex;
                            data.cacheArray = norm_rows[rowIndex];
                            rowData = data.cacheArray[c.columns];
                            data.rawArray = rowData.raw;
                            data.childRowText = '';

                            if (!wo.filter_childByColumn) {
                                txt = '';
                                // child row cached text
                                childRow = rowData.child;
                                // so, if 'table.config.widgetOptions.filter_childRows' is true and there is
                                // a match anywhere in the child row, then it will make the row visible
                                // checked here so the option can be changed dynamically
                                for (indx = 0; indx < childRow.length; indx++) {
                                    txt += ' ' + childRow[indx].join(' ') || '';
                                }
                                data.childRowText = wo.filter_childRows ?
                                    (wo.filter_ignoreCase ? txt.toLowerCase() : txt) :
                                    '';
                            }

                            showRow = false;
                            showParent = tsf.processRow(c, data, vars);
                            $row = rowData.$row;

                            // don't pass reference to val
                            val = showParent ? true : false;
                            childRow = rowData.$row.filter(':gt(0)');
                            if (wo.filter_childRows && childRow.length) {
                                if (wo.filter_childByColumn) {
                                    if (!wo.filter_childWithSibs) {
                                        // hide all child rows
                                        childRow.addClass(wo.filter_filteredRow);
                                        // if only showing resulting child row, only include parent
                                        $row = $row.eq(0);
                                    }
                                    // cycle through each child row
                                    for (indx = 0; indx < childRow.length; indx++) {
                                        data.$row = childRow.eq(indx);
                                        data.cacheArray = rowData.child[indx];
                                        data.rawArray = data.cacheArray;
                                        val = tsf.processRow(c, data, vars);
                                        // use OR comparison on child rows
                                        showRow = showRow || val;
                                        if (!wo.filter_childWithSibs && val) {
                                            childRow.eq(indx).removeClass(wo.filter_filteredRow);
                                        }
                                    }
                                }
                                // keep parent row match even if no child matches... see #1020
                                showRow = showRow || showParent;
                            } else {
                                showRow = val;
                            }
                            $row
                                .toggleClass(wo.filter_filteredRow, !showRow)[0]
                                .display = showRow ? '' : 'none';
                        }
                    }
                    c.filteredRows += $rows.not('.' + wo.filter_filteredRow).length;
                    c.totalRows += $rows.length;
                    ts.processTbody(table, $tbody, false);
                }
                c.lastCombinedFilter = combinedFilters; // save last search
                // don't save 'filters' directly since it may have altered ( AnyMatch column searches )
                c.lastSearch = storedFilters;
                c.$table.data('lastSearch', storedFilters);
                if (wo.filter_saveFilters && ts.storage) {
                    ts.storage(table, 'tablesorter-filters', tsf.processFilters(storedFilters, true));
                }
                if (c.debug) {
                    console.log('Completed filter widget search' + ts.benchmark(time));
                }
                if (wo.filter_initialized) {
                    c.$table.triggerHandler('filterBeforeEnd', c);
                    c.$table.triggerHandler('filterEnd', c);
                }
                setTimeout(function () {
                    ts.applyWidget(c.table); // make sure zebra widget is applied
                }, 0);
            },
            getOptionSource: function (table, column, onlyAvail) {
                table = $(table)[0];
                var c = table.config,
                    wo = c.widgetOptions,
                    arry = false,
                    source = wo.filter_selectSource,
                    last = c.$table.data('lastSearch') || [],
                    fxn = typeof source === 'function' ? true : ts.getColumnData(table, source, column);

                if (onlyAvail && last[column] !== '') {
                    onlyAvail = false;
                }

                // filter select source option
                if (fxn === true) {
                    // OVERALL source
                    arry = source(table, column, onlyAvail);
                } else if (fxn instanceof $ || ($.type(fxn) === 'string' && fxn.indexOf('</option>') >= 0)) {
                    // selectSource is a jQuery object or string of options
                    return fxn;
                } else if ($.isArray(fxn)) {
                    arry = fxn;
                } else if ($.type(source) === 'object' && fxn) {
                    // custom select source function for a SPECIFIC COLUMN
                    arry = fxn(table, column, onlyAvail);
                }
                if (arry === false) {
                    // fall back to original method
                    arry = tsf.getOptions(table, column, onlyAvail);
                }

                return tsf.processOptions(table, column, arry);

            },
            processOptions: function (table, column, arry) {
                if (!$.isArray(arry)) {
                    return false;
                }
                table = $(table)[0];
                var cts, txt, indx, len, parsedTxt, str,
                    c = table.config,
                    validColumn = typeof column !== 'undefined' && column !== null && column >= 0 && column < c.columns,
                    direction = validColumn ? c.$headerIndexed[column].hasClass('filter-select-sort-desc') : false,
                    parsed = [];
                // get unique elements and sort the list
                // if $.tablesorter.sortText exists ( not in the original tablesorter ),
                // then natural sort the list otherwise use a basic sort
                arry = $.grep(arry, function (value, indx) {
                    if (value.text) {
                        return true;
                    }
                    return $.inArray(value, arry) === indx;
                });
                if (validColumn && c.$headerIndexed[column].hasClass('filter-select-nosort')) {
                    // unsorted select options
                    return arry;
                } else {
                    len = arry.length;
                    // parse select option values
                    for (indx = 0; indx < len; indx++) {
                        txt = arry[indx];
                        // check for object
                        str = txt.text ? txt.text : txt;
                        // sortNatural breaks if you don't pass it strings
                        parsedTxt = (validColumn && c.parsers && c.parsers.length &&
                            c.parsers[column].format(str, table, [], column) || str).toString();
                        parsedTxt = c.widgetOptions.filter_ignoreCase ? parsedTxt.toLowerCase() : parsedTxt;
                        // parse array data using set column parser; this DOES NOT pass the original
                        // table cell to the parser format function
                        if (txt.text) {
                            txt.parsed = parsedTxt;
                            parsed[parsed.length] = txt;
                        } else {
                            parsed[parsed.length] = {
                                text: txt,
                                // check parser length - fixes #934
                                parsed: parsedTxt
                            };
                        }
                    }
                    // sort parsed select options
                    cts = c.textSorter || '';
                    parsed.sort(function (a, b) {
                        var x = direction ? b.parsed : a.parsed,
                            y = direction ? a.parsed : b.parsed;
                        if (validColumn && typeof cts === 'function') {
                            // custom OVERALL text sorter
                            return cts(x, y, true, column, table);
                        } else if (validColumn && typeof cts === 'object' && cts.hasOwnProperty(column)) {
                            // custom text sorter for a SPECIFIC COLUMN
                            return cts[column](x, y, true, column, table);
                        } else if (ts.sortNatural) {
                            // fall back to natural sort
                            return ts.sortNatural(x, y);
                        }
                        // using an older version! do a basic sort
                        return true;
                    });
                    // rebuild arry from sorted parsed data
                    arry = [];
                    len = parsed.length;
                    for (indx = 0; indx < len; indx++) {
                        arry[arry.length] = parsed[indx];
                    }
                    return arry;
                }
            },
            getOptions: function (table, column, onlyAvail) {
                table = $(table)[0];
                var rowIndex, tbodyIndex, len, row, cache, indx, child, childLen,
                    c = table.config,
                    wo = c.widgetOptions,
                    arry = [];
                for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) {
                    cache = c.cache[tbodyIndex];
                    len = c.cache[tbodyIndex].normalized.length;
                    // loop through the rows
                    for (rowIndex = 0; rowIndex < len; rowIndex++) {
                        // get cached row from cache.row ( old ) or row data object
                        // ( new; last item in normalized array )
                        row = cache.row ?
                            cache.row[rowIndex] :
                            cache.normalized[rowIndex][c.columns].$row[0];
                        // check if has class filtered
                        if (onlyAvail && row.className.match(wo.filter_filteredRow)) {
                            continue;
                        }
                        // get non-normalized cell content
                        if (wo.filter_useParsedData ||
                            c.parsers[column].parsed ||
                            c.$headerIndexed[column].hasClass('filter-parsed')) {
                            arry[arry.length] = '' + cache.normalized[rowIndex][column];
                            // child row parsed data
                            if (wo.filter_childRows && wo.filter_childByColumn) {
                                childLen = cache.normalized[rowIndex][c.columns].$row.length - 1;
                                for (indx = 0; indx < childLen; indx++) {
                                    arry[arry.length] = '' + cache.normalized[rowIndex][c.columns].child[indx][column];
                                }
                            }
                        } else {
                            // get raw cached data instead of content directly from the cells
                            arry[arry.length] = cache.normalized[rowIndex][c.columns].raw[column];
                            // child row unparsed data
                            if (wo.filter_childRows && wo.filter_childByColumn) {
                                childLen = cache.normalized[rowIndex][c.columns].$row.length;
                                for (indx = 1; indx < childLen; indx++) {
                                    child = cache.normalized[rowIndex][c.columns].$row.eq(indx).children().eq(column);
                                    arry[arry.length] = '' + ts.getElementText(c, child, column);
                                }
                            }
                        }
                    }
                }
                return arry;
            },
            buildSelect: function (table, column, arry, updating, onlyAvail) {
                table = $(table)[0];
                column = parseInt(column, 10);
                if (!table.config.cache || $.isEmptyObject(table.config.cache)) {
                    return;
                }

                var indx, val, txt, t, $filters, $filter, option,
                    c = table.config,
                    wo = c.widgetOptions,
                    node = c.$headerIndexed[column],
                    // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
                    options = '<option value="">' +
                        (node.data('placeholder') ||
                            node.attr('data-placeholder') ||
                            wo.filter_placeholder.select || ''
                        ) + '</option>',
                    // Get curent filter value
                    currentValue = c.$table
                        .find('thead')
                        .find('select.' + tscss.filter + '[data-column="' + column + '"]')
                        .val();

                // nothing included in arry ( external source ), so get the options from
                // filter_selectSource or column data
                if (typeof arry === 'undefined' || arry === '') {
                    arry = tsf.getOptionSource(table, column, onlyAvail);
                }

                if ($.isArray(arry)) {
                    // build option list
                    for (indx = 0; indx < arry.length; indx++) {
                        option = arry[indx];
                        if (option.text) {
                            // OBJECT!! add data-function-name in case the value is set in filter_functions
                            option['data-function-name'] = typeof option.value === 'undefined' ? option.text : option.value;

                            // support jQuery < v1.8, otherwise the below code could be shortened to
                            // options += $( '<option>', option )[ 0 ].outerHTML;
                            options += '<option';
                            for (val in option) {
                                if (option.hasOwnProperty(val) && val !== 'text') {
                                    options += ' ' + val + '="' + option[val] + '"';
                                }
                            }
                            if (!option.value) {
                                options += ' value="' + option.text + '"';
                            }
                            options += '>' + option.text + '</option>';
                            // above code is needed in jQuery < v1.8

                            // make sure we don't turn an object into a string (objects without a "text" property)
                        } else if ('' + option !== '[object Object]') {
                            txt = option = ('' + option).replace(tsfRegex.quote, '&quot;');
                            val = txt;
                            // allow including a symbol in the selectSource array
                            // 'a-z|A through Z' so that 'a-z' becomes the option value
                            // and 'A through Z' becomes the option text
                            if (txt.indexOf(wo.filter_selectSourceSeparator) >= 0) {
                                t = txt.split(wo.filter_selectSourceSeparator);
                                val = t[0];
                                txt = t[1];
                            }
                            // replace quotes - fixes #242 & ignore empty strings
                            // see http://stackoverflow.com/q/14990971/145346
                            options += option !== '' ?
                                '<option ' +
                                    (val === txt ? '' : 'data-function-name="' + option + '" ') +
                                    'value="' + val + '">' + txt +
                                '</option>' : '';
                        }
                    }
                    // clear arry so it doesn't get appended twice
                    arry = [];
                }

                // update all selects in the same column ( clone thead in sticky headers &
                // any external selects ) - fixes 473
                $filters = (c.$filters ? c.$filters : c.$table.children('thead'))
                    .find('.' + tscss.filter);
                if (wo.filter_$externalFilters) {
                    $filters = $filters && $filters.length ?
                        $filters.add(wo.filter_$externalFilters) :
                        wo.filter_$externalFilters;
                }
                $filter = $filters.filter('select[data-column="' + column + '"]');

                // make sure there is a select there!
                if ($filter.length) {
                    $filter[updating ? 'html' : 'append'](options);
                    if (!$.isArray(arry)) {
                        // append options if arry is provided externally as a string or jQuery object
                        // options ( default value ) was already added
                        $filter.append(arry).val(currentValue);
                    }
                    $filter.val(currentValue);
                }
            },
            buildDefault: function (table, updating) {
                var columnIndex, $header, noSelect,
                    c = table.config,
                    wo = c.widgetOptions,
                    columns = c.columns;
                // build default select dropdown
                for (columnIndex = 0; columnIndex < columns; columnIndex++) {
                    $header = c.$headerIndexed[columnIndex];
                    noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
                    // look for the filter-select class; build/update it if found
                    if (($header.hasClass('filter-select') ||
                        ts.getColumnData(table, wo.filter_functions, columnIndex) === true) && noSelect) {
                        tsf.buildSelect(table, columnIndex, '', updating, $header.hasClass(wo.filter_onlyAvail));
                    }
                }
            }
        };

        // filter regex variable
        tsfRegex = tsf.regex;

        ts.getFilters = function (table, getRaw, setFilters, skipFirst) {
            var i, $filters, $column, cols,
                filters = [],
                c = table ? $(table)[0].config : '',
                wo = c ? c.widgetOptions : '';
            if ((getRaw !== true && wo && !wo.filter_columnFilters) ||
                // setFilters called, but last search is exactly the same as the current
                // fixes issue #733 & #903 where calling update causes the input values to reset
                ($.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter)) {
                return $(table).data('lastSearch');
            }
            if (c) {
                if (c.$filters) {
                    $filters = c.$filters.find('.' + tscss.filter);
                }
                if (wo.filter_$externalFilters) {
                    $filters = $filters && $filters.length ?
                        $filters.add(wo.filter_$externalFilters) :
                        wo.filter_$externalFilters;
                }
                if ($filters && $filters.length) {
                    filters = setFilters || [];
                    for (i = 0; i < c.columns + 1; i++) {
                        cols = (i === c.columns ?
                            // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
                            wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
                            '[data-column="' + i + '"]');
                        $column = $filters.filter(cols);
                        if ($column.length) {
                            // move the latest search to the first slot in the array
                            $column = tsf.getLatestSearch($column);
                            if ($.isArray(setFilters)) {
                                // skip first ( latest input ) to maintain cursor position while typing
                                if (skipFirst && $column.length > 1) {
                                    $column = $column.slice(1);
                                }
                                if (i === c.columns) {
                                    // prevent data-column='all' from filling data-column='0,1' ( etc )
                                    cols = $column.filter(wo.filter_anyColumnSelector);
                                    $column = cols.length ? cols : $column;
                                }
                                $column
                                    .val(setFilters[i])
                                    // must include a namespace here; but not c.namespace + 'filter'?
                                    .trigger('change' + c.namespace);
                            } else {
                                filters[i] = $column.val() || '';
                                // don't change the first... it will move the cursor
                                if (i === c.columns) {
                                    // don't update range columns from 'all' setting
                                    $column
                                        .slice(1)
                                        .filter('[data-column*="' + $column.attr('data-column') + '"]')
                                        .val(filters[i]);
                                } else {
                                    $column
                                        .slice(1)
                                        .val(filters[i]);
                                }
                            }
                            // save any match input dynamically
                            if (i === c.columns && $column.length) {
                                wo.filter_$anyMatch = $column;
                            }
                        }
                    }
                }
            }
            return filters;
        };

        ts.setFilters = function (table, filter, apply, skipFirst) {
            var c = table ? $(table)[0].config : '',
                valid = ts.getFilters(table, true, filter, skipFirst);
            // default apply to "true"
            if (typeof apply === 'undefined') {
                apply = true;
            }
            if (c && apply) {
                // ensure new set filters are applied, even if the search is the same
                c.lastCombinedFilter = null;
                c.lastSearch = [];
                tsf.searching(c.table, filter, skipFirst);
                c.$table.triggerHandler('filterFomatterUpdate');
            }
            return valid.length !== 0;
        };

    })(jQuery);

    /*! Widget: stickyHeaders - updated 7/31/2016 (v2.27.0) *//*
 * Requires tablesorter v2.8+ and jQuery 1.4.3+
 * by Rob Garrison
 */
    ; (function ($, window) {
        'use strict';
        var ts = $.tablesorter || {};

        $.extend(ts.css, {
            sticky: 'tablesorter-stickyHeader', // stickyHeader
            stickyVis: 'tablesorter-sticky-visible',
            stickyHide: 'tablesorter-sticky-hidden',
            stickyWrap: 'tablesorter-sticky-wrapper'
        });

        // Add a resize event to table headers
        ts.addHeaderResizeEvent = function (table, disable, settings) {
            table = $(table)[0]; // make sure we're using a dom element
            if (!table.config) { return; }
            var defaults = {
                timer: 250
            },
                options = $.extend({}, defaults, settings),
                c = table.config,
                wo = c.widgetOptions,
                checkSizes = function (triggerEvent) {
                    var index, headers, $header, sizes, width, height,
                        len = c.$headers.length;
                    wo.resize_flag = true;
                    headers = [];
                    for (index = 0; index < len; index++) {
                        $header = c.$headers.eq(index);
                        sizes = $header.data('savedSizes') || [0, 0]; // fixes #394
                        width = $header[0].offsetWidth;
                        height = $header[0].offsetHeight;
                        if (width !== sizes[0] || height !== sizes[1]) {
                            $header.data('savedSizes', [width, height]);
                            headers.push($header[0]);
                        }
                    }
                    if (headers.length && triggerEvent !== false) {
                        c.$table.triggerHandler('resize', [headers]);
                    }
                    wo.resize_flag = false;
                };
            clearInterval(wo.resize_timer);
            if (disable) {
                wo.resize_flag = false;
                return false;
            }
            checkSizes(false);
            wo.resize_timer = setInterval(function () {
                if (wo.resize_flag) { return; }
                checkSizes();
            }, options.timer);
        };

        // Sticky headers based on this awesome article:
        // http://css-tricks.com/13465-persistent-headers/
        // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech
        // **************************
        ts.addWidget({
            id: 'stickyHeaders',
            priority: 55, // sticky widget must be initialized after the filter widget!
            options: {
                stickyHeaders: '',       // extra class name added to the sticky header row
                stickyHeaders_appendTo: null, // jQuery selector or object to phycially attach the sticky headers
                stickyHeaders_attachTo: null, // jQuery selector or object to attach scroll listener to (overridden by xScroll & yScroll settings)
                stickyHeaders_xScroll: null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window)
                stickyHeaders_yScroll: null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window)
                stickyHeaders_offset: 0, // number or jquery selector targeting the position:fixed element
                stickyHeaders_filteredToTop: true, // scroll table top into view after filtering
                stickyHeaders_cloneId: '-sticky', // added to table ID, if it exists
                stickyHeaders_addResizeEvent: true, // trigger 'resize' event on headers
                stickyHeaders_includeCaption: true, // if false and a caption exist, it won't be included in the sticky header
                stickyHeaders_zIndex: 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs
            },
            format: function (table, c, wo) {
                // filter widget doesn't initialize on an empty table. Fixes #449
                if (c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters'))) {
                    return;
                }
                var index, len, $t,
                    $table = c.$table,
                    // add position: relative to attach element, hopefully it won't cause trouble.
                    $attach = $(wo.stickyHeaders_attachTo),
                    namespace = c.namespace + 'stickyheaders ',
                    // element to watch for the scroll event
                    $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window),
                    $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window),
                    $thead = $table.children('thead:first'),
                    $header = $thead.children('tr').not('.sticky-false').children(),
                    $tfoot = $table.children('tfoot'),
                    $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
                    stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
                    // is this table nested? If so, find parent sticky header wrapper (div, not table)
                    $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ?
                        $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [],
                    nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0,
                    // clone table, then wrap to make sticky header
                    $stickyTable = wo.$sticky = $table.clone()
                        .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table')
                        .wrap('<div class="' + ts.css.stickyWrap + '">'),
                    $stickyWrap = $stickyTable.parent()
                        .addClass(ts.css.stickyHide)
                        .css({
                            position: $attach.length ? 'absolute' : 'fixed',
                            padding: parseInt($stickyTable.parent().parent().css('padding-left'), 10),
                            top: stickyOffset + nestedStickyTop,
                            left: 0,
                            visibility: 'hidden',
                            zIndex: wo.stickyHeaders_zIndex || 2
                        }),
                    $stickyThead = $stickyTable.children('thead:first'),
                    $stickyCells,
                    laststate = '',
                    spacing = 0,
                    setWidth = function ($orig, $clone) {
                        var index, width, border, $cell, $this,
                            $cells = $orig.filter(':visible'),
                            len = $cells.length;
                        for (index = 0; index < len; index++) {
                            $cell = $clone.filter(':visible').eq(index);
                            $this = $cells.eq(index);
                            // code from https://github.com/jmosbech/StickyTableHeaders
                            if ($this.css('box-sizing') === 'border-box') {
                                width = $this.outerWidth();
                            } else {
                                if ($cell.css('border-collapse') === 'collapse') {
                                    if (window.getComputedStyle) {
                                        width = parseFloat(window.getComputedStyle($this[0], null).width);
                                    } else {
                                        // ie8 only
                                        border = parseFloat($this.css('border-width'));
                                        width = $this.outerWidth() - parseFloat($this.css('padding-left')) - parseFloat($this.css('padding-right')) - border;
                                    }
                                } else {
                                    width = $this.width();
                                }
                            }
                            $cell.css({
                                'width': width,
                                'min-width': width,
                                'max-width': width
                            });
                        }
                    },
                    resizeHeader = function () {
                        stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
                        spacing = 0;
                        $stickyWrap.css({
                            left: $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 :
                                    $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing,
                            width: $table.outerWidth()
                        });
                        setWidth($table, $stickyTable);
                        setWidth($header, $stickyCells);
                    },
                    scrollSticky = function (resizing) {
                        if (!$table.is(':visible')) { return; } // fixes #278
                        // Detect nested tables - fixes #724
                        nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0;
                        var offset = $table.offset(),
                            yWindow = $.isWindow($yScroll[0]), // $.isWindow needs jQuery 1.4.3
                            xWindow = $.isWindow($xScroll[0]),
                            attachTop = $attach.length ?
                                (yWindow ? $yScroll.scrollTop() : $yScroll.offset().top) :
                                $yScroll.scrollTop(),
                            captionHeight = wo.stickyHeaders_includeCaption ? 0 : $table.children('caption').height() || 0,
                            scrollTop = attachTop + stickyOffset + nestedStickyTop - captionHeight,
                            tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)) - captionHeight,
                            isVisible = (scrollTop > offset.top) && (scrollTop < offset.top + tableHeight) ? 'visible' : 'hidden',
                            cssSettings = { visibility: isVisible };
                        if ($attach.length) {
                            cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop();
                        }
                        if (xWindow) {
                            // adjust when scrolling horizontally - fixes issue #143
                            cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing;
                        }
                        if ($nestedSticky.length) {
                            cssSettings.top = (cssSettings.top || 0) + stickyOffset + nestedStickyTop;
                        }
                        $stickyWrap
                            .removeClass(ts.css.stickyVis + ' ' + ts.css.stickyHide)
                            .addClass(isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide)
                            .css(cssSettings);
                        if (isVisible !== laststate || resizing) {
                            // make sure the column widths match
                            resizeHeader();
                            laststate = isVisible;
                        }
                    };
                // only add a position relative if a position isn't already defined
                if ($attach.length && !$attach.css('position')) {
                    $attach.css('position', 'relative');
                }
                // fix clone ID, if it exists - fixes #271
                if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
                // clear out cloned table, except for sticky header
                // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing
                $stickyTable.find('thead:gt(0), tr.sticky-false').hide();
                $stickyTable.find('tbody, tfoot').remove();
                $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption);
                // issue #172 - find td/th in sticky header
                $stickyCells = $stickyThead.children().children();
                $stickyTable.css({ height: 0, width: 0, margin: 0 });
                // remove resizable block
                $stickyCells.find('.' + ts.css.resizer).remove();
                // update sticky header class names to match real header after sorting
                $table
                    .addClass('hasStickyHeaders')
                    .bind('pagerComplete' + namespace, function () {
                        resizeHeader();
                    });

                ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header));

                if (wo.stickyHeaders_appendTo) {
                    $(wo.stickyHeaders_appendTo).append($stickyWrap);
                } else {
                    // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
                    $table.after($stickyWrap);
                }

                // onRenderHeader is defined, we need to do something about it (fixes #641)
                if (c.onRenderHeader) {
                    $t = $stickyThead.children('tr').children();
                    len = $t.length;
                    for (index = 0; index < len; index++) {
                        // send second parameter
                        c.onRenderHeader.apply($t.eq(index), [index, c, $stickyTable]);
                    }
                }

                // make it sticky!
                $xScroll.add($yScroll)
                    .unbind(('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' '))
                    .bind('scroll resize '.split(' ').join(namespace), function (event) {
                        scrollSticky(event.type === 'resize');
                    });
                c.$table
                    .unbind('stickyHeadersUpdate' + namespace)
                    .bind('stickyHeadersUpdate' + namespace, function () {
                        scrollSticky(true);
                    });

                if (wo.stickyHeaders_addResizeEvent) {
                    ts.addHeaderResizeEvent(table);
                }

                // look for filter widget
                if ($table.hasClass('hasFilters') && wo.filter_columnFilters) {
                    // scroll table into view after filtering, if sticky header is active - #482
                    $table.bind('filterEnd' + namespace, function () {
                        // $(':focus') needs jQuery 1.6+
                        var $td = $(document.activeElement).closest('td'),
                            column = $td.parent().children().index($td);
                        // only scroll if sticky header is active
                        if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) {
                            // scroll to original table (not sticky clone)
                            window.scrollTo(0, $table.position().top);
                            // give same input/select focus; check if c.$filters exists; fixes #594
                            if (column >= 0 && c.$filters) {
                                c.$filters.eq(column).find('a, select, input').filter(':visible').focus();
                            }
                        }
                    });
                    ts.filter.bindSearch($table, $stickyCells.find('.' + ts.css.filter));
                    // support hideFilters
                    if (wo.filter_hideFilters) {
                        ts.filter.hideFilters(c, $stickyTable);
                    }
                }

                // resize table (Firefox)
                if (wo.stickyHeaders_addResizeEvent) {
                    $table.bind('resize' + c.namespace + 'stickyheaders', function () {
                        resizeHeader();
                    });
                }

                $table.triggerHandler('stickyHeadersInit');

            },
            remove: function (table, c, wo) {
                var namespace = c.namespace + 'stickyheaders ';
                c.$table
                    .removeClass('hasStickyHeaders')
                    .unbind(('pagerComplete resize filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' '))
                    .next('.' + ts.css.stickyWrap).remove();
                if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
                $(window)
                    .add(wo.stickyHeaders_xScroll)
                    .add(wo.stickyHeaders_yScroll)
                    .add(wo.stickyHeaders_attachTo)
                    .unbind(('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' '));
                ts.addHeaderResizeEvent(table, true);
            }
        });

    })(jQuery, window);

    /*! Widget: resizable - updated 6/28/2016 (v2.26.5) */
    /*jshint browser:true, jquery:true, unused:false */
    ; (function ($, window) {
        'use strict';
        var ts = $.tablesorter || {};

        $.extend(ts.css, {
            resizableContainer: 'tablesorter-resizable-container',
            resizableHandle: 'tablesorter-resizable-handle',
            resizableNoSelect: 'tablesorter-disableSelection',
            resizableStorage: 'tablesorter-resizable'
        });

        // Add extra scroller css
        $(function () {
            var s = '<style>' +
                'body.' + ts.css.resizableNoSelect + ' { -ms-user-select: none; -moz-user-select: -moz-none;' +
                    '-khtml-user-select: none; -webkit-user-select: none; user-select: none; }' +
                '.' + ts.css.resizableContainer + ' { position: relative; height: 1px; }' +
                // make handle z-index > than stickyHeader z-index, so the handle stays above sticky header
                '.' + ts.css.resizableHandle + ' { position: absolute; display: inline-block; width: 8px;' +
                    'top: 1px; cursor: ew-resize; z-index: 3; user-select: none; -moz-user-select: none; }' +
                '</style>';
            $(s).appendTo('body');
        });

        ts.resizable = {
            init: function (c, wo) {
                if (c.$table.hasClass('hasResizable')) { return; }
                c.$table.addClass('hasResizable');

                var noResize, $header, column, storedSizes, tmp,
                    $table = c.$table,
                    $parent = $table.parent(),
                    marginTop = parseInt($table.css('margin-top'), 10),

                // internal variables
                vars = wo.resizable_vars = {
                    useStorage: ts.storage && wo.resizable !== false,
                    $wrap: $parent,
                    mouseXPosition: 0,
                    $target: null,
                    $next: null,
                    overflow: $parent.css('overflow') === 'auto' ||
                        $parent.css('overflow') === 'scroll' ||
                        $parent.css('overflow-x') === 'auto' ||
                        $parent.css('overflow-x') === 'scroll',
                    storedSizes: []
                };

                // set default widths
                ts.resizableReset(c.table, true);

                // now get measurements!
                vars.tableWidth = $table.width();
                // attempt to autodetect
                vars.fullWidth = Math.abs($parent.width() - vars.tableWidth) < 20;

                /*
                // Hacky method to determine if table width is set to 'auto'
                // http://stackoverflow.com/a/20892048/145346
                if ( !vars.fullWidth ) {
                    tmp = $table.width();
                    $header = $table.wrap('<span>').parent(); // temp variable
                    storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0;
                    $table.css( 'margin-left', storedSizes + 50 );
                    vars.tableWidth = $header.width() > tmp ? 'auto' : tmp;
                    $table.css( 'margin-left', storedSizes ? storedSizes : '' );
                    $header = null;
                    $table.unwrap('<span>');
                }
                */

                if (vars.useStorage && vars.overflow) {
                    // save table width
                    ts.storage(c.table, 'tablesorter-table-original-css-width', vars.tableWidth);
                    tmp = ts.storage(c.table, 'tablesorter-table-resized-width') || 'auto';
                    ts.resizable.setWidth($table, tmp, true);
                }
                wo.resizable_vars.storedSizes = storedSizes = (vars.useStorage ?
                    ts.storage(c.table, ts.css.resizableStorage) :
                    []) || [];
                ts.resizable.setWidths(c, wo, storedSizes);
                ts.resizable.updateStoredSizes(c, wo);

                wo.$resizable_container = $('<div class="' + ts.css.resizableContainer + '">')
                    .css({ top: marginTop })
                    .insertBefore($table);
                // add container
                for (column = 0; column < c.columns; column++) {
                    $header = c.$headerIndexed[column];
                    tmp = ts.getColumnData(c.table, c.headers, column);
                    noResize = ts.getData($header, tmp, 'resizable') === 'false';
                    if (!noResize) {
                        $('<div class="' + ts.css.resizableHandle + '">')
                            .appendTo(wo.$resizable_container)
                            .attr({
                                'data-column': column,
                                'unselectable': 'on'
                            })
                            .data('header', $header)
                            .bind('selectstart', false);
                    }
                }
                ts.resizable.bindings(c, wo);
            },

            updateStoredSizes: function (c, wo) {
                var column, $header,
                    len = c.columns,
                    vars = wo.resizable_vars;
                vars.storedSizes = [];
                for (column = 0; column < len; column++) {
                    $header = c.$headerIndexed[column];
                    vars.storedSizes[column] = $header.is(':visible') ? $header.width() : 0;
                }
            },

            setWidth: function ($el, width, overflow) {
                // overflow tables need min & max width set as well
                $el.css({
                    'width': width,
                    'min-width': overflow ? width : '',
                    'max-width': overflow ? width : ''
                });
            },

            setWidths: function (c, wo, storedSizes) {
                var column, $temp,
                    vars = wo.resizable_vars,
                    $extra = $(c.namespace + '_extra_headers'),
                    $col = c.$table.children('colgroup').children('col');
                storedSizes = storedSizes || vars.storedSizes || [];
                // process only if table ID or url match
                if (storedSizes.length) {
                    for (column = 0; column < c.columns; column++) {
                        // set saved resizable widths
                        ts.resizable.setWidth(c.$headerIndexed[column], storedSizes[column], vars.overflow);
                        if ($extra.length) {
                            // stickyHeaders needs to modify min & max width as well
                            $temp = $extra.eq(column).add($col.eq(column));
                            ts.resizable.setWidth($temp, storedSizes[column], vars.overflow);
                        }
                    }
                    $temp = $(c.namespace + '_extra_table');
                    if ($temp.length && !ts.hasWidget(c.table, 'scroller')) {
                        ts.resizable.setWidth($temp, c.$table.outerWidth(), vars.overflow);
                    }
                }
            },

            setHandlePosition: function (c, wo) {
                var startPosition,
                    tableHeight = c.$table.height(),
                    $handles = wo.$resizable_container.children(),
                    handleCenter = Math.floor($handles.width() / 2);

                if (ts.hasWidget(c.table, 'scroller')) {
                    tableHeight = 0;
                    c.$table.closest('.' + ts.css.scrollerWrap).children().each(function () {
                        var $this = $(this);
                        // center table has a max-height set
                        tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height();
                    });
                }
                // subtract out table left position from resizable handles. Fixes #864
                startPosition = c.$table.position().left;
                $handles.each(function () {
                    var $this = $(this),
                        column = parseInt($this.attr('data-column'), 10),
                        columns = c.columns - 1,
                        $header = $this.data('header');
                    if (!$header) { return; } // see #859
                    if (!$header.is(':visible')) {
                        $this.hide();
                    } else if (column < columns || column === columns && wo.resizable_addLastColumn) {
                        $this.css({
                            display: 'inline-block',
                            height: tableHeight,
                            left: $header.position().left - startPosition + $header.outerWidth() - handleCenter
                        });
                    }
                });
            },

            // prevent text selection while dragging resize bar
            toggleTextSelection: function (c, wo, toggle) {
                var namespace = c.namespace + 'tsresize';
                wo.resizable_vars.disabled = toggle;
                $('body').toggleClass(ts.css.resizableNoSelect, toggle);
                if (toggle) {
                    $('body')
                        .attr('unselectable', 'on')
                        .bind('selectstart' + namespace, false);
                } else {
                    $('body')
                        .removeAttr('unselectable')
                        .unbind('selectstart' + namespace);
                }
            },

            bindings: function (c, wo) {
                var namespace = c.namespace + 'tsresize';
                wo.$resizable_container.children().bind('mousedown', function (event) {
                    // save header cell and mouse position
                    var column,
                        vars = wo.resizable_vars,
                        $extras = $(c.namespace + '_extra_headers'),
                        $header = $(event.target).data('header');

                    column = parseInt($header.attr('data-column'), 10);
                    vars.$target = $header = $header.add($extras.filter('[data-column="' + column + '"]'));
                    vars.target = column;

                    // if table is not as wide as it's parent, then resize the table
                    vars.$next = event.shiftKey || wo.resizable_targetLast ?
                        $header.parent().children().not('.resizable-false').filter(':last') :
                        $header.nextAll(':not(.resizable-false)').eq(0);

                    column = parseInt(vars.$next.attr('data-column'), 10);
                    vars.$next = vars.$next.add($extras.filter('[data-column="' + column + '"]'));
                    vars.next = column;

                    vars.mouseXPosition = event.pageX;
                    ts.resizable.updateStoredSizes(c, wo);
                    ts.resizable.toggleTextSelection(c, wo, true);
                });

                $(document)
                    .bind('mousemove' + namespace, function (event) {
                        var vars = wo.resizable_vars;
                        // ignore mousemove if no mousedown
                        if (!vars.disabled || vars.mouseXPosition === 0 || !vars.$target) { return; }
                        if (wo.resizable_throttle) {
                            clearTimeout(vars.timer);
                            vars.timer = setTimeout(function () {
                                ts.resizable.mouseMove(c, wo, event);
                            }, isNaN(wo.resizable_throttle) ? 5 : wo.resizable_throttle);
                        } else {
                            ts.resizable.mouseMove(c, wo, event);
                        }
                    })
                    .bind('mouseup' + namespace, function () {
                        if (!wo.resizable_vars.disabled) { return; }
                        ts.resizable.toggleTextSelection(c, wo, false);
                        ts.resizable.stopResize(c, wo);
                        ts.resizable.setHandlePosition(c, wo);
                    });

                // resizeEnd event triggered by scroller widget
                $(window).bind('resize' + namespace + ' resizeEnd' + namespace, function () {
                    ts.resizable.setHandlePosition(c, wo);
                });

                // right click to reset columns to default widths
                c.$table
                    .bind('columnUpdate' + namespace + ' pagerComplete' + namespace, function () {
                        ts.resizable.setHandlePosition(c, wo);
                    })
                    .find('thead:first')
                    .add($(c.namespace + '_extra_table').find('thead:first'))
                    .bind('contextmenu' + namespace, function () {
                        // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset
                        var allowClick = wo.resizable_vars.storedSizes.length === 0;
                        ts.resizableReset(c.table);
                        ts.resizable.setHandlePosition(c, wo);
                        wo.resizable_vars.storedSizes = [];
                        return allowClick;
                    });

            },

            mouseMove: function (c, wo, event) {
                if (wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target) { return; }
                // resize columns
                var column,
                    total = 0,
                    vars = wo.resizable_vars,
                    $next = vars.$next,
                    tar = vars.storedSizes[vars.target],
                    leftEdge = event.pageX - vars.mouseXPosition;
                if (vars.overflow) {
                    if (tar + leftEdge > 0) {
                        vars.storedSizes[vars.target] += leftEdge;
                        ts.resizable.setWidth(vars.$target, vars.storedSizes[vars.target], true);
                        // update the entire table width
                        for (column = 0; column < c.columns; column++) {
                            total += vars.storedSizes[column];
                        }
                        ts.resizable.setWidth(c.$table.add($(c.namespace + '_extra_table')), total);
                    }
                    if (!$next.length) {
                        // if expanding right-most column, scroll the wrapper
                        vars.$wrap[0].scrollLeft = c.$table.width();
                    }
                } else if (vars.fullWidth) {
                    vars.storedSizes[vars.target] += leftEdge;
                    vars.storedSizes[vars.next] -= leftEdge;
                    ts.resizable.setWidths(c, wo);
                } else {
                    vars.storedSizes[vars.target] += leftEdge;
                    ts.resizable.setWidths(c, wo);
                }
                vars.mouseXPosition = event.pageX;
                // dynamically update sticky header widths
                c.$table.triggerHandler('stickyHeadersUpdate');
            },

            stopResize: function (c, wo) {
                var vars = wo.resizable_vars;
                ts.resizable.updateStoredSizes(c, wo);
                if (vars.useStorage) {
                    // save all column widths
                    ts.storage(c.table, ts.css.resizableStorage, vars.storedSizes);
                    ts.storage(c.table, 'tablesorter-table-resized-width', c.$table.width());
                }
                vars.mouseXPosition = 0;
                vars.$target = vars.$next = null;
                // will update stickyHeaders, just in case, see #912
                c.$table.triggerHandler('stickyHeadersUpdate');
            }
        };

        // this widget saves the column widths if
        // $.tablesorter.storage function is included
        // **************************
        ts.addWidget({
            id: 'resizable',
            priority: 40,
            options: {
                resizable: true, // save column widths to storage
                resizable_addLastColumn: false,
                resizable_widths: [],
                resizable_throttle: false, // set to true (5ms) or any number 0-10 range
                resizable_targetLast: false,
                resizable_fullWidth: null
            },
            init: function (table, thisWidget, c, wo) {
                ts.resizable.init(c, wo);
            },
            format: function (table, c, wo) {
                ts.resizable.setHandlePosition(c, wo);
            },
            remove: function (table, c, wo, refreshing) {
                if (wo.$resizable_container) {
                    var namespace = c.namespace + 'tsresize';
                    c.$table.add($(c.namespace + '_extra_table'))
                        .removeClass('hasResizable')
                        .children('thead')
                        .unbind('contextmenu' + namespace);

                    wo.$resizable_container.remove();
                    ts.resizable.toggleTextSelection(c, wo, false);
                    ts.resizableReset(table, refreshing);
                    $(document).unbind('mousemove' + namespace + ' mouseup' + namespace);
                }
            }
        });

        ts.resizableReset = function (table, refreshing) {
            $(table).each(function () {
                var index, $t,
                    c = this.config,
                    wo = c && c.widgetOptions,
                    vars = wo.resizable_vars;
                if (table && c && c.$headerIndexed.length) {
                    // restore the initial table width
                    if (vars.overflow && vars.tableWidth) {
                        ts.resizable.setWidth(c.$table, vars.tableWidth, true);
                        if (vars.useStorage) {
                            ts.storage(table, 'tablesorter-table-resized-width', 'auto');
                        }
                    }
                    for (index = 0; index < c.columns; index++) {
                        $t = c.$headerIndexed[index];
                        if (wo.resizable_widths && wo.resizable_widths[index]) {
                            ts.resizable.setWidth($t, wo.resizable_widths[index], vars.overflow);
                        } else if (!$t.hasClass('resizable-false')) {
                            // don't clear the width of any column that is not resizable
                            ts.resizable.setWidth($t, '', vars.overflow);
                        }
                    }

                    // reset stickyHeader widths
                    c.$table.triggerHandler('stickyHeadersUpdate');
                    if (ts.storage && !refreshing) {
                        ts.storage(this, ts.css.resizableStorage, {});
                    }
                }
            });
        };

    })(jQuery, window);

    /*! Widget: saveSort - updated 10/31/2015 (v2.24.0) *//*
* Requires tablesorter v2.16+
* by Rob Garrison
*/
    ; (function ($) {
        'use strict';
        var ts = $.tablesorter || {};

        // this widget saves the last sort only if the
        // saveSort widget option is true AND the
        // $.tablesorter.storage function is included
        // **************************
        ts.addWidget({
            id: 'saveSort',
            priority: 20,
            options: {
                saveSort: true
            },
            init: function (table, thisWidget, c, wo) {
                // run widget format before all other widgets are applied to the table
                thisWidget.format(table, c, wo, true);
            },
            format: function (table, c, wo, init) {
                var stored, time,
                    $table = c.$table,
                    saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true
                    sortList = { 'sortList': c.sortList };
                if (c.debug) {
                    time = new Date();
                }
                if ($table.hasClass('hasSaveSort')) {
                    if (saveSort && table.hasInitialized && ts.storage) {
                        ts.storage(table, 'tablesorter-savesort', sortList);
                        if (c.debug) {
                            console.log('saveSort widget: Saving last sort: ' + c.sortList + ts.benchmark(time));
                        }
                    }
                } else {
                    // set table sort on initial run of the widget
                    $table.addClass('hasSaveSort');
                    sortList = '';
                    // get data
                    if (ts.storage) {
                        stored = ts.storage(table, 'tablesorter-savesort');
                        sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : '';
                        if (c.debug) {
                            console.log('saveSort: Last sort loaded: "' + sortList + '"' + ts.benchmark(time));
                        }
                        $table.bind('saveSortReset', function (event) {
                            event.stopPropagation();
                            ts.storage(table, 'tablesorter-savesort', '');
                        });
                    }
                    // init is true when widget init is run, this will run this widget before all other widgets have initialized
                    // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
                    if (init && sortList && sortList.length > 0) {
                        c.sortList = sortList;
                    } else if (table.hasInitialized && sortList && sortList.length > 0) {
                        // update sort change
                        ts.sortOn(c, sortList);
                    }
                }
            },
            remove: function (table, c) {
                c.$table.removeClass('hasSaveSort');
                // clear storage
                if (ts.storage) { ts.storage(table, 'tablesorter-savesort', ''); }
            }
        });

    })(jQuery);

    return jQuery.tablesorter;
}));


/*! Widget: output - updated 4/2/2017 (v2.28.6) *//*
 * Requires tablesorter v2.8+ and jQuery 1.7+
 * Modified from:
 * HTML Table to CSV: http://www.kunalbabre.com/projects/table2CSV.php (License unknown?)
 * Download-File-JS: https://github.com/PixelsCommander/Download-File-JS (http://www.apache.org/licenses/LICENSE-2.0)
 * FileSaver.js: https://github.com/eligrey/FileSaver.js (MIT)
 */
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery:false, alert:false */
; (function ($) {
	'use strict';

	var ts = $.tablesorter,

		output = ts.output = {

			event: 'outputTable',
			// Double click time is about 500ms; this value ignores double clicks
			// and prevents multiple windows from opening - issue in Firefox
			noDblClick: 600, // ms
			lastEvent: 0,
			// prevent overlapping multiple opens in case rendering of content in
			// popup or download is longer than noDblClick time.
			busy: false,

			// wrap line breaks & tabs in quotes
			regexQuote: /([^0+\n\t\x09\x0d\x0a]|<[^<]+>)/, // test if cell needs wrapping quotes
			regexBR: /(<br([\s\/])?>|\n)/g, // replace
			regexIMG: /<img[^>]+alt\s*=\s*['"]([^'"]+)['"][^>]*>/i, // match
			regexHTML: /<[^<]+>/g, // replace

			replaceCR: '\x0d\x0a',
			replaceTab: '\x09',

			popupTitle: 'Output',
			popupStyle: 'width:100%;height:100%;margin:0;resize:none;', // for textarea
			message: 'Your device does not support downloading. Please try again in desktop browser.',

			init: function (c) {
				c.$table
					.off(output.event)
					.on(output.event, function (e) {
						e.stopPropagation();
						// prevent multiple windows opening
						if (
							!output.busy &&
							(e.timeStamp - output.lastEvent > output.noDblClick)
						) {
							output.lastEvent = e.timeStamp;
							output.busy = true;
							// explicitly use table.config.widgetOptions because we want
							// the most up-to-date values; not the 'wo' from initialization
							output.process(c, c.widgetOptions);
						}
					});
			},

			processRow: function (c, $rows, isHeader, isJSON) {
				var $cell, $cells, cellsLen, rowIndex, row, col, indx, rowspanLen, colspanLen, txt,
					wo = c.widgetOptions,
					tmpRow = [],
					dupe = wo.output_duplicateSpans,
					addSpanIndex = isHeader && isJSON && wo.output_headerRows && $.isFunction(wo.output_callbackJSON),
					cellIndex = 0,
					rowsLength = $rows.length;

				for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) {
					if (!tmpRow[rowIndex]) { tmpRow[rowIndex] = []; }
					cellIndex = 0;
					$cells = $rows.eq(rowIndex).children();
					cellsLen = $cells.length;
					for (indx = 0; indx < cellsLen; indx++) {
						$cell = $cells.eq(indx);
						// process rowspans
						if ($cell.filter('[rowspan]').length) {
							rowspanLen = parseInt($cell.attr('rowspan'), 10) - 1;
							txt = output.formatData(c, wo, $cell, isHeader);
							for (row = 1; row <= rowspanLen; row++) {
								if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; }
								tmpRow[rowIndex + row][cellIndex] = isHeader ? txt : dupe ? txt : '';
							}
						}
						// process colspans
						if ($cell.filter('[colspan]').length) {
							colspanLen = parseInt($cell.attr('colspan'), 10) - 1;
							// allow data-attribute to be an empty string
							txt = output.formatData(c, wo, $cell, isHeader);
							for (col = 0; col < colspanLen; col++) {
								// if we're processing the header & making JSON, the header names need to be unique
								if ($cell.filter('[rowspan]').length) {
									rowspanLen = parseInt($cell.attr('rowspan'), 10);
									for (row = 0; row < rowspanLen; row++) {
										if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; }
										tmpRow[rowIndex + row][cellIndex + col] = addSpanIndex ?
											wo.output_callbackJSON($cell, txt, cellIndex + col) ||
											txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : '';
									}
								} else {
									tmpRow[rowIndex][cellIndex + col] = addSpanIndex ?
										wo.output_callbackJSON($cell, txt, cellIndex + col) ||
										txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : '';
								}
							}
						}

						// skip column if already defined
						while (typeof tmpRow[rowIndex][cellIndex] !== 'undefined') { cellIndex++; }

						tmpRow[rowIndex][cellIndex] = tmpRow[rowIndex][cellIndex] ||
							output.formatData(c, wo, $cell, isHeader);
						cellIndex++;
					}
				}
				return ts.output.removeColumns(c, wo, tmpRow);
			},

			// remove hidden/ignored columns
			removeColumns: function (c, wo, arry) {
				var rowIndex, row, colIndex,
					data = [],
					len = arry.length;
				for (rowIndex = 0; rowIndex < len; rowIndex++) {
					row = arry[rowIndex];
					data[rowIndex] = [];
					for (colIndex = 0; colIndex < c.columns; colIndex++) {
						if (!wo.output_hiddenColumnArray[colIndex]) {
							data[rowIndex].push(row[colIndex]);
						}
					}
				}
				return data;
			},

			process: function (c, wo) {
				var mydata, $this, $rows, headers, csvData, len, rowsLen, tmp,
					hasStringify = window.JSON && JSON.hasOwnProperty('stringify'),
					indx = 0,
					tmpData = (wo.output_separator || ',').toLowerCase(),
					outputJSON = tmpData === 'json',
					outputArray = tmpData === 'array',
					separator = outputJSON || outputArray ? ',' : wo.output_separator,
					saveRows = wo.output_saveRows,
					$el = c.$table;
				// regex to look for the set separator or HTML
				wo.output_regex = new RegExp('(' + (/\\/.test(separator) ? '\\' : '') + separator + ')');

				// make a list of hidden columns
				wo.output_hiddenColumnArray = [];
				for (indx = 0; indx < c.columns; indx++) {
					wo.output_hiddenColumnArray[indx] = $.inArray(indx, wo.output_ignoreColumns) > -1 ||
						(!wo.output_hiddenColumns && c.$headerIndexed[indx].css('display') === 'none' &&
							!c.$headerIndexed[indx].hasClass('tablesorter-scroller-hidden-column'));
				}

				// get header cells
				$this = $el
					.children('thead').children('tr')
					.not('.' + (ts.css.filterRow || 'tablesorter-filter-row'))
					.filter(function () {
						return wo.output_hiddenColumns || $(this).css('display') !== 'none';
					});
				headers = output.processRow(c, $this, true, outputJSON);

				// all tbody rows - do not include widget added rows (e.g. grouping widget headers)
				$rows = $el.children('tbody').children('tr').not(c.selectorRemove);

				// check for a filter callback function first! because
				// /^f/.test(function(){ console.log('test'); }) is TRUE! (function is converted to a string)
				$rows = typeof saveRows === 'function' ? $rows.filter(saveRows) :
					// get (f)iltered, (v)isible, all rows (look for the first letter only), or jQuery filter selector
					/^f/.test(saveRows) ? $rows.not('.' + (wo.filter_filteredRow || 'filtered')) :
						/^v/.test(saveRows) ? $rows.filter(':visible') :
							// look for '.' (class selector), '#' (id selector),
							// ':' (basic filters, e.g. ':not()') or '[' (attribute selector start)
							/^[.#:\[]/.test(saveRows) ? $rows.filter(saveRows) :
								// default to all rows
								$rows;

				// process to array of arrays
				csvData = output.processRow(c, $rows);
				if (wo.output_includeFooter) {
					// clone, to force the tfoot rows to the end of this selection of rows
					// otherwise they appear after the thead (the order in the HTML)
					csvData = csvData.concat(output.processRow(c, $el.children('tfoot').children('tr:visible')));
				}

				len = headers.length;

				if (outputJSON) {
					tmpData = [];
					rowsLen = csvData.length;
					for (indx = 0; indx < rowsLen; indx++) {
						// multiple header rows & output_headerRows = true, pick the last row...
						tmp = headers[(len > 1 && wo.output_headerRows) ? indx % len : len - 1];
						tmpData.push(output.row2Hash(tmp, csvData[indx]));
					}

					// requires JSON stringify; if it doesn't exist, the output will show [object Object],... in the output window
					mydata = hasStringify ? JSON.stringify(tmpData) : tmpData;
				} else {
					if (wo.output_includeHeader) {
						tmp = [headers[(len > 1 && wo.output_headerRows) ? indx % len : len - 1]];
						tmpData = output.row2CSV(wo, wo.output_headerRows ? headers : tmp, outputArray)
							.concat(output.row2CSV(wo, csvData, outputArray));
					} else {
						tmpData = output.row2CSV(wo, csvData, outputArray);
					}

					// stringify the array; if stringify doesn't exist the array will be flattened
					mydata = outputArray && hasStringify ? JSON.stringify(tmpData) : tmpData.join('\n');
				}

				// callback; if true returned, continue processing
				if ($.isFunction(wo.output_callback)) {
					tmp = wo.output_callback(c, mydata, c.pager && c.pager.ajaxObject.url || null);
					if (tmp === false) {
						return;
					} else if (typeof tmp === 'string') {
						mydata = tmp;
					}
				}

				if (/p/i.test(wo.output_delivery || '')) {
					output.popup(mydata, wo.output_popupStyle, outputJSON || outputArray);
				} else {
					output.download(c, wo, mydata);
				}
				output.busy = false;

			}, // end process

			row2CSV: function (wo, tmpRow, outputArray) {
				var tmp, rowIndex,
					csvData = [],
					rowLen = tmpRow.length;
				for (rowIndex = 0; rowIndex < rowLen; rowIndex++) {
					// remove any blank rows
					tmp = (tmpRow[rowIndex] || []).join('').replace(/\"/g, '');
					if ((tmpRow[rowIndex] || []).length > 0 && tmp !== '') {
						csvData[csvData.length] = outputArray ? tmpRow[rowIndex] : tmpRow[rowIndex].join(wo.output_separator);
					}
				}
				return csvData;
			},

			row2Hash: function (keys, values) {
				var indx,
					json = {},
					len = values.length;
				for (indx = 0; indx < len; indx++) {
					if (indx < keys.length) {
						json[keys[indx]] = values[indx];
					}
				}
				return json;
			},

			formatData: function (c, wo, $el, isHeader) {
				var attr = $el.attr(wo.output_dataAttrib),
					txt = typeof attr !== 'undefined' ? attr : $el.html(),
					quotes = (wo.output_separator || ',').toLowerCase(),
					separator = quotes === 'json' || quotes === 'array',
					// replace " with “ if undefined
					result = txt.replace(/\"/g, wo.output_replaceQuote || '\u201c');
				// replace line breaks with \\n & tabs with \\t
				if (!wo.output_trimSpaces) {
					result = result.replace(output.regexBR, output.replaceCR).replace(/\t/g, output.replaceTab);
				} else {
					result = result.replace(output.regexBR, '');
				}
				// extract img alt text
				txt = result.match(output.regexIMG);
				if (!wo.output_includeHTML && txt !== null) {
					result = txt[1];
				}
				// replace/remove html
				result = wo.output_includeHTML && !isHeader ? result : result.replace(output.regexHTML, '');
				result = wo.output_trimSpaces || isHeader ? $.trim(result) : result;
				// JSON & array outputs don't need quotes
				quotes = separator ? false : wo.output_wrapQuotes || wo.output_regex.test(result) || output.regexQuote.test(result);
				result = quotes ? '"' + result + '"' : result;

				// formatting callback - added v2.22.4
				if (typeof wo.output_formatContent === 'function') {
					return wo.output_formatContent(c, wo, {
						isHeader: isHeader,
						$cell: $el,
						content: result
					});
				}

				return result;
			},

			popup: function (data, style, wrap) {
				var generator = window.open('', output.popupTitle, style);
				try {
					generator.document.write(
						'<html><head><title>' + output.popupTitle + '</title></head><body>' +
						'<textarea wrap="' + (wrap ? 'on' : 'off') + '" style="' +
						output.popupStyle + '">' + data + '\n</textarea>' +
						'</body></html>'
					);
					generator.document.close();
					generator.focus();
				} catch (e) {
					// popup already open
					generator.close();
					return output.popup(data, style, wrap);
				}
				// select all text and focus within the textarea in the popup
				// $(generator.document).find('textarea').select().focus();
				return true;
			},

			// modified from https://github.com/PixelsCommander/Download-File-JS
			// & http://html5-demos.appspot.com/static/a.download.html
			download: function (c, wo, data) {

				if (typeof wo.output_savePlugin === 'function') {
					return wo.output_savePlugin(c, wo, data);
				}

				var e, blob, gotBlob, bom,
					nav = window.navigator,
					link = document.createElement('a');

				// iOS devices do not support downloading. We have to inform user about
				// this limitation.
				if (/(iP)/g.test(nav.userAgent)) {
					alert(output.message);
					return false;
				}

				// test for blob support
				try {
					gotBlob = !!new Blob();
				} catch (err) {
					gotBlob = false;
				}

				// Use HTML5 Blob if browser supports it
				if (gotBlob) {

					window.URL = window.URL || window.webkitURL;
					// prepend BOM for UTF-8 XML and text/* types (including HTML)
					// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
					// see https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js#L68
					bom = (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(wo.output_encoding)) ?
						['\ufeff', data] : [data];
					blob = new Blob(bom, { type: wo.output_encoding });

					if (nav.msSaveBlob) {
						// IE 10+
						nav.msSaveBlob(blob, wo.output_saveFileName);
					} else {
						// all other browsers
						link.href = window.URL.createObjectURL(blob);
						link.download = wo.output_saveFileName;
						// Dispatching click event; using $(link).trigger() won't work
						if (document.createEvent) {
							e = document.createEvent('MouseEvents');
							// event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY,
							// ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
							e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
							link.dispatchEvent(e);
						}
					}
					return false;
				}

				// fallback to force file download (whether supported by server).
				// not sure if this actually works in IE9 and older...
				window.open(wo.output_encoding + encodeURIComponent(data) + '?download', '_self');
				return true;

			},

			remove: function (c) {
				c.$table.off(output.event);
			}

		};

	ts.addWidget({
		id: 'output',
		options: {
			output_separator: ',',         // set to 'json', 'array' or any separator
			output_ignoreColumns: [],          // columns to ignore [0, 1,... ] (zero-based index)
			output_hiddenColumns: false,       // include hidden columns in the output
			output_includeFooter: false,       // include footer rows in the output
			output_includeHeader: true,        // include header rows in the output
			output_headerRows: false,       // if true, include multiple header rows
			output_dataAttrib: 'data-name', // header attrib containing modified header name
			output_delivery: 'popup',     // popup, download
			output_saveRows: 'filtered',  // (a)ll, (v)isible, (f)iltered or jQuery filter selector
			output_duplicateSpans: true,        // duplicate output data in tbody colspan/rowspan
			output_replaceQuote: '\u201c;',   // left double quote
			output_includeHTML: false,
			output_trimSpaces: true,
			output_wrapQuotes: false,
			output_popupStyle: 'width=500,height=300',
			output_saveFileName: 'mytable.csv',
			// format $cell content callback
			output_formatContent: null, // function(config, widgetOptions, data){ return data.content; }
			// callback executed when processing completes
			// return true to continue download/output
			// return false to stop delivery & do something else with the data
			output_callback: function (config, data) { return true; },
			// JSON callback executed when a colspan is encountered in the header
			output_callbackJSON: function ($cell, txt, cellIndex) { return txt + '(' + (cellIndex) + ')'; },
			// the need to modify this for Excel no longer exists
			output_encoding: 'data:application/octet-stream;charset=utf8,',
			// override internal save file code and use an external plugin such as
			// https://github.com/eligrey/FileSaver.js
			output_savePlugin: null /* function(c, wo, data) {
				var blob = new Blob([data], {type: wo.output_encoding});
				saveAs(blob, wo.output_saveFileName);
			} */
		},
		init: function (table, thisWidget, c) {
			output.init(c);
		},
		remove: function (table, c) {
			output.remove(c);
		}

	});

})(jQuery);

/*
Copyright 2014 Igor Vaynberg

Version: 3.4.6 Timestamp: Sat Mar 22 22:30:15 EDT 2014

This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
General Public License version 2 (the "GPL License"). You may choose either license to govern your
use of this software only upon the condition that you accept all of the terms of either the Apache
License or the GPL License.

You may obtain a copy of the Apache License and the GPL License at:

http://www.apache.org/licenses/LICENSE-2.0
http://www.gnu.org/licenses/gpl-2.0.html

Unless required by applicable law or agreed to in writing, software distributed under the Apache License
or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the Apache License and the GPL License for the specific language governing
permissions and limitations under the Apache License and the GPL License.
*/
!function(a){"undefined"==typeof a.fn.each2&&a.extend(a.fn,{each2:function(b){for(var c=a([0]),d=-1,e=this.length;++d<e&&(c.context=c[0]=this[d])&&b.call(c[0],d,c)!==!1;);return this}})}(jQuery),function(a,b){"use strict";function n(b){var c=a(document.createTextNode(""));b.before(c),c.before(b),c.remove()}function o(a){var b,c,d,e;if(!a||a.length<1)return a;for(b="",c=0,d=a.length;d>c;c++)e=a.charAt(c),b+=m[e]||e;return b}function p(a,b){for(var c=0,d=b.length;d>c;c+=1)if(r(a,b[c]))return c;return-1}function q(){var b=a(l);b.appendTo("body");var c={width:b.width()-b[0].clientWidth,height:b.height()-b[0].clientHeight};return b.remove(),c}function r(a,c){return a===c?!0:a===b||c===b?!1:null===a||null===c?!1:a.constructor===String?a+""==c+"":c.constructor===String?c+""==a+"":!1}function s(b,c){var d,e,f;if(null===b||b.length<1)return[];for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d}function t(a){return a.outerWidth(!1)-a.width()}function u(c){var d="keyup-change-value";c.on("keydown",function(){a.data(c,d)===b&&a.data(c,d,c.val())}),c.on("keyup",function(){var e=a.data(c,d);e!==b&&c.val()!==e&&(a.removeData(c,d),c.trigger("keyup-change"))})}function v(c){c.on("mousemove",function(c){var d=i;(d===b||d.x!==c.pageX||d.y!==c.pageY)&&a(c.target).trigger("mousemove-filtered",c)})}function w(a,c,d){d=d||b;var e;return function(){var b=arguments;window.clearTimeout(e),e=window.setTimeout(function(){c.apply(d,b)},a)}}function x(a){var c,b=!1;return function(){return b===!1&&(c=a(),b=!0),c}}function y(a,b){var c=w(a,function(a){b.trigger("scroll-debounced",a)});b.on("scroll",function(a){p(a.target,b.get())>=0&&c(a)})}function z(a){a[0]!==document.activeElement&&window.setTimeout(function(){var d,b=a[0],c=a.val().length;a.focus();var e=b.offsetWidth>0||b.offsetHeight>0;e&&b===document.activeElement&&(b.setSelectionRange?b.setSelectionRange(c,c):b.createTextRange&&(d=b.createTextRange(),d.collapse(!1),d.select()))},0)}function A(b){b=a(b)[0];var c=0,d=0;if("selectionStart"in b)c=b.selectionStart,d=b.selectionEnd-c;else if("selection"in document){b.focus();var e=document.selection.createRange();d=document.selection.createRange().text.length,e.moveStart("character",-b.value.length),c=e.text.length-d}return{offset:c,length:d}}function B(a){a.preventDefault(),a.stopPropagation()}function C(a){a.preventDefault(),a.stopImmediatePropagation()}function D(b){if(!h){var c=b[0].currentStyle||window.getComputedStyle(b[0],null);h=a(document.createElement("div")).css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),h.attr("class","select2-sizer"),a("body").append(h)}return h.text(b.val()),h.width()}function E(b,c,d){var e,g,f=[];e=b.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0===this.indexOf("select2-")&&f.push(this)})),e=c.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0!==this.indexOf("select2-")&&(g=d(this),g&&f.push(g))})),b.attr("class",f.join(" "))}function F(a,b,c,d){var e=o(a.toUpperCase()).indexOf(o(b.toUpperCase())),f=b.length;return 0>e?(c.push(d(a)),void 0):(c.push(d(a.substring(0,e))),c.push("<span class='select2-match'>"),c.push(d(a.substring(e,e+f))),c.push("</span>"),c.push(d(a.substring(e+f,a.length))),void 0)}function G(a){var b={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};return String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})}function H(c){var d,e=null,f=c.quietMillis||100,g=c.url,h=this;return function(i){window.clearTimeout(d),d=window.setTimeout(function(){var d=c.data,f=g,j=c.transport||a.fn.select2.ajaxDefaults.transport,k={type:c.type||"GET",cache:c.cache||!1,jsonpCallback:c.jsonpCallback||b,dataType:c.dataType||"json"},l=a.extend({},a.fn.select2.ajaxDefaults.params,k);d=d?d.call(h,i.term,i.page,i.context):null,f="function"==typeof f?f.call(h,i.term,i.page,i.context):f,e&&"function"==typeof e.abort&&e.abort(),c.params&&(a.isFunction(c.params)?a.extend(l,c.params.call(h)):a.extend(l,c.params)),a.extend(l,{url:f,dataType:c.dataType,data:d,success:function(a){var b=c.results(a,i.page);i.callback(b)}}),e=j.call(h,l)},f)}}function I(b){var d,e,c=b,f=function(a){return""+a.text};a.isArray(c)&&(e=c,c={results:e}),a.isFunction(c)===!1&&(e=c,c=function(){return e});var g=c();return g.text&&(f=g.text,a.isFunction(f)||(d=g.text,f=function(a){return a[d]})),function(b){var g,d=b.term,e={results:[]};return""===d?(b.callback(c()),void 0):(g=function(c,e){var h,i;if(c=c[0],c.children){h={};for(i in c)c.hasOwnProperty(i)&&(h[i]=c[i]);h.children=[],a(c.children).each2(function(a,b){g(b,h.children)}),(h.children.length||b.matcher(d,f(h),c))&&e.push(h)}else b.matcher(d,f(c),c)&&e.push(c)},a(c().results).each2(function(a,b){g(b,e.results)}),b.callback(e),void 0)}}function J(c){var d=a.isFunction(c);return function(e){var f=e.term,g={results:[]};a(d?c():c).each(function(){var a=this.text!==b,c=a?this.text:this;(""===f||e.matcher(f,c))&&g.results.push(a?this:{id:this,text:this})}),e.callback(g)}}function K(b,c){if(a.isFunction(b))return!0;if(!b)return!1;if("string"==typeof b)return!0;throw new Error(c+" must be a string, function, or falsy value")}function L(b){if(a.isFunction(b)){var c=Array.prototype.slice.call(arguments,1);return b.apply(null,c)}return b}function M(b){var c=0;return a.each(b,function(a,b){b.children?c+=M(b.children):c++}),c}function N(a,c,d,e){var h,i,j,k,l,f=a,g=!1;if(!e.createSearchChoice||!e.tokenSeparators||e.tokenSeparators.length<1)return b;for(;;){for(i=-1,j=0,k=e.tokenSeparators.length;k>j&&(l=e.tokenSeparators[j],i=a.indexOf(l),!(i>=0));j++);if(0>i)break;if(h=a.substring(0,i),a=a.substring(i+l.length),h.length>0&&(h=e.createSearchChoice.call(this,h,c),h!==b&&null!==h&&e.id(h)!==b&&null!==e.id(h))){for(g=!1,j=0,k=c.length;k>j;j++)if(r(e.id(h),e.id(c[j]))){g=!0;break}g||d(h)}}return f!==a?a:void 0}function O(b,c){var d=function(){};return d.prototype=new b,d.prototype.constructor=d,d.prototype.parent=b.prototype,d.prototype=a.extend(d.prototype,c),d}if(window.Select2===b){var c,d,e,f,g,h,j,k,i={x:0,y:0},c={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(a){switch(a=a.which?a.which:a){case c.LEFT:case c.RIGHT:case c.UP:case c.DOWN:return!0}return!1},isControl:function(a){var b=a.which;switch(b){case c.SHIFT:case c.CTRL:case c.ALT:return!0}return a.metaKey?!0:!1},isFunctionKey:function(a){return a=a.which?a.which:a,a>=112&&123>=a}},l="<div class='select2-measure-scrollbar'></div>",m={"\u24b6":"A","\uff21":"A","\xc0":"A","\xc1":"A","\xc2":"A","\u1ea6":"A","\u1ea4":"A","\u1eaa":"A","\u1ea8":"A","\xc3":"A","\u0100":"A","\u0102":"A","\u1eb0":"A","\u1eae":"A","\u1eb4":"A","\u1eb2":"A","\u0226":"A","\u01e0":"A","\xc4":"A","\u01de":"A","\u1ea2":"A","\xc5":"A","\u01fa":"A","\u01cd":"A","\u0200":"A","\u0202":"A","\u1ea0":"A","\u1eac":"A","\u1eb6":"A","\u1e00":"A","\u0104":"A","\u023a":"A","\u2c6f":"A","\ua732":"AA","\xc6":"AE","\u01fc":"AE","\u01e2":"AE","\ua734":"AO","\ua736":"AU","\ua738":"AV","\ua73a":"AV","\ua73c":"AY","\u24b7":"B","\uff22":"B","\u1e02":"B","\u1e04":"B","\u1e06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24b8":"C","\uff23":"C","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\xc7":"C","\u1e08":"C","\u0187":"C","\u023b":"C","\ua73e":"C","\u24b9":"D","\uff24":"D","\u1e0a":"D","\u010e":"D","\u1e0c":"D","\u1e10":"D","\u1e12":"D","\u1e0e":"D","\u0110":"D","\u018b":"D","\u018a":"D","\u0189":"D","\ua779":"D","\u01f1":"DZ","\u01c4":"DZ","\u01f2":"Dz","\u01c5":"Dz","\u24ba":"E","\uff25":"E","\xc8":"E","\xc9":"E","\xca":"E","\u1ec0":"E","\u1ebe":"E","\u1ec4":"E","\u1ec2":"E","\u1ebc":"E","\u0112":"E","\u1e14":"E","\u1e16":"E","\u0114":"E","\u0116":"E","\xcb":"E","\u1eba":"E","\u011a":"E","\u0204":"E","\u0206":"E","\u1eb8":"E","\u1ec6":"E","\u0228":"E","\u1e1c":"E","\u0118":"E","\u1e18":"E","\u1e1a":"E","\u0190":"E","\u018e":"E","\u24bb":"F","\uff26":"F","\u1e1e":"F","\u0191":"F","\ua77b":"F","\u24bc":"G","\uff27":"G","\u01f4":"G","\u011c":"G","\u1e20":"G","\u011e":"G","\u0120":"G","\u01e6":"G","\u0122":"G","\u01e4":"G","\u0193":"G","\ua7a0":"G","\ua77d":"G","\ua77e":"G","\u24bd":"H","\uff28":"H","\u0124":"H","\u1e22":"H","\u1e26":"H","\u021e":"H","\u1e24":"H","\u1e28":"H","\u1e2a":"H","\u0126":"H","\u2c67":"H","\u2c75":"H","\ua78d":"H","\u24be":"I","\uff29":"I","\xcc":"I","\xcd":"I","\xce":"I","\u0128":"I","\u012a":"I","\u012c":"I","\u0130":"I","\xcf":"I","\u1e2e":"I","\u1ec8":"I","\u01cf":"I","\u0208":"I","\u020a":"I","\u1eca":"I","\u012e":"I","\u1e2c":"I","\u0197":"I","\u24bf":"J","\uff2a":"J","\u0134":"J","\u0248":"J","\u24c0":"K","\uff2b":"K","\u1e30":"K","\u01e8":"K","\u1e32":"K","\u0136":"K","\u1e34":"K","\u0198":"K","\u2c69":"K","\ua740":"K","\ua742":"K","\ua744":"K","\ua7a2":"K","\u24c1":"L","\uff2c":"L","\u013f":"L","\u0139":"L","\u013d":"L","\u1e36":"L","\u1e38":"L","\u013b":"L","\u1e3c":"L","\u1e3a":"L","\u0141":"L","\u023d":"L","\u2c62":"L","\u2c60":"L","\ua748":"L","\ua746":"L","\ua780":"L","\u01c7":"LJ","\u01c8":"Lj","\u24c2":"M","\uff2d":"M","\u1e3e":"M","\u1e40":"M","\u1e42":"M","\u2c6e":"M","\u019c":"M","\u24c3":"N","\uff2e":"N","\u01f8":"N","\u0143":"N","\xd1":"N","\u1e44":"N","\u0147":"N","\u1e46":"N","\u0145":"N","\u1e4a":"N","\u1e48":"N","\u0220":"N","\u019d":"N","\ua790":"N","\ua7a4":"N","\u01ca":"NJ","\u01cb":"Nj","\u24c4":"O","\uff2f":"O","\xd2":"O","\xd3":"O","\xd4":"O","\u1ed2":"O","\u1ed0":"O","\u1ed6":"O","\u1ed4":"O","\xd5":"O","\u1e4c":"O","\u022c":"O","\u1e4e":"O","\u014c":"O","\u1e50":"O","\u1e52":"O","\u014e":"O","\u022e":"O","\u0230":"O","\xd6":"O","\u022a":"O","\u1ece":"O","\u0150":"O","\u01d1":"O","\u020c":"O","\u020e":"O","\u01a0":"O","\u1edc":"O","\u1eda":"O","\u1ee0":"O","\u1ede":"O","\u1ee2":"O","\u1ecc":"O","\u1ed8":"O","\u01ea":"O","\u01ec":"O","\xd8":"O","\u01fe":"O","\u0186":"O","\u019f":"O","\ua74a":"O","\ua74c":"O","\u01a2":"OI","\ua74e":"OO","\u0222":"OU","\u24c5":"P","\uff30":"P","\u1e54":"P","\u1e56":"P","\u01a4":"P","\u2c63":"P","\ua750":"P","\ua752":"P","\ua754":"P","\u24c6":"Q","\uff31":"Q","\ua756":"Q","\ua758":"Q","\u024a":"Q","\u24c7":"R","\uff32":"R","\u0154":"R","\u1e58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1e5a":"R","\u1e5c":"R","\u0156":"R","\u1e5e":"R","\u024c":"R","\u2c64":"R","\ua75a":"R","\ua7a6":"R","\ua782":"R","\u24c8":"S","\uff33":"S","\u1e9e":"S","\u015a":"S","\u1e64":"S","\u015c":"S","\u1e60":"S","\u0160":"S","\u1e66":"S","\u1e62":"S","\u1e68":"S","\u0218":"S","\u015e":"S","\u2c7e":"S","\ua7a8":"S","\ua784":"S","\u24c9":"T","\uff34":"T","\u1e6a":"T","\u0164":"T","\u1e6c":"T","\u021a":"T","\u0162":"T","\u1e70":"T","\u1e6e":"T","\u0166":"T","\u01ac":"T","\u01ae":"T","\u023e":"T","\ua786":"T","\ua728":"TZ","\u24ca":"U","\uff35":"U","\xd9":"U","\xda":"U","\xdb":"U","\u0168":"U","\u1e78":"U","\u016a":"U","\u1e7a":"U","\u016c":"U","\xdc":"U","\u01db":"U","\u01d7":"U","\u01d5":"U","\u01d9":"U","\u1ee6":"U","\u016e":"U","\u0170":"U","\u01d3":"U","\u0214":"U","\u0216":"U","\u01af":"U","\u1eea":"U","\u1ee8":"U","\u1eee":"U","\u1eec":"U","\u1ef0":"U","\u1ee4":"U","\u1e72":"U","\u0172":"U","\u1e76":"U","\u1e74":"U","\u0244":"U","\u24cb":"V","\uff36":"V","\u1e7c":"V","\u1e7e":"V","\u01b2":"V","\ua75e":"V","\u0245":"V","\ua760":"VY","\u24cc":"W","\uff37":"W","\u1e80":"W","\u1e82":"W","\u0174":"W","\u1e86":"W","\u1e84":"W","\u1e88":"W","\u2c72":"W","\u24cd":"X","\uff38":"X","\u1e8a":"X","\u1e8c":"X","\u24ce":"Y","\uff39":"Y","\u1ef2":"Y","\xdd":"Y","\u0176":"Y","\u1ef8":"Y","\u0232":"Y","\u1e8e":"Y","\u0178":"Y","\u1ef6":"Y","\u1ef4":"Y","\u01b3":"Y","\u024e":"Y","\u1efe":"Y","\u24cf":"Z","\uff3a":"Z","\u0179":"Z","\u1e90":"Z","\u017b":"Z","\u017d":"Z","\u1e92":"Z","\u1e94":"Z","\u01b5":"Z","\u0224":"Z","\u2c7f":"Z","\u2c6b":"Z","\ua762":"Z","\u24d0":"a","\uff41":"a","\u1e9a":"a","\xe0":"a","\xe1":"a","\xe2":"a","\u1ea7":"a","\u1ea5":"a","\u1eab":"a","\u1ea9":"a","\xe3":"a","\u0101":"a","\u0103":"a","\u1eb1":"a","\u1eaf":"a","\u1eb5":"a","\u1eb3":"a","\u0227":"a","\u01e1":"a","\xe4":"a","\u01df":"a","\u1ea3":"a","\xe5":"a","\u01fb":"a","\u01ce":"a","\u0201":"a","\u0203":"a","\u1ea1":"a","\u1ead":"a","\u1eb7":"a","\u1e01":"a","\u0105":"a","\u2c65":"a","\u0250":"a","\ua733":"aa","\xe6":"ae","\u01fd":"ae","\u01e3":"ae","\ua735":"ao","\ua737":"au","\ua739":"av","\ua73b":"av","\ua73d":"ay","\u24d1":"b","\uff42":"b","\u1e03":"b","\u1e05":"b","\u1e07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24d2":"c","\uff43":"c","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\xe7":"c","\u1e09":"c","\u0188":"c","\u023c":"c","\ua73f":"c","\u2184":"c","\u24d3":"d","\uff44":"d","\u1e0b":"d","\u010f":"d","\u1e0d":"d","\u1e11":"d","\u1e13":"d","\u1e0f":"d","\u0111":"d","\u018c":"d","\u0256":"d","\u0257":"d","\ua77a":"d","\u01f3":"dz","\u01c6":"dz","\u24d4":"e","\uff45":"e","\xe8":"e","\xe9":"e","\xea":"e","\u1ec1":"e","\u1ebf":"e","\u1ec5":"e","\u1ec3":"e","\u1ebd":"e","\u0113":"e","\u1e15":"e","\u1e17":"e","\u0115":"e","\u0117":"e","\xeb":"e","\u1ebb":"e","\u011b":"e","\u0205":"e","\u0207":"e","\u1eb9":"e","\u1ec7":"e","\u0229":"e","\u1e1d":"e","\u0119":"e","\u1e19":"e","\u1e1b":"e","\u0247":"e","\u025b":"e","\u01dd":"e","\u24d5":"f","\uff46":"f","\u1e1f":"f","\u0192":"f","\ua77c":"f","\u24d6":"g","\uff47":"g","\u01f5":"g","\u011d":"g","\u1e21":"g","\u011f":"g","\u0121":"g","\u01e7":"g","\u0123":"g","\u01e5":"g","\u0260":"g","\ua7a1":"g","\u1d79":"g","\ua77f":"g","\u24d7":"h","\uff48":"h","\u0125":"h","\u1e23":"h","\u1e27":"h","\u021f":"h","\u1e25":"h","\u1e29":"h","\u1e2b":"h","\u1e96":"h","\u0127":"h","\u2c68":"h","\u2c76":"h","\u0265":"h","\u0195":"hv","\u24d8":"i","\uff49":"i","\xec":"i","\xed":"i","\xee":"i","\u0129":"i","\u012b":"i","\u012d":"i","\xef":"i","\u1e2f":"i","\u1ec9":"i","\u01d0":"i","\u0209":"i","\u020b":"i","\u1ecb":"i","\u012f":"i","\u1e2d":"i","\u0268":"i","\u0131":"i","\u24d9":"j","\uff4a":"j","\u0135":"j","\u01f0":"j","\u0249":"j","\u24da":"k","\uff4b":"k","\u1e31":"k","\u01e9":"k","\u1e33":"k","\u0137":"k","\u1e35":"k","\u0199":"k","\u2c6a":"k","\ua741":"k","\ua743":"k","\ua745":"k","\ua7a3":"k","\u24db":"l","\uff4c":"l","\u0140":"l","\u013a":"l","\u013e":"l","\u1e37":"l","\u1e39":"l","\u013c":"l","\u1e3d":"l","\u1e3b":"l","\u017f":"l","\u0142":"l","\u019a":"l","\u026b":"l","\u2c61":"l","\ua749":"l","\ua781":"l","\ua747":"l","\u01c9":"lj","\u24dc":"m","\uff4d":"m","\u1e3f":"m","\u1e41":"m","\u1e43":"m","\u0271":"m","\u026f":"m","\u24dd":"n","\uff4e":"n","\u01f9":"n","\u0144":"n","\xf1":"n","\u1e45":"n","\u0148":"n","\u1e47":"n","\u0146":"n","\u1e4b":"n","\u1e49":"n","\u019e":"n","\u0272":"n","\u0149":"n","\ua791":"n","\ua7a5":"n","\u01cc":"nj","\u24de":"o","\uff4f":"o","\xf2":"o","\xf3":"o","\xf4":"o","\u1ed3":"o","\u1ed1":"o","\u1ed7":"o","\u1ed5":"o","\xf5":"o","\u1e4d":"o","\u022d":"o","\u1e4f":"o","\u014d":"o","\u1e51":"o","\u1e53":"o","\u014f":"o","\u022f":"o","\u0231":"o","\xf6":"o","\u022b":"o","\u1ecf":"o","\u0151":"o","\u01d2":"o","\u020d":"o","\u020f":"o","\u01a1":"o","\u1edd":"o","\u1edb":"o","\u1ee1":"o","\u1edf":"o","\u1ee3":"o","\u1ecd":"o","\u1ed9":"o","\u01eb":"o","\u01ed":"o","\xf8":"o","\u01ff":"o","\u0254":"o","\ua74b":"o","\ua74d":"o","\u0275":"o","\u01a3":"oi","\u0223":"ou","\ua74f":"oo","\u24df":"p","\uff50":"p","\u1e55":"p","\u1e57":"p","\u01a5":"p","\u1d7d":"p","\ua751":"p","\ua753":"p","\ua755":"p","\u24e0":"q","\uff51":"q","\u024b":"q","\ua757":"q","\ua759":"q","\u24e1":"r","\uff52":"r","\u0155":"r","\u1e59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1e5b":"r","\u1e5d":"r","\u0157":"r","\u1e5f":"r","\u024d":"r","\u027d":"r","\ua75b":"r","\ua7a7":"r","\ua783":"r","\u24e2":"s","\uff53":"s","\xdf":"s","\u015b":"s","\u1e65":"s","\u015d":"s","\u1e61":"s","\u0161":"s","\u1e67":"s","\u1e63":"s","\u1e69":"s","\u0219":"s","\u015f":"s","\u023f":"s","\ua7a9":"s","\ua785":"s","\u1e9b":"s","\u24e3":"t","\uff54":"t","\u1e6b":"t","\u1e97":"t","\u0165":"t","\u1e6d":"t","\u021b":"t","\u0163":"t","\u1e71":"t","\u1e6f":"t","\u0167":"t","\u01ad":"t","\u0288":"t","\u2c66":"t","\ua787":"t","\ua729":"tz","\u24e4":"u","\uff55":"u","\xf9":"u","\xfa":"u","\xfb":"u","\u0169":"u","\u1e79":"u","\u016b":"u","\u1e7b":"u","\u016d":"u","\xfc":"u","\u01dc":"u","\u01d8":"u","\u01d6":"u","\u01da":"u","\u1ee7":"u","\u016f":"u","\u0171":"u","\u01d4":"u","\u0215":"u","\u0217":"u","\u01b0":"u","\u1eeb":"u","\u1ee9":"u","\u1eef":"u","\u1eed":"u","\u1ef1":"u","\u1ee5":"u","\u1e73":"u","\u0173":"u","\u1e77":"u","\u1e75":"u","\u0289":"u","\u24e5":"v","\uff56":"v","\u1e7d":"v","\u1e7f":"v","\u028b":"v","\ua75f":"v","\u028c":"v","\ua761":"vy","\u24e6":"w","\uff57":"w","\u1e81":"w","\u1e83":"w","\u0175":"w","\u1e87":"w","\u1e85":"w","\u1e98":"w","\u1e89":"w","\u2c73":"w","\u24e7":"x","\uff58":"x","\u1e8b":"x","\u1e8d":"x","\u24e8":"y","\uff59":"y","\u1ef3":"y","\xfd":"y","\u0177":"y","\u1ef9":"y","\u0233":"y","\u1e8f":"y","\xff":"y","\u1ef7":"y","\u1e99":"y","\u1ef5":"y","\u01b4":"y","\u024f":"y","\u1eff":"y","\u24e9":"z","\uff5a":"z","\u017a":"z","\u1e91":"z","\u017c":"z","\u017e":"z","\u1e93":"z","\u1e95":"z","\u01b6":"z","\u0225":"z","\u0240":"z","\u2c6c":"z","\ua763":"z"};j=a(document),g=function(){var a=1;return function(){return a++}}(),j.on("mousemove",function(a){i.x=a.pageX,i.y=a.pageY}),d=O(Object,{bind:function(a){var b=this;return function(){a.apply(b,arguments)}},init:function(c){var d,e,f=".select2-results";this.opts=c=this.prepareOpts(c),this.id=c.id,c.element.data("select2")!==b&&null!==c.element.data("select2")&&c.element.data("select2").destroy(),this.container=this.createContainer(),this.liveRegion=a("<span>",{role:"status","aria-live":"polite"}).addClass("select2-hidden-accessible").appendTo(document.body),this.containerId="s2id_"+(c.element.attr("id")||"autogen"+g()).replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1"),this.containerSelector="#"+this.containerId,this.container.attr("id",this.containerId),this.body=x(function(){return c.element.closest("body")}),E(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.attr("style",c.element.attr("style")),this.container.css(L(c.containerCss)),this.container.addClass(L(c.containerCssClass)),this.elementTabIndex=this.opts.element.attr("tabindex"),this.opts.element.data("select2",this).attr("tabindex","-1").before(this.container).on("click.select2",B),this.container.data("select2",this),this.dropdown=this.container.find(".select2-drop"),E(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(L(c.dropdownCssClass)),this.dropdown.data("select2",this),this.dropdown.on("click",B),this.results=d=this.container.find(f),this.search=e=this.container.find("input.select2-input"),this.queryCount=0,this.resultsPage=0,this.context=null,this.initContainer(),this.container.on("click",B),v(this.results),this.dropdown.on("mousemove-filtered touchstart touchmove touchend",f,this.bind(this.highlightUnderEvent)),this.dropdown.on("touchend",f,this.bind(this.selectHighlighted)),this.dropdown.on("touchmove",f,this.bind(this.touchMoved)),this.dropdown.on("touchstart touchend",f,this.bind(this.clearTouchMoved)),y(80,this.results),this.dropdown.on("scroll-debounced",f,this.bind(this.loadMoreIfNeeded)),a(this.container).on("change",".select2-input",function(a){a.stopPropagation()}),a(this.dropdown).on("change",".select2-input",function(a){a.stopPropagation()}),a.fn.mousewheel&&d.mousewheel(function(a,b,c,e){var f=d.scrollTop();e>0&&0>=f-e?(d.scrollTop(0),B(a)):0>e&&d.get(0).scrollHeight-d.scrollTop()+e<=d.height()&&(d.scrollTop(d.get(0).scrollHeight-d.height()),B(a))}),u(e),e.on("keyup-change input paste",this.bind(this.updateResults)),e.on("focus",function(){e.addClass("select2-focused")}),e.on("blur",function(){e.removeClass("select2-focused")}),this.dropdown.on("mouseup",f,this.bind(function(b){a(b.target).closest(".select2-result-selectable").length>0&&(this.highlightUnderEvent(b),this.selectHighlighted(b))})),this.dropdown.on("click mouseup mousedown focusin",function(a){a.stopPropagation()}),this.nextSearchTerm=b,a.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource()),null!==c.maximumInputLength&&this.search.attr("maxlength",c.maximumInputLength);var h=c.element.prop("disabled");h===b&&(h=!1),this.enable(!h);var i=c.element.prop("readonly");i===b&&(i=!1),this.readonly(i),k=k||q(),this.autofocus=c.element.prop("autofocus"),c.element.prop("autofocus",!1),this.autofocus&&this.focus(),this.search.attr("placeholder",c.searchInputPlaceholder)},destroy:function(){var a=this.opts.element,c=a.data("select2");this.close(),this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),c!==b&&(c.container.remove(),c.liveRegion.remove(),c.dropdown.remove(),a.removeClass("select2-offscreen").removeData("select2").off(".select2").prop("autofocus",this.autofocus||!1),this.elementTabIndex?a.attr({tabindex:this.elementTabIndex}):a.removeAttr("tabindex"),a.show())},optionToData:function(a){return a.is("option")?{id:a.prop("value"),text:a.text(),element:a.get(),css:a.attr("class"),disabled:a.prop("disabled"),locked:r(a.attr("locked"),"locked")||r(a.data("locked"),!0)}:a.is("optgroup")?{text:a.attr("label"),children:[],element:a.get(),css:a.attr("class")}:void 0},prepareOpts:function(c){var d,e,f,h,i=this;if(d=c.element,"select"===d.get(0).tagName.toLowerCase()&&(this.select=e=c.element),e&&a.each(["id","multiple","ajax","query","createSearchChoice","initSelection","data","tags"],function(){if(this in c)throw new Error("Option '"+this+"' is not allowed for Select2 when attached to a <select> element.")}),c=a.extend({},{populateResults:function(d,e,f){var h,j=this.opts.id,k=this.liveRegion;h=function(d,e,l){var m,n,o,p,q,r,s,t,u,v;for(d=c.sortResults(d,e,f),m=0,n=d.length;n>m;m+=1)o=d[m],q=o.disabled===!0,p=!q&&j(o)!==b,r=o.children&&o.children.length>0,s=a("<li></li>"),s.addClass("select2-results-dept-"+l),s.addClass("select2-result"),s.addClass(p?"select2-result-selectable":"select2-result-unselectable"),q&&s.addClass("select2-disabled"),r&&s.addClass("select2-result-with-children"),s.addClass(i.opts.formatResultCssClass(o)),s.attr("role","presentation"),t=a(document.createElement("div")),t.addClass("select2-result-label"),t.attr("id","select2-result-label-"+g()),t.attr("role","option"),v=c.formatResult(o,t,f,i.opts.escapeMarkup),v!==b&&(t.html(v),s.append(t)),r&&(u=a("<ul></ul>"),u.addClass("select2-result-sub"),h(o.children,u,l+1),s.append(u)),s.data("select2-data",o),e.append(s);k.text(c.formatMatches(d.length))},h(e,d,0)}},a.fn.select2.defaults,c),"function"!=typeof c.id&&(f=c.id,c.id=function(a){return a[f]}),a.isArray(c.element.data("select2Tags"))){if("tags"in c)throw"tags specified as both an attribute 'data-select2-tags' and in options of Select2 "+c.element.attr("id");c.tags=c.element.data("select2Tags")}if(e?(c.query=this.bind(function(a){var f,g,h,c={results:[],more:!1},e=a.term;h=function(b,c){var d;b.is("option")?a.matcher(e,b.text(),b)&&c.push(i.optionToData(b)):b.is("optgroup")&&(d=i.optionToData(b),b.children().each2(function(a,b){h(b,d.children)}),d.children.length>0&&c.push(d))},f=d.children(),this.getPlaceholder()!==b&&f.length>0&&(g=this.getPlaceholderOption(),g&&(f=f.not(g))),f.each2(function(a,b){h(b,c.results)}),a.callback(c)}),c.id=function(a){return a.id}):"query"in c||("ajax"in c?(h=c.element.data("ajax-url"),h&&h.length>0&&(c.ajax.url=h),c.query=H.call(c.element,c.ajax)):"data"in c?c.query=I(c.data):"tags"in c&&(c.query=J(c.tags),c.createSearchChoice===b&&(c.createSearchChoice=function(b){return{id:a.trim(b),text:a.trim(b)}}),c.initSelection===b&&(c.initSelection=function(b,d){var e=[];a(s(b.val(),c.separator)).each(function(){var b={id:this,text:this},d=c.tags;a.isFunction(d)&&(d=d()),a(d).each(function(){return r(this.id,b.id)?(b=this,!1):void 0}),e.push(b)}),d(e)}))),"function"!=typeof c.query)throw"query function not defined for Select2 "+c.element.attr("id");if("top"===c.createSearchChoicePosition)c.createSearchChoicePosition=function(a,b){a.unshift(b)};else if("bottom"===c.createSearchChoicePosition)c.createSearchChoicePosition=function(a,b){a.push(b)};else if("function"!=typeof c.createSearchChoicePosition)throw"invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";return c},monitorSource:function(){var c,d,a=this.opts.element;a.on("change.select2",this.bind(function(){this.opts.element.data("select2-change-triggered")!==!0&&this.initSelection()})),c=this.bind(function(){var c=a.prop("disabled");c===b&&(c=!1),this.enable(!c);var d=a.prop("readonly");d===b&&(d=!1),this.readonly(d),E(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.addClass(L(this.opts.containerCssClass)),E(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(L(this.opts.dropdownCssClass))}),a.on("propertychange.select2",c),this.mutationCallback===b&&(this.mutationCallback=function(a){a.forEach(c)}),d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,d!==b&&(this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),this.propertyObserver=new d(this.mutationCallback),this.propertyObserver.observe(a.get(0),{attributes:!0,subtree:!1}))},triggerSelect:function(b){var c=a.Event("select2-selecting",{val:this.id(b),object:b});return this.opts.element.trigger(c),!c.isDefaultPrevented()},triggerChange:function(b){b=b||{},b=a.extend({},b,{type:"change",val:this.val()}),this.opts.element.data("select2-change-triggered",!0),this.opts.element.trigger(b),this.opts.element.data("select2-change-triggered",!1),this.opts.element.click(),this.opts.blurOnChange&&this.opts.element.blur()},isInterfaceEnabled:function(){return this.enabledInterface===!0},enableInterface:function(){var a=this._enabled&&!this._readonly,b=!a;return a===this.enabledInterface?!1:(this.container.toggleClass("select2-container-disabled",b),this.close(),this.enabledInterface=a,!0)},enable:function(a){a===b&&(a=!0),this._enabled!==a&&(this._enabled=a,this.opts.element.prop("disabled",!a),this.enableInterface())},disable:function(){this.enable(!1)},readonly:function(a){a===b&&(a=!1),this._readonly!==a&&(this._readonly=a,this.opts.element.prop("readonly",a),this.enableInterface())},opened:function(){return this.container.hasClass("select2-dropdown-open")},positionDropdown:function(){var t,u,v,w,x,b=this.dropdown,c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),g=a(window),h=g.width(),i=g.height(),j=g.scrollLeft()+h,l=g.scrollTop()+i,m=c.top+d,n=c.left,o=l>=m+f,p=c.top-f>=g.scrollTop(),q=b.outerWidth(!1),r=j>=n+q,s=b.hasClass("select2-drop-above");s?(u=!0,!p&&o&&(v=!0,u=!1)):(u=!1,!o&&p&&(v=!0,u=!0)),v&&(b.hide(),c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),j=g.scrollLeft()+h,l=g.scrollTop()+i,m=c.top+d,n=c.left,q=b.outerWidth(!1),r=j>=n+q,b.show()),this.opts.dropdownAutoWidth?(x=a(".select2-results",b)[0],b.addClass("select2-drop-auto-width"),b.css("width",""),q=b.outerWidth(!1)+(x.scrollHeight===x.clientHeight?0:k.width),q>e?e=q:q=e,r=j>=n+q):this.container.removeClass("select2-drop-auto-width"),"static"!==this.body().css("position")&&(t=this.body().offset(),m-=t.top,n-=t.left),r||(n=c.left+this.container.outerWidth(!1)-q),w={left:n,width:e},u?(w.top=c.top-f,w.bottom="auto",this.container.addClass("select2-drop-above"),b.addClass("select2-drop-above")):(w.top=m,w.bottom="auto",this.container.removeClass("select2-drop-above"),b.removeClass("select2-drop-above")),w=a.extend(w,L(this.opts.dropdownCss)),b.css(w)},shouldOpen:function(){var b;return this.opened()?!1:this._enabled===!1||this._readonly===!0?!1:(b=a.Event("select2-opening"),this.opts.element.trigger(b),!b.isDefaultPrevented())},clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above")},open:function(){return this.shouldOpen()?(this.opening(),!0):!1},opening:function(){var f,b=this.containerId,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.addClass("select2-dropdown-open").addClass("select2-container-active"),this.clearDropdownAlignmentPreference(),this.dropdown[0]!==this.body().children().last()[0]&&this.dropdown.detach().appendTo(this.body()),f=a("#select2-drop-mask"),0==f.length&&(f=a(document.createElement("div")),f.attr("id","select2-drop-mask").attr("class","select2-drop-mask"),f.hide(),f.appendTo(this.body()),f.on("mousedown touchstart click",function(b){n(f);var d,c=a("#select2-drop");c.length>0&&(d=c.data("select2"),d.opts.selectOnBlur&&d.selectHighlighted({noFocus:!0}),d.close(),b.preventDefault(),b.stopPropagation())})),this.dropdown.prev()[0]!==f[0]&&this.dropdown.before(f),a("#select2-drop").removeAttr("id"),this.dropdown.attr("id","select2-drop"),f.show(),this.positionDropdown(),this.dropdown.show(),this.positionDropdown(),this.dropdown.addClass("select2-drop-active");var g=this;this.container.parents().add(window).each(function(){a(this).on(d+" "+c+" "+e,function(){g.positionDropdown()})})},close:function(){if(this.opened()){var b=this.containerId,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.parents().add(window).each(function(){a(this).off(c).off(d).off(e)}),this.clearDropdownAlignmentPreference(),a("#select2-drop-mask").hide(),this.dropdown.removeAttr("id"),this.dropdown.hide(),this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active"),this.results.empty(),this.clearSearch(),this.search.removeClass("select2-active"),this.opts.element.trigger(a.Event("select2-close"))}},externalSearch:function(a){this.open(),this.search.val(a),this.updateResults(!1)},clearSearch:function(){},getMaximumSelectionSize:function(){return L(this.opts.maximumSelectionSize)},ensureHighlightVisible:function(){var c,d,e,f,g,h,i,b=this.results;if(d=this.highlight(),!(0>d)){if(0==d)return b.scrollTop(0),void 0;c=this.findHighlightableChoices().find(".select2-result-label"),e=a(c[d]),f=e.offset().top+e.outerHeight(!0),d===c.length-1&&(i=b.find("li.select2-more-results"),i.length>0&&(f=i.offset().top+i.outerHeight(!0))),g=b.offset().top+b.outerHeight(!0),f>g&&b.scrollTop(b.scrollTop()+(f-g)),h=e.offset().top-b.offset().top,0>h&&"none"!=e.css("display")&&b.scrollTop(b.scrollTop()+h)}},findHighlightableChoices:function(){return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)")},moveHighlight:function(b){for(var c=this.findHighlightableChoices(),d=this.highlight();d>-1&&d<c.length;){d+=b;var e=a(c[d]);if(e.hasClass("select2-result-selectable")&&!e.hasClass("select2-disabled")&&!e.hasClass("select2-selected")){this.highlight(d);break}}},highlight:function(b){var d,e,c=this.findHighlightableChoices();return 0===arguments.length?p(c.filter(".select2-highlighted")[0],c.get()):(b>=c.length&&(b=c.length-1),0>b&&(b=0),this.removeHighlight(),d=a(c[b]),d.addClass("select2-highlighted"),this.search.attr("aria-activedescendant",d.find(".select2-result-label").attr("id")),this.ensureHighlightVisible(),this.liveRegion.text(d.text()),e=d.data("select2-data"),e&&this.opts.element.trigger({type:"select2-highlight",val:this.id(e),choice:e}),void 0)},removeHighlight:function(){this.results.find(".select2-highlighted").removeClass("select2-highlighted")},touchMoved:function(){this._touchMoved=!0},clearTouchMoved:function(){this._touchMoved=!1},countSelectableResults:function(){return this.findHighlightableChoices().length},highlightUnderEvent:function(b){var c=a(b.target).closest(".select2-result-selectable");if(c.length>0&&!c.is(".select2-highlighted")){var d=this.findHighlightableChoices();this.highlight(d.index(c))}else 0==c.length&&this.removeHighlight()},loadMoreIfNeeded:function(){var c,a=this.results,b=a.find("li.select2-more-results"),d=this.resultsPage+1,e=this,f=this.search.val(),g=this.context;
0!==b.length&&(c=b.offset().top-a.offset().top-a.height(),c<=this.opts.loadMorePadding&&(b.addClass("select2-active"),this.opts.query({element:this.opts.element,term:f,page:d,context:g,matcher:this.opts.matcher,callback:this.bind(function(c){e.opened()&&(e.opts.populateResults.call(this,a,c.results,{term:f,page:d,context:g}),e.postprocessResults(c,!1,!1),c.more===!0?(b.detach().appendTo(a).text(L(e.opts.formatLoadMore,d+1)),window.setTimeout(function(){e.loadMoreIfNeeded()},10)):b.remove(),e.positionDropdown(),e.resultsPage=d,e.context=c.context,this.opts.element.trigger({type:"select2-loaded",items:c}))})})))},tokenize:function(){},updateResults:function(c){function m(){d.removeClass("select2-active"),h.positionDropdown(),e.find(".select2-no-results,.select2-selection-limit,.select2-searching").length?h.liveRegion.text(e.text()):h.liveRegion.text(h.opts.formatMatches(e.find(".select2-result-selectable").length))}function n(a){e.html(a),m()}var g,i,l,d=this.search,e=this.results,f=this.opts,h=this,j=d.val(),k=a.data(this.container,"select2-last-term");if((c===!0||!k||!r(j,k))&&(a.data(this.container,"select2-last-term",j),c===!0||this.showSearchInput!==!1&&this.opened())){l=++this.queryCount;var o=this.getMaximumSelectionSize();if(o>=1&&(g=this.data(),a.isArray(g)&&g.length>=o&&K(f.formatSelectionTooBig,"formatSelectionTooBig")))return n("<li class='select2-selection-limit'>"+L(f.formatSelectionTooBig,o)+"</li>"),void 0;if(d.val().length<f.minimumInputLength)return K(f.formatInputTooShort,"formatInputTooShort")?n("<li class='select2-no-results'>"+L(f.formatInputTooShort,d.val(),f.minimumInputLength)+"</li>"):n(""),c&&this.showSearch&&this.showSearch(!0),void 0;if(f.maximumInputLength&&d.val().length>f.maximumInputLength)return K(f.formatInputTooLong,"formatInputTooLong")?n("<li class='select2-no-results'>"+L(f.formatInputTooLong,d.val(),f.maximumInputLength)+"</li>"):n(""),void 0;f.formatSearching&&0===this.findHighlightableChoices().length&&n("<li class='select2-searching'>"+L(f.formatSearching)+"</li>"),d.addClass("select2-active"),this.removeHighlight(),i=this.tokenize(),i!=b&&null!=i&&d.val(i),this.resultsPage=1,f.query({element:f.element,term:d.val(),page:this.resultsPage,context:null,matcher:f.matcher,callback:this.bind(function(g){var i;if(l==this.queryCount){if(!this.opened())return this.search.removeClass("select2-active"),void 0;if(this.context=g.context===b?null:g.context,this.opts.createSearchChoice&&""!==d.val()&&(i=this.opts.createSearchChoice.call(h,d.val(),g.results),i!==b&&null!==i&&h.id(i)!==b&&null!==h.id(i)&&0===a(g.results).filter(function(){return r(h.id(this),h.id(i))}).length&&this.opts.createSearchChoicePosition(g.results,i)),0===g.results.length&&K(f.formatNoMatches,"formatNoMatches"))return n("<li class='select2-no-results'>"+L(f.formatNoMatches,d.val())+"</li>"),void 0;e.empty(),h.opts.populateResults.call(this,e,g.results,{term:d.val(),page:this.resultsPage,context:null}),g.more===!0&&K(f.formatLoadMore,"formatLoadMore")&&(e.append("<li class='select2-more-results'>"+h.opts.escapeMarkup(L(f.formatLoadMore,this.resultsPage))+"</li>"),window.setTimeout(function(){h.loadMoreIfNeeded()},10)),this.postprocessResults(g,c),m(),this.opts.element.trigger({type:"select2-loaded",items:g})}})})}},cancel:function(){this.close()},blur:function(){this.opts.selectOnBlur&&this.selectHighlighted({noFocus:!0}),this.close(),this.container.removeClass("select2-container-active"),this.search[0]===document.activeElement&&this.search.blur(),this.clearSearch(),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){z(this.search)},selectHighlighted:function(a){if(this._touchMoved)return this.clearTouchMoved(),void 0;var b=this.highlight(),c=this.results.find(".select2-highlighted"),d=c.closest(".select2-result").data("select2-data");d?(this.highlight(b),this.onSelect(d,a)):a&&a.noFocus&&this.close()},getPlaceholder:function(){var a;return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")||this.opts.placeholder||((a=this.getPlaceholderOption())!==b?a.text():b)},getPlaceholderOption:function(){if(this.select){var a=this.select.children("option").first();if(this.opts.placeholderOption!==b)return"first"===this.opts.placeholderOption&&a||"function"==typeof this.opts.placeholderOption&&this.opts.placeholderOption(this.select);if(""===a.text()&&""===a.val())return a}},initContainerWidth:function(){function c(){var c,d,e,f,g,h;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){if(c=this.opts.element.attr("style"),c!==b)for(d=c.split(";"),f=0,g=d.length;g>f;f+=1)if(h=d[f].replace(/\s/g,""),e=h.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i),null!==e&&e.length>=1)return e[1];return"resolve"===this.opts.width?(c=this.opts.element.css("width"),c.indexOf("%")>0?c:0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px"):null}return a.isFunction(this.opts.width)?this.opts.width():this.opts.width}var d=c.call(this);null!==d&&this.container.css("width",d)}}),e=O(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container"}).html(["<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>","   <span class='select2-chosen'>&nbsp;</span><abbr class='select2-search-choice-close'></abbr>","   <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>","</a>","<label for='' class='select2-offscreen'></label>","<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />","<div class='select2-drop select2-display-none'>","   <div class='select2-search'>","       <label for='' class='select2-offscreen'></label>","       <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'","       aria-autocomplete='list' />","   </div>","   <ul class='select2-results' role='listbox'>","   </ul>","</div>"].join(""));return b},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.focusser.prop("disabled",!this.isInterfaceEnabled())},opening:function(){var c,d,e;this.opts.minimumResultsForSearch>=0&&this.showSearch(!0),this.parent.opening.apply(this,arguments),this.showSearchInput!==!1&&this.search.val(this.focusser.val()),this.search.focus(),c=this.search.get(0),c.createTextRange?(d=c.createTextRange(),d.collapse(!1),d.select()):c.setSelectionRange&&(e=this.search.val().length,c.setSelectionRange(e,e)),""===this.search.val()&&this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.search.select()),this.focusser.prop("disabled",!0).val(""),this.updateResults(!0),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus())},focus:function(){this.opened()?this.close():(this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus())},isFocused:function(){return this.container.hasClass("select2-container-active")},cancel:function(){this.parent.cancel.apply(this,arguments),this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus()},destroy:function(){a("label[for='"+this.focusser.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments)},initContainer:function(){var b,h,d=this.container,e=this.dropdown,f=g();this.opts.minimumResultsForSearch<0?this.showSearch(!1):this.showSearch(!0),this.selection=b=d.find(".select2-choice"),this.focusser=d.find(".select2-focusser"),b.find(".select2-chosen").attr("id","select2-chosen-"+f),this.focusser.attr("aria-labelledby","select2-chosen-"+f),this.results.attr("id","select2-results-"+f),this.search.attr("aria-owns","select2-results-"+f),this.focusser.attr("id","s2id_autogen"+f),h=a("label[for='"+this.opts.element.attr("id")+"']"),this.focusser.prev().text(h.text()).attr("for",this.focusser.attr("id"));var i=this.opts.element.attr("title");this.opts.element.attr("title",i||h.text()),this.focusser.attr("tabindex",this.elementTabIndex),this.search.attr("id",this.focusser.attr("id")+"_search"),this.search.prev().text(a("label[for='"+this.focusser.attr("id")+"']").text()).attr("for",this.search.attr("id")),this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){if(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)return B(a),void 0;switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),B(a),void 0;case c.ENTER:return this.selectHighlighted(),B(a),void 0;case c.TAB:return this.selectHighlighted({noFocus:!0}),void 0;case c.ESC:return this.cancel(a),B(a),void 0}}})),this.search.on("blur",this.bind(function(){document.activeElement===this.body().get(0)&&window.setTimeout(this.bind(function(){this.opened()&&this.search.focus()}),0)})),this.focusser.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()&&a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.ESC){if(this.opts.openOnEnter===!1&&a.which===c.ENTER)return B(a),void 0;if(a.which==c.DOWN||a.which==c.UP||a.which==c.ENTER&&this.opts.openOnEnter){if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return;return this.open(),B(a),void 0}return a.which==c.DELETE||a.which==c.BACKSPACE?(this.opts.allowClear&&this.clear(),B(a),void 0):void 0}})),u(this.focusser),this.focusser.on("keyup-change input",this.bind(function(a){if(this.opts.minimumResultsForSearch>=0){if(a.stopPropagation(),this.opened())return;this.open()}})),b.on("mousedown touchstart","abbr",this.bind(function(a){this.isInterfaceEnabled()&&(this.clear(),C(a),this.close(),this.selection.focus())})),b.on("mousedown touchstart",this.bind(function(c){n(b),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.opened()?this.close():this.isInterfaceEnabled()&&this.open(),B(c)})),e.on("mousedown touchstart",this.bind(function(){this.search.focus()})),b.on("focus",this.bind(function(a){B(a)})),this.focusser.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})).on("blur",this.bind(function(){this.opened()||(this.container.removeClass("select2-container-active"),this.opts.element.trigger(a.Event("select2-blur")))})),this.search.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.setPlaceholder()},clear:function(b){var c=this.selection.data("select2-data");if(c){var d=a.Event("select2-clearing");if(this.opts.element.trigger(d),d.isDefaultPrevented())return;var e=this.getPlaceholderOption();this.opts.element.val(e?e.val():""),this.selection.find(".select2-chosen").empty(),this.selection.removeData("select2-data"),this.setPlaceholder(),b!==!1&&(this.opts.element.trigger({type:"select2-removed",val:this.id(c),choice:c}),this.triggerChange({removed:c}))}},initSelection:function(){if(this.isPlaceholderOptionSelected())this.updateSelection(null),this.close(),this.setPlaceholder();else{var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.setPlaceholder(),c.nextSearchTerm=c.opts.nextSearchTerm(a,c.search.val()))})}},isPlaceholderOptionSelected:function(){var a;return this.getPlaceholder()?(a=this.getPlaceholderOption())!==b&&a.prop("selected")||""===this.opts.element.val()||this.opts.element.val()===b||null===this.opts.element.val():!1},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=a.find("option").filter(function(){return this.selected&&!this.disabled});b(c.optionToData(d))}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=c.val(),f=null;b.query({matcher:function(a,c,d){var g=r(e,b.id(d));return g&&(f=d),g},callback:a.isFunction(d)?function(){d(f)}:a.noop})}),b},getPlaceholder:function(){return this.select&&this.getPlaceholderOption()===b?b:this.parent.getPlaceholder.apply(this,arguments)},setPlaceholder:function(){var a=this.getPlaceholder();if(this.isPlaceholderOptionSelected()&&a!==b){if(this.select&&this.getPlaceholderOption()===b)return;this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.container.removeClass("select2-allowclear")}},postprocessResults:function(a,b,c){var d=0,e=this;if(this.findHighlightableChoices().each2(function(a,b){return r(e.id(b.data("select2-data")),e.opts.element.val())?(d=a,!1):void 0}),c!==!1&&(b===!0&&d>=0?this.highlight(d):this.highlight(0)),b===!0){var g=this.opts.minimumResultsForSearch;g>=0&&this.showSearch(M(a.results)>=g)}},showSearch:function(b){this.showSearchInput!==b&&(this.showSearchInput=b,this.dropdown.find(".select2-search").toggleClass("select2-search-hidden",!b),this.dropdown.find(".select2-search").toggleClass("select2-offscreen",!b),a(this.dropdown,this.container).toggleClass("select2-with-searchbox",b))},onSelect:function(a,b){if(this.triggerSelect(a)){var c=this.opts.element.val(),d=this.data();this.opts.element.val(this.id(a)),this.updateSelection(a),this.opts.element.trigger({type:"select2-selected",val:this.id(a),choice:a}),this.nextSearchTerm=this.opts.nextSearchTerm(a,this.search.val()),this.close(),b&&b.noFocus||!this.opts.shouldFocusInput(this)||this.focusser.focus(),r(c,this.id(a))||this.triggerChange({added:a,removed:d})}},updateSelection:function(a){var d,e,c=this.selection.find(".select2-chosen");this.selection.data("select2-data",a),c.empty(),null!==a&&(d=this.opts.formatSelection(a,c,this.opts.escapeMarkup)),d!==b&&c.append(d),e=this.opts.formatSelectionCssClass(a,c),e!==b&&c.addClass(e),this.selection.removeClass("select2-default"),this.opts.allowClear&&this.getPlaceholder()!==b&&this.container.addClass("select2-allowclear")},val:function(){var a,c=!1,d=null,e=this,f=this.data();if(0===arguments.length)return this.opts.element.val();if(a=arguments[0],arguments.length>1&&(c=arguments[1]),this.select)this.select.val(a).find("option").filter(function(){return this.selected}).each2(function(a,b){return d=e.optionToData(b),!1}),this.updateSelection(d),this.setPlaceholder(),c&&this.triggerChange({added:d,removed:f});else{if(!a&&0!==a)return this.clear(c),void 0;if(this.opts.initSelection===b)throw new Error("cannot call val() if initSelection() is not defined");this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){e.opts.element.val(a?e.id(a):""),e.updateSelection(a),e.setPlaceholder(),c&&e.triggerChange({added:a,removed:f})})}},clearSearch:function(){this.search.val(""),this.focusser.val("")},data:function(a){var c,d=!1;return 0===arguments.length?(c=this.selection.data("select2-data"),c==b&&(c=null),c):(arguments.length>1&&(d=arguments[1]),a?(c=this.data(),this.opts.element.val(a?this.id(a):""),this.updateSelection(a),d&&this.triggerChange({added:a,removed:c})):this.clear(d),void 0)}}),f=O(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container select2-container-multi"}).html(["<ul class='select2-choices'>","  <li class='select2-search-field'>","    <label for='' class='select2-offscreen'></label>","    <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>","  </li>","</ul>","<div class='select2-drop select2-drop-multi select2-display-none'>","   <ul class='select2-results'>","   </ul>","</div>"].join(""));return b},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=[];a.find("option").filter(function(){return this.selected&&!this.disabled}).each2(function(a,b){d.push(c.optionToData(b))}),b(d)}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=s(c.val(),b.separator),f=[];b.query({matcher:function(c,d,g){var h=a.grep(e,function(a){return r(a,b.id(g))}).length;return h&&f.push(g),h},callback:a.isFunction(d)?function(){for(var a=[],c=0;c<e.length;c++)for(var g=e[c],h=0;h<f.length;h++){var i=f[h];if(r(g,b.id(i))){a.push(i),f.splice(h,1);break}}d(a)}:a.noop})}),b},selectChoice:function(a){var b=this.container.find(".select2-search-choice-focus");b.length&&a&&a[0]==b[0]||(b.length&&this.opts.element.trigger("choice-deselected",b),b.removeClass("select2-search-choice-focus"),a&&a.length&&(this.close(),a.addClass("select2-search-choice-focus"),this.opts.element.trigger("choice-selected",a)))},destroy:function(){a("label[for='"+this.search.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments)},initContainer:function(){var d,b=".select2-choices";this.searchContainer=this.container.find(".select2-search-field"),this.selection=d=this.container.find(b);var e=this;this.selection.on("click",".select2-search-choice:not(.select2-locked)",function(){e.search[0].focus(),e.selectChoice(a(this))}),this.search.attr("id","s2id_autogen"+g()),this.search.prev().text(a("label[for='"+this.opts.element.attr("id")+"']").text()).attr("for",this.search.attr("id")),this.search.on("input paste",this.bind(function(){this.isInterfaceEnabled()&&(this.opened()||this.open())})),this.search.attr("tabindex",this.elementTabIndex),this.keydowns=0,this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){++this.keydowns;var b=d.find(".select2-search-choice-focus"),e=b.prev(".select2-search-choice:not(.select2-locked)"),f=b.next(".select2-search-choice:not(.select2-locked)"),g=A(this.search);if(b.length&&(a.which==c.LEFT||a.which==c.RIGHT||a.which==c.BACKSPACE||a.which==c.DELETE||a.which==c.ENTER)){var h=b;return a.which==c.LEFT&&e.length?h=e:a.which==c.RIGHT?h=f.length?f:null:a.which===c.BACKSPACE?this.unselect(b.first())&&(this.search.width(10),h=e.length?e:f):a.which==c.DELETE?this.unselect(b.first())&&(this.search.width(10),h=f.length?f:null):a.which==c.ENTER&&(h=null),this.selectChoice(h),B(a),h&&h.length||this.open(),void 0}if((a.which===c.BACKSPACE&&1==this.keydowns||a.which==c.LEFT)&&0==g.offset&&!g.length)return this.selectChoice(d.find(".select2-search-choice:not(.select2-locked)").last()),B(a),void 0;if(this.selectChoice(null),this.opened())switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),B(a),void 0;case c.ENTER:return this.selectHighlighted(),B(a),void 0;case c.TAB:return this.selectHighlighted({noFocus:!0}),this.close(),void 0;case c.ESC:return this.cancel(a),B(a),void 0}if(a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.BACKSPACE&&a.which!==c.ESC){if(a.which===c.ENTER){if(this.opts.openOnEnter===!1)return;if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return}this.open(),(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)&&B(a),a.which===c.ENTER&&B(a)}}})),this.search.on("keyup",this.bind(function(){this.keydowns=0,this.resizeSearch()})),this.search.on("blur",this.bind(function(b){this.container.removeClass("select2-container-active"),this.search.removeClass("select2-focused"),this.selectChoice(null),this.opened()||this.clearSearch(),b.stopImmediatePropagation(),this.opts.element.trigger(a.Event("select2-blur"))})),this.container.on("click",b,this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").length>0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.open(),this.focusSearch(),b.preventDefault()))})),this.container.on("focus",b,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.clearSearch())})}},clearSearch:function(){var a=this.getPlaceholder(),c=this.getMaxSearchWidth();a!==b&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(a).addClass("select2-default"),this.search.width(c>0?c:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),""===this.search.val()&&this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.search.select()),this.updateResults(!0),this.search.focus(),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(b){var c=[],d=[],e=this;a(b).each(function(){p(e.id(this),c)<0&&(c.push(e.id(this)),d.push(this))}),b=d,this.selection.find(".select2-search-choice").remove(),a(b).each(function(){e.addSelectedChoice(this)}),e.postprocessResults()},tokenize:function(){var a=this.search.val();a=this.opts.tokenizer.call(this,a,this.data(),this.bind(this.onSelect),this.opts),null!=a&&a!=b&&(this.search.val(a),a.length>0&&this.open())},onSelect:function(a,c){this.triggerSelect(a)&&(this.addSelectedChoice(a),this.opts.element.trigger({type:"selected",val:this.id(a),choice:a}),this.nextSearchTerm=this.opts.nextSearchTerm(a,this.search.val()),this.clearSearch(),this.updateResults(),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(a,!1,this.opts.closeOnSelect===!0),this.opts.closeOnSelect?(this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()?this.updateResults(!0):this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.updateResults(),this.search.select()),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:a}),c&&c.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(c){var j,k,d=!c.locked,e=a("<li class='select2-search-choice'>    <div></div>    <a href='#' class='select2-search-choice-close' tabindex='-1'></a></li>"),f=a("<li class='select2-search-choice select2-locked'><div></div></li>"),g=d?e:f,h=this.id(c),i=this.getVal();j=this.opts.formatSelection(c,g.find("div"),this.opts.escapeMarkup),j!=b&&g.find("div").replaceWith("<div>"+j+"</div>"),k=this.opts.formatSelectionCssClass(c,g.find("div")),k!=b&&g.addClass(k),d&&g.find(".select2-search-choice-close").on("mousedown",B).on("click dblclick",this.bind(function(b){this.isInterfaceEnabled()&&(this.unselect(a(b.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),B(b),this.close(),this.focusSearch())})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),g.data("select2-data",c),g.insertBefore(this.searchContainer),i.push(h),this.setVal(i)},unselect:function(b){var d,e,c=this.getVal();if(b=b.closest(".select2-search-choice"),0===b.length)throw"Invalid argument: "+b+". Must be .select2-search-choice";if(d=b.data("select2-data")){var f=a.Event("select2-removing");if(f.val=this.id(d),f.choice=d,this.opts.element.trigger(f),f.isDefaultPrevented())return!1;for(;(e=p(this.id(d),c))>=0;)c.splice(e,1),this.setVal(c),this.select&&this.postprocessResults();return b.remove(),this.opts.element.trigger({type:"select2-removed",val:this.id(d),choice:d}),this.triggerChange({removed:d}),!0}},postprocessResults:function(a,b,c){var d=this.getVal(),e=this.results.find(".select2-result"),f=this.results.find(".select2-result-with-children"),g=this;e.each2(function(a,b){var c=g.id(b.data("select2-data"));p(c,d)>=0&&(b.addClass("select2-selected"),b.find(".select2-result-selectable").addClass("select2-selected"))}),f.each2(function(a,b){b.is(".select2-result-selectable")||0!==b.find(".select2-result-selectable:not(.select2-selected)").length||b.addClass("select2-selected")}),-1==this.highlight()&&c!==!1&&g.highlight(0),!this.opts.createSearchChoice&&!e.filter(".select2-result:not(.select2-selected)").length>0&&(!a||a&&!a.more&&0===this.results.find(".select2-no-results").length)&&K(g.opts.formatNoMatches,"formatNoMatches")&&this.results.append("<li class='select2-no-results'>"+L(g.opts.formatNoMatches,g.search.val())+"</li>")},getMaxSearchWidth:function(){return this.selection.width()-t(this.search)},resizeSearch:function(){var a,b,c,d,e,f=t(this.search);a=D(this.search)+10,b=this.search.offset().left,c=this.selection.width(),d=this.selection.offset().left,e=c-(b-d)-f,a>e&&(e=c-f),40>e&&(e=c-f),0>=e&&(e=a),this.search.width(Math.floor(e))},getVal:function(){var a;return this.select?(a=this.select.val(),null===a?[]:a):(a=this.opts.element.val(),s(a,this.opts.separator))},setVal:function(b){var c;this.select?this.select.val(b):(c=[],a(b).each(function(){p(this,c)<0&&c.push(this)}),this.opts.element.val(0===c.length?"":c.join(this.opts.separator)))},buildChangeDetails:function(a,b){for(var b=b.slice(0),a=a.slice(0),c=0;c<b.length;c++)for(var d=0;d<a.length;d++)r(this.opts.id(b[c]),this.opts.id(a[d]))&&(b.splice(c,1),c>0&&c--,a.splice(d,1),d--);return{added:b,removed:a}},val:function(c,d){var e,f=this;if(0===arguments.length)return this.getVal();if(e=this.data(),e.length||(e=[]),!c&&0!==c)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),d&&this.triggerChange({added:this.data(),removed:e}),void 0;if(this.setVal(c),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),d&&this.triggerChange(this.buildChangeDetails(e,this.data()));else{if(this.opts.initSelection===b)throw new Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(b){var c=a.map(b,f.id);f.setVal(c),f.updateSelection(b),f.clearSearch(),d&&f.triggerChange(f.buildChangeDetails(e,f.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var b=[],c=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){b.push(c.opts.id(a(this).data("select2-data")))}),this.setVal(b),this.triggerChange()},data:function(b,c){var e,f,d=this;return 0===arguments.length?this.selection.children(".select2-search-choice").map(function(){return a(this).data("select2-data")}).get():(f=this.data(),b||(b=[]),e=a.map(b,function(a){return d.opts.id(a)}),this.setVal(e),this.updateSelection(b),this.clearSearch(),c&&this.triggerChange(this.buildChangeDetails(f,this.data())),void 0)}}),a.fn.select2=function(){var d,e,f,g,h,c=Array.prototype.slice.call(arguments,0),i=["val","destroy","opened","open","close","focus","isFocused","container","dropdown","onSortStart","onSortEnd","enable","disable","readonly","positionDropdown","data","search"],j=["opened","isFocused","container","dropdown"],k=["val","data"],l={search:"externalSearch"};return this.each(function(){if(0===c.length||"object"==typeof c[0])d=0===c.length?{}:a.extend({},c[0]),d.element=a(this),"select"===d.element.get(0).tagName.toLowerCase()?h=d.element.prop("multiple"):(h=d.multiple||!1,"tags"in d&&(d.multiple=h=!0)),e=h?new window.Select2["class"].multi:new window.Select2["class"].single,e.init(d);else{if("string"!=typeof c[0])throw"Invalid arguments to select2 plugin: "+c;if(p(c[0],i)<0)throw"Unknown method: "+c[0];if(g=b,e=a(this).data("select2"),e===b)return;if(f=c[0],"container"===f?g=e.container:"dropdown"===f?g=e.dropdown:(l[f]&&(f=l[f]),g=e[f].apply(e,c.slice(1))),p(c[0],j)>=0||p(c[0],k)&&1==c.length)return!1}}),g===b?this:g},a.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c,d){var e=[];return F(a.text,c.term,e,d),e.join("")},formatSelection:function(a,c,d){return a?d(a.text):b},sortResults:function(a){return a},formatResultCssClass:function(a){return a.css},formatSelectionCssClass:function(){return b},formatMatches:function(a){return a+" results are available, use up and down arrow keys to navigate."},formatNoMatches:function(){return"No matches found"},formatInputTooShort:function(a,b){var c=b-a.length;return"Please enter "+c+" or more character"+(1==c?"":"s")},formatInputTooLong:function(a,b){var c=a.length-b;return"Please delete "+c+" character"+(1==c?"":"s")},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results\u2026"},formatSearching:function(){return"Searching\u2026"},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(a){return a==b?null:a.id},matcher:function(a,b){return o(""+b).toUpperCase().indexOf(o(""+a).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:N,escapeMarkup:G,blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(a){return a},adaptDropdownCssClass:function(){return null},nextSearchTerm:function(){return b},searchInputPlaceholder:"",createSearchChoicePosition:"top",shouldFocusInput:function(a){return a.opts.minimumResultsForSearch<0?!1:!0}},a.fn.select2.ajaxDefaults={transport:a.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:H,local:I,tags:J},util:{debounce:w,markMatch:F,escapeMarkup:G,stripDiacritics:o},"class":{"abstract":d,single:e,multi:f}}}}(jQuery);
$(document).ready(function () {

    var pagerOptions = {

        // target the pager markup - see the HTML block below
        container: $(".pager"),

        // use this url format "http:/mydatabase.com?page={page}&size={size}&{sortList:col}"
        ajaxUrl: null,

        // modify the url after all processing has been applied
        customAjaxUrl: function (table, url) {
            return url;
        },

        // ajax error callback from $.tablesorter.showError function
        // ajaxError: function( config, xhr, settings, exception ){ return exception; };
        // returning false will abort the error message
        ajaxError: null,

        // add more ajax settings here
        // see http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
        ajaxObject: { dataType: 'json' },

        // process ajax so that the data object is returned along with the total number of rows
        ajaxProcessing: null,

        // Set this option to false if your table data is preloaded into the table, but you are still using ajax
        processAjaxOnInit: true,

        // output string - default is '{page}/{totalPages}'
        // possible variables: {size}, {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
        // also {page:input} & {startRow:input} will add a modifiable input in place of the value
        // In v2.27.7, this can be set as a function
        // output: function(table, pager) { return 'page ' + pager.startRow + ' - ' + pager.endRow; }
		output: '{startRow:input} to {endRow} of {filteredRows} total records',

        // apply disabled classname (cssDisabled option) to the pager arrows when the rows
        // are at either extreme is visible; default is true
        updateArrows: true,

        // starting page of the pager (zero based index)
        page: 0,

        // Number of visible rows - default is 10
        size: 50,

        // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js)
        savePages: false,

        // Saves tablesorter paging to custom key if defined.
        // Key parameter name used by the $.tablesorter.storage function.
        // Useful if you have multiple tables defined
        storageKey: 'tablesorter-pager',

        // Reset pager to this page after filtering; set to desired page number (zero-based index),
        // or false to not change page at filter start
        pageReset: 0,

        // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty
        // table row set to a height to compensate; default is false
        fixedHeight: false,

        // remove rows from the table to speed up the sort of large tables.
        // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled.
        removeRows: false,

        // If true, child rows will be counted towards the pager set size
        countChildRows: false,

        // css class names of pager arrows
        cssNext: '.next', // next page arrow
        cssPrev: '.prev', // previous page arrow
        cssFirst: '.first', // go to first page arrow
        cssLast: '.last', // go to last page arrow
        cssGoto: '.gotoPage', // select dropdown to allow choosing a page

        cssPageDisplay: '.pagedisplay', // location of where the "output" is displayed
        cssPageSize: '.pagesize', // page size selector - select dropdown that sets the "size" option

        // class added to arrows when at the extremes (i.e. prev/first arrows are "disabled" when on the first page)
        cssDisabled: 'disabled', // Note there is no period "." in front of this class name
        cssErrorRow: 'tablesorter-errorRow' // ajax error information row
    };

	$('#payTable')
		//.bind('filterInit', function () {
		//    // check that storage ulility is loaded
		//    if ($.tablesorter.storage) {
		//        // get saved filters
		//        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
		//        $(this).trigger('search', [f]);
		//    }
		//})
		//.bind('filterEnd', function () {
		//    if ($.tablesorter.storage) {
		//        // save current filters
		//        var f = $(this).find('.tablesorter-filter').map(function () {
		//            return $(this).val() || '';
		//        }).get();
		//        $.tablesorter.storage(this, 'tablesorter-filters', f);
		//    }
		//})
		.bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
			var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
			$('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
		})
		.tablesorter({
			// *** APPEARANCE ***
			// Add a theme - try 'blackice', 'blue', 'dark', 'default'
			theme: 'blue',

			// fix the column widths
			widthFixed: true,

			// include zebra and any other widgets, options:
			// 'columns', 'filter' & 'resizable'
			// 'uitheme' is another widget, but requires loading
			// a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter', 'output'],

			widgetOptions: {

				// zebra widget: adding zebra striping, using content and
				// default styles - the ui css removes the background
				// from default even and odd class names included for this
				// demo to allow switching themes
				// [ "even", "odd" ]
				zebra: [
					"ui-widget-content even",
					"ui-state-default odd"
				],

				// uitheme widget: * Updated! in tablesorter v2.4 **
				// Instead of the array of icon class names, this option now
				// contains the name of the theme. Currently jQuery UI ("jui")
				// and Bootstrap ("bootstrap") themes are supported. To modify
				// the class names used, extend from the themes variable
				// look for the "$.extend($.tablesorter.themes.jui" code below
				uitheme: 'jui',

				// columns widget: change the default column class names
				// primary is the 1st column sorted, secondary is the 2nd, etc
				columns: [
					"primary",
					"secondary",
					"tertiary"
				],

				// columns widget: If true, the class names from the columns
				// option will also be added to the table tfoot.
				columns_tfoot: true,

				// columns widget: If true, the class names from the columns
				// option will also be added to the table thead.
				columns_thead: true,

				// filter widget: If there are child rows in the table (rows with
				// class name from "cssChildRow" option) and this option is true
				// and a match is found anywhere in the child row, then it will make
				// that row visible; default is false
				filter_childRows: false,

				// filter widget: If true, a filter will be added to the top of
				// each table column.
				//filter_columnFilters: false,

				// filter widget: css class applied to the table row containing the
				// filters & the inputs within that row
				filter_cssFilter: "tablesorter-filter",

				// filter widget: Customize the filter widget by adding a select
				// dropdown with content, custom options or custom filter functions
				// see http://goo.gl/HQQLW for more details
				filter_functions: null,

				// filter widget: Set this option to true to hide the filter row
				// initially. The rows is revealed by hovering over the filter
				// row or giving any filter input/select focus.
				filter_hideFilters: false,

				// filter widget: Set this option to false to keep the searches
				// case sensitive
				filter_ignoreCase: true,

				// filter widget: jQuery selector string of an element used to
				// reset the filters. 
				filter_reset: null,

				// Delay in milliseconds before the filter widget starts searching;
				// This option prevents searching for every character while typing
				// and should make searching large tables faster.
				filter_searchDelay: 300,

				// filter widget: Set this option to true to use the filter to find
				// text from the start of the column. So typing in "a" will find
				// "albert" but not "frank", both have a's; default is false
				filter_startsWith: false,

				// filter widget: If true, ALL filter searches will only use parsed
				// data. To only use parsed data in specific columns, set this option
				// to false and add class name "filter-parsed" to the header
				filter_useParsedData: false,

				// Resizable widget: If this option is set to false, resized column
				// widths will not be saved. Previous saved values will be restored
				// on page reload
				resizable: false,

				// saveSort widget: If this option is set to false, new sorts will
				// not be saved. Any previous saved sort will be restored on page
				// reload.
				saveSort: false,

				// stickyHeaders widget: css class name applied to the sticky header
				stickyHeaders: "tablesorter-stickyHeader",

				// Download options for table
				output_delivery: 'd',
				output_saveFileName: 'payables.csv',

				// filters for multi select
				//filter_formatter: {
				//	// Alphanumeric (exact)
				//	'th:not(.nofilter)': function ($cell, indx) {
				//		var ts = $.tablesorter;
				//		return ts.filterFormatter.select2($cell, indx, {
				//			match: true // exact match only		
				//			//placeholder: 'Filter'	// messes with column widths - not able to fix it
				//		});
				//	}
				//}
			}
		})
		.tablesorterPager(pagerOptions);

    $('#jobTable')
        //.bind('filterInit', function () {
        //    // check that storage ulility is loaded
        //    if ($.tablesorter.storage) {
        //        // get saved filters
        //        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
        //        $(this).trigger('search', [f]);
        //    }
        //})
        //.bind('filterEnd', function () {
        //    if ($.tablesorter.storage) {
        //        // save current filters
        //        var f = $(this).find('.tablesorter-filter').map(function () {
        //            return $(this).val() || '';
        //        }).get();
        //        $.tablesorter.storage(this, 'tablesorter-filters', f);
        //    }
        //})
        .bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
            var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
            $('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
        })
        .tablesorter({
            // *** APPEARANCE ***
            // Add a theme - try 'blackice', 'blue', 'dark', 'default'
            theme: 'blue',
            //showProcessing: true,

            // fix the column widths
            widthFixed: true,

            // include zebra and any other widgets, options:
            // 'columns', 'filter' & 'resizable'
            // 'uitheme' is another widget, but requires loading
            // a different skin and a jQuery UI theme.
            widgets: ['zebra', 'filter', 'output'],

            widgetOptions: {

                // zebra widget: adding zebra striping, using content and
                // default styles - the ui css removes the background
                // from default even and odd class names included for this
                // demo to allow switching themes
                // [ "even", "odd" ]
                zebra: [
                    "ui-widget-content even",
                    "ui-state-default odd"
                ],

                // uitheme widget: * Updated! in tablesorter v2.4 **
                // Instead of the array of icon class names, this option now
                // contains the name of the theme. Currently jQuery UI ("jui")
                // and Bootstrap ("bootstrap") themes are supported. To modify
                // the class names used, extend from the themes variable
                // look for the "$.extend($.tablesorter.themes.jui" code below
                uitheme: 'jui',

                // columns widget: change the default column class names
                // primary is the 1st column sorted, secondary is the 2nd, etc
                //columns: ["primary", "secondary", "tertiary"],

                // columns widget: If true, the class names from the columns
                // option will also be added to the table tfoot.
                //columns_tfoot: true,

                // columns widget: If true, the class names from the columns
                // option will also be added to the table thead.
                //columns_thead: true,

                // filter widget: If there are child rows in the table (rows with
                // class name from "cssChildRow" option) and this option is true
                // and a match is found anywhere in the child row, then it will make
                // that row visible; default is false
                //filter_childRows: false,

                // filter widget: If true, a filter will be added to the top of
                // each table column.
                //filter_columnFilters: true,

                // filter widget: css class applied to the table row containing the
                // filters & the inputs within that row
                filter_cssFilter: "tablesorter-filter",

                // filter widget: Customize the filter widget by adding a select
                // dropdown with content, custom options or custom filter functions
                // see http://goo.gl/HQQLW for more details
                //filter_functions: null,

                // filter widget: Set this option to true to hide the filter row
                // initially. The rows is revealed by hovering over the filter
                // row or giving any filter input/select focus.
                filter_hideFilters: false,

                // filter widget: Set this option to false to keep the searches
                // case sensitive
                //filter_ignoreCase: true,

                // filter widget: jQuery selector string of an element used to
                // reset the filters. 
                filter_reset: null,

                // Delay in milliseconds before the filter widget starts searching;
                // This option prevents searching for every character while typing
                // and should make searching large tables faster.
                filter_searchDelay: 300,

                // filter widget: Set this option to true to use the filter to find
                // text from the start of the column. So typing in "a" will find
                // "albert" but not "frank", both have a's; default is false
                //filter_startsWith: false,

                // filter widget: If true, ALL filter searches will only use parsed
                // data. To only use parsed data in specific columns, set this option
                // to false and add class name "filter-parsed" to the header
                //filter_useParsedData: false,

                // Resizable widget: If this option is set to false, resized column
                // widths will not be saved. Previous saved values will be restored
                // on page reload
                resizable: false,
                //resizable_widths: ['100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px' ],
                //resizable_addLastColumn : true,

                // fix the column widths
                widthFixed: true,

                // saveSort widget: If this option is set to false, new sorts will
                // not be saved. Any previous saved sort will be restored on page
                // reload.
                saveSort: false,

                // stickyHeaders widget: css class name applied to the sticky header
                //stickyHeaders: "tablesorter-stickyHeader"

				// Download options for table
				output_delivery: 'd',
				output_saveFileName: 'jobs.csv',

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}
            }
        })
        .tablesorterPager(pagerOptions);

	$('#jobConditionsTable')
		//.bind('filterInit', function () {
		//    // check that storage ulility is loaded
		//    if ($.tablesorter.storage) {
		//        // get saved filters
		//        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
		//        $(this).trigger('search', [f]);
		//    }
		//})
		//.bind('filterEnd', function () {
		//    if ($.tablesorter.storage) {
		//        // save current filters
		//        var f = $(this).find('.tablesorter-filter').map(function () {
		//            return $(this).val() || '';
		//        }).get();
		//        $.tablesorter.storage(this, 'tablesorter-filters', f);
		//    }
		//})
		.bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
			var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
			$('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
		})
		.tablesorter({
			// *** APPEARANCE ***
			// Add a theme - try 'blackice', 'blue', 'dark', 'default'
			theme: 'blue',
			//showProcessing: true,

			// fix the column widths
			widthFixed: true,

			// include zebra and any other widgets, options:
			// 'columns', 'filter' & 'resizable'
			// 'uitheme' is another widget, but requires loading
			// a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter'],

			widgetOptions: {

				// zebra widget: adding zebra striping, using content and
				// default styles - the ui css removes the background
				// from default even and odd class names included for this
				// demo to allow switching themes
				// [ "even", "odd" ]
				zebra: [
					"ui-widget-content even",
					"ui-state-default odd"
				],

				// uitheme widget: * Updated! in tablesorter v2.4 **
				// Instead of the array of icon class names, this option now
				// contains the name of the theme. Currently jQuery UI ("jui")
				// and Bootstrap ("bootstrap") themes are supported. To modify
				// the class names used, extend from the themes variable
				// look for the "$.extend($.tablesorter.themes.jui" code below
				uitheme: 'jui',

				// columns widget: change the default column class names
				// primary is the 1st column sorted, secondary is the 2nd, etc
				//columns: ["primary", "secondary", "tertiary"],

				// columns widget: If true, the class names from the columns
				// option will also be added to the table tfoot.
				//columns_tfoot: true,

				// columns widget: If true, the class names from the columns
				// option will also be added to the table thead.
				//columns_thead: true,

				// filter widget: If there are child rows in the table (rows with
				// class name from "cssChildRow" option) and this option is true
				// and a match is found anywhere in the child row, then it will make
				// that row visible; default is false
				//filter_childRows: false,

				// filter widget: If true, a filter will be added to the top of
				// each table column.
				//filter_columnFilters: true,

				// filter widget: css class applied to the table row containing the
				// filters & the inputs within that row
				filter_cssFilter: "tablesorter-filter",

				// filter widget: Customize the filter widget by adding a select
				// dropdown with content, custom options or custom filter functions
				// see http://goo.gl/HQQLW for more details
				//filter_functions: null,

				// filter widget: Set this option to true to hide the filter row
				// initially. The rows is revealed by hovering over the filter
				// row or giving any filter input/select focus.
				filter_hideFilters: false,

				// filter widget: Set this option to false to keep the searches
				// case sensitive
				//filter_ignoreCase: true,

				// filter widget: jQuery selector string of an element used to
				// reset the filters. 
				filter_reset: null,

				// Delay in milliseconds before the filter widget starts searching;
				// This option prevents searching for every character while typing
				// and should make searching large tables faster.
				filter_searchDelay: 300,

				// filter widget: Set this option to true to use the filter to find
				// text from the start of the column. So typing in "a" will find
				// "albert" but not "frank", both have a's; default is false
				//filter_startsWith: false,

				// filter widget: If true, ALL filter searches will only use parsed
				// data. To only use parsed data in specific columns, set this option
				// to false and add class name "filter-parsed" to the header
				//filter_useParsedData: false,

				// Resizable widget: If this option is set to false, resized column
				// widths will not be saved. Previous saved values will be restored
				// on page reload
				resizable: false,
				//resizable_widths: ['100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px' ],
				//resizable_addLastColumn : true,

				// fix the column widths
				widthFixed: true,

				// saveSort widget: If this option is set to false, new sorts will
				// not be saved. Any previous saved sort will be restored on page
				// reload.
				saveSort: false,

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}

				// stickyHeaders widget: css class name applied to the sticky header
				//stickyHeaders: "tablesorter-stickyHeader"

			}
		});

	$('#orderTable')
		//.bind('filterInit', function () {
		//    // check that storage ulility is loaded
		//    if ($.tablesorter.storage) {
		//        // get saved filters
		//        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
		//        $(this).trigger('search', [f]);
		//    }
		//})
		//.bind('filterEnd', function () {
		//    if ($.tablesorter.storage) {
		//        // save current filters
		//        var f = $(this).find('.tablesorter-filter').map(function () {
		//            return $(this).val() || '';
		//        }).get();
		//        $.tablesorter.storage(this, 'tablesorter-filters', f);
		//    }
		//})
		.bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
			var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
			$('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
		})
		.tablesorter({
			// *** APPEARANCE ***
			// Add a theme - try 'blackice', 'blue', 'dark', 'default'
			theme: 'blue',
			//showProcessing: true,

			// fix the column widths
			widthFixed: true,

			// include zebra and any other widgets, options:
			// 'columns', 'filter' & 'resizable'
			// 'uitheme' is another widget, but requires loading
			// a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter', 'output'],

			widgetOptions: {

				// zebra widget: adding zebra striping, using content and
				// default styles - the ui css removes the background
				// from default even and odd class names included for this
				// demo to allow switching themes
				// [ "even", "odd" ]
				zebra: [
					"ui-widget-content even",
					"ui-state-default odd"
				],

				// uitheme widget: * Updated! in tablesorter v2.4 **
				// Instead of the array of icon class names, this option now
				// contains the name of the theme. Currently jQuery UI ("jui")
				// and Bootstrap ("bootstrap") themes are supported. To modify
				// the class names used, extend from the themes variable
				// look for the "$.extend($.tablesorter.themes.jui" code below
				uitheme: 'jui',

				// columns widget: change the default column class names
				// primary is the 1st column sorted, secondary is the 2nd, etc
				//columns: ["primary", "secondary", "tertiary"],

				// columns widget: If true, the class names from the columns
				// option will also be added to the table tfoot.
				//columns_tfoot: true,

				// columns widget: If true, the class names from the columns
				// option will also be added to the table thead.
				//columns_thead: true,

				// filter widget: If there are child rows in the table (rows with
				// class name from "cssChildRow" option) and this option is true
				// and a match is found anywhere in the child row, then it will make
				// that row visible; default is false
				//filter_childRows: false,

				// filter widget: If true, a filter will be added to the top of
				// each table column.
				//filter_columnFilters: true,

				// filter widget: css class applied to the table row containing the
				// filters & the inputs within that row
				filter_cssFilter: "tablesorter-filter",

				// filter widget: Customize the filter widget by adding a select
				// dropdown with content, custom options or custom filter functions
				// see http://goo.gl/HQQLW for more details
				//filter_functions: null,

				// filter widget: Set this option to true to hide the filter row
				// initially. The rows is revealed by hovering over the filter
				// row or giving any filter input/select focus.
				filter_hideFilters: false,

				// filter widget: Set this option to false to keep the searches
				// case sensitive
				//filter_ignoreCase: true,

				// filter widget: jQuery selector string of an element used to
				// reset the filters. 
				filter_reset: null,

				// Delay in milliseconds before the filter widget starts searching;
				// This option prevents searching for every character while typing
				// and should make searching large tables faster.
				filter_searchDelay: 300,

				// filter widget: Set this option to true to use the filter to find
				// text from the start of the column. So typing in "a" will find
				// "albert" but not "frank", both have a's; default is false
				//filter_startsWith: false,

				// filter widget: If true, ALL filter searches will only use parsed
				// data. To only use parsed data in specific columns, set this option
				// to false and add class name "filter-parsed" to the header
				//filter_useParsedData: false,

				// Resizable widget: If this option is set to false, resized column
				// widths will not be saved. Previous saved values will be restored
				// on page reload
				resizable: false,
				//resizable_widths: ['100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px' ],
				//resizable_addLastColumn : true,

				// fix the column widths
				widthFixed: true,

				// saveSort widget: If this option is set to false, new sorts will
				// not be saved. Any previous saved sort will be restored on page
				// reload.
				saveSort: false,

				// stickyHeaders widget: css class name applied to the sticky header
				//stickyHeaders: "tablesorter-stickyHeader"

				// Download options for table
				output_delivery: 'd',
				output_saveFileName: 'orders.csv',

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}
			}
		})
		.tablesorterPager(pagerOptions);

    $('#quoteTable')
        //.bind('filterInit', function () {
        //    // check that storage ulility is loaded
        //    if($.tablesorter.storage) {
        //        // get saved filters
        //        var f = $.tablesorter.storage(this, 'tablesorter-filters') ||[];
        //        $(this).trigger('search', [f]);
        //    }
        //})
        //.bind('filterEnd', function () {
        //    if($.tablesorter.storage) {
        //        // save current filters
        //        var f = $(this).find('.tablesorter-filter').map(function() {
        //            return $(this).val() || '';
        //        }).get();
        //        $.tablesorter.storage(this, 'tablesorter-filters', f);
        //    }
        //})
        .bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
            var msg = '"</span> event triggered, ' +(e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' +(c.page +1) + '/' + c.totalPages + '</span>';
            $('#display')
				.append('<li><span class="str">"' +e.type +msg + '</li>')
				.find('li:first').remove();
        })
        .tablesorter({
                // *** APPEARANCE ***
                    // Add a theme - try 'blackice', 'blue', 'dark', 'default'
            theme: 'blue',

            // fix the column widths
            widthFixed: true,

                    // include zebra and any other widgets, options:
                    // 'columns', 'filter' & 'resizable'
                    // 'uitheme' is another widget, but requires loading
                    // a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter', 'output'],

                    widgetOptions: {

                        // zebra widget: adding zebra striping, using content and
                        // default styles - the ui css removes the background
                        // from default even and odd class names included for this
                        // demo to allow switching themes
                        // [ "even", "odd" ]
                    zebra: [
                    "ui-widget-content even",
                    "ui-state-default odd"
                    ],

                        // uitheme widget: * Updated! in tablesorter v2.4 **
                        // Instead of the array of icon class names, this option now
                        // contains the name of the theme. Currently jQuery UI ("jui")
                        // and Bootstrap ("bootstrap") themes are supported. To modify
                        // the class names used, extend from the themes variable
                        // look for the "$.extend($.tablesorter.themes.jui" code below
                    uitheme: 'jui',

                        // columns widget: change the default column class names
                        // primary is the 1st column sorted, secondary is the 2nd, etc
                    columns: [
                        "primary",
                        "secondary",
                        "tertiary"
                        ],

                        // columns widget: If true, the class names from the columns
                        // option will also be added to the table tfoot.
                    columns_tfoot: true,

                        // columns widget: If true, the class names from the columns
                        // option will also be added to the table thead.
                    columns_thead: true,

                        // filter widget: If there are child rows in the table (rows with
                        // class name from "cssChildRow" option) and this option is true
                        // and a match is found anywhere in the child row, then it will make
                        // that row visible; default is false
                    filter_childRows: false,

                        // filter widget: If true, a filter will be added to the top of
                        // each table column.
                    //filter_columnFilters: false,

                        // filter widget: css class applied to the table row containing the
                        // filters & the inputs within that row
                    filter_cssFilter: "tablesorter-filter",

                        // filter widget: Customize the filter widget by adding a select
                        // dropdown with content, custom options or custom filter functions
                        // see http://goo.gl/HQQLW for more details
                    filter_functions: null,

                        // filter widget: Set this option to true to hide the filter row
                        // initially. The rows is revealed by hovering over the filter
                        // row or giving any filter input/select focus.
                    filter_hideFilters: false,

                        // filter widget: Set this option to false to keep the searches
                        // case sensitive
                    filter_ignoreCase: true,

                        // filter widget: jQuery selector string of an element used to
                        // reset the filters. 
                    filter_reset: null,

                        // Delay in milliseconds before the filter widget starts searching;
                        // This option prevents searching for every character while typing
                        // and should make searching large tables faster.
                    filter_searchDelay: 300,

                        // filter widget: Set this option to true to use the filter to find
                        // text from the start of the column. So typing in "a" will find
                        // "albert" but not "frank", both have a's; default is false
                    filter_startsWith: false,

                        // filter widget: If true, ALL filter searches will only use parsed
                        // data. To only use parsed data in specific columns, set this option
                        // to false and add class name "filter-parsed" to the header
                    filter_useParsedData: false,

                        // Resizable widget: If this option is set to false, resized column
                        // widths will not be saved. Previous saved values will be restored
                        // on page reload
                    resizable: false,

                        // saveSort widget: If this option is set to false, new sorts will
                        // not be saved. Any previous saved sort will be restored on page
                        // reload.
                    saveSort: false,

                        // stickyHeaders widget: css class name applied to the sticky header
                    stickyHeaders: "tablesorter-stickyHeader",

					// Download options for table
					output_delivery: 'd',
					output_saveFileName: 'quotes.csv',
					output_dataAttrib: 'data-text',	// exports only text, not html for hover popups,

					// filters for multi select
					filter_formatter: {
						// Alphanumeric (exact)
						'th': function ($cell, indx) {
							var ts = $.tablesorter;
							return ts.filterFormatter.select2($cell, indx, {
								match: false // exact match only		
								//placeholder: 'Filter'	// messes with column widths - not able to fix it
							});
						}
					}
                }
                })
        .tablesorterPager(pagerOptions);

    $('#motorTable')
        //.bind('filterInit', function () {
        //    // check that storage ulility is loaded
        //    if ($.tablesorter.storage) {
        //        // get saved filters
        //        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
        //        $(this).trigger('search', [f]);
        //    }
        //})
        //.bind('filterEnd', function () {
        //    if ($.tablesorter.storage) {
        //        // save current filters
        //        var f = $(this).find('.tablesorter-filter').map(function () {
        //            return $(this).val() || '';
        //        }).get();
        //        $.tablesorter.storage(this, 'tablesorter-filters', f);
        //    }
        //})
        .bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
            var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
            $('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
        })
        .tablesorter({
            // *** APPEARANCE ***
            // Add a theme - try 'blackice', 'blue', 'dark', 'default'
            theme: 'blue',

            // fix the column widths
            widthFixed: true,

            // include zebra and any other widgets, options:
            // 'columns', 'filter' & 'resizable'
            // 'uitheme' is another widget, but requires loading
            // a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter', 'output'],

            widgetOptions: {

                // zebra widget: adding zebra striping, using content and
                // default styles - the ui css removes the background
                // from default even and odd class names included for this
                // demo to allow switching themes
                // [ "even", "odd" ]
                zebra: [
                "ui-widget-content even",
                "ui-state-default odd"
                ],

                // uitheme widget: * Updated! in tablesorter v2.4 **
                // Instead of the array of icon class names, this option now
                // contains the name of the theme. Currently jQuery UI ("jui")
                // and Bootstrap ("bootstrap") themes are supported. To modify
                // the class names used, extend from the themes variable
                // look for the "$.extend($.tablesorter.themes.jui" code below
                uitheme: 'jui',

                // columns widget: change the default column class names
                // primary is the 1st column sorted, secondary is the 2nd, etc
                columns: [
                    "primary",
                    "secondary",
                    "tertiary"
                ],

                // columns widget: If true, the class names from the columns
                // option will also be added to the table tfoot.
                columns_tfoot: true,

                // columns widget: If true, the class names from the columns
                // option will also be added to the table thead.
                columns_thead: true,

                // filter widget: If there are child rows in the table (rows with
                // class name from "cssChildRow" option) and this option is true
                // and a match is found anywhere in the child row, then it will make
                // that row visible; default is false
                filter_childRows: false,

                // filter widget: If true, a filter will be added to the top of
                // each table column.
                filter_columnFilters: true,

                // filter widget: css class applied to the table row containing the
                // filters & the inputs within that row
                filter_cssFilter: "tablesorter-filter",

                // filter widget: Customize the filter widget by adding a select
                // dropdown with content, custom options or custom filter functions
                // see http://goo.gl/HQQLW for more details
                filter_functions: null,

                // filter widget: Set this option to true to hide the filter row
                // initially. The rows is revealed by hovering over the filter
                // row or giving any filter input/select focus.
                filter_hideFilters: false,

                // filter widget: Set this option to false to keep the searches
                // case sensitive
                filter_ignoreCase: true,

                // filter widget: jQuery selector string of an element used to
                // reset the filters. 
                filter_reset: null,

                // Delay in milliseconds before the filter widget starts searching;
                // This option prevents searching for every character while typing
                // and should make searching large tables faster.
                filter_searchDelay: 300,

                // filter widget: Set this option to true to use the filter to find
                // text from the start of the column. So typing in "a" will find
                // "albert" but not "frank", both have a's; default is false
                filter_startsWith: false,

                // filter widget: If true, ALL filter searches will only use parsed
                // data. To only use parsed data in specific columns, set this option
                // to false and add class name "filter-parsed" to the header
                filter_useParsedData: false,

                // Resizable widget: If this option is set to false, resized column
                // widths will not be saved. Previous saved values will be restored
                // on page reload
                resizable: false,

                // saveSort widget: If this option is set to false, new sorts will
                // not be saved. Any previous saved sort will be restored on page
                // reload.
                saveSort: false,

                // stickyHeaders widget: css class name applied to the sticky header
                stickyHeaders: "tablesorter-stickyHeader",

				// Download options for table
				output_delivery: 'd',
				output_saveFileName: 'equipment.csv',

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}
            }
        })
        .tablesorterPager(pagerOptions);

    $('#invTable')
        //.bind('filterInit', function () {
        //    // check that storage ulility is loaded
        //    if ($.tablesorter.storage) {
        //        // get saved filters
        //        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
        //        $(this).trigger('search', [f]);
        //    }
        //})
        //.bind('filterEnd', function () {
        //    if ($.tablesorter.storage) {
        //        // save current filters
        //        var f = $(this).find('.tablesorter-filter').map(function () {
        //            return $(this).val() || '';
        //        }).get();
        //        $.tablesorter.storage(this, 'tablesorter-filters', f);
        //    }
        //})
        .bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
            var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
            $('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
        })
        .tablesorter({
            // *** APPEARANCE ***
            // Add a theme - try 'blackice', 'blue', 'dark', 'default'
            theme: 'blue',

            // fix the column widths
            widthFixed: true,

            // include zebra and any other widgets, options:
            // 'columns', 'filter' & 'resizable'
            // 'uitheme' is another widget, but requires loading
            // a different skin and a jQuery UI theme.
            widgets: ['zebra', 'filter'],

            widgetOptions: {

                // zebra widget: adding zebra striping, using content and
                // default styles - the ui css removes the background
                // from default even and odd class names included for this
                // demo to allow switching themes
                // [ "even", "odd" ]
                zebra: [
                "ui-widget-content even",
                "ui-state-default odd"
                ],

                // uitheme widget: * Updated! in tablesorter v2.4 **
                // Instead of the array of icon class names, this option now
                // contains the name of the theme. Currently jQuery UI ("jui")
                // and Bootstrap ("bootstrap") themes are supported. To modify
                // the class names used, extend from the themes variable
                // look for the "$.extend($.tablesorter.themes.jui" code below
                uitheme: 'jui',

                // columns widget: change the default column class names
                // primary is the 1st column sorted, secondary is the 2nd, etc
                columns: [
                    "primary",
                    "secondary",
                    "tertiary"
                ],

                // columns widget: If true, the class names from the columns
                // option will also be added to the table tfoot.
                columns_tfoot: true,

                // columns widget: If true, the class names from the columns
                // option will also be added to the table thead.
                columns_thead: true,
				
                // filter widget: If there are child rows in the table (rows with
                // class name from "cssChildRow" option) and this option is true
                // and a match is found anywhere in the child row, then it will make
                // that row visible; default is false
                filter_childRows: false,

                // filter widget: If true, a filter will be added to the top of
                // each table column.
                filter_columnFilters: true,

                // filter widget: css class applied to the table row containing the
                // filters & the inputs within that row
                filter_cssFilter: "tablesorter-filter",

                // filter widget: Customize the filter widget by adding a select
                // dropdown with content, custom options or custom filter functions
                // see http://goo.gl/HQQLW for more details
                filter_functions: null,
                
                // filter widget: Set this option to true to hide the filter row
                // initially. The rows is revealed by hovering over the filter
                // row or giving any filter input/select focus.
                filter_hideFilters: false,

                // filter widget: Set this option to false to keep the searches
                // case sensitive
                filter_ignoreCase: true,

                // filter widget: jQuery selector string of an element used to
                // reset the filters. 
                //filter_reset: null,
                filter_reset: '.reset',

                // Delay in milliseconds before the filter widget starts searching;
                // This option prevents searching for every character while typing
                // and should make searching large tables faster.
                filter_searchDelay: 300,

                // filter widget: Set this option to true to use the filter to find
                // text from the start of the column. So typing in "a" will find
                // "albert" but not "frank", both have a's; default is false
                filter_startsWith: false,

                // filter widget: If true, ALL filter searches will only use parsed
                // data. To only use parsed data in specific columns, set this option
                // to false and add class name "filter-parsed" to the header
                filter_useParsedData: false,

                // Resizable widget: If this option is set to false, resized column
                // widths will not be saved. Previous saved values will be restored
                // on page reload
                resizable: false,

                // saveSort widget: If this option is set to false, new sorts will
                // not be saved. Any previous saved sort will be restored on page
                // reload.
                saveSort: false,

                // stickyHeaders widget: css class name applied to the sticky header
				stickyHeaders: "tablesorter-stickyHeader",

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}
                
            }
        })
        .tablesorterPager(pagerOptions);

	$('#tcoTable')
		//.bind('filterInit', function () {
		//    // check that storage ulility is loaded
		//    if ($.tablesorter.storage) {
		//        // get saved filters
		//        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
		//        $(this).trigger('search', [f]);
		//    }
		//})
		//.bind('filterEnd', function () {
		//    if ($.tablesorter.storage) {
		//        // save current filters
		//        var f = $(this).find('.tablesorter-filter').map(function () {
		//            return $(this).val() || '';
		//        }).get();
		//        $.tablesorter.storage(this, 'tablesorter-filters', f);
		//    }
		//})
		.bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
			var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
			$('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
		})
		.tablesorter({
			// *** APPEARANCE ***
			// Add a theme - try 'blackice', 'blue', 'dark', 'default'
			theme: 'blue',
			//showProcessing: true,

			// fix the column widths
			widthFixed: true,

			// include zebra and any other widgets, options:
			// 'columns', 'filter' & 'resizable'
			// 'uitheme' is another widget, but requires loading
			// a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter', 'output'],

			widgetOptions: {

				// zebra widget: adding zebra striping, using content and
				// default styles - the ui css removes the background
				// from default even and odd class names included for this
				// demo to allow switching themes
				// [ "even", "odd" ]
				zebra: [
					"ui-widget-content even",
					"ui-state-default odd"
				],

				// uitheme widget: * Updated! in tablesorter v2.4 **
				// Instead of the array of icon class names, this option now
				// contains the name of the theme. Currently jQuery UI ("jui")
				// and Bootstrap ("bootstrap") themes are supported. To modify
				// the class names used, extend from the themes variable
				// look for the "$.extend($.tablesorter.themes.jui" code below
				uitheme: 'jui',

				// columns widget: change the default column class names
				// primary is the 1st column sorted, secondary is the 2nd, etc
				//columns: ["primary", "secondary", "tertiary"],

				// columns widget: If true, the class names from the columns
				// option will also be added to the table tfoot.
				//columns_tfoot: true,

				// columns widget: If true, the class names from the columns
				// option will also be added to the table thead.
				//columns_thead: true,

				// filter widget: If there are child rows in the table (rows with
				// class name from "cssChildRow" option) and this option is true
				// and a match is found anywhere in the child row, then it will make
				// that row visible; default is false
				//filter_childRows: false,

				// filter widget: If true, a filter will be added to the top of
				// each table column.
				//filter_columnFilters: true,

				// filter widget: css class applied to the table row containing the
				// filters & the inputs within that row
				filter_cssFilter: "tablesorter-filter",

				// filter widget: Customize the filter widget by adding a select
				// dropdown with content, custom options or custom filter functions
				// see http://goo.gl/HQQLW for more details
				//filter_functions: null,

				// filter widget: Set this option to true to hide the filter row
				// initially. The rows is revealed by hovering over the filter
				// row or giving any filter input/select focus.
				filter_hideFilters: false,

				// filter widget: Set this option to false to keep the searches
				// case sensitive
				//filter_ignoreCase: true,

				// filter widget: jQuery selector string of an element used to
				// reset the filters. 
				filter_reset: null,

				// Delay in milliseconds before the filter widget starts searching;
				// This option prevents searching for every character while typing
				// and should make searching large tables faster.
				filter_searchDelay: 300,

				// filter widget: Set this option to true to use the filter to find
				// text from the start of the column. So typing in "a" will find
				// "albert" but not "frank", both have a's; default is false
				//filter_startsWith: false,

				// filter widget: If true, ALL filter searches will only use parsed
				// data. To only use parsed data in specific columns, set this option
				// to false and add class name "filter-parsed" to the header
				//filter_useParsedData: false,

				// Resizable widget: If this option is set to false, resized column
				// widths will not be saved. Previous saved values will be restored
				// on page reload
				resizable: false,
				//resizable_widths: ['100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px' ],
				//resizable_addLastColumn : true,

				// fix the column widths
				widthFixed: true,

				// saveSort widget: If this option is set to false, new sorts will
				// not be saved. Any previous saved sort will be restored on page
				// reload.
				saveSort: false,

				// stickyHeaders widget: css class name applied to the sticky header
				//stickyHeaders: "tablesorter-stickyHeader"

				// Download options for table
				output_delivery: 'd',
				output_saveFileName: 'tco.csv',

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}
			}
		})
		.tablesorterPager(pagerOptions);

	$('#tcoTable1')
		//.bind('filterInit', function () {
		//    // check that storage ulility is loaded
		//    if ($.tablesorter.storage) {
		//        // get saved filters
		//        var f = $.tablesorter.storage(this, 'tablesorter-filters') || [];
		//        $(this).trigger('search', [f]);
		//    }
		//})
		//.bind('filterEnd', function () {
		//    if ($.tablesorter.storage) {
		//        // save current filters
		//        var f = $(this).find('.tablesorter-filter').map(function () {
		//            return $(this).val() || '';
		//        }).get();
		//        $.tablesorter.storage(this, 'tablesorter-filters', f);
		//    }
		//})
		.bind('pagerChange pagerComplete pagerInitialized pageMoved', function (e, c) {
			var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
				' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
			$('#display')
				.append('<li><span class="str">"' + e.type + msg + '</li>')
				.find('li:first').remove();
		})
		.tablesorter({
			// *** APPEARANCE ***
			// Add a theme - try 'blackice', 'blue', 'dark', 'default'
			theme: 'blue',
			//showProcessing: true,

			// fix the column widths
			widthFixed: true,

			// include zebra and any other widgets, options:
			// 'columns', 'filter' & 'resizable'
			// 'uitheme' is another widget, but requires loading
			// a different skin and a jQuery UI theme.
			widgets: ['zebra', 'filter', 'output'],

			widgetOptions: {

				// zebra widget: adding zebra striping, using content and
				// default styles - the ui css removes the background
				// from default even and odd class names included for this
				// demo to allow switching themes
				// [ "even", "odd" ]
				zebra: [
					"ui-widget-content even",
					"ui-state-default odd"
				],

				// uitheme widget: * Updated! in tablesorter v2.4 **
				// Instead of the array of icon class names, this option now
				// contains the name of the theme. Currently jQuery UI ("jui")
				// and Bootstrap ("bootstrap") themes are supported. To modify
				// the class names used, extend from the themes variable
				// look for the "$.extend($.tablesorter.themes.jui" code below
				uitheme: 'jui',

				// columns widget: change the default column class names
				// primary is the 1st column sorted, secondary is the 2nd, etc
				//columns: ["primary", "secondary", "tertiary"],

				// columns widget: If true, the class names from the columns
				// option will also be added to the table tfoot.
				//columns_tfoot: true,

				// columns widget: If true, the class names from the columns
				// option will also be added to the table thead.
				//columns_thead: true,

				// filter widget: If there are child rows in the table (rows with
				// class name from "cssChildRow" option) and this option is true
				// and a match is found anywhere in the child row, then it will make
				// that row visible; default is false
				//filter_childRows: false,

				// filter widget: If true, a filter will be added to the top of
				// each table column.
				//filter_columnFilters: true,

				// filter widget: css class applied to the table row containing the
				// filters & the inputs within that row
				filter_cssFilter: "tablesorter-filter",

				// filter widget: Customize the filter widget by adding a select
				// dropdown with content, custom options or custom filter functions
				// see http://goo.gl/HQQLW for more details
				//filter_functions: null,

				// filter widget: Set this option to true to hide the filter row
				// initially. The rows is revealed by hovering over the filter
				// row or giving any filter input/select focus.
				filter_hideFilters: false,

				// filter widget: Set this option to false to keep the searches
				// case sensitive
				//filter_ignoreCase: true,

				// filter widget: jQuery selector string of an element used to
				// reset the filters. 
				filter_reset: null,

				// Delay in milliseconds before the filter widget starts searching;
				// This option prevents searching for every character while typing
				// and should make searching large tables faster.
				filter_searchDelay: 300,

				// filter widget: Set this option to true to use the filter to find
				// text from the start of the column. So typing in "a" will find
				// "albert" but not "frank", both have a's; default is false
				//filter_startsWith: false,

				// filter widget: If true, ALL filter searches will only use parsed
				// data. To only use parsed data in specific columns, set this option
				// to false and add class name "filter-parsed" to the header
				//filter_useParsedData: false,

				// Resizable widget: If this option is set to false, resized column
				// widths will not be saved. Previous saved values will be restored
				// on page reload
				resizable: false,
				//resizable_widths: ['100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px', '100px' ],
				//resizable_addLastColumn : true,

				// fix the column widths
				widthFixed: true,

				// saveSort widget: If this option is set to false, new sorts will
				// not be saved. Any previous saved sort will be restored on page
				// reload.
				saveSort: false,

				// stickyHeaders widget: css class name applied to the sticky header
				//stickyHeaders: "tablesorter-stickyHeader"

				// Download options for table
				output_delivery: 'd',
				output_saveFileName: 'tco.csv',

				// filters for multi select
				filter_formatter: {
					// Alphanumeric (exact)
					'th': function ($cell, indx) {
						var ts = $.tablesorter;
						return ts.filterFormatter.select2($cell, indx, {
							match: false // exact match only		
							//placeholder: 'Filter'	// messes with column widths - not able to fix it
						});
					}
				}
			}
		})
		.tablesorterPager(pagerOptions);

	// used for theming only - aka attachments/files listing
	$('.tableSorterTable')
		.tablesorter({
			theme: 'blue',
			widthFixed: true,
			widgets: ['zebra'],
			widgetOptions: {
				zebra: [
					"ui-widget-content even",
					"ui-state-default odd"
				],
				uitheme: 'jui'
			}
		});

	$(window).bind('onunload', function () {
		window.location.href = 'close.aspx';
	});

});


/*! Widget: filter, select2 formatter function - updated 12/1/2019 (v2.31.2) *//*
* requires: jQuery 1.7.2+, tableSorter (FORK) 2.16+, filter widget 2.16+
and select2 v3.4.6+ plugin (this code is NOT compatible with select2 v4+)
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
(function ($) {
	'use strict';

	var ts = $.tablesorter || {};
	ts.filterFormatter = ts.filterFormatter || {};

	//Select2 Filter Formatter
	ts.filterFormatter.select2 = function ($cell, indx, select2Def) {
		var o = $.extend({
			// select2 filter formatter options
			cellText: '', // Text (wrapped in a label element)
			match: true, // adds 'filter-match' to header
			value: '',
			// include ANY select2 options below
			multiple: true,
			width: '100%'

		}, select2Def),
			arry, data,
			// add class to $cell since it may point to a removed DOM node
			// after a "refreshWidgets"; see #1237
			c = $cell.addClass('select2col' + indx).closest('table')[0].config,
			wo = c.widgetOptions,
			// Add a hidden input to hold the range values
			$input = $('<input class="filter" type="hidden">')
				.appendTo($cell)
				// hidden filter update namespace trigger by filter widget
				.bind('change' + c.namespace + 'filter', function () {
					var val = convertRegex(this.value);
					c.$table.find('.select2col' + indx + ' .select2').select2('val', val);
					updateSelect2();
				}),
			$header = c.$headerIndexed[indx],
			onlyAvail = $header.hasClass(wo.filter_onlyAvail),
			matchPrefix = o.match ? '' : '^',
			matchSuffix = o.match ? '' : '$',
			flags = wo.filter_ignoreCase ? 'i' : '',

			convertRegex = function (val) {
				// value = '/(^x$|^y$)/' => ['x','y']
				return val
					.replace(/^\/\(\^?/, '')
					.replace(/\$\|\^/g, '|')
					.replace(/\$?\)\/i?$/g, '')
					// unescape special regex characters
					.replace(/\\/g, '')
					.split('|');
			},

			// this function updates the hidden input and adds the current values to the header cell text
			updateSelect2 = function () {
				var arry = false,
					v = c.$table.find('.select2col' + indx + ' .select2').select2('val') || o.value || '';
				// convert array to string
				if ($.isArray(v)) {
					arry = true;
					v = v.join('\u0000');
				}
				// escape special regex characters (http://stackoverflow.com/a/9310752/145346)
				var v_escape = v.replace(/[-[\]{}()*+?.,/\\^$|#]/g, '\\$&');
				// convert string back into an array
				if (arry) {
					v = v.split('\u0000');
					v_escape = v_escape.split('\u0000');
				}
				if (!ts.isEmptyObject($cell.find('.select2').data())) {
					$input
						// add regex, so we filter exact numbers
						.val(
							$.isArray(v_escape) && v_escape.length && v_escape.join('') !== '' ?
								'/(' + matchPrefix + (v_escape || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' + flags :
								''
						)
						.trigger('search');
					$cell.find('.select2').select2('val', v);
					// update sticky header cell
					if (c.widgetOptions.$sticky) {
						c.widgetOptions.$sticky.find('.select2col' + indx + ' .select2').select2('val', v);
					}
				}
			},

			// get options from table cell content or filter_selectSource (v2.16)
			updateOptions = function () {
				data = [];
				arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || [];
				// build select2 data option
				$.each(arry, function (i, v) {
					// getOptionSource returns { parsed: "value", text: "value" } in v2.24.4
					data.push({ id: '' + v.parsed, text: v.text });
				});
				o.data = data;
			};

		// get filter-match class from option
		$header.toggleClass('filter-match', o.match);
		if (o.cellText) {
			$cell.prepend('<label>' + o.cellText + '</label>');
		}

		// don't add default in table options if either ajax or
		// data options are already defined
		if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) {
			updateOptions();
			c.$table.bind('filterEnd', function () {
				updateOptions();
				c.$table
					.find('.select2col' + indx)
					.add(c.widgetOptions.$sticky && c.widgetOptions.$sticky.find('.select2col' + indx))
					.find('.select2').select2(o);
			});
		}

		// add a select2 hidden input!
		$('<input class="select2 select2-' + indx + '" type="hidden" />')
			.val(o.value)
			.appendTo($cell)
			.select2(o)
			.bind('change', function () {
				updateSelect2();
			});

		// update select2 from filter hidden input, in case of saved filters
		c.$table.bind('filterFomatterUpdate', function () {
			// value = '/(^x$|^y$)/' => 'x,y'
			var val = convertRegex(c.$table.data('lastSearch')[indx] || '');
			$cell = c.$table.find('.select2col' + indx);
			$cell.find('.select2').select2('val', val);
			updateSelect2();
			ts.filter.formatterUpdated($cell, indx);
		});

		// has sticky headers?
		c.$table.bind('stickyHeadersInit', function () {
			var $shcell = c.widgetOptions.$sticky.find('.select2col' + indx).empty();
			// add a select2!
			$('<input class="select2 select2-' + indx + '" type="hidden">')
				.val(o.value)
				.appendTo($shcell)
				.select2(o)
				.bind('change', function () {
					c.$table.find('.select2col' + indx)
						.find('.select2')
						.select2('val', c.widgetOptions.$sticky.find('.select2col' + indx + ' .select2').select2('val'));
					updateSelect2();
				});
			if (o.cellText) {
				$shcell.prepend('<label>' + o.cellText + '</label>');
			}
		});

		// on reset
		c.$table.bind('filterReset', function () {
			c.$table.find('.select2col' + indx).find('.select2').select2('val', o.value || '');
			setTimeout(function () {
				updateSelect2();
			}, 0);
		});

		updateSelect2();
		return $input;
	};

})(jQuery);
