]> source.dussan.org Git - vaadin-framework.git/commitdiff
Grid html/component content in headers
authorAleksi Hietanen <aleksi@vaadin.com>
Mon, 17 Oct 2016 07:13:33 +0000 (10:13 +0300)
committerAleksi Hietanen <aleksi@vaadin.com>
Tue, 25 Oct 2016 11:38:11 +0000 (14:38 +0300)
Change-Id: Ie6129b51d15d4f30a6b4c034999ff02deec1c6a7

client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
server/src/main/java/com/vaadin/ui/Grid.java
server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java
shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java

index a19c955fc45312fc4164c6f188ffa2a9d5361cce..ad5f8200ea07acb7dab5c2b107219781c30f2d3a 100644 (file)
@@ -54,6 +54,7 @@ import com.vaadin.client.widget.grid.sort.SortOrder;
 import com.vaadin.client.widgets.Grid;
 import com.vaadin.client.widgets.Grid.Column;
 import com.vaadin.client.widgets.Grid.FooterRow;
+import com.vaadin.client.widgets.Grid.HeaderCell;
 import com.vaadin.client.widgets.Grid.HeaderRow;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.data.DataCommunicatorConstants;
@@ -66,6 +67,7 @@ import com.vaadin.shared.ui.grid.GridConstants.Section;
 import com.vaadin.shared.ui.grid.GridServerRpc;
 import com.vaadin.shared.ui.grid.GridState;
 import com.vaadin.shared.ui.grid.SectionState;
+import com.vaadin.shared.ui.grid.SectionState.CellState;
 import com.vaadin.shared.ui.grid.SectionState.RowState;
 
 import elemental.json.JsonObject;
@@ -255,14 +257,41 @@ public class GridConnector
         for (RowState rowState : state.rows) {
             HeaderRow row = grid.appendHeaderRow();
 
+            if (rowState.defaultHeader) {
+                grid.setDefaultHeaderRow(row);
+            }
+
             rowState.cells.forEach((columnId, cellState) -> {
-                row.getCell(getColumn(columnId)).setText(cellState.text);
+                updateHeaderCellFromState(row.getCell(getColumn(columnId)),
+                        cellState);
             });
+        }
+    }
 
-            if (rowState.defaultHeader) {
-                grid.setDefaultHeaderRow(row);
+    private void updateHeaderCellFromState(HeaderCell cell,
+            CellState cellState) {
+        switch (cellState.type) {
+        case TEXT:
+            cell.setText(cellState.text);
+            break;
+        case HTML:
+            cell.setHtml(cellState.html);
+            break;
+        case WIDGET:
+            ComponentConnector connector = (ComponentConnector) cellState.connector;
+            if (connector != null) {
+                cell.setWidget(connector.getWidget());
+            } else {
+                // This happens if you do setVisible(false) on the component on
+                // the server side
+                cell.setWidget(null);
             }
+            break;
+        default:
+            throw new IllegalStateException(
+                    "unexpected cell type: " + cellState.type);
         }
+        cell.setStyleName(cellState.styleName);
     }
 
     /**
index db85ec1fb22f14b0ed8881b43fab94a6f5b7239d..490f9bf8f212b7d28fd29d7e37f205aff0eb7337 100644 (file)
@@ -54,6 +54,7 @@ import com.vaadin.shared.ui.grid.GridConstants;
 import com.vaadin.shared.ui.grid.GridConstants.Section;
 import com.vaadin.shared.ui.grid.GridServerRpc;
 import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.GridStaticCellType;
 import com.vaadin.shared.ui.grid.HeightMode;
 import com.vaadin.shared.ui.grid.SectionState;
 import com.vaadin.shared.util.SharedUtil;
@@ -1573,6 +1574,44 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
          *            the header caption to set, not null
          */
         public void setText(String text);
+
+        /**
+         * Returns the HTML content displayed in this cell.
+         *
+         * @return the html
+         *
+         */
+        public String getHtml();
+
+        /**
+         * Sets the HTML content displayed in this cell.
+         *
+         * @param html
+         *            the html to set
+         */
+        public void setHtml(String html);
+
+        /**
+         * Returns the component displayed in this cell.
+         *
+         * @return the component
+         */
+        public Component getComponent();
+
+        /**
+         * Sets the component displayed in this cell.
+         *
+         * @param component
+         *            the component to set
+         */
+        public void setComponent(Component component);
+
+        /**
+         * Returns the type of content stored in this cell.
+         *
+         * @return cell content type
+         */
+        public GridStaticCellType getCellType();
     }
 
     /**
@@ -1628,6 +1667,11 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
 
     private class HeaderImpl extends Header {
 
+        @Override
+        protected Grid<T> getGrid() {
+            return Grid.this;
+        }
+
         @Override
         protected SectionState getState(boolean markAsDirty) {
             return Grid.this.getState(markAsDirty).header;
@@ -1641,6 +1685,11 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
 
     private class FooterImpl extends Footer {
 
+        @Override
+        protected Grid<T> getGrid() {
+            return Grid.this;
+        }
+
         @Override
         protected SectionState getState(boolean markAsDirty) {
             return Grid.this.getState(markAsDirty).footer;
@@ -1885,7 +1934,18 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
 
     @Override
     public Iterator<Component> iterator() {
-        return Collections.unmodifiableSet(extensionComponents).iterator();
+        Set<Component> componentSet = new LinkedHashSet<>(extensionComponents);
+        Header header = getHeader();
+        for (int i = 0; i < header.getRowCount(); ++i) {
+            HeaderRow row = header.getRow(i);
+            getColumns().forEach(column -> {
+                HeaderCell cell = row.getCell(column);
+                if (cell.getCellType() == GridStaticCellType.WIDGET) {
+                    componentSet.add(cell.getComponent());
+                }
+            });
+        }
+        return Collections.unmodifiableSet(componentSet).iterator();
     }
 
     /**
index e1c64f31e66b2de1d5e67d14c2e21e3e26f17f70..eb02be2bbc3df4963a7d86c00d2fa74170a885f8 100644 (file)
@@ -1,12 +1,12 @@
 /*
  * Copyright 2000-2016 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
@@ -24,16 +24,19 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+import com.vaadin.shared.ui.grid.GridStaticCellType;
 import com.vaadin.shared.ui.grid.SectionState;
 import com.vaadin.shared.ui.grid.SectionState.CellState;
 import com.vaadin.shared.ui.grid.SectionState.RowState;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Grid;
 import com.vaadin.ui.Grid.Column;
 
 /**
  * Represents the header or footer section of a Grid.
  *
  * @author Vaadin Ltd.
- * 
+ *
  * @param <ROW>
  *            the type of the rows in the section
  *
@@ -57,7 +60,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
         /**
          * Creates a new row belonging to the given section.
-         * 
+         *
          * @param section
          *            the section of the row
          */
@@ -74,14 +77,14 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
         /**
          * Returns the declarative tag name used for the cells in this row.
-         * 
+         *
          * @return the cell tag name
          */
         protected abstract String getCellTagName();
 
         /**
          * Adds a cell to this section, corresponding to the given column id.
-         * 
+         *
          * @param columnId
          *            the id of the column for which to add a cell
          */
@@ -95,7 +98,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
         /**
          * Removes the cell from this section that corresponds to the given
          * column id. If there is no such cell, does nothing.
-         * 
+         *
          * @param columnId
          *            the id of the column from which to remove the cell
          */
@@ -108,7 +111,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
         /**
          * Returns the shared state of this row.
-         * 
+         *
          * @return the row state
          */
         protected RowState getRowState() {
@@ -122,7 +125,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
          * @param columnId
          *            the id of the column
          * @return the cell for the given column
-         * 
+         *
          * @throws IllegalArgumentException
          *             if no cell was found for the column id
          */
@@ -134,6 +137,12 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
             }
             return cell;
         }
+
+        void detach() {
+            for (CELL cell : cells.values()) {
+                cell.detach();
+            }
+        }
     }
 
     /**
@@ -167,7 +176,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
         /**
          * Returns the shared state of this cell.
-         * 
+         *
          * @return the cell state
          */
         protected CellState getCellState() {
@@ -182,7 +191,9 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
          */
         public void setText(String text) {
             Objects.requireNonNull(text, "text cannot be null");
+            removeComponentIfPresent();
             cellState.text = text;
+            cellState.type = GridStaticCellType.TEXT;
             row.section.markAsDirty();
         }
 
@@ -194,20 +205,99 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
         public String getText() {
             return cellState.text;
         }
+
+        /**
+         * Returns the HTML content displayed in this cell.
+         *
+         * @return the html
+         *
+         */
+        public String getHtml() {
+            if (cellState.type != GridStaticCellType.HTML) {
+                throw new IllegalStateException(
+                        "Cannot fetch HTML from a cell with type "
+                                + cellState.type);
+            }
+            return cellState.html;
+        }
+
+        /**
+         * Sets the HTML content displayed in this cell.
+         *
+         * @param html
+         *            the html to set, not null
+         */
+        public void setHtml(String html) {
+            Objects.requireNonNull(html, "html cannot be null");
+            removeComponentIfPresent();
+            cellState.html = html;
+            cellState.type = GridStaticCellType.HTML;
+            row.section.markAsDirty();
+        }
+
+        /**
+         * Returns the component displayed in this cell.
+         *
+         * @return the component
+         */
+        public Component getComponent() {
+            if (cellState.type != GridStaticCellType.WIDGET) {
+                throw new IllegalStateException(
+                        "Cannot fetch Component from a cell with type "
+                                + cellState.type);
+            }
+            return (Component) cellState.connector;
+        }
+
+        /**
+         * Sets the component displayed in this cell.
+         *
+         * @param component
+         *            the component to set, not null
+         */
+        public void setComponent(Component component) {
+            Objects.requireNonNull(component, "component cannot be null");
+            removeComponentIfPresent();
+            component.setParent(row.section.getGrid());
+            cellState.connector = component;
+            cellState.type = GridStaticCellType.WIDGET;
+            row.section.markAsDirty();
+        }
+
+        /**
+         * Returns the type of content stored in this cell.
+         *
+         * @return cell content type
+         */
+        public GridStaticCellType getCellType() {
+            return cellState.type;
+        }
+
+        private void removeComponentIfPresent() {
+            Component component = (Component) cellState.connector;
+            if (component != null) {
+                component.setParent(null);
+                cellState.connector = null;
+            }
+        }
+
+        void detach() {
+            removeComponentIfPresent();
+        }
     }
 
     private final List<ROW> rows = new ArrayList<>();
 
     /**
      * Creates a new row instance.
-     * 
+     *
      * @return the new row
      */
     protected abstract ROW createRow();
 
     /**
      * Returns the shared state of this section.
-     * 
+     *
      * @param markAsDirty
      *            {@code true} to mark the state as modified, {@code false}
      *            otherwise
@@ -215,6 +305,8 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
      */
     protected abstract SectionState getState(boolean markAsDirty);
 
+    protected abstract Grid<?> getGrid();
+
     protected abstract Collection<? extends Column<?, ?>> getColumns();
 
     /**
@@ -226,7 +318,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
     /**
      * Adds a new row at the given index.
-     * 
+     *
      * @param index
      *            the index of the new row
      * @return the added row
@@ -245,20 +337,21 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
     /**
      * Removes the row at the given index.
-     * 
+     *
      * @param index
      *            the index of the row to remove
      * @throws IndexOutOfBoundsException
      *             if {@code index < 0 || index >= getRowCount()}
      */
     public void removeRow(int index) {
-        rows.remove(index);
+        ROW row = rows.remove(index);
+        row.detach();
         getState(true).rows.remove(index);
     }
 
     /**
      * Removes the given row from this section.
-     * 
+     *
      * @param row
      *            the row to remove, not null
      * @throws IllegalArgumentException
@@ -276,7 +369,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
     /**
      * Returns the row at the given index.
-     * 
+     *
      * @param index
      *            the index of the row
      * @return the row at the index
@@ -322,7 +415,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
 
     /**
      * Returns an unmodifiable list of the rows in this section.
-     * 
+     *
      * @return the rows in this section
      */
     protected List<ROW> getRows() {
index 55a8df99b8d0c10bab778bd571e0f731859b8ab9..39d2d2f2e9af2b82b17972ec6339051ac3f690b0 100644 (file)
@@ -21,6 +21,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.vaadin.shared.Connector;
+
 /**
  * Shared state for Grid headers and footers.
  *
@@ -45,9 +47,23 @@ public class SectionState implements Serializable {
     /** The state of a header or footer cell. */
     public static class CellState implements Serializable {
 
+        public GridStaticCellType type = GridStaticCellType.TEXT;
+
+        /** The style name for this cell. Null if none. */
+        public String styleName = null;
+
         /** The textual caption of this cell. */
         public String text;
 
+        /** The html content of this cell. */
+        public String html;
+
+        /**
+         * The connector for the component that is set to be displayed in this
+         * cell. Null if none.
+         */
+        public Connector connector = null;
+
         /** The id of the column that this cell belongs to. */
         public String columnId;
     }
index f56767e3d3c97ce4c8e2d79e27e91adc7c1cca80..4e9b1546280333b74c23dfba2068d2a45678ef87 100644 (file)
@@ -14,7 +14,7 @@ import com.vaadin.annotations.Widgetset;
 import com.vaadin.server.VaadinRequest;
 import com.vaadin.shared.Registration;
 import com.vaadin.shared.ui.grid.HeightMode;
-import com.vaadin.tests.components.AbstractReindeerTestUIWithLog;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
 import com.vaadin.ui.Button;
 import com.vaadin.ui.Component;
 import com.vaadin.ui.Grid;
@@ -36,7 +36,7 @@ import com.vaadin.ui.renderers.NumberRenderer;
 import com.vaadin.ui.renderers.ProgressBarRenderer;
 
 @Widgetset("com.vaadin.DefaultWidgetSet")
-public class GridBasics extends AbstractReindeerTestUIWithLog {
+public class GridBasics extends AbstractTestUIWithLog {
 
     public static final String ROW_STYLE_GENERATOR_ROW_NUMBERS_FOR_3_OF_4 = "Row numbers for 3/4";
     public static final String ROW_STYLE_GENERATOR_NONE = "None";
@@ -229,6 +229,21 @@ public class GridBasics extends AbstractReindeerTestUIWithLog {
                             .toArray(new Column[columnOrder.size()]));
                 }
             });
+
+            MenuItem headerTypeMenu = columnMenu.addItem("Header Type", null);
+            headerTypeMenu.addItem("Text Header", selectedItem -> grid
+                    .getDefaultHeaderRow().getCell(col).setText("Text Header"));
+            headerTypeMenu
+                    .addItem("HTML Header",
+                            selectedItem -> grid.getDefaultHeaderRow()
+                                    .getCell(col)
+                                    .setHtml("<b>HTML Header</b>"));
+            headerTypeMenu.addItem("Widget Header", selectedItem -> {
+                final Button button = new Button("Button Header");
+                button.addClickListener(clickEvent -> log("Button clicked!"));
+                grid.getDefaultHeaderRow().getCell(col).setComponent(button);
+            });
+
             columnMenu
                     .addItem("Sortable",
                             selectedItem -> col
@@ -278,12 +293,15 @@ public class GridBasics extends AbstractReindeerTestUIWithLog {
                         : null))
                 .setCheckable(true);
         stateMenu
-                .addItem("Cell description generator", item -> grid.getColumns()
-                        .stream().findFirst()
-                        .ifPresent(c -> c.setDescriptionGenerator(
-                                item.isChecked() ? t -> "Cell tooltip for row "
-                                        + t.getRowNumber() + ", Column 0"
-                                        : null)))
+                .addItem("Cell description generator",
+                        item -> grid.getColumns().stream().findFirst()
+                                .ifPresent(
+                                        c -> c.setDescriptionGenerator(
+                                                item.isChecked()
+                                                        ? t -> "Cell tooltip for row "
+                                                                + t.getRowNumber()
+                                                                + ", Column 0"
+                                                        : null)))
                 .setCheckable(true);
         stateMenu.addItem("Item click listener", new Command() {
 
@@ -414,9 +432,10 @@ public class GridBasics extends AbstractReindeerTestUIWithLog {
     private void createFooterMenu(MenuItem footerMenu) {
         footerMenu.addItem("Add default footer row", menuItem -> {
             FooterRow defaultFooter = grid.appendFooterRow();
-            grid.getColumns().forEach(
-                    column -> defaultFooter.getCell(column).setText(grid
-                            .getDefaultHeaderRow().getCell(column).getText()));
+            grid.getColumns()
+                    .forEach(column -> defaultFooter.getCell(column)
+                            .setText(grid.getDefaultHeaderRow().getCell(column)
+                                    .getText()));
             footerMenu.removeChild(menuItem);
         });
         footerMenu.addItem("Append footer row", menuItem -> {
index eed08032de523c137add222f372c0268827c20f1..c128ba2634e29512ea37d8f4f10ee916cb2a5e17 100644 (file)
@@ -165,4 +165,43 @@ public abstract class GridBasicsTest extends MultiBrowserTest {
         getGridElement().getCell(row, column).click();
     }
 
+    protected WebElement getSidebarPopup() {
+        List<WebElement> elements = findElements(
+                By.className("v-grid-sidebar-popup"));
+        if (elements.isEmpty()) {
+            getSidebarOpenButton().click();
+            elements = findElements(By.className("v-grid-sidebar-popup"));
+        }
+        return elements.isEmpty() ? null : elements.get(0);
+    }
+
+    protected WebElement getSidebarPopupIfPresent() {
+        List<WebElement> elements = findElements(
+                By.className("v-grid-sidebar-popup"));
+        return elements.isEmpty() ? null : elements.get(0);
+    }
+
+    protected WebElement getSidebarOpenButton() {
+        List<WebElement> elements = findElements(
+                By.className("v-grid-sidebar-button"));
+        return elements.isEmpty() ? null : elements.get(0);
+    }
+
+    /**
+     * Returns the toggle inside the sidebar for hiding the column at the given
+     * index, or null if not found.
+     */
+    protected WebElement getColumnHidingToggle(int columnIndex) {
+        WebElement sidebar = getSidebarPopup();
+        List<WebElement> elements = sidebar
+                .findElements(By.className("column-hiding-toggle"));
+        for (WebElement e : elements) {
+            if ((e.getText().toLowerCase())
+                    .startsWith("column " + columnIndex)) {
+                return e;
+            }
+        }
+        return null;
+    }
+
 }
index 681da7443fdcdae5972c6085caf692dc6c77ca18..2d0be2a733e0e22ca1c2fc98096f9e250c27f084 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.Test;
 
 import com.vaadin.testbench.By;
 import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.NotificationElement;
 
 public class GridHeaderFooterTest extends GridBasicsTest {
 
@@ -80,7 +81,7 @@ public class GridHeaderFooterTest extends GridBasicsTest {
         selectMenuPath("Component", "Header", "Prepend header row");
         selectMenuPath("Component", "Header", "Set first row as default");
 
-        assertHeaderTexts(0, GridBasics.COLUMN_CAPTIONS);
+        assertHeaderTexts(0, HEADER_TEXTS);
     }
 
     @Test
@@ -137,6 +138,75 @@ public class GridHeaderFooterTest extends GridBasicsTest {
         assertFooterTexts(1, GridBasics.COLUMN_CAPTIONS);
     }
 
+    public void testDynamicallyChangingCellType() throws Exception {
+        openTestURL();
+
+        selectMenuPath("Component", "Columns", "Column 0", "Header Type",
+                "Widget Header");
+        GridCellElement widgetCell = getGridElement().getHeaderCell(0, 0);
+        assertTrue(widgetCell.isElementPresent(By.className("v-button")));
+
+        selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+                "HTML Header");
+        GridCellElement htmlCell = getGridElement().getHeaderCell(0, 1);
+        assertEquals("<b>HTML Header</b>",
+                htmlCell.findElement(
+                        By.className("v-grid-column-header-content"))
+                        .getAttribute("innerHTML"));
+
+        selectMenuPath("Component", "Columns", "Column 2", "Header Type",
+                "Text Header");
+        GridCellElement textCell = getGridElement().getHeaderCell(0, 2);
+
+        assertEquals("text header", textCell.getText().toLowerCase());
+    }
+
+    @Test
+    public void testButtonInHeader() throws Exception {
+        openTestURL();
+
+        selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+                "Widget Header");
+
+        getGridElement().findElements(By.className("v-button")).get(0).click();
+
+        assertTrue("Button click should be logged",
+                logContainsText("Button clicked!"));
+    }
+
+    @Test
+    public void testRemoveComponentFromHeader() throws Exception {
+        openTestURL();
+        selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+                "Widget Header");
+        selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+                "Text Header");
+        assertTrue("No notifications should've been shown",
+                !$(NotificationElement.class).exists());
+        assertEquals("Header should've been reverted back to text header",
+                "text header",
+                getGridElement().getHeaderCell(0, 1).getText().toLowerCase());
+    }
+
+    @Test
+    public void testColumnHidingToggleCaption_settingWidgetToHeader_toggleCaptionStays() {
+        toggleColumnHidable(1);
+        getSidebarOpenButton().click();
+        assertEquals("column 1",
+                getGridElement().getHeaderCell(0, 1).getText().toLowerCase());
+        assertEquals("Column 1", getColumnHidingToggle(1).getText());
+
+        selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+                "Widget Header");
+
+        getSidebarOpenButton().click();
+        assertEquals("Column 1", getColumnHidingToggle(1).getText());
+    }
+
+    private void toggleColumnHidable(int index) {
+        selectMenuPath("Component", "Columns", "Column " + index, "Hidable");
+    }
+
     protected static void assertText(String expected, GridCellElement e) {
         // TBE.getText returns "" if the element is scrolled out of view
         String actual = e.findElement(By.tagName("div"))