Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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);