/*
 * DaiShu CMS 5 ComboBox
 *
 * Copyright (c) 2010 Scorpion Design, Inc.
 */
(function( $ ) {

$.widget( "cms.combobox", {
	options: {
		toggleValue: false,
		ajaxUrl: null,
		style: {
			main: 'ui-select-style',
			panel: 'ui-select-panel',
			option: 'ui-select-option',
			hover: 'ui-option-hover'
		},
		minchars: 3,
		valueField: 'PageID',
		textField: 'PageName',
		pathField: 'Path',
		place: {
			my: 'left top',
			at: 'left bottom',
			collision: 'none none'
		},
		maxHeight: 300
	},

	// Create the combo box.
	_create: function() {
		// Privatize the document events so they will have unique guids.
		this._onClick = function() {
			return this._handleClick.apply(this, arguments);
		};
		this._onKey = function() {
			return this._handleKey.apply(this, arguments);
		};

		// Initialize the child elements collection.
		this.elements = {
			// Add the panel for the options.
			panel: $('<div></div>')
				.attr('class',this.options.style.panel)
				.css({
					position:'absolute',
					display:'none'
				})
				.bind('mouseover mouseleave', $.proxy(this._hoverPanel, this))
				.captureScroll()
		};

		// Set up the combox box.
		if ( this.element.is('select') ) {
			this._type = 'select';
			this._initSelect();
		} else if ( this.element.is('input:text') ) {
			this._type = 'input';
			this._initAjax();
		} else {
			this._type = null;
		}
	},

	// Initialize a select element replacement.
	_initSelect: function() {
		var padding = $.toInt(this.element.css('paddingLeft'))
			+$.toInt(this.element.css('paddingRight'));
		var w = $.toInt( $.curCSS( this.element[0], 'width' ) ) - 21;
		var w2 = this.element.width()-3;

		// Hide the original select element.
		this.element
			.css({position:'absolute', opacity:0, display: 'block', height: 1, width: 1, overflow: 'hidden' })
			.bind('update', $.proxy(this._buildFromSelect, this))
			.bind('focus', $.proxy(this._activate, this))
			.bind('change', $.proxy(this._update, this))
			.bind('blur', $.proxy( function(e) { this._deactivate(); }, this) )
			.bind('keydown', $.proxy( function(e) {
				if ( e.which === $.ui.keyCode.TAB ) {
					// If you tab off the underlying select element, don't bubble the event up, as the blur
					// will take care of deactivation.
					e.stopPropagation();
				} else {
					// Prevent all other default keys that could fire the select change event.
					e.preventDefault();
				}
			}, this));

		// Put the combobox in its place.
		this.elements.combo = $('<div><span></span></div>')
			.insertAfter(this.element)
			.attr('class',this.options.style.main)
			.children('span')
				.attr('class',this.options.style.main)
			.end()
			.bind('click', $.proxy(this._onCombo, this));

		// If the select element has a named width, apply this to the combo replacement as well.
		if ( w > 10 ) {
			this.elements.combo.css({width: w+padding,display:'inline-block'});
		} else if ( $.browser.msie && $.browser.version < 8 ) {
			this.elements.combo.css({width: Math.max(w2+padding+14,50) });
		} else {
			this.elements.combo.css({minWidth: Math.max(w2+padding,50) });
		}

		// Migrate the title, if there is one.
		var title = this.element.attr('title');
		if ( title ) {
			this.elements.combo.attr('title', title);
			this.element.removeAttr('title');
		}

		// Get the "selected item" label.
		this.elements.label = this.elements.combo.children('span');

		// Build the list of child items.
		this._buildFromSelect();
	},

	// Initialize an ajax drop-down combo box.
	_initAjax: function() {
		// The flyout panel will be attached to the input element's parent.
		this.elements.combo = this.element.parent();

		// Bind the input box keydown.
		this.element
			.bind('focus.combobox blur.combobox', $.proxy(this._focusInput, this))
			.bind('keydown.combobox', $.proxy(this._inputKey, this));
	},

	// Build the list of items off of the select list.
	_buildFromSelect: function() {
		var id = this.options.valueField;
		var txt = this.options.textField;
		var results = [];
		this.element.children('option').each(function(i){
			var option = $(this);
			var name = option.text();
			var val = option.attr('value');
			if ( val === undefined ) {
				val = name;
			}
			var row = [];
			row[id] = val;
			row[txt] = name;
			results.push(row);
		});
		this._buildItems(results);
	},

	// Build (or rebuild) the list of items off of the select list.
	_buildItems: function(results) {
		// Fire the build event.
		if ( $.isFunction(this.options.onbuild) ) {
			// If the event explicitly returns false, stop now.
			if ( this.options.onbuild.apply(this, [results,this]) === false ) {
				return false;
			}
		}

		// If we have no results, do nothing.
		if ( !results ) {
			return;
		}

		// Clear the panel of items.
		this.elements.panel.empty();

		// Build out the items.
		var id = this.options.valueField;
		var txt = this.options.textField;
		var cls = this.options.style.option||'';
		for (var i=0; i<results.length; i++) {
			var row = results[i];
			var name = row[txt];
			if (!name) {
				continue;
			} else {
				name = name.replace(/</g,'&lt;').replace(/>/g,'&gt;');
			}
			var val = row[id]||'';
			$('<label class="'+cls+'" _value="'+val+'"><span>'+name+'</span></label>')
				.appendTo(this.elements.panel)
				[0].$row = row;
		}

		// Get a reference to the available items.
		this.elements.items = this.elements.panel.children("."+this.options.style.option);

		if ( this._type === 'select' ) {
			this._update();
		}
	},

	// Update the combobox label based on the value of the underlying select element.
	_update: function(e, select) {
		// If we don't yet have any items, do nothing.
		if ( !this.elements.items ) {
			return;
		}

		// Get the value.
		var val = this.element.val();

		// Find the active item.
		var item = this.elements.items.filter("[_value='"+(val||'')+"']");
		if ( !item.length ) {
			item = this.elements.items.eq(0);
		}

		// If the input element has a null value, assign the default (first) item.
		if ( val === null ) {
			this.element.val( item.attr('_value') );
		}

		// If we've been asked to select that item as well.
		if ( select ) {
			// Assign the new active item.
			if ( this._selected ) {
				this._selected.removeClass(this.options.style.hover);
			}
			this._selected = item.addClass(this.options.style.hover);
		}

		// Record the selected value in the main combobox label.
		if ( this.elements.label ) {
			this.elements.label.html($.trim(item.html())||'');
		}
	},

	// Are we over the panel or its children?
	_hoverPanel: function(e){
		if ( e.type === 'mouseleave' ) {
			return;
		}
		
		// Get the element hovered over.
		var item = $(e.target).closest('.'+this.options.style.option);
		if ( this._selected && this._selected.length && item.length && this._selected[0] === item[0] ) {
			return;
		} else if ( item.length ) {
			this._selected && this._selected.removeClass(this.options.style.hover);
			this._selected = item.addClass(this.options.style.hover);
		}
	},

	// Activate the combobox.
	_activate: function(e) {
		if ( !this._active ) {
			// Add the panel to its offset parent.
			var parent = this.element.offsetParent();
			if ( parent.is('div.ui-dialog-main') ) {
				parent = parent.closest('div.ui-dialog');
			} else if ( parent.is('html') ) {
				parent = $(document.body);
			}
			this.elements.panel.appendTo(parent);

			// Mark this combo as active.
			if ( this._type === 'select' ) {
				this.elements.combo.addClass('ui-select-active');
			}

			// Wire up the events.
			this._active = true;
			$(document).bind('mousedown', $.proxy(this._onClick, this));
			$(document)
				.bind('keydown', $.proxy(this._onKey, this))
				.flipevent('keydown', this._onKey);
		}
	},

	// Clicked on the combobox.
	_onCombo: function(e) {
		if ( this.elements.panel.is(':visible') ) {
			this.elements.panel.css({display: 'none' });
		} else {
			this._showPanel(e);
		}
		return false;
	},

	// Display the panel.
	_showPanel: function(e) {
		// If the panel hasn't been activated, do so now.
		if (!this._active) {
			this._activate();
		}

		// If we clicked on an open panel, close it.
		if ( e && e.type === 'click' && this.elements.panel.is(':visible') ) {
			this.elements.panel.css({display: 'none' });
			return false;
		}

		// Reset the term.
		this._term = '';

		// Get the panel width.
		var w = this.options.width;
		var minWidth = this.options.minWidth;
		if ( !minWidth ) {
			minWidth = this.elements.combo.outerWidth() -
				$.toInt(this.elements.panel.css('paddingLeft')) -
				$.toInt(this.elements.panel.css('paddingRight')) -
				$.toInt(this.elements.panel.css('borderLeftWidth')) -
				$.toInt(this.elements.panel.css('borderRightWidth'));

			if ( $.browser.msie && $.browser.version == 8 ) {
				minWidth -= 17;
			}
		}

		// Get the placement.
		var place;
		if ( this._type === 'select' ) {
			place = $.extend(this.options.place, { of: this.elements.combo });
		} else {
			place = $.extend(this.options.place, { of: this.element });
		}

		// Place and activate the combo panel.
		var six = $.browser.msie6;
		this.elements.panel
			.css({
				width: this.options.width || ( six ? w || minWidth : 'auto' ),
				minWidth: six ? minWidth : w || minWidth
			})
			.place(place);

		// Set the max height.
		if ( this.options.maxHeight && this.elements.panel.height() > this.options.maxHeight ) {
			this._scroll = true;
			this.elements.panel.css({
				height: this.options.maxHeight,
				overflow: 'auto',
				overflowX: 'hidden',
				overflowY: 'auto'
			});
		} else if ( /(auto|scroll)/.test($.curCSS(this.elements.panel[0],'overflow',1)+$.curCSS(this.elements.panel[0],'overflow-y',1)+$.curCSS(this.elements.panel[0],'overflow-x',1)) ) {
			this._scroll = true;
		} else {
			this._scroll = false;
		}

		// Fix a bug with IE6/7
		if ( $.browser.msie && $.browser.version < 8 && this.elements.panel[0].style.width === 'auto' ) {
			this.elements.panel.css({width:this.elements.panel.width()});
		}

		// Update the selected item.
		if ( this._type === 'select' ) {
			this._update(e, true);
		}

		// Make the active item visible.
		if ( this._selected && this._scroll ) {
			this._selected.scrollIntoView();
		}
		
		return false;
	},

	// Handle the focus/blur of the input element.
	_focusInput: function(e) {
		if ( this.options.toggleValue ) {
			var action = e.type === 'focus' ? 'clearValue' : 'restoreValue';
			$[action].apply(e.target, arguments);
		}
		
		// If we've re-focused on an item after we've done a search, show the panel.
		if ( e.type === 'focus' && this.elements.panel.children().length ) {
			this._showPanel();
		}
	},

	// When typing in the input box.
	_inputKey: function(e) {
		if (this._searchTimeout) {
			clearTimeout(this._searchTimeout);
		}
		// Handle the search on 1/2 second timeout.
		this._searchTimeout = setTimeout($.proxy(this._handleSearch, this), 500);
	},

	// Handle keypresses in the search box. 
	_handleSearch: function(e) {
		if (this._searchTimeout) {
			clearTimeout(this._searchTimeout);
			this._searchTimeout = null;
		}

		// Get the search term.
		var text = this.element.val();
		if (!text) {
			this.elements.panel.css({display:'none'});
		} else if ( text.length >= this.options.minchars && text != this._lastText ) {
			// Call the ajax call to get the results.
			this._lastText = text;
			var url = $.getAjaxUrl(this.options.ajaxUrl, {TERMS:text});
			if (url) {
				this._showPanel();
				if ( !this.options.noloading ) {
					this.elements.panel.loading();
				}

				$.ajax({
					url:url,
					dataType:'json',
					success:function(combo){
						return function(results){
							// Build the list of items.
							combo.elements.panel.loading('done');
							combo._buildItems.apply(combo, arguments);
						};
					}(this),
					error:function(combo){
						return function(){
							// Hide the panel and notify the user.
							combo.elements.panel.loading('done');
							combo.elements.panel.css({display:'none'});
							$.alert('There was a problem with the system, tech services has been notified.');
						};
					}(this)
				});
			}
		}
	},

	// Hide the panel if we moused down on something that WASN'T in the panel.
	_handleClick: function(e) {
		// See if we clicked on an element in this combobox.
		var elem = e.target;
		do {
			if ( elem === this.elements.combo[0] ) {
				// We clicked on the main combobox, let the it's own event handle it.
				return;
			} else if ( elem === this.elements.panel[0] ) {
				if ( elem.scrollHeight > elem.offsetHeight ) {
					// If we have a scrollbar.
					var doc = elem.ownerDocument;
					var x = e.clientX + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft);
					var dim = this.elements.panel.dimensions();
					if ( x > dim.left + dim.width - 20 ) {
						// We're clicking on the scrollbar.
						return;
					}
				}

				if ( this._selected && this._selected.length ) {
					// If we clicked on an element in the panel, select the active item.
					this._selectActive(e);
				}
				return;
			}
		} while ( elem = elem.parentNode );

		// Otherwise, deactivate the combobox, as we clicked outside it.
		this._deactivate();
	},

	// When a key is pressed.
	_handleKey: function(e) {
		if ( e.ctrlKey || e.altKey ) {
			return;
		}
		
		if ( e.shiftKey ) {
			if ( e.which === $.ui.keyCode.TAB && this._type === 'select' ) {
				if ( this._active ) {
					// Send the tab event to the underlying select box.
					this.element.focus().trigger(e);
				}
				return;
			}
			return;
		}

		// Handle the key down.
		switch (e.which) {
			case $.ui.keyCode.DOWN:
				this._move(1);
				return false;
			case $.ui.keyCode.UP:
				this._move(-1);
				return false;
			case $.ui.keyCode.PAGE_DOWN:
				this._move(10);
				return false;
			case $.ui.keyCode.PAGE_UP:
				this._move(-10);
				return false;
			case $.ui.keyCode.END:
				this._move(999);
				return false;
			case $.ui.keyCode.HOME:
				this._move(-999);
				return false;
			case $.ui.keyCode.ENTER:
				this._selectActive(e);
				return false;
			case $.ui.keyCode.TAB:
				if ( this._active && this._type === 'select' ) {
					// Send the tab event to the underlying select box.
					this.element.focus().trigger(e);
					setTimeout( $.proxy(this._deactivate,this), 1);
				}
				return;
			case $.ui.keyCode.ESCAPE:
				if ( this.elements.panel.is(':visible') ) {
					this.elements.panel.css({display: 'none' });
					e.stopImmediatePropagation();
					return false;
				} else {
					this._deactivate();
				}
				break;
			default:
				if ( this._type === 'select' ) {
					if ( this.elements.panel.is(':hidden') ) {
						this._showPanel();
					}
					if ( e.which >= 32 && e.which <= 160 ) {
						this._term += String.fromCharCode(e.which);
						return this._search();
					}
				}
				break;
		}
	},

	// Move the selected item.
	_move: function(amount) {
		if ( !this._active ) {
			return;
		}
		
		amount = $.toInt(amount);
		if (!amount) {
			return;
		}

		// If we issued a move command while the panel was hidden, show it first.
		if (!this.elements.panel.is(':visible')) {
			this._showPanel();
			return;
		}

		// Get the list of child items.
		var items = this.elements.items;

		// Make sure we have an active item.
		if ( !this._selected || !this._selected.length ) {
			this._selected = items.eq(0);
			if ( !this._selected.length ) {
				return;
			} else if ( amount === 1 ) {
				this._setItem(this._selected);
				return;
			}
		}

		// Get the current index.
		var index = items.index(this._selected) + amount;
		if ( index < 0 ) {
			index = 0;
		} else if ( index >= items.length ) {
			index = items.length -1;
		}

		// Set the new item.
		this._setItem(items.eq(index));

		// Reset the search term.
		this._term = '';
	},

	// Search for an item based on the keys pressed.
	_search: function() {
		if ( !this._term || !this.elements.items ) {
			// Make sure we have a search term.
			return;
		}

		// Get the list of child items.
		var items = this.elements.items;

		// Make sure we have an active item.
		if ( !this._selected || !this._selected.length ) {
			this._selected = items.eq(0);
			if ( !this._selected.length ) {
				return;
			}
		}
		
		// Get the current position
		var index = items.index(this._selected);
		var start = index;

		// Search for a matching item by name.
		var item = this._selected;
		while ( item && !item.children('span').text().startsWith(this._term) ) {
			index++;
			item = index < items.length ? items.eq(index) : null;
		}

		// If we just typed the first letter partway down the list (i.e. NOT the first item), and we didn't find anything,
		// Search again from the beginning up the point where we started.
		if ( !item && start > 0 && this._term.length === 1 ) {
			index = 0;
			item = items.eq(0);
			while ( index < start && item && !item.children('span').text().startsWith(this._term) ) {
				index++;
				item = index < items.length ? items.eq(index) : null;
			}
		}

		// Assign any new active item.
		this._setItem(item);

		// Clear the letters in the search term after a half-second.
		if ( this._termTimeout ) {
			clearTimeout(this._termTimeout);
		}
		this._termTimeout = setTimeout($.proxy(this._clearTerm, this), 500);
	},

	// Clear the search term.
	_clearTerm: function() {
		this._term = '';
		this._termTimeout = null;
	},

	// Set the active item and ensure it is visible.
	_setItem: function(item) {
		if ( !item || !item.length ) {
			return;
		}
		// Assign the new active item.
		if ( this._selected ) {
			this._selected.removeClass(this.options.style.hover);
		}
		this._selected = item.addClass(this.options.style.hover);

		if ( this._scroll ) {
			item.scrollIntoView();
		}
	},

	// Select the active item.
	_selectActive: function(e) {
		// Assign the value.
		if ( this._selected && this._selected.length ) {

			// Fire the select event.
			if ( $.isFunction(this.options.onselect) ) {
				// If the event explicitly returns false, stop now.
				if ( this.options.onselect.apply(this, [e, this._selected]) === false ) {
					return false;
				}
			}

			var val = this._selected.attr('_value');
			var name = this._selected.children('span').text();

			// Update the main label.
			if ( this.elements.label ) {
				this.elements.label.html(name||'');
			}

			// Assign the original element's value and trigger the change event.
			this.element.val(val||'').trigger('change');
			
			// Hide the panel.
			this.elements.panel.css({display:'none'});
		} else {
			// Hide the panel.
			this._deactivate();
		}
	},

	// Hide the panel.
	_deactivate: function(e) {
		if ( this._active ) {
			// unmark this combo as active.
			this.elements.combo.removeClass('ui-select-active');

			// Hide the panel and reset the events.
			this.elements.panel.css({display:'none'});
			if ( this.elements.label ) {
				this.elements.label.unselect();
			}
			this._active = false;
			this._over = false;
			$(document).unbind('mousedown', this._onClick);
			$(document).unbind('keydown', this._onKey);
		}
	},

	// Show or hide the combobox.
	show: function() {
		this.elements && this.elements.combo ? this.elements.combo.show() : this.element.show();
	},

	// Show or hide the combobox.
	hide: function() {
		this.elements && this.elements.combo ? this.elements.combo.hide() : this.element.hide();
	},

	destroy: function() {

		$.Widget.prototype.destroy.apply( this, arguments );
	}
});

// Static methods and properties
$.extend($.cms.combobox, {

});


})( jQuery );

