﻿/*
	Simplified suggestion popup.

	OPTIONS:
	- url: 
	- source: 
	- search_highlight:
	- build_request:
		function(element)
	- request: extra values to post to the search service (only applies if url option is used)
	- select: called when an item is selected. 
		function(element, null, object { element: jquery suggest list item, item: data })
	- itemcreated: called on the creation of each suggest list item. 
		function(element, null, object { element: jquery suggest list item, item: data }) 
	- dropDownClass: class name applied to drop down list

	POSTED VALUES
	- max: the maximum number of results to sho0 (default: 100)
	- text: the text from the element to search
	- from: the index from where to start the search

	EXPECTED RESULTS
	string[] or { items : string[], total : int }
*/

(function ($) {

	$.widget("ui.suggestlist", {
		options: {
			url: null,
			source: null,
			search_highlight: true,
			build_request: false,
			request: {
				max: 100
			},
			select: null,
			itemcreated: null,
			dropDownClass: null
		},

		_keys: {
			up: 38,
			left: 37,
			down: 40,
			right: 39,
			enter: 13,
			esc: 27,
			tab: 9
		},

		_select_keys: [],

		_$dropdown: null,
		_$list: null,
		_$processing: null,

		_selected_class: "suggest-list-selected",
		_xhr: null,
		_timer: null,
		_search_text: null,
		_can_blur: true,
		_mouse: null,
		_searcher: null,

		_create: function () {
			var self = this;
			var $element = self.element;
			var options = self.options;

			$element.attr("autocomplete", "off");
			$element.addClass("suggest-list-txt");
			self._$dropdown = $("<div class='suggest-list-drop' style='position: absolute; display: none; z-index: 9999;'><ul></ul></div>");
			self._$list = self._$dropdown.find("ul");

			self._$processing = $("<div class='suggest-list-processing' style='position: absolute; display: none; z-index: 9999;'></div>");

			self._select_keys = [self._keys.left, self._keys.right, self._keys.tab];

			$element.blur(function (e) {
				if (!self._can_blur) {
					self._can_blur = true;
					return;
				}
				self.visible(false);
			});

			self._$dropdown.mousemove(function (e) {
				// For chrome - fires blur event when scroll bar is clicked
				self._can_blur = false;
				$element.focus();
			}).mouseleave(function (e) {
				$element.focus();
			});

			$element.keydown(function (e) {
				if ($.inArray(e.keyCode, self._select_keys) > -1) {
					self._select();
					return;
				}

				if (e.keyCode == self._keys.esc) {
					self.visible(false);
					return;
				}

				if (e.keyCode == self._keys.up || e.keyCode == self._keys.down) {
					var $nav = self._$list.children();
					var $selected = $nav.filter("li." + self._selected_class);
					var selected_index = $nav.index($selected);
					var new_index = selected_index;

					if (e.keyCode == self._keys.down) {
						if ($selected.hasClass("suggest-list-more")) {
							$selected.click();
							return;
						}
						new_index++;
					} else if (e.keyCode == self._keys.up) {
						new_index--;
					}

					if (new_index > $nav.length - 1) new_index = 0;
					if (new_index < 0) new_index = $nav.length - 1;

					self._can_blur = false;
					$selected.removeClass(self._selected_class);
					$($nav[new_index]).addClass(self._selected_class).focus();
					$element.focus();

					e.preventDefault();
				}
			});

			$element.keyup(function (e) {
				if (e.keyCode == self._keys.enter
						|| e.keyCode == self._keys.esc
						|| $.inArray(e.keyCode, self._select_keys) > -1
						|| e.keyCode == self._keys.up) {
					return;
				}

				if (e.keyCode == self._keys.down) {
					self.open();
					return;
				}

				window.clearTimeout(self._timer);
				self._timer = window.setTimeout(function () {
					//					if (!$.trim($element.val())) {
					//						self.visible(false);
					//						return;
					//					}
					self._suggest();
				}, 100);
			});

			$element.keypress(function (e) {
				if (e.keyCode == self._keys.enter) {
					if (self._select()) return false; // Stop enter key from bubbling on keypress				
				}
			});

			self._options_set();
		},

		_options_set: function () {
			var self = this;
			var options = self.options;

			if (options.source && options.source.constructor == Array) {
				self._searcher = new SearchHelper();
				self._searcher.index(options.source, function (i) { return i.label }, function (i) { return i.id });
			}

			if (options.dropDownClass) {
				self._$dropdown.addClass(options.dropDownClass);
			}
		},

		_suggest: function (from) {
			var self = this;
			var options = self.options;
			var $element = self.element;

			var text = $.trim($element.val());
			if (text == self._search_text) return;
			self._search_text = text;

			var pos = $element.offset();
			var height = $element.outerHeight();
			var width = $element.width();

			if (!self._$processing.parent().length) self._$processing.appendTo(document.body);

			self._$processing.css({
				display: "block",
				left: pos.left + width - 20,
				top: pos.top + height / 2 - 10
			});

			if (options.url) {
				var args = {
					text: self._search_text,
					from: from
				}
				args = $.extend(args, options.request);

				if (typeof options.build_request == "function") {
					args = $.extend(args, options.build_request.call($element));
				}

				if (self._xhr) self._xhr.abort();

				self._xhr = $.ajax({
					url: options.url,
					data: args,
					complete: function () { self._$processing.hide(); },
					success: function (data) {
						self._create_list(data, from);
						self._xhr = null;
					},
					type: "POST",
					dataType: "json"
				});
			} else {
				var results = self._searcher.search(text);
				var list = options.source;
				if (results.terms.length) {
					list = [];
					for (var i in results.items) list.push(results.items[i].item);
				}
				self._create_list(list);
			}
		},

		_select: function () {
			var self = this;
			if (!self._$dropdown.is(":visible")) return false;

			var $selected = self._$list.find("li." + self._selected_class);
			if ($selected.length) {
				$selected.click();
				return true;
			}

			return false;
		},

		_create_list: function (data, from) {
			var self = this;
			var options = self.options;
			var $element = self.element;

			self._$processing.hide();
			if (!from) self._$list.empty();
			self._$list.children(".suggest-list-more").remove();

			var items = null;
			var total = null;

			if (data.constructor == Array) {
				items = data;
			} else if (typeof data == "object" && data.items) {
				items = data.items;
				total = data.total;
			} else {
				throw "SuggestList recevied unknown data type: " + data;
			}

			var terms = self._search_text.split(/\W/);
			$.each(items, function (i, item) {
				var label = item ? item.label ? item.label : item : null;
				if (!label) return;

				var display = label;

				if (options.search_highlight) {
					display = label.replace(/([\w']{1,})\b/g, function () {
						var w = arguments[1];
						for (var i in terms) {
							var h = terms[i];
							if (w.toLowerCase().indexOf(h) == 0) {
								return "<span class='suggest-list-search-highlight'>" + w.substr(0, h.length) + "</span>" + w.substr(h.length);
							}
						}
						return w;
					});
				}

				var $li = $("<li tabIndex='-1' class='suggest-list-item' style='outline-width: 0'></li>");
				$li.html(display);

				$li.mouseover(function (e) {
					var temp = { x: e.pageX, y: e.pageY };
					if (self._mouse && self._mouse.x == temp.x && self._mouse.y == temp.y) return;

					self._$list.children("." + self._selected_class).removeClass(self._selected_class);
					$li.addClass(self._selected_class);
					self._mouse = temp;
				});

				$li.click(function () {
					$element.val(label);
					self._search_text = label;
					self.visible(false);
					if (options.select) options.select.call($element[0], {}, { element: $li, item: item });
				});

				self._$list.append($li);

				if (from && i == 0) {
					self._$list.children("." + self._selected_class).removeClass(self._selected_class);
					$li.addClass(self._selected_class);
				}

				if (options.itemcreated) {
					options.itemcreated.call($element[0], {}, { element: $li, item: item });
				}
			});

			if (total) {
				if (!from) from = 0;
				from += items.length;
				var left = total - from;

				if (left > 0) {
					var $li = $("<li tabIndex='-1' class='suggest-list-more' style='outline-width: 0'>next " + (options.request.max > left ? left : options.request.max) + " (" + from + "/" + total + ")</li>");

					$li.mouseover(function (e) {
						var temp = { x: e.pageX, y: e.pageY };
						if (self._mouse && self._mouse.x == temp.x && self._mouse.y == temp.y) return;

						self._$list.children("." + self._selected_class).removeClass(self._selected_class);
						$li.addClass(self._selected_class);
						self._mouse = temp;
					});

					$li.click(function (e) {
						if (self._xhr == null) {
							self._search_text = null;
							self._suggest(from);
						}
					});

					self._$list.append($li);
				}
			}

			if (items.length > 0 && $element.is(":visible")) {
				var pos = $element.offset();
				var height = $element.outerHeight();
				var width = $element.width();
				var isvisible = self.visible();

				if (!self._$dropdown.parent().length) self._$dropdown.appendTo(document.body);

				self._$dropdown.css({
					display: "block",
					left: pos.left,
					top: pos.top + height,
					width: width
				});

				if (!isvisible) self._$dropdown.scrollTop(0);
			} else {
				self.visible(false);
			}
		},

		_setOption: function (key, value) {
			$.Widget.prototype._setOption.apply(this, arguments);
			this._options_set();
		},

		visible: function () {
			var self = this;
			if (!arguments.length) {
				return self._$dropdown.is(":visible");
			}

			var show = arguments[0];
			if (!show) {
				if (self._xhr) self._xhr.abort();
				self._xhr = null;
				self._$processing.hide();
				self._$dropdown.hide();
			} else {
				self._$dropdown.show();
			}
		},

		destroy: function () {
			var self = this;
			var $element = self.element;

			$element.attr("autocomplete", "on");
			$element.removeClass("suggest-list-txt");
			self._$dropdown.remove();
			self._$list.remove();
			self._$processing.remove();

			$.Widget.prototype.destroy.call(this);
		},

		open: function () {
			var self = this;
			if (self._$dropdown.is(":visible")) return;
			// Forces a new search
			self._search_text = null;
			self._suggest();
		}
	});
})(jQuery);
