diff -r 91127e62f38f -r d74ff822acc9 includes/clientside/static/autofill.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/includes/clientside/static/autofill.js Fri Oct 12 14:41:51 2007 -0400 @@ -0,0 +1,512 @@ +/** + * Javascript auto-completion for form fields. + */ + +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'); + + 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) + { + // window.console.info('Processing the following dataset.'); + // window.console.debug(resp_json); + var autofill = this; + + 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($(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; + } + } + } + + 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 = $(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; + + // 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 tr = document.createElement('tr'); + var th = document.createElement('th'); + th.appendChild(document.createTextNode('Username suggestions')); + tr.appendChild(th); + table.appendChild(tr); + + if ( users.length < 1 ) + { + var tr = document.createElement('tr'); + var td = document.createElement('td'); + td.className = 'row1'; + td.appendChild(document.createTextNode('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); + } + + // Finalize div + var tb_top = $(autofill.field_id).Top(); + var tb_height = $(autofill.field_id).Height(); + var af_top = tb_top + tb_height - 9; + var tb_left = $(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; + } + + // perform ajax call + this.fetch_and_process = function() + { + af_current = this; + var processResponse = function() + { + if ( ajax.readyState == 4 ) + { + 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:
' + ajax.responseText + '
'); + return false; + } + if ( $(afobj.field_id).object.value.length < 3 ) + return false; + var resp_json = parseJSON(response); + var resp_code = $(afobj.field_id).object.value.toLowerCase().substr(0, 3); + afobj.responses[resp_code] = resp_json; + afobj.process_dataset(resp_json); + } + } + var usernamefragment = ajaxEscape($(this.field_id).object.value); + ajaxGet(stdAjaxPrefix + '&_mode=fillusername&name=' + usernamefragment + '&allowanon=' + ( this.allowanon ? '1' : '0' ), processResponse); + } + + this.go = function() + { + if ( document.getElementById(this.field_id).value.length < 3 ) + { + this.destroy(); + return false; + } + + if ( af_current ) + return false; + + var resp_code = $(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; + } + } + form._af_acting = true; + } + } + + this.keyhandler = function() + { + var key = this.event.keyCode; + if ( key == this.KEY_ENTER && !this.repeat ) + { + var form = findParentForm($(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($(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() + { + 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... + // --> + // + // 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 = $(this.box_id).Height(); + var top = $(this.box_id).object.scrollTop; + var scroll_bottom = height + top; + + var td_top = $(state_td.parentNode.nextSibling.firstChild).Top() - $(this.box_id).Top(); + var td_height = $(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); + $(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 + $(this.field_id).object.focus(); + } + } + else + { + return false; + } + } + + 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... + // <-- + // + // 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 = $(this.box_id).object.scrollTop; + + var td_top = $(state_td.parentNode.previousSibling.firstChild).Top() - $(this.box_id).Top(); + + if ( td_top < top ) + { + $(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 + $(this.field_id).object.focus(); + } + } + else + { + $(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.box_id = false; + this.state = false; + } + + 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(); + } + + this.sleep = function() + { + if ( this.box_id ) + { + var div = document.getElementById(this.box_id); + div.style.display = 'none'; + } + var el = $(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; + } +} + +function findParentForm(o) +{ + if ( o.tagName == 'FORM' ) + return o; + while(true) + { + o = o.parentNode; + if ( !o ) + return false; + if ( o.tagName == 'FORM' ) + return o; + } + return false; +} +