// // This script was created // by Mircho Mirev // mo /mo@momche.net/ // Copyright (c) 2004-2005 Mircho Mirev // // :: feel free to use it BUT // :: if you want to use this code PLEASE send me a note // :: and please keep this disclaimer intact // function cAutocomplete( sInputId ) { this.init( sInputId ) } cAutocomplete.CS_NAME = 'Autocomplete component' cAutocomplete.CS_OBJ_NAME = 'AC_COMPONENT' cAutocomplete.CS_LIST_PREFIX = 'ACL_' cAutocomplete.CS_BUTTON_PREFIX = 'ACB_' cAutocomplete.CS_INPUT_PREFIX = 'AC_' cAutocomplete.CS_HIDDEN_INPUT_PREFIX = 'ACH_' cAutocomplete.CS_INPUT_CLASSNAME = 'dropdown' cAutocomplete.CB_AUTOINIT = true cAutocomplete.CB_AUTOCOMPLETE = false cAutocomplete.CB_FORCECORRECT = false //the separator when autocompleting multiple values cAutocomplete.CB_MATCHSUBSTRING = false cAutocomplete.CS_SEPARATOR = ',' //the separator of associative arrays cAutocomplete.CS_ARRAY_SEPARATOR = ',' //match the input string only against the begining of the strings //or anywhere in the string cAutocomplete.CB_MATCHSTRINGBEGIN = true cAutocomplete.CN_OFFSET_TOP = 2 cAutocomplete.CN_OFFSET_LEFT = -1 cAutocomplete.CN_LINE_HEIGHT = 19 cAutocomplete.CN_NUMBER_OF_LINES = 10 cAutocomplete.CN_HEIGHT_FIX = 2 cAutocomplete.CN_CLEAR_TIMEOUT = 300 cAutocomplete.CN_SHOW_TIMEOUT = 400 cAutocomplete.CN_REMOTE_SHOW_TIMEOUT = 1000 cAutocomplete.CN_MARK_TIMEOUT = 400 cAutocomplete.hListDisplayed = null cAutocomplete.nCount = 0 cAutocomplete.autoInit = function() { var nI = 0 var hACE = null var sLangAtt for( nI = 0; nI < document.getElementsByTagName( 'INPUT' ).length; nI++ ) { if( document.getElementsByTagName( 'INPUT' )[ nI ].type.toLowerCase() == 'text' ) { sLangAtt = document.getElementsByTagName( 'INPUT' )[ nI ].getAttribute( 'acdropdown' ) if( sLangAtt != null && sLangAtt.length > 0 ) { if( document.getElementsByTagName( 'INPUT' )[ nI ].id == null || document.getElementsByTagName( 'INPUT' )[ nI ].id.length == 0 ) { document.getElementsByTagName( 'INPUT' )[ nI ].id = cAutocomplete.CS_OBJ_NAME + cAutocomplete.nCount } hACE = new cAutocomplete( document.getElementsByTagName( 'INPUT' )[ nI ].id ) } } } var nTALength = document.getElementsByTagName( 'TEXTAREA' ).length for( nI = 0; nI < nTALength; nI++ ) { sLangAtt = document.getElementsByTagName( 'TEXTAREA' )[ nI ].getAttribute( 'acdropdown' ) if( sLangAtt != null && sLangAtt.length > 0 ) { if( document.getElementsByTagName( 'TEXTAREA' )[ nI ].id == null || document.getElementsByTagName( 'TEXTAREA' )[ nI ].id.length == 0 ) { document.getElementsByTagName( 'TEXTAREA' )[ nI ].id = cAutocomplete.CS_OBJ_NAME + cAutocomplete.nCount } hACE = new cAutocomplete( document.getElementsByTagName( 'TEXTAREA' )[ nI ].id ) } } var nSelectsLength = document.getElementsByTagName( 'SELECT' ).length var aSelect = null for( nI = 0; nI < nSelectsLength; nI++ ) { aSelect = document.getElementsByTagName( 'SELECT' )[ nI ] sLangAtt = aSelect.getAttribute( 'acdropdown' ) if( sLangAtt != null && sLangAtt.length > 0 ) { if( aSelect.id == null || aSelect.id.length == 0 ) { aSelect.id = cAutocomplete.CS_OBJ_NAME + cAutocomplete.nCount } hACE = new cAutocomplete( aSelect.id ) nSelectsLength-- nI-- } } } if( cAutocomplete.CB_AUTOINIT ) { if( window.attachEvent ) { window.attachEvent( 'onload', cAutocomplete.autoInit ) } else if( window.addEventListener ) { window.addEventListener( 'load', cAutocomplete.autoInit, false ) } } cAutocomplete.prototype.init = function( sInputId ) { this.sInputId = sInputId this.sListId = cAutocomplete.CS_LIST_PREFIX + sInputId this.sObjName = cAutocomplete.CS_OBJ_NAME + '_obj_' + (cAutocomplete.nCount++) this.hObj = this.sObjName this.hActiveSelection = null this.nSelectedItemIdx = -1 //the value of the input before the list is displayed this.sLastActiveValue = '' this.sActiveValue = '' this.bListDisplayed = false this.nItemsDisplayed = 0 //if I transform a select option or the supplied array is associative I create a hidden input //with the name of the original input and replace the original input's name this.bAssociative = false this.sHiddenInputId = null this.bHasButton = false //the actual data this.aData = null //the search array object this.aSearchData = new Array() this.bSorted = false //the length of the last matched typed string this.nLastMatchLength = 0 this.bForceCorrect = cAutocomplete.CB_FORCECORRECT var sForceCorrect = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_forcecorrect' ) if( sForceCorrect != null && sForceCorrect.length > 0 ) { this.bForceCorrect = eval( sForceCorrect ) } //match a only from the beginning or anywhere in the values this.bMatchBegin = cAutocomplete.CB_MATCHSTRINGBEGIN var sMatchBegin = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_matchbegin' ) if( sMatchBegin != null && sMatchBegin.length > 0 ) { this.bMatchBegin = eval( sMatchBegin ) } //match substrings separated by cAutocomplete.CS_SEPARATOR this.bMatchSubstring = cAutocomplete.CB_MATCHSUBSTRING var sMatchSubstring = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_matchsubstring' ) if( sMatchSubstring != null && sMatchSubstring.length > 0 ) { this.bMatchSubstring = true } //autocomplete with the first option from the list this.bAutoComplete = cAutocomplete.CB_AUTOCOMPLETE this.bAutocompleted = false var sAutoComplete = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_complete' ) if( sAutoComplete != null && sAutoComplete.length > 0 ) { this.bAutoComplete = eval( sAutoComplete ) } //format function this.formatOptions = null var sFormatFunction = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_format' ) if( sFormatFunction != null && sFormatFunction.length > 0 ) { this.formatOptions = eval( sFormatFunction ) } //onselect callback function - get called when a new option is selected, either by changing the focus in the list by using the keyboard or by //clicking on it with the mouse this.onSelect = null var sOnSelectFunction = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_onselect' ) if( sOnSelectFunction != null && sOnSelectFunction.length > 0 ) { this.onSelect = eval( sOnSelectFunction ) } //onchange callback function - get called when a new option is selected by clicking on it or by pressing enter //almost the same as onselect, but will get activated on /* this.onChange = null var sOnSelectFunction = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_onselect' ) if( sOnSelectFunction != null && sOnSelectFunction.length > 0 ) { this.onSelect = eval( sOnSelectFunction ) } */ //I assume that we always have the associative type //you can turn it off only with the autocomplete_assoc=false attribute this.bAssociative = true var sAssociative = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_assoc' ) if( sAssociative != null && sAssociative.length > 0 ) { if( sAssociative == 'false' ) { this.bAssociative = false } } //if we have remote list then we postpone the list creation if( this.getListArrayType() != 'url' ) { this.bRemoteList = false } else { this.bRemoteList = true this.sListURL = this.getListURL() this.hXMLHttp = XmlHttp.create() } this.initListArray() this.initListContainer() //this.createList() this.initInput() eval( this.hObj + '= this' ) } cAutocomplete.prototype.initInput = function() { var hInput = document.getElementById( this.sInputId ) hInput.hAutocomplete = this var hContainer = document.getElementById( this.sListId ) hContainer.hAutocomplete = this //any element ( and it's children ) with display:none have offset values of 0 ( in mozilla ) var nWidth = hInput.offsetWidth if( !nWidth || nWidth == 0 ) { //any element ( and it's children ) with display:none have offset values of 0 ( in mozilla ) var hOWInput = hInput.cloneNode( true ) hOWInput.style.position = 'absolute' hOWInput.style.top = '-1000px' document.body.appendChild( hOWInput ) var nWidth = hOWInput.offsetWidth document.body.removeChild( hOWInput ) } var sInputName = hInput.name var hForm = hInput.form var bHasButton = false var sHiddenValue = hInput.value var sValue = hInput.type.toLowerCase() == 'text' ? hInput.value : '' var sHasButton = hInput.getAttribute( 'autocomplete_button' ) if( sHasButton != null && sHasButton.length > 0 ) { bHasButton = true } //if it is a select - I unconditionally add a button if( hInput.type.toLowerCase() == 'select-one' ) { bHasButton = true if( hInput.selectedIndex >= 0 ) { sHiddenValue = hInput.options[ hInput.selectedIndex ].value sValue = hInput.options[ hInput.selectedIndex ].text } } //this is the case when the control is a transformed select or the list supplied is of the type - key,value not only values if( hForm ) { var hHiddenInput = document.createElement( 'INPUT' ) hHiddenInput.id = cAutocomplete.CS_HIDDEN_INPUT_PREFIX + this.sInputId hHiddenInput.type = 'hidden' hForm.appendChild( hHiddenInput ) if( this.bAssociative ) { hHiddenInput.name = sInputName hInput.name = cAutocomplete.CS_INPUT_PREFIX + sInputName } else { hHiddenInput.name = cAutocomplete.CS_INPUT_PREFIX + sInputName } hHiddenInput.value = sHiddenValue this.sHiddenInputId = hHiddenInput.id } if( bHasButton ) { this.bHasButton = true var hInputContainer = document.createElement( 'DIV' ) hInputContainer.className = 'acinputContainer' hInputContainer.style.width = nWidth var hInputButton = document.createElement( 'INPUT' ) hInputButton.id = cAutocomplete.CS_BUTTON_PREFIX + this.sInputId hInputButton.type = 'button' hInputButton.className = 'button' hInputButton.tabIndex = hInput.tabIndex + 1 hInputButton.hAutocomplete = this var hNewInput = document.createElement( 'INPUT' ) if( this.bAssociative ) { hNewInput.name = cAutocomplete.CS_INPUT_PREFIX + sInputName } else { hNewInput.name = sInputName } hNewInput.type = 'text' hNewInput.value = sValue hNewInput.style.width = nWidth-22 hNewInput.className = cAutocomplete.CS_INPUT_CLASSNAME hNewInput.tabIndex = hInput.tabIndex hNewInput.hAutocomplete = this hInputContainer.appendChild( hNewInput ) hInputContainer.appendChild( hInputButton ) hInput.parentNode.replaceChild( hInputContainer, hInput ) hNewInput.id = this.sInputId hInput = hNewInput } if( hInput.attachEvent ) { hInput.attachEvent( 'onkeyup', cAutocomplete.onInputKeyUp ) hInput.attachEvent( 'onkeyup', cAutocomplete.saveCaretPosition ) hInput.attachEvent( 'onkeydown', cAutocomplete.onInputKeyDown ) hInput.attachEvent( 'onblur', cAutocomplete.onInputBlur ) hInput.attachEvent( 'onfocus', cAutocomplete.onInputFocus ) if( hInputButton ) { hInputButton.attachEvent( 'onclick', cAutocomplete.onButtonClick ) } } else if( hInput.addEventListener ) { hInput.addEventListener( 'keyup', cAutocomplete.onInputKeyUp, false ) hInput.addEventListener( 'keyup', cAutocomplete.saveCaretPosition, false ) hInput.addEventListener( 'keydown', cAutocomplete.onInputKeyDown, false ) hInput.addEventListener( 'keypress', cAutocomplete.onInputKeyPress, false ) hInput.addEventListener( 'blur', cAutocomplete.onInputBlur, false ) hInput.addEventListener( 'focus', cAutocomplete.onInputFocus, false ) if( hInputButton ) { hInputButton.addEventListener( 'click', cAutocomplete.onButtonClick, false ) } } //I don't need the standard autocomplete hInput.setAttribute( 'autocomplete', 'OFF' ) if( hForm ) { if( hForm.attachEvent ) { hForm.attachEvent( 'onsubmit', cAutocomplete.onFormSubmit ) } else if( hForm.addEventListener ) { hForm.addEventListener( 'submit', cAutocomplete.onFormSubmit, false ) } } } cAutocomplete.prototype.initListContainer = function() { var hInput = document.getElementById( this.sInputId ) var hContainer = document.createElement( 'DIV' ) hContainer.className = 'autocomplete_holder' hContainer.id = this.sListId hContainer.style.zIndex = 10000 + cAutocomplete.nCount hContainer.hAutocomplete = this var hFirstBorder = document.createElement( 'DIV' ) hFirstBorder.className = 'autocomplete_firstborder' var hSecondBorder = document.createElement( 'DIV' ) hSecondBorder.className = 'autocomplete_secondborder' var hList = document.createElement( 'UL' ) hList.className = 'autocomplete' hSecondBorder.appendChild( hList ) hFirstBorder.appendChild( hSecondBorder ) hContainer.appendChild( hFirstBorder ) document.body.appendChild( hContainer ) if( hContainer.attachEvent ) { hContainer.attachEvent( 'onblur', cAutocomplete.onListBlur ) hContainer.attachEvent( 'onfocus', cAutocomplete.onListFocus ) } else if( hInput.addEventListener ) { hContainer.addEventListener( 'blur', cAutocomplete.onListBlur, false ) hContainer.addEventListener( 'focus', cAutocomplete.onListFocus, false ) } if( hContainer.attachEvent ) { hContainer.attachEvent( 'onclick', cAutocomplete.onItemClick ) } else if( hContainer.addEventListener ) { hContainer.addEventListener( 'click', cAutocomplete.onItemClick, false ) } } cAutocomplete.prototype.createList = function() { var hInput = document.getElementById( this.sInputId ) var hContainer = document.getElementById( this.sListId ) var hList = hContainer.getElementsByTagName( 'UL' )[0] if( hList ) { hList = hList.parentNode.removeChild( hList ) while( hList.hasChildNodes() ) { hList.removeChild( hList.childNodes[ 0 ] ) } } var hListItem = null var hListItemLink = null var hArrKey = null var sArrEl = null var hArr = this.aData var nI = 0 var sRealText for( hArrKey in hArr ) { sArrEl = hArr[ hArrKey ] hListItem = document.createElement( 'LI' ) hListItemLink = document.createElement( 'A' ) hListItemLink.setAttribute( 'itemvalue', hArrKey ) /* so you can attach data to the element */ /* it's a hack but seems to work */ if(sArrEl.split) { var sArrData = sArrEl.split( cAutocomplete.CS_ARRAY_SEPARATOR ) if( sArrData.length > 1 ) { this.aData[ hArrKey ] = sArrData[ 0 ] hListItemLink.setAttribute( 'itemdata', sArrEl.substring( sArrEl.indexOf( cAutocomplete.CS_ARRAY_SEPARATOR ) + 1 ) ) sRealText = sArrData[ 0 ] } else { sRealText = sArrEl } /* end of attach data to the element */ hListItemLink.href = '#' hListItemLink.appendChild( document.createTextNode( sRealText ) ) hListItemLink.realText = sRealText if( nI == this.nSelectedItemIdx ) { this.hActiveSelection = hListItemLink this.hActiveSelection.className = 'selected' } hListItem.appendChild( hListItemLink ) hList.appendChild( hListItem ) this.aSearchData[ nI++ ] = sRealText.toLowerCase() } } var hSecondBorder = hContainer.firstChild.firstChild hSecondBorder.appendChild( hList ) this.bListUpdated = false } /* list array functions */ cAutocomplete.prototype.initListArray = function() { var hInput = document.getElementById( this.sInputId ) var hArr = null if( hInput.type.toLowerCase() == 'select-one' ) { hArr = new Object() for( var nI = 0; nI < hInput.options.length; nI++ ) { hArrKey = hInput.options.item( nI ).value sArrEl = hInput.options.item( nI ).text hArr[ hArrKey ] = sArrEl if( hInput.options.item( nI ).selected ) { this.nSelectedItemIdx = nI } } } else { var sAA = hInput.getAttribute( 'autocomplete_list' ) var sAAS = hInput.getAttribute( 'autocomplete_list_sort' ) var sArrayType = this.getListArrayType() switch( sArrayType ) { case 'array' : hArr = eval( sAA.substring( 6 ) ) break case 'list' : hArr = new Array() var hTmpArray = sAA.substring( 5 ).split( '|' ) var aValueArr for( hKey in hTmpArray ) { aValueArr = hTmpArray[ hKey ].split( cAutocomplete.CS_ARRAY_SEPARATOR ) if( aValueArr.length == 1 ) { hArr[ hKey ] = hTmpArray[ hKey ] this.bAssociative = false } else { hArr[ aValueArr[ 0 ] ] = aValueArr[ 1 ] } } break } if( sAAS != null && eval( sAAS ) ) { this.bSorted = true this.aData = hArr.sort() hArr = hArr.sort() } } this.setArray( hArr ) } cAutocomplete.prototype.setArray = function( sArray ) { if( typeof sArray == 'string' ) { this.aData = eval( sArray ) } else { this.aData = sArray } this.bListUpdated = true } //use this function to change the list of autocomplete values to a new one //supply as an argument the name as a literal of an JS array object //well things changed - you can supply an actual array too cAutocomplete.prototype.setListArray = function( sArray ) { this.setArray( sArray ) this.updateAndShowList() } cAutocomplete.prototype.getListArrayType = function() { var hInput = document.getElementById( this.sInputId ) var sAA = hInput.getAttribute( 'autocomplete_list' ) if( sAA != null && sAA.length > 0 ) { if( sAA.indexOf( 'array:' ) >= 0 ) { return 'array' } else if( sAA.indexOf( 'list:' ) >= 0 ) { return 'list' } else if( sAA.indexOf( 'url:' ) >= 0 ) { return 'url' } } } cAutocomplete.prototype.getListURL = function() { var hInput = document.getElementById( this.sInputId ) var sAA = hInput.getAttribute( 'autocomplete_list' ) if( sAA != null && sAA.length > 0 ) { if( sAA.indexOf( 'url:' ) >= 0 ) { return sAA.substring( 4 ) } } } cAutocomplete.prototype.setListURL = function( sURL ) { this.sListURL = sURL; } cAutocomplete.prototype.onXmlHttpLoad = function() { if( this.hXMLHttp.readyState == 4 ) { var hError = this.hXMLHttp.parseError if( hError && hError.errorCode != 0 ) { alert( hError.reason ) } else { this.afterRemoteLoad() } } } cAutocomplete.prototype.loadListArray = function() { var sURL = this.sListURL var sStartWith = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint ) sStartWith = sStartWith.replace( /^\s/, '' ) sStartWith = sStartWith.replace( /\s$/, '' ) if( sURL.indexOf( '[S]' ) >= 0 ) { sURL = sURL.replace( '[S]', sStartWith ) } else { sURL += this.sActiveValue } this.hXMLHttp.open( 'GET', sURL, true ) var hAC = this this.hXMLHttp.onreadystatechange = function() { hAC.onXmlHttpLoad() } this.hXMLHttp.send( null ) } cAutocomplete.prototype.afterRemoteLoad = function() { var hInput = document.getElementById( this.sInputId ) var hArr = new Array() var hTmpArray = this.hXMLHttp.responseText.split( '|' ) var aValueArr for( hKey in hTmpArray ) { if(hTmpArray[ hKey ].split) { aValueArr = hTmpArray[ hKey ].split( cAutocomplete.CS_ARRAY_SEPARATOR ) if( aValueArr.length == 1 ) { hArr[ hKey ] = hTmpArray[ hKey ] } else { hArr[ aValueArr[ 0 ] ] = hTmpArray[ hKey ].substr( hTmpArray[ hKey ].indexOf( cAutocomplete.CS_ARRAY_SEPARATOR ) + 1 ) } } } hInput.className = '' hInput.readonly = false hInput.value = this.sActiveValue this.setListArray( hArr ) } /**/ cAutocomplete.prototype.prepareList = function( bFullList ) { var hInput = document.getElementById( this.sInputId ) this.sActiveValue = hInput.value if( this.bRemoteList ) { hInput.readonly = true } //check if this was invoked by a key that did not change the value var sST = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint ) var sLST = this.getStringForAutocompletion( this.sLastActiveValue, this.nInsertPoint ) if( sLST != sST || bFullList || !this.bListDisplayed || this.bMatchSubstring ) { if( this.bRemoteList ) { hInput.className = 'search' hInput.value = 'please wait...' this.loadListArray() return } this.updateAndShowList( bFullList ) } } cAutocomplete.prototype.updateAndShowList = function( bFullList ) { var hContainer = document.getElementById( this.sListId ) var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ] var hInput = document.getElementById( this.sInputId ) if( this.bListUpdated ) { this.createList() } //stupid hack just for speed var sST = this.bMatchSubstring ? this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint ) : this.sActiveValue var sLST = this.bMatchSubstring ? this.getStringForAutocompletion( this.sLastActiveValue, this.nInsertPoint ) : this.sLastActiveValue //nothing changed since last type - maybe only function keys were pressed //this is the case when for example the down key was pressed if( sST == sLST ) { if( !this.bMatchSubstring ) { bFullList = true } } this.filterOptions( bFullList ) if( this.nItemsDisplayed == 0 ) { if( this.bForceCorrect ) { var aPos = this.getInsertPos( this.sActiveValue, this.nInsertPoint, '' ) cAutocomplete.markInputRange( hInput, this.nLastMatchLength, aPos[0] ) } } this.sLastActiveValue = this.sActiveValue if( this.nItemsDisplayed > 0 ) { if( !bFullList || this.bMatchSubstring ) { this.deselectOption() } if( this.bAutoComplete && this.nItemsDisplayed == 1 ) { //test if we have a full match i.e. the user typed the entire value var sStartWith = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint ) var sItemText = hList.getElementsByTagName( 'LI' )[ this.nFirstDisplayed ].getElementsByTagName( 'A' )[ 0 ].realText if( sStartWith.toLowerCase() == sItemText.toLowerCase() ) { this.selectOption( hList.getElementsByTagName( 'LI' )[ this.nFirstDisplayed ].getElementsByTagName( 'A' )[ 0 ] ) this.hideOptions() //and do not show the list return } } if( this.bAutoComplete && !bFullList ) { this.selectOption( hList.getElementsByTagName( 'LI' )[ this.nFirstDisplayed ].getElementsByTagName( 'A' )[ 0 ] ) } this.showList() } else { this.clearList() } } cAutocomplete.prototype.showList = function() { if( cAutocomplete.hListDisplayed ) { cAutocomplete.hListDisplayed.clearList() } var hInput = document.getElementById( this.sInputId ) var nTop = cDomObject.getOffsetParam( hInput, 'offsetTop' ) var nLeft = cDomObject.getOffsetParam( hInput, 'offsetLeft' ) var hContainer = document.getElementById( this.sListId ) var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ] if( this.bHasButton ) { hContainer.style.width = document.getElementById( this.sInputId ).parentNode.offsetWidth } else { hContainer.style.width = document.getElementById( this.sInputId ).offsetWidth } var nNumLines = ( this.nItemsDisplayed < cAutocomplete.CN_NUMBER_OF_LINES ) ? this.nItemsDisplayed : cAutocomplete.CN_NUMBER_OF_LINES; hList.style.height = nNumLines * cAutocomplete.CN_LINE_HEIGHT + cAutocomplete.CN_HEIGHT_FIX + 'px' hContainer.style.top = nTop + hInput.offsetHeight + cAutocomplete.CN_OFFSET_TOP + 'px' hContainer.style.left = nLeft + cAutocomplete.CN_OFFSET_LEFT + 'px' hContainer.style.display = 'none' hContainer.style.visibility = 'visible' hContainer.style.display = 'block' cAutocomplete.hListDisplayed = this this.bListDisplayed = true } cAutocomplete.prototype.binarySearch = function( sFilter ) { var nLow = 0 var nHigh = this.aSearchData.length - 1 var nMid var nTry, nLastTry var sData var nLen = sFilter.length var lastTry while ( nLow <= nHigh ) { nMid = ( nLow + nHigh ) / 2 nTry = ( nMid < 1 ) ? 0 : parseInt( nMid ) sData = this.aSearchData[ nTry ].substr( 0, nLen ) if ( sData < sFilter ) { nLow = nTry + 1 continue } if ( sData > sFilter ) { nHigh = nTry - 1 continue } if ( sData == sFilter ) { nHigh = nTry - 1 nLastTry = nTry continue } return nTry } if ( typeof ( nLastTry ) != "undefined" ) { return nLastTry } else { return null } } cAutocomplete.prototype.getStringForAutocompletion = function( sString, nPos ) { if( sString == null || sString.length == 0 ) { return '' } if( this.bMatchSubstring ) { var nStartPos = sString.lastIndexOf( cAutocomplete.CS_SEPARATOR, nPos - 1 ) nStartPos = nStartPos < 0 ? 0 : nStartPos var nEndPos = sString.indexOf( cAutocomplete.CS_SEPARATOR, nPos ) nEndPos = nEndPos < 0 ? sString.length : nEndPos var sStr = sString.substr( nStartPos, nEndPos - nStartPos ) sStr = sStr.replace( /^(\,?)(\s*)(\S*)(\s*)(\,?)$/g, '$3' ) return sStr } else { return sString } } cAutocomplete.prototype.insertString = function( sString, nPos, sInsert ) { if( this.bMatchSubstring ) { var nStartPos = sString.lastIndexOf( cAutocomplete.CS_SEPARATOR, nPos - 1 ) nStartPos = nStartPos < 0 ? 0 : nStartPos var nEndPos = sString.indexOf( cAutocomplete.CS_SEPARATOR, nPos ) nEndPos = nEndPos < 0 ? sString.length : nEndPos var sStr = sString.substr( nStartPos, nEndPos - nStartPos ) sStr = sStr.replace( /^(\,?)(\s*)(\S?[\S\s]*\S?)(\s*)(\,?)$/g, '$1$2'+sInsert+'$4$5' ) sStr = sString.substr( 0, nStartPos ) + sStr + sString.substr( nEndPos ) return sStr } else { return sInsert } } cAutocomplete.prototype.getInsertPos = function( sString, nPos, sInsert ) { nPos = nPos == null ? 0 : nPos var nStartPos = sString.lastIndexOf( cAutocomplete.CS_SEPARATOR, nPos - 1 ) nStartPos = nStartPos < 0 ? 0 : nStartPos var nEndPos = sString.indexOf( cAutocomplete.CS_SEPARATOR, nPos ) nEndPos = nEndPos < 0 ? sString.length : nEndPos var sStr = sString.substr( nStartPos, nEndPos - nStartPos ) sStr = sStr.replace( /^(\,?)(\s*)(\S?[\S\s]*\S?)(\s*)(\,?)$/g, '$1$2'+sInsert ) return [ nPos, nStartPos + sStr.length ] } cAutocomplete.prototype.filterOptions = function( bShowAll ) { if( this.hActiveSelection && !bShowAll ) { this.hActiveSelection.className = '' } if( typeof bShowAll == 'undefined' ) { bShowAll = false } var hInput = document.getElementById( this.sInputId ) var sStartWith = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint ) if( bShowAll ) { sStartWith = '' } var hContainer = document.getElementById( this.sListId ) var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ] var nItemsLength = hList.childNodes.length var hLinkItem = null var nCount = 0 var hParent = hList.parentNode var hList = hList.parentNode.removeChild( hList ) var hTItems = hList.childNodes this.nItemsDisplayed = 0 if( sStartWith.length == 0 ) { for( var nI = 0; nI < nItemsLength; nI++ ) { if( this.formatOptions ) { hTItems[ nI ].childNodes[0].innerHTML = this.formatOptions( hTItems[ nI ].childNodes[0].realText, nI ) } hTItems[ nI ].style.display = 'block' } nCount = nItemsLength if( nItemsLength > 0 ) { this.nFirstDisplayed = 0 this.nLastDisplayed = nItemsLength - 1 } else { this.nFirstDisplayed = this.nLastDisplayed = -1 } //this.nLastMatchLength = 0 var aPos = this.getInsertPos( this.sActiveValue, this.nInsertPoint, sStartWith ) this.nLastMatchLength = aPos[0] } else { this.nFirstDisplayed = this.nLastDisplayed = -1 sStartWith = sStartWith.toLowerCase() var bEnd = false if( this.bSorted && this.bMatchBegin ) { var nStartAt = this.binarySearch( sStartWith ) for( var nI = 0; nI < nItemsLength; nI++ ) { hTItems[ nI ].style.display = 'none' if( nI >= nStartAt && !bEnd ) { if( !bEnd && this.aSearchData[ nI ].indexOf( sStartWith ) != 0 ) { bEnd = true continue } if( this.formatOptions ) { hTItems[ nI ].childNodes[0].innerHTML = this.formatOptions( hTItems[ nI ].childNodes[0].realText, nI ) } hTItems[ nI ].style.display = 'block' nCount++ if( this.nFirstDisplayed < 0 ) { this.nFirstDisplayed = nI } this.nLastDisplayed = nI } } } else { for( var nI = 0; nI < nItemsLength; nI++ ) { hTItems[ nI ].style.display = 'none' if( ( this.bMatchBegin && this.aSearchData[ nI ].indexOf( sStartWith ) == 0 ) || ( !this.bMatchBegin && this.aSearchData[ nI ].indexOf( sStartWith ) >= 0 ) ) { if( this.formatOptions ) { hTItems[ nI ].childNodes[0].innerHTML = this.formatOptions( hTItems[ nI ].childNodes[0].realText, nI ) } hTItems[ nI ].style.display = 'block' nCount++ if( this.nFirstDisplayed < 0 ) { this.nFirstDisplayed = nI } this.nLastDisplayed = nI } } } if( nCount > 0 ) { //this.nLastMatchLength = this.sActiveValue.length var aPos = this.getInsertPos( this.sActiveValue, this.nInsertPoint, sStartWith ) this.nLastMatchLength = aPos[0] } } hParent.appendChild( hList ) this.nItemsDisplayed = nCount } cAutocomplete.prototype.hideOptions = function() { var hContainer = document.getElementById( this.sListId ) hContainer.style.visibility = 'hidden' hContainer.style.display = 'none' cAutocomplete.hListDisplayed = null } cAutocomplete.prototype.markAutocompletedValue = function() { var hInput = document.getElementById( this.sInputId ) var sValue = this.hActiveSelection.realText if( this.bMatchSubstring ) { var aPos = this.getInsertPos( this.sLastActiveValue, this.nInsertPoint, sValue ) var nStartPos = aPos[ 0 ] var nEndPos = aPos[ 1 ] } else { var nStartPos = this.nInsertPoint var nEndPos = sValue.length } this.nStartAC = nStartPos this.nEndAC = nEndPos if( this.hMarkRangeTimeout != null ) { clearTimeout( this.hMarkRangeTimeout ) } this.hMarkRangeTimeout = setTimeout( function() { cAutocomplete.markInputRange2( hInput.id ) } , cAutocomplete.CN_MARK_TIMEOUT ) //cAutocomplete.markInputRange( hInput, nStartPos, nEndPos ) } cAutocomplete.prototype.selectOptionByIndex = function( nOptionIndex ) { if( this.bListUpdated ) { this.createList() } var hContainer = document.getElementById( this.sListId ) var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ] var nItemsLength = hList.childNodes.length if( nOptionIndex >=0 && nOptionIndex < nItemsLength ) { this.selectOption( hList.childNodes[ nOptionIndex ].getElementsByTagName( 'A' )[ 0 ] ) } } cAutocomplete.prototype.selectOptionByValue = function( sValue ) { if( this.bListUpdated ) { this.createList() } sValue = sValue.toLowerCase() var hContainer = document.getElementById( this.sListId ) var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ] var nItemsLength = hList.childNodes.length var nSelectedIndex = -1 for( var nI = 0; nI < nItemsLength; nI++ ) { if( this.aSearchData[ nI ].indexOf( sValue ) == 0 ) { nSelectedIndex = nI } } if( nSelectedIndex >=0 ) { this.selectOption( hList.childNodes[ nSelectedIndex ].getElementsByTagName( 'A' )[ 0 ] ) } } cAutocomplete.prototype.selectOption = function( hNewOption ) { if( this.hActiveSelection ) { if( this.hActiveSelection == hNewOption ) { return } else { this.hActiveSelection.className = '' } } this.hActiveSelection = hNewOption var hInput = document.getElementById( this.sInputId ) if( this.hActiveSelection != null ) { if( this.sHiddenInputId != null ) { if( this.bMatchSubstring ) { document.getElementById( this.sHiddenInputId ).value = this.hActiveSelection.getAttribute( 'itemvalue' ) } else { document.getElementById( this.sHiddenInputId ).value = this.hActiveSelection.getAttribute( 'itemvalue' ) } } this.hActiveSelection.className = 'selected' if( this.bAutoComplete ) { hInput.value = this.insertString( this.sLastActiveValue, this.nInsertPoint, this.hActiveSelection.realText ) this.bAutocompleted = true this.markAutocompletedValue() } else { var aPos = this.getInsertPos( this.sLastActiveValue, this.nInsertPoint, this.hActiveSelection.realText ) hInput.value = this.insertString( this.sActiveValue, this.nInsertPoint, this.hActiveSelection.realText ) //cAutocomplete.setInputCaretPosition( hInput, this.nInsertPoint ) cAutocomplete.setInputCaretPosition( hInput, aPos[ 1 ] ) } this.sActiveValue = hInput.value if( this.onSelect ) { this.onSelect() } } else { hInput.value = this.sActiveValue cAutocomplete.setInputCaretPosition( hInput, this.nInsertPoint ) } } cAutocomplete.prototype.deselectOption = function( ) { if( this.hActiveSelection != null ) { this.hActiveSelection.className = '' this.hActiveSelection = null } } cAutocomplete.prototype.clearList = function() { //this.deselectOption() this.hideOptions() this.bListDisplayed = false } cAutocomplete.prototype.getPrevDisplayedItem = function( hItem ) { if( hItem == null ) { var hContainer = document.getElementById( this.sListId ) hItem = hContainer.getElementsByTagName( 'UL' )[ 0 ].childNodes.item( hContainer.getElementsByTagName( 'UL' )[ 0 ].childNodes.length - 1 ) } else { hItem = getPrevNodeSibling( hItem.parentNode ) } while( hItem != null ) { if( hItem.style.display == 'block' ) { return hItem } hItem = hItem.previousSibling } return null } cAutocomplete.prototype.getNextDisplayedItem = function( hItem ) { if( hItem == null ) { var hContainer = document.getElementById( this.sListId ) hItem = hContainer.getElementsByTagName( 'UL' )[ 0 ].childNodes.item( 0 ) } else { hItem = getNextNodeSibling( hItem.parentNode ) } while( hItem != null ) { if( hItem.style.display == 'block' ) { return hItem } hItem = hItem.nextSibling } return null } cAutocomplete.onInputKeyDown = function ( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget var hAC = hElement.hAutocomplete var hContainer = document.getElementById( hAC.sListId ) var hInput = document.getElementById( hAC.sInputId ) var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ] var hEl = getParentByTagName( hElement, 'A' ) if( hContainer != null && hAC.bListDisplayed ) { var hLI = null var hLINext = null //the new active selection if( ( hEvent.keyCode == 13 ) || ( hEvent.keyCode == 27 ) ) { var bItemSelected = hEvent.keyCode == 13 ? true : false hAC.clearList() } if( hEvent.keyCode == 38 ) { //up key pressed hLINext = hAC.getPrevDisplayedItem( hAC.hActiveSelection ) if( hLINext != null ) { hAC.selectOption( hLINext.childNodes.item(0) ) if( hAC.nItemsDisplayed > cAutocomplete.CN_NUMBER_OF_LINES ) { if( hList.scrollTop < 5 && hLINext.offsetTop > hList.offsetHeight ) { hList.scrollTop = hList.scrollHeight - hList.offsetHeight } if( hLINext.offsetTop - hList.scrollTop < 0 ) { hList.scrollTop -= hLINext.offsetHeight } } } else { hAC.selectOption( null ) } } else if ( hEvent.keyCode == 40 ) { //down key pressed hLINext = hAC.getNextDisplayedItem( hAC.hActiveSelection ) if( hLINext != null ) { hAC.selectOption( hLINext.childNodes.item(0) ) if( hAC.nItemsDisplayed > cAutocomplete.CN_NUMBER_OF_LINES ) { if( hList.scrollTop > 0 && hList.scrollTop > hLINext.offsetTop ) { hList.scrollTop = 0 } if( Math.abs( hLINext.offsetTop - hList.scrollTop - hList.offsetHeight ) < 5 ) { hList.scrollTop += hLINext.offsetHeight } } } else { hAC.selectOption( null ) } } } if( hInput.form ) { hInput.form.bLocked = true } if ( hEvent.keyCode == 13 || hEvent.keyCode == 27 || hEvent.keyCode == 38 || hEvent.keyCode == 40 ) { if( hEvent.preventDefault ) { hEvent.preventDefault() } hEvent.cancelBubble = true hEvent.returnValue = false return false } } cAutocomplete.onInputKeyPress = function ( hEvent ) { if ( hEvent.keyCode == 13 || hEvent.keyCode == 38 || hEvent.keyCode == 40 ) { if( hEvent.preventDefault ) { hEvent.preventDefault() } hEvent.cancelBubble = true hEvent.returnValue = false return false } } cAutocomplete.onInputKeyUp = function ( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget var hAC = hElement.hAutocomplete var hInput = document.getElementById( hAC.sInputId ) //if we press the keys for up down enter or escape skip showing the list switch( hEvent.keyCode ) { case 8 : if( hAC.bAutoComplete && hAC.bAutocompleted ) { hAC.bAutocompleted = false return false } break case 38 : case 40 : if( hAC.bListDisplayed ) { if( hEvent.preventDefault ) { hEvent.preventDefault() } hEvent.cancelBubble = true hEvent.returnValue = false return false } break case 13 : case 32 : case 46 : //case 37 : //case 39 : case 35 : case 36 : break; default : if( hEvent.keyCode < 48 ) { if( hEvent.preventDefault ) { hEvent.preventDefault() } hEvent.cancelBubble = true hEvent.returnValue = false return false } break } if( hAC.hMarkRangeTimeout != null ) { clearTimeout( hAC.hMarkRangeTimeout ) } if( hAC.hShowTimeout ) { clearTimeout( hAC.hShowTimeout ) hAC.hShowTimeout = null } var nTimeout = hAC.bRemoteList ? cAutocomplete.CN_REMOTE_SHOW_TIMEOUT : cAutocomplete.CN_SHOW_TIMEOUT hAC.hShowTimeout = setTimeout( function(){ hAC.prepareList() }, nTimeout ) } cAutocomplete.onInputBlur = function( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget if( hElement.form ) { hElement.form.bLocked = false } var hAC = hElement.hAutocomplete if( !hAC.hClearTimeout ) { hAC.hClearTimeout = setTimeout( function(){ hAC.clearList() }, cAutocomplete.CN_CLEAR_TIMEOUT ) } } cAutocomplete.onInputFocus = function( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget var hAC = hElement.hAutocomplete if( hAC.hClearTimeout ) { clearTimeout( hAC.hClearTimeout ) hAC.hClearTimeout = null } } cAutocomplete.saveCaretPosition = function( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget var hAC = hElement.hAutocomplete var hInput = document.getElementById( hAC.sInputId ) //there is something weird about hitting up and down keys in a textarea if( hEvent.keyCode != 38 && hEvent.keyCode != 40 ) { hAC.nInsertPoint = cAutocomplete.getInputCaretPosition( hInput ) } } cAutocomplete.getInputCaretPosition = function( hInput ) { if( typeof hInput.selectionStart != 'undefined' ) { if( hInput.selectionStart == hInput.selectionEnd ) { return hInput.selectionStart } else { return hInput.selectionStart } } else if( hInput.createTextRange ) { var hSelRange = document.selection.createRange() if( hInput.tagName.toLowerCase() == 'textarea' ) { var hSelBefore = hSelRange.duplicate() var hSelAfter = hSelRange.duplicate() hSelRange.moveToElementText( hInput ) hSelBefore.setEndPoint( 'StartToStart', hSelRange ) return hSelBefore.text.length } else { hSelRange.moveStart( 'character', -1*hInput.value.length ) var nLen = hSelRange.text.length return nLen } } return null } cAutocomplete.setInputCaretPosition = function( hInput, nPosition ) { if ( hInput.setSelectionRange ) { hInput.setSelectionRange( nPosition ,nPosition ) } else if ( hInput.createTextRange ) { var hRange = hInput.createTextRange() hRange.moveStart( 'character', nPosition ) hRange.moveEnd( 'character', nPosition ) hRange.collapse(true) hRange.select() } } cAutocomplete.markInputRange = function( hInput, nStartPos, nEndPos ) { if( hInput.setSelectionRange ) { hInput.focus() hInput.setSelectionRange( nStartPos, nEndPos ) } else if( hInput.createTextRange ) { var hRange = hInput.createTextRange() hRange.collapse(true) hRange.moveStart( 'character', nStartPos ) hRange.moveEnd( 'character', nEndPos - nStartPos ) hRange.select() } } cAutocomplete.markInputRange2 = function( sInputId ) { var hInput = document.getElementById( sInputId ) var nStartPos = hInput.hAutocomplete.nStartAC var nEndPos = hInput.hAutocomplete.nEndAC cAutocomplete.markInputRange( hInput, nStartPos, nEndPos ) } cAutocomplete.onListBlur = function( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget hElement = getParentByProperty( hElement, 'className', 'autocomplete_holder' ) var hAC = hElement.hAutocomplete if( !hAC.hClearTimeout ) { hAC.hClearTimeout = setTimeout( function() { hAC.clearList() }, cAutocomplete.CN_CLEAR_TIMEOUT ) } } cAutocomplete.onListFocus = function( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget hElement = getParentByProperty( hElement, 'className', 'autocomplete_holder' ) var hAC = hElement.hAutocomplete if( hAC.hClearTimeout ) { clearTimeout( hAC.hClearTimeout ) hAC.hClearTimeout = null } } cAutocomplete.onItemClick = function( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget var hContainer = getParentByProperty( hElement, 'className', 'autocomplete_holder' ) var hEl = getParentByTagName( hElement, 'A' ) if( hContainer != null ) { var hAC = hContainer.hAutocomplete hAC.selectOption( hEl ) document.getElementById( hAC.sInputId ).focus() hAC.clearList() } if( hEvent.preventDefault ) { hEvent.preventDefault() } hEvent.cancelBubble = true hEvent.returnValue = false return false } cAutocomplete.onButtonClick = function ( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget var hAC = hElement.hAutocomplete var hInput = document.getElementById( hAC.sInputId ) if( hInput.disabled ) { return } hAC.prepareList( true ) var hInput = document.getElementById( hAC.sInputId ) hInput.focus() } cAutocomplete.onFormSubmit = function ( hEvent ) { if( hEvent == null ) { hEvent = window.event } var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget if( hElement.bLocked ) { hElement.bLocked = false hEvent.returnValue = false if( hEvent.preventDefault ) { hEvent.preventDefault() } return false } }