| 16 |
- |
1 |
/**
|
|
|
2 |
* jQuery Editable Select
|
|
|
3 |
* Indri Muska <indrimuska@gmail.com>
|
|
|
4 |
*
|
|
|
5 |
* Source on GitHub @ https://github.com/indrimuska/jquery-editable-select
|
|
|
6 |
*/
|
|
|
7 |
|
|
|
8 |
+(function ($) {
|
|
|
9 |
// jQuery Editable Select
|
|
|
10 |
EditableSelect = function (select, options) {
|
|
|
11 |
var that = this;
|
|
|
12 |
|
|
|
13 |
this.options = options;
|
|
|
14 |
this.$select = $(select);
|
|
|
15 |
this.$input = $('<input type="text" autocomplete="off">');
|
|
|
16 |
this.$list = $('<ul class="es-list">');
|
|
|
17 |
this.utility = new EditableSelectUtility(this);
|
|
|
18 |
|
|
|
19 |
if (['focus', 'manual'].indexOf(this.options.trigger) < 0) this.options.trigger = 'focus';
|
|
|
20 |
if (['default', 'fade', 'slide'].indexOf(this.options.effects) < 0) this.options.effects = 'default';
|
|
|
21 |
if (isNaN(this.options.duration) && ['fast', 'slow'].indexOf(this.options.duration) < 0) this.options.duration = 'fast';
|
|
|
22 |
|
|
|
23 |
// create text input
|
|
|
24 |
this.$select.replaceWith(this.$input);
|
|
|
25 |
this.$list.appendTo(this.options.appendTo || this.$input.parent());
|
|
|
26 |
|
|
|
27 |
// initalization
|
|
|
28 |
this.utility.initialize();
|
|
|
29 |
this.utility.initializeList();
|
|
|
30 |
this.utility.initializeInput();
|
|
|
31 |
this.utility.trigger('created');
|
|
|
32 |
}
|
|
|
33 |
EditableSelect.DEFAULTS = { filter: true, effects: 'default', duration: 'fast', trigger: 'focus' };
|
|
|
34 |
EditableSelect.prototype.filter = function () {
|
|
|
35 |
var hiddens = 0;
|
|
|
36 |
var search = this.$input.val().toLowerCase().trim();
|
|
|
37 |
|
|
|
38 |
this.$list.find('li').addClass('es-visible').show();
|
|
|
39 |
if (this.options.filter) {
|
|
|
40 |
hiddens = this.$list.find('li').filter(function (i, li) { return $(li).text().toLowerCase().indexOf(search) < 0; }).hide().removeClass('es-visible').length;
|
|
|
41 |
if (this.$list.find('li').length == hiddens) this.hide();
|
|
|
42 |
}
|
|
|
43 |
};
|
|
|
44 |
EditableSelect.prototype.show = function () {
|
|
|
45 |
this.$list.css({
|
|
|
46 |
top: this.$input.position().top + this.$input.outerHeight() - 1,
|
|
|
47 |
left: this.$input.position().left,
|
|
|
48 |
width: this.$input.outerWidth()
|
|
|
49 |
});
|
|
|
50 |
|
|
|
51 |
if (!this.$list.is(':visible') && this.$list.find('li.es-visible').length > 0) {
|
|
|
52 |
var fns = { default: 'show', fade: 'fadeIn', slide: 'slideDown' };
|
|
|
53 |
var fn = fns[this.options.effects];
|
|
|
54 |
|
|
|
55 |
this.utility.trigger('show');
|
|
|
56 |
this.$input.addClass('open');
|
|
|
57 |
this.$list[fn](this.options.duration, $.proxy(this.utility.trigger, this.utility, 'shown'));
|
|
|
58 |
}
|
|
|
59 |
};
|
|
|
60 |
EditableSelect.prototype.hide = function () {
|
|
|
61 |
var fns = { default: 'hide', fade: 'fadeOut', slide: 'slideUp' };
|
|
|
62 |
var fn = fns[this.options.effects];
|
|
|
63 |
|
|
|
64 |
this.utility.trigger('hide');
|
|
|
65 |
this.$input.removeClass('open');
|
|
|
66 |
this.$list[fn](this.options.duration, $.proxy(this.utility.trigger, this.utility, 'hidden'));
|
|
|
67 |
};
|
|
|
68 |
EditableSelect.prototype.select = function ($li) {
|
|
|
69 |
if (!this.$list.has($li) || !$li.is('li.es-visible:not([disabled])')) return;
|
|
|
70 |
this.$input.val($li.text());
|
|
|
71 |
if (this.options.filter) this.hide();
|
|
|
72 |
this.filter();
|
|
|
73 |
this.utility.trigger('select', $li);
|
|
|
74 |
};
|
|
|
75 |
EditableSelect.prototype.add = function (text, index, attrs, data) {
|
|
|
76 |
var $li = $('<li>').html(text);
|
|
|
77 |
var $option = $('<option>').text(text);
|
|
|
78 |
var last = this.$list.find('li').length;
|
|
|
79 |
|
|
|
80 |
if (isNaN(index)) index = last;
|
|
|
81 |
else index = Math.min(Math.max(0, index), last);
|
|
|
82 |
if (index == 0) {
|
|
|
83 |
this.$list.prepend($li);
|
|
|
84 |
this.$select.prepend($option);
|
|
|
85 |
} else {
|
|
|
86 |
this.$list.find('li').eq(index - 1).after($li);
|
|
|
87 |
this.$select.find('option').eq(index - 1).after($option);
|
|
|
88 |
}
|
|
|
89 |
this.utility.setAttributes($li, attrs, data);
|
|
|
90 |
this.utility.setAttributes($option, attrs, data);
|
|
|
91 |
this.filter();
|
|
|
92 |
};
|
|
|
93 |
EditableSelect.prototype.remove = function (index) {
|
|
|
94 |
var last = this.$list.find('li').length;
|
|
|
95 |
|
|
|
96 |
if (isNaN(index)) index = last;
|
|
|
97 |
else index = Math.min(Math.max(0, index), last - 1);
|
|
|
98 |
this.$list.find('li').eq(index).remove();
|
|
|
99 |
this.$select.find('option').eq(index).remove();
|
|
|
100 |
this.filter();
|
|
|
101 |
};
|
|
|
102 |
EditableSelect.prototype.clear = function () {
|
|
|
103 |
this.$list.find('li').remove();
|
|
|
104 |
this.$select.find('option').remove();
|
|
|
105 |
this.filter();
|
|
|
106 |
};
|
|
|
107 |
EditableSelect.prototype.destroy = function () {
|
|
|
108 |
this.$list.off('mousemove mousedown mouseup');
|
|
|
109 |
this.$input.off('focus blur input keydown');
|
|
|
110 |
this.$input.replaceWith(this.$select);
|
|
|
111 |
this.$list.remove();
|
|
|
112 |
this.$select.removeData('editable-select');
|
|
|
113 |
};
|
|
|
114 |
|
|
|
115 |
// Utility
|
|
|
116 |
EditableSelectUtility = function (es) {
|
|
|
117 |
this.es = es;
|
|
|
118 |
}
|
|
|
119 |
EditableSelectUtility.prototype.initialize = function () {
|
|
|
120 |
var that = this;
|
|
|
121 |
that.setAttributes(that.es.$input, that.es.$select[0].attributes, that.es.$select.data());
|
|
|
122 |
that.es.$input.addClass('es-input').data('editable-select', that.es);
|
|
|
123 |
that.es.$select.find('option').each(function (i, option) {
|
|
|
124 |
var $option = $(option).remove();
|
|
|
125 |
that.es.add($option.text(), i, option.attributes, $option.data());
|
|
|
126 |
if ($option.attr('selected')) that.es.$input.val($option.text());
|
|
|
127 |
});
|
|
|
128 |
that.es.filter();
|
|
|
129 |
};
|
|
|
130 |
EditableSelectUtility.prototype.initializeList = function () {
|
|
|
131 |
var that = this;
|
|
|
132 |
that.es.$list
|
|
|
133 |
.on('mousemove', 'li:not([disabled])', function () {
|
|
|
134 |
that.es.$list.find('.selected').removeClass('selected');
|
|
|
135 |
$(this).addClass('selected');
|
|
|
136 |
})
|
|
|
137 |
.on('mousedown', 'li', function (e) {
|
|
|
138 |
if ($(this).is('[disabled]')) e.preventDefault();
|
|
|
139 |
else that.es.select($(this));
|
|
|
140 |
})
|
|
|
141 |
.on('mouseup', function () {
|
|
|
142 |
that.es.$list.find('li.selected').removeClass('selected');
|
|
|
143 |
});
|
|
|
144 |
};
|
|
|
145 |
EditableSelectUtility.prototype.initializeInput = function () {
|
|
|
146 |
var that = this;
|
|
|
147 |
switch (this.es.options.trigger) {
|
|
|
148 |
default:
|
|
|
149 |
case 'focus':
|
|
|
150 |
that.es.$input
|
|
|
151 |
.on('focus', $.proxy(that.es.show, that.es))
|
|
|
152 |
.on('blur', $.proxy(that.es.hide, that.es));
|
|
|
153 |
break;
|
|
|
154 |
case 'manual':
|
|
|
155 |
break;
|
|
|
156 |
}
|
|
|
157 |
that.es.$input.on('input keydown', function (e) {
|
|
|
158 |
switch (e.keyCode) {
|
|
|
159 |
case 38: // Up
|
|
|
160 |
var visibles = that.es.$list.find('li.es-visible:not([disabled])');
|
|
|
161 |
var selectedIndex = visibles.index(visibles.filter('li.selected'));
|
|
|
162 |
that.highlight(selectedIndex - 1);
|
|
|
163 |
e.preventDefault();
|
|
|
164 |
break;
|
|
|
165 |
case 40: // Down
|
|
|
166 |
var visibles = that.es.$list.find('li.es-visible:not([disabled])');
|
|
|
167 |
var selectedIndex = visibles.index(visibles.filter('li.selected'));
|
|
|
168 |
that.highlight(selectedIndex + 1);
|
|
|
169 |
e.preventDefault();
|
|
|
170 |
break;
|
|
|
171 |
case 13: // Enter
|
|
|
172 |
if (that.es.$list.is(':visible')) {
|
|
|
173 |
that.es.select(that.es.$list.find('li.selected'));
|
|
|
174 |
e.preventDefault();
|
|
|
175 |
}
|
|
|
176 |
break;
|
|
|
177 |
case 9: // Tab
|
|
|
178 |
case 27: // Esc
|
|
|
179 |
that.es.hide();
|
|
|
180 |
break;
|
|
|
181 |
default:
|
|
|
182 |
that.es.filter();
|
|
|
183 |
that.highlight(0);
|
|
|
184 |
break;
|
|
|
185 |
}
|
|
|
186 |
});
|
|
|
187 |
};
|
|
|
188 |
EditableSelectUtility.prototype.highlight = function (index) {
|
|
|
189 |
var that = this;
|
|
|
190 |
that.es.show();
|
|
|
191 |
setTimeout(function () {
|
|
|
192 |
var visibles = that.es.$list.find('li.es-visible');
|
|
|
193 |
var oldSelected = that.es.$list.find('li.selected').removeClass('selected');
|
|
|
194 |
var oldSelectedIndex = visibles.index(oldSelected);
|
|
|
195 |
|
|
|
196 |
if (visibles.length > 0) {
|
|
|
197 |
var selectedIndex = (visibles.length + index) % visibles.length;
|
|
|
198 |
var selected = visibles.eq(selectedIndex);
|
|
|
199 |
var top = selected.position().top;
|
|
|
200 |
|
|
|
201 |
selected.addClass('selected');
|
|
|
202 |
if (selectedIndex < oldSelectedIndex && top < 0)
|
|
|
203 |
that.es.$list.scrollTop(that.es.$list.scrollTop() + top);
|
|
|
204 |
if (selectedIndex > oldSelectedIndex && top + selected.outerHeight() > that.es.$list.outerHeight())
|
|
|
205 |
that.es.$list.scrollTop(that.es.$list.scrollTop() + selected.outerHeight() + 2 * (top - that.es.$list.outerHeight()));
|
|
|
206 |
}
|
|
|
207 |
});
|
|
|
208 |
};
|
|
|
209 |
EditableSelectUtility.prototype.setAttributes = function ($element, attrs, data) {
|
|
|
210 |
$.each(attrs || {}, function (i, attr) { $element.attr(attr.name, attr.value); });
|
|
|
211 |
$element.data(data);
|
|
|
212 |
};
|
|
|
213 |
EditableSelectUtility.prototype.trigger = function (event) {
|
|
|
214 |
var params = Array.prototype.slice.call(arguments, 1);
|
|
|
215 |
var args = [event + '.editable-select'];
|
|
|
216 |
args.push(params);
|
|
|
217 |
this.es.$select.trigger.apply(this.es.$select, args);
|
|
|
218 |
this.es.$input.trigger.apply(this.es.$input, args);
|
|
|
219 |
};
|
|
|
220 |
|
|
|
221 |
// Plugin
|
|
|
222 |
Plugin = function (option) {
|
|
|
223 |
var args = Array.prototype.slice.call(arguments, 1);
|
|
|
224 |
return this.each(function () {
|
|
|
225 |
var $this = $(this);
|
|
|
226 |
var data = $this.data('editable-select');
|
|
|
227 |
var options = $.extend({}, EditableSelect.DEFAULTS, $this.data(), typeof option == 'object' && option);
|
|
|
228 |
|
|
|
229 |
if (!data) data = new EditableSelect(this, options);
|
|
|
230 |
if (typeof option == 'string') data[option].apply(data, args);
|
|
|
231 |
});
|
|
|
232 |
}
|
|
|
233 |
$.fn.editableSelect = Plugin;
|
|
|
234 |
$.fn.editableSelect.Constructor = EditableSelect;
|
|
|
235 |
|
|
|
236 |
})(jQuery);
|