Author: mgrigorov Date: Sun Jul 18 09:29:02 2010 New Revision: 965183 URL: http://svn.apache.org/viewvc?rev=965183&view=rev Log: Port from wicket-1.4.x
r886735 Author: jcompagner Date: Thu Dec 3 10:50:01 2009 UTC (7 months, 2 weeks ago) Changed paths: 4 Log Message: fixes and improvements (browser issues) Modified: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutoCompleteTextField.css wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js Modified: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java?rev=965183&r1=965182&r2=965183&view=diff ============================================================================== --- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java (original) +++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AbstractAutoCompleteBehavior.java Sun Jul 18 09:29:02 2010 @@ -82,6 +82,8 @@ public abstract class AbstractAutoComple 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/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java?rev=965183&r1=965182&r2=965183&view=diff ============================================================================== --- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java (original) +++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/AutoCompleteSettings.java Sun Jul 18 09:29:02 2010 @@ -55,6 +55,10 @@ public final class AutoCompleteSettings private boolean showListOnEmptyInput = false; + private boolean useSmartPositioning = false; + + private boolean useHideShowCoveredIEFix = true; + private String cssClassName = null; private boolean adjustInputWidth = true; @@ -150,6 +154,29 @@ public final class AutoCompleteSettings } /** + * 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 @@ -191,10 +218,12 @@ public final class AutoCompleteSettings * * @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; } /** @@ -218,10 +247,12 @@ public final class AutoCompleteSettings * @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; } /** @@ -240,10 +271,12 @@ public final class AutoCompleteSettings * * @param showCompleteListOnFocusGain * the flag + * @return this {...@link AutoCompleteSettings}. */ - public void setShowCompleteListOnFocusGain(final boolean showCompleteListOnFocusGain) + public AutoCompleteSettings setShowCompleteListOnFocusGain(final boolean showCompleteListOnFocusGain) { this.showCompleteListOnFocusGain = showCompleteListOnFocusGain; + return this; } /** @@ -262,9 +295,41 @@ public final class AutoCompleteSettings * * @param showListOnFocusGain * 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/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutoCompleteTextField.css URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutoCompleteTextField.css?rev=965183&r1=965182&r2=965183&view=diff ============================================================================== --- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutoCompleteTextField.css (original) +++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/DefaultCssAutoCompleteTextField.css Sun Jul 18 09:29:02 2010 @@ -14,15 +14,18 @@ * 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; } Modified: wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js?rev=965183&r1=965182&r2=965183&view=diff ============================================================================== --- wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js (original) +++ wicket/trunk/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js Sun Jul 18 09:29:02 2010 @@ -52,10 +52,24 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, obj.onblur=function(event){ if(mouseactive==1){ - ignoreOneFocusGain = true; + ignoreOneFocusGain = true; Wicket.$(elementId).focus(); return killEvent(event); } @@ -98,8 +112,11 @@ Wicket.AutoComplete=function(elementId, } 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 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, updateChoices(); } if(typeof objonkeyup=="function")objonkeyup.apply(this,[event]); - return null; } obj.onkeypress=function(event){ @@ -192,13 +207,20 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, } function updateChoices(showAll){ - selected=-1; + setSelected(-1); if (showAll) { localThrottler.throttle(getMenuId(), throttleDelay, actualUpdateChoicesShowAll); } else { @@ -278,7 +302,8 @@ Wicket.AutoComplete=function(elementId, { 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 @@ Wicket.AutoComplete=function(elementId, } 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 @@ Wicket.AutoComplete=function(elementId, leftPosition -= obj.scrollLeft || 0; obj = obj.offsetParent; } - + return [leftPosition,topPosition]; } @@ -349,53 +532,55 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, 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 @@ Wicket.AutoComplete=function(elementId, 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();