/*
title: jQuery.jQselectable.js (ex jQuery.selectable.js)
required: jQuery(tested on 1.4.2)
encoding: UTF-8
copy: Copyright 2008-2010 nori (norimania@gmail.com)
license: MIT
author: 5509 - http://5509.me/
archive: http://jqselectable.googlecode.com/
modified: 2010-12-02 14:00
rebuild: 2009-09-16 22:48
date: 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"/>',
simpleBox: '<div class="simple_cont"/>'
}

// 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;
},

// Rebuild selectable
// @ 09-09-18 17:28
rebuild: function() {

//console.log('called rebuild');

// 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 ? true : false;

var _this = this;
var generate_anchors = function(obj, parent) {
var _a = $('<a/>');
$(parent).append(_a);

_a.text(obj.text()).attr({
href: '#'+encodeURI(obj.text()),
name: obj.val()
});

if ( obj.is(':selected') ) {
_this.m_text.text(obj.text());
_a.addClass('selected');
}
if ( obj.hasClass('br') ) {
_a.after('<br/>');
}
}

if ( !this.m_input ) {
this.m_input = $('<a/>');
this.m_text = $('<span/>');
var _style = this.conf.style.match(/simple/) ? 'sBox' : 'sctble';

this.m_input.append(this.m_text).attr({
id: this.attrs.id+'_dammy',
href: '#'
}).addClass('sctble_display').addClass(_style).addClass(this.attrs.cl).insertAfter(this.target);
this.target.hide();
this.mat = $('<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"/>');
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/>');

for ( var i=0; i<_optgroup.length; i++ ) {
var _dt = $('<dt/>');
_dt.text($(_optgroup[i]).attr('label'));
var _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);
