includes/clientside/static/autofill.js
changeset 581 5e8fd89c02ea
parent 550 685e839d934e
child 582 a38876c0793c
--- a/includes/clientside/static/autofill.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/autofill.js	Sun Jun 22 18:13:59 2008 -0400
@@ -2,505 +2,145 @@
  * Javascript auto-completion for form fields. This supercedes the code in autocomplete.js for MOZILLA ONLY. It doesn't seem to work real
  * well with other browsers yet.
  */
- 
-var af_current = false;
- 
-function AutofillUsername(parent, event, allowanon)
-{
-  // if this is IE, use the old code
-  if ( IE )
-  {
-    ajaxUserNameComplete(parent);
-    return false;
-  }
-  if ( parent.afobj )
-  {
-    parent.afobj.go();
-    return true;
-  }
-  
-  parent.autocomplete = 'off';
-  parent.setAttribute('autocomplete', 'off');
+
+// fill schemas
+var autofill_schemas = {};
+
+// default, generic schema
+autofill_schemas.generic = {
+  template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
+            '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
+            '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
+            '      <td class="row1" spry:suggest="{name}">{name}</td>' + "\n" +
+            '    </tr>' + "\n" +
+            '  </table>' + "\n" +
+            '</div>',
   
-  this.repeat = false;
-  this.event = event;
-  this.box_id = false;
-  this.boxes = new Array();
-  this.state = false;
-  this.allowanon = ( allowanon ) ? true : false;
-  
-  if ( !parent.id )
-    parent.id = 'afuser_' + Math.floor(Math.random() * 1000000);
-  
-  this.field_id = parent.id;
-  
-  // constants
-  this.KEY_UP    = 38;
-  this.KEY_DOWN  = 40;
-  this.KEY_ESC   = 27;
-  this.KEY_TAB   = 9;
-  this.KEY_ENTER = 13;
-  
-  // response cache
-  this.responses = new Object();
-  
-  // ajax placeholder
-  this.process_dataset = function(resp_json)
+  init: function(element, fillclass)
   {
-    // window.console.info('Processing the following dataset.');
-    // window.console.debug(resp_json);
-    var autofill = this;
+    // setup the dataset
+    window["autofill_region_" + element.id] = new Spry.Data.JSONDataSet(makeUrlNS('Special', 'Autofill', 'type=' + fillclass));
     
-    if ( typeof(autofill.event) == 'object' )
-    {
-      if ( autofill.event.keyCode )
-      {
-        if ( autofill.event.keyCode == autofill.KEY_ENTER && autofill.boxes.length < 1 && !autofill.box_id )
-        {
-          // user hit enter after accepting a suggestion - submit the form
-          var frm = findParentForm($dynano(autofill.field_id).object);
-          frm._af_acting = false;
-          frm.submit();
-          // window.console.info('Submitting form');
-          return false;
-        }
-        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 )
-        {
-          autofill.keyhandler();
-          // window.console.info('Control key detected, called keyhandler and exiting');
-          return true;
-        }
-      }
-    }
+    // inject our HTML wrapper
+    var template = this.template.replace(new RegExp('--ID--', 'g'), element.id);
+    var wrapper = element.parentNode; // document.createElement('div');
+    wrapper.id = 'autofill_wrap_' + element.id;
     
-    if ( this.box_id )
-    {
-      this.destroy();
-      // window.console.info('already have a box open - destroying and exiting');
-      //return false;
-    }
-    
-    var users = new Array();
-    for ( var i = 0; i < resp_json.users_real.length; i++ )
-    {
-      try
-      {
-        var user = resp_json.users_real[i].toLowerCase();
-        var inp  = $dynano(autofill.field_id).object.value;
-        inp = inp.toLowerCase();
-        if ( user.indexOf(inp) > -1 )
-        {
-          users.push(resp_json.users_real[i]);
-        }
-      }
-      catch(e)
-      {
-        users.push(resp_json.users_real[i]);
-      }
-    }
-
-    // This was used ONLY for debugging the DOM and list logic    
-    // resp_json.users = resp_json.users_real;
+    // a bunch of hacks to add a spry wrapper
+    // var el2 = element.cloneNode(false);
+    // wrapper.appendChild(el2);
+    wrapper.innerHTML += template;
+    // insertAfter(element.parentNode, wrapper, element);
+    // element.parentNode.removeChild(element);
+    // element = el2;
     
-    // construct table
-    var div = document.createElement('div');
-    div.className = 'tblholder';
-    div.style.clip = 'rect(0px,auto,auto,0px)';
-    div.style.maxHeight = '200px';
-    div.style.overflow = 'auto';
-    div.style.zIndex = '9999';
-    var table = document.createElement('table');
-    table.border = '0';
-    table.cellSpacing = '1';
-    table.cellPadding = '3';
+    var autosuggest = new Spry.Widget.AutoSuggest("autofill_wrap_" + element.id, element.id + '_region', window["autofill_region_" + element.id], 'name', {loadFromServer: true, urlParam: 'userinput', hoverSuggestClass: 'row2', minCharsType: 3});
+  }
+};
+
+function autofill_init_element(element, params)
+{
+  if ( element.parentNode.id.match(/^autofill_wrap_/) )
+    return false;
+  
+  params = params || {};
+  // assign an ID if it doesn't have one yet
+  if ( !element.id )
+  {
+    element.id = 'autofill_' + Math.floor(Math.random() * 100000);
+  }
+  var id = element.id;
+  
+  // get the fill type
+  var fillclass = element.className;
+  fillclass = fillclass.split(' ');
+  fillclass = fillclass[1];
+  
+  var schema = ( autofill_schemas[fillclass] ) ? autofill_schemas[fillclass] : autofill_schemas['generic'];
+  if ( typeof(schema.init) != 'function' )
+  {
+    schema.init = autofill_schemas.generic.init;
+  }
+  schema.init(element, fillclass, params);
+  
+  element.af_initted = true;
+}
+
+var autofill_onload = function()
+{
+  autofill_schemas.username = {
+    template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
+              '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
+              '    <tr>' + "\n" +
+              '      <th>' + $lang.get('user_autofill_heading_suggestions') + '</th>' + "\n" +
+              '    </tr>' + "\n" +
+              '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
+              '      <td class="row1" spry:suggest="{name}">{name_highlight}<br /><small style="{rank_style}">{rank_title}</small></td>' + "\n" +
+              '    </tr>' + "\n" +
+              '  </table>' + "\n" +
+              '</div>',
     
-    var tr = document.createElement('tr');
-    var th = document.createElement('th');
-    th.appendChild(document.createTextNode($lang.get('user_autofill_heading_suggestions')));
-    tr.appendChild(th);
-    table.appendChild(tr);
-    
-    if ( users.length < 1 )
+    init: function(element, fillclass, params)
     {
-      var tr = document.createElement('tr');
-      var td = document.createElement('td');
-      td.className = 'row1';
-      td.appendChild(document.createTextNode($lang.get('user_autofill_msg_no_suggestions')));
-      td.afobj = autofill;
-      tr.appendChild(td);
-      table.appendChild(tr);
-    }
-    else
-      
-      for ( var i = 0; i < users.length; i++ )
-      {
-        var user = users[i];
-        var tr = document.createElement('tr');
-        var td = document.createElement('td');
-        td.className = ( i == 0 ) ? 'row2' : 'row1';
-        td.appendChild(document.createTextNode(user));
-        td.afobj = autofill;
-        td.style.cursor = 'pointer';
-        td.onclick = function()
-        {
-          this.afobj.set(this.firstChild.nodeValue);
-        }
-        tr.appendChild(td);
-        table.appendChild(tr);
-      }
+      // calculate positions before spry f***s everything up
+      var top = $dynano(element).Top() + $dynano(element).Height() - 10; // tblholder has 10px top margin
+      var left = $dynano(element).Left();
       
-    // Finalize div
-    var tb_top    = $dynano(autofill.field_id).Top();
-    var tb_height = $dynano(autofill.field_id).Height();
-    var af_top    = tb_top + tb_height - 9;
-    var tb_left   = $dynano(autofill.field_id).Left();
-    var af_left   = tb_left;
-    
-    div.style.position = 'absolute';
-    div.style.left = af_left + 'px';
-    div.style.top  = af_top  + 'px';
-    div.style.width = '200px';
-    div.style.fontSize = '7pt';
-    div.style.fontFamily = 'Trebuchet MS, arial, helvetica, sans-serif';
-    div.id = 'afuserdrop_' + Math.floor(Math.random() * 1000000);
-    div.appendChild(table);
-    
-    autofill.boxes.push(div.id);
-    autofill.box_id = div.id;
-    if ( users.length > 0 )
-      autofill.state = users[0];
-    
-    var body = document.getElementsByTagName('body')[0];
-    body.appendChild(div);
-    
-    autofill.repeat = true;
-  }
+      var allow_anon = ( params.allow_anon ) ? '1' : '0';
+      // setup the dataset
+      window["autofill_region_" + element.id] = new Spry.Data.JSONDataSet(makeUrlNS('Special', 'Autofill', 'type=' + fillclass + '&allow_anon' + allow_anon));
+      Spry.Data.initRegions(document.body);
+      (window["autofill_region_" + element.id]).loadData();
+      
+      // inject our HTML wrapper
+      var template = this.template.replace(new RegExp('--ID--', 'g'), element.id);
+      var wrapper = element.parentNode; // document.createElement('div');
+      wrapper.id = 'autofill_wrap_' + element.id;
+      
+      // a bunch of hacks to add a spry wrapper
+      wrapper.innerHTML = template + wrapper.innerHTML;
+      
+      var autosuggest = new Spry.Widget.AutoSuggest("autofill_wrap_" + element.id, element.id + '_region', window["autofill_region_" + element.id], 'name', {loadFromServer: true, urlParam: 'userinput', hoverSuggestClass: 'row2', minCharsType: 3});
+      var regiondiv = document.getElementById(element.id + '_region');
+      regiondiv.style.position = 'absolute';
+      regiondiv.style.top = top + 'px';
+      regiondiv.style.left = left + 'px';
+    }
+  };
   
-  // perform ajax call
-  this.fetch_and_process = function()
-  {
-    af_current = this;
-    var processResponse = function()
-    {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        var afobj = af_current;
-        af_current = false;
-        // parse the JSON response
-        var response = String(ajax.responseText) + ' ';
-        if ( response.substr(0,1) != '{' )
-        {
-          new MessageBox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>');
-          return false;
-        }
-        if ( $dynano(afobj.field_id).object.value.length < 3 )
-          return false;
-        var resp_json = parseJSON(response);
-        var resp_code = $dynano(afobj.field_id).object.value.toLowerCase().substr(0, 3);
-        afobj.responses[resp_code] = resp_json;
-        afobj.process_dataset(resp_json);
-      }
-    }
-    var usernamefragment = ajaxEscape($dynano(this.field_id).object.value);
-    ajaxGet(stdAjaxPrefix + '&_mode=fillusername&name=' + usernamefragment + '&allowanon=' + ( this.allowanon ? '1' : '0' ), processResponse);
+  autofill_schemas.page = {
+    template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
+              '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
+              '    <tr>' + "\n" +
+              '      <th colspan="2">' + $lang.get('page_autosuggest_heading') + '</th>' + "\n" +
+              '    </tr>' + "\n" +
+              '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
+              '      <td class="row1" spry:suggest="{page_id}">{pid_highlight}<br /><small>{name_highlight}</small></td>' + "\n" +
+              '    </tr>' + "\n" +
+              '  </table>' + "\n" +
+              '</div>'
   }
   
-  this.go = function()
-  {
-    if ( document.getElementById(this.field_id).value.length < 3 )
-    {
-      this.destroy();
-      return false;
-    }
-    
-    if ( af_current )
-      return false;
-    
-    var resp_code = $dynano(this.field_id).object.value.toLowerCase().substr(0, 3);
-    if ( this.responses.length < 1 || ! this.responses[ resp_code ] )
-    {
-      // window.console.info('Cannot find dataset ' + resp_code + ' in cache, sending AJAX request');
-      this.fetch_and_process();
-    }
-    else
-    {
-      // window.console.info('Using cached dataset: ' + resp_code);
-      var resp_json = this.responses[ resp_code ];
-      this.process_dataset(resp_json);
-    }
-    document.getElementById(this.field_id).onkeyup = function(event)
-    {
-      this.afobj.event = event;
-      this.afobj.go();
-    }
-    document.getElementById(this.field_id).onkeydown = function(event)
-    {
-      var form = findParentForm(this);
-      if ( typeof(event) != 'object' )
-        var event = window.event;
-      if ( typeof(event) == 'object' )
-      {
-        if ( event.keyCode == this.afobj.KEY_ENTER && this.afobj.boxes.length < 1 && !this.afobj.box_id )
-        {
-          // user hit enter after accepting a suggestion - submit the form
-          form._af_acting = false;
-          return true;
-        }
-        else
-        {
-          form._af_acting = true;
-          return true;
-        }
-      }
-    }
-  }
+  var inputs = document.getElementsByClassName('input', 'autofill');
   
-  this.keyhandler = function()
-  {
-    var key = this.event.keyCode;
-    if ( key == this.KEY_ENTER && !this.repeat )
-    {
-      submitAuthorized = true;
-      var form = findParentForm($dynano(this.field_id).object);
-      form._af_acting = false;
-      return true;
-    }
-    switch(key)
-    {
-      case this.KEY_UP:
-        this.focus_up();
-        break;
-      case this.KEY_DOWN:
-        this.focus_down();
-        break;
-      case this.KEY_ESC:
-        this.destroy();
-        break;
-      case this.KEY_TAB:
-        this.destroy();
-        break;
-      case this.KEY_ENTER:
-        this.set();
-        break;
-    }
-    
-    var form = findParentForm($dynano(this.field_id).object);
-      form._af_acting = false;
-  }
-  
-  this.get_state_td = function()
-  {
-    var div = document.getElementById(this.box_id);
-    if ( !div )
-      return false;
-    if ( !this.state )
-      return false;
-    var table = div.firstChild;
-    for ( var i = 1; i < table.childNodes.length; i++ )
-    {
-      // the table is DOM-constructed so no cruddy HTML hacks :-)
-      var child = table.childNodes[i];
-      var tn = child.firstChild.firstChild;
-      if ( tn.nodeValue == this.state )
-        return child.firstChild;
-    }
-    return false;
-  }
-  
-  this.focus_down = function()
+  for ( var i = 0; i < inputs.length; i++ )
   {
-    var state_td = this.get_state_td();
-    if ( !state_td )
-      return false;
-    if ( state_td.parentNode.nextSibling )
-    {
-      // Ooh boy, DOM stuff can be so complicated...
-      //   <tr>   →   <tr>
-      // ↑ <td>       <td> ↓
-      //   user       user
-      
-      var newstate = state_td.parentNode.nextSibling.firstChild.firstChild.nodeValue;
-      if ( !newstate )
-        return false;
-      this.state = newstate;
-      state_td.className = 'row1';
-      state_td.parentNode.nextSibling.firstChild.className = 'row2';
-      
-      // Exception - automatically scroll around if the item is off-screen
-      var height = $dynano(this.box_id).Height();
-      var top = $dynano(this.box_id).object.scrollTop;
-      var scroll_bottom = height + top;
-      
-      var td_top = $dynano(state_td.parentNode.nextSibling.firstChild).Top() - $dynano(this.box_id).Top();
-      var td_height = $dynano(state_td.parentNode.nextSibling.firstChild).Height();
-      var td_bottom = td_top + td_height;
-      
-      if ( td_bottom > scroll_bottom )
-      {
-        var scrollY = td_top - height + 2*td_height - 7;
-        // window.console.debug(scrollY);
-        $dynano(this.box_id).object.scrollTop = scrollY;
-        /*
-        var newtd = state_td.parentNode.nextSibling.firstChild;
-        var a = document.createElement('a');
-        var id = 'autofill' + Math.floor(Math.random() * 100000);
-        a.name = id;
-        a.id = id;
-        newtd.appendChild(a);
-        window.location.hash = '#' + id;
-        */
-        
-        // In firefox, scrolling like that makes the field get unfocused
-        $dynano(this.field_id).object.focus();
-      }
-    }
-    else
-    {
-      return false;
-    }
+    autofill_init_element(inputs[i]);
   }
+}
+
+addOnloadHook(autofill_onload);
+
+function AutofillUsername(element, event, allowanon)
+{
+  element.onkeyup = element.onkeydown = element.onkeypress = function(e) {};
   
-  this.focus_up = function()
-  {
-    var state_td = this.get_state_td();
-    if ( !state_td )
-      return false;
-    if ( state_td.parentNode.previousSibling && state_td.parentNode.previousSibling.firstChild.tagName != 'TH' )
-    {
-      // Ooh boy, DOM stuff can be so complicated...
-      //   <tr>   ←   <tr>
-      // ↓ <td>       <td> ↑
-      //   user       user
-      
-      var newstate = state_td.parentNode.previousSibling.firstChild.firstChild.nodeValue;
-      if ( !newstate )
-      {
-        return false;
-      }
-      this.state = newstate;
-      state_td.className = 'row1';
-      state_td.parentNode.previousSibling.firstChild.className = 'row2';
-      
-      // Exception - automatically scroll around if the item is off-screen
-      var top = $dynano(this.box_id).object.scrollTop;
-      
-      var td_top = $dynano(state_td.parentNode.previousSibling.firstChild).Top() - $dynano(this.box_id).Top();
-      
-      if ( td_top < top )
-      {
-        $dynano(this.box_id).object.scrollTop = td_top - 10;
-        /*
-        var newtd = state_td.parentNode.previousSibling.firstChild;
-        var a = document.createElement('a');
-        var id = 'autofill' + Math.floor(Math.random() * 100000);
-        a.name = id;
-        a.id = id;
-        newtd.appendChild(a);
-        window.location.hash = '#' + id;
-        */
-        
-        // In firefox, scrolling like that makes the field get unfocused
-        $dynano(this.field_id).object.focus();
-      }
-    }
-    else
-    {
-      $dynano(this.box_id).object.scrollTop = 0;
-      return false;
-    }
-  }
-  
-  this.destroy = function()
-  {
-    this.repeat = false;
-    var body = document.getElementsByTagName('body')[0];
-    var div = document.getElementById(this.box_id);
-    if ( !div )
-      return false;
-    setTimeout('var body = document.getElementsByTagName("body")[0]; body.removeChild(document.getElementById("'+div.id+'"));', 20);
-    // hackish workaround for divs that stick around past their welcoming period
-    for ( var i = 0; i < this.boxes.length; i++ )
-    {
-      var div = document.getElementById(this.boxes[i]);
-      if ( div )
-        setTimeout('var body = document.getElementsByTagName("body")[0]; var div = document.getElementById("'+div.id+'"); if ( div ) body.removeChild(div);', 20);
-      delete(this.boxes[i]);
-    }
-    this.boxes = new Array();
-    this.box_id = false;
-    this.state = false;
-  }
+  element.className = 'autofill username';
   
-  this.set = function(val)
-  {
-    var ta = document.getElementById(this.field_id);
-    if ( val )
-      ta.value = val;
-    else if ( this.state )
-      ta.value = this.state;
-    this.destroy();
-    findParentForm($dynano(this.field_id.object))._af_acting = false;
-  }
-  
-  this.sleep = function()
-  {
-    if ( this.box_id )
-    {
-      var div = document.getElementById(this.box_id);
-      div.style.display = 'none';
-    }
-    var el = $dynano(this.field_id).object;
-    var fr = findParentForm(el);
-    el._af_acting = false;
-  }
-  
-  this.wake = function()
-  {
-    if ( this.box_id )
-    {
-      var div = document.getElementById(this.box_id);
-      div.style.display = 'block';
-    }
-  }
-  
-  parent.onblur = function()
-  {
-    af_current = this.afobj;
-    window.setTimeout('if ( af_current ) af_current.sleep(); af_current = false;', 50);
-  }
-  
-  parent.onfocus = function()
-  {
-    af_current = this.afobj;
-    window.setTimeout('if ( af_current ) af_current.wake(); af_current = false;', 50);
-  }
-  
-  parent.afobj = this;
-  var frm = findParentForm(parent);
-  if ( frm.onsubmit )
-  {
-    frm.orig_onsubmit = frm.onsubmit;
-    frm.onsubmit = function(e)
-    {
-      if ( this._af_acting )
-        return false;
-      this.orig_onsubmit(e);
-    }
-  }
-  else
-  {
-    frm.onsubmit = function()
-    {
-      if ( this._af_acting )
-        return false;
-    }
-  }
-  
-  if ( parent.value.length < 3 )
-  {
-    this.destroy();
-    return false;
-  }
+  allowanon = allowanon ? true : false;
+  autofill_init_element(element, {
+      allow_anon: allowanon
+    });
 }
 
 function findParentForm(o)