');
var optionHeight = parseInt(tmp.find('.selectBox-options A:first').html(' ').outerHeight());
tmp.remove();
control.height(optionHeight * size);
}
this.disableSelection(control);
} else {
// Dropdown controls
var label = $(''),
arrow = $('');
// Update label
label.attr('class', this.getLabelClass()).text(this.getLabelText());
options = this.getOptions('dropdown');
options.appendTo('BODY');
control
.data('selectBox-options', options)
.addClass('selectBox-dropdown')
.append(label)
.append(arrow)
.bind('mousedown.selectBox', function (event) {
if (1 === event.which) {
if (control.hasClass('selectBox-menuShowing')) {
self.hideMenus();
} else {
event.stopPropagation();
// Webkit fix to prevent premature selection of options
options
.data('selectBox-down-at-x', event.screenX)
.data('selectBox-down-at-y', event.screenY);
self.showMenu();
}
}
})
.bind('keydown.selectBox', function (event) {
self.handleKeyDown(event);
})
.bind('keypress.selectBox', function (event) {
self.handleKeyPress(event);
})
.bind('open.selectBox',function (event, triggerData) {
if (triggerData && triggerData._selectBox === true) {
return;
}
self.showMenu();
})
.bind('close.selectBox', function (event, triggerData) {
if (triggerData && triggerData._selectBox === true) {
return;
}
self.hideMenus();
})
.insertAfter(select);
// Set label width
var labelWidth =
control.width()
- arrow.outerWidth()
- parseInt(label.css('paddingLeft')) || 0
- parseInt(label.css('paddingRight')) || 0;
label.width(labelWidth);
this.disableSelection(control);
}
// Store data for later use and show the control
select
.addClass('selectBox')
.data('selectBox-control', control)
.data('selectBox-settings', settings)
.hide();
};
/**
* @param {String} type 'inline'|'dropdown'
* @returns {jQuery}
*/
SelectBox.prototype.getOptions = function (type) {
var options;
var select = $(this.selectElement);
var self = this;
// Private function to handle recursion in the getOptions function.
var _getOptions = function (select, options) {
// Loop through the set in order of element children.
select.children('OPTION, OPTGROUP').each(function () {
// If the element is an option, add it to the list.
if ($(this).is('OPTION')) {
// Check for a value in the option found.
if ($(this).length > 0) {
// Create an option form the found element.
self.generateOptions($(this), options);
} else {
// No option information found, so add an empty.
options.append('
\u00A0
');
}
} else {
// If the element is an option group, add the group and call this function on it.
var optgroup = $('');
optgroup.text($(this).attr('label'));
options.append(optgroup);
options = _getOptions($(this), options);
}
});
// Return the built strin
return options;
};
switch (type) {
case 'inline':
options = $('
');
options = _getOptions(select, options);
options
.find('A')
.bind('mouseover.selectBox', function (event) {
self.addHover($(this).parent());
})
.bind('mouseout.selectBox',function (event) {
self.removeHover($(this).parent());
})
.bind('mousedown.selectBox',function (event) {
if (1 !== event.which) {
return
}
event.preventDefault(); // Prevent options from being "dragged"
if (!select.selectBox('control').hasClass('selectBox-active')) {
select.selectBox('control').focus();
}
})
.bind('mouseup.selectBox', function (event) {
if (1 !== event.which) {
return;
}
self.hideMenus();
self.selectOption($(this).parent(), event);
});
this.disableSelection(options);
return options;
case 'dropdown':
options = $('
');
options = _getOptions(select, options);
options
.data('selectBox-select', select)
.css('display', 'none')
.appendTo('BODY')
.find('A')
.bind('mousedown.selectBox', function (event) {
if (event.which === 1) {
event.preventDefault(); // Prevent options from being "dragged"
if (event.screenX === options.data('selectBox-down-at-x') &&
event.screenY === options.data('selectBox-down-at-y')) {
options.removeData('selectBox-down-at-x').removeData('selectBox-down-at-y');
self.hideMenus();
}
}
})
.bind('mouseup.selectBox', function (event) {
if (1 !== event.which) {
return;
}
if (event.screenX === options.data('selectBox-down-at-x') &&
event.screenY === options.data('selectBox-down-at-y')) {
return;
} else {
options.removeData('selectBox-down-at-x').removeData('selectBox-down-at-y');
}
self.selectOption($(this).parent());
self.hideMenus();
})
.bind('mouseover.selectBox', function (event) {
self.addHover($(this).parent());
})
.bind('mouseout.selectBox', function (event) {
self.removeHover($(this).parent());
});
// Inherit classes for dropdown menu
var classes = select.attr('class') || '';
if ('' !== classes) {
classes = classes.split(' ');
for (var i in classes) {
options.addClass(classes[i] + '-selectBox-dropdown-menu');
}
}
this.disableSelection(options);
return options;
}
};
/**
* Returns the current class of the selected option.
*
* @returns {String}
*/
SelectBox.prototype.getLabelClass = function () {
var selected = $(this.selectElement).find('OPTION:selected');
return ('selectBox-label ' + (selected.attr('class') || '')).replace(/\s+$/, '');
};
/**
* Returns the current label of the selected option.
*
* @returns {String}
*/
SelectBox.prototype.getLabelText = function () {
var selected = $(this.selectElement).find('OPTION:selected');
return selected.text() || '\u00A0';
};
/**
* Sets the label.
* This method uses the getLabelClass() and getLabelText() methods.
*/
SelectBox.prototype.setLabel = function () {
var select = $(this.selectElement);
var control = select.data('selectBox-control');
if (!control) {
return;
}
control
.find('.selectBox-label')
.attr('class', this.getLabelClass())
.text(this.getLabelText());
};
/**
* Destroys the SelectBox instance and shows the origin select element.
*
*/
SelectBox.prototype.destroy = function () {
var select = $(this.selectElement);
var control = select.data('selectBox-control');
if (!control) {
return;
}
var options = control.data('selectBox-options');
options.remove();
control.remove();
select
.removeClass('selectBox')
.removeData('selectBox-control')
.data('selectBox-control', null)
.removeData('selectBox-settings')
.data('selectBox-settings', null)
.show();
};
/**
* Refreshes the option elements.
*/
SelectBox.prototype.refresh = function () {
var select = $(this.selectElement),
control = select.data('selectBox-control'),
dropdown = control.hasClass('selectBox-dropdown'),
menuOpened = control.hasClass('selectBox-menuShowing');
select.selectBox('options', select.html());
// Restore opened dropdown state (original menu was trashed)
if (dropdown && menuOpened) {
this.showMenu();
}
};
/**
* Shows the dropdown menu.
*/
SelectBox.prototype.showMenu = function () {
var self = this
, select = $(this.selectElement)
, control = select.data('selectBox-control')
, settings = select.data('selectBox-settings')
, options = control.data('selectBox-options');
if (control.hasClass('selectBox-disabled')) {
return false;
}
this.hideMenus();
var borderBottomWidth = parseInt(control.css('borderBottomWidth')) || 0;
// Menu position
options
.width(control.innerWidth())
.css({
top: control.offset().top + control.outerHeight() - borderBottomWidth,
left: control.offset().left
});
if (select.triggerHandler('beforeopen')) {
return false;
}
var dispatchOpenEvent = function () {
select.triggerHandler('open', {
_selectBox: true
});
};
// Show menu
switch (settings.menuTransition) {
case 'fade':
options.fadeIn(settings.menuSpeed, dispatchOpenEvent);
break;
case 'slide':
options.slideDown(settings.menuSpeed, dispatchOpenEvent);
break;
default:
options.show(settings.menuSpeed, dispatchOpenEvent);
break;
}
if (!settings.menuSpeed) {
dispatchOpenEvent();
}
// Center on selected option
var li = options.find('.selectBox-selected:first');
this.keepOptionInView(li, true);
this.addHover(li);
control.addClass('selectBox-menuShowing');
$(document).bind('mousedown.selectBox', function (event) {
if (1 === event.which) {
if ($(event.target).parents().andSelf().hasClass('selectBox-options')) {
return;
}
self.hideMenus();
}
});
};
/**
* Hides the menu of all instances.
*/
SelectBox.prototype.hideMenus = function () {
if ($(".selectBox-dropdown-menu:visible").length === 0) {
return;
}
$(document).unbind('mousedown.selectBox');
$(".selectBox-dropdown-menu").each(function () {
var options = $(this)
, select = options.data('selectBox-select')
, control = select.data('selectBox-control')
, settings = select.data('selectBox-settings');
if (select.triggerHandler('beforeclose')) {
return false;
}
var dispatchCloseEvent = function () {
select.triggerHandler('close', {
_selectBox: true
});
};
if (settings) {
switch (settings.menuTransition) {
case 'fade':
options.fadeOut(settings.menuSpeed, dispatchCloseEvent);
break;
case 'slide':
options.slideUp(settings.menuSpeed, dispatchCloseEvent);
break;
default:
options.hide(settings.menuSpeed, dispatchCloseEvent);
break;
}
if (!settings.menuSpeed) {
dispatchCloseEvent();
}
control.removeClass('selectBox-menuShowing');
} else {
$(this).hide();
$(this).triggerHandler('close', {
_selectBox: true
});
$(this).removeClass('selectBox-menuShowing');
}
});
};
/**
* Selects an option.
*
* @param {HTMLElement} li
* @param {DOMEvent} event
* @returns {Boolean}
*/
SelectBox.prototype.selectOption = function (li, event) {
var select = $(this.selectElement);
li = $(li);
var control = select.data('selectBox-control')
, settings = select.data('selectBox-settings');
if (control.hasClass('selectBox-disabled')) {
return false;
}
if (0 === li.length || li.hasClass('selectBox-disabled')) {
return false;
}
if (select.attr('multiple')) {
// If event.shiftKey is true, this will select all options between li and the last li selected
if (event.shiftKey && control.data('selectBox-last-selected')) {
li.toggleClass('selectBox-selected');
var affectedOptions;
if (li.index() > control.data('selectBox-last-selected').index()) {
affectedOptions = li
.siblings()
.slice(control.data('selectBox-last-selected').index(), li.index());
} else {
affectedOptions = li
.siblings()
.slice(li.index(), control.data('selectBox-last-selected').index());
}
affectedOptions = affectedOptions.not('.selectBox-optgroup, .selectBox-disabled');
if (li.hasClass('selectBox-selected')) {
affectedOptions.addClass('selectBox-selected');
} else {
affectedOptions.removeClass('selectBox-selected');
}
} else if ((this.isMac && event.metaKey) || (!this.isMac && event.ctrlKey)) {
li.toggleClass('selectBox-selected');
} else {
li.siblings().removeClass('selectBox-selected');
li.addClass('selectBox-selected');
}
} else {
li.siblings().removeClass('selectBox-selected');
li.addClass('selectBox-selected');
}
if (control.hasClass('selectBox-dropdown')) {
control.find('.selectBox-label').text(li.text());
}
// Update original control's value
var i = 0, selection = [];
if (select.attr('multiple')) {
control.find('.selectBox-selected A').each(function () {
selection[i++] = $(this).attr('rel');
});
} else {
selection = li.find('A').attr('rel');
}
// Remember most recently selected item
control.data('selectBox-last-selected', li);
// Change callback
if (select.val() !== selection) {
select.val(selection);
this.setLabel();
select.trigger('change');
}
return true;
};
/**
* Adds the hover class.
*
* @param {HTMLElement} li
*/
SelectBox.prototype.addHover = function (li) {
li = $(li);
var select = $(this.selectElement)
, control = select.data('selectBox-control')
, options = control.data('selectBox-options');
options.find('.selectBox-hover').removeClass('selectBox-hover');
li.addClass('selectBox-hover');
};
/**
* Returns the original HTML select element.
*
* @returns {HTMLElement}
*/
SelectBox.prototype.getSelectElement = function () {
return this.selectElement;
};
/**
* Remove the hover class.
*
* @param {HTMLElement} li
*/
SelectBox.prototype.removeHover = function (li) {
li = $(li);
var select = $(this.selectElement)
, control = select.data('selectBox-control')
, options = control.data('selectBox-options');
options.find('.selectBox-hover').removeClass('selectBox-hover');
};
/**
* Checks if the widget is in the view.
*
* @param {jQuery} li
* @param {Boolean} center
*/
SelectBox.prototype.keepOptionInView = function (li, center) {
if (!li || li.length === 0) {
return;
}
var select = $(this.selectElement)
, control = select.data('selectBox-control')
, options = control.data('selectBox-options')
, scrollBox = control.hasClass('selectBox-dropdown') ? options : options.parent()
, top = parseInt(li.offset().top -scrollBox.position().top)
, bottom = parseInt(top + li.outerHeight());
if (center) {
scrollBox.scrollTop(li.offset().top - scrollBox.offset().top + scrollBox.scrollTop() -
(scrollBox.height() / 2));
} else {
if (top < 0) {
scrollBox.scrollTop(li.offset().top - scrollBox.offset().top + scrollBox.scrollTop());
}
if (bottom > scrollBox.height()) {
scrollBox.scrollTop((li.offset().top + li.outerHeight()) - scrollBox.offset().top +
scrollBox.scrollTop() - scrollBox.height());
}
}
};
/**
* Handles the keyDown event.
* Handles open/close and arrow key functionality
*
* @param {DOMEvent} event
*/
SelectBox.prototype.handleKeyDown = function (event) {
var select = $(this.selectElement)
, control = select.data('selectBox-control')
, options = control.data('selectBox-options')
, settings = select.data('selectBox-settings')
, totalOptions = 0, i = 0;
if (control.hasClass('selectBox-disabled')) {
return;
}
switch (event.keyCode) {
case 8:
// backspace
event.preventDefault();
this.typeSearch = '';
break;
case 9:
// tab
case 27:
// esc
this.hideMenus();
this.removeHover();
break;
case 13:
// enter
if (control.hasClass('selectBox-menuShowing')) {
this.selectOption(options.find('LI.selectBox-hover:first'), event);
if (control.hasClass('selectBox-dropdown')) {
this.hideMenus();
}
} else {
this.showMenu();
}
break;
case 38:
// up
case 37:
// left
event.preventDefault();
if (control.hasClass('selectBox-menuShowing')) {
var prev = options.find('.selectBox-hover').prev('LI');
totalOptions = options.find('LI:not(.selectBox-optgroup)').length;
i = 0;
while (prev.length === 0 || prev.hasClass('selectBox-disabled') ||
prev.hasClass('selectBox-optgroup')) {
prev = prev.prev('LI');
if (prev.length === 0) {
if (settings.loopOptions) {
prev = options.find('LI:last');
} else {
prev = options.find('LI:first');
}
}
if (++i >= totalOptions) {
break;
}
}
this.addHover(prev);
this.selectOption(prev, event);
this.keepOptionInView(prev);
} else {
this.showMenu();
}
break;
case 40:
// down
case 39:
// right
event.preventDefault();
if (control.hasClass('selectBox-menuShowing')) {
var next = options.find('.selectBox-hover').next('LI');
totalOptions = options.find('LI:not(.selectBox-optgroup)').length;
i = 0;
while (0 === next.length || next.hasClass('selectBox-disabled') ||
next.hasClass('selectBox-optgroup')) {
next = next.next('LI');
if (next.length === 0) {
if (settings.loopOptions) {
next = options.find('LI:first');
} else {
next = options.find('LI:last');
}
}
if (++i >= totalOptions) {
break;
}
}
this.addHover(next);
this.selectOption(next, event);
this.keepOptionInView(next);
} else {
this.showMenu();
}
break;
}
};
/**
* Handles the keyPress event.
* Handles type-to-find functionality
*
* @param {DOMEvent} event
*/
SelectBox.prototype.handleKeyPress = function (event) {
var select = $(this.selectElement)
, control = select.data('selectBox-control')
, options = control.data('selectBox-options');
if (control.hasClass('selectBox-disabled')) {
return;
}
switch (event.keyCode) {
case 9:
// tab
case 27:
// esc
case 13:
// enter
case 38:
// up
case 37:
// left
case 40:
// down
case 39:
// right
// Don't interfere with the keydown event!
break;
default:
// Type to find
if (!control.hasClass('selectBox-menuShowing')) {
this.showMenu();
}
event.preventDefault();
clearTimeout(this.typeTimer);
this.typeSearch += String.fromCharCode(event.charCode || event.keyCode);
options.find('A').each(function () {
if ($(this).text().substr(0, this.typeSearch.length).toLowerCase() === this.typeSearch.toLowerCase()) {
this.addHover($(this).parent());
this.selectOption($(this).parent(), event);
this.keepOptionInView($(this).parent());
return false;
}
});
// Clear after a brief pause
this.typeTimer = setTimeout(function () {
this.typeSearch = '';
}, 1000);
break;
}
};
/**
* Enables the selectBox.
*/
SelectBox.prototype.enable = function () {
var select = $(this.selectElement);
select.prop('disabled', false);
var control = select.data('selectBox-control');
if (!control) {
return;
}
control.removeClass('selectBox-disabled');
};
/**
* Disables the selectBox.
*/
SelectBox.prototype.disable = function () {
var select = $(this.selectElement);
select.prop('disabled', true);
var control = select.data('selectBox-control');
if (!control) {
return;
}
control.addClass('selectBox-disabled');
};
/**
* Sets the current value.
*
* @param {String} value
*/
SelectBox.prototype.setValue = function (value) {
var select = $(this.selectElement);
select.val(value);
value = select.val(); // IE9's select would be null if it was set with a non-exist options value
if (null === value) { // So check it here and set it with the first option's value if possible
value = select.children().first().val();
select.val(value);
}
var control = select.data('selectBox-control');
if (!control) {
return;
}
var settings = select.data('selectBox-settings')
, options = control.data('selectBox-options');
// Update label
this.setLabel();
// Update control values
options.find('.selectBox-selected').removeClass('selectBox-selected');
options.find('A').each(function () {
if (typeof(value) === 'object') {
for (var i = 0; i < value.length; i++) {
if ($(this).attr('rel') == value[i]) {
$(this).parent().addClass('selectBox-selected');
}
}
} else {
if ($(this).attr('rel') == value) {
$(this).parent().addClass('selectBox-selected');
}
}
});
if (settings.change) {
settings.change.call(select);
}
};
/**
* Sets the option elements.
*
* @param {String|Object} options
*/
SelectBox.prototype.setOptions = function (options) {
var select = $(this.selectElement)
, control = select.data('selectBox-control')
, settings = select.data('selectBox-settings')
, type;
switch (typeof(options)) {
case 'string':
select.html(options);
break;
case 'object':
select.html('');
for (var i in options) {
if (options[i] === null) {
continue;
}
if (typeof(options[i]) === 'object') {
var optgroup = $('');
for (var j in options[i]) {
optgroup.append('');
}
select.append(optgroup);
} else {
var option = $('');
select.append(option);
}
}
break;
}
if (!control) {
return;
}
// Remove old options
control.data('selectBox-options').remove();
// Generate new options
type = control.hasClass('selectBox-dropdown') ? 'dropdown' : 'inline';
options = this.getOptions(type);
control.data('selectBox-options', options);
switch (type) {
case 'inline':
control.append(options);
break;
case 'dropdown':
// Update label
this.setLabel();
$("BODY").append(options);
break;
}
};
/**
* Disables the selection.
*
* @param {*} selector
*/
SelectBox.prototype.disableSelection = function (selector) {
$(selector).css('MozUserSelect', 'none').bind('selectstart', function (event) {
event.preventDefault();
});
};
/**
* Generates the options.
*
* @param {jQuery} self
* @param {jQuery} options
*/
SelectBox.prototype.generateOptions = function (self, options) {
var li = $(''), a = $('');
li.addClass(self.attr('class'));
li.data(self.data());
a.attr('rel', self.val()).text(self.text());
li.append(a);
if (self.attr('disabled')) {
li.addClass('selectBox-disabled');
}
if (self.attr('selected')) {
li.addClass('selectBox-selected');
}
options.append(li);
};
/**
* Extends the jQuery.fn object.
*/
$.extend($.fn, {
selectBox: function (method, options) {
var selectBox;
switch (method) {
case 'control':
return $(this).data('selectBox-control');
case 'settings':
if (!options) {
return $(this).data('selectBox-settings');
}
$(this).each(function () {
$(this).data('selectBox-settings', $.extend(true, $(this).data('selectBox-settings'), options));
});
break;
case 'options':
// Getter
if (undefined === options) {
return $(this).data('selectBox-control').data('selectBox-options');
}
// Setter
$(this).each(function () {
if (selectBox = $(this).data('selectBox')) {
selectBox.setOptions(options);
}
});
break;
case 'value':
// Empty string is a valid value
if (undefined === options) {
return $(this).val();
}
$(this).each(function () {
if (selectBox = $(this).data('selectBox')) {
selectBox.setValue(options);
}
});
break;
case 'refresh':
$(this).each(function () {
if (selectBox = $(this).data('selectBox')) {
selectBox.refresh();
}
});
break;
case 'enable':
$(this).each(function () {
if (selectBox = $(this).data('selectBox')) {
selectBox.enable(this);
}
});
break;
case 'disable':
$(this).each(function () {
if (selectBox = $(this).data('selectBox')) {
selectBox.disable();
}
});
break;
case 'destroy':
$(this).each(function () {
if (selectBox = $(this).data('selectBox')) {
selectBox.destroy();
$(this).data('selectBox', null);
}
});
break;
case 'instance':
return $(this).data('selectBox');
default:
$(this).each(function (idx, select) {
if (!$(select).data('selectBox')) {
$(select).data('selectBox', new SelectBox(select, method));
}
});
break;
}
return $(this);
}
});
})(jQuery);