Browse Source

Bottom component click scroll up the parent panel in a window (#12943)

Due to old fix for (#11994) the v-scrollable div of the window would
expand to 110% of its size then immediately back to the original size.
The first action, expanding the v-scrollable to 110% would decrease
the scrollTop value of our panel, while increasing its height. When
the revert back action would set the v-scrollable to its own size,
the panel's scrollTop would remain decreased, causing the scroll bar
to move up, hiding the ~10% at the bottom.

Fixed by calling Util.runWebkitOverflowAutoFix(); instead of changing
the height.

Change-Id: I79eafd1f9500c2e4c10dadbfc7100608c0732e04
tags/7.3.0.rc1
Bogdan Udrescu 10 years ago
parent
commit
9b19675dff

+ 96
- 0
WebContent/html-tests/BottomComponentScrollsUp.html View File

<html>

<head>
<title>Bottom component scroll when focus - test with plain html to see the default behaviour</title>
<link rel="stylesheet" type="text/css" href="./../VAADIN/themes/reindeer/styles.css">

<script>

function setScrollOnPanel() {
var popups = document.getElementsByClassName("v-panel-content");
popups[0].scrollTop = 756;

console.log("popups[0]: " + popups[0].scrollTop);
}

</script>

</head>

<body>

<div id="runBottomComponentScrollsUp-1897366330-overlays" class="v-app reindeer v-overlay-container" aria-live="assertive" aria-label="This content is announced automatically and does not need to be navigated into." aria-relevant="additions">

<div class="v-tooltip" role="tooltip" aria-live="assertive" aria-relevant="additions" style="margin-left: 0px; margin-top: 0px; left: -4990px; top: -4990px; z-index: 20000; visibility: visible; position: absolute; overflow: visible;">

<div class="popupContent"><div><div class="v-errormessage" aria-hidden="true" style="display: none;"></div><div class="v-tooltip-text"> </div></div></div></div>

<div class="v-window v-widget v-has-width v-has-height" role="dialog" aria-relevant="additions" aria-labelledby="gwt-uid-2" style="margin-left: 0px; margin-top: 0px; left: 400px; top: 19px; z-index: 10000; width: 300px; height: 300px; visibility: visible; position: absolute; overflow: visible; min-width: 66px; min-height: 52px;">
<div class="popupContent">
<div class="v-window-wrap">

<div tabindex="0" aria-label="Top of dialog"></div>

<div class="v-window-outerheader">
<div class="v-window-header" id="gwt-uid-2">
<span class="v-assistive-device-only"></span>
<span class="v-assistive-device-only"></span>
</div>
</div>

<div class="v-window-maximizebox" tabindex="0" role="button" aria-label="maximize button" id="28_window_maximizerestore"></div>
<div class="v-window-closebox" tabindex="0" role="button" aria-label="close button" id="28_window_close"></div>

<div class="v-window-contents" style="padding-top: 37px; margin-top: -37px; padding-bottom: 15px; margin-bottom: -15px;">
<div tabindex="0" class="v-scrollable" style="zoom: 1; position: relative;">
<div class="v-panel v-widget v-has-width v-has-height" style="overflow: hidden; width: 100%; height: 100%; position: absolute; padding-top: 1px; padding-bottom: 1px;">

<div class="v-panel-captionwrap" style="margin-top: -1px;">
<div class="v-panel-nocaption"><span></span>
</div>
</div>

<div class="v-panel-content v-scrollable" tabindex="-1" style="position: relative;">
<div class="v-verticallayout v-layout v-vertical v-widget v-has-width v-has-height" style="width: 100%; height: 1000px;">

<div class="v-expand" style="padding-top: 0px;">

<div class="v-slot" style="height: 50%; margin-top: 0px;">
<div role="combobox" class="v-filterselect v-widget v-filterselect-prompt">
<input type="text" class="v-filterselect-input" tabindex="0" style="width: 129px;">
<div class="v-filterselect-button" aria-hidden="true" role="button"></div>
</div>
</div>

<div class="v-slot v-align-center v-align-bottom" style="height: 50%;">
<div tabindex="0" role="button" class="v-button v-widget v-has-height" style="height: 100px;" onclick="setScrollOnPanel();">
<span class="v-button-wrap"><span class="v-button-caption">Press me</span></span>
</div>
</div>

</div>

</div>
</div>

<div class="v-panel-deco" style="margin-bottom: -1px;"></div>

</div>
</div>
</div>

<div class="v-window-footer">
<div class="v-window-resizebox"></div>
</div>

<div tabindex="0" aria-label="Bottom of Dialog"></div>

</div>
</div>
</div>

</div>

</body>

</html>

+ 0
- 1
client/src/com/vaadin/client/ApplicationConnection.java View File

import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConfiguration.ErrorMessage; import com.vaadin.client.ApplicationConfiguration.ErrorMessage;
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.ResourceLoader.ResourceLoadListener;
import com.vaadin.client.communication.HasJavaScriptConnectorHelper; import com.vaadin.client.communication.HasJavaScriptConnectorHelper;

+ 18
- 0
client/src/com/vaadin/client/Util.java View File

return detectedScrollbarSize; return detectedScrollbarSize;
} }


/**
* Defers the execution of {@link #runWebkitOverflowAutoFix(Element)}
*
* @since
* @param elem
* with overflow auto
*/
public static void runWebkitOverflowAutoFixDeferred(final Element elem) {
Scheduler.get().scheduleDeferred(new Command() {

@Override
public void execute() {
Util.runWebkitOverflowAutoFix(elem);
}
});

}

/** /**
* Run workaround for webkits overflow auto issue. * Run workaround for webkits overflow auto issue.
* *

+ 3
- 20
client/src/com/vaadin/client/ui/VScrollTable.java View File

* Ensures the column alignments are correct at initial loading. <br/> * Ensures the column alignments are correct at initial loading. <br/>
* (child components widths are correct) * (child components widths are correct)
*/ */
Scheduler.get().scheduleDeferred(new Command() {

@Override
public void execute() {
Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
}
});
Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());


hadScrollBars = willHaveScrollbarz; hadScrollBars = willHaveScrollbarz;
} }
Util.notifyParentOfSizeChange(VScrollTable.this, rendering); Util.notifyParentOfSizeChange(VScrollTable.this, rendering);
} }
} }
Scheduler.get().scheduleDeferred(new Command() {


@Override
public void execute() {
Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
}
});
Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());


forceRealignColumnHeaders(); forceRealignColumnHeaders();
} }
// We must run the fix as a deferred command to prevent it from // We must run the fix as a deferred command to prevent it from
// overwriting the scroll position with an outdated value, see // overwriting the scroll position with an outdated value, see
// #7607. // #7607.
Scheduler.get().scheduleDeferred(new Command() {

@Override
public void execute() {
Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
}
});
Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());
} }


triggerLazyColumnAdjustment(false); triggerLazyColumnAdjustment(false);

+ 30
- 60
client/src/com/vaadin/client/ui/VWindow.java View File

/* /*
* Copyright 2000-2014 Vaadin Ltd. * Copyright 2000-2014 Vaadin Ltd.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of * use this file except in compliance with the License. You may obtain a copy of
* the License at * the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the


/** /**
* "Sub window" component. * "Sub window" component.
*
*
* @author Vaadin Ltd * @author Vaadin Ltd
*/ */
public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,


/** /**
* Returns true if this window is the topmost VWindow * Returns true if this window is the topmost VWindow
*
*
* @return * @return
*/ */
private boolean isActive() { private boolean isActive() {
* is prevented. * is prevented.
* <p> * <p>
* This message is not visible on the screen. * This message is not visible on the screen.
*
*
* @param topMessage * @param topMessage
* String provided when the user navigates with Shift-Tab keys to * String provided when the user navigates with Shift-Tab keys to
* the top of the window * the top of the window
* key is prevented. * key is prevented.
* <p> * <p>
* This message is not visible on the screen. * This message is not visible on the screen.
*
*
* @param bottomMessage * @param bottomMessage
* String provided when the user navigates with the Tab key to * String provided when the user navigates with the Tab key to
* the bottom of the window * the bottom of the window
* Gets the message that is provided to users of assistive devices when the * Gets the message that is provided to users of assistive devices when the
* user reaches the top of the window when leaving a window with the tab key * user reaches the top of the window when leaving a window with the tab key
* is prevented. * is prevented.
*
*
* @return the top message * @return the top message
*/ */
public String getTabStopTopAssistiveText() { public String getTabStopTopAssistiveText() {
* Gets the message that is provided to users of assistive devices when the * Gets the message that is provided to users of assistive devices when the
* user reaches the bottom of the window when leaving a window with the tab * user reaches the bottom of the window when leaving a window with the tab
* key is prevented. * key is prevented.
*
*
* @return the bottom message * @return the bottom message
*/ */
public String getTabStopBottomAssistiveText() { public String getTabStopBottomAssistiveText() {


/* /*
* Shake up the DOM a bit to make the window shed unnecessary * Shake up the DOM a bit to make the window shed unnecessary
* scrollbars and resize correctly afterwards. This resulting code
* took over a week to summon forth, and involved some pretty hairy
* black magic. Don't touch it unless you know what you're doing!
* Fixes ticket #11994
* scrollbars and resize correctly afterwards. The version fixing
* ticket #11994 which was changing the size to 110% was replaced
* with this due to ticket #12943
*/ */
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
final com.google.gwt.dom.client.Element scrollable = contents
.getFirstChildElement();

// Adjusting the width or height may change the scroll
// position, so store the current position
int horizontalScrollPosition = scrollable.getScrollLeft();
int verticalScrollPosition = scrollable.getScrollTop();

final String oldWidth = scrollable.getStyle().getWidth();
final String oldHeight = scrollable.getStyle().getHeight();

scrollable.getStyle().setWidth(110, Unit.PCT);
scrollable.getOffsetWidth();
scrollable.getStyle().setProperty("width", oldWidth);

scrollable.getStyle().setHeight(110, Unit.PCT);
scrollable.getOffsetHeight();
scrollable.getStyle().setProperty("height", oldHeight);

// Restore the scroll position
scrollable.setScrollLeft(horizontalScrollPosition);
scrollable.setScrollTop(verticalScrollPosition);

updateContentsSize();
positionOrSizeUpdated();
}
});
Util.runWebkitOverflowAutoFix(contents.getFirstChildElement());
} }
} }


/** /**
* Sets the closable state of the window. Additionally hides/shows the close * Sets the closable state of the window. Additionally hides/shows the close
* button according to the new state. * button according to the new state.
*
*
* @param closable * @param closable
* true if the window can be closed by the user * true if the window can be closed by the user
*/ */
* Returns the closable state of the sub window. If the sub window is * Returns the closable state of the sub window. If the sub window is
* closable a decoration (typically an X) is shown to the user. By clicking * closable a decoration (typically an X) is shown to the user. By clicking
* on the X the user can close the window. * on the X the user can close the window.
*
*
* @return true if the sub window is closable * @return true if the sub window is closable
*/ */
protected boolean isClosable() { protected boolean isClosable() {
/** /**
* Setter for the text for assistive devices the window caption is prefixed * Setter for the text for assistive devices the window caption is prefixed
* with. * with.
*
*
* @param assistivePrefix * @param assistivePrefix
* the assistivePrefix to set * the assistivePrefix to set
*/ */
/** /**
* Getter for the text for assistive devices the window caption is prefixed * Getter for the text for assistive devices the window caption is prefixed
* with. * with.
*
*
* @return the assistivePrefix * @return the assistivePrefix
*/ */
public String getAssistivePrefix() { public String getAssistivePrefix() {
/** /**
* Setter for the text for assistive devices the window caption is postfixed * Setter for the text for assistive devices the window caption is postfixed
* with. * with.
*
*
* @param assistivePostfix * @param assistivePostfix
* the assistivePostfix to set * the assistivePostfix to set
*/ */
/** /**
* Getter for the text for assistive devices the window caption is postfixed * Getter for the text for assistive devices the window caption is postfixed
* with. * with.
*
*
* @return the assistivePostfix * @return the assistivePostfix
*/ */
public String getAssistivePostfix() { public String getAssistivePostfix() {


/** /**
* TODO check if we need to support this with touch based devices. * TODO check if we need to support this with touch based devices.
*
*
* Checks if the cursor was inside the browser content area when the event * Checks if the cursor was inside the browser content area when the event
* happened. * happened.
*
*
* @param event * @param event
* The event to be checked * The event to be checked
* @return true, if the cursor is inside the browser content area * @return true, if the cursor is inside the browser content area
*
*
* false, otherwise * false, otherwise
*/ */
private boolean cursorInsideBrowserContentArea(Event event) { private boolean cursorInsideBrowserContentArea(Event event) {
* assistive devices when it is opened. * assistive devices when it is opened.
* <p> * <p>
* When the provided array is empty, an existing description is removed. * When the provided array is empty, an existing description is removed.
*
*
* @param connectors * @param connectors
* with the connectors of the widgets to use as description * with the connectors of the widgets to use as description
*/ */
* Gets the connectors that are used as assistive description. Text * Gets the connectors that are used as assistive description. Text
* contained in these connectors will be read by assistive devices when the * contained in these connectors will be read by assistive devices when the
* window is opened. * window is opened.
*
*
* @return list of previously set connectors * @return list of previously set connectors
*/ */
public List<Connector> getAssistiveDescription() { public List<Connector> getAssistiveDescription() {


/** /**
* Sets the WAI-ARIA role the window. * Sets the WAI-ARIA role the window.
*
*
* This role defines how an assistive device handles a window. Available * This role defines how an assistive device handles a window. Available
* roles are alertdialog and dialog (@see <a * roles are alertdialog and dialog (@see <a
* href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
* Model</a>). * Model</a>).
*
*
* The default role is dialog. * The default role is dialog.
*
*
* @param role * @param role
* WAI-ARIA role to set for the window * WAI-ARIA role to set for the window
*/ */
* The value of the parameter doTabStop is stored and used for non-modal * The value of the parameter doTabStop is stored and used for non-modal
* windows. For modal windows, the handlers are always registered, while * windows. For modal windows, the handlers are always registered, while
* preserving the stored value. * preserving the stored value.
*
*
* @param doTabStop * @param doTabStop
* true to prevent leaving the window, false to allow leaving the * true to prevent leaving the window, false to allow leaving the
* window for non modal windows * window for non modal windows


/** /**
* Adds a Handler for when user moves the window. * Adds a Handler for when user moves the window.
*
*
* @since 7.1.9 * @since 7.1.9
*
*
* @return {@link HandlerRegistration} used to remove the handler * @return {@link HandlerRegistration} used to remove the handler
*/ */
public HandlerRegistration addMoveHandler(WindowMoveHandler handler) { public HandlerRegistration addMoveHandler(WindowMoveHandler handler) {

+ 2
- 8
client/src/com/vaadin/client/ui/table/TableConnector.java View File

import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo; import com.vaadin.client.BrowserInfo;
// by changing overflows as the length of the contents // by changing overflows as the length of the contents
// *shouldn't* have changed (unless the number of rows // *shouldn't* have changed (unless the number of rows
// or the height of the widget has also changed) // or the height of the widget has also changed)
Scheduler.get().scheduleDeferred(new Command() {
@Override
public void execute() {
Util.runWebkitOverflowAutoFix(getWidget().scrollBodyPanel
.getElement());
}
});
Util.runWebkitOverflowAutoFixDeferred(getWidget().scrollBodyPanel
.getElement());
} }
} else { } else {
getWidget().initializeRows(uidl, rowData); getWidget().initializeRows(uidl, rowData);

+ 103
- 0
uitest/src/com/vaadin/tests/components/window/BottomComponentScrollsUp.java View File

/*
* Copyright 2000-2014 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.tests.components.window;

import java.util.ArrayList;
import java.util.List;

import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;

/**
* Reproducing bug #12943 where an action on a Button or ComboBox placed at the
* bottom of a window in a scroll panel, will scroll up the parent panel.
*
* This was due to the fact that with the state confirmation notification from
* the server, the window.setVisible would be call again, and the hack that
* solved the scrollbars in a window (#11994) would cause the our bug.
*
* @since
* @author Vaadin Ltd
*/
@SuppressWarnings("serial")
public class BottomComponentScrollsUp extends AbstractTestUI {

@Override
protected void setup(VaadinRequest request) {
Button b = new Button("Open window");
addComponent(b);
b.addClickListener(new ClickListener() {

@Override
public void buttonClick(ClickEvent event) {
openWindow();
}

});

openWindow();
}

private void openWindow() {
Window w = new Window();
w.setWidth("300px");
w.setHeight("300px");
w.center();

Panel p = createPanel();
p.setSizeFull();

w.setContent(p);

addWindow(w);
}

private Panel createPanel() {
Panel p = new Panel();

VerticalLayout content = new VerticalLayout();
p.setContent(content);
content.setHeight("500px");

List<String> items = new ArrayList<String>();
items.add("1");
items.add("2");
items.add("3");

Button button = new Button("Press me");
content.addComponent(button);
content.setComponentAlignment(button, Alignment.BOTTOM_CENTER);
return p;
}

@Override
protected String getTestDescription() {
return "Interacting with a component at the bottom of scrollable panel within a subwindow scrolls up";
}

@Override
protected Integer getTicketNumber() {
return 12943;
}

}

+ 71
- 0
uitest/src/com/vaadin/tests/components/window/BottomComponentScrollsUpTest.java View File

/*
* Copyright 2000-2014 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.tests.components.window;

import java.io.IOException;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;

import com.vaadin.testbench.TestBenchElement;
import com.vaadin.tests.tb3.MultiBrowserTest;

/**
* Automatic test for fix for #12943.
*
* While testing without the fix, the test failed on both Chrome and PhantomJS.
*
* @since
* @author Vaadin Ltd
*/
public class BottomComponentScrollsUpTest extends MultiBrowserTest {

@Override
public void setup() throws Exception {
super.setup();

openTestURL();
}

@Test
public void windowScrollTest() throws IOException, InterruptedException {
TestBenchElement panelScrollable = (TestBenchElement) getDriver()
.findElement(By.className("v-panel-content"));
Dimension panelScrollableSize = panelScrollable.getSize();

WebElement verticalLayout = panelScrollable.findElement(By
.className("v-verticallayout"));
Dimension verticalLayoutSize = verticalLayout.getSize();

panelScrollable.scroll(verticalLayoutSize.height);

WebElement button = verticalLayout
.findElement(By.className("v-button"));

button.click();

// Loose the focus from the button.
new Actions(getDriver())
.moveToElement(panelScrollable, panelScrollableSize.width / 2,
panelScrollableSize.height / 2).click().build()
.perform();

compareScreen("window");
}
}

Loading…
Cancel
Save