2 // This script was created
5 // Copyright (c) 2004-2005 Mircho Mirev
7 // :: feel free to use it BUT
8 // :: if you want to use this code PLEASE send me a note
9 // :: and please keep this disclaimer intact
12 function cAutocomplete( sInputId )
17 cAutocomplete.CS_NAME = 'Autocomplete component'
18 cAutocomplete.CS_OBJ_NAME = 'AC_COMPONENT'
19 cAutocomplete.CS_LIST_PREFIX = 'ACL_'
20 cAutocomplete.CS_BUTTON_PREFIX = 'ACB_'
21 cAutocomplete.CS_INPUT_PREFIX = 'AC_'
22 cAutocomplete.CS_HIDDEN_INPUT_PREFIX = 'ACH_'
23 cAutocomplete.CS_INPUT_CLASSNAME = 'dropdown'
25 cAutocomplete.CB_AUTOINIT = true
27 cAutocomplete.CB_AUTOCOMPLETE = false
29 cAutocomplete.CB_FORCECORRECT = false
31 //the separator when autocompleting multiple values
32 cAutocomplete.CB_MATCHSUBSTRING = false
33 cAutocomplete.CS_SEPARATOR = ','
35 //the separator of associative arrays
36 cAutocomplete.CS_ARRAY_SEPARATOR = ','
38 //match the input string only against the begining of the strings
39 //or anywhere in the string
40 cAutocomplete.CB_MATCHSTRINGBEGIN = true
42 cAutocomplete.CN_OFFSET_TOP = 2
43 cAutocomplete.CN_OFFSET_LEFT = -1
45 cAutocomplete.CN_LINE_HEIGHT = 19
46 cAutocomplete.CN_NUMBER_OF_LINES = 10
47 cAutocomplete.CN_HEIGHT_FIX = 2
49 cAutocomplete.CN_CLEAR_TIMEOUT = 300
50 cAutocomplete.CN_SHOW_TIMEOUT = 400
51 cAutocomplete.CN_REMOTE_SHOW_TIMEOUT = 1000
52 cAutocomplete.CN_MARK_TIMEOUT = 400
54 cAutocomplete.hListDisplayed = null
55 cAutocomplete.nCount = 0
57 cAutocomplete.autoInit = function()
63 for( nI = 0; nI < document.getElementsByTagName( 'INPUT' ).length; nI++ )
65 if( document.getElementsByTagName( 'INPUT' )[ nI ].type.toLowerCase() == 'text' )
67 sLangAtt = document.getElementsByTagName( 'INPUT' )[ nI ].getAttribute( 'acdropdown' )
68 if( sLangAtt != null && sLangAtt.length > 0 )
70 if( document.getElementsByTagName( 'INPUT' )[ nI ].id == null || document.getElementsByTagName( 'INPUT' )[ nI ].id.length == 0 )
72 document.getElementsByTagName( 'INPUT' )[ nI ].id = cAutocomplete.CS_OBJ_NAME + cAutocomplete.nCount
74 hACE = new cAutocomplete( document.getElementsByTagName( 'INPUT' )[ nI ].id )
79 var nTALength = document.getElementsByTagName( 'TEXTAREA' ).length
80 for( nI = 0; nI < nTALength; nI++ )
82 sLangAtt = document.getElementsByTagName( 'TEXTAREA' )[ nI ].getAttribute( 'acdropdown' )
83 if( sLangAtt != null && sLangAtt.length > 0 )
85 if( document.getElementsByTagName( 'TEXTAREA' )[ nI ].id == null || document.getElementsByTagName( 'TEXTAREA' )[ nI ].id.length == 0 )
87 document.getElementsByTagName( 'TEXTAREA' )[ nI ].id = cAutocomplete.CS_OBJ_NAME + cAutocomplete.nCount
89 hACE = new cAutocomplete( document.getElementsByTagName( 'TEXTAREA' )[ nI ].id )
94 var nSelectsLength = document.getElementsByTagName( 'SELECT' ).length
96 for( nI = 0; nI < nSelectsLength; nI++ )
98 aSelect = document.getElementsByTagName( 'SELECT' )[ nI ]
99 sLangAtt = aSelect.getAttribute( 'acdropdown' )
100 if( sLangAtt != null && sLangAtt.length > 0 )
102 if( aSelect.id == null || aSelect.id.length == 0 )
104 aSelect.id = cAutocomplete.CS_OBJ_NAME + cAutocomplete.nCount
106 hACE = new cAutocomplete( aSelect.id )
113 if( cAutocomplete.CB_AUTOINIT )
115 if( window.attachEvent )
117 window.attachEvent( 'onload', cAutocomplete.autoInit )
119 else if( window.addEventListener )
121 window.addEventListener( 'load', cAutocomplete.autoInit, false )
125 cAutocomplete.prototype.init = function( sInputId )
127 this.sInputId = sInputId
128 this.sListId = cAutocomplete.CS_LIST_PREFIX + sInputId
130 this.sObjName = cAutocomplete.CS_OBJ_NAME + '_obj_' + (cAutocomplete.nCount++)
131 this.hObj = this.sObjName
133 this.hActiveSelection = null
134 this.nSelectedItemIdx = -1
136 //the value of the input before the list is displayed
137 this.sLastActiveValue = ''
138 this.sActiveValue = ''
139 this.bListDisplayed = false
140 this.nItemsDisplayed = 0
142 //if I transform a select option or the supplied array is associative I create a hidden input
143 //with the name of the original input and replace the original input's name
144 this.bAssociative = false
145 this.sHiddenInputId = null
146 this.bHasButton = false
150 //the search array object
151 this.aSearchData = new Array()
154 //the length of the last matched typed string
155 this.nLastMatchLength = 0
157 this.bForceCorrect = cAutocomplete.CB_FORCECORRECT
158 var sForceCorrect = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_forcecorrect' )
159 if( sForceCorrect != null && sForceCorrect.length > 0 )
161 this.bForceCorrect = eval( sForceCorrect )
164 //match a only from the beginning or anywhere in the values
165 this.bMatchBegin = cAutocomplete.CB_MATCHSTRINGBEGIN
166 var sMatchBegin = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_matchbegin' )
167 if( sMatchBegin != null && sMatchBegin.length > 0 )
169 this.bMatchBegin = eval( sMatchBegin )
171 //match substrings separated by cAutocomplete.CS_SEPARATOR
172 this.bMatchSubstring = cAutocomplete.CB_MATCHSUBSTRING
173 var sMatchSubstring = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_matchsubstring' )
174 if( sMatchSubstring != null && sMatchSubstring.length > 0 )
176 this.bMatchSubstring = true
179 //autocomplete with the first option from the list
180 this.bAutoComplete = cAutocomplete.CB_AUTOCOMPLETE
181 this.bAutocompleted = false
182 var sAutoComplete = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_complete' )
183 if( sAutoComplete != null && sAutoComplete.length > 0 )
185 this.bAutoComplete = eval( sAutoComplete )
188 this.formatOptions = null
189 var sFormatFunction = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_format' )
190 if( sFormatFunction != null && sFormatFunction.length > 0 )
192 this.formatOptions = eval( sFormatFunction )
194 //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
195 //clicking on it with the mouse
197 var sOnSelectFunction = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_onselect' )
198 if( sOnSelectFunction != null && sOnSelectFunction.length > 0 )
200 this.onSelect = eval( sOnSelectFunction )
203 //onchange callback function - get called when a new option is selected by clicking on it or by pressing enter
204 //almost the same as onselect, but will get activated on
207 var sOnSelectFunction = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_onselect' )
208 if( sOnSelectFunction != null && sOnSelectFunction.length > 0 )
210 this.onSelect = eval( sOnSelectFunction )
214 //I assume that we always have the associative type
215 //you can turn it off only with the autocomplete_assoc=false attribute
216 this.bAssociative = true
217 var sAssociative = document.getElementById( this.sInputId ).getAttribute( 'autocomplete_assoc' )
218 if( sAssociative != null && sAssociative.length > 0 )
220 if( sAssociative == 'false' )
222 this.bAssociative = false
226 //if we have remote list then we postpone the list creation
227 if( this.getListArrayType() != 'url' )
229 this.bRemoteList = false
233 this.bRemoteList = true
234 this.sListURL = this.getListURL()
235 this.hXMLHttp = XmlHttp.create()
238 this.initListContainer()
242 eval( this.hObj + '= this' )
245 cAutocomplete.prototype.initInput = function()
247 var hInput = document.getElementById( this.sInputId )
248 hInput.hAutocomplete = this
249 var hContainer = document.getElementById( this.sListId )
250 hContainer.hAutocomplete = this
252 //any element ( and it's children ) with display:none have offset values of 0 ( in mozilla )
253 var nWidth = hInput.offsetWidth
254 if( !nWidth || nWidth == 0 )
256 //any element ( and it's children ) with display:none have offset values of 0 ( in mozilla )
257 var hOWInput = hInput.cloneNode( true )
258 hOWInput.style.position = 'absolute'
259 hOWInput.style.top = '-1000px'
260 document.body.appendChild( hOWInput )
261 var nWidth = hOWInput.offsetWidth
262 document.body.removeChild( hOWInput )
265 var sInputName = hInput.name
266 var hForm = hInput.form
267 var bHasButton = false
268 var sHiddenValue = hInput.value
269 var sValue = hInput.type.toLowerCase() == 'text' ? hInput.value : ''
271 var sHasButton = hInput.getAttribute( 'autocomplete_button' )
272 if( sHasButton != null && sHasButton.length > 0 )
277 //if it is a select - I unconditionally add a button
278 if( hInput.type.toLowerCase() == 'select-one' )
281 if( hInput.selectedIndex >= 0 )
283 sHiddenValue = hInput.options[ hInput.selectedIndex ].value
284 sValue = hInput.options[ hInput.selectedIndex ].text
288 //this is the case when the control is a transformed select or the list supplied is of the type - key,value not only values
291 var hHiddenInput = document.createElement( 'INPUT' )
292 hHiddenInput.id = cAutocomplete.CS_HIDDEN_INPUT_PREFIX + this.sInputId
293 hHiddenInput.type = 'hidden'
294 hForm.appendChild( hHiddenInput )
296 if( this.bAssociative )
298 hHiddenInput.name = sInputName
299 hInput.name = cAutocomplete.CS_INPUT_PREFIX + sInputName
303 hHiddenInput.name = cAutocomplete.CS_INPUT_PREFIX + sInputName
306 hHiddenInput.value = sHiddenValue
307 this.sHiddenInputId = hHiddenInput.id
312 this.bHasButton = true
314 var hInputContainer = document.createElement( 'DIV' )
315 hInputContainer.className = 'acinputContainer'
316 hInputContainer.style.width = nWidth
318 var hInputButton = document.createElement( 'INPUT' )
319 hInputButton.id = cAutocomplete.CS_BUTTON_PREFIX + this.sInputId
320 hInputButton.type = 'button'
321 hInputButton.className = 'button'
322 hInputButton.tabIndex = hInput.tabIndex + 1
323 hInputButton.hAutocomplete = this
325 var hNewInput = document.createElement( 'INPUT' )
326 if( this.bAssociative )
328 hNewInput.name = cAutocomplete.CS_INPUT_PREFIX + sInputName
332 hNewInput.name = sInputName
335 hNewInput.type = 'text'
336 hNewInput.value = sValue
337 hNewInput.style.width = nWidth-22
338 hNewInput.className = cAutocomplete.CS_INPUT_CLASSNAME
339 hNewInput.tabIndex = hInput.tabIndex
340 hNewInput.hAutocomplete = this
342 hInputContainer.appendChild( hNewInput )
343 hInputContainer.appendChild( hInputButton )
345 hInput.parentNode.replaceChild( hInputContainer, hInput )
347 hNewInput.id = this.sInputId
351 if( hInput.attachEvent )
353 hInput.attachEvent( 'onkeyup', cAutocomplete.onInputKeyUp )
354 hInput.attachEvent( 'onkeyup', cAutocomplete.saveCaretPosition )
355 hInput.attachEvent( 'onkeydown', cAutocomplete.onInputKeyDown )
356 hInput.attachEvent( 'onblur', cAutocomplete.onInputBlur )
357 hInput.attachEvent( 'onfocus', cAutocomplete.onInputFocus )
361 hInputButton.attachEvent( 'onclick', cAutocomplete.onButtonClick )
364 else if( hInput.addEventListener )
366 hInput.addEventListener( 'keyup', cAutocomplete.onInputKeyUp, false )
367 hInput.addEventListener( 'keyup', cAutocomplete.saveCaretPosition, false )
368 hInput.addEventListener( 'keydown', cAutocomplete.onInputKeyDown, false )
369 hInput.addEventListener( 'keypress', cAutocomplete.onInputKeyPress, false )
370 hInput.addEventListener( 'blur', cAutocomplete.onInputBlur, false )
371 hInput.addEventListener( 'focus', cAutocomplete.onInputFocus, false )
375 hInputButton.addEventListener( 'click', cAutocomplete.onButtonClick, false )
379 //I don't need the standard autocomplete
380 hInput.setAttribute( 'autocomplete', 'OFF' )
384 if( hForm.attachEvent )
386 hForm.attachEvent( 'onsubmit', cAutocomplete.onFormSubmit )
388 else if( hForm.addEventListener )
390 hForm.addEventListener( 'submit', cAutocomplete.onFormSubmit, false )
395 cAutocomplete.prototype.initListContainer = function()
397 var hInput = document.getElementById( this.sInputId )
398 var hContainer = document.createElement( 'DIV' )
399 hContainer.className = 'autocomplete_holder'
400 hContainer.id = this.sListId
401 hContainer.style.zIndex = 10000 + cAutocomplete.nCount
402 hContainer.hAutocomplete = this
404 var hFirstBorder = document.createElement( 'DIV' )
405 hFirstBorder.className = 'autocomplete_firstborder'
406 var hSecondBorder = document.createElement( 'DIV' )
407 hSecondBorder.className = 'autocomplete_secondborder'
409 var hList = document.createElement( 'UL' )
410 hList.className = 'autocomplete'
412 hSecondBorder.appendChild( hList )
413 hFirstBorder.appendChild( hSecondBorder )
414 hContainer.appendChild( hFirstBorder )
415 document.body.appendChild( hContainer )
417 if( hContainer.attachEvent )
419 hContainer.attachEvent( 'onblur', cAutocomplete.onListBlur )
420 hContainer.attachEvent( 'onfocus', cAutocomplete.onListFocus )
422 else if( hInput.addEventListener )
424 hContainer.addEventListener( 'blur', cAutocomplete.onListBlur, false )
425 hContainer.addEventListener( 'focus', cAutocomplete.onListFocus, false )
429 if( hContainer.attachEvent )
431 hContainer.attachEvent( 'onclick', cAutocomplete.onItemClick )
433 else if( hContainer.addEventListener )
435 hContainer.addEventListener( 'click', cAutocomplete.onItemClick, false )
439 cAutocomplete.prototype.createList = function()
441 var hInput = document.getElementById( this.sInputId )
442 var hContainer = document.getElementById( this.sListId )
443 var hList = hContainer.getElementsByTagName( 'UL' )[0]
446 hList = hList.parentNode.removeChild( hList )
447 while( hList.hasChildNodes() )
449 hList.removeChild( hList.childNodes[ 0 ] )
454 var hListItemLink = null
458 var hArr = this.aData
461 for( hArrKey in hArr )
463 sArrEl = hArr[ hArrKey ]
464 hListItem = document.createElement( 'LI' )
465 hListItemLink = document.createElement( 'A' )
466 hListItemLink.setAttribute( 'itemvalue', hArrKey )
468 /* so you can attach data to the element */
469 /* it's a hack but seems to work */
471 var sArrData = sArrEl.split( cAutocomplete.CS_ARRAY_SEPARATOR )
472 if( sArrData.length > 1 )
474 this.aData[ hArrKey ] = sArrData[ 0 ]
475 hListItemLink.setAttribute( 'itemdata', sArrEl.substring( sArrEl.indexOf( cAutocomplete.CS_ARRAY_SEPARATOR ) + 1 ) )
476 sRealText = sArrData[ 0 ]
482 /* end of attach data to the element */
484 hListItemLink.href = '#'
485 hListItemLink.appendChild( document.createTextNode( sRealText ) )
486 hListItemLink.realText = sRealText
487 if( nI == this.nSelectedItemIdx )
489 this.hActiveSelection = hListItemLink
490 this.hActiveSelection.className = 'selected'
492 hListItem.appendChild( hListItemLink )
493 hList.appendChild( hListItem )
494 this.aSearchData[ nI++ ] = sRealText.toLowerCase()
497 var hSecondBorder = hContainer.firstChild.firstChild
498 hSecondBorder.appendChild( hList )
499 this.bListUpdated = false
502 /* list array functions */
504 cAutocomplete.prototype.initListArray = function()
506 var hInput = document.getElementById( this.sInputId )
509 if( hInput.type.toLowerCase() == 'select-one' )
512 for( var nI = 0; nI < hInput.options.length; nI++ )
514 hArrKey = hInput.options.item( nI ).value
515 sArrEl = hInput.options.item( nI ).text
516 hArr[ hArrKey ] = sArrEl
517 if( hInput.options.item( nI ).selected )
519 this.nSelectedItemIdx = nI
525 var sAA = hInput.getAttribute( 'autocomplete_list' )
526 var sAAS = hInput.getAttribute( 'autocomplete_list_sort' )
528 var sArrayType = this.getListArrayType()
532 case 'array' : hArr = eval( sAA.substring( 6 ) )
535 case 'list' : hArr = new Array()
536 var hTmpArray = sAA.substring( 5 ).split( '|' )
538 for( hKey in hTmpArray )
540 aValueArr = hTmpArray[ hKey ].split( cAutocomplete.CS_ARRAY_SEPARATOR )
541 if( aValueArr.length == 1 )
543 hArr[ hKey ] = hTmpArray[ hKey ]
544 this.bAssociative = false
548 hArr[ aValueArr[ 0 ] ] = aValueArr[ 1 ]
553 if( sAAS != null && eval( sAAS ) )
556 this.aData = hArr.sort()
560 this.setArray( hArr )
563 cAutocomplete.prototype.setArray = function( sArray )
565 if( typeof sArray == 'string' )
567 this.aData = eval( sArray )
573 this.bListUpdated = true
576 //use this function to change the list of autocomplete values to a new one
577 //supply as an argument the name as a literal of an JS array object
578 //well things changed - you can supply an actual array too
579 cAutocomplete.prototype.setListArray = function( sArray )
581 this.setArray( sArray )
582 this.updateAndShowList()
585 cAutocomplete.prototype.getListArrayType = function()
587 var hInput = document.getElementById( this.sInputId )
588 var sAA = hInput.getAttribute( 'autocomplete_list' )
589 if( sAA != null && sAA.length > 0 )
591 if( sAA.indexOf( 'array:' ) >= 0 )
595 else if( sAA.indexOf( 'list:' ) >= 0 )
599 else if( sAA.indexOf( 'url:' ) >= 0 )
606 cAutocomplete.prototype.getListURL = function()
608 var hInput = document.getElementById( this.sInputId )
609 var sAA = hInput.getAttribute( 'autocomplete_list' )
610 if( sAA != null && sAA.length > 0 )
612 if( sAA.indexOf( 'url:' ) >= 0 )
614 return sAA.substring( 4 )
619 cAutocomplete.prototype.setListURL = function( sURL )
621 this.sListURL = sURL;
624 cAutocomplete.prototype.onXmlHttpLoad = function()
626 if( this.hXMLHttp.readyState == 4 )
628 var hError = this.hXMLHttp.parseError
629 if( hError && hError.errorCode != 0 )
631 alert( hError.reason )
635 this.afterRemoteLoad()
640 cAutocomplete.prototype.loadListArray = function()
642 var sURL = this.sListURL
643 var sStartWith = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint )
644 sStartWith = sStartWith.replace( /^\s/, '' )
645 sStartWith = sStartWith.replace( /\s$/, '' )
646 if( sURL.indexOf( '[S]' ) >= 0 )
648 sURL = sURL.replace( '[S]', sStartWith )
652 sURL += this.sActiveValue
654 this.hXMLHttp.open( 'GET', sURL, true )
657 this.hXMLHttp.onreadystatechange = function() { hAC.onXmlHttpLoad() }
658 this.hXMLHttp.send( null )
661 cAutocomplete.prototype.afterRemoteLoad = function()
663 var hInput = document.getElementById( this.sInputId )
665 var hArr = new Array()
666 var hTmpArray = this.hXMLHttp.responseText.split( '|' )
668 for( hKey in hTmpArray )
670 if(hTmpArray[ hKey ].split) {
671 aValueArr = hTmpArray[ hKey ].split( cAutocomplete.CS_ARRAY_SEPARATOR )
672 if( aValueArr.length == 1 )
674 hArr[ hKey ] = hTmpArray[ hKey ]
678 hArr[ aValueArr[ 0 ] ] = hTmpArray[ hKey ].substr( hTmpArray[ hKey ].indexOf( cAutocomplete.CS_ARRAY_SEPARATOR ) + 1 )
683 hInput.className = ''
684 hInput.readonly = false
685 hInput.value = this.sActiveValue
686 this.setListArray( hArr )
691 cAutocomplete.prototype.prepareList = function( bFullList )
693 var hInput = document.getElementById( this.sInputId )
694 this.sActiveValue = hInput.value
695 if( this.bRemoteList )
697 hInput.readonly = true
700 //check if this was invoked by a key that did not change the value
701 var sST = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint )
702 var sLST = this.getStringForAutocompletion( this.sLastActiveValue, this.nInsertPoint )
704 if( sLST != sST || bFullList || !this.bListDisplayed || this.bMatchSubstring )
706 if( this.bRemoteList )
708 hInput.className = 'search'
709 hInput.value = 'please wait...'
713 this.updateAndShowList( bFullList )
717 cAutocomplete.prototype.updateAndShowList = function( bFullList )
719 var hContainer = document.getElementById( this.sListId )
720 var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ]
721 var hInput = document.getElementById( this.sInputId )
723 if( this.bListUpdated )
728 //stupid hack just for speed
729 var sST = this.bMatchSubstring ? this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint ) : this.sActiveValue
730 var sLST = this.bMatchSubstring ? this.getStringForAutocompletion( this.sLastActiveValue, this.nInsertPoint ) : this.sLastActiveValue
732 //nothing changed since last type - maybe only function keys were pressed
733 //this is the case when for example the down key was pressed
736 if( !this.bMatchSubstring )
741 this.filterOptions( bFullList )
743 if( this.nItemsDisplayed == 0 )
745 if( this.bForceCorrect )
747 var aPos = this.getInsertPos( this.sActiveValue, this.nInsertPoint, '' )
748 cAutocomplete.markInputRange( hInput, this.nLastMatchLength, aPos[0] )
752 this.sLastActiveValue = this.sActiveValue
754 if( this.nItemsDisplayed > 0 )
756 if( !bFullList || this.bMatchSubstring )
758 this.deselectOption()
760 if( this.bAutoComplete && this.nItemsDisplayed == 1 )
762 //test if we have a full match i.e. the user typed the entire value
763 var sStartWith = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint )
764 var sItemText = hList.getElementsByTagName( 'LI' )[ this.nFirstDisplayed ].getElementsByTagName( 'A' )[ 0 ].realText
765 if( sStartWith.toLowerCase() == sItemText.toLowerCase() )
767 this.selectOption( hList.getElementsByTagName( 'LI' )[ this.nFirstDisplayed ].getElementsByTagName( 'A' )[ 0 ] )
769 //and do not show the list
773 if( this.bAutoComplete && !bFullList )
775 this.selectOption( hList.getElementsByTagName( 'LI' )[ this.nFirstDisplayed ].getElementsByTagName( 'A' )[ 0 ] )
785 cAutocomplete.prototype.showList = function()
787 if( cAutocomplete.hListDisplayed )
789 cAutocomplete.hListDisplayed.clearList()
791 var hInput = document.getElementById( this.sInputId )
792 var nTop = cDomObject.getOffsetParam( hInput, 'offsetTop' )
793 var nLeft = cDomObject.getOffsetParam( hInput, 'offsetLeft' )
794 var hContainer = document.getElementById( this.sListId )
796 var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ]
797 if( this.bHasButton )
799 hContainer.style.width = document.getElementById( this.sInputId ).parentNode.offsetWidth
803 hContainer.style.width = document.getElementById( this.sInputId ).offsetWidth
805 var nNumLines = ( this.nItemsDisplayed < cAutocomplete.CN_NUMBER_OF_LINES ) ? this.nItemsDisplayed : cAutocomplete.CN_NUMBER_OF_LINES;
806 hList.style.height = nNumLines * cAutocomplete.CN_LINE_HEIGHT + cAutocomplete.CN_HEIGHT_FIX + 'px'
808 hContainer.style.top = nTop + hInput.offsetHeight + cAutocomplete.CN_OFFSET_TOP + 'px'
809 hContainer.style.left = nLeft + cAutocomplete.CN_OFFSET_LEFT + 'px'
811 hContainer.style.display = 'none'
812 hContainer.style.visibility = 'visible'
813 hContainer.style.display = 'block'
815 cAutocomplete.hListDisplayed = this
816 this.bListDisplayed = true
819 cAutocomplete.prototype.binarySearch = function( sFilter )
822 var nHigh = this.aSearchData.length - 1
826 var nLen = sFilter.length
830 while ( nLow <= nHigh )
832 nMid = ( nLow + nHigh ) / 2
833 nTry = ( nMid < 1 ) ? 0 : parseInt( nMid )
835 sData = this.aSearchData[ nTry ].substr( 0, nLen )
837 if ( sData < sFilter )
842 if ( sData > sFilter )
847 if ( sData == sFilter )
856 if ( typeof ( nLastTry ) != "undefined" )
866 cAutocomplete.prototype.getStringForAutocompletion = function( sString, nPos )
868 if( sString == null || sString.length == 0 )
872 if( this.bMatchSubstring )
874 var nStartPos = sString.lastIndexOf( cAutocomplete.CS_SEPARATOR, nPos - 1 )
875 nStartPos = nStartPos < 0 ? 0 : nStartPos
876 var nEndPos = sString.indexOf( cAutocomplete.CS_SEPARATOR, nPos )
877 nEndPos = nEndPos < 0 ? sString.length : nEndPos
878 var sStr = sString.substr( nStartPos, nEndPos - nStartPos )
879 sStr = sStr.replace( /^(\,?)(\s*)(\S*)(\s*)(\,?)$/g, '$3' )
888 cAutocomplete.prototype.insertString = function( sString, nPos, sInsert )
890 if( this.bMatchSubstring )
892 var nStartPos = sString.lastIndexOf( cAutocomplete.CS_SEPARATOR, nPos - 1 )
893 nStartPos = nStartPos < 0 ? 0 : nStartPos
894 var nEndPos = sString.indexOf( cAutocomplete.CS_SEPARATOR, nPos )
895 nEndPos = nEndPos < 0 ? sString.length : nEndPos
896 var sStr = sString.substr( nStartPos, nEndPos - nStartPos )
897 sStr = sStr.replace( /^(\,?)(\s*)(\S?[\S\s]*\S?)(\s*)(\,?)$/g, '$1$2'+sInsert+'$4$5' )
898 sStr = sString.substr( 0, nStartPos ) + sStr + sString.substr( nEndPos )
907 cAutocomplete.prototype.getInsertPos = function( sString, nPos, sInsert )
909 nPos = nPos == null ? 0 : nPos
910 var nStartPos = sString.lastIndexOf( cAutocomplete.CS_SEPARATOR, nPos - 1 )
911 nStartPos = nStartPos < 0 ? 0 : nStartPos
912 var nEndPos = sString.indexOf( cAutocomplete.CS_SEPARATOR, nPos )
913 nEndPos = nEndPos < 0 ? sString.length : nEndPos
914 var sStr = sString.substr( nStartPos, nEndPos - nStartPos )
915 sStr = sStr.replace( /^(\,?)(\s*)(\S?[\S\s]*\S?)(\s*)(\,?)$/g, '$1$2'+sInsert )
916 return [ nPos, nStartPos + sStr.length ]
919 cAutocomplete.prototype.filterOptions = function( bShowAll )
921 if( this.hActiveSelection && !bShowAll )
923 this.hActiveSelection.className = ''
925 if( typeof bShowAll == 'undefined' )
930 var hInput = document.getElementById( this.sInputId )
932 var sStartWith = this.getStringForAutocompletion( this.sActiveValue, this.nInsertPoint )
938 var hContainer = document.getElementById( this.sListId )
939 var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ]
940 var nItemsLength = hList.childNodes.length
944 var hParent = hList.parentNode
945 var hList = hList.parentNode.removeChild( hList )
946 var hTItems = hList.childNodes
948 this.nItemsDisplayed = 0
950 if( sStartWith.length == 0 )
952 for( var nI = 0; nI < nItemsLength; nI++ )
954 if( this.formatOptions )
956 hTItems[ nI ].childNodes[0].innerHTML = this.formatOptions( hTItems[ nI ].childNodes[0].realText, nI )
958 hTItems[ nI ].style.display = 'block'
961 nCount = nItemsLength
963 if( nItemsLength > 0 )
965 this.nFirstDisplayed = 0
966 this.nLastDisplayed = nItemsLength - 1
970 this.nFirstDisplayed = this.nLastDisplayed = -1
973 //this.nLastMatchLength = 0
974 var aPos = this.getInsertPos( this.sActiveValue, this.nInsertPoint, sStartWith )
975 this.nLastMatchLength = aPos[0]
979 this.nFirstDisplayed = this.nLastDisplayed = -1
980 sStartWith = sStartWith.toLowerCase()
982 if( this.bSorted && this.bMatchBegin )
984 var nStartAt = this.binarySearch( sStartWith )
985 for( var nI = 0; nI < nItemsLength; nI++ )
987 hTItems[ nI ].style.display = 'none'
988 if( nI >= nStartAt && !bEnd )
990 if( !bEnd && this.aSearchData[ nI ].indexOf( sStartWith ) != 0 )
995 if( this.formatOptions )
997 hTItems[ nI ].childNodes[0].innerHTML = this.formatOptions( hTItems[ nI ].childNodes[0].realText, nI )
999 hTItems[ nI ].style.display = 'block'
1001 if( this.nFirstDisplayed < 0 )
1003 this.nFirstDisplayed = nI
1005 this.nLastDisplayed = nI
1011 for( var nI = 0; nI < nItemsLength; nI++ )
1013 hTItems[ nI ].style.display = 'none'
1014 if( ( this.bMatchBegin && this.aSearchData[ nI ].indexOf( sStartWith ) == 0 ) || ( !this.bMatchBegin && this.aSearchData[ nI ].indexOf( sStartWith ) >= 0 ) )
1016 if( this.formatOptions )
1018 hTItems[ nI ].childNodes[0].innerHTML = this.formatOptions( hTItems[ nI ].childNodes[0].realText, nI )
1020 hTItems[ nI ].style.display = 'block'
1022 if( this.nFirstDisplayed < 0 )
1024 this.nFirstDisplayed = nI
1026 this.nLastDisplayed = nI
1033 //this.nLastMatchLength = this.sActiveValue.length
1034 var aPos = this.getInsertPos( this.sActiveValue, this.nInsertPoint, sStartWith )
1035 this.nLastMatchLength = aPos[0]
1038 hParent.appendChild( hList )
1039 this.nItemsDisplayed = nCount
1042 cAutocomplete.prototype.hideOptions = function()
1044 var hContainer = document.getElementById( this.sListId )
1045 hContainer.style.visibility = 'hidden'
1046 hContainer.style.display = 'none'
1047 cAutocomplete.hListDisplayed = null
1050 cAutocomplete.prototype.markAutocompletedValue = function()
1052 var hInput = document.getElementById( this.sInputId )
1053 var sValue = this.hActiveSelection.realText
1054 if( this.bMatchSubstring )
1056 var aPos = this.getInsertPos( this.sLastActiveValue, this.nInsertPoint, sValue )
1057 var nStartPos = aPos[ 0 ]
1058 var nEndPos = aPos[ 1 ]
1062 var nStartPos = this.nInsertPoint
1063 var nEndPos = sValue.length
1065 this.nStartAC = nStartPos
1066 this.nEndAC = nEndPos
1068 if( this.hMarkRangeTimeout != null )
1070 clearTimeout( this.hMarkRangeTimeout )
1072 this.hMarkRangeTimeout = setTimeout( function() {
1073 cAutocomplete.markInputRange2( hInput.id )
1075 , cAutocomplete.CN_MARK_TIMEOUT )
1076 //cAutocomplete.markInputRange( hInput, nStartPos, nEndPos )
1079 cAutocomplete.prototype.selectOptionByIndex = function( nOptionIndex )
1081 if( this.bListUpdated )
1086 var hContainer = document.getElementById( this.sListId )
1087 var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ]
1088 var nItemsLength = hList.childNodes.length
1089 if( nOptionIndex >=0 && nOptionIndex < nItemsLength )
1091 this.selectOption( hList.childNodes[ nOptionIndex ].getElementsByTagName( 'A' )[ 0 ] )
1095 cAutocomplete.prototype.selectOptionByValue = function( sValue )
1097 if( this.bListUpdated )
1102 sValue = sValue.toLowerCase()
1104 var hContainer = document.getElementById( this.sListId )
1105 var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ]
1106 var nItemsLength = hList.childNodes.length
1108 var nSelectedIndex = -1
1109 for( var nI = 0; nI < nItemsLength; nI++ )
1111 if( this.aSearchData[ nI ].indexOf( sValue ) == 0 )
1116 if( nSelectedIndex >=0 )
1118 this.selectOption( hList.childNodes[ nSelectedIndex ].getElementsByTagName( 'A' )[ 0 ] )
1122 cAutocomplete.prototype.selectOption = function( hNewOption )
1124 if( this.hActiveSelection )
1126 if( this.hActiveSelection == hNewOption )
1132 this.hActiveSelection.className = ''
1135 this.hActiveSelection = hNewOption
1136 var hInput = document.getElementById( this.sInputId )
1137 if( this.hActiveSelection != null )
1139 if( this.sHiddenInputId != null )
1141 if( this.bMatchSubstring )
1143 document.getElementById( this.sHiddenInputId ).value = this.hActiveSelection.getAttribute( 'itemvalue' )
1147 document.getElementById( this.sHiddenInputId ).value = this.hActiveSelection.getAttribute( 'itemvalue' )
1151 this.hActiveSelection.className = 'selected'
1152 if( this.bAutoComplete )
1154 hInput.value = this.insertString( this.sLastActiveValue, this.nInsertPoint, this.hActiveSelection.realText )
1155 this.bAutocompleted = true
1156 this.markAutocompletedValue()
1160 var aPos = this.getInsertPos( this.sLastActiveValue, this.nInsertPoint, this.hActiveSelection.realText )
1161 hInput.value = this.insertString( this.sActiveValue, this.nInsertPoint, this.hActiveSelection.realText )
1162 //cAutocomplete.setInputCaretPosition( hInput, this.nInsertPoint )
1163 cAutocomplete.setInputCaretPosition( hInput, aPos[ 1 ] )
1166 this.sActiveValue = hInput.value
1175 hInput.value = this.sActiveValue
1176 cAutocomplete.setInputCaretPosition( hInput, this.nInsertPoint )
1180 cAutocomplete.prototype.deselectOption = function( )
1182 if( this.hActiveSelection != null )
1184 this.hActiveSelection.className = ''
1185 this.hActiveSelection = null
1189 cAutocomplete.prototype.clearList = function()
1191 //this.deselectOption()
1193 this.bListDisplayed = false
1196 cAutocomplete.prototype.getPrevDisplayedItem = function( hItem )
1200 var hContainer = document.getElementById( this.sListId )
1201 hItem = hContainer.getElementsByTagName( 'UL' )[ 0 ].childNodes.item( hContainer.getElementsByTagName( 'UL' )[ 0 ].childNodes.length - 1 )
1205 hItem = getPrevNodeSibling( hItem.parentNode )
1207 while( hItem != null )
1209 if( hItem.style.display == 'block' )
1213 hItem = hItem.previousSibling
1218 cAutocomplete.prototype.getNextDisplayedItem = function( hItem )
1222 var hContainer = document.getElementById( this.sListId )
1223 hItem = hContainer.getElementsByTagName( 'UL' )[ 0 ].childNodes.item( 0 )
1227 hItem = getNextNodeSibling( hItem.parentNode )
1229 while( hItem != null )
1231 if( hItem.style.display == 'block' )
1235 hItem = hItem.nextSibling
1240 cAutocomplete.onInputKeyDown = function ( hEvent )
1242 if( hEvent == null )
1244 hEvent = window.event
1246 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1247 var hAC = hElement.hAutocomplete
1248 var hContainer = document.getElementById( hAC.sListId )
1249 var hInput = document.getElementById( hAC.sInputId )
1250 var hList = hContainer.getElementsByTagName( 'UL' )[ 0 ]
1251 var hEl = getParentByTagName( hElement, 'A' )
1252 if( hContainer != null && hAC.bListDisplayed )
1256 //the new active selection
1257 if( ( hEvent.keyCode == 13 ) || ( hEvent.keyCode == 27 ) )
1259 var bItemSelected = hEvent.keyCode == 13 ? true : false
1262 if( hEvent.keyCode == 38 )
1265 hLINext = hAC.getPrevDisplayedItem( hAC.hActiveSelection )
1266 if( hLINext != null )
1268 hAC.selectOption( hLINext.childNodes.item(0) )
1269 if( hAC.nItemsDisplayed > cAutocomplete.CN_NUMBER_OF_LINES )
1271 if( hList.scrollTop < 5 && hLINext.offsetTop > hList.offsetHeight )
1273 hList.scrollTop = hList.scrollHeight - hList.offsetHeight
1275 if( hLINext.offsetTop - hList.scrollTop < 0 )
1277 hList.scrollTop -= hLINext.offsetHeight
1283 hAC.selectOption( null )
1286 else if ( hEvent.keyCode == 40 )
1289 hLINext = hAC.getNextDisplayedItem( hAC.hActiveSelection )
1290 if( hLINext != null )
1292 hAC.selectOption( hLINext.childNodes.item(0) )
1293 if( hAC.nItemsDisplayed > cAutocomplete.CN_NUMBER_OF_LINES )
1295 if( hList.scrollTop > 0 && hList.scrollTop > hLINext.offsetTop )
1299 if( Math.abs( hLINext.offsetTop - hList.scrollTop - hList.offsetHeight ) < 5 )
1301 hList.scrollTop += hLINext.offsetHeight
1307 hAC.selectOption( null )
1313 hInput.form.bLocked = true
1315 if ( hEvent.keyCode == 13 || hEvent.keyCode == 27 || hEvent.keyCode == 38 || hEvent.keyCode == 40 )
1317 if( hEvent.preventDefault )
1319 hEvent.preventDefault()
1321 hEvent.cancelBubble = true
1322 hEvent.returnValue = false
1327 cAutocomplete.onInputKeyPress = function ( hEvent )
1329 if ( hEvent.keyCode == 13 || hEvent.keyCode == 38 || hEvent.keyCode == 40 )
1331 if( hEvent.preventDefault )
1333 hEvent.preventDefault()
1335 hEvent.cancelBubble = true
1336 hEvent.returnValue = false
1341 cAutocomplete.onInputKeyUp = function ( hEvent )
1343 if( hEvent == null )
1345 hEvent = window.event
1347 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1348 var hAC = hElement.hAutocomplete
1349 var hInput = document.getElementById( hAC.sInputId )
1350 //if we press the keys for up down enter or escape skip showing the list
1351 switch( hEvent.keyCode )
1353 case 8 : if( hAC.bAutoComplete && hAC.bAutocompleted )
1355 hAC.bAutocompleted = false
1360 case 40 : if( hAC.bListDisplayed )
1362 if( hEvent.preventDefault )
1364 hEvent.preventDefault()
1366 hEvent.cancelBubble = true
1367 hEvent.returnValue = false
1378 default : if( hEvent.keyCode < 48 )
1380 if( hEvent.preventDefault )
1382 hEvent.preventDefault()
1384 hEvent.cancelBubble = true
1385 hEvent.returnValue = false
1391 if( hAC.hMarkRangeTimeout != null )
1393 clearTimeout( hAC.hMarkRangeTimeout )
1396 if( hAC.hShowTimeout )
1398 clearTimeout( hAC.hShowTimeout )
1399 hAC.hShowTimeout = null
1401 var nTimeout = hAC.bRemoteList ? cAutocomplete.CN_REMOTE_SHOW_TIMEOUT : cAutocomplete.CN_SHOW_TIMEOUT
1402 hAC.hShowTimeout = setTimeout( function(){ hAC.prepareList() }, nTimeout )
1405 cAutocomplete.onInputBlur = function( hEvent )
1407 if( hEvent == null )
1409 hEvent = window.event
1411 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1414 hElement.form.bLocked = false
1416 var hAC = hElement.hAutocomplete
1417 if( !hAC.hClearTimeout )
1419 hAC.hClearTimeout = setTimeout( function(){ hAC.clearList() }, cAutocomplete.CN_CLEAR_TIMEOUT )
1423 cAutocomplete.onInputFocus = function( hEvent )
1425 if( hEvent == null )
1427 hEvent = window.event
1429 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1430 var hAC = hElement.hAutocomplete
1431 if( hAC.hClearTimeout )
1433 clearTimeout( hAC.hClearTimeout )
1434 hAC.hClearTimeout = null
1438 cAutocomplete.saveCaretPosition = function( hEvent )
1440 if( hEvent == null )
1442 hEvent = window.event
1444 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1445 var hAC = hElement.hAutocomplete
1446 var hInput = document.getElementById( hAC.sInputId )
1448 //there is something weird about hitting up and down keys in a textarea
1449 if( hEvent.keyCode != 38 && hEvent.keyCode != 40 )
1451 hAC.nInsertPoint = cAutocomplete.getInputCaretPosition( hInput )
1455 cAutocomplete.getInputCaretPosition = function( hInput )
1457 if( typeof hInput.selectionStart != 'undefined' )
1459 if( hInput.selectionStart == hInput.selectionEnd )
1461 return hInput.selectionStart
1465 return hInput.selectionStart
1468 else if( hInput.createTextRange )
1470 var hSelRange = document.selection.createRange()
1471 if( hInput.tagName.toLowerCase() == 'textarea' )
1473 var hSelBefore = hSelRange.duplicate()
1474 var hSelAfter = hSelRange.duplicate()
1475 hSelRange.moveToElementText( hInput )
1476 hSelBefore.setEndPoint( 'StartToStart', hSelRange )
1477 return hSelBefore.text.length
1481 hSelRange.moveStart( 'character', -1*hInput.value.length )
1482 var nLen = hSelRange.text.length
1489 cAutocomplete.setInputCaretPosition = function( hInput, nPosition )
1491 if ( hInput.setSelectionRange )
1493 hInput.setSelectionRange( nPosition ,nPosition )
1495 else if ( hInput.createTextRange )
1497 var hRange = hInput.createTextRange()
1498 hRange.moveStart( 'character', nPosition )
1499 hRange.moveEnd( 'character', nPosition )
1500 hRange.collapse(true)
1505 cAutocomplete.markInputRange = function( hInput, nStartPos, nEndPos )
1507 if( hInput.setSelectionRange )
1510 hInput.setSelectionRange( nStartPos, nEndPos )
1512 else if( hInput.createTextRange )
1514 var hRange = hInput.createTextRange()
1515 hRange.collapse(true)
1516 hRange.moveStart( 'character', nStartPos )
1517 hRange.moveEnd( 'character', nEndPos - nStartPos )
1522 cAutocomplete.markInputRange2 = function( sInputId )
1524 var hInput = document.getElementById( sInputId )
1525 var nStartPos = hInput.hAutocomplete.nStartAC
1526 var nEndPos = hInput.hAutocomplete.nEndAC
1527 cAutocomplete.markInputRange( hInput, nStartPos, nEndPos )
1531 cAutocomplete.onListBlur = function( hEvent )
1533 if( hEvent == null )
1535 hEvent = window.event
1537 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1538 hElement = getParentByProperty( hElement, 'className', 'autocomplete_holder' )
1539 var hAC = hElement.hAutocomplete
1540 if( !hAC.hClearTimeout )
1542 hAC.hClearTimeout = setTimeout( function() { hAC.clearList() }, cAutocomplete.CN_CLEAR_TIMEOUT )
1546 cAutocomplete.onListFocus = function( hEvent )
1548 if( hEvent == null )
1550 hEvent = window.event
1552 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1553 hElement = getParentByProperty( hElement, 'className', 'autocomplete_holder' )
1554 var hAC = hElement.hAutocomplete
1555 if( hAC.hClearTimeout )
1557 clearTimeout( hAC.hClearTimeout )
1558 hAC.hClearTimeout = null
1562 cAutocomplete.onItemClick = function( hEvent )
1564 if( hEvent == null )
1566 hEvent = window.event
1568 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1569 var hContainer = getParentByProperty( hElement, 'className', 'autocomplete_holder' )
1570 var hEl = getParentByTagName( hElement, 'A' )
1571 if( hContainer != null )
1573 var hAC = hContainer.hAutocomplete
1574 hAC.selectOption( hEl )
1575 document.getElementById( hAC.sInputId ).focus()
1578 if( hEvent.preventDefault )
1580 hEvent.preventDefault()
1582 hEvent.cancelBubble = true
1583 hEvent.returnValue = false
1587 cAutocomplete.onButtonClick = function ( hEvent )
1589 if( hEvent == null )
1591 hEvent = window.event
1593 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1594 var hAC = hElement.hAutocomplete
1595 var hInput = document.getElementById( hAC.sInputId )
1596 if( hInput.disabled )
1600 hAC.prepareList( true )
1601 var hInput = document.getElementById( hAC.sInputId )
1605 cAutocomplete.onFormSubmit = function ( hEvent )
1607 if( hEvent == null )
1609 hEvent = window.event
1611 var hElement = ( hEvent.srcElement ) ? hEvent.srcElement : hEvent.originalTarget
1612 if( hElement.bLocked )
1614 hElement.bLocked = false
1615 hEvent.returnValue = false
1616 if( hEvent.preventDefault )
1618 hEvent.preventDefault()