]> git.mxchange.org Git - friendica.git/blob - library/jquery_ac/jquery.autocomplete.js
Merge pull request #674 from fermionic/20130503-basic-video-support
[friendica.git] / library / jquery_ac / jquery.autocomplete.js
1 /**\r
2 *  Ajax Autocomplete for jQuery, version 1.1.3\r
3 *  (c) 2010 Tomas Kirda\r
4 *\r
5 *  Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.\r
6 *  For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/\r
7 *\r
8 *  Last Review: 04/19/2010\r
9 */\r
10 \r
11 /*jslint onevar: true, evil: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */\r
12 /*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */\r
13 \r
14 (function($) {\r
15 \r
16   var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g');\r
17 \r
18   function fnFormatResult(value, data, currentValue) {\r
19     var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';\r
20     return value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');\r
21   }\r
22 \r
23   function Autocomplete(el, options) {\r
24     this.el = $(el);\r
25     this.el.attr('autocomplete', 'off');\r
26     this.suggestions = [];\r
27     this.data = [];\r
28     this.badQueries = [];\r
29     this.selectedIndex = -1;\r
30     this.currentValue = this.el.val();\r
31     this.intervalId = 0;\r
32     this.cachedResponse = [];\r
33     this.onChangeInterval = null;\r
34     this.ignoreValueChange = false;\r
35     this.serviceUrl = options.serviceUrl;\r
36     this.isLocal = false;\r
37     this.options = {\r
38       autoSubmit: false,\r
39       minChars: 1,\r
40       maxHeight: 300,\r
41       deferRequestBy: 0,\r
42       width: 0,\r
43       highlight: true,\r
44       params: {},\r
45       fnFormatResult: fnFormatResult,\r
46       delimiter: null,\r
47       zIndex: 9999\r
48     };\r
49     this.initialize();\r
50     this.setOptions(options);\r
51   }\r
52   \r
53   $.fn.autocomplete = function(options) {\r
54     return new Autocomplete(this.get(0)||$('<input />'), options);\r
55   };\r
56 \r
57 \r
58   Autocomplete.prototype = {\r
59 \r
60     killerFn: null,\r
61 \r
62     initialize: function() {\r
63 \r
64       var me, uid, autocompleteElId;\r
65       me = this;\r
66       uid = Math.floor(Math.random()*0x100000).toString(16);\r
67       autocompleteElId = 'Autocomplete_' + uid;\r
68 \r
69       this.killerFn = function(e) {\r
70         if ($(e.target).parents('.autocomplete').size() === 0) {\r
71           me.killSuggestions();\r
72           me.disableKillerFn();\r
73         }\r
74       };\r
75 \r
76       if (!this.options.width) { this.options.width = this.el.width(); }\r
77       this.mainContainerId = 'AutocompleteContainter_' + uid;\r
78 \r
79       $('<div id="' + this.mainContainerId + '" style="position:absolute;z-index:9999;"><div class="autocomplete-w1"><div class="autocomplete" id="' + autocompleteElId + '" style="display:none; width:300px;"></div></div></div>').appendTo('body');\r
80 \r
81       this.container = $('#' + autocompleteElId);\r
82       this.fixPosition();\r
83       if (window.opera) {\r
84         this.el.keypress(function(e) { me.onKeyPress(e); });\r
85       } else {\r
86         this.el.keydown(function(e) { me.onKeyPress(e); });\r
87       }\r
88       this.el.keyup(function(e) { me.onKeyUp(e); });\r
89       this.el.blur(function() { me.enableKillerFn(); });\r
90       this.el.focus(function() { me.fixPosition(); });\r
91     },\r
92     \r
93     setOptions: function(options){\r
94       var o = this.options;\r
95       $.extend(o, options);\r
96       if(o.lookup){\r
97         this.isLocal = true;\r
98         if($.isArray(o.lookup)){ o.lookup = { suggestions:o.lookup, data:[] }; }\r
99       }\r
100       $('#'+this.mainContainerId).css({ zIndex:o.zIndex });\r
101       this.container.css({ maxHeight: o.maxHeight + 'px', width:o.width });\r
102     },\r
103     \r
104     clearCache: function(){\r
105       this.cachedResponse = [];\r
106       this.badQueries = [];\r
107     },\r
108     \r
109     disable: function(){\r
110       this.disabled = true;\r
111     },\r
112     \r
113     enable: function(){\r
114       this.disabled = false;\r
115     },\r
116 \r
117     fixPosition: function() {\r
118       var offset = this.el.offset();\r
119       $('#' + this.mainContainerId).css({ top: (offset.top + this.el.innerHeight()) + 'px', left: offset.left + 'px' });\r
120     },\r
121 \r
122     enableKillerFn: function() {\r
123       var me = this;\r
124       $(document).bind('click', me.killerFn);\r
125     },\r
126 \r
127     disableKillerFn: function() {\r
128       var me = this;\r
129       $(document).unbind('click', me.killerFn);\r
130     },\r
131 \r
132     killSuggestions: function() {\r
133       var me = this;\r
134       this.stopKillSuggestions();\r
135       this.intervalId = window.setInterval(function() { me.hide(); me.stopKillSuggestions(); }, 300);\r
136     },\r
137 \r
138     stopKillSuggestions: function() {\r
139       window.clearInterval(this.intervalId);\r
140     },\r
141 \r
142     onKeyPress: function(e) {\r
143       if (this.disabled || !this.enabled) { return; }\r
144       // return will exit the function\r
145       // and event will not be prevented\r
146       switch (e.keyCode) {\r
147         case 27: //KEY_ESC:\r
148           this.el.val(this.currentValue);\r
149           this.hide();\r
150           break;\r
151         case 9: //KEY_TAB:\r
152         case 13: //KEY_RETURN:\r
153           if (this.selectedIndex === -1) {\r
154             this.hide();\r
155             return;\r
156           }\r
157           this.select(this.selectedIndex);\r
158           if(e.keyCode === 9){ return; }\r
159           break;\r
160         case 38: //KEY_UP:\r
161           this.moveUp();\r
162           break;\r
163         case 40: //KEY_DOWN:\r
164           this.moveDown();\r
165           break;\r
166         default:\r
167           return;\r
168       }\r
169       e.stopImmediatePropagation();\r
170       e.preventDefault();\r
171     },\r
172 \r
173     onKeyUp: function(e) {\r
174       if(this.disabled){ return; }\r
175       switch (e.keyCode) {\r
176         case 38: //KEY_UP:\r
177         case 40: //KEY_DOWN:\r
178           return;\r
179       }\r
180       clearInterval(this.onChangeInterval);\r
181       if (this.currentValue !== this.el.val()) {\r
182         if (this.options.deferRequestBy > 0) {\r
183           // Defer lookup in case when value changes very quickly:\r
184           var me = this;\r
185           this.onChangeInterval = setInterval(function() { me.onValueChange(); }, this.options.deferRequestBy);\r
186         } else {\r
187           this.onValueChange();\r
188         }\r
189       }\r
190     },\r
191 \r
192     onValueChange: function() {\r
193       clearInterval(this.onChangeInterval);\r
194       this.currentValue = this.el.val();\r
195       var q = this.getQuery(this.currentValue);\r
196       this.selectedIndex = -1;\r
197       if (this.ignoreValueChange) {\r
198         this.ignoreValueChange = false;\r
199         return;\r
200       }\r
201       if (q === '' || q.length < this.options.minChars) {\r
202         this.hide();\r
203       } else {\r
204         this.getSuggestions(q);\r
205       }\r
206     },\r
207 \r
208     getQuery: function(val) {\r
209       var d, arr;\r
210       d = this.options.delimiter;\r
211       if (!d) { return $.trim(val); }\r
212       arr = val.split(d);\r
213       return $.trim(arr[arr.length - 1]);\r
214     },\r
215 \r
216     getSuggestionsLocal: function(q) {\r
217       var ret, arr, len, val, i;\r
218       arr = this.options.lookup;\r
219       len = arr.suggestions.length;\r
220       ret = { suggestions:[], data:[] };\r
221       q = q.toLowerCase();\r
222       for(i=0; i< len; i++){\r
223         val = arr.suggestions[i];\r
224         if(val.toLowerCase().indexOf(q) === 0){\r
225           ret.suggestions.push(val);\r
226           ret.data.push(arr.data[i]);\r
227         }\r
228       }\r
229       return ret;\r
230     },\r
231     \r
232     getSuggestions: function(q) {\r
233       var cr, me;\r
234       cr = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q];\r
235       if (cr && $.isArray(cr.suggestions)) {\r
236         this.suggestions = cr.suggestions;\r
237         this.data = cr.data;\r
238         this.suggest();\r
239       } else if (!this.isBadQuery(q)) {\r
240         me = this;\r
241         me.options.params.query = q;\r
242         $.get(this.serviceUrl, me.options.params, function(txt) { me.processResponse(txt); }, 'text');\r
243       }\r
244     },\r
245 \r
246     isBadQuery: function(q) {\r
247       var i = this.badQueries.length;\r
248       while (i--) {\r
249         if (q.indexOf(this.badQueries[i]) === 0) { return true; }\r
250       }\r
251       return false;\r
252     },\r
253 \r
254     hide: function() {\r
255       this.enabled = false;\r
256       this.selectedIndex = -1;\r
257       this.container.hide();\r
258     },\r
259 \r
260     suggest: function() {\r
261       if (this.suggestions.length === 0) {\r
262         this.hide();\r
263         return;\r
264       }\r
265 \r
266       var me, len, div, f, v, i, s, mOver, mClick;\r
267       me = this;\r
268       len = this.suggestions.length;\r
269       f = this.options.fnFormatResult;\r
270       v = this.getQuery(this.currentValue);\r
271       mOver = function(xi) { return function() { me.activate(xi); }; };\r
272       mClick = function(xi) { return function() { me.select(xi); }; };\r
273       this.container.hide().empty();\r
274       for (i = 0; i < len; i++) {\r
275         s = this.suggestions[i];\r
276         div = $((me.selectedIndex === i ? '<div class="selected"' : '<div') + ' title="' + s + '">' + f(s, this.data[i], v) + '</div>');\r
277         div.mouseover(mOver(i));\r
278         div.click(mClick(i));\r
279         this.container.append(div);\r
280       }\r
281       this.enabled = true;\r
282       this.container.show();\r
283     },\r
284 \r
285     processResponse: function(text) {\r
286       var response;\r
287       try {\r
288         response = eval('(' + text + ')');\r
289       } catch (err) { return; }\r
290       if (!$.isArray(response.data)) { response.data = []; }\r
291       if(!this.options.noCache){\r
292         this.cachedResponse[response.query] = response;\r
293         if (response.suggestions.length === 0) { this.badQueries.push(response.query); }\r
294       }\r
295       if (response.query === this.getQuery(this.currentValue)) {\r
296         this.suggestions = response.suggestions;\r
297         this.data = response.data;\r
298         this.suggest(); \r
299       }\r
300     },\r
301 \r
302     activate: function(index) {\r
303       var divs, activeItem;\r
304       divs = this.container.children();\r
305       // Clear previous selection:\r
306       if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {\r
307         $(divs.get(this.selectedIndex)).removeClass();\r
308       }\r
309       this.selectedIndex = index;\r
310       if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {\r
311         activeItem = divs.get(this.selectedIndex);\r
312         $(activeItem).addClass('selected');\r
313       }\r
314       return activeItem;\r
315     },\r
316 \r
317     deactivate: function(div, index) {\r
318       div.className = '';\r
319       if (this.selectedIndex === index) { this.selectedIndex = -1; }\r
320     },\r
321 \r
322     select: function(i) {\r
323       var selectedValue, f;\r
324       selectedValue = this.suggestions[i];\r
325       if (selectedValue) {\r
326         this.el.val(selectedValue);\r
327         if (this.options.autoSubmit) {\r
328           f = this.el.parents('form');\r
329           if (f.length > 0) { f.get(0).submit(); }\r
330         }\r
331         this.ignoreValueChange = true;\r
332         this.hide();\r
333         this.onSelect(i);\r
334       }\r
335     },\r
336 \r
337     moveUp: function() {\r
338       if (this.selectedIndex === -1) { return; }\r
339       if (this.selectedIndex === 0) {\r
340         this.container.children().get(0).className = '';\r
341         this.selectedIndex = -1;\r
342         this.el.val(this.currentValue);\r
343         return;\r
344       }\r
345       this.adjustScroll(this.selectedIndex - 1);\r
346     },\r
347 \r
348     moveDown: function() {\r
349       if (this.selectedIndex === (this.suggestions.length - 1)) { return; }\r
350       this.adjustScroll(this.selectedIndex + 1);\r
351     },\r
352 \r
353     adjustScroll: function(i) {\r
354       var activeItem, offsetTop, upperBound, lowerBound;\r
355       activeItem = this.activate(i);\r
356       offsetTop = activeItem.offsetTop;\r
357       upperBound = this.container.scrollTop();\r
358       lowerBound = upperBound + this.options.maxHeight - 25;\r
359       if (offsetTop < upperBound) {\r
360         this.container.scrollTop(offsetTop);\r
361       } else if (offsetTop > lowerBound) {\r
362         this.container.scrollTop(offsetTop - this.options.maxHeight + 25);\r
363       }\r
364       this.el.val(this.getValue(this.suggestions[i]));\r
365     },\r
366 \r
367     onSelect: function(i) {\r
368       var me, fn, s, d;\r
369       me = this;\r
370       fn = me.options.onSelect;\r
371       s = me.suggestions[i];\r
372       d = me.data[i];\r
373       me.el.val(me.getValue(s));\r
374       if ($.isFunction(fn)) { fn(s, d, me.el); }\r
375     },\r
376     \r
377     getValue: function(value){\r
378         var del, currVal, arr, me;\r
379         me = this;\r
380         del = me.options.delimiter;\r
381         if (!del) { return value; }\r
382         currVal = me.currentValue;\r
383         arr = currVal.split(del);\r
384         if (arr.length === 1) { return value; }\r
385         return currVal.substr(0, currVal.length - arr[arr.length - 1].length) + value;\r
386     }\r
387 \r
388   };\r
389 \r
390 }(jQuery));\r