From add05e15fcd82d73f4c46245dbe6d0999437cdec Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 17 Feb 2015 11:51:11 +0200 Subject: [PATCH] Modifies GridElement to support details (#16644) Grid's SubPartAware logic was refactored, splitting it into both Grid and Escalator. Also adds tests for grid details rows. Change-Id: I4876a8a9a397eea35526e15f7e447c69b0d96983 --- .../com/vaadin/client/widgets/Escalator.java | 240 +++++++++++++++++- .../src/com/vaadin/client/widgets/Grid.java | 169 ++++-------- .../testbench/elements/GridElement.java | 38 ++- .../basicfeatures/client/GridDetailsTest.java | 99 ++++++++ 4 files changed, 410 insertions(+), 136 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 6c6998277f..567262c6b2 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -16,6 +16,7 @@ package com.vaadin.client.widgets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -57,6 +58,7 @@ import com.vaadin.client.BrowserInfo; import com.vaadin.client.DeferredWorker; import com.vaadin.client.Profiler; import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.SubPartAware; import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widget.escalator.ColumnConfiguration; import com.vaadin.client.widget.escalator.EscalatorUpdater; @@ -267,7 +269,8 @@ abstract class JsniWorkaround { * @since 7.4 * @author Vaadin Ltd */ -public class Escalator extends Widget implements RequiresResize, DeferredWorker { +public class Escalator extends Widget implements RequiresResize, + DeferredWorker, SubPartAware { // todo comments legend /* @@ -4319,6 +4322,26 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching"; assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching"; } + + public String getSubPartName(Element subElement) { + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + if (spacer.getRootElement().isOrHasChild(subElement)) { + return "spacer[" + spacer.getRow() + "]"; + } + } + return null; + } + + public Element getSubPartElement(int index) { + getLogger().warning("SpacerContainer.getSubPartElement " + index); + + SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index)); + if (spacer != null) { + return spacer.getElement(); + } else { + return null; + } + } } private class ElementPositionBookkeeper { @@ -4347,6 +4370,50 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } } + public static class SubPartArguments { + private String type; + private int[] indices; + + private SubPartArguments(String type, int[] indices) { + /* + * The constructor is private so that no third party would by + * mistake start using this parsing scheme, since it's not official + * by TestBench (yet?). + */ + + this.type = type; + this.indices = indices; + } + + public String getType() { + return type; + } + + public int getIndicesLength() { + return indices.length; + } + + public int getIndex(int i) { + return indices[i]; + } + + public int[] getIndices() { + return Arrays.copyOf(indices, indices.length); + } + + static SubPartArguments create(String subPart) { + String[] splitArgs = subPart.split("\\["); + String type = splitArgs[0]; + int[] indices = new int[splitArgs.length - 1]; + for (int i = 0; i < indices.length; ++i) { + String tmp = splitArgs[i + 1]; + indices[i] = Integer + .parseInt(tmp.substring(0, tmp.length() - 1)); + } + return new SubPartArguments(type, indices); + } + } + // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y /** * The solution to @@ -5295,4 +5362,175 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker int to = (int) Math.ceil(body.heightOfSection); return Range.between(from, to); } + + @Override + @SuppressWarnings("deprecation") + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + SubPartArguments args = parseSubPartArguments(subPart); + + Element tableStructureElement = getSubPartElementTableStructure(args); + if (tableStructureElement != null) { + return DOM.asOld(tableStructureElement); + } + + Element spacerElement = getSubPartElementSpacer(args); + if (spacerElement != null) { + return DOM.asOld(spacerElement); + } + + return null; + } + + private Element getSubPartElementTableStructure(SubPartArguments args) { + + String type = args.getType(); + int[] indices = args.getIndices(); + + // Get correct RowContainer for type from Escalator + RowContainer container = null; + if (type.equalsIgnoreCase("header")) { + container = getHeader(); + } else if (type.equalsIgnoreCase("cell")) { + // If wanted row is not visible, we need to scroll there. + Range visibleRowRange = getVisibleRowRange(); + if (indices.length > 0 && !visibleRowRange.contains(indices[0])) { + try { + scrollToRow(indices[0], ScrollDestination.ANY, 0); + } catch (IllegalArgumentException e) { + getLogger().log(Level.SEVERE, e.getMessage()); + } + // Scrolling causes a lazy loading event. No element can + // currently be retrieved. + return null; + } + container = getBody(); + } else if (type.equalsIgnoreCase("footer")) { + container = getFooter(); + } + + if (null != container) { + if (indices.length == 0) { + // No indexing. Just return the wanted container element + return container.getElement(); + } else { + try { + return getSubPart(container, indices); + } catch (Exception e) { + getLogger().log(Level.SEVERE, e.getMessage()); + } + } + } + return null; + } + + private Element getSubPart(RowContainer container, int[] indices) { + Element targetElement = container.getRowElement(indices[0]); + + // Scroll wanted column to view if able + if (indices.length > 1 && targetElement != null) { + if (getColumnConfiguration().getFrozenColumnCount() <= indices[1]) { + scrollToColumn(indices[1], ScrollDestination.ANY, 0); + } + + targetElement = getCellFromRow(TableRowElement.as(targetElement), + indices[1]); + + for (int i = 2; i < indices.length && targetElement != null; ++i) { + targetElement = (Element) targetElement.getChild(indices[i]); + } + } + + return targetElement; + } + + private static Element getCellFromRow(TableRowElement rowElement, int index) { + int childCount = rowElement.getCells().getLength(); + if (index < 0 || index >= childCount) { + return null; + } + + TableCellElement currentCell = null; + boolean indexInColspan = false; + int i = 0; + + while (!indexInColspan) { + currentCell = rowElement.getCells().getItem(i); + + // Calculate if this is the cell we are looking for + int colSpan = currentCell.getColSpan(); + indexInColspan = index < colSpan + i; + + // Increment by colspan to skip over hidden cells + i += colSpan; + } + return currentCell; + } + + private Element getSubPartElementSpacer(SubPartArguments args) { + if ("spacer".equals(args.getType()) && args.getIndicesLength() == 1) { + return body.spacerContainer.getSubPartElement(args.getIndex(0)); + } else { + return null; + } + } + + @Override + @SuppressWarnings("deprecation") + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + + /* + * The spacer check needs to be before table structure check, because + * (for now) the table structure will take spacer elements into account + * as well, when it shouldn't. + */ + + String spacer = getSubPartNameSpacer(subElement); + if (spacer != null) { + return spacer; + } + + String tableStructure = getSubPartNameTableStructure(subElement); + if (tableStructure != null) { + return tableStructure; + } + + return null; + } + + private String getSubPartNameTableStructure(Element subElement) { + + List containers = Arrays.asList(getHeader(), getBody(), + getFooter()); + List containerType = Arrays.asList("header", "cell", "footer"); + + for (int i = 0; i < containers.size(); ++i) { + RowContainer container = containers.get(i); + boolean containerRow = (subElement.getTagName().equalsIgnoreCase( + "tr") && subElement.getParentElement() == container + .getElement()); + if (containerRow) { + /* + * Wanted SubPart is row that is a child of containers root to + * get indices, we use a cell that is a child of this row + */ + subElement = subElement.getFirstChildElement(); + } + + Cell cell = container.getCell(subElement); + if (cell != null) { + // Skip the column index if subElement was a child of root + return containerType.get(i) + "[" + cell.getRow() + + (containerRow ? "]" : "][" + cell.getColumn() + "]"); + } + } + return null; + } + + private String getSubPartNameSpacer(Element subElement) { + return body.spacerContainer.getSubPartName(subElement); + } + + public static SubPartArguments parseSubPartArguments(String subPart) { + return SubPartArguments.create(subPart); + } } diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 31984f7d5b..7890b13904 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -130,6 +130,7 @@ import com.vaadin.client.widget.grid.sort.SortEvent; import com.vaadin.client.widget.grid.sort.SortHandler; import com.vaadin.client.widget.grid.sort.SortOrder; import com.vaadin.client.widgets.Escalator.AbstractRowContainer; +import com.vaadin.client.widgets.Escalator.SubPartArguments; import com.vaadin.client.widgets.Grid.Editor.State; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.grid.GridConstants; @@ -5250,153 +5251,77 @@ public class Grid extends ResizeComposite implements } @Override + @SuppressWarnings("deprecation") public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - // Parse SubPart string to type and indices - String[] splitArgs = subPart.split("\\["); - - String type = splitArgs[0]; - int[] indices = new int[splitArgs.length - 1]; - for (int i = 0; i < indices.length; ++i) { - String tmp = splitArgs[i + 1]; - indices[i] = Integer.parseInt(tmp.substring(0, tmp.length() - 1)); - } - - // Get correct RowContainer for type from Escalator - RowContainer container = null; - if (type.equalsIgnoreCase("header")) { - container = escalator.getHeader(); - } else if (type.equalsIgnoreCase("cell")) { - // If wanted row is not visible, we need to scroll there. - Range visibleRowRange = escalator.getVisibleRowRange(); - if (indices.length > 0 && !visibleRowRange.contains(indices[0])) { - try { - scrollToRow(indices[0]); - } catch (IllegalArgumentException e) { - getLogger().log(Level.SEVERE, e.getMessage()); - } - // Scrolling causes a lazy loading event. No element can - // currently be retrieved. - return null; - } - container = escalator.getBody(); - } else if (type.equalsIgnoreCase("footer")) { - container = escalator.getFooter(); - } else if (type.equalsIgnoreCase("editor")) { - if (editor.getState() != State.ACTIVE) { - // Editor is not there. - return null; - } - if (indices.length == 0) { - return DOM.asOld(editor.editorOverlay); - } else if (indices.length == 1 && indices[0] < columns.size()) { - escalator.scrollToColumn(indices[0], ScrollDestination.ANY, 0); - return editor.getWidget(columns.get(indices[0])).getElement(); - } else { - return null; - } + Element subPartElement = escalator.getSubPartElement(subPart + .replaceFirst("^details\\[", "spacer[")); + if (subPartElement != null) { + return DOM.asOld(subPartElement); } - if (null != container) { - if (indices.length == 0) { - // No indexing. Just return the wanted container element - return DOM.asOld(container.getElement()); - } else { - try { - return DOM.asOld(getSubPart(container, indices)); - } catch (Exception e) { - getLogger().log(Level.SEVERE, e.getMessage()); - } - } + SubPartArguments args = Escalator.parseSubPartArguments(subPart); + + Element editor = getSubPartElementEditor(args); + if (editor != null) { + return DOM.asOld(editor); } + return null; } - private Element getSubPart(RowContainer container, int[] indices) { - Element targetElement = container.getRowElement(indices[0]); - - // Scroll wanted column to view if able - if (indices.length > 1 && targetElement != null) { - if (escalator.getColumnConfiguration().getFrozenColumnCount() <= indices[1]) { - escalator.scrollToColumn(indices[1], ScrollDestination.ANY, 0); - } + private Element getSubPartElementEditor(SubPartArguments args) { - targetElement = getCellFromRow(TableRowElement.as(targetElement), - indices[1]); - - for (int i = 2; i < indices.length && targetElement != null; ++i) { - targetElement = (Element) targetElement.getChild(indices[i]); - } + if (!args.getType().equalsIgnoreCase("editor") + || editor.getState() != State.ACTIVE) { + return null; } - return targetElement; - } - - private Element getCellFromRow(TableRowElement rowElement, int index) { - int childCount = rowElement.getCells().getLength(); - if (index < 0 || index >= childCount) { - return null; + if (args.getIndicesLength() == 0) { + return editor.editorOverlay; + } else if (args.getIndicesLength() == 1 + && args.getIndex(0) < columns.size()) { + escalator + .scrollToColumn(args.getIndex(0), ScrollDestination.ANY, 0); + return editor.getWidget(columns.get(args.getIndex(0))).getElement(); } - TableCellElement currentCell = null; - boolean indexInColspan = false; - int i = 0; + return null; + } - while (!indexInColspan) { - currentCell = rowElement.getCells().getItem(i); + @Override + @SuppressWarnings("deprecation") + public String getSubPartName(com.google.gwt.user.client.Element subElement) { - // Calculate if this is the cell we are looking for - int colSpan = currentCell.getColSpan(); - indexInColspan = index < colSpan + i; + String escalatorStructureName = escalator.getSubPartName(subElement); + if (escalatorStructureName != null) { + return escalatorStructureName.replaceFirst("^spacer", "details"); + } - // Increment by colspan to skip over hidden cells - i += colSpan; + String editorName = getSubPartNameEditor(subElement); + if (editorName != null) { + return editorName; } - return currentCell; - } - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - // Containers and matching SubPart types - List containers = Arrays.asList(escalator.getHeader(), - escalator.getBody(), escalator.getFooter()); - List containerType = Arrays.asList("header", "cell", "footer"); + return null; + } - for (int i = 0; i < containers.size(); ++i) { - RowContainer container = containers.get(i); - boolean containerRow = (subElement.getTagName().equalsIgnoreCase( - "tr") && subElement.getParentElement() == container - .getElement()); - if (containerRow) { - // Wanted SubPart is row that is a child of containers root - // To get indices, we use a cell that is a child of this row - subElement = DOM.asOld(subElement.getFirstChildElement()); - } + private String getSubPartNameEditor(Element subElement) { - Cell cell = container.getCell(subElement); - if (cell != null) { - // Skip the column index if subElement was a child of root - return containerType.get(i) + "[" + cell.getRow() - + (containerRow ? "]" : "][" + cell.getColumn() + "]"); - } + if (editor.getState() != State.ACTIVE + || !editor.editorOverlay.isOrHasChild(subElement)) { + return null; } - // Check if subelement is part of editor. - if (editor.getState() == State.ACTIVE) { - if (editor.editorOverlay.isOrHasChild(subElement)) { - int i = 0; - for (Column column : columns) { - if (editor.getWidget(column).getElement() - .isOrHasChild(subElement)) { - return "editor[" + i + "]"; - } - ++i; - } - return "editor"; + int i = 0; + for (Column column : columns) { + if (editor.getWidget(column).getElement().isOrHasChild(subElement)) { + return "editor[" + i + "]"; } + ++i; } - return null; + return "editor"; } private void setSelectColumnRenderer( diff --git a/uitest/src/com/vaadin/testbench/elements/GridElement.java b/uitest/src/com/vaadin/testbench/elements/GridElement.java index 3753696855..ff7343a3d6 100644 --- a/uitest/src/com/vaadin/testbench/elements/GridElement.java +++ b/uitest/src/com/vaadin/testbench/elements/GridElement.java @@ -25,7 +25,7 @@ import com.vaadin.testbench.By; import com.vaadin.testbench.TestBenchElement; /** - * TestBench Element API for Grid + * TestBench Element API for Grid. * * @since * @author Vaadin Ltd @@ -148,7 +148,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Scrolls Grid element so that wanted row is displayed + * Scrolls Grid element so that wanted row is displayed. * * @param index * Target row @@ -251,7 +251,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get header row count + * Get header row count. * * @return Header row count */ @@ -260,7 +260,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get footer row count + * Get footer row count. * * @return Footer row count */ @@ -269,7 +269,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get a header row by index + * Get a header row by index. * * @param rowIndex * Row index @@ -280,7 +280,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get a footer row by index + * Get a footer row by index. * * @param rowIndex * Row index @@ -291,7 +291,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the vertical scroll element + * Get the vertical scroll element. * * @return The element representing the vertical scrollbar */ @@ -301,7 +301,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the horizontal scroll element + * Get the horizontal scroll element. * * @return The element representing the horizontal scrollbar */ @@ -311,7 +311,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the header element + * Get the header element. * * @return The thead element */ @@ -320,7 +320,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the body element + * Get the body element. * * @return the tbody element */ @@ -329,7 +329,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the footer element + * Get the footer element. * * @return the tfoot element */ @@ -338,7 +338,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the element wrapping the table element + * Get the element wrapping the table element. * * @return The element that wraps the table element */ @@ -353,7 +353,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Helper function to get Grid subparts wrapped correctly + * Helper function to get Grid subparts wrapped correctly. * * @param subPartSelector * SubPart to be used in ComponentLocator @@ -362,4 +362,16 @@ public class GridElement extends AbstractComponentElement { private TestBenchElement getSubPart(String subPartSelector) { return (TestBenchElement) findElement(By.vaadin(subPartSelector)); } + + /** + * Gets the element that contains the details of a row. + * + * @since + * @param rowIndex + * the index of the row for the details + * @return the element that contains the details of a row + */ + public TestBenchElement getDetails(int rowIndex) { + return getSubPart("#details[" + rowIndex + "]"); + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java new file mode 100644 index 0000000000..981a1cc217 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java @@ -0,0 +1,99 @@ +/* + * 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.grid.basicfeatures.client; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.NoSuchElementException; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; + +public class GridDetailsTest extends GridBasicClientFeaturesTest { + + private static final String[] SET_GENERATOR = new String[] { "Component", + "Row details", "Set generator" }; + private static final String[] TOGGLE_DETAILS_FOR_ROW_1 = new String[] { + "Component", "Row details", "Toggle details for row 1" }; + private static final String[] TOGGLE_DETAILS_FOR_ROW_100 = new String[] { + "Component", "Row details", "Toggle details for row 100" }; + + @Before + public void setUp() { + openTestURL(); + } + + @Test(expected = NoSuchElementException.class) + public void noDetailsByDefault() { + assertNull("details for row 1 should not exist at the start", + getGridElement().getDetails(1)); + } + + @Test + public void nullRendererShowsDetailsPlaceholder() { + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + TestBenchElement details = getGridElement().getDetails(1); + assertNotNull("details for row 1 should not exist at the start", + details); + assertTrue("details should've been empty for null renderer", details + .getText().isEmpty()); + } + + @Test + public void applyRendererThenOpenDetails() { + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + TestBenchElement details = getGridElement().getDetails(1); + assertTrue("Unexpected details content", + details.getText().startsWith("Row: 1.")); + } + + @Test + public void openDetailsThenAppyRenderer() { + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + selectMenuPath(SET_GENERATOR); + + TestBenchElement details = getGridElement().getDetails(1); + assertTrue("Unexpected details content", + details.getText().startsWith("Row: 1.")); + } + + @Test + public void openHiddenDetailsThenScrollToIt() { + try { + getGridElement().getDetails(100); + fail("details row for 100 was apparently found, while it shouldn't have been."); + } catch (NoSuchElementException e) { + // expected + } + + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_100); + + // scroll a bit beyond so we see below. + getGridElement().scrollToRow(101); + + TestBenchElement details = getGridElement().getDetails(100); + assertTrue("Unexpected details content", + details.getText().startsWith("Row: 100.")); + } +} -- 2.39.5