/**
* Copyright (c) 2009 Anders Ekdahl (http://coffeescripter.com/)
* var select = $('.editable-select:first');
* var instances = select.editableSelectInstances();
* instances[0].addOption('Germany', 'value added programmatically');
*
* Version: 1.3.2
* yingxian modify
* Demo and documentation: http://coffeescripter.com/code/editable-select/
*/
(function($) {
var instances = [];
$.fn.editableSelect = function(options) {
var defaults = { bg_iframe: false,
onSelect: false,
items_then_scroll: 10,
case_sensitive: false
};
var settings = $.extend(defaults, options);
// Only do bg_iframe for browsers that need it
/*if(settings.bg_iframe && !$.browser.msie) {
settings.bg_iframe = false;
};*/
var instance = false;
$(this).each(function() {
var i = instances.length;
if($(this).data('editable-selecter') !== null) {
instances[i] = new EditableSelect(this, settings);
$(this).data('editable-selecter', i);
};
});
return $(this);
};
$.fn.editableSelectInstances = function() {
var ret = [];
$(this).each(function() {
if($(this).data('editable-selecter') !== null) {
ret[ret.length] = instances[$(this).data('editable-selecter')];
};
});
return ret;
};
var EditableSelect = function(select, settings) {
this.init(select, settings);
};
EditableSelect.prototype = {
settings: false,
text: false,
select: false,
select_width: 0,
wrapper: false,
list_item_height: 20,
list_height: 0,
list_is_visible: false,
hide_on_blur_timeout: false,
bg_iframe: false,
current_value: '',
init: function(select, settings) {
this.settings = settings;
this.wrapper = $(document.createElement('div'));
this.wrapper.addClass('editable-select-options');
this.select = $(select);
var name = this.select.attr('name');
if(!name) {
name = 'editable-select'+ instances.length;
};
var id = this.select.attr('id');
if(!id) {
id = 'editable-select'+ instances.length;
};
this.text = $('');
this.text_submit = $('');
this.text.attr('name', name + "_sele");
this.text_submit.attr('name', name);
this.text.data('editable-selecter', this.select.data('editable-selecter'));
this.text_submit.data('editable-selecter', this.select.data('editable-selecter'));
// Because we don't want the value of the select when the form
// is submitted
this.select.attr('disabled', 'disabled');
this.text[0].className = this.select[0].className;
this.text_submit[0].className = this.select[0].className;
this.text.attr('id', id + "_sele");
this.text_submit.attr('id', id);
this.wrapper.attr('id',id+'_editable-select-options');
this.text.attr('autocomplete', 'off');
this.text.attr('autocomplete', 'off');
this.text.addClass('editable-select');
this.text_submit.addClass('editable-select');
this.select.attr('id', id +'_hidden_select');
this.select.attr('name', name +'_hidden_select');
this.select.after(this.text);
this.select.after(this.text_submit);
if(this.select.css('display') == 'none') {
//this.text.css('display', 'none');
this.text_submit.css('display', 'none');
}
if(this.select.css('visibility') == 'hidden') {
//this.text.css('visibility', 'visibility');
this.text_submit.css('visibility', 'visibility');
}
// Set to hidden, because we want to call .show()
// on it to get it's width but not having it display
// on the screen
this.select.css('visibility', 'hidden');
this.select.hide();
this.initInputEvents(this.text);
this.duplicateOptions();
this.setWidths();
$(document.body).append(this.wrapper);
if(this.settings.bg_iframe) {
this.createBackgroundIframe();
};
if(typeof this.settings.success == "function") {
this.settings.success.call(this,this.text_submit[0]);
};
},
/**
* Take the select lists options and
* populate an unordered list with them
*/
duplicateOptions: function() {
var context = this,text,val;
var option_list = $(document.createElement('ul'));
this.wrapper.empty();
this.wrapper.append(option_list);
var options = this.select.find('option');
this.dataList = [];
options.each(function(i) {
text = $(this).text();
val = $(this).val();
if($(this).attr('selected') /*|| i == 0*/) {
context.text.val(text);
context.text_submit.val(val);
context.current_value = text;
};
if(context.trim(text) != "") context.dataList.push(text);
var li = $('
'+ text +'');
li.hide();
context.initListItemEvents(li);
option_list.append(li);
});
this.setWidths();
this.checkScroll();
},in_array:function(e,arr)
{
for(i=0,len = arr.length;i < len;i++)
{
if(arr[i] == e)
{
return true;
}
}
return false;
},trim:function(str){
return typeof str == "string" ? str.replace(/^\s*|\s*$/g,"") : str;
},
/**
* Check if the list has enough items to display a scroll
*/
checkScroll: function() {
var options = this.wrapper.find('li');
if(options.length > this.settings.items_then_scroll) {
this.list_height = this.list_item_height * this.settings.items_then_scroll;
this.wrapper.css('height', this.list_height +'px');
this.wrapper.css('overflow', 'auto');
} else {
this.wrapper.css('height', 'auto');
this.wrapper.css('overflow', 'visible');
};
},
addOption: function(value,text) {
var li = $(''+ text +'');
var option = $('');
this.select.append(option);
this.initListItemEvents(li);
this.wrapper.find('ul').append(li);
this.setWidths();
this.checkScroll();
},
/**
* Init the different events on the input element
*/
initInputEvents: function(text) {
var context = this;
var timer = false;
$(document.body).click(
function() {
context.clearSelectedListItem();
context.hideList();
}
);
text.blur(
function(e) {
var val = context.trim(this.value);
var isInArr = context.in_array(val,context.dataList);
if( val == "")
{
context.text_submit.val("");
}else if(val != "" && !isInArr)
{
context.text_submit.val("-1");
}
var list_item = typeof context.settings.onSelect == 'function' && isInArr ? context.findItem(val) : null;
if(typeof context.settings.onSelect == 'function' && list_item != null) {
context.text.val(list_item.text());
context.text_submit.val(list_item.attr("value"));
context.current_value = context.text.val();
context.settings.onSelect.call(context, list_item,context.text_submit[0]);
};
context.hideList();
e.preventDefault();
e.stopPropagation();
}
);
text.focus(
function(e) {
// Can't use the blur event to hide the list, because the blur event
// is fired in some browsers when you scroll the list
context.showList();
context.highlightSelected();
e.stopPropagation();
}
).click(
function(e) {
e.stopPropagation();
context.showList();
context.highlightSelected();
}
).keydown(
// Capture key events so the user can navigate through the list
function(e) {
switch(e.keyCode) {
// Down
case 40:
if(!context.listIsVisible()) {
context.showList();
context.highlightSelected();
} else {
e.preventDefault();
context.selectNewListItem('down');
};
break;
// Up
case 38:
e.preventDefault();
context.selectNewListItem('up');
break;
// Tab
case 9:
context.pickListItem(context.selectedListItem());
break;
// Esc
case 27:
e.preventDefault();
context.hideList();
return false;
break;
// Enter, prevent form submission
case 13:
e.preventDefault();
context.pickListItem(context.selectedListItem());
return false;
};
}
).keyup(
function(e) {
// Prevent lots of calls if it's a fast typer
if(timer !== false) {
clearTimeout(timer);
timer = false;
};
timer = setTimeout(
function() {
// If the user types in a value, select it if it's in the list
if(context.text.val() != context.current_value) {
context.current_value = context.text.val();
context.highlightSelected();
//context.showList();
};
},
200
);
// if input text change,list show.yingxian add hack by 2013-09-08
(e.keyCode == 13) ? context.hideList() : context.showList();
e.stopPropagation();
}
).keypress(
function(e) {
if(e.keyCode == 13) {
// Enter, prevent form submission
e.preventDefault();
return false;
};
}
);
},
initListItemEvents: function(list_item) {
var context = this;
list_item.mouseover(
function() {
context.clearSelectedListItem();
context.selectListItem(list_item);
}
).mousedown(
// Needs to be mousedown and not click, since the inputs blur events
// fires before the list items click event
function(e) {
e.stopPropagation();
context.pickListItem(context.selectedListItem());
}
);
},
selectNewListItem: function(direction) {
var li = this.selectedListItem();
if(!li.length) {
li = this.selectFirstListItem();
};
if(direction == 'down') {
var sib = this.selectNextItem(li);
} else {
var sib = this.selectPrevItem(li);
};
if(sib.length) {
this.selectListItem(sib);
this.scrollToListItem(sib);
this.unselectListItem(li);
};
},selectNextItem:function(el){
var e = el.next();
if(e && e[0].display == "none")
{
return el;
}
return e;
},selectPrevItem:function(el){
var e = el.prev();
if(e && e[0].display == "none")
{
return el;
}
return e;
},
selectListItem: function(list_item) {
this.clearSelectedListItem();
list_item.addClass('selected');
},
selectFirstListItem: function() {
this.clearSelectedListItem();
var first = this.wrapper.find('li:first');
//this.wrapper.find('li').hide();
first.addClass('selected');
//first.show();
return first;
},
unselectListItem: function(list_item) {
list_item.removeClass('selected');
},
selectedListItem: function() {
return this.wrapper.find('li.selected');
},
clearSelectedListItem: function() {
this.wrapper.find('li.selected').removeClass('selected');
},
/**
* The difference between this method and selectListItem
* is that this method also changes the text field and
* then hides the list
*/
pickListItem: function(list_item) {
if(list_item.length) {
this.text.val(list_item.text());
this.text_submit.val(list_item.attr("value"));
this.current_value = this.text.val();
};
if(typeof this.settings.onSelect == 'function') {
this.settings.onSelect.call(this, list_item,this.text_submit[0]);
};
this.hideList();
},
listIsVisible: function() {
return this.list_is_visible;
},
showList: function() {
this.positionElements();
this.setWidths();
this.wrapper.show();
this.hideOtherLists();
this.list_is_visible = true;
if(this.settings.bg_iframe) {
this.bg_iframe.show();
};
},
findItem: function(text1) {
var context = this;
var current_value = context.trim(text1);
var list_items = context.wrapper.find('li');
var best_candiate = false;
var value_found = false;
list_items.each(
function() {
var text = context.trim($(this).text());
if(!value_found) {
if(!context.settings.case_sensitive) {
text = text.toLowerCase();
};
if(text == current_value) {
value_found = true;
best_candiate = $(this);
return false;
}
};
}
);
if(value_found) {
return best_candiate;
}else if(!best_candiate && !value_found) {
return null;
};
},
highlightSelected: function() {
var context = this;
var current_value = context.trim(this.text.val());
if(current_value.length < 0) {
if(highlight_first) {
this.selectFirstListItem();
};
return;
};
var list_items = this.wrapper.find('li');
if(current_value.length == 0) {
list_items.show();
this.selectFirstListItem();
return;
};
if(!context.settings.case_sensitive) {
current_value = current_value.toLowerCase();
};
var best_candiate = false;
var value_found = false;
list_items.each(
function() {
var text = $(this).text();
if(!value_found) {
if(!context.settings.case_sensitive) {
text = text.toLowerCase();
};
if(text == current_value) {
value_found = true;
context.clearSelectedListItem();
context.selectListItem($(this));
context.scrollToListItem($(this));
//return false;
} else if(text.search(current_value) > -1 && !best_candiate) {
// Can't do return false here, since we still need to iterate over
// all list items to see if there is an exact match
best_candiate = $(this);
};
};
if(context.settings.isFilter && text.search(current_value) > -1 && current_value != "")
{
$(this).show();
}else if(context.settings.isFilter)
{
$(this).hide();
}
}
);
if(best_candiate && !value_found) {
context.clearSelectedListItem();
context.selectListItem(best_candiate);
context.scrollToListItem(best_candiate);
}else if(!best_candiate && !value_found) {
this.selectFirstListItem();
};
},
scrollToListItem: function(list_item) {
if(this.list_height) {
this.wrapper.scrollTop(list_item[0].offsetTop - (this.list_height / 2));
};
},
hideList: function() {
this.wrapper.hide();
this.list_is_visible = false;
if(this.settings.bg_iframe) {
this.bg_iframe.hide();
};
},
hideOtherLists: function() {
for(var i = 0; i < instances.length; i++) {
if(i != this.select.data('editable-selecter')) {
instances[i].hideList();
};
};
},
positionElements: function() {
var offset = this.text.offset();
offset = { top: offset.top, left: offset.left };
offset.top += this.text[0].offsetHeight;
this.wrapper.css({top: offset.top +'px', left: offset.left +'px'});
// Need to do this in order to get the list item height
this.wrapper.css('visibility', 'hidden');
this.wrapper.show();
this.list_item_height = this.wrapper.find('li')[0] ? this.wrapper.find('li')[0].offsetHeight : 0;
this.wrapper.css('visibility', 'visible');
this.wrapper.hide();
},
setWidths: function() {
// The text input has a right margin because of the background arrow image
// so we need to remove that from the width
this.select.show();
var width = this.select.width() + 2 + 20;
this.select.hide();
var padding_right = parseInt(this.text.css('padding-right').replace(/px/, ''), 10);
this.text.width(width - padding_right + 18);
this.wrapper.width(width + 2 + 20);
if(this.bg_iframe) {
this.bg_iframe.width(width + 4 + 20);
};
},
createBackgroundIframe: function() {
var bg_iframe = $('');
$(document.body).append(bg_iframe);
bg_iframe.width(this.select.width() + 2);
bg_iframe.height(this.wrapper.height());
bg_iframe.css({top: this.wrapper.css('top'), left: this.wrapper.css('left')});
this.bg_iframe = bg_iframe;
}
};
})(jQuery);