/**
 * jQselectable
 *
 * @author     : nori (norimania@gmail.com)
 * @copyright  : 5509 (http://5509.me/)
 * @license    : The MIT License
 * @link       : http://5509.me/log/jqueryselectable
 * @repository : https://github.com/5509/jQselectable
 * @modified   : 2010-04-21 11:40
 * @since      : 2008-09-14 02:34
 */
;(function($) {
	
	// jQuery.jQselectable
	// Make selectbox so usuful and accesible
	// @ 2010-01-09
	var jQselectable = function(select, options, temp) {
		this.conf = {
			style: 'selectable', // or 'simple'
			set: 'show', // 'show', 'slideDown' or 'fadeIn'
			out: 'hide', // 'hide', 'slideUp' or 'fadeOut'
			setDuration: 'normal', // 'slow', 'normal', 'fast' or int(millisecond)
			outDuration: 'normal',
			opacity: 1, // pulldown opacity
			top: 0,
			left: -0,
			callback: null
		}
		this.temp = {
			selectable: '<div class="sctble_cont"></div>',
			simpleBox: '<div class="simple_cont"></div>'
		}
		
		// Extend confs and temps by user options
		$.extend(this.conf, options || {});
		$.extend(this.temp, temp || {});
		
		this.target = $(select);
		this.matHeight = 0;
		this.attrs = {
			id: this.target.attr('id'),
			cl: this.target.attr('class')
		}
		this.generatedFlg = false;
		
		// Init start
		this.init();
	}
	
	jQselectable.prototype = {
		// Init selectable
		// @ 10-01-09 21:00
		init: function() {
			// Build selectable
			this.build();
			// Event apply
			this.bind_events();
			// Switch flag true
			this.generatedFlg = true;
		},
		// API : disabled, enabled
		disabled: function() {
			if ( !this.clickBlocker ) {
				// Generate the clickBlocker
				var _this = this,
					_m = this.m_input,
					_mPos = _m.offset();
				this.clickBlocker = $('<div></div>')
					.css({
						width: _m.attr('offsetWidth'),
						height: _m.attr('offsetHeight'),
						position: 'absolute',
						top: _mPos.top,
						left: _mPos.left,
						background: '#fff',
						opacity: 0
					});
				$(window).resize(function() {
					_mPos = _m.offset();
					_this.clickBlocker
						.css({
							top: _mPos.top,
							left: _mPos.left
						});
				});
				$('body').append(this.clickBlocker);
				
				// Unbind events
				this.m_input.addClass('disabled').unbind();
				this.mat.unbind();
				$('a',this.mat).unbind();
				$('label[for="'+this.attrs.id+'"]').unbind();
			}
		},
		enabled: function() {
			if ( this.clickBlocker ) {
				// Remove the clickBlocker
				this.clickBlocker.remove();
				this.clickBlocker = null;
				// Bind events
				this.m_input.removeClass('disabled');
				this.bind_events();
			}
		},
		// Rebuild selectable
		// @ 09-09-18 17:28
		rebuild: function() {
			// unbind events from elements related selectable
			this.m_input.unbind();
			this.mat.unbind();
			$('a',this.mat).unbind();
			$('label[for="'+this.attrs.id+'"]').unbind();
			
			// Build selectable
			this.build();
			
			// Event apply
			this.bind_events();
		},
		
		// Building selectable from original select element
		// @ 2010-01-09 21:00
		build: function() {
			
			// Declare flag
			var has_optgroup = $('optgroup', this.target).length > 0;
			
			var _this = this;
			var generate_anchors = function(obj, parent) {
				// options:disabled - added at 11-04-21 11:41
				var _option;
				if ( obj.attr("disabled") ) {
					_option = $('<span class="disabled"></span>');
				} else {
					_option = $('<a></a>');
				}
				$(parent).append(_option);
				
				_option.text(obj.text()).attr({
					href: '#'+encodeURI(obj.text()),
					name: obj.val()
				});
				
				if ( obj.is(':selected') ) {
					_this.m_text.text(obj.text());
					_option.addClass('selected');
				}
				if ( obj.hasClass('br') ) {
					_option.after('<br/>');
				}
			}
			
			if ( !this.m_input ) {
				this.m_input = $('<a></a>');
				this.m_text = $('<span></span>');
				var _style = this.conf.style.match(/simple/) ? 'sBox' : 'sctble';
				
				this.m_input.append(this.m_text).attr({
					id: this.attrs.id+'_dammy',
					href: 'javascript:void(0)'
				}).addClass('sctble_display').addClass(_style).addClass(this.attrs.cl).insertAfter(this.target);
				this.target.hide();
				this.mat = $('<div></div>');
				
				// Customized
				if ( _style == 'simple' ) {
					this.mat.append(this.temp.selectable);
				} else {
					this.mat.append(this.temp.simpleBox);
				}
				// Customized end
				this.mat.attr({
					id: this.attrs.id+'_mat'
				}).addClass(_style).addClass(this.attrs.cl);
			}
			
			// For rebuilding
			if ( this.generatedFlg) {
				this.mat.empty();
				
				if ( _style == 'simple' ) {
					this.mat.append(this.temp.selectable);
				} else {
					this.mat.append(this.temp.simpleBox);
				}
			}
			
			this._div = $('<div class="body"></div>');
			if ( has_optgroup ) {
				this.mat.addClass('otpgroup');
				var _optgroup = $('optgroup', this.target);
				var _option = [];
				
				for ( var i=0; i<_optgroup.length; i++ ) {
					_option[i] = $('option', _optgroup[i]);
				}
				
				var _dl = $('<dl></dl>');
				
				for ( var i=0; i<_optgroup.length; i++ ) {
					var _dt = $('<dt></dt>');
					_dt.text($(_optgroup[i]).attr('label'));
					var _dd = $('<dd></dd>');
					for ( var j=0; j<_option[i].length; j++ ) {
						generate_anchors($(_option[i][j]), _dd);
					}
					_dl.append(_dt).append(_dd);
				}
				this._div.append(_dl).addClass('optg');
				$('div', this.mat).append(this._div);
				
			} else {
				this.mat.addClass('nooptgroup');
				var _option = $('option', this.target);
				for ( var i=0; i<_option.length; i++ ) {
					generate_anchors($(_option[i]), this._div);
				}
				$('div', this.mat).append(this._div.addClass('nooptg'));
			}
			
			// For rebuilding
			if ( !this.generatedFlg ) {
				$('body').append(this.mat);
				this.mat.addClass('sctble_mat').css({
					position: 'absolute',
					zIndex: 1000,
					display: 'none'
				});
				$('*:first-child',this.mat).addClass('first-child');
				$('*:last-child',this.mat).addClass('last-child');
			}
			
			// This is for IE6 that doesn't have "max-height" properties
			if ( document.all && typeof document.body.style.maxHeight == 'undefined' ) {
				if ( this.conf.height < this.mat.height() ) {
					$(this._div).css('height', this.conf.height);
				}
			// Other browsers
			} else {
				$(this._div).css('maxHeight', this.conf.height);
			}
			
			// get height of the mat
			this.mat.show();
			this.matHeight = this.mat.attr('offsetHeight');
			this.mat.hide();
		},
		
		// Bind events
		// @ 09-09-17 22:59
		bind_events: function() {
			var _this = this;
			// Flag checking where the events was called
			var is_called = true;
			
			var set_pos = function() {
				var topPos,
					scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
					clientHeight = document.documentElement.clientHeight || document.body.clientHeight,
					_pos = _this.m_input.offset();
					
				if ( clientHeight/2 < (_pos.top - scrollTop) ) {
					topPos = _pos.top - _this.matHeight + _this.conf.top - 5;
				} else {
					topPos = _pos.top + _this.m_input.height()*1.3 + _this.conf.top;
				}
				_this.mat.css({
					top: topPos,
					left: _pos.left + _this.conf.left
				});
			}
			$(window).resize(function() {
				set_pos();
			});
			
			// Hide all mats are displayed
			var mat_hide = function() {
				var _mat = $('.sctble_mat');
				
				switch( _this.conf.out ) {
					case 'slideUp':
						_mat.slideUp(_this.conf.outDuration);
						break;
					case 'fadeOut':
						_mat.fadeOut(_this.conf.outDuration);
						break;
					default:
						_mat.hide();
						break;
				}
			}
			
			// Show the mat
			var mat_show = function() {
				mat_hide();
				
				if ( _this.conf.set == 'slideDown' ) {
					var scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
						clientHeight = document.documentElement.clientHeight || document.body.clientHeight,
						_pos = _this.m_input.offset(),
						balance = clientHeight/2 < (_pos.top - scrollTop);
						
					if ( balance ) {
						_this.mat.css('top', _pos.top + _this.conf.top - 5);
					}
				}
				
				if ( _this.conf.set == 'slideDown' ) {
					if ( balance ) {
						_this.mat
							.animate({
								height: 'toggle',
								top: parseInt(_this.mat.css('top')) -  _this.matHeight
							}, {
								easing: 'swing',
								duration: _this.conf.setDuration
							})
							.css('opacity', _this.conf.opacity);
					} else {
						_this.mat.slideDown(_this.conf.setDuration).css('opacity', _this.conf.opacity);
					}
				} else
				if ( _this.conf.set == 'fadeIn' ) {
					_this.mat.css({
						display: 'block',
						opacity: 0
					}).fadeTo(_this.conf.setDuration, _this.conf.opacity);
				} else {
					_this.mat.show().css('opacity', _this.conf.opacity);
				}
				
				var _interval = isNaN(_this.conf.setDuration) ? null : _this.conf.setDuration+10;
				if( _interval == null ) {
					if ( _this.conf.setDuration.match(/slow/) ) {
						interval = 610;
					} else if ( _this.conf.setDuration.match(/normal/) ) {
						interval = 410;
					} else {
						interval = 210;
					}
				}
				
				var _chk = setInterval(function() {
					$('a.selected', _this.mat).focus();
					clearInterval(_chk);
				}, _interval);
			}
			
			// Call selectable
			this.m_input.click(function(event) {
				if ( _this.mat.is(':visible') ) return false;
				set_pos();
				$(this).addClass('sctble_focus');
				$('a.sctble_display').not(this).removeClass('sctble_focus');
				
				mat_show();
				event.stopPropagation();
				return false;
			}).keyup(function(event) {
				if( is_called ){
					set_pos();
					mat_show();
					event.stopPropagation();
				} else {
					is_called = true;
				}
			});
			
			// Stop event propagation
			this.mat.click(function(event) {
				event.stopPropagation();
			});
			
			// Hide the mat
			$('body, a').not('a.sctble_display').click(function(event) {
				$('a.sctble_display').removeClass('sctble_focus');
				mat_hide();
			}).not('a').keyup(function(event) {
				if ( event.keyCode=='27' ) {
					$('a.sctble_focus').removeClass('sctble_focus');
					is_called = false;
					_this.m_input.blur();
					mat_hide();
				}
			});
			
			// Click value append to both dummy and change original select value
			$('a', this.mat).click(function() {
				var self = $(this);
				_this.m_text.text(decodeURI(self.attr('href').split('#')[1]));
				$('option[value="'+self.attr('name')+'"]', _this.target).attr('selected', 'selected');
				$('.selected', _this.mat).removeClass('selected');
				self.addClass('selected');
				_this.m_input.removeClass('sctble_focus');
				is_called = false;
				mat_hide();
				
				if ( _this.conf.callback && typeof _this.conf.callback=='function' ) {
					_this.conf.callback.call(_this.target);
				}
				
				_this.m_input.focus();
				return false;
			});
			
			// Be able to click original select label
			$('label[for="'+this.attrs.id+'"]').click(function(event) {
				set_pos();
				_this.m_input.addClass('sctble_focus');
				$('a.sctble_focus').not(_this.m_input).removeClass('sctble_focus');
				mat_show();
				event.stopPropagation();
				return false;
			});
		}
	}
	
	// Extense the namespace of jQuery as method
	// This function returns (the) instance(s)
	$.fn.jQselectable = function(options, temp) {
		if ( $(this).length>1 ) {
			var _instances = [];
			$(this).each(function(i) {
				_instances[i] = new jQselectable(this, options, temp);
			});
			return _instances;
		} else {
			return new jQselectable(this, options, temp);
		}
	}
	
	// If namespace of jQuery.fn has 'selectable', this is 'jQselectable'
	// To prevent the interference of namespace
	// You can call 'selectable' method by both 'jQuery.fn.selectable' and 'jQuery.fn.jQselectable' you like
	if ( !jQuery.fn.selectable ) {
		$.fn.selectable = $.fn.jQselectable;
	}
	
})(jQuery);

