remove autocomplete and use autosuggest instead
[phpfspot.git] / autosuggest / js / bsn.AutoSuggest_2.1.3.js
1 /**
2  *  author:             Timothy Groves - http://www.brandspankingnew.net
3  *      version:        1.2 - 2006-11-17
4  *              1.3 - 2006-12-04
5  *              2.0 - 2007-02-07
6  *              2.1.1 - 2007-04-13
7  *              2.1.2 - 2007-07-07
8  *              2.1.3 - 2007-07-19
9  *
10  */
11
12
13 if (typeof(bsn) == "undefined")
14         _b = bsn = {};
15
16
17 if (typeof(_b.Autosuggest) == "undefined")
18         _b.Autosuggest = {};
19 else
20         alert("Autosuggest is already set!");
21
22
23
24
25
26
27
28
29
30
31
32
33 _b.AutoSuggest = function (id, param)
34 {
35         // no DOM - give up!
36         //
37         if (!document.getElementById)
38                 return 0;
39         
40         
41         
42         
43         // get field via DOM
44         //
45         this.fld = _b.DOM.gE(id);
46
47         if (!this.fld)
48                 return 0;
49         
50         
51         
52         
53         // init variables
54         //
55         this.sInp       = "";
56         this.nInpC      = 0;
57         this.aSug       = [];
58         this.iHigh      = 0;
59         
60         
61         
62         
63         // parameters object
64         //
65         this.oP = param ? param : {};
66         
67         // defaults     
68         //
69         var k, def = {minchars:1, meth:"get", varname:"input", className:"autosuggest", timeout:2500, delay:500, offsety:-5, shownoresults: true, noresults: "No results!", maxheight: 250, cache: true, maxentries: 25};
70         for (k in def)
71         {
72                 if (typeof(this.oP[k]) != typeof(def[k]))
73                         this.oP[k] = def[k];
74         }
75         
76         
77         // set keyup handler for field
78         // and prevent autocomplete from client
79         //
80         var p = this;
81         
82         // NOTE: not using addEventListener because UpArrow fired twice in Safari
83         //_b.DOM.addEvent( this.fld, 'keyup', function(ev){ return pointer.onKeyPress(ev); } );
84         
85         this.fld.onkeypress     = function(ev){ return p.onKeyPress(ev); };
86         this.fld.onkeyup                = function(ev){ return p.onKeyUp(ev); };
87         
88         this.fld.setAttribute("autocomplete","off");
89 };
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 _b.AutoSuggest.prototype.onKeyPress = function(ev)
107 {
108         
109         var key = (window.event) ? window.event.keyCode : ev.keyCode;
110
111
112
113         // set responses to keydown events in the field
114         // this allows the user to use the arrow keys to scroll through the results
115         // ESCAPE clears the list
116         // TAB sets the current highlighted value
117         //
118         var RETURN = 13;
119         var TAB = 9;
120         var ESC = 27;
121         
122         var bubble = 1;
123
124         switch(key)
125         {
126                 case RETURN:
127                         this.setHighlightedValue();
128                         bubble = 0;
129                         break;
130
131                 case ESC:
132                         this.clearSuggestions();
133                         break;
134         }
135
136         return bubble;
137 };
138
139
140
141 _b.AutoSuggest.prototype.onKeyUp = function(ev)
142 {
143         var key = (window.event) ? window.event.keyCode : ev.keyCode;
144         
145
146
147         // set responses to keydown events in the field
148         // this allows the user to use the arrow keys to scroll through the results
149         // ESCAPE clears the list
150         // TAB sets the current highlighted value
151         //
152
153         var ARRUP = 38;
154         var ARRDN = 40;
155         
156         var bubble = 1;
157
158         switch(key)
159         {
160
161
162                 case ARRUP:
163                         this.changeHighlight(key);
164                         bubble = 0;
165                         break;
166
167
168                 case ARRDN:
169                         this.changeHighlight(key);
170                         bubble = 0;
171                         break;
172                 
173                 
174                 default:
175                         this.getSuggestions(this.fld.value);
176         }
177
178         return bubble;
179         
180
181 };
182
183
184
185
186
187
188
189
190 _b.AutoSuggest.prototype.getSuggestions = function (val)
191 {
192         
193         // if input stays the same, do nothing
194         //
195         if (val == this.sInp)
196                 return 0;
197         
198         
199         // kill list
200         //
201         _b.DOM.remE(this.idAs);
202         
203         
204         this.sInp = val;
205         
206         
207         // input length is less than the min required to trigger a request
208         // do nothing
209         //
210         if (val.length < this.oP.minchars)
211         {
212                 this.aSug = [];
213                 this.nInpC = val.length;
214                 return 0;
215         }
216         
217         
218         
219         
220         var ol = this.nInpC; // old length
221         this.nInpC = val.length ? val.length : 0;
222         
223         
224         
225         // if caching enabled, and user is typing (ie. length of input is increasing)
226         // filter results out of aSuggestions from last request
227         //
228         var l = this.aSug.length;
229         if (this.nInpC > ol && l && l<this.oP.maxentries && this.oP.cache)
230         {
231                 var arr = [];
232                 for (var i=0;i<l;i++)
233                 {
234                         if (this.aSug[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
235                                 arr.push( this.aSug[i] );
236                 }
237                 this.aSug = arr;
238                 
239                 this.createList(this.aSug);
240                 
241                 
242                 
243                 return false;
244         }
245         else
246         // do new request
247         //
248         {
249                 var pointer = this;
250                 var input = this.sInp;
251                 clearTimeout(this.ajID);
252                 this.ajID = setTimeout( function() { pointer.doAjaxRequest(input) }, this.oP.delay );
253         }
254
255         return false;
256 };
257
258
259
260
261
262 _b.AutoSuggest.prototype.doAjaxRequest = function (input)
263 {
264         // check that saved input is still the value of the field
265         //
266         if (input != this.fld.value)
267                 return false;
268         
269         
270         var pointer = this;
271         
272         
273         // create ajax request
274         //
275         if (typeof(this.oP.script) == "function")
276                 var url = this.oP.script(encodeURIComponent(this.sInp));
277         else
278                 var url = this.oP.script+this.oP.varname+"="+encodeURIComponent(this.sInp);
279         
280         if (!url)
281                 return false;
282         
283         var meth = this.oP.meth;
284         var input = this.sInp;
285         
286         var onSuccessFunc = function (req) { pointer.setSuggestions(req, input) };
287         var onErrorFunc = function (status) { alert("AJAX error: "+status); };
288
289         var myAjax = new _b.Ajax();
290         myAjax.makeRequest( url, meth, onSuccessFunc, onErrorFunc );
291 };
292
293
294
295
296
297 _b.AutoSuggest.prototype.setSuggestions = function (req, input)
298 {
299         // if field input no longer matches what was passed to the request
300         // don't show the suggestions
301         //
302         if (input != this.fld.value)
303                 return false;
304         
305         
306         this.aSug = [];
307         
308         
309         if (this.oP.json)
310         {
311                 var jsondata = eval('(' + req.responseText + ')');
312                 
313                 for (var i=0;i<jsondata.results.length;i++)
314                 {
315                         this.aSug.push(  { 'id':jsondata.results[i].id, 'value':jsondata.results[i].value, 'info':jsondata.results[i].info }  );
316                 }
317         }
318         else
319         {
320
321                 var xml = req.responseXML;
322         
323                 // traverse xml
324                 //
325                 var results = xml.getElementsByTagName('results')[0].childNodes;
326
327                 for (var i=0;i<results.length;i++)
328                 {
329                         if (results[i].hasChildNodes())
330                                 this.aSug.push(  { 'id':results[i].getAttribute('id'), 'value':results[i].childNodes[0].nodeValue, 'info':results[i].getAttribute('info') }  );
331                 }
332         
333         }
334         
335         this.idAs = "as_"+this.fld.id;
336         
337
338         this.createList(this.aSug);
339
340 };
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 _b.AutoSuggest.prototype.createList = function(arr)
356 {
357         var pointer = this;
358         
359         
360         
361         
362         // get rid of old list
363         // and clear the list removal timeout
364         //
365         _b.DOM.remE(this.idAs);
366         this.killTimeout();
367         
368         
369         // if no results, and shownoresults is false, do nothing
370         //
371         if (arr.length == 0 && !this.oP.shownoresults)
372                 return false;
373         
374         
375         // create holding div
376         //
377         var div = _b.DOM.cE("div", {id:this.idAs, className:this.oP.className});        
378         
379         var hcorner = _b.DOM.cE("div", {className:"as_corner"});
380         var hbar = _b.DOM.cE("div", {className:"as_bar"});
381         var header = _b.DOM.cE("div", {className:"as_header"});
382         header.appendChild(hcorner);
383         header.appendChild(hbar);
384         div.appendChild(header);
385         
386         
387         
388         
389         // create and populate ul
390         //
391         var ul = _b.DOM.cE("ul", {id:"as_ul"});
392         
393         
394         
395         
396         // loop throught arr of suggestions
397         // creating an LI element for each suggestion
398         //
399         for (var i=0;i<arr.length;i++)
400         {
401                 // format output with the input enclosed in a EM element
402                 // (as HTML, not DOM)
403                 //
404                 var val = arr[i].value;
405                 var st = val.toLowerCase().indexOf( this.sInp.toLowerCase() );
406                 var output = val.substring(0,st) + "<em>" + val.substring(st, st+this.sInp.length) + "</em>" + val.substring(st+this.sInp.length);
407                 
408                 
409                 var span                = _b.DOM.cE("span", {}, output, true);
410                 if (arr[i].info != "")
411                 {
412                         var br                  = _b.DOM.cE("br", {});
413                         span.appendChild(br);
414                         var small               = _b.DOM.cE("small", {}, arr[i].info);
415                         span.appendChild(small);
416                 }
417                 
418                 var a                   = _b.DOM.cE("a", { href:"#" });
419                 
420                 var tl          = _b.DOM.cE("span", {className:"tl"}, " ");
421                 var tr          = _b.DOM.cE("span", {className:"tr"}, " ");
422                 a.appendChild(tl);
423                 a.appendChild(tr);
424                 
425                 a.appendChild(span);
426                 
427                 a.name = i+1;
428                 a.onclick = function () { pointer.setHighlightedValue(); return false; };
429                 a.onmouseover = function () { pointer.setHighlight(this.name); };
430                 
431                 var li = _b.DOM.cE(  "li", {}, a  );
432                 
433                 ul.appendChild( li );
434         }
435         
436         
437         // no results
438         //
439         if (arr.length == 0 && this.oP.shownoresults)
440         {
441                 var li = _b.DOM.cE(  "li", {className:"as_warning"}, this.oP.noresults  );
442                 ul.appendChild( li );
443         }
444         
445         
446         div.appendChild( ul );
447         
448         
449         var fcorner = _b.DOM.cE("div", {className:"as_corner"});
450         var fbar = _b.DOM.cE("div", {className:"as_bar"});
451         var footer = _b.DOM.cE("div", {className:"as_footer"});
452         footer.appendChild(fcorner);
453         footer.appendChild(fbar);
454         div.appendChild(footer);
455         
456         
457         
458         // get position of target textfield
459         // position holding div below it
460         // set width of holding div to width of field
461         //
462         var pos = _b.DOM.getPos(this.fld);
463         
464         div.style.left          = pos.x + "px";
465         div.style.top           = ( pos.y + this.fld.offsetHeight + this.oP.offsety ) + "px";
466         div.style.width         = this.fld.offsetWidth + "px";
467         
468         
469         
470         // set mouseover functions for div
471         // when mouse pointer leaves div, set a timeout to remove the list after an interval
472         // when mouse enters div, kill the timeout so the list won't be removed
473         //
474         div.onmouseover         = function(){ pointer.killTimeout() };
475         div.onmouseout          = function(){ pointer.resetTimeout() };
476
477
478         // add DIV to document
479         //
480         document.getElementsByTagName("body")[0].appendChild(div);
481         
482         
483         
484         // currently no item is highlighted
485         //
486         this.iHigh = 0;
487         
488         
489         
490         
491         
492         
493         // remove list after an interval
494         //
495         var pointer = this;
496         this.toID = setTimeout(function () { pointer.clearSuggestions() }, this.oP.timeout);
497 };
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513 _b.AutoSuggest.prototype.changeHighlight = function(key)
514 {       
515         var list = _b.DOM.gE("as_ul");
516         if (!list)
517                 return false;
518         
519         var n;
520
521         if (key == 40)
522                 n = this.iHigh + 1;
523         else if (key == 38)
524                 n = this.iHigh - 1;
525         
526         
527         if (n > list.childNodes.length)
528                 n = list.childNodes.length;
529         if (n < 1)
530                 n = 1;
531         
532         
533         this.setHighlight(n);
534 };
535
536
537
538 _b.AutoSuggest.prototype.setHighlight = function(n)
539 {
540         var list = _b.DOM.gE("as_ul");
541         if (!list)
542                 return false;
543         
544         if (this.iHigh > 0)
545                 this.clearHighlight();
546         
547         this.iHigh = Number(n);
548         
549         list.childNodes[this.iHigh-1].className = "as_highlight";
550
551
552         this.killTimeout();
553 };
554
555
556 _b.AutoSuggest.prototype.clearHighlight = function()
557 {
558         var list = _b.DOM.gE("as_ul");
559         if (!list)
560                 return false;
561         
562         if (this.iHigh > 0)
563         {
564                 list.childNodes[this.iHigh-1].className = "";
565                 this.iHigh = 0;
566         }
567 };
568
569
570 _b.AutoSuggest.prototype.setHighlightedValue = function ()
571 {
572         if (this.iHigh)
573         {
574                 this.sInp = this.fld.value = this.aSug[ this.iHigh-1 ].value;
575                 
576                 // move cursor to end of input (safari)
577                 //
578                 this.fld.focus();
579                 if (this.fld.selectionStart)
580                         this.fld.setSelectionRange(this.sInp.length, this.sInp.length);
581                 
582
583                 this.clearSuggestions();
584                 
585                 // pass selected object to callback function, if exists
586                 //
587                 if (typeof(this.oP.callback) == "function")
588                         this.oP.callback( this.aSug[this.iHigh-1] );
589         }
590 };
591
592
593
594
595
596
597
598
599
600
601
602
603
604 _b.AutoSuggest.prototype.killTimeout = function()
605 {
606         clearTimeout(this.toID);
607 };
608
609 _b.AutoSuggest.prototype.resetTimeout = function()
610 {
611         clearTimeout(this.toID);
612         var pointer = this;
613         this.toID = setTimeout(function () { pointer.clearSuggestions() }, 1000);
614 };
615
616
617
618
619
620
621
622 _b.AutoSuggest.prototype.clearSuggestions = function ()
623 {
624         
625         this.killTimeout();
626         
627         var ele = _b.DOM.gE(this.idAs);
628         var pointer = this;
629         if (ele)
630         {
631                 var fade = new _b.Fader(ele,1,0,250,function () { _b.DOM.remE(pointer.idAs) });
632         }
633 };
634
635
636
637
638
639
640
641
642
643
644 // AJAX PROTOTYPE _____________________________________________
645
646
647 if (typeof(_b.Ajax) == "undefined")
648         _b.Ajax = {};
649
650
651
652 _b.Ajax = function ()
653 {
654         this.req = {};
655         this.isIE = false;
656 };
657
658
659
660 _b.Ajax.prototype.makeRequest = function (url, meth, onComp, onErr)
661 {
662         
663         if (meth != "POST")
664                 meth = "GET";
665         
666         this.onComplete = onComp;
667         this.onError = onErr;
668         
669         var pointer = this;
670         
671         // branch for native XMLHttpRequest object
672         if (window.XMLHttpRequest)
673         {
674                 this.req = new XMLHttpRequest();
675                 this.req.onreadystatechange = function () { pointer.processReqChange() };
676                 this.req.open("GET", url, true); //
677                 this.req.send(null);
678         // branch for IE/Windows ActiveX version
679         }
680         else if (window.ActiveXObject)
681         {
682                 this.req = new ActiveXObject("Microsoft.XMLHTTP");
683                 if (this.req)
684                 {
685                         this.req.onreadystatechange = function () { pointer.processReqChange() };
686                         this.req.open(meth, url, true);
687                         this.req.send();
688                 }
689         }
690 };
691
692
693 _b.Ajax.prototype.processReqChange = function()
694 {
695         
696         // only if req shows "loaded"
697         if (this.req.readyState == 4) {
698                 // only if "OK"
699                 if (this.req.status == 200)
700                 {
701                         this.onComplete( this.req );
702                 } else {
703                         this.onError( this.req.status );
704                 }
705         }
706 };
707
708
709
710
711
712
713
714
715
716
717 // DOM PROTOTYPE _____________________________________________
718
719
720 if (typeof(_b.DOM) == "undefined")
721         _b.DOM = {};
722
723
724
725 /* create element */
726 _b.DOM.cE = function ( type, attr, cont, html )
727 {
728         var ne = document.createElement( type );
729         if (!ne)
730                 return 0;
731                 
732         for (var a in attr)
733                 ne[a] = attr[a];
734         
735         var t = typeof(cont);
736         
737         if (t == "string" && !html)
738                 ne.appendChild( document.createTextNode(cont) );
739         else if (t == "string" && html)
740                 ne.innerHTML = cont;
741         else if (t == "object")
742                 ne.appendChild( cont );
743
744         return ne;
745 };
746
747
748
749 /* get element */
750 _b.DOM.gE = function ( e )
751 {
752         var t=typeof(e);
753         if (t == "undefined")
754                 return 0;
755         else if (t == "string")
756         {
757                 var re = document.getElementById( e );
758                 if (!re)
759                         return 0;
760                 else if (typeof(re.appendChild) != "undefined" )
761                         return re;
762                 else
763                         return 0;
764         }
765         else if (typeof(e.appendChild) != "undefined")
766                 return e;
767         else
768                 return 0;
769 };
770
771
772
773 /* remove element */
774 _b.DOM.remE = function ( ele )
775 {
776         var e = this.gE(ele);
777         
778         if (!e)
779                 return 0;
780         else if (e.parentNode.removeChild(e))
781                 return true;
782         else
783                 return 0;
784 };
785
786
787
788 /* get position */
789 _b.DOM.getPos = function ( e )
790 {
791         var e = this.gE(e);
792
793         var obj = e;
794
795         var curleft = 0;
796         if (obj.offsetParent)
797         {
798                 while (obj.offsetParent)
799                 {
800                         curleft += obj.offsetLeft;
801                         obj = obj.offsetParent;
802                 }
803         }
804         else if (obj.x)
805                 curleft += obj.x;
806         
807         var obj = e;
808         
809         var curtop = 0;
810         if (obj.offsetParent)
811         {
812                 while (obj.offsetParent)
813                 {
814                         curtop += obj.offsetTop;
815                         obj = obj.offsetParent;
816                 }
817         }
818         else if (obj.y)
819                 curtop += obj.y;
820
821         return {x:curleft, y:curtop};
822 };
823
824
825
826
827
828
829
830
831
832
833 // FADER PROTOTYPE _____________________________________________
834
835
836
837 if (typeof(_b.Fader) == "undefined")
838         _b.Fader = {};
839
840
841
842
843
844 _b.Fader = function (ele, from, to, fadetime, callback)
845 {       
846         if (!ele)
847                 return 0;
848         
849         this.e = ele;
850         
851         this.from = from;
852         this.to = to;
853         
854         this.cb = callback;
855         
856         this.nDur = fadetime;
857                 
858         this.nInt = 50;
859         this.nTime = 0;
860         
861         var p = this;
862         this.nID = setInterval(function() { p._fade() }, this.nInt);
863 };
864
865
866
867
868 _b.Fader.prototype._fade = function()
869 {
870         this.nTime += this.nInt;
871         
872         var ieop = Math.round( this._tween(this.nTime, this.from, this.to, this.nDur) * 100 );
873         var op = ieop / 100;
874         
875         if (this.e.filters) // internet explorer
876         {
877                 try
878                 {
879                         this.e.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
880                 } catch (e) { 
881                         // If it is not set initially, the browser will throw an error.  This will set it if it is not set yet.
882                         this.e.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+ieop+')';
883                 }
884         }
885         else // other browsers
886         {
887                 this.e.style.opacity = op;
888         }
889         
890         
891         if (this.nTime == this.nDur)
892         {
893                 clearInterval( this.nID );
894                 if (this.cb != undefined)
895                         this.cb();
896         }
897 };
898
899
900
901 _b.Fader.prototype._tween = function(t,b,c,d)
902 {
903         return b + ( (c-b) * (t/d) );
904 };