includes/clientside/static/autofill.js
changeset 174 d74ff822acc9
child 212 d57af0b0302e
equal deleted inserted replaced
173:91127e62f38f 174:d74ff822acc9
       
     1 /**
       
     2  * Javascript auto-completion for form fields.
       
     3  */
       
     4  
       
     5 var af_current = false;
       
     6  
       
     7 function AutofillUsername(parent, event, allowanon)
       
     8 {
       
     9   // if this is IE, use the old code
       
    10   if ( IE )
       
    11   {
       
    12     ajaxUserNameComplete(parent);
       
    13     return false;
       
    14   }
       
    15   if ( parent.afobj )
       
    16   {
       
    17     parent.afobj.go();
       
    18     return true;
       
    19   }
       
    20   
       
    21   parent.autocomplete = 'off';
       
    22   parent.setAttribute('autocomplete', 'off');
       
    23   
       
    24   this.repeat = false;
       
    25   this.event = event;
       
    26   this.box_id = false;
       
    27   this.boxes = new Array();
       
    28   this.state = false;
       
    29   this.allowanon = ( allowanon ) ? true : false;
       
    30   
       
    31   if ( !parent.id )
       
    32     parent.id = 'afuser_' + Math.floor(Math.random() * 1000000);
       
    33   
       
    34   this.field_id = parent.id;
       
    35   
       
    36   // constants
       
    37   this.KEY_UP    = 38;
       
    38   this.KEY_DOWN  = 40;
       
    39   this.KEY_ESC   = 27;
       
    40   this.KEY_TAB   = 9;
       
    41   this.KEY_ENTER = 13;
       
    42   
       
    43   // response cache
       
    44   this.responses = new Object();
       
    45   
       
    46   // ajax placeholder
       
    47   this.process_dataset = function(resp_json)
       
    48   {
       
    49     // window.console.info('Processing the following dataset.');
       
    50     // window.console.debug(resp_json);
       
    51     var autofill = this;
       
    52     
       
    53     if ( typeof(autofill.event) == 'object' )
       
    54     {
       
    55       if ( autofill.event.keyCode )
       
    56       {
       
    57         if ( autofill.event.keyCode == autofill.KEY_ENTER && autofill.boxes.length < 1 && !autofill.box_id )
       
    58         {
       
    59           // user hit enter after accepting a suggestion - submit the form
       
    60           var frm = findParentForm($(autofill.field_id).object);
       
    61           frm._af_acting = false;
       
    62           frm.submit();
       
    63           // window.console.info('Submitting form');
       
    64           return false;
       
    65         }
       
    66         if ( autofill.event.keyCode == autofill.KEY_UP || autofill.event.keyCode == autofill.KEY_DOWN || autofill.event.keyCode == autofill.KEY_ESC || autofill.event.keyCode == autofill.KEY_TAB || autofill.event.keyCode == autofill.KEY_ENTER )
       
    67         {
       
    68           autofill.keyhandler();
       
    69           // window.console.info('Control key detected, called keyhandler and exiting');
       
    70           return true;
       
    71         }
       
    72       }
       
    73     }
       
    74     
       
    75     if ( this.box_id )
       
    76     {
       
    77       this.destroy();
       
    78       // window.console.info('already have a box open - destroying and exiting');
       
    79       //return false;
       
    80     }
       
    81     
       
    82     var users = new Array();
       
    83     for ( var i = 0; i < resp_json.users_real.length; i++ )
       
    84     {
       
    85       try
       
    86       {
       
    87         var user = resp_json.users_real[i].toLowerCase();
       
    88         var inp  = $(autofill.field_id).object.value;
       
    89         inp = inp.toLowerCase();
       
    90         if ( user.indexOf(inp) > -1 )
       
    91         {
       
    92           users.push(resp_json.users_real[i]);
       
    93         }
       
    94       }
       
    95       catch(e)
       
    96       {
       
    97         users.push(resp_json.users_real[i]);
       
    98       }
       
    99     }
       
   100 
       
   101     // This was used ONLY for debugging the DOM and list logic    
       
   102     // resp_json.users = resp_json.users_real;
       
   103     
       
   104     // construct table
       
   105     var div = document.createElement('div');
       
   106     div.className = 'tblholder';
       
   107     div.style.clip = 'rect(0px,auto,auto,0px)';
       
   108     div.style.maxHeight = '200px';
       
   109     div.style.overflow = 'auto';
       
   110     div.style.zIndex = '9999';
       
   111     var table = document.createElement('table');
       
   112     table.border = '0';
       
   113     table.cellSpacing = '1';
       
   114     table.cellPadding = '3';
       
   115     
       
   116     var tr = document.createElement('tr');
       
   117     var th = document.createElement('th');
       
   118     th.appendChild(document.createTextNode('Username suggestions'));
       
   119     tr.appendChild(th);
       
   120     table.appendChild(tr);
       
   121     
       
   122     if ( users.length < 1 )
       
   123     {
       
   124       var tr = document.createElement('tr');
       
   125       var td = document.createElement('td');
       
   126       td.className = 'row1';
       
   127       td.appendChild(document.createTextNode('No suggestions'));
       
   128       td.afobj = autofill;
       
   129       tr.appendChild(td);
       
   130       table.appendChild(tr);
       
   131     }
       
   132     else
       
   133       
       
   134       for ( var i = 0; i < users.length; i++ )
       
   135       {
       
   136         var user = users[i];
       
   137         var tr = document.createElement('tr');
       
   138         var td = document.createElement('td');
       
   139         td.className = ( i == 0 ) ? 'row2' : 'row1';
       
   140         td.appendChild(document.createTextNode(user));
       
   141         td.afobj = autofill;
       
   142         td.style.cursor = 'pointer';
       
   143         td.onclick = function()
       
   144         {
       
   145           this.afobj.set(this.firstChild.nodeValue);
       
   146         }
       
   147         tr.appendChild(td);
       
   148         table.appendChild(tr);
       
   149       }
       
   150       
       
   151     // Finalize div
       
   152     var tb_top    = $(autofill.field_id).Top();
       
   153     var tb_height = $(autofill.field_id).Height();
       
   154     var af_top    = tb_top + tb_height - 9;
       
   155     var tb_left   = $(autofill.field_id).Left();
       
   156     var af_left   = tb_left;
       
   157     
       
   158     div.style.position = 'absolute';
       
   159     div.style.left = af_left + 'px';
       
   160     div.style.top  = af_top  + 'px';
       
   161     div.style.width = '200px';
       
   162     div.style.fontSize = '7pt';
       
   163     div.style.fontFamily = 'Trebuchet MS, arial, helvetica, sans-serif';
       
   164     div.id = 'afuserdrop_' + Math.floor(Math.random() * 1000000);
       
   165     div.appendChild(table);
       
   166     
       
   167     autofill.boxes.push(div.id);
       
   168     autofill.box_id = div.id;
       
   169     if ( users.length > 0 )
       
   170       autofill.state = users[0];
       
   171     
       
   172     var body = document.getElementsByTagName('body')[0];
       
   173     body.appendChild(div);
       
   174     
       
   175     autofill.repeat = true;
       
   176   }
       
   177   
       
   178   // perform ajax call
       
   179   this.fetch_and_process = function()
       
   180   {
       
   181     af_current = this;
       
   182     var processResponse = function()
       
   183     {
       
   184       if ( ajax.readyState == 4 )
       
   185       {
       
   186         var afobj = af_current;
       
   187         af_current = false;
       
   188         // parse the JSON response
       
   189         var response = String(ajax.responseText) + ' ';
       
   190         if ( response.substr(0,1) != '{' )
       
   191         {
       
   192           new messagebox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>');
       
   193           return false;
       
   194         }
       
   195         if ( $(afobj.field_id).object.value.length < 3 )
       
   196           return false;
       
   197         var resp_json = parseJSON(response);
       
   198         var resp_code = $(afobj.field_id).object.value.toLowerCase().substr(0, 3);
       
   199         afobj.responses[resp_code] = resp_json;
       
   200         afobj.process_dataset(resp_json);
       
   201       }
       
   202     }
       
   203     var usernamefragment = ajaxEscape($(this.field_id).object.value);
       
   204     ajaxGet(stdAjaxPrefix + '&_mode=fillusername&name=' + usernamefragment + '&allowanon=' + ( this.allowanon ? '1' : '0' ), processResponse);
       
   205   }
       
   206   
       
   207   this.go = function()
       
   208   {
       
   209     if ( document.getElementById(this.field_id).value.length < 3 )
       
   210     {
       
   211       this.destroy();
       
   212       return false;
       
   213     }
       
   214     
       
   215     if ( af_current )
       
   216       return false;
       
   217     
       
   218     var resp_code = $(this.field_id).object.value.toLowerCase().substr(0, 3);
       
   219     if ( this.responses.length < 1 || ! this.responses[ resp_code ] )
       
   220     {
       
   221       // window.console.info('Cannot find dataset ' + resp_code + ' in cache, sending AJAX request');
       
   222       this.fetch_and_process();
       
   223     }
       
   224     else
       
   225     {
       
   226       // window.console.info('Using cached dataset: ' + resp_code);
       
   227       var resp_json = this.responses[ resp_code ];
       
   228       this.process_dataset(resp_json);
       
   229     }
       
   230     document.getElementById(this.field_id).onkeyup = function(event)
       
   231     {
       
   232       this.afobj.event = event;
       
   233       this.afobj.go();
       
   234     }
       
   235     document.getElementById(this.field_id).onkeydown = function(event)
       
   236     {
       
   237       var form = findParentForm(this);
       
   238       if ( typeof(event) != 'object' )
       
   239         var event = window.event;
       
   240       if ( typeof(event) == 'object' )
       
   241       {
       
   242         if ( event.keyCode == this.afobj.KEY_ENTER && this.afobj.boxes.length < 1 && !this.afobj.box_id )
       
   243         {
       
   244           // user hit enter after accepting a suggestion - submit the form
       
   245           form._af_acting = false;
       
   246           return true;
       
   247         }
       
   248       }
       
   249       form._af_acting = true;
       
   250     }
       
   251   }
       
   252   
       
   253   this.keyhandler = function()
       
   254   {
       
   255     var key = this.event.keyCode;
       
   256     if ( key == this.KEY_ENTER && !this.repeat )
       
   257     {
       
   258       var form = findParentForm($(this.field_id).object);
       
   259         form._af_acting = false;
       
   260       return true;
       
   261     }
       
   262     switch(key)
       
   263     {
       
   264       case this.KEY_UP:
       
   265         this.focus_up();
       
   266         break;
       
   267       case this.KEY_DOWN:
       
   268         this.focus_down();
       
   269         break;
       
   270       case this.KEY_ESC:
       
   271         this.destroy();
       
   272         break;
       
   273       case this.KEY_TAB:
       
   274         this.destroy();
       
   275         break;
       
   276       case this.KEY_ENTER:
       
   277         this.set();
       
   278         break;
       
   279     }
       
   280     
       
   281     var form = findParentForm($(this.field_id).object);
       
   282       form._af_acting = false;
       
   283   }
       
   284   
       
   285   this.get_state_td = function()
       
   286   {
       
   287     var div = document.getElementById(this.box_id);
       
   288     if ( !div )
       
   289       return false;
       
   290     if ( !this.state )
       
   291       return false;
       
   292     var table = div.firstChild;
       
   293     for ( var i = 1; i < table.childNodes.length; i++ )
       
   294     {
       
   295       // the table is DOM-constructed so no cruddy HTML hacks :-)
       
   296       var child = table.childNodes[i];
       
   297       var tn = child.firstChild.firstChild;
       
   298       if ( tn.nodeValue == this.state )
       
   299         return child.firstChild;
       
   300     }
       
   301     return false;
       
   302   }
       
   303   
       
   304   this.focus_down = function()
       
   305   {
       
   306     var state_td = this.get_state_td();
       
   307     if ( !state_td )
       
   308       return false;
       
   309     if ( state_td.parentNode.nextSibling )
       
   310     {
       
   311       // Ooh boy, DOM stuff can be so complicated...
       
   312       // <tr>  -->  <tr>
       
   313       // <td>       <td>
       
   314       // user       user
       
   315       
       
   316       var newstate = state_td.parentNode.nextSibling.firstChild.firstChild.nodeValue;
       
   317       if ( !newstate )
       
   318         return false;
       
   319       this.state = newstate;
       
   320       state_td.className = 'row1';
       
   321       state_td.parentNode.nextSibling.firstChild.className = 'row2';
       
   322       
       
   323       // Exception - automatically scroll around if the item is off-screen
       
   324       var height = $(this.box_id).Height();
       
   325       var top = $(this.box_id).object.scrollTop;
       
   326       var scroll_bottom = height + top;
       
   327       
       
   328       var td_top = $(state_td.parentNode.nextSibling.firstChild).Top() - $(this.box_id).Top();
       
   329       var td_height = $(state_td.parentNode.nextSibling.firstChild).Height();
       
   330       var td_bottom = td_top + td_height;
       
   331       
       
   332       if ( td_bottom > scroll_bottom )
       
   333       {
       
   334         var scrollY = td_top - height + 2*td_height - 7;
       
   335         // window.console.debug(scrollY);
       
   336         $(this.box_id).object.scrollTop = scrollY;
       
   337         /*
       
   338         var newtd = state_td.parentNode.nextSibling.firstChild;
       
   339         var a = document.createElement('a');
       
   340         var id = 'autofill' + Math.floor(Math.random() * 100000);
       
   341         a.name = id;
       
   342         a.id = id;
       
   343         newtd.appendChild(a);
       
   344         window.location.hash = '#' + id;
       
   345         */
       
   346         
       
   347         // In firefox, scrolling like that makes the field get unfocused
       
   348         $(this.field_id).object.focus();
       
   349       }
       
   350     }
       
   351     else
       
   352     {
       
   353       return false;
       
   354     }
       
   355   }
       
   356   
       
   357   this.focus_up = function()
       
   358   {
       
   359     var state_td = this.get_state_td();
       
   360     if ( !state_td )
       
   361       return false;
       
   362     if ( state_td.parentNode.previousSibling && state_td.parentNode.previousSibling.firstChild.tagName != 'TH' )
       
   363     {
       
   364       // Ooh boy, DOM stuff can be so complicated...
       
   365       // <tr>  <--  <tr>
       
   366       // <td>       <td>
       
   367       // user       user
       
   368       
       
   369       var newstate = state_td.parentNode.previousSibling.firstChild.firstChild.nodeValue;
       
   370       if ( !newstate )
       
   371       {
       
   372         return false;
       
   373       }
       
   374       this.state = newstate;
       
   375       state_td.className = 'row1';
       
   376       state_td.parentNode.previousSibling.firstChild.className = 'row2';
       
   377       
       
   378       // Exception - automatically scroll around if the item is off-screen
       
   379       var top = $(this.box_id).object.scrollTop;
       
   380       
       
   381       var td_top = $(state_td.parentNode.previousSibling.firstChild).Top() - $(this.box_id).Top();
       
   382       
       
   383       if ( td_top < top )
       
   384       {
       
   385         $(this.box_id).object.scrollTop = td_top - 10;
       
   386         /*
       
   387         var newtd = state_td.parentNode.previousSibling.firstChild;
       
   388         var a = document.createElement('a');
       
   389         var id = 'autofill' + Math.floor(Math.random() * 100000);
       
   390         a.name = id;
       
   391         a.id = id;
       
   392         newtd.appendChild(a);
       
   393         window.location.hash = '#' + id;
       
   394         */
       
   395         
       
   396         // In firefox, scrolling like that makes the field get unfocused
       
   397         $(this.field_id).object.focus();
       
   398       }
       
   399     }
       
   400     else
       
   401     {
       
   402       $(this.box_id).object.scrollTop = 0;
       
   403       return false;
       
   404     }
       
   405   }
       
   406   
       
   407   this.destroy = function()
       
   408   {
       
   409     this.repeat = false;
       
   410     var body = document.getElementsByTagName('body')[0];
       
   411     var div = document.getElementById(this.box_id);
       
   412     if ( !div )
       
   413       return false;
       
   414     setTimeout('var body = document.getElementsByTagName("body")[0]; body.removeChild(document.getElementById("'+div.id+'"));', 20);
       
   415     // hackish workaround for divs that stick around past their welcoming period
       
   416     for ( var i = 0; i < this.boxes.length; i++ )
       
   417     {
       
   418       var div = document.getElementById(this.boxes[i]);
       
   419       if ( div )
       
   420         setTimeout('var body = document.getElementsByTagName("body")[0]; var div = document.getElementById("'+div.id+'"); if ( div ) body.removeChild(div);', 20);
       
   421       delete(this.boxes[i]);
       
   422     }
       
   423     this.box_id = false;
       
   424     this.state = false;
       
   425   }
       
   426   
       
   427   this.set = function(val)
       
   428   {
       
   429     var ta = document.getElementById(this.field_id);
       
   430     if ( val )
       
   431       ta.value = val;
       
   432     else if ( this.state )
       
   433       ta.value = this.state;
       
   434     this.destroy();
       
   435   }
       
   436   
       
   437   this.sleep = function()
       
   438   {
       
   439     if ( this.box_id )
       
   440     {
       
   441       var div = document.getElementById(this.box_id);
       
   442       div.style.display = 'none';
       
   443     }
       
   444     var el = $(this.field_id).object;
       
   445     var fr = findParentForm(el);
       
   446     el._af_acting = false;
       
   447   }
       
   448   
       
   449   this.wake = function()
       
   450   {
       
   451     if ( this.box_id )
       
   452     {
       
   453       var div = document.getElementById(this.box_id);
       
   454       div.style.display = 'block';
       
   455     }
       
   456   }
       
   457   
       
   458   parent.onblur = function()
       
   459   {
       
   460     af_current = this.afobj;
       
   461     window.setTimeout('if ( af_current ) af_current.sleep(); af_current = false;', 50);
       
   462   }
       
   463   
       
   464   parent.onfocus = function()
       
   465   {
       
   466     af_current = this.afobj;
       
   467     window.setTimeout('if ( af_current ) af_current.wake(); af_current = false;', 50);
       
   468   }
       
   469   
       
   470   parent.afobj = this;
       
   471   var frm = findParentForm(parent);
       
   472   if ( frm.onsubmit )
       
   473   {
       
   474     frm.orig_onsubmit = frm.onsubmit;
       
   475     frm.onsubmit = function(e)
       
   476     {
       
   477       if ( this._af_acting )
       
   478         return false;
       
   479       this.orig_onsubmit(e);
       
   480     }
       
   481   }
       
   482   else
       
   483   {
       
   484     frm.onsubmit = function()
       
   485     {
       
   486       if ( this._af_acting )
       
   487         return false;
       
   488     }
       
   489   }
       
   490   
       
   491   if ( parent.value.length < 3 )
       
   492   {
       
   493     this.destroy();
       
   494     return false;
       
   495   }
       
   496 }
       
   497 
       
   498 function findParentForm(o)
       
   499 {
       
   500   if ( o.tagName == 'FORM' )
       
   501     return o;
       
   502   while(true)
       
   503   {
       
   504     o = o.parentNode;
       
   505     if ( !o )
       
   506       return false;
       
   507     if ( o.tagName == 'FORM' )
       
   508       return o;
       
   509   }
       
   510   return false;
       
   511 }
       
   512