diff options
author | Leif Åstrand <leif@vaadin.com> | 2015-08-28 21:47:26 +0300 |
---|---|---|
committer | Teemu Suo-Anttila <teemusa@vaadin.com> | 2015-11-05 13:06:42 +0200 |
commit | 8018ec184131d63911ce9fe8f8a398cab029ca82 (patch) | |
tree | fa66233cb684377e94b7b67497fe34ef6f3e92a6 | |
parent | 6da674f2dfae2e2bc5de22b89a0b7e7648d52ed7 (diff) | |
download | vaadin-framework-8018ec184131d63911ce9fe8f8a398cab029ca82.tar.gz vaadin-framework-8018ec184131d63911ce9fe8f8a398cab029ca82.zip |
Force FormLayout children to shrink with the layout (#11154)7.5.9
The <table> used by the FormLayout doesn't reduce its size if any child
component has locked its own size (which is the case with e.g. Table and
some other components doing explicit pixel calculations). To work around
this, we need to detect the situation, force the <table> to reduce its
width by temporarily assigning explicit widths to the cells and then
remove the forced size after all children have adjusted.
Change-Id: Iacef62979acf24c869a5cbeb82efb0c1e537ec95
6 files changed, 332 insertions, 2 deletions
diff --git a/WebContent/VAADIN/themes/base/formlayout/formlayout.scss b/WebContent/VAADIN/themes/base/formlayout/formlayout.scss index a8006fdfe3..2e2a3213f9 100644 --- a/WebContent/VAADIN/themes/base/formlayout/formlayout.scss +++ b/WebContent/VAADIN/themes/base/formlayout/formlayout.scss @@ -19,6 +19,7 @@ } .#{$primaryStyleName}-errorcell, .#{$primaryStyleName}-captioncell { width: 1px; /* Don't use any extra space */ + min-width: 1px; } .#{$primaryStyleName}-captioncell .v-caption { overflow: visible; diff --git a/WebContent/VAADIN/themes/reindeer/formlayout/formlayout.scss b/WebContent/VAADIN/themes/reindeer/formlayout/formlayout.scss index 51b8a96b60..7f8871fdac 100644 --- a/WebContent/VAADIN/themes/reindeer/formlayout/formlayout.scss +++ b/WebContent/VAADIN/themes/reindeer/formlayout/formlayout.scss @@ -2,6 +2,7 @@ .#{$primaryStyleName}-errorcell { width: 13px; + min-width: 13px; } .#{$primaryStyleName}-cell .v-errorindicator { width: 13px; diff --git a/client/src/com/vaadin/client/ui/VFormLayout.java b/client/src/com/vaadin/client/ui/VFormLayout.java index bcbb3ebf7b..3719688f2b 100644 --- a/client/src/com/vaadin/client/ui/VFormLayout.java +++ b/client/src/com/vaadin/client/ui/VFormLayout.java @@ -86,7 +86,7 @@ public class VFormLayout extends SimplePanel { private static final int COLUMN_CAPTION = 0; private static final int COLUMN_ERRORFLAG = 1; - private static final int COLUMN_WIDGET = 2; + public static final int COLUMN_WIDGET = 2; private HashMap<Widget, Caption> widgetToCaption = new HashMap<Widget, Caption>(); private HashMap<Widget, ErrorFlag> widgetToError = new HashMap<Widget, ErrorFlag>(); diff --git a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java index 9517619cf3..febbb5d6a2 100644 --- a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java +++ b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java @@ -15,26 +15,124 @@ */ package com.vaadin.client.ui.formlayout; +import java.util.HashMap; +import java.util.Map; + import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; import com.vaadin.client.TooltipInfo; import com.vaadin.client.WidgetUtil; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.AbstractLayoutConnector; +import com.vaadin.client.ui.PostLayoutListener; import com.vaadin.client.ui.VFormLayout; import com.vaadin.client.ui.VFormLayout.Caption; import com.vaadin.client.ui.VFormLayout.ErrorFlag; import com.vaadin.client.ui.VFormLayout.VFormLayoutTable; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.MarginInfo; import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState; import com.vaadin.ui.FormLayout; @Connect(FormLayout.class) -public class FormLayoutConnector extends AbstractLayoutConnector { +public class FormLayoutConnector extends AbstractLayoutConnector implements + PostLayoutListener { + + private Map<ComponentConnector, String> oldMaxWidths = null; + + private static final ElementResizeListener dummyFirstCellResizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + // Ignore event, listener added just to make measurements available + } + }; + + // Detects situations when there's something inside the FormLayout that + // prevents it from shrinking + private ElementResizeListener resizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + LayoutManager layoutManager = getLayoutManager(); + double tableWidth = layoutManager + .getOuterWidthDouble(getWidget().table.getElement()); + double ownWidth = layoutManager.getInnerWidthDouble(getWidget() + .getElement()); + if (ownWidth < tableWidth) { + // Something inside the table prevents it from shrinking, + // temporarily force column widths + double excessWidth = tableWidth - ownWidth; + + // All td elements in the component column have the same width, + // so we only need to check the width of the first one to know + // how wide the column is. + Element firstComponentTd = findFirstComponentTd(); + if (firstComponentTd == null) { + // Can't do anything if there are no rows + return; + } + + double componentColWidth = layoutManager + .getOuterWidthDouble(firstComponentTd); + + if (componentColWidth == -1) { + // Didn't get a proper width reading, best to not touch + // anything + return; + } + + // Restrict content td width + // Round down to prevent interactions with fractional sizes of + // other columns + int targetWidth = (int) Math.floor(componentColWidth + - excessWidth); + + // Target might be negative if captions are wider than the total + // available width + targetWidth = Math.max(0, targetWidth); + + if (oldMaxWidths == null) { + oldMaxWidths = new HashMap<ComponentConnector, String>(); + } + + for (ComponentConnector child : getChildComponents()) { + Element childElement = child.getWidget().getElement(); + if (!oldMaxWidths.containsKey(child)) { + oldMaxWidths.put(child, + childElement.getPropertyString("maxWidth")); + } + childElement.getStyle().setPropertyPx("maxWidth", + targetWidth); + layoutManager.reportOuterWidth(child, targetWidth); + } + } + } + }; + + @Override + protected void init() { + super.init(); + getLayoutManager().addElementResizeListener( + getWidget().table.getElement(), resizeListener); + getLayoutManager().addElementResizeListener(getWidget().getElement(), + resizeListener); + addComponentCellListener(); + } + + @Override + public void onUnregister() { + getLayoutManager().removeElementResizeListener( + getWidget().table.getElement(), resizeListener); + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), resizeListener); + removeComponentCellListener(); + super.onUnregister(); + } @Override public AbstractOrderedLayoutState getState() { @@ -57,6 +155,8 @@ public class FormLayoutConnector extends AbstractLayoutConnector { VFormLayout formLayout = getWidget(); VFormLayoutTable formLayoutTable = getWidget().table; + removeComponentCellListener(); + int childId = 0; formLayoutTable.setRowCount(getChildComponents().size()); @@ -87,6 +187,33 @@ public class FormLayoutConnector extends AbstractLayoutConnector { formLayoutTable.cleanReferences(oldChild.getWidget()); } + addComponentCellListener(); + } + + private void addComponentCellListener() { + Element td = findFirstComponentTd(); + if (td != null) { + getLayoutManager().addElementResizeListener(td, + dummyFirstCellResizeListener); + } + } + + private void removeComponentCellListener() { + Element td = findFirstComponentTd(); + if (td != null) { + getLayoutManager().removeElementResizeListener(td, + dummyFirstCellResizeListener); + } + } + + private Element findFirstComponentTd() { + VFormLayoutTable table = getWidget().table; + if (table.getRowCount() == 0) { + return null; + } else { + return table.getCellFormatter().getElement(0, + VFormLayoutTable.COLUMN_WIDGET); + } } @Override @@ -148,4 +275,20 @@ public class FormLayoutConnector extends AbstractLayoutConnector { return true; } + @Override + public void postLayout() { + if (oldMaxWidths != null) { + for (ComponentConnector child : getChildComponents()) { + Element childNode = child.getWidget().getElement(); + String oldValue = oldMaxWidths.get(child); + if (oldValue == null) { + childNode.getStyle().clearProperty("maxWidth"); + } else { + childNode.getStyle().setProperty("maxWidth", oldValue); + } + } + oldMaxWidths = null; + } + } + } diff --git a/uitest/src/com/vaadin/tests/components/formlayout/FormLayoutResizing.java b/uitest/src/com/vaadin/tests/components/formlayout/FormLayoutResizing.java new file mode 100644 index 0000000000..7ed91979e2 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/formlayout/FormLayoutResizing.java @@ -0,0 +1,85 @@ +/* + * 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.formlayout; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.FormLayout; +import com.vaadin.ui.Table; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +public class FormLayoutResizing extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + FormLayout form1 = createForm("Table", createTable()); + + CssLayout cssLayout = new CssLayout(createTable()); + cssLayout.setWidth("100%"); + FormLayout form2 = createForm("Wrap", cssLayout); + + final VerticalLayout view = new VerticalLayout(form1, form2); + view.setWidth("400px"); + + addComponent(view); + + addComponent(new Button("Toggle width", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + if ((int) view.getWidth() == 400) { + view.setWidth("600px"); + } else { + view.setWidth("400px"); + } + } + })); + } + + private static FormLayout createForm(String caption, Component table) { + table.setCaption(caption); + + TextField tf = new TextField("Text field"); + tf.setWidth("100%"); + + FormLayout form = new FormLayout(); + form.setWidth("100%"); + + form.addComponent(tf); + form.addComponent(table); + return form; + } + + private static Table createTable() { + Table table = new Table(); + table.setHeight("100px"); + + table.addContainerProperty("Column 1", String.class, ""); + table.addContainerProperty("Column 2", String.class, ""); + table.setWidth("100%"); + return table; + } + + @Override + protected String getTestDescription() { + return "100% wide Table inside FormLayout should resize when the layout width changes"; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/formlayout/FormLayoutResizingTest.java b/uitest/src/com/vaadin/tests/components/formlayout/FormLayoutResizingTest.java new file mode 100644 index 0000000000..3285503aeb --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/formlayout/FormLayoutResizingTest.java @@ -0,0 +1,100 @@ +/* + * 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.formlayout; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.FormLayoutElement; +import com.vaadin.testbench.elements.TableElement; +import com.vaadin.testbench.parallel.BrowserUtil; +import com.vaadin.tests.tb3.MultiBrowserThemeTest; + +public class FormLayoutResizingTest extends MultiBrowserThemeTest { + @Test + public void testTableResizing() { + openTestURL(); + + List<TableElement> tables = $(TableElement.class).all(); + Assert.assertEquals("Sanity check", 2, tables.size()); + + List<FormLayoutElement> layouts = $(FormLayoutElement.class).all(); + Assert.assertEquals("Sanity check", 2, layouts.size()); + + ButtonElement toggleButton = $(ButtonElement.class).first(); + + int[] originalWidths = getWidths(tables); + + // In some browser and theme combinations, the original rendering is + // slightly too wide. Find out this overshoot and adjust the expected + // table width accordingly. + for (int i = 0; i < 2; i++) { + FormLayoutElement formLayout = layouts.get(i); + WebElement table = formLayout.findElement(By.tagName("table")); + int overshoot = table.getSize().width - formLayout.getSize().width; + originalWidths[i] -= overshoot; + } + + // Toggle size from 400 px to 600 px + toggleButton.click(); + + int[] expandedWidths = getWidths(tables); + + Assert.assertEquals("Table should have grown ", + originalWidths[0] + 200, expandedWidths[0]); + Assert.assertEquals("Wrapped table should have grown ", + originalWidths[1] + 200, expandedWidths[1]); + + // Toggle size from 600 px to 400 px + toggleButton.click(); + + int[] collapsedWidths = getWidths(tables); + + Assert.assertEquals("Table should return to original width ", + originalWidths[0], collapsedWidths[0]); + Assert.assertEquals("Wrapped table should return to original width ", + originalWidths[1], collapsedWidths[1]); + + // Verify that growing is not restricted after triggering the fix + // Toggle size from 400 px to 600 px + toggleButton.click(); + + expandedWidths = getWidths(tables); + + Assert.assertEquals("Table should have grown ", + originalWidths[0] + 200, expandedWidths[0]); + Assert.assertEquals("Wrapped table should have grown ", + originalWidths[1] + 200, expandedWidths[1]); + } + + @Override + protected boolean useNativeEventsForIE() { + return !BrowserUtil.isIE(getDesiredCapabilities(), 11); + } + + private static int[] getWidths(List<TableElement> tables) { + int[] widths = new int[tables.size()]; + for (int i = 0; i < widths.length; i++) { + widths[i] = tables.get(i).getSize().width; + } + return widths; + } +} |