Vojtech Szocs has uploaded a new change for review. Change subject: webadmin,userportal: Support column resizing for all tables ......................................................................
webadmin,userportal: Support column resizing for all tables This patch moves column resize implementation out of AbstractActionTable and into dedicated table widget, ColumnResizeCellTable. 1. All custom CellTable widgets now inherit from ColumnResizeCellTable: - ElementIdCellTable [ActionCellTable + EntityModelCellTable] - IVdcQueryableCellTable 2. ColumnResizeCellTable now contains enableColumnResizing() method, plus some other related/useful methods 3. Columns added to ColumnResizeCellTable automatically benefit from tooltip displayed when the given header has its content (text) truncated, i.e. after shrinking the column or with long header text 4. AbstractActionTable now just delegates to table/tableHeader widgets, plus code to synchronize both tables (during column resize, etc.) 5. AbstractCellWithTooltip now contains common tooltip-related logic used by different subclasses: - SafeHtmlCellWithTooltip [resizable + non-resizable header cells] - TextCellWithTooltip [textual body cells] 6. Minor ElementIdCellTable improvement: call configureElementId() for every insertColumn() call for better consistency AbstractCellWithTooltip does two kinds of content overflow detection: (a) scrollWidth with temporary CSS 'overflow:auto' (b) clientHeight with temporary CSS 'whiteSpace:normal' In practice, (a) is used for both header and body cells. In Firefox, header cells (SafeHtmlCellWithTooltip) use <div> with 'display:block' to work around Firefox-specific issue with scrollWidth. Change-Id: Ic69b597d7c5f2330cef5a93bc8812494af731734 Bug-Url: https://bugzilla.redhat.com/956378 Signed-off-by: Vojtech Szocs <vsz...@redhat.com> --- M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/EntityModelCellTable.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/IVdcQueryableCellTable.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/AbstractActionTable.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/ElementIdCellTable.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java A frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractCellWithTooltip.java A frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/SafeHtmlCellWithTooltip.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/TextCellWithTooltip.java A frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ColumnResizeCellTable.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeader.java D frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeaderCell.java 11 files changed, 490 insertions(+), 228 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/41/14341/1 diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/EntityModelCellTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/EntityModelCellTable.java index 52ef6e4..e41c97b 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/EntityModelCellTable.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/EntityModelCellTable.java @@ -325,12 +325,6 @@ super.onLoadingStateChanged(state); } - @Override - public void insertColumn(int beforeIndex, Column col, Header header, Header footer) { - super.insertColumn(beforeIndex, col, header, footer); - configureElementId(col); - } - @SuppressWarnings("unchecked") @Override public void edit(M object) { diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/IVdcQueryableCellTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/IVdcQueryableCellTable.java index 7d24033..04cdeb7 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/IVdcQueryableCellTable.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/editor/IVdcQueryableCellTable.java @@ -8,6 +8,7 @@ import org.ovirt.engine.ui.common.widget.HasEditorDriver; import org.ovirt.engine.ui.common.widget.table.column.RadioboxCell; import org.ovirt.engine.ui.common.widget.table.header.SelectAllCheckBoxHeader; +import org.ovirt.engine.ui.common.widget.table.resize.ColumnResizeCellTable; import org.ovirt.engine.ui.uicommonweb.models.ListModel; import org.ovirt.engine.ui.uicompat.Event; import org.ovirt.engine.ui.uicompat.EventArgs; @@ -26,7 +27,7 @@ import com.google.gwt.view.client.SelectionModel; import com.google.gwt.view.client.SingleSelectionModel; -public class IVdcQueryableCellTable<IVdcQueryable, M extends ListModel> extends CellTable<IVdcQueryable> implements HasEditorDriver<M> { +public class IVdcQueryableCellTable<IVdcQueryable, M extends ListModel> extends ColumnResizeCellTable<IVdcQueryable> implements HasEditorDriver<M> { private static final int DEFAULT_PAGESIZE = 1000; private static final int CHECK_COLUMN_WIDTH = 27; @@ -36,7 +37,7 @@ private M listModel; public IVdcQueryableCellTable() { - super(DEFAULT_PAGESIZE, (Resources) GWT.create(PopupTableResources.class)); + super(DEFAULT_PAGESIZE, (CellTable.Resources) GWT.create(PopupTableResources.class)); SingleSelectionModel<IVdcQueryable> selectionModel = new SingleSelectionModel<IVdcQueryable>(); setSelectionModel(selectionModel); diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/AbstractActionTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/AbstractActionTable.java index de1db2f..cd95534 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/AbstractActionTable.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/AbstractActionTable.java @@ -1,7 +1,6 @@ package org.ovirt.engine.ui.common.widget.table; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.ovirt.engine.ui.common.CommonApplicationConstants; @@ -10,22 +9,16 @@ import org.ovirt.engine.ui.common.uicommon.model.SearchableTableModelProvider; import org.ovirt.engine.ui.common.widget.action.AbstractActionPanel; import org.ovirt.engine.ui.common.widget.label.NoItemsLabel; -import org.ovirt.engine.ui.common.widget.table.column.EmptyColumn; -import org.ovirt.engine.ui.common.widget.table.resize.HasResizableColumns; -import org.ovirt.engine.ui.common.widget.table.resize.ResizableHeader; import org.ovirt.engine.ui.uicommonweb.UICommand; import org.ovirt.engine.ui.uicommonweb.models.Model; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableElement; import com.google.gwt.dom.client.TableRowElement; -import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.dom.client.DoubleClickEvent; @@ -36,18 +29,14 @@ import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.shared.EventBus; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.cellview.client.CellTable.Resources; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.AsyncHandler; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; -import com.google.gwt.user.cellview.client.Header; import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.user.cellview.client.RowStyles; -import com.google.gwt.user.cellview.client.SafeHtmlHeader; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; @@ -68,17 +57,14 @@ * <li>{@link #actionPanel} widget into which action button widgets will be rendered * <li>{@link #prevPageButton} widget representing the "previous page" button * <li>{@link #nextPageButton} widget representing the "next page" button - * <li>{@link #refreshPageButton} widget representing the "refresh current page" button * <li>{@link #tableContainer} widget for displaying the actual table + * <li>{@link #tableHeaderContainer} widget for displaying the table header * </ul> * * @param <T> * Table row data type. */ -public abstract class AbstractActionTable<T> extends AbstractActionPanel<T> implements ActionTable<T>, HasResizableColumns<T> { - - // Minimum width of a column used with column resizing, in pixels - private static final int RESIZE_MINIMUM_COLUMN_WIDTH = 30; +public abstract class AbstractActionTable<T> extends AbstractActionPanel<T> implements ActionTable<T> { // Click event type private static final String CLICK = "click"; //$NON-NLS-1$ @@ -97,9 +83,6 @@ @UiField public SimplePanel tableHeaderContainer; - @UiField - public SimplePanel tableOverhead; - private final OrderedMultiSelectionModel<T> selectionModel; @WithElementId("content") @@ -112,11 +95,6 @@ private boolean multiSelectionDisabled; private final int[] mousePosition = new int[2]; - - private boolean columnResizingEnabled = false; - - // Reference to an empty, no-width column used with resizable columns - private Column<T, ?> emptyNoWidthColumn; // Table container's horizontal scroll position, used to align table header with main table private int tableContainerHorizontalScrollPosition = 0; @@ -137,8 +115,9 @@ selectionModel.setMultiSelectEnabled(event.getCtrlKey()); selectionModel.setMultiRangeSelectEnabled(event.getShiftKey()); } + // Remove focus from the table so refreshes won't try to focus on the - // selected row. This important when the user has scrolled the selected + // selected row. This is important when the user has scrolled the selected // row off the screen, we don't want the browser to scroll back. table.setFocus(false); super.onBrowserEvent2(event); @@ -191,7 +170,7 @@ }; - // Can't do this in the onBrowserEvent, as the GWT Cell table doesn't support double click. + // Can't do this in the onBrowserEvent, as GWT CellTable doesn't support double click. this.table.addDomHandler(new DoubleClickHandler() { @Override public void onDoubleClick(DoubleClickEvent event) { @@ -205,7 +184,26 @@ }, DoubleClickEvent.getType()); // Create table header row - this.tableHeader = new ActionCellTable<T>(dataProvider, headerResources); + this.tableHeader = new ActionCellTable<T>(dataProvider, headerResources) { + + @Override + public void onResizeEnd(Column<T, ?> column, Element headerElement) { + super.onResizeEnd(column, headerElement); + + // Redraw main table + table.redraw(); + } + + @Override + public void resizeColumn(Column<T, ?> column, int newWidth) { + super.resizeColumn(column, newWidth); + + // Resize the corresponding column in main table + table.resizeColumn(column, newWidth); + } + + }; + this.tableHeader.setRowData(new ArrayList<T>()); this.showDefaultHeader = headerResources == null; @@ -426,81 +424,21 @@ getDataProvider().goForward(); } - void addColumn(Column<T, ?> column, Header<?> header) { - table.addColumn(column, header); - tableHeader.addColumn(column, header); - - // Configure column content element ID options - table.configureElementId(column); - - // Resizable columns require empty, no-width column to be the last table column - if (columnResizingEnabled) { - if (emptyNoWidthColumn != null) { - table.removeColumn(emptyNoWidthColumn); - tableHeader.removeColumn(emptyNoWidthColumn); - } - - emptyNoWidthColumn = new EmptyColumn<T>(); - table.addColumn(emptyNoWidthColumn); - tableHeader.addColumn(emptyNoWidthColumn); - } - } - void setColumnWidth(Column<T, ?> column, String width) { table.setColumnWidth(column, width); tableHeader.setColumnWidth(column, width); - - // Update cell widths - int columnIndex = table.getColumnIndex(column); - for (TableCellElement cell : getTableBodyCells(columnIndex)) { - cell.getStyle().setProperty("width", width); //$NON-NLS-1$ - } - for (TableCellElement cell : getTableHeaderCells(columnIndex)) { - cell.getStyle().setProperty("width", width); //$NON-NLS-1$ - } - } - - List<TableCellElement> getTableBodyCells(int columnIndex) { - TableElement tableElement = table.getElement().cast(); - TableSectionElement firstTBodyElement = tableElement.getTBodies().getItem(0); - return firstTBodyElement != null ? getCells(firstTBodyElement.getRows(), columnIndex) - : Collections.<TableCellElement> emptyList(); - } - - List<TableCellElement> getTableHeaderCells(int columnIndex) { - Element tableHeaderElement = showDefaultHeader ? table.getElement() : tableHeader.getElement(); - TableElement tableHeaderElementCast = tableHeaderElement.cast(); - TableSectionElement tHeadElement = tableHeaderElementCast.getTHead(); - return tHeadElement != null ? getCells(tHeadElement.getRows(), columnIndex) - : Collections.<TableCellElement> emptyList(); - } - - List<TableCellElement> getCells(NodeList<TableRowElement> rows, int columnIndex) { - List<TableCellElement> result = new ArrayList<TableCellElement>(); - for (int i = 0; i < rows.getLength(); i++) { - TableCellElement cell = rows.getItem(i).getCells().getItem(columnIndex); - if (cell != null) { - result.add(cell); - } - } - return result; - } - - Header<?> getHeader(Column<T, ?> column, String headerTextOrHtml, boolean allowHtml) { - SafeHtml text = allowHtml ? SafeHtmlUtils.fromSafeConstant(headerTextOrHtml) - : SafeHtmlUtils.fromString(headerTextOrHtml); - return columnResizingEnabled ? new ResizableHeader<T>(text, column, this) : new SafeHtmlHeader(text); } /** - * Adds a new table column, without specifying the column width. + * Adds a new column, without specifying column width. */ public void addColumn(Column<T, ?> column, String headerText) { - addColumn(column, getHeader(column, headerText, false)); + table.addColumn(column, headerText); + tableHeader.addColumn(column, headerText); } /** - * Adds a new table column, using the given column width. + * Adds a new column, setting the column width. */ public void addColumn(Column<T, ?> column, String headerText, String width) { addColumn(column, headerText); @@ -508,24 +446,25 @@ } /** - * Adds a new table column with HTML in the header text, without specifying the column width. + * Adds a new column with HTML header text, without specifying column width. * <p> - * {@code headerHtml} must honor the {@link SafeHtml} contract as specified in - * {@link SafeHtmlUtils#fromSafeConstant(String) fromSafeConstant}. + * {@code headerHtml} must honor the SafeHtml contract as specified in + * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant(String) SafeHtmlUtils.fromSafeConstant}. */ public void addColumnWithHtmlHeader(Column<T, ?> column, String headerHtml) { - addColumn(column, getHeader(column, headerHtml, true)); + table.addColumnWithHtmlHeader(column, headerHtml); + tableHeader.addColumnWithHtmlHeader(column, headerHtml); } /** - * Adds a new table column with HTML in the header text, using the given column width. + * Adds a new column with HTML header text, setting the column width. * <p> - * {@code headerHtml} must honor the {@link SafeHtml} contract as specified in - * {@link SafeHtmlUtils#fromSafeConstant(String) fromSafeConstant}. + * {@code headerHtml} must honor the SafeHtml contract as specified in + * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant(String) SafeHtmlUtils.fromSafeConstant}. */ public void addColumnWithHtmlHeader(Column<T, ?> column, String headerHtml, String width) { - addColumnWithHtmlHeader(column, headerHtml); - setColumnWidth(column, width); + table.addColumnWithHtmlHeader(column, headerHtml, width); + tableHeader.addColumnWithHtmlHeader(column, headerHtml, width); } /** @@ -540,28 +479,18 @@ * Ensures that the given column is added (or removed), unless it's already present (or absent). */ public void ensureColumnPresent(Column<T, ?> column, String headerText, boolean present) { - ensureColumnPresent(column, headerText, present, null); + table.ensureColumnPresent(column, headerText, present); + tableHeader.ensureColumnPresent(column, headerText, present); } /** * Ensures that the given column is added (or removed), unless it's already present (or absent). * <p> - * This method also sets the width of the column in case the column needs to be added. + * This method also sets the column width in case the column needs to be added. */ public void ensureColumnPresent(Column<T, ?> column, String headerText, boolean present, String width) { - if (present) { - if (table.getColumnIndex(column) != -1) { - removeColumn(column); - } - - if (width == null) { - addColumnWithHtmlHeader(column, headerText); - } else { - addColumnWithHtmlHeader(column, headerText, width); - } - } else if (!present && table.getColumnIndex(column) != -1) { - removeColumn(column); - } + table.ensureColumnPresent(column, headerText, present, width); + tableHeader.ensureColumnPresent(column, headerText, present, width); } /** @@ -569,38 +498,15 @@ * <p> * This method should be called before calling any {@code addColumn} methods. * <p> - * After calling this method, each column must have an explicit width defined in PX units. + * <em>After calling this method, each column must have an explicit width defined in PX units, otherwise the resize + * behavior will not function properly.</em> */ public void enableColumnResizing() { // Column resizing is supported only when the tableHeader widget is visible - columnResizingEnabled = !showDefaultHeader; - } - - @Override - public void onResizeStart(Column<T, ?> column, Element headerElement) { - headerElement.getStyle().setBackgroundColor("#D6DCFF"); //$NON-NLS-1$ - } - - @Override - public void onResizeEnd(Column<T, ?> column, Element headerElement) { - headerElement.getStyle().clearBackgroundColor(); - - // Redraw main table - table.redraw(); - - // Note: DO NOT redraw tableHeader, as this would cause header cell elements - // to be re-created, and any event handlers attached to original header cell - // elements would be lost - } - - @Override - public void resizeColumn(Column<T, ?> column, int newWidth) { - setColumnWidth(column, newWidth + "px"); //$NON-NLS-1$ - } - - @Override - public int getMinimumColumnWidth(Column<T, ?> column) { - return RESIZE_MINIMUM_COLUMN_WIDTH; + if (!showDefaultHeader) { + table.enableColumnResizing(); + tableHeader.enableColumnResizing(); + } } @Override diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/ElementIdCellTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/ElementIdCellTable.java index 6f28b28..f96e213 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/ElementIdCellTable.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/ElementIdCellTable.java @@ -2,9 +2,11 @@ import org.ovirt.engine.ui.common.idhandler.HasElementId; import org.ovirt.engine.ui.common.widget.table.column.ColumnWithElementId; +import org.ovirt.engine.ui.common.widget.table.resize.ColumnResizeCellTable; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ProvidesKey; @@ -12,8 +14,11 @@ /** * A {@link CellTable} which adds support for configuring column DOM element IDs through {@link ColumnWithElementId} * interface. + * + * @param <T> + * Table row data type. */ -public abstract class ElementIdCellTable<T> extends CellTable<T> implements HasElementId { +public class ElementIdCellTable<T> extends ColumnResizeCellTable<T> implements HasElementId { private String elementId = DOM.createUniqueId(); @@ -47,6 +52,12 @@ super(keyProvider); } + @Override + public void insertColumn(int beforeIndex, Column<T, ?> col, Header<?> header, Header<?> footer) { + super.insertColumn(beforeIndex, col, header, footer); + configureElementId(col); + } + /** * Sets up the element ID for the given column, if the column implements {@link ColumnWithElementId}. */ @@ -73,4 +84,5 @@ public String getElementId() { return elementId; } + } diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java index 17f3bca..6bb7bc6 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java @@ -22,6 +22,7 @@ import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.MenuItem; +import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; public class SimpleActionTable<T> extends AbstractActionTable<T> { @@ -34,6 +35,9 @@ Style style; @UiField + SimplePanel tableOverhead; + + @UiField HTMLPanel barPanel; @UiField(provided = true) diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractCellWithTooltip.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractCellWithTooltip.java new file mode 100644 index 0000000..cbf4b0c --- /dev/null +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractCellWithTooltip.java @@ -0,0 +1,89 @@ +package org.ovirt.engine.ui.common.widget.table.column; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.cell.client.ValueUpdater; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; + +/** + * An {@link AbstractCell} that provides tooltip in case the content doesn't fit the parent element. + * <p> + * Make sure to specify {@code mouseover} within cell consumed events, otherwise the tooltip feature will not work. + * + * @param <C> + * Cell data type. + */ +public abstract class AbstractCellWithTooltip<C> extends AbstractCell<C> { + + public AbstractCellWithTooltip(String... consumedEvents) { + super(consumedEvents); + } + + @Override + public void onBrowserEvent(Context context, Element parent, C value, + NativeEvent event, ValueUpdater<C> valueUpdater) { + super.onBrowserEvent(context, parent, value, event, valueUpdater); + + // Skip events other than 'mouseover' + if (!"mouseover".equals(event.getType())) { //$NON-NLS-1$ + return; + } + + if (value != null && showTooltip(parent, value)) { + parent.setTitle(getTooltip(value)); + } else { + parent.setTitle(""); //$NON-NLS-1$ + } + } + + /** + * Returns tooltip to show for the given value. + */ + protected abstract String getTooltip(C value); + + /** + * Returns {@code true} if tooltip should be shown for the given {@code parent} element. + */ + protected boolean showTooltip(Element parent, C value) { + return contentOverflows(parent); + } + + /** + * Returns {@code true} when the content of the given {@code parent} element overflows its area. + */ + protected boolean contentOverflows(Element parent) { + return parent != null && (detectOverflowUsingScrollWidth(parent) || detectOverflowUsingClientHeight(parent)); + } + + /** + * Uses scrollWidth with temporary CSS 'overflow:auto' to detect horizontal overflow. + */ + boolean detectOverflowUsingScrollWidth(Element parent) { + int scrollWidthBefore = parent.getScrollWidth(); + String overflowValue = parent.getStyle().getProperty("overflow"); //$NON-NLS-1$ + parent.getStyle().setProperty("overflow", "auto"); //$NON-NLS-1$ //$NON-NLS-2$ + + int scrollWidthAfter = parent.getScrollWidth(); + parent.getStyle().setProperty("overflow", overflowValue); //$NON-NLS-1$ + + return scrollWidthAfter > scrollWidthBefore; + } + + /** + * Uses clientHeight with temporary CSS 'whiteSpace:normal' to detect vertical overflow. + * <p> + * This is necessary due to some browsers (Firefox) having issues with scrollWidth (e.g. elements with CSS 'display' + * other than 'block' have incorrect scrollWidth value). + */ + boolean detectOverflowUsingClientHeight(Element parent) { + int clientHeightBefore = parent.getClientHeight(); + String whiteSpaceValue = parent.getStyle().getProperty("whiteSpace"); //$NON-NLS-1$ + parent.getStyle().setProperty("whiteSpace", "normal"); //$NON-NLS-1$ //$NON-NLS-2$ + + int clientHeightAfter = parent.getClientHeight(); + parent.getStyle().setProperty("whiteSpace", whiteSpaceValue); //$NON-NLS-1$ + + return clientHeightAfter > clientHeightBefore; + } + +} diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/SafeHtmlCellWithTooltip.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/SafeHtmlCellWithTooltip.java new file mode 100644 index 0000000..2f89c97 --- /dev/null +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/SafeHtmlCellWithTooltip.java @@ -0,0 +1,37 @@ +package org.ovirt.engine.ui.common.widget.table.column; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; + +public class SafeHtmlCellWithTooltip extends AbstractCellWithTooltip<SafeHtml> { + + public SafeHtmlCellWithTooltip() { + super("mouseover"); //$NON-NLS-1$ + } + + public SafeHtmlCellWithTooltip(String... consumedEvents) { + super(consumedEvents); + } + + @Override + public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) { + if (value != null) { + sb.appendHtmlConstant("<div style='display:block'>"); //$NON-NLS-1$ + sb.append(value); + sb.appendHtmlConstant("</div>"); //$NON-NLS-1$ + } + } + + @Override + protected boolean contentOverflows(Element parent) { + // Perform content overflow detection on child DIV element + return super.contentOverflows(parent.getFirstChildElement()); + } + + @Override + protected String getTooltip(SafeHtml value) { + return value.asString(); + } + +} diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/TextCellWithTooltip.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/TextCellWithTooltip.java index 75a7f70..ba74330 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/TextCellWithTooltip.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/TextCellWithTooltip.java @@ -2,45 +2,42 @@ import org.ovirt.engine.ui.common.utils.ElementIdUtils; -import com.google.gwt.cell.client.AbstractSafeHtmlCell; -import com.google.gwt.cell.client.Cell; -import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.google.gwt.text.shared.SimpleSafeHtmlRenderer; import com.google.gwt.user.client.DOM; /** - * A {@link Cell} used to render text, providing a tooltip in case the text does not fit within the parent element. + * A Cell used to render text, providing tooltip in case the content doesn't fit the parent element. * * @see com.google.gwt.cell.client.TextCell */ -public class TextCellWithTooltip extends AbstractSafeHtmlCell<String> { +public class TextCellWithTooltip extends AbstractCellWithTooltip<String> { interface CellTemplate extends SafeHtmlTemplates { + @Template("<div id=\"{0}\">{1}</div>") SafeHtml textContainer(String id, SafeHtml text); + } public static final int UNLIMITED_LENGTH = -1; private static final String TOO_LONG_TEXT_POSTFIX = "..."; //$NON-NLS-1$ - // DOM element ID settings for the text container element + // DOM element ID settings for text container element private String elementIdPrefix = DOM.createUniqueId(); private String columnId; - // Text longer than this value will be shortened, providing a tooltip with original text + // Text longer than this value will be shortened, providing tooltip with original text private final int maxTextLength; private static CellTemplate template; public TextCellWithTooltip(int maxTextLength) { - super(SimpleSafeHtmlRenderer.getInstance(), "mouseover"); //$NON-NLS-1$ + super("mouseover"); //$NON-NLS-1$ this.maxTextLength = maxTextLength; // Delay cell template creation until the first time it's needed @@ -58,67 +55,47 @@ } @Override - public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) { - String rawData = value != null ? value.asString() : ""; //$NON-NLS-1$ + public void render(Context context, String value, SafeHtmlBuilder sb) { + if (value != null) { + SafeHtml escapedValue = getEscapedValue(value); + SafeHtml renderedValue = getRenderedValue(escapedValue); - sb.append(template.textContainer( - ElementIdUtils.createTableCellElementId(elementIdPrefix, columnId, context), - getRenderedValue(rawData))); + sb.append(template.textContainer( + ElementIdUtils.createTableCellElementId(elementIdPrefix, columnId, context), + renderedValue)); + } } @Override - public void onBrowserEvent(Cell.Context context, Element parent, - String value, NativeEvent event, ValueUpdater<String> valueUpdater) { - super.onBrowserEvent(context, parent, value, event, valueUpdater); + protected String getTooltip(String value) { + return getEscapedValue(value).asString(); + } - // Ignore events other than 'mouseover' - if (!"mouseover".equals(event.getType())) { //$NON-NLS-1$ - return; - } + @Override + protected boolean showTooltip(Element parent, String value) { + // Enforce tooltip when the presented text gets truncated due to maxTextLength + SafeHtml escapedValue = getEscapedValue(value); + SafeHtml renderedValue = getRenderedValue(escapedValue); + return super.showTooltip(parent, value) || !escapedValue.equals(renderedValue); + } - // Enforce tooltip when the presented text doesn't match original value - SafeHtml safeValue = value != null ? SafeHtmlUtils.fromSafeConstant(value) : null; - boolean forceTooltip = (safeValue != null && !safeValue.equals(getRenderedValue(value))); - - // If the parent element content overflows its area, provide tooltip to the element - if (forceTooltip || contentOverflows(parent)) { - parent.setTitle(value); - } else { - parent.setTitle(""); //$NON-NLS-1$ - } + SafeHtml getEscapedValue(String value) { + return SafeHtmlUtils.fromString(value); } /** - * Returns the text value to be rendered by this cell. + * Returns the (possibly truncated) value to be rendered by this cell. */ - SafeHtml getRenderedValue(final String text) { - String result = text; + SafeHtml getRenderedValue(SafeHtml value) { + String result = value.asString(); // Check if the text needs to be shortened - if (maxTextLength > 0 && text.length() > maxTextLength) { + if (maxTextLength > 0 && result.length() > maxTextLength) { result = result.substring(0, Math.max(maxTextLength - TOO_LONG_TEXT_POSTFIX.length(), 0)); result = result + TOO_LONG_TEXT_POSTFIX; } return SafeHtmlUtils.fromSafeConstant(result); - } - - /** - * Returns {@code true} when the content of the given element overflows the element's content area, {@code false} - * otherwise. - */ - boolean contentOverflows(Element elm) { - String overflowValue = elm.getStyle().getOverflow(); - - // Temporarily allow element content to overflow through scroll bars - elm.getStyle().setProperty("overflow", "auto"); //$NON-NLS-1$ //$NON-NLS-2$ - boolean overflowX = elm.getScrollWidth() > elm.getClientWidth(); - boolean overflowY = elm.getScrollHeight() > elm.getClientHeight(); - - // Revert to the original overflow value - elm.getStyle().setProperty("overflow", overflowValue); //$NON-NLS-1$ - - return overflowX || overflowY; } } diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ColumnResizeCellTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ColumnResizeCellTable.java new file mode 100644 index 0000000..400bccf --- /dev/null +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ColumnResizeCellTable.java @@ -0,0 +1,258 @@ +package org.ovirt.engine.ui.common.widget.table.resize; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.ovirt.engine.ui.common.widget.table.column.EmptyColumn; +import org.ovirt.engine.ui.common.widget.table.column.SafeHtmlCellWithTooltip; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.ProvidesKey; + +/** + * A {@link CellTable} that supports resizing its columns using mouse. + * <p> + * Column resize feature is disabled by default, use {@link #enableColumnResizing} to enable it. + * + * @param <T> + * Table row data type. + */ +public class ColumnResizeCellTable<T> extends CellTable<T> implements HasResizableColumns<T> { + + private static final int DEFAULT_MINIMUM_COLUMN_WIDTH = 30; + + private int minimumColumnWidth = DEFAULT_MINIMUM_COLUMN_WIDTH; + + // Empty, no-width column used with resizable columns feature + // that occupies remaining horizontal space within the table + private Column<T, ?> emptyNoWidthColumn; + + private boolean columnResizingEnabled = false; + + public ColumnResizeCellTable() { + super(); + } + + public ColumnResizeCellTable(int pageSize, ProvidesKey<T> keyProvider) { + super(pageSize, keyProvider); + } + + public ColumnResizeCellTable(int pageSize, CellTable.Resources resources, + ProvidesKey<T> keyProvider, Widget loadingIndicator) { + super(pageSize, resources, keyProvider, loadingIndicator); + } + + public ColumnResizeCellTable(int pageSize, CellTable.Resources resources, + ProvidesKey<T> keyProvider) { + super(pageSize, resources, keyProvider); + } + + public ColumnResizeCellTable(int pageSize, CellTable.Resources resources) { + super(pageSize, resources); + } + + public ColumnResizeCellTable(int pageSize) { + super(pageSize); + } + + public ColumnResizeCellTable(ProvidesKey<T> keyProvider) { + super(keyProvider); + } + + /** + * {@inheritDoc} + * <p> + * When calling this method, consider using a header that supports displaying tooltip in case the header content + * doesn't fit the header element. + */ + @Override + public void addColumn(Column<T, ?> column, Header<?> header) { + super.addColumn(column, header); + + if (columnResizingEnabled) { + if (emptyNoWidthColumn != null) { + removeColumn(emptyNoWidthColumn); + } + + // Add empty, no-width column as the last column + emptyNoWidthColumn = new EmptyColumn<T>(); + addColumn(emptyNoWidthColumn); + } + } + + /** + * Adds a new column, without specifying column width. + */ + @Override + public void addColumn(Column<T, ?> column, String headerText) { + addColumn(column, createHeader(column, headerText, false)); + } + + /** + * Adds a new column, setting the column width. + */ + public void addColumnAndSetWidth(Column<T, ?> column, String headerText, String width) { + addColumn(column, headerText); + setColumnWidth(column, width); + } + + /** + * Adds a new column with HTML header text, without specifying column width. + * <p> + * {@code headerHtml} must honor the SafeHtml contract as specified in + * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant(String) SafeHtmlUtils.fromSafeConstant}. + */ + public void addColumnWithHtmlHeader(Column<T, ?> column, String headerHtml) { + addColumn(column, createHeader(column, headerHtml, true)); + } + + /** + * Adds a new column with HTML header text, setting the column width. + * <p> + * {@code headerHtml} must honor the SafeHtml contract as specified in + * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant(String) SafeHtmlUtils.fromSafeConstant}. + */ + public void addColumnWithHtmlHeader(Column<T, ?> column, String headerHtml, String width) { + addColumnWithHtmlHeader(column, headerHtml); + setColumnWidth(column, width); + } + + Header<?> createHeader(Column<T, ?> column, String headerTextOrHtml, boolean allowHtml) { + SafeHtml text = allowHtml ? SafeHtmlUtils.fromSafeConstant(headerTextOrHtml) + : SafeHtmlUtils.fromString(headerTextOrHtml); + return columnResizingEnabled ? new ResizableHeader<T>(text, column, this) : createSafeHtmlHeader(text); + } + + Header<?> createSafeHtmlHeader(final SafeHtml text) { + return new Header<SafeHtml>(new SafeHtmlCellWithTooltip()) { + @Override + public SafeHtml getValue() { + return text; + } + }; + } + + /** + * Ensures that the given column is added (or removed), unless it's already present (or absent). + */ + public void ensureColumnPresent(Column<T, ?> column, String headerText, boolean present) { + ensureColumnPresent(column, headerText, present, null); + } + + /** + * Ensures that the given column is added (or removed), unless it's already present (or absent). + * <p> + * This method also sets the column width in case the column needs to be added. + */ + public void ensureColumnPresent(Column<T, ?> column, String headerText, boolean present, String width) { + if (present) { + // Remove the column first + if (getColumnIndex(column) != -1) { + removeColumn(column); + } + + // Re-add the column + if (width == null) { + addColumnWithHtmlHeader(column, headerText); + } else { + addColumnWithHtmlHeader(column, headerText, width); + } + } else if (!present && getColumnIndex(column) != -1) { + // Remove the column + removeColumn(column); + } + } + + @Override + public void setColumnWidth(Column<T, ?> column, String width) { + super.setColumnWidth(column, width); + + if (columnResizingEnabled) { + int columnIndex = getColumnIndex(column); + TableElement tableElement = getElement().cast(); + + // Update body and header cell widths + for (TableCellElement cell : getTableBodyCells(tableElement, columnIndex)) { + cell.getStyle().setProperty("width", width); //$NON-NLS-1$ + } + for (TableCellElement cell : getTableHeaderCells(tableElement, columnIndex)) { + cell.getStyle().setProperty("width", width); //$NON-NLS-1$ + } + } + } + + List<TableCellElement> getTableBodyCells(TableElement tableElement, int columnIndex) { + TableSectionElement firstTBodyElement = tableElement.getTBodies().getItem(0); + return firstTBodyElement != null ? getCells(firstTBodyElement.getRows(), columnIndex) + : Collections.<TableCellElement> emptyList(); + } + + List<TableCellElement> getTableHeaderCells(TableElement tableElement, int columnIndex) { + TableSectionElement tHeadElement = tableElement.getTHead(); + return tHeadElement != null ? getCells(tHeadElement.getRows(), columnIndex) + : Collections.<TableCellElement> emptyList(); + } + + List<TableCellElement> getCells(NodeList<TableRowElement> rows, int columnIndex) { + List<TableCellElement> result = new ArrayList<TableCellElement>(); + for (int i = 0; i < rows.getLength(); i++) { + TableCellElement cell = rows.getItem(i).getCells().getItem(columnIndex); + if (cell != null) { + result.add(cell); + } + } + return result; + } + + /** + * Allows table columns to be resized by dragging their right-hand border using mouse. + * <p> + * This method should be called before calling any {@code addColumn} methods. + * <p> + * <em>After calling this method, each column must have an explicit width defined in PX units, otherwise the resize + * behavior will not function properly.</em> + */ + public void enableColumnResizing() { + columnResizingEnabled = true; + } + + @Override + public void onResizeStart(Column<T, ?> column, Element headerElement) { + headerElement.getStyle().setBackgroundColor("#D6DCFF"); //$NON-NLS-1$ + } + + @Override + public void onResizeEnd(Column<T, ?> column, Element headerElement) { + headerElement.getStyle().clearBackgroundColor(); + + // Redraw the table + redraw(); + } + + @Override + public void resizeColumn(Column<T, ?> column, int newWidth) { + setColumnWidth(column, newWidth + "px"); //$NON-NLS-1$ + } + + @Override + public int getMinimumColumnWidth(Column<T, ?> column) { + return minimumColumnWidth; + } + + public void setMinimumColumnWidth(int minimumColumnWidth) { + this.minimumColumnWidth = minimumColumnWidth; + } + +} diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeader.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeader.java index 7d3c5d7..19cea6f 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeader.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeader.java @@ -1,5 +1,7 @@ package org.ovirt.engine.ui.common.widget.table.resize; +import org.ovirt.engine.ui.common.widget.table.column.SafeHtmlCellWithTooltip; + import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; @@ -26,7 +28,7 @@ private final HasResizableColumns<T> table; public ResizableHeader(SafeHtml text, Column<T, ?> column, HasResizableColumns<T> table) { - super(new ResizableHeaderCell()); + super(new SafeHtmlCellWithTooltip("click", "mousedown", "mousemove", "mouseover")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ this.text = text; this.column = column; this.table = table; @@ -39,6 +41,8 @@ @Override public void onBrowserEvent(Context context, Element target, NativeEvent event) { + super.onBrowserEvent(context, target, event); + int clientX = event.getClientX(); int absoluteLeft = target.getAbsoluteLeft(); int offsetWidth = target.getOffsetWidth(); diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeaderCell.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeaderCell.java deleted file mode 100644 index efe4a48..0000000 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/resize/ResizableHeaderCell.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.ovirt.engine.ui.common.widget.table.resize; - -import com.google.gwt.cell.client.AbstractCell; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; - -public class ResizableHeaderCell extends AbstractCell<SafeHtml> { - - public ResizableHeaderCell() { - super("click", "dblclick", "mousedown", "mousemove"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ - } - - @Override - public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) { - if (value != null) { - sb.append(value); - } - } - -} -- To view, visit http://gerrit.ovirt.org/14341 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ic69b597d7c5f2330cef5a93bc8812494af731734 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Vojtech Szocs <vsz...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches