View file upload/js/xenforo/full/filter_list.js

File size: 11.93Kb
/** @param {jQuery} $ jQuery Object */
!function($, window, document, _undefined)
{
	XenForo.FilterList = function($list) { this.__construct($list); };
	XenForo.FilterList.prototype =
	{
		__construct: function($list)
		{
			this.$list = $list;
			this.$form = this.$list.closest('form').bind('AutoValidationComplete', $.context(function(e)
			{
				this.$form.find('input.Toggler').each(function()
				{
					// TODO: un-toggle if there was an error
					$(this).closest('h4').toggleClass('disabled', !this.checked);
				})
			}, this));
			this.$listCounter = this.$form.find('.FilterListCount');

			this.lookUpUrl = XenForo.isPositive(this.$list.data('ajaxfilter')) ?
					this.$form.attr('action') : false;

			this.registerListItems();
			this.handleLast();

			if (this.activateFilterControls())
			{
				this.filter();
			}


		},

		/**
		 * Finds and activates the filter controls for the list
		 */
		activateFilterControls: function()
		{
			if (this.$form.length)
			{
				this.$filter = $('input[name="filter"]', this.$form)
					.keyup($.context(this, 'filterKeyUp'))
					.on('paste', $.context(this, 'filterPaste'))
					.bind('search', $.context(this, 'instantSearch'))
					.keypress($.context(this, 'filterKeyPress'));

				this.$prefixMatch = $('input[name="prefixmatch"]', this.$form)
					.change($.context(this, 'filter'));

				this.$clearFilter = $('input[name="clearfilter"]', this.$form)
					.click($.context(this, 'clearFilter'));

				console.info('XenForo.FilterList %o', this.$filter);

				if (this.$filter.length)
				{
					return this.getCookie();
				}
			}

			return false;
		},

		/**
		 * Create XenForo.FilterListItem objects for each list item
		 *
		 * @return array this.listItems
		 */
		registerListItems: function()
		{
			this.FilterListItems = [];

			this.$listItems = this.$list.find('.listItem');

			this.$listItems.each($.context(function(i)
			{
				this.FilterListItems.push(new XenForo.FilterListItem($(this.$listItems[i])));
			}, this));

			this.$groups = this.$list.find('> li:not(.listItem)');
		},

		/**
		 * Read the query string for a 'last' parameter, and if it's found, scroll the item into view, if possible.
		 */
		handleLast: function()
		{
			if (window.location.hash)
			{
				var $last = $(window.location.hash.replace(/[^\w_#-.]/g, '').replace('.', '\\.'));
				if ($last.hasClass('listItem'))
				{
					console.log('Last: %o', $last);
					$last.addClass('Last');
				}
			}
		},

		/**
		 * A little speed-up for live typing
		 *
		 * @param event e
		 *
		 * @return
		 */
		filterKeyUp: function(e)
		{
			if (e.keyCode == 13)
			{
				// enter key - instant search
				this.instantSearch();
				return;
			}

			clearTimeout(this.timeOut);
			this.timeOut = setTimeout($.context(this, 'filter'), 250);
		},

		filterPaste: function(e)
		{
			clearTimeout(this.timeOut);
			this.timeOut = setTimeout($.context(this, 'filter'), 250);
		},

		/**
		 * Filters key press events to make enter search instantly
		 *
		 * @param event e
		 */
		filterKeyPress: function(e)
		{
			if (e.keyCode == 13)
			{
				// enter - disable form submitting
				e.preventDefault();
			}
		},

		/**
		 * Instantly begins a search.
		 */
		instantSearch: function()
		{
			clearTimeout(this.timeOut);
			this.filter();
		},

		/**
		 * Filters the list of templates according to the filter and prefixmatch controls
		 *
		 * @param event e
		 */
		filter: function(e)
		{
			var val = this.$filter.data('XenForo.Prompt').val(),
				prefixMatch = this.$prefixMatch.is(':checked'),
				filterEvent = $.Event('XFFilter', {
					list: this.$list,
					filter: val,
					prefix: this.$prefixMatch.is(':checked')
				});

			this.$list.trigger(filterEvent);

			if (this.$filter.hasClass('prompt') || val === '')
			{
				this.$groups.show();
				this.$listItems.show();
				this.applyFilter(this.FilterListItems);
				this.$listCounter.text(this.$listItems.length);
				if (this.lookUpUrl)
				{
					$('.PageNav').show();
				}

				this.removeAjaxResults();
				this.showHideNoResults(false);
				this.deleteCookie();
				return;
			}

			console.log('Filtering on \'%s\'', val);

			this.setCookie();

			if (this.lookUpUrl)
			{
				XenForo.ajax(this.lookUpUrl,
					{ _filter: { value: val, prefix: prefixMatch ? 1 : 0 } },
					$.context(this, 'filterAjax'),
					{ type: 'GET' }
				);
				return;
			}

			var $groups,
				visible = this.applyFilter(this.FilterListItems);

			this.$listCounter.text(visible);

			// hide empty groups
			this.$groups.each(function(i, group)
			{
				var $group = $(group);

				$group.show();

				if ($group.find('li.listItem:visible').length == 0)
				{
					$group.hide();
				}
			});

			this.removeAjaxResults();
			this.showHideNoResults(visible ? false : true);
		},

		removeAjaxResults: function()
		{
			if (this.$ajaxResults)
			{
				this.$ajaxResults.remove();
				delete(this.$ajaxResults);
			}
		},

		applyFilter: function(items)
		{
			var i,
				visible = 0,
				filterRegex = new RegExp(
					(this.$prefixMatch.is(':checked') ? '^' : '')
					+ '(' + XenForo.regexQuote(this.$filter.data('XenForo.Prompt').val()) + ')', 'i');

			for (i = items.length - 1; i >= 0; i--) // much faster than .each(...)
			{
				visible += items[i].filter(filterRegex);
			}

			return visible;
		},

		showHideNoResults: function(show)
		{
			var $noRes = $('#noResults');

			if (show)
			{
				if (!$noRes.length)
				{
					$noRes = $('<li id="noResults" class="listNote" style="display:none" />')
						.text(XenForo.phrases.no_items_matched_your_filter || 'No items matched your filter.');

					this.$list.append($noRes);
				}

				$noRes.xfFadeIn(XenForo.speed.normal);
			}
			else
			{
				$noRes.xfHide();
			}
		},

		filterAjax: function(ajaxData)
		{
			if (XenForo.hasResponseError(ajaxData))
			{
				return;
			}

			var $children = $('<ul />').html($.trim(ajaxData.templateHtml)).children();

			this.$groups.hide();
			this.$listItems.hide();
			if (this.lookUpUrl)
			{
				$('.PageNav').hide();
			}

			this.removeAjaxResults();

			if (!$children.length)
			{
				this.$listCounter.text(0);
				this.showHideNoResults(true);
			}
			else
			{
				this.$ajaxResults = $children;

				this.showHideNoResults(false);
				this.$list.append($children);
				$children.xfActivate();

				var $items = $children.filter('.listItem').add($children.find('.listItem')), items = [];
				$items.each(function(i, el) {
					items[i] = new XenForo.FilterListItem($(el));
				});
				this.applyFilter(items);
				this.$listCounter.text($items.length);
			}

			this.handleLast();
		},

		/**
		 * Gets the name of the filter controls cookie, in the form FilterList_{form.action}
		 *
		 * @return string
		 */
		getCookieName: function()
		{
			var action = XenForo.canonicalizeUrl(this.$form.attr('action')),
				match;
			action = action.replace(XenForo.baseUrl(), '');

			if (match = action.match(/\?([^&=]+)(&|$)/))
			{
				action = match[1];
			}
			else
			{
				action = action.replace(/\?.*$/, '');
			}

			// TODO: use crc32 or something similar?
			return 'FilterList_' + encodeURIComponent(action).replace(/[\.\+\/]|(%([0-9a-f]{2}|u\d+))/gi, '');
		},

		/**
		 * Sets the filter controls cookie in the form {prefixmatch 1/0},{filter value}
		 *
		 * @return string
		 */
		setCookie: function()
		{
			var value = (this.$prefixMatch.is(':checked') ? 1 : 0) + ',' + this.$filter.data('XenForo.Prompt').val();

			if (this.$filter.data('XenForo.Prompt').isEmpty())
			{
				this.deleteCookie();
			}
			else
			{
				$.setCookie(this.getCookieName(), value);
			}

			return value;
		},

		/**
		 * Gets the value of the filter controls cookie, and applies its values to the filter controls if possible
		 *
		 * @return string Raw cookie value
		 */
		getCookie: function()
		{
			var cookie = $.getCookie(this.getCookieName());

			if (cookie)
			{
				this.$prefixMatch.attr('checked', cookie.substring(0, 1) == '1' ? true : false);

				this.$filter.data('XenForo.Prompt').val(cookie.substring(2));
			}

			return cookie;
		},

		deleteCookie: function()
		{
			$.deleteCookie(this.getCookieName(), '');
		},

		/**
		 * Clears the filter input
		 *
		 * @param jQuery Event e
		 */
		clearFilter: function(e)
		{
			this.$filter.focus();

			this.$filter.data('XenForo.Prompt').val('', true);

			this.filter(e);

			return true;
		}
	};

	// *********************************************************************

	/**
	 * Controls a single FilterListItem, registering its textual content and handling the filtering
	 * Intended to be called by XenForo.FilterList
	 *
	 * @param jQuery .FilterList > .listItem
	 */
	XenForo.FilterListItem = function($item) { this.__construct($item); };
	XenForo.FilterListItem.prototype =
	{
		__construct: function($item)
		{
			this.$item = $item;
			this.$textContainer = this.$item.find('h4 em');
			this.text = this.$textContainer.text();

			this.$item.find('input.Toggler').click(function(e)
			{
				$(this).closest('h4').toggleClass('disabled', !this.checked);
			});
		},

		/**
		 * Show or hide the item based on whether its text matches the filterRegex
		 *
		 * @param regexp filterRegex
		 *
		 * @return integer 1 if matched, 0 if not
		 */
		filter: function(filterRegex)
		{
			if (this.text.match(filterRegex))
			{
				this.$textContainer.html(XenForo.htmlspecialchars(this.text).replace(filterRegex, '<strong>$1</strong>'));

				this.$item.css('display', 'block'); // much faster in Opera

				return 1;
			}
			else
			{
				this.$item.css('display', 'none'); // much faster in Opera

				return 0;
			}
		}
	};

	// *********************************************************************

	XenForo.RadioTablets = function($container)
	{
		$container.children('label').each(function()
		{
			var $control = $(this),
				$radio = $control.find('input:radio');

			$control.addClass('radioTablet');

			function setStatus(e)
			{
				if (e)
				{
					$radio.closest('form').submit();
				}

				$container.find('input:radio[name="' + $radio.attr('name') + '"]').each(function()
				{
					if ($(this).is(':checked'))
					{
						$(this).closest('.radioTablet').addClass('checked');
					}
					else
					{
						$(this).closest('.radioTablet').removeClass('checked');
					}
				});
			}

			$radio.hide().change(setStatus);

			setStatus();
		});
	};

	// *********************************************************************

	XenForo.Sortable = function($container)
	{
		var connectWith = $container.data('connect-with'),
			itemSelector = 'li.listItem',
			$filterList = $container.closest('.FilterList'),
			sortUrl = $filterList.data('sort-url');

		if (!sortUrl)
		{
			console.log('No FilterList or data-sort-url for sortable.');
			return;
		}

		$container.sortable(
		{
			items: itemSelector,
			connectWith: connectWith,
			forcePlaceholderSize: true

		}).bind('sortupdate', function(e)
		{
			var order = [];

			$filterList.find('[data-item-id]').each(function(i)
			{
				var $this = $(this),
					itemId = $this.data('item-id'),
					parentId = $this.parent().data('parent-id');

				if (parentId !== undefined)
				{
					order[i] = [itemId, parentId];
				}
				else
				{
					order[i] = itemId;
				}
			});
			
			// show / hide noResults items
			$filterList.find(connectWith).each(function(i, el)
			{				
				var $el = $(el);
				
				if ($el.find(itemSelector).length == 0)
				{
					$el.find('li.noResults').show();
				}
				else
				{
					$el.find('li.noResults').hide();
				}
			});

			// moving across groups can trigger this multiple times
			if ($filterList.data('sort-timer'))
			{
				clearTimeout($filterList.data('sort-timer'));
			}
			$filterList.data('sort-timer', setTimeout(function()
			{
				XenForo.ajax(
					sortUrl,
					{ order: order },
					function(e)
					{
						// ajax progress complete, execute display order update
						console.info('ajax request complete');
					}
				);
			}, 100));
		});
	};

	// *********************************************************************

	XenForo.register('.FilterList', 'XenForo.FilterList');

	XenForo.register('.RadioTablets', 'XenForo.RadioTablets');

	XenForo.register('.Sortable', 'XenForo.Sortable');

}
(jQuery, this, document);