/*
* FooTable v3 - FooTable is a jQuery plugin that aims to make HTML tables on smaller devices look awesome.
* @version 3.1.5
* @link http://fooplugins.com
* @copyright Steven Usher & Brad Vincent 2015
* @license Released under the GPLv3 license.
*/
(function(F){
	F.Filter = F.Class.extend(/** @lends FooTable.Filter */{
		/**
		 * The filter object contains the query to filter by and the columns to apply it to.
		 * @constructs
		 * @extends FooTable.Class
		 * @param {string} name - The name for the filter.
		 * @param {(string|FooTable.Query)} query - The query for the filter.
		 * @param {Array.<FooTable.Column>} columns - The columns to apply the query to.
		 * @param {string} [space="AND"] - How the query treats space chars.
		 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces.
		 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching.
		 * @param {boolean} [hidden=true] - Whether or not this is a hidden filter.
		 * @returns {FooTable.Filter}
		 */
		construct: function(name, query, columns, space, connectors, ignoreCase, hidden){
			/**
			 * The name of the filter.
			 * @instance
			 * @type {string}
			 */
			this.name = name;
			/**
			 * A string specifying how the filter treats space characters. Can be either "OR" or "AND".
			 * @instance
			 * @type {string}
			 */
			this.space = F.is.string(space) && (space == 'OR' || space == 'AND') ? space : 'AND';
			/**
			 * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query.
			 * @instance
			 * @type {boolean}
			 */
			this.connectors = F.is.boolean(connectors) ? connectors : true;
			/**
			 * Whether or not ignore case when matching.
			 * @instance
			 * @type {boolean}
			 */
			this.ignoreCase = F.is.boolean(ignoreCase) ? ignoreCase : true;
			/**
			 * Whether or not this is a hidden filter.
			 * @instance
			 * @type {boolean}
			 */
			this.hidden = F.is.boolean(hidden) ? hidden : false;
			/**
			 * The query for the filter.
			 * @instance
			 * @type {(string|FooTable.Query)}
			 */
			this.query = query instanceof F.Query ? query : new F.Query(query, this.space, this.connectors, this.ignoreCase);
			/**
			 * The columns to apply the query to.
			 * @instance
			 * @type {Array.<FooTable.Column>}
			 */
			this.columns = columns;
		},
		/**
		 * Checks if the current filter matches the supplied string.
		 * If the current query property is a string it will be auto converted to a {@link FooTable.Query} object to perform the match.
		 * @instance
		 * @param {string} str - The string to check.
		 * @returns {boolean}
		 */
		match: function(str){
			if (!F.is.string(str)) return false;
			if (F.is.string(this.query)){
				this.query = new F.Query(this.query, this.space, this.connectors, this.ignoreCase);
			}
			return this.query instanceof F.Query ? this.query.match(str) : false;
		},
		/**
		 * Checks if the current filter matches the supplied {@link FooTable.Row}.
		 * @instance
		 * @param {FooTable.Row} row - The row to check.
		 * @returns {boolean}
		 */
		matchRow: function(row){
			var self = this, text = F.arr.map(row.cells, function(cell){
				return F.arr.contains(self.columns, cell.column) ? cell.filterValue : null;
			}).join(' ');
			return self.match(text);
		}
	});

})(FooTable);
(function ($, F) {
	F.Filtering = F.Component.extend(/** @lends FooTable.Filtering */{
		/**
		 * The filtering component adds a search input and column selector dropdown to the table allowing users to filter the using space delimited queries.
		 * @constructs
		 * @extends FooTable.Component
		 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component.
		 * @returns {FooTable.Filtering}
		 */
		construct: function (table) {
			// call the constructor of the base class
			this._super(table, table.o.filtering.enabled);

			/* PUBLIC */
			/**
			 * The filters to apply to the current {@link FooTable.Rows#array}.
			 * @instance
			 * @type {Array.<FooTable.Filter>}
			 */
			this.filters = table.o.filtering.filters;
			/**
			 * The delay in milliseconds before the query is auto applied after a change.
			 * @instance
			 * @type {number}
			 */
			this.delay = table.o.filtering.delay;
			/**
			 * The minimum number of characters allowed in the search input before it is auto applied.
			 * @instance
			 * @type {number}
			 */
			this.min = table.o.filtering.min;
			/**
			 * Specifies how whitespace in a filter query is handled.
			 * @instance
			 * @type {string}
			 */
			this.space = table.o.filtering.space;
			/**
			 * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query.
			 * @instance
			 * @type {boolean}
			 */
			this.connectors = table.o.filtering.connectors;
			/**
			 * Whether or not ignore case when matching.
			 * @instance
			 * @type {boolean}
			 */
			this.ignoreCase = table.o.filtering.ignoreCase;
			/**
			 * Whether or not search queries are treated as phrases when matching.
			 * @instance
			 * @type {boolean}
			 */
			this.exactMatch = table.o.filtering.exactMatch;
			/**
			 * The placeholder text to display within the search $input.
			 * @instance
			 * @type {string}
			 */
			this.placeholder = table.o.filtering.placeholder;
			/**
			 * The title to display at the top of the search input column select.
			 * @type {string}
			 */
			this.dropdownTitle = table.o.filtering.dropdownTitle;
			/**
			 * The position of the $search input within the filtering rows cell.
			 * @type {string}
			 */
			this.position = table.o.filtering.position;
			/**
			 * Whether or not to focus the search input after the search/clear button is clicked or after auto applying the search input query.
			 * @type {boolean}
			 */
			this.focus = table.o.filtering.focus;
			/**
			 * A selector specifying where to place the filtering components form, if null the form is displayed within a row in the head of the table.
			 * @type {string}
			 */
			this.container = table.o.filtering.container;
			/**
			 * The jQuery object of the element containing the entire filtering form.
			 * @instance
			 * @type {jQuery}
			 */
			this.$container = null;
			/**
			 * The jQuery row object that contains all the filtering specific elements.
			 * @instance
			 * @type {jQuery}
			 */
			this.$row = null;
			/**
			 * The jQuery cell object that contains the search input and column selector.
			 * @instance
			 * @type {jQuery}
			 */
			this.$cell = null;
			/**
			 * The jQuery form object of the form that contains the search input and column selector.
			 * @instance
			 * @type {jQuery}
			 */
			this.$form = null;
			/**
			 * The jQuery object of the column selector dropdown.
			 * @instance
			 * @type {jQuery}
			 */
			this.$dropdown = null;
			/**
			 * The jQuery object of the search input.
			 * @instance
			 * @type {jQuery}
			 */
			this.$input = null;
			/**
			 * The jQuery object of the search button.
			 * @instance
			 * @type {jQuery}
			 */
			this.$button = null;

			/* PRIVATE */
			/**
			 * The timeout ID for the filter changed event.
			 * @instance
			 * @private
			 * @type {?number}
			 */
			this._filterTimeout = null;
			/**
			 * The regular expression used to check for encapsulating quotations.
			 * @instance
			 * @private
			 * @type {RegExp}
			 */
			this._exactRegExp = /^"(.*?)"$/;
		},

		/* PROTECTED */
		/**
		 * Checks the supplied data and options for the filtering component.
		 * @instance
		 * @protected
		 * @param {object} data - The jQuery data object from the parent table.
		 * @fires FooTable.Filtering#"preinit.ft.filtering"
		 */
		preinit: function(data){
			var self = this;
			/**
			 * The preinit.ft.filtering event is raised before the UI is created and provides the tables jQuery data object for additional options parsing.
			 * Calling preventDefault on this event will disable the component.
			 * @event FooTable.Filtering#"preinit.ft.filtering"
			 * @param {jQuery.Event} e - The jQuery.Event object for the event.
			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
			 * @param {object} data - The jQuery data object of the table raising the event.
			 */
			return self.ft.raise('preinit.ft.filtering').then(function(){
				// first check if filtering is enabled via the class being applied
				if (self.ft.$el.hasClass('footable-filtering'))
					self.enabled = true;
				// then check if the data-filtering-enabled attribute has been set
				self.enabled = F.is.boolean(data.filtering)
					? data.filtering
					: self.enabled;

				// if filtering is not enabled exit early as we don't need to do anything else
				if (!self.enabled) return;

				self.space = F.is.string(data.filterSpace)
					? data.filterSpace
					: self.space;

				self.min = F.is.number(data.filterMin)
					? data.filterMin
					: self.min;

				self.connectors = F.is.boolean(data.filterConnectors)
					? data.filterConnectors
					: self.connectors;

				self.ignoreCase = F.is.boolean(data.filterIgnoreCase)
					? data.filterIgnoreCase
					: self.ignoreCase;

				self.exactMatch = F.is.boolean(data.filterExactMatch)
					? data.filterExactMatch
					: self.exactMatch;

				self.focus = F.is.boolean(data.filterFocus)
					? data.filterFocus
					: self.focus;

				self.delay = F.is.number(data.filterDelay)
					? data.filterDelay
					: self.delay;

				self.placeholder = F.is.string(data.filterPlaceholder)
					? data.filterPlaceholder
					: self.placeholder;

				self.dropdownTitle = F.is.string(data.filterDropdownTitle)
					? data.filterDropdownTitle
					: self.dropdownTitle;

				self.container = F.is.string(data.filterContainer)
					? data.filterContainer
					: self.container;

				self.filters = F.is.array(data.filterFilters)
					? self.ensure(data.filterFilters)
					: self.ensure(self.filters);

				if (self.ft.$el.hasClass('footable-filtering-left'))
					self.position = 'left';
				if (self.ft.$el.hasClass('footable-filtering-center'))
					self.position = 'center';
				if (self.ft.$el.hasClass('footable-filtering-right'))
					self.position = 'right';

				self.position = F.is.string(data.filterPosition)
					? data.filterPosition
					: self.position;
			},function(){
				self.enabled = false;
			});
		},
		/**
		 * Initializes the filtering component for the plugin.
		 * @instance
		 * @protected
		 * @fires FooTable.Filtering#"init.ft.filtering"
		 */
		init: function () {
			var self = this;
			/**
			 * The init.ft.filtering event is raised before its UI is generated.
			 * Calling preventDefault on this event will disable the component.
			 * @event FooTable.Filtering#"init.ft.filtering"
			 * @param {jQuery.Event} e - The jQuery.Event object for the event.
			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
			 */
			return self.ft.raise('init.ft.filtering').then(function(){
				self.$create();
			}, function(){
				self.enabled = false;
			});
		},
		/**
		 * Destroys the filtering component removing any UI from the table.
		 * @instance
		 * @protected
		 * @fires FooTable.Filtering#"destroy.ft.filtering"
		 */
		destroy: function () {
			var self = this;
			/**
			 * The destroy.ft.filtering event is raised before its UI is removed.
			 * Calling preventDefault on this event will prevent the component from being destroyed.
			 * @event FooTable.Filtering#"destroy.ft.filtering"
			 * @param {jQuery.Event} e - The jQuery.Event object for the event.
			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
			 */
			return self.ft.raise('destroy.ft.filtering').then(function(){
				self.ft.$el.removeClass('footable-filtering')
					.find('thead > tr.footable-filtering').remove();
			});
		},
		/**
		 * Creates the filtering UI from the current options setting the various jQuery properties of this component.
		 * @instance
		 * @protected
		 * @this FooTable.Filtering
		 */
		$create: function () {
			var self = this;
			// generate the cell that actually contains all the UI.
			var $form_grp = $('<div/>', {'class': 'form-group footable-filtering-search'})
					.append($('<label/>', {'class': 'sr-only', text: 'Search'})),
				$input_grp = $('<div/>', {'class': 'input-group'}).appendTo($form_grp),
				$input_grp_btn = $('<div/>', {'class': 'input-group-btn'}),
				$dropdown_toggle = $('<button/>', {type: 'button', 'class': 'btn btn-default dropdown-toggle'})
					.on('click', { self: self }, self._onDropdownToggleClicked)
					.append($('<span/>', {'class': 'caret'})),
				position;

			switch (self.position){
				case 'left': position = 'footable-filtering-left'; break;
				case 'center': position = 'footable-filtering-center'; break;
				default: position = 'footable-filtering-right'; break;
			}
			self.ft.$el.addClass('footable-filtering').addClass(position);

			self.$container = self.container === null ? $() : $(self.container).first();
			if (!self.$container.length){
				// add it to a row and then populate it with the search input and column selector dropdown.
				self.$row = $('<tr/>', {'class': 'footable-filtering'}).prependTo(self.ft.$el.children('thead'));
				self.$cell = $('<th/>').attr('colspan', self.ft.columns.visibleColspan).appendTo(self.$row);
				self.$container = self.$cell;
			} else {
				self.$container.addClass('footable-filtering-external').addClass(position);
			}
			self.$form = $('<form/>', {'class': 'form-inline'}).append($form_grp).appendTo(self.$container);

			self.$input = $('<input/>', {type: 'text', 'class': 'form-control', placeholder: self.placeholder});

			self.$button = $('<button/>', {type: 'button', 'class': 'btn btn-primary'})
				.on('click', { self: self }, self._onSearchButtonClicked)
				.append($('<span/>', {'class': 'fooicon fooicon-search'}));

			self.$dropdown = $('<ul/>', {'class': 'dropdown-menu dropdown-menu-right'});
			if (!F.is.emptyString(self.dropdownTitle)){
				self.$dropdown.append($('<li/>', {'class': 'dropdown-header','text': self.dropdownTitle}));
			}
			self.$dropdown.append(
				F.arr.map(self.ft.columns.array, function (col) {
					return col.filterable ? $('<li/>').append(
						$('<a/>', {'class': 'checkbox'}).append(
							$('<label/>', {html: col.title}).prepend(
								$('<input/>', {type: 'checkbox', checked: true}).data('__FooTableColumn__', col)
							)
						)
					) : null;
				})
			);

			if (self.delay > 0){
				self.$input.on('keypress keyup paste', { self: self }, self._onSearchInputChanged);
				self.$dropdown.on('click', 'input[type="checkbox"]', {self: self}, self._onSearchColumnClicked);
			}

			$input_grp_btn.append(self.$button, $dropdown_toggle, self.$dropdown);
			$input_grp.append(self.$input, $input_grp_btn);
		},
		/**
		 * Performs the filtering of rows before they are appended to the page.
		 * @instance
		 * @protected
		 */
		predraw: function(){
			if (F.is.emptyArray(this.filters))
				return;

			var self = this;
			self.ft.rows.array = $.grep(self.ft.rows.array, function(r){
				return r.filtered(self.filters);
			});
		},
		/**
		 * As the rows are drawn by the {@link FooTable.Rows#draw} method this simply updates the colspan for the UI.
		 * @instance
		 * @protected
		 */
		draw: function(){
			if (F.is.jq(this.$cell)){
				this.$cell.attr('colspan', this.ft.columns.visibleColspan);
			}
			var search = this.find('search');
			if (search instanceof F.Filter){
				var query = search.query.val();
				if (this.exactMatch && this._exactRegExp.test(query)){
					query = query.replace(this._exactRegExp, '$1');
				}
				this.$input.val(query);
			} else {
				this.$input.val(null);
			}
			this.setButton(!F.arr.any(this.filters, function(f){ return !f.hidden; }));
		},

		/* PUBLIC */
		/**
		 * Adds or updates the filter using the supplied name, query and columns.
		 * @instance
		 * @param {(string|FooTable.Filter|object)} nameOrFilter - The name for the filter or the actual filter object itself.
		 * @param {(string|FooTable.Query)} [query] - The query for the filter. This is only optional when the first parameter is a filter object.
		 * @param {(Array.<number>|Array.<string>|Array.<FooTable.Column>)} [columns] - The columns to apply the filter to.
		 * 	If not supplied the filter will be applied to all selected columns in the search input dropdown.
		 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching.
		 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces.
		 * @param {string} [space="AND"] - How the query treats space chars.
		 * @param {boolean} [hidden=true] - Whether or not this is a hidden filter.
		 */
		addFilter: function(nameOrFilter, query, columns, ignoreCase, connectors, space, hidden){
			var f = this.createFilter(nameOrFilter, query, columns, ignoreCase, connectors, space, hidden);
			if (f instanceof F.Filter){
				this.removeFilter(f.name);
				this.filters.push(f);
			}
		},
		/**
		 * Removes the filter using the supplied name if it exists.
		 * @instance
		 * @param {string} name - The name of the filter to remove.
		 */
		removeFilter: function(name){
			F.arr.remove(this.filters, function(f){ return f.name == name; });
		},
		/**
		 * Performs the required steps to handle filtering including the raising of the {@link FooTable.Filtering#"before.ft.filtering"} and {@link FooTable.Filtering#"after.ft.filtering"} events.
		 * @instance
		 * @param {boolean} [focus=false] - Whether or not to set the focus to the input once filtering is complete.
		 * @returns {jQuery.Promise}
		 * @fires FooTable.Filtering#"before.ft.filtering"
		 * @fires FooTable.Filtering#"after.ft.filtering"
		 */
		filter: function(focus){
			var self = this;
			self.filters = self.ensure(self.filters);
			/**
			 * The before.ft.filtering event is raised before a filter is applied and allows listeners to modify the filter or cancel it completely by calling preventDefault on the jQuery.Event object.
			 * @event FooTable.Filtering#"before.ft.filtering"
			 * @param {jQuery.Event} e - The jQuery.Event object for the event.
			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
			 * @param {Array.<FooTable.Filter>} filters - The filters that are about to be applied.
			 */
			return self.ft.raise('before.ft.filtering', [self.filters]).then(function(){
				self.filters = self.ensure(self.filters);
				if (focus){
					var start = self.$input.prop('selectionStart'),
						end = self.$input.prop('selectionEnd');
				}
				return self.ft.draw().then(function(){
					if (focus){
						self.$input.focus().prop({
							selectionStart: start,
							selectionEnd: end
						});
					}
					/**
					 * The after.ft.filtering event is raised after a filter has been applied.
					 * @event FooTable.Filtering#"after.ft.filtering"
					 * @param {jQuery.Event} e - The jQuery.Event object for the event.
					 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
					 * @param {FooTable.Filter} filter - The filters that were applied.
					 */
					self.ft.raise('after.ft.filtering', [self.filters]);
				});
			});
		},
		/**
		 * Removes the current search filter.
		 * @instance
		 * @returns {jQuery.Promise}
		 * @fires FooTable.Filtering#"before.ft.filtering"
		 * @fires FooTable.Filtering#"after.ft.filtering"
		 */
		clear: function(){
			this.filters = F.arr.get(this.filters, function(f){ return f.hidden; });
			return this.filter(this.focus);
		},
		/**
		 * Toggles the button icon between the search and clear icons based on the supplied value.
		 * @instance
		 * @param {boolean} search - Whether or not to display the search icon.
		 */
		setButton: function(search){
			if (!search){
				this.$button.children('.fooicon').removeClass('fooicon-search').addClass('fooicon-remove');
			} else {
				this.$button.children('.fooicon').removeClass('fooicon-remove').addClass('fooicon-search');
			}
		},
		/**
		 * Finds a filter by name.
		 * @param {string} name - The name of the filter to find.
		 * @returns {(FooTable.Filter|null)}
		 */
		find: function(name){
			return F.arr.first(this.filters, function(f){ return f.name == name; });
		},
		/**
		 * Gets an array of {@link FooTable.Column} to apply the search filter to. This also doubles as the default columns for filters which do not specify any columns.
		 * @instance
		 * @returns {Array.<FooTable.Column>}
		 */
		columns: function(){
			if (F.is.jq(this.$dropdown)){
				// if we have a dropdown containing the column names get the selected columns from there
				return this.$dropdown.find('input:checked').map(function(){
					return $(this).data('__FooTableColumn__');
				}).get();
			} else {
				// otherwise find all columns that are set to be filterable.
				return this.ft.columns.get(function(c){ return c.filterable; });
			}
		},
		/**
		 * Takes an array of plain objects containing the filter values or actual {@link FooTable.Filter} objects and ensures that an array of only {@link FooTable.Filter} is returned.
		 * If supplied a plain object that object must contain a name, query and columns properties which are used to create a new {@link FooTable.Filter}.
		 * @instance
		 * @param {({name: string, query: (string|FooTable.Query), columns: (Array.<string>|Array.<number>|Array.<FooTable.Column>)}|Array.<FooTable.Filter>)} filters - The array of filters to check.
		 * @returns {Array.<FooTable.Filter>}
		 */
		ensure: function(filters){
			var self = this, parsed = [], filterable = self.columns();
			if (!F.is.emptyArray(filters)){
				F.arr.each(filters, function(f){
					f = self._ensure(f, filterable);
					if (f instanceof F.Filter) parsed.push(f);
				});
			}
			return parsed;
		},

		/**
		 * Creates a new filter using the supplied object or individual parameters to populate it.
		 * @instance
		 * @param {(string|FooTable.Filter|object)} nameOrObject - The name for the filter or the actual filter object itself.
		 * @param {(string|FooTable.Query)} [query] - The query for the filter. This is only optional when the first parameter is a filter object.
		 * @param {(Array.<number>|Array.<string>|Array.<FooTable.Column>)} [columns] - The columns to apply the filter to.
		 * 	If not supplied the filter will be applied to all selected columns in the search input dropdown.
		 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching.
		 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces.
		 * @param {string} [space="AND"] - How the query treats space chars.
		 * @param {boolean} [hidden=true] - Whether or not this is a hidden filter.
		 * @returns {*}
		 */
		createFilter: function(nameOrObject, query, columns, ignoreCase, connectors, space, hidden){
			if (F.is.string(nameOrObject)){
				nameOrObject = {name: nameOrObject, query: query, columns: columns, ignoreCase: ignoreCase, connectors: connectors, space: space, hidden: hidden};
			}
			return this._ensure(nameOrObject, this.columns());
		},

		/* PRIVATE */
		_ensure: function(filter, selectedColumns){
			if ((F.is.hash(filter) || filter instanceof F.Filter) && !F.is.emptyString(filter.name) && (!F.is.emptyString(filter.query) || filter.query instanceof F.Query)){
				filter.columns = F.is.emptyArray(filter.columns) ? selectedColumns : this.ft.columns.ensure(filter.columns);
				filter.ignoreCase = F.is.boolean(filter.ignoreCase) ? filter.ignoreCase : this.ignoreCase;
				filter.connectors = F.is.boolean(filter.connectors) ? filter.connectors : this.connectors;
				filter.hidden = F.is.boolean(filter.hidden) ? filter.hidden : false;
				filter.space = F.is.string(filter.space) && (filter.space === 'AND' || filter.space === 'OR') ? filter.space : this.space;
				filter.query = F.is.string(filter.query) ? new F.Query(filter.query, filter.space, filter.connectors, filter.ignoreCase) : filter.query;
				return (filter instanceof F.Filter)
					? filter
					: new F.Filter(filter.name, filter.query, filter.columns, filter.space, filter.connectors, filter.ignoreCase, filter.hidden);
			}
			return null;
		},
		/**
		 * Handles the change event for the {@link FooTable.Filtering#$input}.
		 * @instance
		 * @private
		 * @param {jQuery.Event} e - The event object for the event.
		 */
		_onSearchInputChanged: function (e) {
			var self = e.data.self;
			var alpha = e.type == 'keypress' && !F.is.emptyString(String.fromCharCode(e.charCode)),
				ctrl = e.type == 'keyup' && (e.which == 8 || e.which == 46),
				paste = e.type == 'paste'; // backspace & delete

			// if alphanumeric characters or specific control characters
			if(alpha || ctrl || paste) {
				if (e.which == 13) e.preventDefault();
				if (self._filterTimeout != null) clearTimeout(self._filterTimeout);
				self._filterTimeout = setTimeout(function(){
					self._filterTimeout = null;
					var query = self.$input.val();
					if (query.length >= self.min){
						if (self.exactMatch && !self._exactRegExp.test(query)){
							query = '"' + query + '"';
						}
						self.addFilter('search', query);
						self.filter(self.focus);
					} else if (F.is.emptyString(query)){
						self.clear();
					}
				}, self.delay);
			}
		},
		/**
		 * Handles the click event for the {@link FooTable.Filtering#$button}.
		 * @instance
		 * @private
		 * @param {jQuery.Event} e - The event object for the event.
		 */
		_onSearchButtonClicked: function (e) {
			e.preventDefault();
			var self = e.data.self;
			if (self._filterTimeout != null) clearTimeout(self._filterTimeout);
			var $icon = self.$button.children('.fooicon');
			if ($icon.hasClass('fooicon-remove')) self.clear();
			else {
				var query = self.$input.val();
				if (query.length >= self.min){
					if (self.exactMatch && !self._exactRegExp.test(query)){
						query = '"' + query + '"';
					}
					self.addFilter('search', query);
					self.filter(self.focus);
				}
			}
		},
		/**
		 * Handles the click event for the column checkboxes in the {@link FooTable.Filtering#$dropdown}.
		 * @instance
		 * @private
		 * @param {jQuery.Event} e - The event object for the event.
		 */
		_onSearchColumnClicked: function (e) {
			var self = e.data.self;
			if (self._filterTimeout != null) clearTimeout(self._filterTimeout);
			self._filterTimeout = setTimeout(function(){
				self._filterTimeout = null;
				var $icon = self.$button.children('.fooicon');
				if ($icon.hasClass('fooicon-remove')){
					$icon.removeClass('fooicon-remove').addClass('fooicon-search');
					self.addFilter('search', self.$input.val());
					self.filter();
				}
			}, self.delay);
		},
		/**
		 * Handles the click event for the {@link FooTable.Filtering#$dropdown} toggle.
		 * @instance
		 * @private
		 * @param {jQuery.Event} e - The event object for the event.
		 */
		_onDropdownToggleClicked: function (e) {
			e.preventDefault();
			e.stopPropagation();
			var self = e.data.self;
			self.$dropdown.parent().toggleClass('open');
			if (self.$dropdown.parent().hasClass('open')) $(document).on('click.footable', { self: self }, self._onDocumentClicked);
			else $(document).off('click.footable', self._onDocumentClicked);
		},
		/**
		 * Checks all click events when the dropdown is visible and closes the menu if the target is not the dropdown.
		 * @instance
		 * @private
		 * @param {jQuery.Event} e - The event object for the event.
		 */
		_onDocumentClicked: function(e){
			if ($(e.target).closest('.dropdown-menu').length == 0){
				e.preventDefault();
				var self = e.data.self;
				self.$dropdown.parent().removeClass('open');
				$(document).off('click.footable', self._onDocumentClicked);
			}
		}
	});

	F.components.register('filtering', F.Filtering, 500);

})(jQuery, FooTable);

(function(F){
	F.Query = F.Class.extend(/** @lends FooTable.Query */{
		/**
		 * The query object is used to parse and test the filtering component's queries
		 * @constructs
		 * @extends FooTable.Class
		 * @param {string} query - The string value of the query.
		 * @param {string} [space="AND"] - How the query treats whitespace.
		 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces.
		 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching.
		 * @returns {FooTable.Query}
		 */
		construct: function(query, space, connectors, ignoreCase){
			/* PRIVATE */
			/**
			 * Holds the previous value of the query and is used internally in the {@link FooTable.Query#val} method.
			 * @type {string}
			 * @private
			 */
			this._original = null;
			/**
			 * Holds the value for the query. Access to this variable is provided through the {@link FooTable.Query#val} method.
			 * @type {string}
			 * @private
			 */
			this._value = null;
			/* PUBLIC */
			/**
			 * A string specifying how the query treats whitespace. Can be either "OR" or "AND".
			 * @type {string}
			 */
			this.space = F.is.string(space) && (space == 'OR' || space == 'AND') ? space : 'AND';
			/**
			 * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query.
			 * @instance
			 * @type {boolean}
			 */
			this.connectors = F.is.boolean(connectors) ? connectors : true;
			/**
			 * Whether or not ignore case when matching.
			 * @instance
			 * @type {boolean}
			 */
			this.ignoreCase = F.is.boolean(ignoreCase) ? ignoreCase : true;
			/**
			 * The left side of the query if one exists. OR takes precedence over AND.
			 * @type {FooTable.Query}
			 * @example <caption>The below shows what is meant by the "left" side of a query</caption>
			 * query = "Dave AND Mary" - "Dave" is the left side of the query.
			 * query = "Dave AND Mary OR John" - "Dave and Mary" is the left side of the query.
			 */
			this.left = null;
			/**
			 * The right side of the query if one exists. OR takes precedence over AND.
			 * @type {FooTable.Query}
			 * @example <caption>The below shows what is meant by the "right" side of a query</caption>
			 * query = "Dave AND Mary" - "Mary" is the right side of the query.
			 * query = "Dave AND Mary OR John" - "John" is the right side of the query.
			 */
			this.right = null;
			/**
			 * The parsed parts of the query. This contains the information used to actually perform a match against a string.
			 * @type {Array}
			 */
			this.parts = [];
			/**
			 * The type of operand to apply to the results of the individual parts of the query.
			 * @type {string}
			 */
			this.operator = null;
			this.val(query);
		},
		/**
		 * Gets or sets the value for the query. During set the value is parsed setting all properties as required.
		 * @param {string} [value] - If supplied the value to set for this query.
		 * @returns {(string|undefined)}
		 */
		val: function(value){
			// get
			if (F.is.emptyString(value)) return this._value;

			// set
			if (F.is.emptyString(this._original)) this._original = value;
			else if (this._original == value) return;

			this._value = value;
			this._parse();
		},
		/**
		 * Tests the supplied string against the query.
		 * @param {string} str - The string to test.
		 * @returns {boolean}
		 */
		match: function(str){
			if (F.is.emptyString(this.operator) || this.operator === 'OR')
				return this._left(str, false) || this._match(str, false) || this._right(str, false);
			if (this.operator === 'AND')
				return this._left(str, true) && this._match(str, true) && this._right(str, true);
		},
		/**
		 * Matches this queries parts array against the supplied string.
		 * @param {string} str - The string to test.
		 * @param {boolean} def - The default value to return based on the operand.
		 * @returns {boolean}
		 * @private
		 */
		_match: function(str, def){
			var self = this, result = false, empty = F.is.emptyString(str);
			if (F.is.emptyArray(self.parts) && self.left instanceof F.Query) return def;
			if (F.is.emptyArray(self.parts)) return result;
			if (self.space === 'OR'){
				// with OR we give the str every part to test and if any match it is a success, we do exit early if a negated match occurs
				F.arr.each(self.parts, function(p){
					if (p.empty && empty){
						result = true;
						if (p.negate){
							result = false;
							return result;
						}
					} else {
						var match = (p.exact ? F.str.containsExact : F.str.contains)(str, p.query, self.ignoreCase);
						if (match && !p.negate) result = true;
						if (match && p.negate) {
							result = false;
							return result;
						}
					}
				});
			} else {
				// otherwise with AND we check until the first failure and then exit
				result = true;
				F.arr.each(self.parts, function(p){
					if (p.empty){
						if ((!empty && !p.negate) || (empty && p.negate)) result = false;
						return result;
					} else {
						var match = (p.exact ? F.str.containsExact : F.str.contains)(str, p.query, self.ignoreCase);
						if ((!match && !p.negate) || (match && p.negate)) result = false;
						return result;
					}
				});
			}
			return result;
		},
		/**
		 * Matches the left side of the query if one exists with the supplied string.
		 * @param {string} str - The string to test.
		 * @param {boolean} def - The default value to return based on the operand.
		 * @returns {boolean}
		 * @private
		 */
		_left: function(str, def){
			return (this.left instanceof F.Query) ? this.left.match(str) : def;
		},
		/**
		 * Matches the right side of the query if one exists with the supplied string.
		 * @param {string} str - The string to test.
		 * @param {boolean} def - The default value to return based on the operand.
		 * @returns {boolean}
		 * @private
		 */
		_right: function(str, def){
			return (this.right instanceof F.Query) ? this.right.match(str) : def;
		},
		/**
		 * Parses the private {@link FooTable.Query#_value} property and populates the object.
		 * @private
		 */
		_parse: function(){
			if (F.is.emptyString(this._value)) return;
			// OR takes precedence so test for it first
			if (/\sOR\s/.test(this._value)){
				// we have an OR so split the value on the first occurrence of OR to get the left and right sides of the statement
				this.operator = 'OR';
				var or = this._value.split(/(?:\sOR\s)(.*)?/);
				this.left = new F.Query(or[0], this.space, this.connectors, this.ignoreCase);
				this.right = new F.Query(or[1], this.space, this.connectors, this.ignoreCase);
			} else if (/\sAND\s/.test(this._value)) {
				// there are no more OR's so start with AND
				this.operator = 'AND';
				var and = this._value.split(/(?:\sAND\s)(.*)?/);
				this.left = new F.Query(and[0], this.space, this.connectors, this.ignoreCase);
				this.right = new F.Query(and[1], this.space, this.connectors, this.ignoreCase);
			} else {
				// we have no more statements to parse so set the parts array by parsing each part of the remaining query
				var self = this;
				this.parts = F.arr.map(this._value.match(/(?:[^\s"]+|"[^"]*")+/g), function(str){
					return self._part(str);
				});
			}
		},
		/**
		 * Parses a single part of a query into an object to use during matching.
		 * @param {string} str - The string representation of the part.
		 * @returns {{query: string, negate: boolean, phrase: boolean, exact: boolean}}
		 * @private
		 */
		_part: function(str){
			var p = {
				query: str,
				negate: false,
				phrase: false,
				exact: false,
				empty: false
			};
			// support for NEGATE operand - (minus sign). Remove this first so we can get onto phrase checking
			if (F.str.startsWith(p.query, '-')){
				p.query = F.str.from(p.query, '-');
				p.negate = true;
			}
			// support for PHRASES (exact matches)
			if (/^"(.*?)"$/.test(p.query)){ // if surrounded in quotes strip them and nothing else
				p.query = p.query.replace(/^"(.*?)"$/, '$1');
				p.phrase = true;
				p.exact = true;
			} else if (this.connectors && /(?:\w)+?([-_\+\.])(?:\w)+?/.test(p.query)) { // otherwise replace supported phrase connectors (-_+.) with spaces
				p.query = p.query.replace(/(?:\w)+?([-_\+\.])(?:\w)+?/g, function(match, p1){
					return match.replace(p1, ' ');
				});
				p.phrase = true;
			}
			p.empty = p.phrase && F.is.emptyString(p.query);
			return p;
		}
	});

})(FooTable);
(function(F){

	/**
	 * The value used by the filtering component during filter operations. Must be a string and can be set using the data-filter-value attribute on the cell itself.
	 * If this is not supplied it is set to the result of the toString method called on the value for the cell. Added by the {@link FooTable.Filtering} component.
	 * @type {string}
	 * @default null
	 */
	F.Cell.prototype.filterValue = null;

	// this is used to define the filtering specific properties on cell creation
	F.Cell.prototype.__filtering_define__ = function(valueOrElement){
		this.filterValue = this.column.filterValue.call(this.column, valueOrElement);
	};

	// this is used to update the filterValue property whenever the cell value is changed
	F.Cell.prototype.__filtering_val__ = function(value){
		if (F.is.defined(value)){
			// set only
			this.filterValue = this.column.filterValue.call(this.column, value);
		}
	};

	// overrides the public define method and replaces it with our own
	F.Cell.extend('define', function(valueOrElement){
		this._super(valueOrElement);
		this.__filtering_define__(valueOrElement);
	});
	// overrides the public val method and replaces it with our own
	F.Cell.extend('val', function(value, redraw, redrawSelf){
		var val = this._super(value, redraw, redrawSelf);
		this.__filtering_val__(value);
		return val;
	});
})(FooTable);
(function($, F){
	/**
	 * Whether or not the column can be used during filtering. Added by the {@link FooTable.Filtering} component.
	 * @type {boolean}
	 * @default true
	 */
	F.Column.prototype.filterable = true;

	/**
	 * This is supplied either the cell value or jQuery object to parse. A string value must be returned from this method and will be used during filtering operations.
	 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object.
	 * @returns {string}
	 * @this FooTable.Column
	 */
	F.Column.prototype.filterValue = function(valueOrElement){
		// if we have an element or a jQuery object use jQuery to get the value
		if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){
			var data = $(valueOrElement).data('filterValue');
			return F.is.defined(data) ? ''+data : $(valueOrElement).text();
		}
		// if options are supplied with the value
		if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){
			if (F.is.string(valueOrElement.options.filterValue)) return valueOrElement.options.filterValue;
			if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value;
		}
		if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; // use the native toString of the value
		return ''; // otherwise we have no value so return an empty string
	};

	// this is used to define the filtering specific properties on column creation
	F.Column.prototype.__filtering_define__ = function(definition){
		this.filterable = F.is.boolean(definition.filterable) ? definition.filterable : this.filterable;
		this.filterValue = F.checkFnValue(this, definition.filterValue, this.filterValue);
	};

	// overrides the public define method and replaces it with our own
	F.Column.extend('define', function(definition){
		this._super(definition); // call the base so we don't have to redefine any previously set properties
		this.__filtering_define__(definition); // then call our own
	});
})(jQuery, FooTable);
(function(F){
	/**
	 * An object containing the filtering options for the plugin. Added by the {@link FooTable.Filtering} component.
	 * @type {object}
	 * @prop {boolean} enabled=false - Whether or not to allow filtering on the table.
	 * @prop {({name: string, query: (string|FooTable.Query), columns: (Array.<string>|Array.<number>|Array.<FooTable.Column>)}|Array.<FooTable.Filter>)} filters - The filters to apply to the current {@link FooTable.Rows#array}.
	 * @prop {number} delay=1200 - The delay in milliseconds before the query is auto applied after a change (any value equal to or less than zero will disable this).
	 * @prop {number} min=1 - The minimum number of characters allowed in the search input before it is auto applied.
	 * @prop {string} space="AND" - Specifies how whitespace in a filter query is handled.
	 * @prop {string} placeholder="Search" - The string used as the placeholder for the search input.
	 * @prop {string} dropdownTitle=null - The title to display at the top of the search input column select.
	 * @prop {string} position="right" - The string used to specify the alignment of the search input.
	 * @prop {string} connectors=true - Whether or not to replace phrase connectors (+.-_) with space before executing the query.
	 * @prop {boolean} ignoreCase=true - Whether or not ignore case when matching.
	 * @prop {boolean} exactMatch=false - Whether or not search queries are treated as phrases when matching.
	 * @prop {boolean} focus=true - Whether or not to focus the search input after the search/clear button is clicked or after auto applying the search input query.
	 * @prop {string} container=null - A selector specifying where to place the filtering components form, if null the form is displayed within a row in the head of the table.
	 */
	F.Defaults.prototype.filtering = {
		enabled: false,
		filters: [],
		delay: 1200,
		min: 1,
		space: 'AND',
		placeholder: 'Search',
		dropdownTitle: null,
		position: 'right',
		connectors: true,
		ignoreCase: true,
		exactMatch: false,
		focus: true,
		container: null
	};
})(FooTable);
(function(F){
	/**
	 * Checks if the row is filtered using the supplied filters.
	 * @this FooTable.Row
	 * @param {Array.<FooTable.Filter>} filters - The filters to apply.
	 * @returns {boolean}
	 */
	F.Row.prototype.filtered = function(filters){
		var result = true, self = this;
		F.arr.each(filters, function(f){
			if ((result = f.matchRow(self)) == false) return false;
		});
		return result;
	};
})(FooTable);