includes/clientside/static/autocomplete.js
author Dan
Sat, 25 Aug 2007 12:11:31 -0400
changeset 98 6457a9b983c6
parent 73 0a74676a2f2f
child 174 d74ff822acc9
permissions -rw-r--r--
Fixed non-object reference in databaseless template, added locking for Javascript paginator, made comments on AES key size more clear in constants, and disallowed "anonymous" and IP addresses for admin username in install.php; Loch Ness release candidate

/*
 * Auto-completing page/username fields
 */

// The ultimate Javascript app: AJAX auto-completion, which responds to up/down arrow keys, the enter key, and the escape key
// The idea was pilfered mercilessly from vBulletin, but uses about 8
// bytes of vB code. All the rest was coded by me, Mr. Javascript Newbie...

// ...in about 8 hours.
// You folks better like this stuff.

function nameCompleteEventHandler(e)
{
  if(!e) e = window.event;
  switch(e.keyCode)
  {
    case 38: // up
      unSelectMove('up');
      break;
    case 40: // down
      unSelectMove('down');
      break;
    case 27: // escape
    case 9:  // tab
      destroyUsernameDropdowns();
      break;
    case 13: // enter
      unSelect();
      break;
    default: return false; break;
  }
  return true;
}

function unSelectMove(dir)
{
  if(submitAuthorized) return false;
  var thediv = document.getElementById(unObjDivCurrentId);
  thetable = thediv.firstChild;
  cel = thetable.firstChild.firstChild;
  d = true;
  index = false;
  changed = false;
  // Object of the game: extract the username, determine its index in the userlist array, and then color the menu items and set unObjCurrentSelection
  while(d) // Set to false if an exception occurs or if we arrive at our destination
  {
    //*
    if(!cel) d=false;
    celbak = cel;
    cel = cel.nextSibling;
    if(!cel) d=false;
    try {
      if(cel.firstChild.nextSibling) html = cel.firstChild.nextSibling.innerHTML;
      else html = cel.firstChild.innerHTML;
      cel.firstChild.className = 'row1';
      if(cel.firstChild.nextSibling) cel.firstChild.nextSibling.className = 'row1';
      thename = html.substr(7, html.length-15);
      // FINALLY! we have extracted the username
      // Now get its position in the userlist array
      if(thename == unObjCurrentSelection)
      {
        index = parseInt(in_array(thename, userlist));
      }
      if(typeof(index) == 'number')
      {
        if(dir=='down')
          n = index+1;
        else if(dir == 'up')
          n = index - 1;
        
        // Try to trap moving the selection up or down beyond the top of bottom
        if(n > userlist.length-1 || n < 0)
        {
          cel.firstChild.className = 'row2';
          if(cel.firstChild.nextSibling) cel.firstChild.nextSibling.className = 'row2';
          return;
        }
        
        if(dir=='down') no=cel.nextSibling;
        else if(dir=='up') no=cel.previousSibling;
        no.firstChild.className = 'row2';
        if(no.firstChild.nextSibling) no.firstChild.nextSibling.className = 'row2';
        if(no.firstChild.id)
        {
          scroll = getScrollOffset() + getHeight();
          elemht = getElementHeight(no.firstChild.id);
          elemoff = fetch_offset(no.firstChild);
          whereto = elemoff['top'] + elemht;
          if(whereto > scroll)
          {
            window.location.hash = '#'+no.firstChild.id;
            unObj.focus();
          }
        }
        cel=cel.nextSibling;
        unObjCurrentSelection = userlist[n];
        index = false;
        changed = true;
        return;
      }
    } catch(e) { }
    //*/ d = false;
  }
}

function unSelect()
{
  if(!unObj || submitAuthorized) return false;
  if ( unObjCurrentSelection )
    unObj.value = unObjCurrentSelection;
  destroyUsernameDropdowns(); 
}

function in_array(needle, haystack)
{
  for(var i in haystack)
  {
    if(haystack[i] == needle) return i;
  }
  return false;
}

function ajaxUserNameComplete(o)
{
  if(!o) {destroyUsernameDropdowns(); return;}
  if(!o.value) {destroyUsernameDropdowns(); return;}
  if(o.value.length < 3) {destroyUsernameDropdowns(); return;}
  //if(IE) return; // This control doesn't work in IE. Yes, it's true! document.createElement doesn't work.
  if(!o.id)
  {
    o.id = 'usernametextboxobj_' + Math.floor(Math.random() * 10000000);
  }
  unObj = o;
  o.setAttribute("autocomplete","off");
  o.onkeyup = function(e, o) { o=unObj; if(!nameCompleteEventHandler(e)) ajaxUserNameComplete(o); }
  val = escape(o.value).replace('+', '%2B');
  ajaxGet(stdAjaxPrefix+'&_mode=fillusername&name='+val, function()
    {
      if(ajax.readyState==4)
      {
        // Determine the appropriate left/top positions, then create a div to use for the drop-down list
        // The trick here is to be able to make the div dynamically destroy itself depending on how far the user's mouse is from it
        destroyUsernameDropdowns();
        off = fetch_offset(unObj);
        dim = fetch_dimensions(unObj);
        left = off['left'];
        i1 = off['top'];
        i2 = dim['h'];
        var top = 0;
        top = i1 + i2;
        var thediv = document.createElement('div');
        thediv.className = 'tblholder';
        thediv.style.marginTop = '0px';
        thediv.style.position = 'absolute';
        thediv.style.top  = top  + 'px';
        thediv.style.left = left + 'px';
        thediv.style.zIndex = getHighestZ() + 2;
        id = 'usernamehoverobj_' + Math.floor(Math.random() * 10000000);
        unObjDivCurrentId = id;
        thediv.id = id;
        unObj.onblur = function() { destroyUsernameDropdowns(); }
        
        eval(ajax.responseText);
        if(errorstring)
        {
          html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
        }
        else
        {
          html = '<table border="0" cellspacing="1" cellpadding="3" style="width: auto;"><tr><th><small>Username matches</small></th></tr>';
          cls = 'row2';
          unObjCurrentSelection = userlist[0];
          for(i=0;i<userlist.length;i++)
          {
            tmpnam = 'listobjnode_'+Math.floor(Math.random() * 10000000);
            html = html + '<tr><td id="'+tmpnam+'" class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+userlist[i]+'</small></td></tr>';
            if(cls=='row2') cls='row1';
          }
          html = html + '</table>';
        }
        
        thediv.innerHTML = html;
        var body = document.getElementsByTagName('body');
        body = body[0];
        unSelectMenuOn = true;
        submitAuthorized = false;
        body.appendChild(thediv);
      }
    });
}

function ajaxPageNameComplete(o)
{
  if(!o) {destroyUsernameDropdowns(); return;}
  if(!o.value) {destroyUsernameDropdowns(); return;}
  if(o.value.length < 3) {destroyUsernameDropdowns(); return;}
  if(IE) return; // This control doesn't work in IE. Yes, it's true! document.createElement doesn't work.
  if(!o.id)
  {
    o.id = 'usernametextboxobj_' + Math.floor(Math.random() * 10000000);
  }
  o.setAttribute("autocomplete","off");
  unObj = o;
  o.onkeyup = function(e, o) { o=unObj; if(!nameCompleteEventHandler(e)) ajaxPageNameComplete(o); }
  val = escape(o.value).replace('+', '%2B');
  ajaxGet(stdAjaxPrefix+'&_mode=fillpagename&name='+val, function()
    {
      if(!ajax) return;
      if(ajax.readyState==4)
      {
        // Determine the appropriate left/top positions, then create a div to use for the drop-down list
        // The trick here is to be able to make the div dynamically destroy itself depending on how far the user's mouse is from it
        destroyUsernameDropdowns();
        off = fetch_offset(unObj);
        dim = fetch_dimensions(unObj);
        left = off['left'];
        top = off['top'] + dim['h'];
        var thediv = document.createElement('div');
        thediv.className = 'tblholder';
        thediv.style.marginTop = '0px';
        thediv.style.position = 'absolute';
        thediv.style.top  = top  + 'px';
        thediv.style.left = left + 'px';
        thediv.style.zIndex = getHighestZ() + 2;
        id = 'usernamehoverobj_' + Math.floor(Math.random() * 10000000);
        unObjDivCurrentId = id;
        thediv.id = id;
        
        eval(ajax.responseText);
        if(errorstring)
        {
          html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
        }
        else
        {
          html = '<table border="0" cellspacing="1" cellpadding="3" style="width: auto;"><tr><th colspan="2">Page name matches</th></tr><tr><th><small>Page title</small></th><th><small>Page ID</small></th></tr>';
          cls = 'row2';
          unObjCurrentSelection = userlist[0];
          for(i=0;i<userlist.length;i++)
          {
            tmpnam = 'listobjnode_'+Math.floor(Math.random() * 10000000);
            html = html + '<tr><td id="'+tmpnam+'" class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+namelist[i]+'</small></td><td class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+userlist[i]+'</small></td></tr>';
            if(cls=='row2') cls='row1';
          }
          html = html + '</table>';
        }
        
        thediv.innerHTML = html;
        var body = document.getElementsByTagName('body');
        body = body[0];
        unSelectMenuOn = true;
        submitAuthorized = false;
        body.appendChild(thediv);
      }
    });
}

function destroyUsernameDropdowns()
{
  var divs = document.getElementsByTagName('div');
  var prefix = 'usernamehoverobj_';
  for(i=0;i<divs.length;i++)                                                                                                                                                                                                                         
  {
    if ( divs[i].id )
    {
      if(divs[i].id.substr(0, prefix.length)==prefix)
      {
        divs[i].innerHTML = '';
        divs[i].style.display = 'none';
      }
    }
  }
  unSelectMenuOn = false;
  unObjDivCurrentId = false;
  unObjCurrentSelection = false;
  submitAuthorized = true;
}

function get_parent_form(o)
{
  if ( !o.parentNode )
    return false;
  if ( o.tagName == 'FORM' )
    return o;
  var p = o.parentNode;
  while(true)
  {
    if ( p.tagName == 'FORM' )
      return p;
    else if ( !p )
      return false;
    else
      p = p.parentNode;
  }
}