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

JQUERY TAGDRAGON
(v1.10, Mar 2009, by Ferdy Christant - ferdychristant.com)

jQuery TagDragon is a versatile jQuery 	plugin for autosuggest functionality of
input boxes and texareas. You can learn more about TagDragon at:

http://www.s3maphor3.org/tagdragon

LICENSE

Tagdragon is charityware. It is not free. You can make use of it after making
a required donation at :

http://www.s3maphor3.org/tagdragon/buy

100% of the revenue will be used for project JungleDragon, a charitable project!

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

(function($){
	$.fn.extend({
		tagdragon: function(options) {
			return this.each(function() { new $.tagdragonz(this,options); });
		},
		// the configure function allows users to override options during runtime, after initialization
		configure: function(options){ return this.trigger("configure", [options]); },
		// the load function allows users to trigger the loading and display of the suggestion list,
		// the suggestion list will only show when there are results for the filter and at least
		// options.minchar characters are entered
		load: function() { return this.trigger("load"); },
		// the clear function allows user to hide the suggestion list
		clear: function() { return this.trigger("clear"); }
	});
	$.tagdragonz = function(input,options) {

		// get handle to the tagbox
		var tagbox = input;

		// when users do not specify explicit options, these are the defaults:
		var defaults = {
			field: 'tags',								// id of input control (textbox or text area)
			url: 'jsontags.php',						// the remote url to get the suggestion list from
			tagsep: ',',								// multi-value delimiter of field
			enclose: '',								// character to enclose multi-word filters
			max:10,										// maximum number of results to show in the suggestion list
			cache: true,								// cache results from suggestion list or not
			delay: 500,									// pause after which the suggestion list is loaded
			charMin: 1,									// minimum number of chars for filter before a lookup is done
			dblClick: true,								// activate suggestion list on double click?
			postData: null,								// extra post data specified in object notation
			onRenderItem: function(row) { return decodeURIComponent(row.tag); },	// callback made before item is rendered into suggestion list
			onSelectItem: function(val) { window.open( val.link, '_self' ); },							// callback made once a value is selected but before it is inserted
			onLoadList: function(filter) { return true; }							// callback made before the suggestion list is loaded
		};

		// override the defaults with the explicit options passed by the user
		var options = $.extend(defaults, options);

		// get handle to the input field inside the tagbox
		var input = $('#' + options.field);
		// disable automcomplete for the field, as that will hover over our suggestion list
		$(input).attr("autocomplete", "off");

		// create the markup for the suggestion list and place it directly below the input field
		var lkup = document.createElement('div');
		$(lkup).attr({'id': 'tagbox-lkup'});
		$(lkup,tagbox).show();
		input.after(lkup);
		var lkuplst = document.createElement('ol');
		$(lkup,tagbox).append(lkuplst);

		// global vars, mostly used for control/state behavior

		var cursor = -1;				// keyboard arrow cursor in suggestion list (0= first position, -1 = no position)
		var length = 0;					// length of last suggestion list
		var loading = false;			// loading indicator of suggestion list
		var loaded = false;				// loaded indicator of suggestion list
		var cacheLst = null;			// in-memory suggestion list, used for containing the rich objects inside
		var inserted = false;			// state variable to prevent double suggestion list after inserting a value

		var hideLkup = function() {
			// clear the suggestion list and hide it
			$(lkuplst,tagbox).empty();
			$(lkup,tagbox).hide();
			loaded = false;
			cacheLst = null;
			inserted = false;
		};

		var insertTag = function(filter,tag) {
			// replaces the filter word the user entered with the selected value in the input control

			// current value of input
			var cur = input.val();
			// count the number of words in the filter
			var words = tag.split(' ').length;
			// determine if we need to enclose the filter, based on options and word count
			var enclose = (words > 1)? options.enclose.length > 0 ? options.enclose : '' : '';
			// calculate the replacement value, case-insensitive
			cur = cur.replace(eval('/' + filter + '$/i'),enclose + tag + enclose);
			// set the replacement value
			input.val(cur);
			// reinitialize cursor
			cursor = -1;
		}

		var addItem = function(val,filter,index){
			// adds one item to the suggestion list

			// cache the rich object that was passed
			var row = val;
			
			// make the callback to onRenderItem, passing along the value, current index, total values and filter
			// this gives users the opportunity to influence the rendered item in the list
			var val = options.onRenderItem(val, index, length, filter);

			// create a LI DOM element for the item to add and add it to the lookup list
			var li = document.createElement('li');
			lkuplst.appendChild(li);
			// create an A DOM element and place it inside the new LI
			var aLink = document.createElement('a');
			// set link target to nothing, we will overrule the onclick anyway
			$(aLink).attr({'href': '#'});
			$(aLink).attr({'rel': row.rel});
			// set the link text
			$(aLink,tagbox).text(val);
			// based on index, set class for alternate row styling
			$(aLink,tagbox).addClass(index % 2 == 0 ? 'td-odd' : 'td-even');
			// set the full HTML to insert by highlighting the filter text
			$(aLink,tagbox).html($(aLink,tagbox).text().replace(eval('/(' + filter + ')/gi'),"<em>$1</em>"));
			// add the created A element to the LI element
			
			if( row.rel != "summary" )
			{
				var img = document.createElement( "img" );
				$(img).attr({'src': 'images/icon/search_'+ row.rel +'.gif'});
				$(img).attr({'width': '22'});
				$(img).attr({'height': '22'});
				$(img).attr({'class': 'iLeft'});
				li.appendChild(img);
			}
			else
				li.className = "liSummary";
			
			var div = document.createElement( "div" );
			$(div).attr({'class': 'moreinfo'});
			$(div).html( row.moreinfo );

			aLink.appendChild( div );
			
			
			li.appendChild(aLink);
			//li.appendChild(div);

			// hookup the click event of the new A, this should insert the selected value
			$(aLink).click(function(e){
				// make callback to onSelectItem, passing the rich object to the callback,
				// so that users can get additional values besides the flat value that was selected
				options.onSelectItem(row);
				// insert the tag to the input
				insertTag(filter,val);
				// prevent default behavior of clicking the link
				e.preventDefault();
				// hide the suggestion list after adding the item
				hideLkup();
				// saved inserted state so that focus call will not trigger suggestion list loading again
				inserted = true;
				// since we clicked outside the input to select a value from the suggestion list, move the
				// focus back to the input field
				input.focus();
			});
		};

		var loadList = function() {
			// loads the suggestion list by doing a remote post to the backend script

			// clear insert state, since we are doing a fresh load
			inserted = false;
			// get the latest filter to search for from the input list
			var filter = parseFilter(input.val());
			// make the callback to the user, so that they can trigger other things before the suggestion list is loaded
			options.onLoadList(filter);
			// clear the existing list
			$(lkuplst,tagbox).empty();
			// do the remote post
			$.ajax({
				type: "POST",				// we do a POST, so that we do not have to mess with URL params, length limits and encoding
				url: options.url,
				data: $.extend({			// as data we will post the filter, the max results, and optionally the postdata set by the user in the options
					tag: encodeURIComponent(filter),
					max: options.max
				}, options.postData),
				dataType: "json",
				cache:options.cache,
				success: function(json) {
					// loading was successful, to be sure clear the existing list again
					$(lkuplst,tagbox).empty();
					// set the length of the suggestion list
					length=json.length;
					// cache the list so that we can reuse the rich objects later on
					cacheLst = json;
					// reinitialize keyboard arrow counter
					cursor=-1;
					// loop through results and add them to the suggestion list
					for (i=0;i<json.length && i<options.max;i++) {
						addItem(json[i],filter,i);
					}
					// show the results
					$(lkup,tagbox).show();
					// set loading to false, this is needed for the delay function
					loading = false;
					loaded = true;
				},
				error: function (XMLHttpRequest, textStatus, errorThrown) {
					// no result found or an error occured, reinitialize control/state vars
					length = 0;
					cacheLst = null;
					loading = false;
					loaded = false;
				}
			});
		};

		var parseFilter = function(val) {
			// get the filter entered by the user from the input box

			// if there is no tag seperator specified, the filter simply is the entire value of the input box
			if (options.tagsep.length==0) return val;

			// a tag seperator is specified in the options, check if is entered by the user
			if (val.indexOf(options.tagsep)>-1) {
				// there is a tag seperator entered, the filter is everything from the tag seperator to the end of the total value
				val = val.substring(val.lastIndexOf(options.tagsep)+1,val.length);
			}
			return val;
		}

		var triggerLoad = function() {
			// trigger the loading of the suggestion list

			// do not load the suggestion list when we just inserted a value
			if (inserted) return false;
			// get the latest filter entered by the user
			else {
				var filter = parseFilter(input.val());
				// see if the user entered enough chars to trigger the suggestion list
				if (filter.length >= options.charMin) {
					// load the suggestion list with the delay that was set in the options
					loading = true;
					setTimeout(function() {loadList()},options.delay);
				} else { hideLkup(); }
			}
		}

		$("*",input.form).focus(function(e){
			// when users focus on the field, trigger the load.
			// do not trigger the load when it concerns other fields on the form
			if (this.id == options.field) {triggerLoad();} else {hideLkup(); };
		})

		input.dblclick(function(e){
			// when double click suggest is enabled in the options, trigger the load
			if (options.dblClick && !loading) triggerLoad();
		})

		$(lkuplst,tagbox).blur(function(e) {
			// when users click outside our control, hide the suggestion list
			hideLkup();
		});

		var handleSpecials = function(e) {
			// handle arrow keys when they are pressed to navigate the suggestion list

			// capture key
			var e = e || window.event;
			var key = e.charCode || e.keyCode;

			// do not block any behavior of special keys when we are not navigating the suggestion list
			if (!loaded ) return true;
			switch (key) {
				case 40: {
					// DOWN key pressed
					// increase cursor counter if it is not at the end of the list already
					cursor = ((cursor+1) < length) ? cursor+1 : cursor;
					if (cursor < length) {
						// add highlight class to new position, remove highlight class from previous position
						$('li:eq(' + cursor + ')',tagbox).addClass('hl');
						if ((cursor-1)>-1) $('li:eq(' + (cursor-1) + ')',tagbox).removeClass('hl');
						// block default behavior (arrow to scroll)
						e.preventDefault();
					}
				} break;
				case 38: {
					// UP key pressed
					// decrease cursor counter if it is not at the beginning of the list already
					cursor = (cursor-1 >= 0) ? cursor-1 : cursor;
					if (cursor >= 0) {
						// add highlight class to new position, remove highlight class from previous position
						$('li:eq(' + cursor + ')',tagbox).addClass('hl');
						$('li:eq(' + (cursor+1) + ')',tagbox).removeClass('hl');
						// block default behavior (arrow to scroll)
						e.preventDefault();
					}
				} break;
				case 13: {
					// ENTER key pressed
					// block default behavior (form submit). Unfortunately, this does not work in Opera
					if (input[0].type != "textarea") e.preventDefault();
					if (cursor >= 0 && cursor < length) {
						// callback
						options.onSelectItem(cacheLst[cursor]);
						// if the cursor was on a valid position, add the selected tag to the input box
						insertTag(parseFilter(input.val()),$('li:eq(' + (cursor) + ')',tagbox).text());
						// hide the suggestion list after adding the tag to the input box
						e.preventDefault();
						hideLkup();
					}
				} break;
				case 27: {
					// ESC key pressed
					// hide the lookup list and prevent the default behavior
					hideLkup();
					e.preventDefault();
				} break;
			}
		};

		var handleKey = function(e) {
			// handle all non-special keys

			// capture key
			var e = e || window.event;
			var key = e.charCode || e.keyCode;

			// don't block the enter key, it should work as expected for textareas (new line)
			if (key == 13) return true;
			// return false for these special keys, these are handled in handleSpecials()
			if (key > 8 && key < 46 && key != 32) { return false; }

			// non-special key pressed, trigger the load
			if (loading == false) { triggerLoad(); }
			// show the suggestion list when the loading is completed
			$(lkup,tagbox).show();
		};

		// bind the keyup event for normal keys
		$(input).keyup(handleKey);
		// bind the keydown event for special keys
		$(input).keydown(handleSpecials);
		// bind the setopions function
		$(tagbox).bind("configure", function() { $.extend(options, arguments[1]); })
		// bind the load function
		$(tagbox).bind("load", function() { triggerLoad(); })
		// bind the clear function
		$(tagbox).bind("clear", function() { hideLkup(); })
	};
})(jQuery);