/*
---
name: Chosen
description: Creates a Picker, which can be used for anything
authors:
- Patrick Filler
- Jules Janssen
- Jonnathan Soares
requires:
- Core/*
- More/Element.Measure
- More/Locale
- More/Binds
license: MIT-style license
provides: Chosen
...
*/
Elements.implement({
chosen: function(options){
return this.each(function(el){
if (!el.hasClass("chzn-done")) {
return new Chosen(el, options);
}
});
}
});
var Chosen = new Class({
Implements: Options,
Binds: ['test_active_click', 'container_mousedown', 'container_mouseup',
'mouse_enter', 'mouse_leave','search_results_mouseup',
'search_results_mouseover', 'search_results_mouseout',
'input_blur','keyup_checker', 'keydown_checker',
'choices_click','input_focus', 'activate_field',
'results_update_field'],
options: {
allow_single_deselect: false,
disable_search_threshold: 0
},
active_field: false,
mouse_on_container: false,
results_showing: false,
result_highlighted: null,
result_single_selected: null,
choices: 0,
initialize: function(elmn, options){
this.setOptions(options);
this.form_field = elmn;
this.is_multiple = this.form_field.multiple;
this.is_rtl = this.form_field.hasClass('chzn-rtl');
this.set_up_html();
this.register_observers();
this.form_field.addClass('chzn-done')
},
set_up_html: function(){
if (!this.form_field.get('id')) this.form_field.set('id', String.uniqueID());
this.container_id = this.form_field.id.replace(/(:|\.)/g, '_') + "_chzn";
this.f_width = this.form_field.measure(function() {
return this.getSize().x;
});
this.default_text = this.form_field.get('data-placeholder') ? this.form_field.get('data-placeholder') : Locale.get('Chosen.placeholder', this.form_field.multiple);
this.container = new Element('div', {
'id': this.container_id,
'class': 'chzn-container'+ (this.is_rtl ? ' chzn-rtl' : '') + " chzn-container-" + (this.is_multiple ? "multi" : "single"),
'styles': {
'width': this.f_width
}
});
if (this.is_multiple){
this.container.set('html', '
');
} else {
this.container.set('html', '' + this.default_text + '
');
}
this.form_field.setStyle('display', 'none').grab(this.container, 'after');
this.dropdown = this.container.getElement('div.chzn-drop');
var dd_top = this.container.getCoordinates().height;
var dd_width = this.f_width - this.dropdown.get_side_border_padding();
this.dropdown.setStyles({
'width': dd_width,
'top': dd_top
});
this.search_field = this.container.getElement('input');
this.search_results = this.container.getElement('ul.chzn-results');
this.search_field_scale();
this.search_no_results = this.container.getElement('li.no-results');
if (this.is_multiple){
this.search_choices = this.container.getElement('ul.chzn-choices');
this.search_container = this.container.getElement('li.search-field');
} else {
this.search_container = this.container.getElement('div.chzn-search');
this.selected_item = this.container.getElement('.chzn-single');
var sf_width = dd_width - this.search_container.get_side_border_padding() - this.search_field.get_side_border_padding();
this.search_field.setStyle('width', sf_width);
}
this.results_build();
this.set_tab_index();
this.form_field.fireEvent('liszt:ready', this);
},
register_observers: function(){
this.container.addEvents({
mousedown: this.container_mousedown,
mouseup: this.container_mouseup,
mouseenter: this.mouse_enter,
mouseleave: this.mouse_leave
});
this.search_results.addEvents({
mouseup: this.search_results_mouseup,
mouseover: this.search_results_mouseover,
mouseout: this.search_results_mouseout
});
this.form_field.addEvent("liszt:updated", this.results_update_field);
this.search_field.addEvents({
blur: this.input_blur,
keyup: this.keyup_checker,
keydown: this.keydown_checker
});
if (this.is_multiple){
this.search_choices.addEvent("click", this.choices_click);
this.search_field.addEvent("focus", this.input_focus);
} else {
this.selected_item.addEvent("focus", this.activate_field);
}
},
unregister_observers: function(){
this.container.removeEvents({
mousedown: this.container_mousedown,
mouseup: this.container_mouseup,
mouseenter: this.mouse_enter,
mouseleave: this.mouse_leave
});
this.search_results.removeEvents({
mouseup: this.search_results_mouseup,
mouseover: this.search_results_mouseover,
mouseout: this.search_results_mouseout
});
this.form_field.removeEvent("liszt:updated", this.results_update_field);
this.search_field.removeEvents({
blur: this.input_blur,
keyup: this.keyup_checker,
keydown: this.keydown_checker
});
if (this.is_multiple){
this.search_choices.removeEvent("click", this.choices_click);
this.search_field.removeEvent("focus", this.input_focus);
} else {
this.selected_item.removeEvent("focus", this.activate_field);
}
document.removeEvent('click', this.test_active_click);
},
search_field_disabled: function() {
this.is_disabled = this.form_field.get('disabled');
if (this.is_disabled) {
this.container.addClass('chzn-disabled');
this.search_field.set('disabled', true);
if (!this.is_multiple) {
this.selected_item.removeEvent("focus", this.activate_field);
}
this.close_field();
} else {
this.container.removeClass('chzn-disabled');
this.search_field.set('disabled', false);
if (!this.is_multiple) {
this.selected_item.addEvent("focus", this.activate_field);
}
}
},
container_mousedown: function(evt){
if (!this.is_disabled) {
var target_closelink = evt != null ? evt.target.hasClass("search-choice-close") : false;
if (evt && evt.type === "mousedown"){
evt.stopPropagation();
}
if (!this.pending_destroy_click && !target_closelink){
if (!this.active_field){
if (this.is_multiple){
this.search_field.set('value', '');
}
document.addEvent('click', this.test_active_click);
this.results_show();
} else if (!this.is_multiple && evt && (evt.target === this.selected_item || evt.target.getParents('a.chzn-single').length)){
evt.preventDefault();
this.results_toggle();
}
this.activate_field();
} else {
this.pending_destroy_click = false;
}
}
},
container_mouseup: function(evt) {
if (evt.target.get('tag').toUpperCase() === "ABBR") {
return this.results_reset(evt);
}
},
mouse_enter: function(){
this.mouse_on_container = true;
},
mouse_leave: function(){
this.mouse_on_container = false;
},
input_focus: function(evt){
if (!this.active_field){
setTimeout(this.container_mousedown, 50);
}
},
input_blur: function(evt){
if (!this.mouse_on_container){
this.active_field = false;
setTimeout(this.blur_test.bind(this), 100);
}
},
blur_test: function(evt){
if (!this.active_field && this.container.hasClass('chzn-container-active')){
this.close_field();
}
},
close_field: function(){
document.removeEvent('click', this.test_active_click);
if (!this.is_multiple){
this.selected_item.set('tabindex', this.search_field.get('tabindex'));
this.search_field.set('tabindex', -1);
}
this.active_field = false;
this.results_hide();
this.container.removeClass("chzn-container-active");
this.winnow_results_clear();
this.clear_backstroke();
this.show_search_field_default();
this.search_field_scale();
},
activate_field: function(){
if (!this.is_multiple && !this.active_field){
this.search_field.set('tabindex', this.selected_item.get('tabindex'));
this.selected_item.set('tabindex', -1);
}
this.container.addClass('chzn-container-active');
this.active_field = true;
this.search_field.set('value', this.search_field.get('value'));
this.search_field.focus();
},
test_active_click: function(evt){
if (evt.target.getParents('#' + this.container_id).length){
this.active_field = true;
} else {
this.close_field();
}
},
results_build: function(){
this.parsing = true;
this.results_data = this.form_field.select_to_array();
if (this.is_multiple && this.choices > 0){
this.search_choices.getElements("li.search-choice").destroy();
this.choices = 0;
} else if (!this.is_multiple){
this.selected_item.getElements("span").set('text', this.default_text);
if (this.form_field.options.length <= this.options.disable_search_threshold) {
this.container.addClass("chzn-container-single-nosearch");
} else {
this.container.removeClass("chzn-container-single-nosearch");
}
}
var content = '';
this.results_data.each(function(data){
if (data.group){
content += this.result_add_group(data);
} else if (!data.empty){
content += this.result_add_option(data);
if (data.selected && this.is_multiple){
this.choice_build(data);
} else if (data.selected && !this.is_multiple){
this.selected_item.getElements("span").set('text', data.text);
if (this.options.allow_single_deselect) {
this.single_deselect_control_build();
}
}
}
}, this);
this.search_field_disabled();
this.show_search_field_default();
this.search_field_scale();
this.search_results.set('html', content);
this.parsing = false;
},
result_add_group: function(group){
if (!group.disabled){
group.dom_id = this.container_id + "_g_" + group.array_index;
return ''+ group.label + '
';
} else {
return '';
}
},
result_add_option: function(option){
if (!option.disabled){
option.dom_id = this.container_id + "_o_" + option.array_index;
var classes = option.selected && this.is_multiple ? [] : ["active-result"];
if (option.selected){
classes.push('result-selected');
}
if (option.group_array_index != null){
classes.push("group-option");
}
if (option.classes !== "") {
classes.push(option.classes);
}
var style = option.style.cssText !== '' ? ' style="' + option.style + '"' : '';
return ''+ option.html + '';
} else {
return '';
}
},
results_update_field: function(){
this.result_clear_highlight();
this.result_single_selected = null;
this.results_build();
},
result_do_highlight: function(el){
if (el){
this.result_clear_highlight();
this.result_highlight = el;
this.result_highlight.addClass("highlighted");
var maxHeight = parseInt(this.search_results.getStyle("maxHeight"), 10);
var visible_top = this.search_results.getScroll().y,
visible_bottom = maxHeight + visible_top,
high_top = this.result_highlight.getPosition(this.search_results).y + this.search_results.getScroll().y,
high_bottom = high_top + this.result_highlight.getCoordinates().height;
if (high_bottom >= visible_bottom){
this.search_results.scrollTo(0, (high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
} else if (high_top < visible_top){
this.search_results.scrollTo(0, high_top);
}
}
},
result_clear_highlight: function(){
if (this.result_highlight){
this.result_highlight.removeClass("highlighted");
}
this.result_highlight = null;
},
results_toggle: function(){
if (this.results_showing) {
this.results_hide();
} else {
this.results_show();
}
},
results_show: function(){
if (!this.is_multiple){
this.selected_item.addClass("chzn-single-with-drop");
if (this.result_single_selected){
this.result_do_highlight(this.result_single_selected);
}
}
var dd_top = this.is_multiple ? this.container.getCoordinates().height : this.container.getCoordinates().height - 1;
this.dropdown.setStyles({
"top": dd_top,
"left": 0
});
this.results_showing = true;
this.search_field.focus();
this.search_field.set('value', this.search_field.get('value'));
this.winnow_results();
},
results_hide: function(){
if (!this.is_multiple){
this.selected_item.removeClass("chzn-single-with-drop");
}
this.result_clear_highlight();
this.dropdown.setStyle('left', -9000);
this.results_showing = false;
},
set_tab_index: function(el){
if (this.form_field.get('tabindex')){
var ti = this.form_field.get('tabindex');
this.form_field.set('tabindex', -1);
if (this.is_multiple){
this.search_field.set('tabindex', ti);
} else {
this.selected_item.set('tabindex', ti);
this.search_field.set('tabindex', -1);
}
}
},
show_search_field_default: function(){
if (this.is_multiple && this.choices < 1 && !this.active_field){
this.search_field.set('value', this.default_text);
this.search_field.addClass("default");
} else {
this.search_field.set('value', "");
this.search_field.removeClass("default");
}
},
search_results_mouseup: function(evt){
var target = evt.target.hasClass("active-result") ? evt.target : evt.target.getParent(".active-result");
if (target){
this.result_highlight = target;
this.result_select(evt);
}
},
search_results_mouseover: function(evt){
var target = evt.target.hasClass("active-result") ? evt.target : evt.target.getParent(".active-result");
if (target){
this.result_do_highlight(target);
}
},
search_results_mouseout: function(evt){
if (evt.target.hasClass("active-result") || evt.target.getParent('.active-result')){
this.result_clear_highlight();
}
},
choices_click: function(evt){
evt.preventDefault();
if (this.active_field && !(evt.target.hasClass("search-choice") || evt.target.getParent('.search-choice')) && !this.results_showing){
this.results_show();
}
},
choice_build: function(item){
var choice_id = this.container_id + "_c_" + item.array_index;
this.choices += 1;
var el = new Element('li', {'id': choice_id})
.addClass('search-choice')
.set('html', '' + item.html + '');
this.search_container.grab(el, 'before');
el.getElement("a")
.addEvent('click', this.choice_destroy_link_click.bind(this));
},
choice_destroy_link_click: function(evt){
evt.preventDefault();
if (!this.is_disabled) {
this.pending_destroy_click = true;
this.choice_destroy(evt.target);
} else {
evt.stop();
}
},
choice_destroy: function(link){
this.choices -= 1;
this.show_search_field_default();
if (this.is_multiple && this.choices > 0 && this.search_field.value.length < 1){
this.results_hide();
}
this.result_deselect(link.get("rel"));
link.getParent('li').destroy();
},
results_reset: function(evt) {
this.form_field.options[0].selected = true;
this.selected_item.getElement("span").set('text', this.default_text);
this.show_search_field_default();
evt.target.destroy();
this.form_field.fireEvent("change");
if (this.active_field) {
this.results_hide();
}
},
result_select: function(evt){
if (this.result_highlight){
var high = this.result_highlight,
high_id = high.id;
this.result_clear_highlight();
if (this.is_multiple){
this.result_deactivate(high);
} else {
var selected = this.search_results.getElement(".result-selected");
if (selected) selected.removeClass("result-selected");
this.result_single_selected = high;
}
high.addClass("result-selected");
var position = high_id.substr(high_id.lastIndexOf("_") + 1),
item = this.results_data[position];
item.selected = true;
this.form_field.options[item.options_index].selected = true;
if (this.is_multiple){
this.choice_build(item);
} else {
this.selected_item.getElement("span").set('text', item.text);
if (this.options.allow_single_deselect) {
this.single_deselect_control_build();
}
}
if (!this.is_multiple || !evt.control) this.results_hide();
this.search_field.set('value', "");
this.form_field.fireEvent("change");
this.search_field_scale();
}
},
result_activate: function(el){
el.addClass("active-result");
},
result_deactivate: function(el){
el.removeClass("active-result")
},
result_deselect: function(pos){
var result_data = this.results_data[pos];
result_data.selected = false;
this.form_field.options[result_data.options_index].selected = false;
var result = document.id( this.container_id + "_o_" + pos);
result.removeClass("result-selected").addClass("active-result").setStyle('display', 'block');
this.result_clear_highlight();
this.winnow_results();
this.form_field.fireEvent("change");
this.search_field_scale();
},
single_deselect_control_build: function() {
if (this.options.allow_single_deselect && this.selected_item.getElements("abbr").length < 1) {
return this.selected_item.getElement("span").grab(new Element('abbr', {'class': 'search-choice-close'}), 'before');
}
},
results_search: function(evt){
if (this.results_showing){
this.winnow_results();
} else {
this.results_show();
}
},
winnow_results: function(){
this.no_results_clear();
var results = 0,
searchText = this.search_field.get('value') === this.default_text ? "" : new Element('div', {text: this.search_field.get('value').trim()}).get('html'),
regex = new RegExp('^' + searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'),
zregex = new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');
this.results_data.each(function(option){
if (!option.disabled && !option.empty){
if (option.group){
document.id(option.dom_id).setStyle('display', 'none');
} else if (!(this.is_multiple && option.selected)){
var found = false,
result_id = option.dom_id,
result = $(result_id);
if (regex.test(option.html)){
found = true;
results += 1;
} else if (option.html.indexOf(" ") >= 0 || option.html.indexOf("[") === 0){
var parts = option.html.replace(/\[|\]/g, "").split(" ");
if (parts.length){
parts.each(function(part){
if (regex.test(part)){
found = true;
results += 1;
}
});
}
}
if (found){
var text;
if (searchText.length){
var startpos = option.html.search(zregex);
text = option.html.substr(0, startpos + searchText.length) + '' + option.html.substr(startpos + searchText.length);
text = text.substr(0, startpos) + '' + text.substr(startpos);
} else {
text = option.html;
}
result.set('html', text);
this.result_activate(result);
if (option.group_array_index != null){
document.id(this.results_data[option.group_array_index].dom_id).setStyle('display', 'list-item');
}
} else {
if (this.result_highlight && result_id === this.result_highlight.id){
this.result_clear_highlight();
}
this.result_deactivate(result);
}
}
}
}, this);
if (results < 1 && searchText.length){
this.no_results(searchText);
} else {
this.winnow_results_set_highlight();
}
},
winnow_results_clear: function(){
this.search_field.set('value', '');
this.search_results.getElements("li").each(function(li){
li.hasClass("group-result") ? li.setStyle('display', 'block') : !this.is_multiple || !li.hasClass("result-selected") ? this.result_activate(li) : void 0;
}, this);
},
winnow_results_set_highlight: function(){
if (!this.result_highlight){
var selected_results = !this.is_multiple ? this.search_results.getElements(".result-selected") : [];
var do_high = selected_results.length ? selected_results[0] : this.search_results.getElement(".active-result");
if (do_high != null) {
this.result_do_highlight(do_high);
}
}
},
no_results: function(terms){
var no_results_html = new Element('li', {'class': 'no-results'}).set('html', Locale.get('Chosen.noResults')+' ""');
no_results_html.getElement("span").set('html', terms);
this.search_results.grab(no_results_html);
},
no_results_clear: function(){
this.search_results.getElements(".no-results").destroy();
},
keydown_arrow: function(){
if (!this.result_highlight){
var first_active = this.search_results.getElement("li.active-result");
if (first_active){
this.result_do_highlight(first_active);
}
} else if (this.results_showing){
var next_sib = this.result_highlight.getNext("li.active-result");
if (next_sib){
this.result_do_highlight(next_sib);
}
}
if (!this.results_showing){
this.results_show();
}
},
keyup_arrow: function(){
if (!this.results_showing && !this.is_multiple){
this.results_show();
} else if (this.result_highlight){
var prev_sib = this.result_highlight.getPrevious('li.active-result');
if (prev_sib){
this.result_do_highlight(prev_sib);
} else {
if (this.choices > 0){
this.results_hide();
}
this.result_clear_highlight();
}
}
},
keydown_backstroke: function(){
if (this.pending_backstroke){
this.choice_destroy(this.pending_backstroke.getElement("a"));
this.clear_backstroke();
} else {
this.pending_backstroke = this.search_choices.getLast("li.search-choice");
this.pending_backstroke.addClass("search-choice-focus");
}
},
clear_backstroke: function(){
if (this.pending_backstroke){
this.pending_backstroke.removeClass("search-choice-focus");
}
this.pending_backstroke = null;
},
keyup_checker: function(evt){
this.search_field_scale();
switch (evt.key){
case 'backspace':
if (this.is_multiple && this.backstroke_length < 1 && this.choices > 0){
this.keydown_backstroke();
}else if (!this.pending_backstroke){
this.result_clear_highlight();
this.results_search();
}
break;
case 'enter':
evt.preventDefault();
if (this.results_showing){
this.result_select(evt);
}
break;
case 'esc':
if (this.results_showing) {
this.results_hide();
}
break;
case 'tab':
case 'up':
case 'down':
case 'shift':
case 'ctrl':
break;
default:
this.results_search();
}
},
keydown_checker: function(evt){
this.search_field_scale();
if (evt.key !== 'backspace' && this.pending_backstroke){
this.clear_backstroke();
}
switch(evt.key){
case 'backspace':
this.backstroke_length = this.search_field.value.length;
break;
case 'tab':
if (this.results_showing && !this.is_multiple) {
this.result_select(evt);
}
this.mouse_on_container = false;
break;
case 'enter':
evt.preventDefault();
break;
case 'up':
evt.preventDefault();
this.keyup_arrow();
break;
case 'down':
this.keydown_arrow();
break;
}
},
search_field_scale: function(){
if (this.is_multiple){
var h = 0,
w = 0,
style_block = {
position: 'absolute',
left: '-1000px',
top: '-1000px',
display: 'none'
},
styles = this.search_field.getStyles('font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing');
Object.merge(style_block, styles);
var div = new Element('div', {
'styles': style_block
});
div.set('text', this.search_field.get('value'));
$(document.body).grab(div);
w = div.getDimensions().width + 25;
div.destroy();
if (w > this.f_width - 10) {
w = this.f_width - 10;
}
this.search_field.setStyle('width', w);
var dd_top = this.container.getCoordinates().height;
this.dropdown.setStyle('top', dd_top);
}
}
});
Element.implement({
get_side_border_padding: function(){
var styles = this.getStyles('padding-left', 'padding-right', 'border-left-width', 'border-right-width'),
notNull = Object.filter(styles, function(value){
return (typeof(value) == 'string');
}),
mapped = Object.map(notNull, function(s){ return s.toInt();}),
array = Object.values(mapped),
result = 0, l = array.length;
if (l){
while (l--) result += array[l];
}
return result;
},
select_to_array: function(){
var parser = new SelectParser();
this.getChildren().each(function(child){
parser.add_node(child);
});
return parser.parsed;
}
});
var SelectParser = new Class({
options_index: 0,
parsed: [],
add_node: function(child){
if (child.nodeName.toUpperCase() === "OPTGROUP"){
this.add_group(child);
} else {
this.add_option(child);
}
},
add_group: function(group){
var group_position = this.parsed.length;
this.parsed.push({
array_index: group_position,
group: true,
label: group.label,
children: 0,
disabled: group.disabled
});
group.getChildren().each(function(option){
this.add_option(option, group_position, group.disabled);
}, this);
},
add_option: function(option, group_position, group_disabled){
if (option.nodeName.toUpperCase() === "OPTION") {
if (option.text !== ""){
if (group_position != null) {
this.parsed[group_position].children += 1;
}
this.parsed.push({
array_index: this.parsed.length,
options_index: this.options_index,
value: option.get("value"),
text: option.get("text").trim(),
html: option.get("html"),
selected: option.selected,
disabled: group_disabled === true ? group_disabled : option.disabled,
group_array_index: group_position,
classes: option.className,
style: option.style.cssText
});
} else {
this.parsed.push({
array_index: this.parsed.length,
options_index: this.options_index,
empty: true
});
}
this.options_index += 1;
}
}
});