1
|
|
|
/**
|
2
|
|
|
* JQuery Searchable DropDown Plugin
|
3
|
|
|
*
|
4
|
|
|
* @required jQuery 1.3.x or above
|
5
|
|
|
* @author Sascha Woo <[email protected]>
|
6
|
|
|
* $Id: jquery.searchabledropdown.js 53 2012-11-22 08:48:14Z xhaggi $
|
7
|
|
|
*
|
8
|
|
|
* Copyright (c) 2012 xhaggi
|
9
|
|
|
* https://sourceforge.net/projects/jsearchdropdown/
|
10
|
|
|
*
|
11
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
12
|
|
|
* a copy of this software and associated documentation files (the
|
13
|
|
|
* "Software"), to deal in the Software without restriction, including
|
14
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
15
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
16
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
17
|
|
|
* the following conditions:
|
18
|
|
|
*
|
19
|
|
|
* The above copyright notice and this permission notice shall be
|
20
|
|
|
* included in all copies or substantial portions of the Software.
|
21
|
|
|
*
|
22
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
23
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
24
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
25
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
26
|
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
27
|
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
28
|
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
29
|
|
|
*/
|
30
|
|
|
(function($) {
|
31
|
|
|
|
32
|
|
|
// register plugin
|
33
|
|
|
var plugin = register("searchable");
|
34
|
|
|
|
35
|
|
|
// defaults
|
36
|
|
|
plugin.defaults = {
|
37
|
|
|
maxListSize: 100,
|
38
|
|
|
maxMultiMatch: 50,
|
39
|
|
|
exactMatch: false,
|
40
|
|
|
wildcards: true,
|
41
|
|
|
ignoreCase: true,
|
42
|
|
|
warnMultiMatch: "top {0} matches ...",
|
43
|
|
|
warnNoMatch: "no matches ...",
|
44
|
|
|
latency: 200,
|
45
|
|
|
zIndex: "auto"
|
46
|
|
|
};
|
47
|
|
|
|
48
|
|
|
/**
|
49
|
|
|
* Execute function
|
50
|
|
|
* element-specific code here
|
51
|
|
|
* @param {Options} settings Settings
|
52
|
|
|
*/
|
53
|
|
|
plugin.execute = function(settings, zindex) {
|
54
|
|
|
|
55
|
|
|
var timer = null;
|
56
|
|
|
var searchCache = null;
|
57
|
|
|
var search = null;
|
58
|
|
|
|
59
|
|
|
// do not attach on IE6 or lower
|
60
|
|
|
if ($.browser.msie && parseInt(jQuery.browser.version) < 7)
|
61
|
|
|
return this;
|
62
|
|
|
|
63
|
|
|
// only active select elements with drop down capability
|
64
|
|
|
if (this.nodeName != "SELECT" || this.size > 1)
|
65
|
|
|
return this;
|
66
|
|
|
|
67
|
|
|
var self = $(this);
|
68
|
|
|
var storage = {index: -1, options: null}; // holds data for restoring
|
69
|
|
|
var idxAttr = "lang";
|
70
|
|
|
var enabled = false;
|
71
|
|
|
|
72
|
|
|
// detecting chrome
|
73
|
|
|
$.browser.chrome = /chrome/.test(navigator.userAgent.toLowerCase());
|
|
|
|
|
74
|
|
|
if($.browser.chrome) $.browser.safari = false;
|
75
|
|
|
|
76
|
|
|
// lets you override the options
|
77
|
|
|
// inside the dom objects class property
|
78
|
|
|
// requires the jQuery metadata plugin
|
79
|
|
|
// <div class="hello {color: 'red'}">ddd</div>
|
80
|
|
|
if ($.meta){
|
81
|
|
|
settings = $.extend({}, options, self.data());
|
|
|
|
|
82
|
|
|
}
|
83
|
|
|
|
84
|
|
|
// objects
|
85
|
|
|
var wrapper = $("<div/>");
|
86
|
|
|
var overlay = $("<div/>");
|
87
|
|
|
var input = $("<input/>");
|
88
|
|
|
var selector = $("<select/>");
|
89
|
|
|
|
90
|
|
|
// matching option items
|
91
|
|
|
var topMatchItem = $("<option>"+settings.warnMultiMatch.replace(/\{0\}/g, settings.maxMultiMatch)+"</option>").attr("disabled", "true");
|
92
|
|
|
var noMatchItem = $("<option>"+settings.warnNoMatch+"</option>").attr("disabled", "true");
|
93
|
|
|
|
94
|
|
|
|
95
|
|
|
var selectorHelper = {
|
96
|
|
|
/**
|
97
|
|
|
* Return DOM options of selector element
|
98
|
|
|
*/
|
99
|
|
|
option: function(idx) {
|
100
|
|
|
return $(selector.get(0).options[idx]);
|
101
|
|
|
},
|
102
|
|
|
/**
|
103
|
|
|
* Returns the selected item of selector element
|
104
|
|
|
*/
|
105
|
|
|
selected: function() {
|
106
|
|
|
return selector.find(":selected");
|
107
|
|
|
},
|
108
|
|
|
/**
|
109
|
|
|
* Get or Set the selectedIndex of the selector element
|
110
|
|
|
* @param {int} idx SelectedIndex
|
111
|
|
|
*/
|
112
|
|
|
selectedIndex: function(idx) {
|
113
|
|
|
if(idx > -1)
|
114
|
|
|
selector.get(0).selectedIndex = idx;
|
115
|
|
|
return selector.get(0).selectedIndex;
|
116
|
|
|
},
|
117
|
|
|
/**
|
118
|
|
|
* Resize selector depends on the parameter size
|
119
|
|
|
* @param {Number} size Size
|
120
|
|
|
*/
|
121
|
|
|
size: function(size) {
|
122
|
|
|
selector.attr("size", Math.max(2, Math.min(size, 20)));
|
123
|
|
|
},
|
124
|
|
|
/**
|
125
|
|
|
* Reset the entries, which can be choose to it's inital state depends on selectedIndex and maxMultiMatch
|
126
|
|
|
*/
|
127
|
|
|
reset: function() {
|
128
|
|
|
// return if selector has data and stored index equal selectedIndex of source select element
|
129
|
|
|
if((self.get(0).selectedIndex-1) == self.data("index"))
|
130
|
|
|
return;
|
131
|
|
|
|
132
|
|
|
// calc start and length of iteration
|
133
|
|
|
var idx = self.get(0).selectedIndex;
|
134
|
|
|
var len = self.get(0).length;
|
135
|
|
|
var mc = Math.floor(settings.maxMultiMatch / 2);
|
136
|
|
|
var begin = Math.max(1, (idx - mc));
|
137
|
|
|
var end = Math.min(len, Math.max(settings.maxMultiMatch, (idx + mc)));
|
138
|
|
|
var si = idx - begin;
|
139
|
|
|
|
140
|
|
|
// clear selector select element
|
141
|
|
|
selector.empty();
|
142
|
|
|
this.size(end-begin);
|
143
|
|
|
|
144
|
|
|
// append options
|
145
|
|
|
for (var i=begin; i < end; i++)
|
146
|
|
|
selector.append($(self.get(0).options[i]).clone().attr(idxAttr, i-1));
|
147
|
|
|
|
148
|
|
|
// append top match item if length exceeds
|
149
|
|
|
if(end > settings.maxMultiMatch)
|
150
|
|
|
selector.append(topMatchItem);
|
151
|
|
|
|
152
|
|
|
// set selectedIndex of selector
|
153
|
|
|
selector.get(0).selectedIndex = si;
|
154
|
|
|
}
|
155
|
|
|
};
|
156
|
|
|
|
157
|
|
|
// draw it
|
158
|
|
|
draw();
|
159
|
|
|
|
160
|
|
|
/*
|
161
|
|
|
* EVENT HANDLING
|
162
|
|
|
*/
|
163
|
|
|
var suspendBlur = false;
|
164
|
|
|
overlay.mouseover(function() {
|
165
|
|
|
suspendBlur = true;
|
166
|
|
|
});
|
167
|
|
|
overlay.mouseout(function() {
|
168
|
|
|
suspendBlur = false;
|
169
|
|
|
});
|
170
|
|
|
selector.mouseover(function() {
|
171
|
|
|
suspendBlur = true;
|
172
|
|
|
});
|
173
|
|
|
selector.mouseout(function() {
|
174
|
|
|
suspendBlur = false;
|
175
|
|
|
});
|
176
|
|
|
input.click(function(e) {
|
177
|
|
|
if(!enabled)
|
178
|
|
|
enable(e, true);
|
179
|
|
|
else
|
180
|
|
|
disable(e, true);
|
181
|
|
|
});
|
182
|
|
|
input.blur(function(e) {
|
183
|
|
|
if(!suspendBlur && enabled)
|
184
|
|
|
disable(e, true);
|
185
|
|
|
});
|
186
|
|
|
self.keydown(function(e) {
|
187
|
|
|
if(e.keyCode != 9 && !e.shiftKey && !e.ctrlKey && !e.altKey)
|
188
|
|
|
input.click();
|
189
|
|
|
});
|
190
|
|
|
self.click(function(e) {
|
|
|
|
|
191
|
|
|
selector.focus();
|
192
|
|
|
});
|
193
|
|
|
selector.click(function(e) {
|
194
|
|
|
if (selectorHelper.selectedIndex() < 0)
|
195
|
|
|
return;
|
196
|
|
|
disable(e);
|
197
|
|
|
});
|
198
|
|
|
selector.focus(function(e) {
|
|
|
|
|
199
|
|
|
input.focus();
|
200
|
|
|
});
|
201
|
|
|
selector.blur(function(e) {
|
202
|
|
|
if(!suspendBlur)
|
203
|
|
|
disable(e, true);
|
204
|
|
|
});
|
205
|
|
|
selector.mousemove(function(e) {
|
206
|
|
|
// Disabled on opera because of <select> elements always return scrollTop of 0
|
207
|
|
|
// Affects up to Opera 10 beta 1, can be removed if bug is fixed
|
208
|
|
|
// http://www.greywyvern.com/code/opera/bugs/selectScrollTop
|
209
|
|
|
if($.browser.opera && parseFloat(jQuery.browser.version) >= 9.8)
|
210
|
|
|
return true;
|
211
|
|
|
|
212
|
|
|
// get font-size of option
|
213
|
|
|
var fs = Math.floor(parseFloat(/([0-9\.]+)px/.exec(selectorHelper.option(0).css("font-size"))));
|
214
|
|
|
// calc line height depends on browser
|
215
|
|
|
var fsdiff = 4;
|
216
|
|
|
if($.browser.opera)
|
217
|
|
|
fsdiff = 2.5;
|
218
|
|
|
if($.browser.safari || $.browser.chrome)
|
219
|
|
|
fsdiff = 3;
|
220
|
|
|
fs += Math.round(fs / fsdiff);
|
221
|
|
|
// set selectedIndex depends on mouse position and line height
|
222
|
|
|
selectorHelper.selectedIndex(Math.floor((e.pageY - selector.offset().top + this.scrollTop) / fs));
|
|
|
|
|
223
|
|
|
});
|
224
|
|
|
|
225
|
|
|
// toggle click event on overlay div
|
226
|
|
|
overlay.click(function(e) {
|
|
|
|
|
227
|
|
|
input.click();
|
228
|
|
|
});
|
229
|
|
|
|
230
|
|
|
// trigger event keyup
|
231
|
|
|
input.keyup(function(e) {
|
232
|
|
|
|
233
|
|
|
// break searching while using navigation keys
|
234
|
|
|
if(jQuery.inArray(e.keyCode, new Array(9, 13, 16, 33, 34, 35, 36, 38, 40)) > -1)
|
|
|
|
|
235
|
|
|
return true;
|
236
|
|
|
|
237
|
|
|
// set search text
|
238
|
|
|
search = $.trim(input.val().toLowerCase());
|
239
|
|
|
|
240
|
|
|
// if a previous timer is running, stop it
|
241
|
|
|
clearSearchTimer();
|
242
|
|
|
|
243
|
|
|
// start new timer
|
244
|
|
|
timer = setTimeout(searching, settings.latency);
|
|
|
|
|
245
|
|
|
});
|
246
|
|
|
|
247
|
|
|
// trigger keydown event for keyboard usage
|
248
|
|
|
input.keydown(function(e) {
|
249
|
|
|
|
250
|
|
|
// tab stop
|
251
|
|
|
if(e.keyCode == 9) {
|
252
|
|
|
disable(e);
|
253
|
|
|
}
|
254
|
|
|
|
255
|
|
|
// return on shift, ctrl, alt key mode
|
256
|
|
|
if(e.shiftKey || e.ctrlKey || e.altKey)
|
257
|
|
|
return;
|
258
|
|
|
|
259
|
|
|
// which key is pressed
|
260
|
|
|
switch(e.keyCode) {
|
261
|
|
|
case 13: // enter
|
262
|
|
|
disable(e);
|
263
|
|
|
self.focus();
|
264
|
|
|
break;
|
265
|
|
|
case 27: // escape
|
266
|
|
|
disable(e, true);
|
267
|
|
|
self.focus();
|
268
|
|
|
break;
|
269
|
|
|
case 33: // pgup
|
270
|
|
|
if (selectorHelper.selectedIndex() - selector.attr("size") > 0) {
|
271
|
|
|
selectorHelper.selectedIndex(selectorHelper.selectedIndex() - selector.attr("size"));
|
272
|
|
|
}
|
273
|
|
|
else {
|
274
|
|
|
selectorHelper.selectedIndex(0);
|
275
|
|
|
}
|
276
|
|
|
synchronize();
|
277
|
|
|
break;
|
278
|
|
|
case 34: // pgdown
|
279
|
|
|
if (selectorHelper.selectedIndex() + selector.attr("size") < selector.get(0).options.length - 1) {
|
280
|
|
|
selectorHelper.selectedIndex(selectorHelper.selectedIndex() + selector.attr("size"));
|
281
|
|
|
}
|
282
|
|
|
else {
|
283
|
|
|
selectorHelper.selectedIndex(selector.get(0).options.length-1);
|
284
|
|
|
}
|
285
|
|
|
synchronize();
|
286
|
|
|
break;
|
287
|
|
|
case 38: // up
|
288
|
|
|
if (selectorHelper.selectedIndex() > 0){
|
289
|
|
|
selectorHelper.selectedIndex(selectorHelper.selectedIndex()-1);
|
290
|
|
|
synchronize();
|
291
|
|
|
}
|
292
|
|
|
break;
|
293
|
|
|
case 40: // down
|
294
|
|
|
if (selectorHelper.selectedIndex() < selector.get(0).options.length - 1){
|
295
|
|
|
selectorHelper.selectedIndex(selectorHelper.selectedIndex()+1);
|
296
|
|
|
synchronize();
|
297
|
|
|
}
|
298
|
|
|
break;
|
299
|
|
|
default:
|
300
|
|
|
return true;
|
301
|
|
|
}
|
302
|
|
|
|
303
|
|
|
// we handled the key.stop
|
304
|
|
|
// doing anything with it!
|
305
|
|
|
return false;
|
306
|
|
|
});
|
307
|
|
|
|
308
|
|
|
/**
|
309
|
|
|
* Draw the needed elements
|
310
|
|
|
*/
|
311
|
|
|
function draw() {
|
312
|
|
|
|
313
|
|
|
// fix some styles
|
314
|
|
|
self.css("text-decoration", "none");
|
315
|
|
|
self.width(self.outerWidth());
|
316
|
|
|
self.height(self.outerHeight());
|
317
|
|
|
|
318
|
|
|
// wrapper styles
|
319
|
|
|
wrapper.css("position", "relative");
|
320
|
|
|
/*wrapper.css("width", self.outerWidth());*/
|
321
|
|
|
// relative div needs an z-index (related to IE z-index bug)
|
322
|
|
|
if($.browser.msie)
|
323
|
|
|
wrapper.css("z-index", zindex);
|
324
|
|
|
|
325
|
|
|
// overlay div to block events of source select element
|
326
|
|
|
overlay.css({
|
327
|
|
|
"position": "absolute",
|
328
|
|
|
"top": 0,
|
329
|
|
|
"left": "0px",
|
330
|
|
|
"width": self.outerWidth(),
|
331
|
|
|
"height": self.outerHeight(),
|
332
|
|
|
"background-color": "#FFFFFF",
|
333
|
|
|
"opacity": "0.01"
|
334
|
|
|
});
|
335
|
|
|
|
336
|
|
|
// overlay text field for searching capability
|
337
|
|
|
input.attr("type", "text");
|
338
|
|
|
input.hide();
|
339
|
|
|
input.height(self.outerHeight());
|
340
|
|
|
|
341
|
|
|
// default styles for text field
|
342
|
|
|
input.attr("class", "col-md-5 form-control");
|
343
|
|
|
/*- input.className = "col-md-5 form-control";*/
|
344
|
|
|
input.css({
|
345
|
|
|
"position": "absolute",
|
346
|
|
|
"top": 0,
|
347
|
|
|
"left": "0px",
|
348
|
|
|
"margin": "0px",
|
349
|
|
|
"padding": "0px",
|
350
|
|
|
"outline-style": "none",
|
351
|
|
|
"border-style": "solid",
|
352
|
|
|
"border-bottom-style": "none",
|
353
|
|
|
"border-color": "transparent",
|
354
|
|
|
"background-color": "transparent"
|
355
|
|
|
|
356
|
|
|
// "background-color": "red"
|
357
|
|
|
});
|
358
|
|
|
|
359
|
|
|
// copy selected styles to text field
|
360
|
|
|
var sty = new Array();
|
|
|
|
|
361
|
|
|
sty.push("border-left-width");
|
362
|
|
|
sty.push("border-top-width");
|
363
|
|
|
//sty.push("font-family");
|
364
|
|
|
sty.push("font-size");
|
365
|
|
|
sty.push("font-stretch");
|
366
|
|
|
sty.push("font-variant");
|
367
|
|
|
sty.push("font-weight");
|
368
|
|
|
sty.push("color");
|
369
|
|
|
sty.push("text-align");
|
370
|
|
|
sty.push("text-indent");
|
371
|
|
|
sty.push("text-shadow");
|
372
|
|
|
sty.push("text-transform");
|
373
|
|
|
sty.push("padding-left");
|
374
|
|
|
sty.push("padding-top");
|
375
|
|
|
for(var i=0; i < sty.length;i++)
|
376
|
|
|
input.css(sty[i], self.css(sty[i]));
|
377
|
|
|
|
378
|
|
|
// adjust search text field
|
379
|
|
|
// IE7
|
380
|
|
|
if($.browser.msie && parseInt(jQuery.browser.version) < 8) {
|
381
|
|
|
input.css("padding", "0px");
|
382
|
|
|
input.css("padding-left", "3px");
|
383
|
|
|
input.css("border-left-width", "2px");
|
384
|
|
|
input.css("border-top-width", "3px");
|
385
|
|
|
}
|
386
|
|
|
// chrome
|
387
|
|
|
else if($.browser.chrome) {
|
388
|
|
|
input.height(self.innerHeight());
|
389
|
|
|
input.css("text-transform", "none");
|
390
|
|
|
input.css("padding-left", parseFloatPx(input.css("padding-left"))+3);
|
391
|
|
|
input.css("padding-top", 2);
|
392
|
|
|
}
|
393
|
|
|
// safari
|
394
|
|
|
else if($.browser.safari) {
|
395
|
|
|
input.height(self.innerHeight());
|
396
|
|
|
input.css("padding-top", 2);
|
397
|
|
|
input.css("padding-left", 3);
|
398
|
|
|
input.css("text-transform", "none");
|
399
|
|
|
}
|
400
|
|
|
// opera
|
401
|
|
|
else if($.browser.opera) {
|
402
|
|
|
input.height(self.innerHeight());
|
403
|
|
|
var pl = parseFloatPx(self.css("padding-left"));
|
404
|
|
|
input.css("padding-left", pl == 1 ? pl+1 : pl);
|
405
|
|
|
input.css("padding-top", 0);
|
406
|
|
|
}
|
407
|
|
|
else if($.browser.mozilla) {
|
408
|
|
|
input.css("padding-top", "0px");
|
409
|
|
|
input.css("border-top", "0px");
|
410
|
|
|
input.css("padding-left", parseFloatPx(self.css("padding-left"))+3);
|
411
|
|
|
}
|
412
|
|
|
// all other browsers
|
413
|
|
|
else {
|
414
|
|
|
input.css("padding-left", parseFloatPx(self.css("padding-left"))+3);
|
415
|
|
|
input.css("padding-top", parseFloatPx(self.css("padding-top"))+1);
|
416
|
|
|
}
|
417
|
|
|
|
418
|
|
|
// adjust width of search field
|
419
|
|
|
var offset = parseFloatPx(self.css("padding-left")) + parseFloatPx(self.css("padding-right")) +
|
420
|
|
|
parseFloatPx(self.css("border-left-width")) + parseFloatPx(self.css("border-left-width")) + 23;
|
421
|
|
|
input.width(self.outerWidth() - offset);
|
422
|
|
|
|
423
|
|
|
// store css width of source select object then set width
|
424
|
|
|
// to auto to obtain the maximum width depends on the longest entry.
|
425
|
|
|
// this is nessesary to set the width of the selector, because min-width
|
426
|
|
|
// do not work in all browser.
|
427
|
|
|
var w = self.css("width");
|
428
|
|
|
var ow = self.outerWidth();
|
429
|
|
|
self.css("width", "auto");
|
430
|
|
|
ow = ow > self.outerWidth() ? ow : self.outerWidth();
|
|
|
|
|
431
|
|
|
self.css("width", w);
|
432
|
|
|
|
433
|
|
|
// entries selector replacement
|
434
|
|
|
selector.hide();
|
435
|
|
|
|
436
|
|
|
selectorHelper.size(self.get(0).length);
|
437
|
|
|
|
438
|
|
|
selector.attr("class", "col-md-5 form-control");
|
439
|
|
|
selector.css({
|
440
|
|
|
"position": "absolute",
|
441
|
|
|
"top": self.outerHeight(),
|
442
|
|
|
"left": "0px",
|
443
|
|
|
/* "width": ow,*/
|
444
|
|
|
// "border": "0px solid #333",
|
445
|
|
|
"font-weight": "normal",
|
446
|
|
|
"padding": 0,
|
447
|
|
|
"background-color": self.css("background-color"),
|
448
|
|
|
"text-transform": self.css("text-transform")
|
449
|
|
|
});
|
450
|
|
|
|
451
|
|
|
// z-index handling
|
452
|
|
|
var zIndex = /^\d+$/.test(self.css("z-index")) ? self.css("z-index") : 1;
|
453
|
|
|
// if z-index option is defined, use it instead of select element z-index
|
454
|
|
|
if (settings.zIndex && /^\d+$/.test(settings.zIndex))
|
455
|
|
|
zIndex = settings.zIndex;
|
456
|
|
|
overlay.css("z-index", (zIndex).toString(10));
|
457
|
|
|
input.css("z-index", (zIndex+1).toString(10));
|
458
|
|
|
selector.css("z-index", (zIndex+2).toString(10));
|
459
|
|
|
|
460
|
|
|
// append to container
|
461
|
|
|
self.wrap(wrapper);
|
462
|
|
|
self.after(overlay);
|
463
|
|
|
self.after(input);
|
464
|
|
|
self.after(selector);
|
465
|
|
|
};
|
466
|
|
|
|
467
|
|
|
/**
|
468
|
|
|
* Enable the search facilities
|
469
|
|
|
*
|
470
|
|
|
* @param {Object} e Event
|
471
|
|
|
* @param {boolean} s Show selector
|
472
|
|
|
* @param {boolean} v Verbose enabling
|
473
|
|
|
*/
|
474
|
|
|
function enable(e, s, v) {
|
475
|
|
|
|
476
|
|
|
// exit event on disabled select element
|
477
|
|
|
if(self.attr("disabled"))
|
478
|
|
|
return false;
|
479
|
|
|
|
480
|
|
|
// prepend empty option
|
481
|
|
|
self.prepend("<option />");
|
482
|
|
|
|
483
|
|
|
// set state to enabled
|
484
|
|
|
if(typeof v == "undefined")
|
485
|
|
|
enabled = !enabled;
|
486
|
|
|
|
487
|
|
|
// reset selector
|
488
|
|
|
selectorHelper.reset();
|
489
|
|
|
|
490
|
|
|
// synchronize select and dropdown replacement
|
491
|
|
|
synchronize();
|
492
|
|
|
|
493
|
|
|
// store search result
|
494
|
|
|
store();
|
495
|
|
|
|
496
|
|
|
// show selector
|
497
|
|
|
if(s)
|
498
|
|
|
selector.show();
|
499
|
|
|
|
500
|
|
|
// show search field
|
501
|
|
|
input.show();
|
502
|
|
|
input.focus();
|
503
|
|
|
input.select();
|
504
|
|
|
|
505
|
|
|
// select empty option
|
506
|
|
|
self.get(0).selectedIndex = 0;
|
507
|
|
|
|
508
|
|
|
if(typeof e != "undefined")
|
|
|
|
|
509
|
|
|
e.stopPropagation();
|
|
|
|
|
510
|
|
|
};
|
511
|
|
|
|
512
|
|
|
/**
|
513
|
|
|
* Disable the search facilities
|
514
|
|
|
*
|
515
|
|
|
* @param {Object} e Event
|
516
|
|
|
* @param {boolean} rs Restore last results
|
517
|
|
|
*/
|
518
|
|
|
function disable(e, rs) {
|
519
|
|
|
|
520
|
|
|
// set state to disabled
|
521
|
|
|
enabled = false;
|
522
|
|
|
|
523
|
|
|
// remove empty option
|
524
|
|
|
self.find(":first").remove();
|
525
|
|
|
|
526
|
|
|
// clear running search timer
|
527
|
|
|
clearSearchTimer();
|
528
|
|
|
|
529
|
|
|
// hide search field and selector
|
530
|
|
|
input.hide();
|
531
|
|
|
selector.hide();
|
532
|
|
|
|
533
|
|
|
// restore last results
|
534
|
|
|
if(typeof rs != "undefined")
|
535
|
|
|
restore();
|
536
|
|
|
|
537
|
|
|
// populate changes
|
538
|
|
|
populate();
|
539
|
|
|
|
540
|
|
|
if(typeof e != "undefined")
|
541
|
|
|
e.stopPropagation();
|
542
|
|
|
};
|
543
|
|
|
|
544
|
|
|
/**
|
545
|
|
|
* Clears running search timer
|
546
|
|
|
*/
|
547
|
|
|
function clearSearchTimer() {
|
548
|
|
|
// clear running timer
|
549
|
|
|
if(timer != null)
|
550
|
|
|
clearTimeout(timer);
|
551
|
|
|
};
|
552
|
|
|
|
553
|
|
|
/**
|
554
|
|
|
* Populate changes to select element
|
555
|
|
|
*/
|
556
|
|
|
function populate() {
|
557
|
|
|
// invalid selectedIndex or disabled elements do not be populate
|
558
|
|
|
if(selectorHelper.selectedIndex() < 0 || selectorHelper.selected().get(0).disabled)
|
559
|
|
|
return;
|
560
|
|
|
|
561
|
|
|
// store selectedIndex
|
562
|
|
|
self.get(0).selectedIndex = parseInt(selector.find(":selected").attr(idxAttr));
|
563
|
|
|
|
564
|
|
|
// trigger change event
|
565
|
|
|
self.change();
|
566
|
|
|
|
567
|
|
|
// store selected index
|
568
|
|
|
self.data("index", new Number(self.get(0).selectedIndex));
|
569
|
|
|
};
|
570
|
|
|
|
571
|
|
|
/**
|
572
|
|
|
* Synchronize selected item on dropdown replacement with source select element
|
573
|
|
|
*/
|
574
|
|
|
function synchronize() {
|
575
|
|
|
if(selectorHelper.selectedIndex() > -1 && !selectorHelper.selected().get(0).disabled)
|
576
|
|
|
input.val(selector.find(":selected").text());
|
577
|
|
|
else
|
578
|
|
|
input.val(self.find(":selected").text());
|
579
|
|
|
};
|
580
|
|
|
|
581
|
|
|
/**
|
582
|
|
|
* Stores last results of selector
|
583
|
|
|
*/
|
584
|
|
|
function store() {
|
585
|
|
|
storage.index = selectorHelper.selectedIndex();
|
586
|
|
|
storage.options = new Array();
|
|
|
|
|
587
|
|
|
for(var i=0;i<selector.get(0).options.length;i++)
|
588
|
|
|
storage.options.push(selector.get(0).options[i]);
|
589
|
|
|
};
|
590
|
|
|
|
591
|
|
|
/**
|
592
|
|
|
* Restores last results of selector previously stored by store function
|
593
|
|
|
*/
|
594
|
|
|
function restore() {
|
595
|
|
|
selector.empty();
|
596
|
|
|
for(var i=0;i<storage.options.length;i++)
|
597
|
|
|
selector.append(storage.options[i]);
|
598
|
|
|
selectorHelper.selectedIndex(storage.index);
|
599
|
|
|
selectorHelper.size(storage.options.length);
|
600
|
|
|
};
|
601
|
|
|
|
602
|
|
|
/**
|
603
|
|
|
* Escape regular expression string
|
604
|
|
|
*
|
605
|
|
|
* @param str String
|
606
|
|
|
* @return escaped regexp string
|
607
|
|
|
*/
|
608
|
|
|
function escapeRegExp(str) {
|
609
|
|
|
var specials = ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\", "^", "$"];
|
610
|
|
|
var regexp = new RegExp("(\\" + specials.join("|\\") + ")", "g");
|
611
|
|
|
return str.replace(regexp, "\\$1");
|
612
|
|
|
};
|
613
|
|
|
|
614
|
|
|
/**
|
615
|
|
|
* The actual searching gets done here
|
616
|
|
|
*/
|
617
|
|
|
function searching() {
|
618
|
|
|
if (searchCache == search) { // no change ...
|
619
|
|
|
timer = null;
|
620
|
|
|
return;
|
621
|
|
|
}
|
622
|
|
|
|
623
|
|
|
var matches = 0;
|
624
|
|
|
searchCache = search;
|
625
|
|
|
selector.hide();
|
626
|
|
|
selector.empty();
|
627
|
|
|
|
628
|
|
|
// escape regexp characters
|
629
|
|
|
var regexp = escapeRegExp(search);
|
630
|
|
|
// exact match
|
631
|
|
|
if(settings.exactMatch)
|
632
|
|
|
regexp = "^" + regexp;
|
633
|
|
|
// wildcard support
|
634
|
|
|
if(settings.wildcards) {
|
635
|
|
|
regexp = regexp.replace(/\\\*/g, ".*");
|
636
|
|
|
regexp = regexp.replace(/\\\?/g, ".");
|
637
|
|
|
}
|
638
|
|
|
// ignore case sensitive
|
639
|
|
|
var flags = null;
|
640
|
|
|
if(settings.ignoreCase)
|
641
|
|
|
flags = "i";
|
642
|
|
|
|
643
|
|
|
// RegExp object
|
644
|
|
|
search = new RegExp(regexp, flags);
|
645
|
|
|
|
646
|
|
|
// for each item in list
|
647
|
|
|
for(var i=1;i<self.get(0).length && matches < settings.maxMultiMatch;i++){
|
648
|
|
|
// search
|
649
|
|
|
if(search.length == 0 || search.test(self.get(0).options[i].text)){
|
650
|
|
|
var opt = $(self.get(0).options[i]).clone().attr(idxAttr, i-1);
|
651
|
|
|
if(self.data("index") == i)
|
652
|
|
|
opt.text(self.data("text"));
|
653
|
|
|
selector.append(opt);
|
654
|
|
|
matches++;
|
655
|
|
|
}
|
656
|
|
|
}
|
657
|
|
|
|
658
|
|
|
// result actions
|
659
|
|
|
if(matches >= 1){
|
660
|
|
|
selectorHelper.selectedIndex(0);
|
661
|
|
|
}
|
662
|
|
|
else if(matches == 0){
|
663
|
|
|
selector.append(noMatchItem);
|
664
|
|
|
}
|
665
|
|
|
|
666
|
|
|
// append top match item if matches exceeds maxMultiMatch
|
667
|
|
|
if(matches >= settings.maxMultiMatch){
|
668
|
|
|
selector.append(topMatchItem);
|
669
|
|
|
}
|
670
|
|
|
|
671
|
|
|
// resize selector
|
672
|
|
|
selectorHelper.size(matches);
|
673
|
|
|
selector.show();
|
674
|
|
|
timer = null;
|
675
|
|
|
};
|
676
|
|
|
|
677
|
|
|
/**
|
678
|
|
|
* Parse a given pixel size value to a float value
|
679
|
|
|
* @param value Pixel size value
|
680
|
|
|
*/
|
681
|
|
|
function parseFloatPx(value) {
|
682
|
|
|
try {
|
683
|
|
|
value = parseFloat(value.replace(/[\s]*px/, ""));
|
684
|
|
|
if(!isNaN(value))
|
685
|
|
|
return value;
|
686
|
|
|
}
|
687
|
|
|
catch(e) {}
|
|
|
|
|
688
|
|
|
return 0;
|
689
|
|
|
};
|
690
|
|
|
|
691
|
|
|
return;
|
|
|
|
|
692
|
|
|
};
|
693
|
|
|
|
694
|
|
|
/**
|
695
|
|
|
* Register plugin under given namespace
|
696
|
|
|
*
|
697
|
|
|
* Plugin Pattern informations
|
698
|
|
|
* The function creates the namespace under jQuery
|
699
|
|
|
* and bind the function to execute the plugin code.
|
700
|
|
|
* The plugin code goes to the plugin.execute function.
|
701
|
|
|
* The defaults can setup under plugin.defaults.
|
702
|
|
|
*
|
703
|
|
|
* @param {String} nsp Namespace for the plugin
|
704
|
|
|
* @return {Object} Plugin object
|
705
|
|
|
*/
|
706
|
|
|
function register(nsp) {
|
707
|
|
|
|
708
|
|
|
// init plugin namespace
|
709
|
|
|
var plugin = $[nsp] = {};
|
710
|
|
|
|
711
|
|
|
// bind function to jQuery fn object
|
712
|
|
|
$.fn[nsp] = function(settings) {
|
713
|
|
|
// extend default settings
|
714
|
|
|
settings = $.extend(plugin.defaults, settings);
|
715
|
|
|
|
716
|
|
|
var elmSize = this.size();
|
717
|
|
|
return this.each(function(index) {
|
718
|
|
|
plugin.execute.call(this, settings, elmSize-index);
|
719
|
|
|
});
|
720
|
|
|
};
|
721
|
|
|
|
722
|
|
|
return plugin;
|
723
|
|
|
};
|
724
|
|
|
|
725
|
|
|
})(jQuery);
|
726
|
|
|
|
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.
To learn more about declaring variables in Javascript, see the MDN.