]> git.sur5r.net Git - contagged/blob - scripts/interface/iautocompleter.js
Version 0.6.5
[contagged] / scripts / interface / iautocompleter.js
1 /**
2  * Interface Elements for jQuery
3  * Autocompleter
4  * 
5  * http://interface.eyecon.ro
6  * 
7  * Copyright (c) 2006 Stefan Petre
8  * Dual licensed under the MIT (MIT-LICENSE.txt) 
9  * and GPL (GPL-LICENSE.txt) licenses.
10  *  
11  */
12
13 /**
14  * Attach AJAX driven autocomplete/sugestion box to text input fields.
15  *
16  * 
17  * 
18  * @name Autocomplete
19  * @description Attach AJAX driven autocomplete/sugestion box to text input fields.
20  * @param Hash hash A hash of parameters
21  * @option String source the URL to request
22  * @option Integer delay (optional) the delayed time to start the AJAX request
23  * @option Boolean autofill (optional) when true the first sugested value fills the input
24  * @option String helperClass (optional) the CSS class applied to sugestion box
25  * @option String selectClass (optional) the CSS class applied to selected/hovered item
26  * @option Integer minchars (optional) the number of characters needed before starting AJAX request
27  * @option Hash fx (optional) {type:[slide|blind|fade]; duration: integer} the fx type to apply to sugestion box and duration for that fx
28  * @option Function onSelect (optional) A function to be executed whenever an item it is selected
29  * @option Function onShow (optional) A function to be executed whenever the suggection box is displayed
30  * @option Function onHide (optional) A function to be executed whenever the suggection box is hidden
31  * @option Function onHighlight (optional) A function to be executed whenever an item it is highlighted
32  *
33  * @type jQuery
34  * @cat Plugins/Interface
35  * @author Stefan Petre
36  */
37 jQuery.iAuto = {
38         helper : null,
39         content : null,
40         iframe: null,
41         timer : null,
42         lastValue: null,
43         currentValue: null,
44         subject: null,
45         selectedItem : null,
46         items: null,
47         
48         empty : function()
49         {
50                 jQuery.iAuto.content.empty();
51                 if (jQuery.iAuto.iframe) {
52                         jQuery.iAuto.iframe.hide();
53                 }
54         },
55
56         clear : function()
57         {
58                 jQuery.iAuto.items = null;
59                 jQuery.iAuto.selectedItem = null;
60                 jQuery.iAuto.lastValue = jQuery.iAuto.subject.value;
61                 if(jQuery.iAuto.helper.css('display') == 'block') {
62                         if (jQuery.iAuto.subject.autoCFG.fx) {
63                                 switch(jQuery.iAuto.subject.autoCFG.fx.type) {
64                                         case 'fade':
65                                                 jQuery.iAuto.helper.fadeOut(jQuery.iAuto.subject.autoCFG.fx.duration, jQuery.iAuto.empty);
66                                                 break;
67                                         case 'slide':
68                                                 jQuery.iAuto.helper.SlideOutUp(jQuery.iAuto.subject.autoCFG.fx.duration, jQuery.iAuto.empty);
69                                                 break;
70                                         case 'blind':
71                                                 jQuery.iAuto.helper.BlindUp(jQuery.iAuto.subject.autoCFG.fx.duration, jQuery.iAuto.empty);
72                                                 break;
73                                 }
74                         } else {
75                                 jQuery.iAuto.helper.hide();
76                         }
77                         if (jQuery.iAuto.subject.autoCFG.onHide)
78                                 jQuery.iAuto.subject.autoCFG.onHide.apply(jQuery.iAuto.subject, [jQuery.iAuto.helper, jQuery.iAuto.iframe]);
79                 } else {
80                         jQuery.iAuto.empty();
81                 }
82                 window.clearTimeout(jQuery.iAuto.timer);
83         },
84
85         update : function ()
86         {
87                 var subject = jQuery.iAuto.subject;
88                 var subjectValue = jQuery.iAuto.getFieldValues(subject);
89                 //var selectionStart = jQuery.iAuto.getSelectionStart(subject);
90                 if (subject && subjectValue.item != jQuery.iAuto.lastValue && subjectValue.item.length >= subject.autoCFG.minchars) {
91                         jQuery.iAuto.lastValue = subjectValue.item;
92                         jQuery.iAuto.currentValue = subjectValue.item;
93
94                         data = {
95                                 field: jQuery(subject).attr('name')||'field',
96                                 value: subjectValue.item
97                         };
98
99                         jQuery.ajax(
100                                 {
101                                         type: 'POST',
102                                         data: jQuery.param(data),
103                                         success: function(xml)
104                                         {
105                                                 subject.autoCFG.lastSuggestion = jQuery('item',xml);
106                                                 size = subject.autoCFG.lastSuggestion.size();
107                                                 if (size > 0) {
108                                                         var toWrite = '';
109                                                         subject.autoCFG.lastSuggestion.each(
110                                                                 function(nr)
111                                                                 {
112                                                                         toWrite += '<li rel="' + jQuery('value', this).text() + '" dir="' + nr + '" style="cursor: default;">' + jQuery('text', this).text() + '</li>';
113                                                                 }
114                                                         );
115                                                         if (subject.autoCFG.autofill) {
116                                                                 var valueToAdd = jQuery('value', subject.autoCFG.lastSuggestion.get(0)).text();
117                                                                 subject.value = subjectValue.pre + valueToAdd + subject.autoCFG.multipleSeparator + subjectValue.post;
118                                                                 jQuery.iAuto.selection(
119                                                                         subject, 
120                                                                         subjectValue.item.length != valueToAdd.length ? (subjectValue.pre.length + subjectValue.item.length) : valueToAdd.length,
121                                                                         subjectValue.item.length != valueToAdd.length ? (subjectValue.pre.length + valueToAdd.length) : valueToAdd.length
122                                                                 );
123                                                         }
124                                                         
125                                                         if (size > 0) {
126                                                                 jQuery.iAuto.writeItems(subject, toWrite);
127                                                         } else {
128                                                                 jQuery.iAuto.clear();
129                                                         }
130                                                 } else {
131                                                         jQuery.iAuto.clear();
132                                                 }
133                                         },
134                                         url : subject.autoCFG.source
135                                 }
136                         );
137                 }
138         },
139         
140         writeItems : function(subject, toWrite)
141         {
142                 jQuery.iAuto.content.html(toWrite);
143                 jQuery.iAuto.items = jQuery('li', jQuery.iAuto.content.get(0));
144                 jQuery.iAuto.items
145                         .mouseover(jQuery.iAuto.hoverItem)
146                         .bind('click', jQuery.iAuto.clickItem);
147                 var position = jQuery.iUtil.getPosition(subject);
148                 var size = jQuery.iUtil.getSize(subject);
149                 jQuery.iAuto.helper
150                         .css('top', position.y + size.hb + 'px')
151                         .css('left', position.x +  'px')
152                         .addClass(subject.autoCFG.helperClass);
153                 if (jQuery.iAuto.iframe) {
154                         jQuery.iAuto.iframe
155                                 .css('display', 'block')
156                                 .css('top', position.y + size.hb + 'px')
157                                 .css('left', position.x +  'px')
158                                 .css('width', jQuery.iAuto.helper.css('width'))
159                                 .css('height', jQuery.iAuto.helper.css('height'));
160                 }
161                 jQuery.iAuto.selectedItem = 0;
162                 jQuery.iAuto.items.get(0).className = subject.autoCFG.selectClass;
163                 jQuery.iAuto.applyOn(subject,subject.autoCFG.lastSuggestion.get(0), 'onHighlight');
164                 
165                 if (jQuery.iAuto.helper.css('display') == 'none') {
166                         if (subject.autoCFG.inputWidth) {
167                                 var borders = jQuery.iUtil.getPadding(subject, true);
168                                 var paddings = jQuery.iUtil.getBorder(subject, true);
169                                 jQuery.iAuto.helper.css('width', subject.offsetWidth - (jQuery.boxModel ? (borders.l + borders.r + paddings.l + paddings.r) : 0 ) + 'px');
170                         }
171                         if (subject.autoCFG.fx) {
172                                 switch(subject.autoCFG.fx.type) {
173                                         case 'fade':
174                                                 jQuery.iAuto.helper.fadeIn(subject.autoCFG.fx.duration);
175                                                 break;
176                                         case 'slide':
177                                                 jQuery.iAuto.helper.SlideInUp(subject.autoCFG.fx.duration);
178                                                 break;
179                                         case 'blind':
180                                                 jQuery.iAuto.helper.BlindDown(subject.autoCFG.fx.duration);
181                                                 break;
182                                 }
183                         } else {
184                                 jQuery.iAuto.helper.show();
185                         }
186                         
187                         if (jQuery.iAuto.subject.autoCFG.onShow)
188                                 jQuery.iAuto.subject.autoCFG.onShow.apply(jQuery.iAuto.subject, [jQuery.iAuto.helper, jQuery.iAuto.iframe]);
189                 }
190         },
191         
192         checkCache : function()
193         {
194                 var subject = this;
195                 if (subject.autoCFG.lastSuggestion) {
196                         
197                         jQuery.iAuto.lastValue = subject.value;
198                         jQuery.iAuto.currentValue = subject.value;
199                         
200                         var toWrite = '';
201                         subject.autoCFG.lastSuggestion.each(
202                                 function(nr)
203                                 {
204                                         value = jQuery('value', this).text().toLowerCase();
205                                         inputValue = subject.value.toLowerCase();
206                                         if (value.indexOf(inputValue) == 0) {
207                                                 toWrite += '<li rel="' + jQuery('value', this).text() + '" dir="' + nr + '" style="cursor: default;">' + jQuery('text', this).text() + '</li>';
208                                         }
209                                 }
210                         );
211                         
212                         if (toWrite != '') {
213                                 jQuery.iAuto.writeItems(subject, toWrite);
214                                 
215                                 this.autoCFG.inCache = true;
216                                 return;
217                         }
218                 }
219                 subject.autoCFG.lastSuggestion = null;
220                 this.autoCFG.inCache = false;
221         },
222
223         selection : function(field, start, end)
224         {
225                 if (field.createTextRange) {
226                         var selRange = field.createTextRange();
227                         selRange.collapse(true);
228                         selRange.moveStart("character", start);
229                         selRange.moveEnd("character", - end + start);
230                         selRange.select();
231                 } else if (field.setSelectionRange) {
232                         field.setSelectionRange(start, end);
233                 } else {
234                         if (field.selectionStart) {
235                                 field.selectionStart = start;
236                                 field.selectionEnd = end;
237                         }
238                 }
239                 field.focus();
240         },
241         
242         getSelectionStart : function(field)
243         {
244                 if (field.selectionStart)
245                         return field.selectionStart;
246                 else if(field.createTextRange) {
247                         var selRange = document.selection.createRange();
248                         var selRange2 = selRange.duplicate();
249                         return 0 - selRange2.moveStart('character', -100000);
250                         //result.end = result.start + range.text.length;
251                         /*var selRange = document.selection.createRange();
252                         var isCollapsed = selRange.compareEndPoints("StartToEnd", selRange) == 0;
253                         if (!isCollapsed)
254                                 selRange.collapse(true);
255                         var bookmark = selRange.getBookmark();
256                         return bookmark.charCodeAt(2) - 2;*/
257                 }
258         },
259         
260         getFieldValues : function(field)
261         {
262                 var fieldData = {
263                         value: field.value,
264                         pre: '',
265                         post: '',
266                         item: ''
267                 };
268                 
269                 if(field.autoCFG.multiple) {
270                         var finishedPre = false;
271                         var selectionStart = jQuery.iAuto.getSelectionStart(field)||0;
272                         var chunks = fieldData.value.split(field.autoCFG.multipleSeparator);
273                         for (var i=0; i<chunks.length; i++) {
274                                 if(
275                                         (fieldData.pre.length + chunks[i].length >= selectionStart
276                                          || 
277                                         selectionStart == 0)
278                                          && 
279                                         !finishedPre 
280                                 ) {
281                                         if (fieldData.pre.length <= selectionStart)
282                                                 fieldData.item = chunks[i];
283                                         else 
284                                                 fieldData.post += chunks[i] + (chunks[i] != '' ? field.autoCFG.multipleSeparator : '');
285                                         finishedPre = true;
286                                 } else if (finishedPre){
287                                         fieldData.post += chunks[i] + (chunks[i] != '' ? field.autoCFG.multipleSeparator : '');
288                                 }
289                                 if(!finishedPre) {
290                                         fieldData.pre += chunks[i] + (chunks.length > 1 ? field.autoCFG.multipleSeparator : '');
291                                 }
292                         }
293                 } else {
294                         fieldData.item = fieldData.value;
295                 }
296                 return fieldData;
297         },
298         
299         autocomplete : function(e)
300         {
301                 window.clearTimeout(jQuery.iAuto.timer);
302                 var subject = jQuery.iAuto.getFieldValues(this);
303                                 
304                 var pressedKey = e.charCode || e.keyCode || -1;
305                 if (/13|27|35|36|38|40|9/.test(pressedKey) && jQuery.iAuto.items) {
306                         if (window.event) {
307                                 window.event.cancelBubble = true;
308                                 window.event.returnValue = false;
309                         } else {
310                                 e.preventDefault();
311                                 e.stopPropagation();
312                         }
313                         if (jQuery.iAuto.selectedItem != null) 
314                                 jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).className = '';
315                         else
316                                 jQuery.iAuto.selectedItem = -1;
317                         switch(pressedKey) {
318                                 //enter
319                                 case 9:
320                                 case 13:
321                                         if (jQuery.iAuto.selectedItem == -1)
322                                                 jQuery.iAuto.selectedItem = 0;
323                                         var selectedItem = jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0);
324                                         var valueToAdd = selectedItem.getAttribute('rel');
325                                         this.value = subject.pre + valueToAdd + this.autoCFG.multipleSeparator + subject.post;
326                                         jQuery.iAuto.lastValue = subject.item;
327                                         jQuery.iAuto.selection(
328                                                 this, 
329                                                 subject.pre.length + valueToAdd.length + this.autoCFG.multipleSeparator.length, 
330                                                 subject.pre.length + valueToAdd.length + this.autoCFG.multipleSeparator.length
331                                         );
332                                         jQuery.iAuto.clear();
333                                         if (this.autoCFG.onSelect) {
334                                                 iteration = parseInt(selectedItem.getAttribute('dir'))||0;
335                                                 jQuery.iAuto.applyOn(this,this.autoCFG.lastSuggestion.get(iteration), 'onSelect');
336                                         }
337                                         if (this.scrollIntoView)
338                                                 this.scrollIntoView(false);
339                                         return pressedKey != 13;
340                                         break;
341                                 //escape
342                                 case 27:
343                                         this.value = subject.pre + jQuery.iAuto.lastValue + this.autoCFG.multipleSeparator + subject.post;
344                                         this.autoCFG.lastSuggestion = null;
345                                         jQuery.iAuto.clear();
346                                         if (this.scrollIntoView)
347                                                 this.scrollIntoView(false);
348                                         return false;
349                                         break;
350                                 //end
351                                 case 35:
352                                         jQuery.iAuto.selectedItem = jQuery.iAuto.items.size() - 1;
353                                         break;
354                                 //home
355                                 case 36:
356                                         jQuery.iAuto.selectedItem = 0;
357                                         break;
358                                 //up
359                                 case 38:
360                                         jQuery.iAuto.selectedItem --;
361                                         if (jQuery.iAuto.selectedItem < 0)
362                                                 jQuery.iAuto.selectedItem = jQuery.iAuto.items.size() - 1;
363                                         break;
364                                 case 40:
365                                         jQuery.iAuto.selectedItem ++;
366                                         if (jQuery.iAuto.selectedItem == jQuery.iAuto.items.size())
367                                                 jQuery.iAuto.selectedItem = 0;
368                                         break;
369                         }
370                         jQuery.iAuto.applyOn(this,this.autoCFG.lastSuggestion.get(jQuery.iAuto.selectedItem||0), 'onHighlight');
371                         jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).className = this.autoCFG.selectClass;
372                         if (jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).scrollIntoView)
373                                 jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).scrollIntoView(false);
374                         if(this.autoCFG.autofill) {
375                                 var valToAdd = jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).getAttribute('rel');
376                                 this.value = subject.pre + valToAdd + this.autoCFG.multipleSeparator + subject.post;
377                                 if(jQuery.iAuto.lastValue.length != valToAdd.length)
378                                         jQuery.iAuto.selection(
379                                                 this, 
380                                                 subject.pre.length + jQuery.iAuto.lastValue.length, 
381                                                 subject.pre.length + valToAdd.length
382                                         );
383                         }
384                         return false;
385                 }
386                 jQuery.iAuto.checkCache.apply(this);
387                 
388                 if (this.autoCFG.inCache == false) {
389                         if (subject.item != jQuery.iAuto.lastValue && subject.item.length >= this.autoCFG.minchars)
390                                 jQuery.iAuto.timer = window.setTimeout(jQuery.iAuto.update, this.autoCFG.delay);
391                         if (jQuery.iAuto.items) {
392                                 jQuery.iAuto.clear();
393                         }
394                 }
395                 return true;
396         },
397
398         applyOn: function(field, item, type)
399         {
400                 if (field.autoCFG[type]) {
401                         var data = {};
402                         childs = item.getElementsByTagName('*');
403                         for(i=0; i<childs.length; i++){
404                                 data[childs[i].tagName] = childs[i].firstChild.nodeValue;
405                         }
406                         field.autoCFG[type].apply(field,[data]);
407                 }
408         },
409         
410         hoverItem : function(e)
411         {
412                 if (jQuery.iAuto.items) {
413                         if (jQuery.iAuto.selectedItem != null) 
414                                 jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).className = '';
415                         jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).className = '';
416                         jQuery.iAuto.selectedItem = parseInt(this.getAttribute('dir'))||0;
417                         jQuery.iAuto.items.get(jQuery.iAuto.selectedItem||0).className = jQuery.iAuto.subject.autoCFG.selectClass;
418                 }
419         },
420
421         clickItem : function(event)
422         {       
423                 window.clearTimeout(jQuery.iAuto.timer);
424                 
425                 event = event || jQuery.event.fix( window.event );
426                 event.preventDefault();
427                 event.stopPropagation();
428                 var subject = jQuery.iAuto.getFieldValues(jQuery.iAuto.subject);
429                 var valueToAdd = this.getAttribute('rel');
430                 jQuery.iAuto.subject.value = subject.pre + valueToAdd + jQuery.iAuto.subject.autoCFG.multipleSeparator + subject.post;
431                 jQuery.iAuto.lastValue = this.getAttribute('rel');
432                 jQuery.iAuto.selection(
433                         jQuery.iAuto.subject, 
434                         subject.pre.length + valueToAdd.length + jQuery.iAuto.subject.autoCFG.multipleSeparator.length, 
435                         subject.pre.length + valueToAdd.length + jQuery.iAuto.subject.autoCFG.multipleSeparator.length
436                 );
437                 jQuery.iAuto.clear();
438                 if (jQuery.iAuto.subject.autoCFG.onSelect) {
439                         iteration = parseInt(this.getAttribute('dir'))||0;
440                         jQuery.iAuto.applyOn(jQuery.iAuto.subject,jQuery.iAuto.subject.autoCFG.lastSuggestion.get(iteration), 'onSelect');
441                 }
442
443                 return false;
444         },
445
446         protect : function(e)
447         {
448                 pressedKey = e.charCode || e.keyCode || -1;
449                 if (/13|27|35|36|38|40/.test(pressedKey) && jQuery.iAuto.items) {
450                         if (window.event) {
451                                 window.event.cancelBubble = true;
452                                 window.event.returnValue = false;
453                         } else {
454                                 e.preventDefault();
455                                 e.stopPropagation();
456                         }
457                         return false;
458                 }
459         },
460
461         build : function(options)
462         {
463                 if (!options.source || !jQuery.iUtil) {
464                         return;
465                 }
466
467                 if (!jQuery.iAuto.helper) {
468                         if (jQuery.browser.msie) {
469                                 jQuery('body', document).append('<iframe style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" id="autocompleteIframe" src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
470                                 jQuery.iAuto.iframe = jQuery('#autocompleteIframe');
471                         }
472                         jQuery('body', document).append('<div id="autocompleteHelper" style="position: absolute; top: 0; left: 0; z-index: 30001; display: none;"><ul style="margin: 0;padding: 0; list-style: none; z-index: 30002;">&nbsp;</ul></div>');
473                         jQuery.iAuto.helper = jQuery('#autocompleteHelper');
474                         jQuery.iAuto.content = jQuery('ul', jQuery.iAuto.helper);
475                 }
476
477                 return this.each(
478                         function()
479                         {
480                 //modified for contagged: accept textareas, too
481                                 if (! ((this.tagName == 'INPUT' && this.getAttribute('type') == 'text' ) || this.tagName == 'TEXTAREA'))
482                                         return;
483                                 this.autoCFG = {};
484                                 this.autoCFG.source = options.source;
485                                 this.autoCFG.minchars = Math.abs(parseInt(options.minchars)||1);
486                                 this.autoCFG.helperClass = options.helperClass ? options.helperClass : '';
487                                 this.autoCFG.selectClass = options.selectClass ? options.selectClass : '';
488                                 this.autoCFG.onSelect = options.onSelect && options.onSelect.constructor == Function ? options.onSelect : null;
489                                 this.autoCFG.onShow = options.onShow && options.onShow.constructor == Function ? options.onShow : null;
490                                 this.autoCFG.onHide = options.onHide && options.onHide.constructor == Function ? options.onHide : null;
491                                 this.autoCFG.onHighlight = options.onHighlight && options.onHighlight.constructor == Function ? options.onHighlight : null;
492                                 this.autoCFG.inputWidth = options.inputWidth||false;
493                                 this.autoCFG.multiple = options.multiple||false;
494                                 this.autoCFG.multipleSeparator = this.autoCFG.multiple ? (options.multipleSeparator||', '):'';
495                                 this.autoCFG.autofill = options.autofill ? true : false;
496                                 this.autoCFG.delay = Math.abs(parseInt(options.delay)||1000);
497                                 if (options.fx && options.fx.constructor == Object) {
498                                         if (!options.fx.type || !/fade|slide|blind/.test(options.fx.type)) {
499                                                 options.fx.type = 'slide';
500                                         }
501                                         if (options.fx.type == 'slide' && !jQuery.fx.slide)
502                                                 return;
503                                         if (options.fx.type == 'blind' && !jQuery.fx.BlindDirection)
504                                                 return;
505
506                                         options.fx.duration = Math.abs(parseInt(options.fx.duration)||400);
507                                         if (options.fx.duration > this.autoCFG.delay) {
508                                                 options.fx.duration = this.autoCFG.delay - 100;
509                                         }
510                                         this.autoCFG.fx = options.fx;
511                                 }
512                                 this.autoCFG.lastSuggestion = null;
513                                 this.autoCFG.inCache = false;
514
515                                 jQuery(this)
516                                         .attr('autocomplete', 'off')
517                                         .focus(
518                                                 function()
519                                                 {
520                                                         jQuery.iAuto.subject = this;
521                                                         jQuery.iAuto.lastValue = this.value;
522                                                 }
523                                         )
524                                         .keypress(jQuery.iAuto.protect)
525                                         .keyup(jQuery.iAuto.autocomplete)
526                                         
527                                         .blur(
528                                                 function()
529                                                 {
530                                                         jQuery.iAuto.timer = window.setTimeout(jQuery.iAuto.clear, 200);
531                                                 }
532                                         );
533                         }
534                 );
535         }
536 };
537 jQuery.fn.Autocomplete = jQuery.iAuto.build;