You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by jc...@apache.org on 2009/12/03 11:50:02 UTC

svn commit: r886735 - /wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/

Author: jcompagner
Date: Thu Dec  3 10:50:01 2009
New Revision: 886735

URL: http://svn.apache.org/viewvc?rev=886735&view=rev
Log:
fixes and improvements (browser issues)

Modified:
    wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java
    wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java
    wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutocompleteTextField.css
    wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js

Modified: wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java
URL: http://svn.apache.org/viewvc/wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java?rev=886735&r1=886734&r2=886735&view=diff
==============================================================================
--- wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java (original)
+++ wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java Thu Dec  3 10:50:01 2009
@@ -82,6 +82,8 @@
 		sb.append("{preselect: ").append(settings.getPreselect());
 		sb.append(",maxHeight: ").append(settings.getMaxHeightInPx());
 		sb.append(",adjustInputWidth: ").append(settings.isAdjustInputWidth());
+		sb.append(",useSmartPositioning: ").append(settings.getUseSmartPositioning());
+		sb.append(",useHideShowCoveredIEFix: ").append(settings.getUseHideShowCoveredIEFix());
 		sb.append(",showListOnEmptyInput: ").append(settings.getShowListOnEmptyInput());
 		sb.append(",showListOnFocusGain: ").append(settings.getShowListOnFocusGain());
 		sb.append(",throttleDelay: ").append(settings.getThrottleDelay());

Modified: wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java
URL: http://svn.apache.org/viewvc/wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java?rev=886735&r1=886734&r2=886735&view=diff
==============================================================================
--- wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java (original)
+++ wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java Thu Dec  3 10:50:01 2009
@@ -55,6 +55,10 @@
 
 	private boolean showListOnEmptyInput = false;
 
+	private boolean useSmartPositioning = false;
+
+	private boolean useHideShowCoveredIEFix = true;
+
 	private String cssClassName = null;
 
 	private boolean adjustInputWidth = true;
@@ -148,6 +152,29 @@
 	}
 
 	/**
+	 * Indicates whether the popup positioning will take into account browser window visible area or
+	 * not. (so always show popup bottom-right or not)
+	 * 
+	 * @return true if popup smart positioning is used, false otherwise.
+	 */
+	public boolean getUseSmartPositioning()
+	{
+		return useSmartPositioning;
+	}
+
+	/**
+	 * Indicates whether in case of IE (and Opera), "select" "iframe" and "applet" tags should be
+	 * hidden if covered by popup. (as they might appear on top)<br>
+	 * By default this is true (before this flag was added).
+	 * 
+	 * @return true if the fix/workaround should be used for IE and Opera, false otherwise.
+	 */
+	public boolean getUseHideShowCoveredIEFix()
+	{
+		return useHideShowCoveredIEFix;
+	}
+
+	/**
 	 * Indicates whether the autocomplete list will be shown if the input is empty.
 	 * 
 	 * @return true if the autocomlete list will be shown if the input string is empty, false
@@ -189,10 +216,12 @@
 	 * 
 	 * @param cssClassName
 	 *            valid CSS class name
+	 * @return this {@link AutoCompleteSettings}.
 	 */
-	public void setCssClassName(final String cssClassName)
+	public AutoCompleteSettings setCssClassName(final String cssClassName)
 	{
 		this.cssClassName = cssClassName;
+		return this;
 	}
 
 	/**
@@ -216,10 +245,12 @@
 	 * @param adjustInputWidth
 	 *            <code>true</code> if the autocompleter should have the same size as the input
 	 *            field, <code>false</code> for default browser behavior
+	 * @return this {@link AutoCompleteSettings}.
 	 */
-	public void setAdjustInputWidth(final boolean adjustInputWidth)
+	public AutoCompleteSettings setAdjustInputWidth(final boolean adjustInputWidth)
 	{
 		this.adjustInputWidth = adjustInputWidth;
+		return this;
 	}
 
 	/**
@@ -238,10 +269,12 @@
 	 * 
 	 * @param showListOnEmptyInput
 	 *            the flag
+	 * @return this {@link AutoCompleteSettings}.
 	 */
-	public void setShowCompleteListOnFocusGain(final boolean showCompleteListOnFocusGain)
+	public AutoCompleteSettings setShowCompleteListOnFocusGain(final boolean showCompleteListOnFocusGain)
 	{
 		this.showCompleteListOnFocusGain = showCompleteListOnFocusGain;
+		return this;
 	}
 
 	/**
@@ -260,9 +293,41 @@
 	 * 
 	 * @param showListOnEmptyInput
 	 *            the flag
+	 * @return this {@link AutoCompleteSettings}.
 	 */
-	public void setShowListOnFocusGain(final boolean showListOnFocusGain)
+	public AutoCompleteSettings setShowListOnFocusGain(final boolean showListOnFocusGain)
 	{
 		this.showListOnFocusGain = showListOnFocusGain;
+		return this;
 	}
+
+	/**
+	 * Sets whether the popup positioning will take into account browser window visible area or not. (so always show popup bottom-right or not)<br>
+	 * THIS WILL PRODUCE UNWANTED BEHAVIOR WITH IE versions < 8 (probably because of unreliable clientWidth/clientHeight browser element properties).
+	 * 
+	 * @param useSmartPositioning
+	 *            the flag
+	 * @return this {@link AutoCompleteSettings}.
+	 */
+	public AutoCompleteSettings setUseSmartPositioning(final boolean useSmartPositioning)
+	{
+		this.useSmartPositioning = useSmartPositioning;
+		return this;
+	}
+
+	/**
+	 * Indicates whether in case of IE (and Opera), "select" "iframe" and "applet" tags should be
+	 * hidden if covered by popup. (as they might appear on top)<br>
+	 * By default this is true (before this flag was added).
+	 * 
+	 * @param useHideShowCoveredIEFix
+	 *            the flag
+	 * @return this {@link AutoCompleteSettings}.
+	 */
+	public AutoCompleteSettings setUseHideShowCoveredIEFix(final boolean useHideShowCoveredIEFix)
+	{
+		this.useHideShowCoveredIEFix = useHideShowCoveredIEFix;
+		return this;
+	}
+
 }

Modified: wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutocompleteTextField.css
URL: http://svn.apache.org/viewvc/wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutocompleteTextField.css?rev=886735&r1=886734&r2=886735&view=diff
==============================================================================
--- wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutocompleteTextField.css (original)
+++ wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutocompleteTextField.css Thu Dec  3 10:50:01 2009
@@ -14,18 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+div.wicket-aa-container { 
+        border-width: 1px;
+        border-color: #cccccc;
+        border-style: solid;
+} 
+
 div.wicket-aa {
         font-family: "Lucida Grande","Lucida Sans Unicode",Tahoma,Verdana;
         font-size: 12px;
         background-color: white;
-        border-color: #cccccc;
-        border-width: 1px;
-        border-style: solid;
         padding: 2px;
-        margin: 1px 0 0 0;
         text-align:left;
 }
- 
+
 div.wicket-aa ul {
         list-style:none;
         padding: 2px;

Modified: wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js
URL: http://svn.apache.org/viewvc/wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js?rev=886735&r1=886734&r2=886735&view=diff
==============================================================================
--- wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js (original)
+++ wicket/branches/wicket-1.4.x/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js Thu Dec  3 10:50:01 2009
@@ -52,10 +52,24 @@
 	var objonkeypress;
 	var objonchange;
 	var objonchangeoriginal;
-	var objonfocus;
 	
-      var ignoreOneFocusGain = false; // on FF, clicking an option in the popup would make field loose focus; focus() call only has effect in FF after popup is hidden, so the re-focusing must not show popup again in this case
+	// holds the eventual margins, padding, etc. of the menu container.
+	// it is computed when the menu is first rendered, and then reused.
+	var initialDelta = -1;
+	// remember popup container border size so we can use style.width/height = ... correctly; array [horizontal, vertical]
+	var usefulDimensionsInitialized = false;
+	var containerBorderWidths = [0, 0];
+	var scrollbarSize = 0;
+	var selChSinceLastRender = false;
+	
 
+    // this are the last visible and non-temporary bounds of the pop-up; the may change position and size several times when showing/updating choices and so on
+    // before it reaches the bounds that will be visible by the user (this is because of height/width settings limits or because it tries to compute preferred size);
+    // used for IE fix with hideShowCovered() - to be able to call it when bounds change while popup is visible.  
+    var lastStablePopupBounds = [0, 0, 0, 0];
+    
+    var ignoreOneFocusGain = false; // on FF, clicking an option in the pop-up would make field loose focus; focus() call only has effect in FF after popup is hidden, so the re-focusing must not show popup again in this case
+    
 	// holds a throttler, for not sending many requests if the user types
 	// too quickly.
 	var localThrottler = new Wicket.Throttler(true);
@@ -89,7 +103,7 @@
                 
       	obj.onblur=function(event){      		
     		if(mouseactive==1){
-                  ignoreOneFocusGain = true;
+                ignoreOneFocusGain = true;
     			Wicket.$(elementId).focus();
     			return killEvent(event);
     		}
@@ -98,8 +112,11 @@
         }
       	
       	obj.onfocus=function(event){
-			if (mouseactive==1) return killEvent(event);
-            if (!ignoreOneFocusGain && cfg.showListOnFocusGain) {
+            if (mouseactive==1) {
+                ignoreOneFocusGain = false;
+                return killEvent(event);
+            }
+            if (!ignoreOneFocusGain && cfg.showListOnFocusGain && visible==0) {
                 if (cfg.showCompleteListOnFocusGain) {
                     updateChoices(true);
                 } else {
@@ -113,20 +130,22 @@
         obj.onkeydown=function(event){
             switch(wicketKeyCode(Wicket.fixEvent(event))){
                 case KEY_UP:
-        	        if(selected>-1)selected--;
+        	        if(selected>-1) setSelected(selected-1);
             	    if(selected==-1){
     	           	    hideAutoComplete();
                    	} else {
-	                    render();
+	                    render(true);
         	        }
             	    if(Wicket.Browser.isSafari())return killEvent(event);
                 	break;
                 case KEY_DOWN:
-               		if(selected<elementCount-1) selected++;
+               		if(selected<elementCount-1){
+                	    setSelected(selected+1);
+	                }
     	            if(visible==0){
         	            updateChoices();
             	    } else {
-                	    render();
+                	    render(true);
                     	showAutoComplete();
 	                }
     	            if(Wicket.Browser.isSafari())return killEvent(event);
@@ -139,23 +158,20 @@
                 case KEY_ENTER:
                     if(selected > -1) {
                         var value = getSelectedValue();
-                        if(value = handleSelection(value)) {
+                        value = handleSelection(value);
+                        hideAutoComplete();
+                        hidingAutocomplete = 1;                        
+                        if(value) {
                           obj.value = value;
                           if(typeof objonchange=="function") objonchange.apply(this,[event]);
                         }
-                        hideAutoComplete();
-                        hidingAutocomplete = 1;
                     } else if (Wicket.AutoCompleteSettings.enterHidesWithNoSelection) {
                         hideAutoComplete();
                         hidingAutocomplete = 1;
                     }
-	                mouseactive=0;
-	                if(typeof objonkeydown=="function")objonkeydown.apply(this,[event]);
-
-	                if(selected>-1){
-	                	//return killEvent(event);
-            	    }
-            	    return true;
+                    mouseactive = 0;
+                    if (typeof objonkeydown=="function") objonkeydown.apply(this,[event]);
+                    return true;
                 break;
                 default:
             }
@@ -163,7 +179,7 @@
 
         obj.onkeyup=function(event){
             switch(wicketKeyCode(Wicket.fixEvent(event))){
-            	case KEY_TAB:
+                case KEY_TAB:
                 case KEY_ENTER:
 	                return killEvent(event);
                 case KEY_UP:
@@ -179,7 +195,6 @@
     	            updateChoices();
             }
 			if(typeof objonkeyup=="function")objonkeyup.apply(this,[event]);
-            return null;
         }
 
         obj.onkeypress=function(event){
@@ -192,13 +207,20 @@
 			if(typeof objonkeypress=="function")objonkeypress.apply(this,[event]);
         }
     }
+    
+    function setSelected(newSelected) {
+        if (newSelected != selected) {
+            selected = newSelected;
+            selChSinceLastRender = true;
+        }
+    }
 
     function handleSelection(input) {
-    	var menu = getAutocompleteMenu();
-    	var attr = menu.firstChild.childNodes[selected].attributes['onselect'];
-    	return attr ? eval(attr.value) : input;
+        var menu = getAutocompleteMenu();
+        var attr = menu.firstChild.childNodes[selected].attributes['onselect'];
+        return attr ? eval(attr.value) : input;
     }
-    
+
     function getMenuId() {
         return elementId+"-autocomplete";
     }
@@ -213,7 +235,9 @@
             document.body.appendChild(container);
         	container.style.display="none";
         	container.style.overflow="auto";
-            container.style.position="absolute";            
+            container.style.position="absolute";
+            container.style.margin="0px"; // this needs to be 0 or size/location calculations would not be exact
+            container.style.padding="0px"; // this needs to be 0 or size/location calculations would not be exact
             container.id=getMenuId()+"-container";
             	
             container.show = function() { wicketShow(this.id) };
@@ -259,7 +283,7 @@
     }
 
     function updateChoices(showAll){
-       	selected=-1;
+        setSelected(-1);
         if (showAll) {
             localThrottler.throttle(getMenuId(), throttleDelay, actualUpdateChoicesShowAll);
         } else {
@@ -278,7 +302,8 @@
     {
     	showIndicator();
         var value = wicketGet(elementId).value;
-       	var request = new Wicket.Ajax.Request(callbackUrl+(callbackUrl.indexOf("?")>-1 ? "&" : "?") + "q="+processValue(value), doUpdateChoices, false, true, false, "wicket-autocomplete|d");       	request.get();
+       	var request = new Wicket.Ajax.Request(callbackUrl+(callbackUrl.indexOf("?")>-1 ? "&" : "?") + "q="+processValue(value), doUpdateChoices, false, true, false, "wicket-autocomplete|d");
+       	request.get();
     }
     
     function showIndicator() {
@@ -298,33 +323,191 @@
     }
 
     function showAutoComplete(){
-        var position=getPosition(wicketGet(elementId));
+        var input = wicketGet(elementId);
         var container = getAutocompleteContainer();
-        var input=wicketGet(elementId);
         var index=getOffsetParentZIndex(elementId);
         container.show();
         if (!isNaN(new Number(index))) {
             container.style.zIndex=(new Number(index)+1);
-        }  
-        container.style.left=position[0]+'px';
-        container.style.top=(input.offsetHeight+position[1])+'px';
-        if(cfg.adjustInputWidth)
-          container.style.width=input.offsetWidth+'px';
-        visible=1;
-        hideShowCovered();
+        } 
+        if (!usefulDimensionsInitialized)
+        {
+            initializeUsefulDimensions(input, container);
+        }
+        if (cfg.adjustInputWidth) {
+          var newW = input.offsetWidth-containerBorderWidths[0];
+          container.style.width = (newW >= 0 ? newW : input.offsetWidth)+'px';
+        }
+          
+        calculateAndSetPopupBounds(input, container);
+        
+        if (visible == 0) {
+            visible = 1;
+            hideShowCovered(true, lastStablePopupBounds[0], lastStablePopupBounds[1], lastStablePopupBounds[2], lastStablePopupBounds[3]);
+        }
+    }
+    
+    function initializeUsefulDimensions(input, container) {
+        usefulDimensionsInitialized = true;
+        // a few checks to increase the odds that we can count on clientWidth/Height
+        if (typeof (container.clientWidth) != "undefined" && typeof (container.clientHeight) != "undefined" && container.clientWidth > 0 && container.clientHeight > 0) {
+            var tmp = container.style.overflow; // clientWidth & clientHeight exclude border and scollbars
+            container.style.overflow = "visible";
+            containerBorderWidths[0] = container.offsetWidth - container.clientWidth;
+            containerBorderWidths[1] = container.offsetHeight - container.clientHeight;
+            
+            if (cfg.useSmartPositioning) {
+                container.style.overflow = "scroll";
+                scrollbarSize = container.offsetWidth - container.clientWidth - containerBorderWidths[0];
+            }
+            // although border is computed correctly, resizing needs 1 less px on FF for some dubious reason...
+            if (Wicket.Browser.isGecko() && containerBorderWidths[0] > 0 && containerBorderWidths[1] > 0) {
+                containerBorderWidths[0]--;
+                containerBorderWidths[1]--;
+            }
+            
+            container.style.overflow = tmp;
+        }
     }
 
     function hideAutoComplete(){
         visible=0;
-        selected=-1;
-        if ( getAutocompleteContainer() )
+        setSelected(-1);
+        mouseactive=0;
+        var container = getAutocompleteContainer();
+        if (container)
         {
-	        getAutocompleteContainer().hide();
-    	    hideShowCovered();
+            hideShowCovered(false, lastStablePopupBounds[0], lastStablePopupBounds[1], lastStablePopupBounds[2], lastStablePopupBounds[3]);
+            container.hide();
+            if (!cfg.adjustInputWidth && container.style.width != "auto") {
+                container.style.width = "auto"; // let browser auto-set width again next time it is shown
+            }
+        }
+    }
+
+    function getWindowWidthAndHeigth() {
+        var myWidth = 0, myHeight = 0;
+        if( typeof( window.innerWidth ) == 'number' ) {
+            //Non-IE
+            myWidth = window.innerWidth;
+            myHeight = window.innerHeight;
+        } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
+            //IE 6+ in 'standards compliant mode'
+            myWidth = document.documentElement.clientWidth;
+            myHeight = document.documentElement.clientHeight;
+        } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
+            //IE 4 compatible
+            myWidth = document.body.clientWidth;
+            myHeight = document.body.clientHeight;
+        }
+        return [ myWidth, myHeight ];
+    }
+
+    function getWindowScrollXY() {
+        var scrOfX = 0, scrOfY = 0;
+        if( typeof( window.pageYOffset ) == 'number' ) {
+            //Netscape compliant
+            scrOfY = window.pageYOffset;
+            scrOfX = window.pageXOffset;
+        } else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
+            //DOM compliant
+            scrOfY = document.body.scrollTop;
+            scrOfX = document.body.scrollLeft;
+        } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
+            //IE6 standards compliant mode
+            scrOfY = document.documentElement.scrollTop;
+            scrOfX = document.documentElement.scrollLeft;
+        }
+        return [ scrOfX, scrOfY ];
+    }
+
+    function calculateAndSetPopupBounds(input, popup)
+    {
+        var leftPosition=0;
+        var topPosition=0;
+        var inputPosition=getPosition(input);
+        if (cfg.useSmartPositioning) {
+            // there are 4 possible positions for the popup: top-left, top-right, buttom-left, bottom-right
+            // relative to the field; we will try to use the position that does not get out of the visible page 
+            if (popup.style.width == "auto") {
+                popup.style.left = "0px"; // allow browser to stretch div as much as needed to see where the popup should be put
+                popup.style.top = "0px";
+            }
+            var windowScrollXY = getWindowScrollXY();
+            var windowWH = getWindowWidthAndHeigth();
+            var windowScrollX = windowScrollXY[0];
+            var windowScrollY = windowScrollXY[1];
+            var windowWidth = windowWH[0];
+            var windowHeight = windowWH[1];
+            
+            var dx1 = windowScrollX + windowWidth - inputPosition[0] - popup.offsetWidth;
+            var dx2 = inputPosition[0] + input.offsetWidth - popup.offsetWidth - windowScrollX;
+            if (popup.style.width == "auto" && dx1 < 0 && dx2 < 0) {
+                // browser determined popup width; if it does not fit either right or left aligned with the input, calculate and set fixed width
+                // so that after initial position calculation after popup opens, bounds do not change every time a mouse over or other event happens.
+                // The browser can change the width/height when div if repositioned - if they were not already restricted because of maxHeight and field width (and that can result in a relocation of the div and so on).
+                var newW = popup.offsetWidth + Math.max(dx1, dx2) - containerBorderWidths[0];
+                popup.style.width = (newW >= 0 ? newW : popup.offsetWidth + Math.max(dx1, dx2))+'px';
+                dx1 = windowScrollX + windowWidth - inputPosition[0] - popup.offsetWidth;
+                dx2 = inputPosition[0] + input.offsetWidth - popup.offsetWidth - windowScrollX;
+            }
+
+            var dy1 = windowScrollY + windowHeight - inputPosition[1] - input.offsetHeight - popup.offsetHeight;
+            var dy2 = inputPosition[1] - popup.offsetHeight - windowScrollY;
+            if (dy1 < 0 && dy2 < 0) {
+                // limit height if it gets outside the screen
+                var newH = popup.offsetHeight + Math.max(dy1, dy2) - containerBorderWidths[1];
+                popup.style.height = (newH >= 0 ? newH : popup.offsetHeight + Math.max(dy1, dy2))+'px';
+                var dy1 = windowScrollY + windowHeight - inputPosition[1] - input.offsetHeight - popup.offsetHeight;
+                var dy2 = inputPosition[1] - popup.offsetHeight - windowScrollY;
+            }
+            
+            // choose the location that shows the most surface of the popup, with preference for bottom right
+            if (dx1 < 0 && dx1 < dx2) {
+                if (dy1 < 0 && dy1 < dy2) {
+                    // choice 4 : top left
+                    leftPosition = inputPosition[0] + input.offsetWidth - popup.offsetWidth;
+                    topPosition = inputPosition[1] - popup.offsetHeight;
+                } else {
+                    // choice 3 : bottom left
+                    leftPosition = inputPosition[0] + input.offsetWidth - popup.offsetWidth;
+                    topPosition = inputPosition[1] + input.offsetHeight;
+                }
+            } else {
+                if (dy1 < 0 && dy1 < dy2) {
+                    // choice 2 : top right
+                    leftPosition = inputPosition[0];
+                    topPosition = inputPosition[1] - popup.offsetHeight;
+                } else {
+                    // choice 1 : bottom right
+                    leftPosition = inputPosition[0];
+                    topPosition = inputPosition[1] + input.offsetHeight;
+                }
+            }
+            if (popup.style.width == "auto") {
+                var newW = popup.offsetWidth - containerBorderWidths[0];
+                popup.style.width = (newW >= 0 ? newW : popup.offsetWidth)+'px';
+            }
+        } else {
+            leftPosition = inputPosition[0];
+            topPosition = inputPosition[1] + input.offsetHeight;
+        }
+        popup.style.left=leftPosition+'px';
+        popup.style.top=topPosition+'px';
+        
+        if (visible == 1 &&
+                (lastStablePopupBounds[0] != popup.offsetLeft ||
+                 lastStablePopupBounds[1] != popup.offsetTop ||
+                 lastStablePopupBounds[2] != popup.offsetWidth ||
+                 lastStablePopupBounds[3] != popup.offsetHeight)) {
+            hideShowCovered(false, lastStablePopupBounds[0], lastStablePopupBounds[1], lastStablePopupBounds[2], lastStablePopupBounds[3]); // show previously hidden
+            hideShowCovered(true, popup.offsetLeft, popup.offsetTop, popup.offsetWidth, popup.offsetHeight); // hide ones under new bounds
         }
+
+        lastStablePopupBounds = [popup.offsetLeft, popup.offsetTop, popup.offsetWidth, popup.offsetHeight];
     }
 
-	 function getPosition(obj) {
+    function getPosition(obj) {
         var leftPosition = obj.offsetLeft || 0;
         var topPosition = obj.offsetTop || 0;
         obj = obj.offsetParent;
@@ -335,7 +518,7 @@
      		leftPosition -= obj.scrollLeft || 0;
             obj = obj.offsetParent;
         }
- 
+
         return [leftPosition,topPosition];
     }
     
@@ -349,53 +532,55 @@
    			hideIndicator();
    			return;
    		}
-    
+
         var element = getAutocompleteMenu();
+        if (!cfg.adjustInputWidth && element.parentNode && element.parentNode.style.width != "auto") {
+            element.parentNode.style.width = "auto"; // let browser auto-set width again as displayed elements may change
+            selChSinceLastRender = true; // selected item will not have selected style until rendrered
+        }
         element.innerHTML=resp;
         if(element.firstChild && element.firstChild.childNodes) {
-		elementCount=element.firstChild.childNodes.length;
+		    elementCount=element.firstChild.childNodes.length;
 
-		var clickFunc = function(event){
-			mouseactive=0;
-			var value = getSelectedValue();
-			if(value = handleSelection(value)) {
-				wicketGet(elementId).value=value;
-				if (typeof objonchange=="function") {objonchange.apply(wicketGet(elementId), [event]);}
-			}
-			hideAutoComplete();
-                if (Wicket.Focus.getFocusedElement() != input)
-                {
+            var clickFunc = function(event) {
+                mouseactive = 0;
+                var value = getSelectedValue();
+                var input = wicketGet(elementId);
+                if(value = handleSelection(value)) {
+                  input.value = value;
+                  if(typeof objonchange=="function") objonchange.apply(input,[event]);
+                }
+                hideAutoComplete();
+                if (Wicket.Focus.getFocusedElement() != input) {
                     ignoreOneFocusGain = true;
                     input.focus();
                 }
-		};
+            };
 			
-		var mouseOverFunc = function(event){
-                mouseactive=1; // Opera needs this as mousemove for menu is not always triggered
-			selected = getElementIndex(this);
-			render();
-		 	showAutoComplete();
-		};
-
-		var parentNode = element.firstChild;
-		for(var i = 0;i < elementCount; i++) {
-			var node = parentNode.childNodes[i];
-			node.onclick = clickFunc;
-			node.onmouseover = mouseOverFunc;
-      		}
+            var mouseOverFunc = function(event) {
+                setSelected(getElementIndex(this));
+                render(false); // don't scroll - breaks mouse weel scrolling
+                showAutoComplete();
+            };
+            var parentNode = element.firstChild; 
+            for(var i = 0;i < elementCount; i++) {
+                var node = parentNode.childNodes[i];
+                node.onclick = clickFunc;
+                node.onmouseover = mouseOverFunc;
+            }
         } else {
             elementCount=0;
         }
 
         if(elementCount>0){
-        	 if(cfg.preselect==true){
-        		 selected = 0;
-        	 }            
-        	 showAutoComplete();
+            if(cfg.preselect==true){
+                setSelected(0);
+            }            
+            showAutoComplete();
         } else {
             hideAutoComplete();
         }
-        render();
+        render(false);
         
         scheduleEmptyCheck();
         
@@ -437,31 +622,33 @@
         return str.replace(/<[^>]+>/g,"");
     }
     
-    function adjustScrollOffset(menu, item) {    	
+    function adjustScrollOffset(menu, item) { // this should consider margins/paddings; now it is not exact
     	if (item.offsetTop + item.offsetHeight > menu.scrollTop + menu.offsetHeight) {
 			menu.scrollTop = item.offsetTop + item.offsetHeight - menu.offsetHeight;
 		} else
 		// adjust to the top
 		if (item.offsetTop < menu.scrollTop) {
 			menu.scrollTop = item.offsetTop;
-		}	
+		}
     }
 
-    function render(){
+    function render(adjustScroll){
         var menu=getAutocompleteMenu();
         var height=0;
 		var node=menu.firstChild.childNodes[0];
 		var re = /\bselected\b/gi;
+		var sizeAffected = false;
         for(var i=0;i<elementCount;i++)
 		{
             var origClassNames = node.className;
 			var classNames = origClassNames.replace(re, "");
 			if(selected==i){
 				classNames += " selected";
-				adjustScrollOffset(menu.parentNode, node);
+				if (adjustScroll) adjustScrollOffset(menu.parentNode, node);
 			}
-			if (classNames != origClassNames)
+			if (classNames != origClassNames) {
                 node.className = classNames;
+            }
 	
 			if (cfg.maxHeight > -1)
 				height+=node.offsetHeight;
@@ -469,12 +656,27 @@
 			node = node.nextSibling;
 		}
         if (cfg.maxHeight > -1) {
-        	if (height<cfg.maxHeight) {
-                menu.parentNode.style.height="auto";
-            } else {
-        	    menu.parentNode.style.height=cfg.maxHeight+"px";
+            if (initialDelta == -1)
+            {
+                // remember size occupied by parent border, padding, and menu+ul margins, border, padding
+                initialDelta = menu.parentNode.offsetHeight - height;
+            }
+            if (height + initialDelta > cfg.maxHeight) {
+                var newH = cfg.maxHeight - containerBorderWidths[1];
+                menu.parentNode.style.height = (newH >= 0 ? newH : cfg.maxHeight) + "px";
+                sizeAffected = true;
+            } else if (menu.parentNode.style.height != "auto") { // if height is limited
+                menu.parentNode.style.height = "auto"; // no limiting, let popup determine it's own height
+                sizeAffected = true;
             }
         }
+        if (cfg.useSmartPositioning && !cfg.adjustInputWidth && menu.parentNode.style.width != "auto" && selChSinceLastRender) {
+            // selected item has different padding - so the preferred width of the popup might want to change so as not to wrap it
+            selChSinceLastRender = false;
+            menu.parentNode.style.width = "auto";
+            sizeAffected = true;
+        }
+        if (sizeAffected) calculateAndSetPopupBounds(wicketGet(elementId), menu.parentNode); // update stuff related to bounds if needed 
     }
     
     // From http://www.robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
@@ -511,49 +713,35 @@
     	return index;
     }
 
-    function hideShowCovered(){
-        if (!/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
+    function hideShowCovered(popupVisible, acLeftX, acTopY, acWidth, acHeight) {
+        if (!cfg.useHideShowCoveredIEFix || (!/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent))) {
             return;
         }
-        // IE7 fix, if this doesn't go in a timeout then the complete page could become invisible.
-        // when closing the popup.
-		setTimeout(hideShowCoveredTimeout,1);
-    }
-    
-    function hideShowCoveredTimeout(){
-		var el=getAutocompleteContainer();
-        var p=getPosition(el);
-
-        var acLeftX=p[0];
-        var acRightX=el.offsetWidth+acLeftX;
-        var acTopY=p[1];
-        var acBottomY=el.offsetHeight+acTopY;
 
         var hideTags=new Array("select","iframe","applet");
+        var acRightX = acLeftX + acWidth;
+        var acBottomY = acTopY + acHeight;
 
         for (var j=0;j<hideTags.length;j++) {
             var tagsFound=document.getElementsByTagName(hideTags[j]);
             for (var i=0; i<tagsFound.length; i++){
                 var tag=tagsFound[i];
-                p=getPosition(tag);
+                var p=getPosition(tag); // maybe determine only visible area of tag somehow? as it could be in a small scrolled container...
                 var leftX=p[0];
                 var rightX=leftX+tag.offsetWidth;
                 var topY=p[1];
                 var bottomY=topY+tag.offsetHeight;
 
-                if (this.hidden || (leftX>acRightX) || (rightX<acLeftX) || (topY>acBottomY) || (bottomY<acTopY)) {
-                    if(!tag.wicket_element_visibility) {
-                        tag.wicket_element_visibility=isVisible(tag);
-                    }
-                    tag.style.visibility=tag.wicket_element_visibility;
+                if (!tag.wicket_element_visibility) {
+                    tag.wicket_element_visibility=isVisible(tag);
+                }
+                if (popupVisible==0 || (leftX>acRightX) || (rightX<acLeftX) || (topY>acBottomY) || (bottomY<acTopY)) {
+                    tag.style.visibility = tag.wicket_element_visibility;
 				} else {
-					if (!tag.wicket_element_visibility) {
-						tag.wicket_element_visibility=isVisible(tag);
-					}
                     tag.style.visibility = "hidden";
                 }
             }
-        }        
+        }
     }
 
     initialize();