Change-Id: Ifa976fa4be1258fd35999de17775da70afedb2a8tags/7.5.0.beta1
@@ -133,4 +133,17 @@ | |||
z-index: 1; | |||
} | |||
.#{$primaryStyleName}-spacer { | |||
position: absolute; | |||
display: block; | |||
background-color: $background-color; | |||
> td { | |||
width: 100%; | |||
height: 100%; | |||
@include box-sizing(border-box); | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
$v-grid-border: 1px solid #ddd !default; | |||
$v-grid-border-size: 1px !default; | |||
$v-grid-border: $v-grid-border-size solid #ddd !default; | |||
$v-grid-cell-vertical-border: $v-grid-border !default; | |||
$v-grid-cell-horizontal-border: $v-grid-cell-vertical-border !default; | |||
$v-grid-cell-focused-border: 1px solid !default; | |||
@@ -14,6 +15,7 @@ $v-grid-row-focused-background-color: null !default; | |||
$v-grid-header-row-height: null !default; | |||
$v-grid-header-font-size: $v-font-size !default; | |||
$v-grid-header-background-color: $v-grid-row-background-color !default; | |||
$v-grid-header-drag-marked-color: $v-grid-row-selected-background-color !default; | |||
$v-grid-footer-row-height: $v-grid-header-row-height !default; | |||
$v-grid-footer-font-size: $v-grid-header-font-size !default; | |||
@@ -23,6 +25,12 @@ $v-grid-cell-padding-horizontal: 5px !default; | |||
$v-grid-editor-background-color: $v-grid-row-background-color !default; | |||
$v-grid-details-marker-width: 2px !default; | |||
$v-grid-details-marker-color: $v-grid-row-selected-background-color !default; | |||
$v-grid-details-border-top: $v-grid-cell-horizontal-border !default; | |||
$v-grid-details-border-top-stripe: $v-grid-cell-horizontal-border !default; | |||
$v-grid-details-border-bottom: 1px solid darken($v-grid-row-stripe-background-color, 10%) !default; | |||
$v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-color, 10%) !default; | |||
@import "../escalator/escalator"; | |||
@@ -51,6 +59,103 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; | |||
.#{$primaryStyleName}-tablewrapper { | |||
border: $v-grid-border; | |||
} | |||
// Column drag and drop elements | |||
.#{$primaryStyleName} .header-drag-table { | |||
border-spacing: 0; | |||
position: relative; | |||
table-layout: fixed; | |||
width: inherit; // a decent default fallback | |||
.#{$primaryStyleName}-header { | |||
position: absolute; | |||
> .#{$primaryStyleName}-cell { | |||
border: $v-grid-border; | |||
margin-top: -10px; | |||
opacity: 0.9; | |||
filter: alpha(opacity=90); // IE8 | |||
z-index: 30000; | |||
} | |||
> .#{$primaryStyleName}-drop-marker { | |||
background-color: $v-grid-header-drag-marked-color; | |||
position: absolute; | |||
width: 3px; | |||
} | |||
} | |||
} | |||
// Sidebar | |||
.#{$primaryStyleName}-sidebar.v-contextmenu { | |||
@include box-shadow(none); | |||
border-radius: 0; | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
background-color: $v-grid-header-background-color; | |||
border: $v-grid-header-border; | |||
padding: 0; | |||
z-index: 5; | |||
.#{$primaryStyleName}-sidebar-button { | |||
background: transparent; | |||
border: none; | |||
color: inherit; | |||
cursor: pointer; | |||
outline: none; | |||
padding: 0 4px; | |||
text-align: right; | |||
&::-moz-focus-inner { | |||
border: 0; | |||
} | |||
&:after { | |||
content: "\f0c9"; | |||
display: block; | |||
font-family: FontAwesome, sans-serif; | |||
font-size: $v-grid-header-font-size; | |||
} | |||
} | |||
&.closed { | |||
border-radius: 0; | |||
} | |||
&.opened { | |||
.#{$primaryStyleName}-sidebar-button { | |||
width: 100%; | |||
&:after { | |||
content: "\00d7"; | |||
font-size: 16px; | |||
line-height: 1; | |||
} | |||
} | |||
} | |||
.v-ie &.opened .#{$primaryStyleName}-sidebar-button { | |||
vertical-align: middle; | |||
} | |||
.v-ie8 &.opened .#{$primaryStyleName}-sidebar-button:after { | |||
display: inline; | |||
} | |||
.#{$primaryStyleName}-sidebar-content { | |||
border-top: $v-grid-border; | |||
padding: 4px 0; | |||
.gwt-MenuBar { | |||
.gwt-MenuItem .column-hiding-toggle { | |||
text-shadow: none; | |||
} | |||
} | |||
} | |||
} | |||
// Common cell styles | |||
@@ -331,6 +436,53 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; | |||
margin-right: 4px; | |||
} | |||
.#{$primaryStyleName}-spacer { | |||
left: $v-grid-details-marker-width - $v-grid-border-size; | |||
} | |||
.#{$primaryStyleName}-spacer > td { | |||
display: block; | |||
padding: 0; | |||
background-color: $v-grid-row-background-color; | |||
border-top: $v-grid-details-border-top; | |||
border-bottom: $v-grid-details-border-bottom; | |||
} | |||
.#{$primaryStyleName}-spacer.stripe > td { | |||
background-color: $v-grid-row-stripe-background-color; | |||
border-top: $v-grid-details-border-top-stripe; | |||
border-bottom: $v-grid-details-border-bottom-stripe; | |||
} | |||
.#{$primaryStyleName}-spacer-deco-container { | |||
border-top: $v-grid-border-size solid transparent; // same size as table wrapper border | |||
position: relative; | |||
top: 0; // escalator will override top for scrolling and margin-top for header offset. | |||
z-index: 5; | |||
} | |||
.#{$primaryStyleName}-spacer-deco { | |||
top: 0; // this will be overridden by code, but it's a good default. | |||
left: 0; | |||
width: $v-grid-details-marker-width; | |||
background-color: $v-grid-details-marker-color; | |||
position: absolute; | |||
height: 100%; // this will be overridden by code, but it's a good default. | |||
pointer-events: none; | |||
// IE 8-10 apply "pointer-events" only to SVG elements. | |||
// Using an empty SVG instead of an empty text node makes IE | |||
// obey the "pointer-events: none" and forwards click events | |||
// to the underlying element. The data decodes to: | |||
// <svg xmlns="http://www.w3.org/2000/svg"></svg> | |||
.ie8 &:before, | |||
.ie9 &:before, | |||
.ie10 &:before { | |||
content: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==); | |||
} | |||
} | |||
// Renderers | |||
.#{$primaryStyleName}-cell > .v-progressbar { |
@@ -35,6 +35,13 @@ | |||
} | |||
} | |||
// Sidebar | |||
.#{$primaryStyleName}-sidebar.v-contextmenu { | |||
.#{$primaryStyleName}-sidebar-content { | |||
background-color: #f8f8f9; | |||
} | |||
} | |||
// Sort indicators | |||
.#{$primaryStyleName} th.sort-asc, | |||
.#{$primaryStyleName} th.sort-desc { |
@@ -27,6 +27,25 @@ | |||
border-color: lighten($v-grid-row-selected-background-color, 20%); | |||
} | |||
} | |||
// Sidebar | |||
.#{$primaryStyleName}-sidebar.v-contextmenu { | |||
&.opened { | |||
.#{$primaryStyleName}-sidebar-button { | |||
&:after { | |||
font-size: 22px; | |||
} | |||
} | |||
} | |||
.#{$primaryStyleName}-sidebar-content { | |||
background-color: transparent; | |||
.gwt-MenuBar { | |||
border: none; | |||
} | |||
} | |||
} | |||
// Sort indicators | |||
.#{$primaryStyleName} th.sort-asc, |
@@ -3,7 +3,8 @@ | |||
$v-grid-row-background-color: valo-table-background-color() !default; | |||
$v-grid-row-stripe-background-color: scale-color($v-grid-row-background-color, $lightness: if(color-luminance($v-grid-row-background-color) < 10, 4%, -4%)) !default; | |||
$v-grid-border: flatten-list(valo-border($color: $v-grid-row-background-color, $strength: 0.8)) !default; | |||
$v-grid-border-color-source: $v-grid-row-background-color !default; | |||
$v-grid-border: flatten-list(valo-border($color: $v-grid-border-color-source, $strength: 0.8)) !default; | |||
$v-grid-cell-focused-border: max(2px, first-number($v-border)) solid $v-selection-color !default; | |||
$v-grid-row-height: $v-table-row-height !default; | |||
@@ -16,6 +17,12 @@ $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default; | |||
$v-grid-animations-enabled: $v-animations-enabled !default; | |||
$v-grid-details-marker-width: first-number($v-grid-border) * 2 !default; | |||
$v-grid-details-marker-color: $v-selection-color !default; | |||
$v-grid-details-border-top: valo-border($color: $v-grid-border-color-source, $strength: 0.3) !default; | |||
$v-grid-details-border-top-stripe: valo-border($color: $v-grid-row-stripe-background-color, $strength: 0.3) !default; | |||
$v-grid-details-border-bottom: $v-grid-cell-horizontal-border !default; | |||
$v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; | |||
@import "../../base/grid/grid"; | |||
@@ -40,6 +47,15 @@ $v-grid-animations-enabled: $v-animations-enabled !default; | |||
text-shadow: valo-text-shadow($font-color: valo-font-color($v-grid-header-background-color), $background-color: $v-grid-header-background-color); | |||
} | |||
.#{$primary-stylename}-header .#{$primary-stylename}-cell.dragged { | |||
@include opacity(0.5, false); | |||
@include transition (opacity .3s ease-in-out); | |||
} | |||
.#{$primary-stylename}-header .#{$primary-stylename}-cell.dragged-column-header { | |||
margin-top: round($v-grid-row-height/-2); | |||
} | |||
.#{$primary-stylename}-footer .#{$primary-stylename}-cell { | |||
@include valo-gradient($v-grid-footer-background-color); | |||
text-shadow: valo-text-shadow($font-color: valo-font-color($v-grid-footer-background-color), $background-color: $v-grid-footer-background-color); | |||
@@ -166,6 +182,28 @@ $v-grid-animations-enabled: $v-animations-enabled !default; | |||
padding: round($v-layout-spacing-vertical / 2) round($v-layout-spacing-horizontal / 2); | |||
margin: 0; | |||
outline: none; | |||
} | |||
.#{$primary-stylename}-spacer { | |||
margin-top: first-number($v-grid-border) * -1; | |||
} | |||
// Sidebar | |||
.#{$primary-stylename}-sidebar.v-contextmenu { | |||
&.opened { | |||
.#{$primary-stylename}-sidebar-button:after { | |||
font-size: 20px; | |||
} | |||
.#{$primary-stylename}-sidebar-content { | |||
margin: 0 0 2px; | |||
padding: 4px 4px 2px; | |||
} | |||
} | |||
&.closed { | |||
@include valo-gradient($v-grid-header-background-color); | |||
} | |||
} | |||
// Customize scrollbars |
@@ -63,8 +63,10 @@ | |||
<h2 id="overview">Overview of Vaadin @version@ Release</h2> | |||
<p> | |||
Vaadin @version@ is a feature release that includes a | |||
number of new features and bug fixes, as listed in the <a | |||
Vaadin @version@ is a | |||
<!-- feature release that includes --> | |||
pre-release for evaluating | |||
a number of new features and bug fixes, as listed in the <a | |||
href="#enhancements">list of enhancements</a> and <a | |||
href="#changelog">change log</a> below. | |||
</p> | |||
@@ -93,34 +95,15 @@ | |||
enhancements. Below is a list of the most notable changes:</p> | |||
<ul> | |||
<li>Grid is a new component for showing tabular data. It has been | |||
designed from the ground up to eventually replace the Table | |||
and TreeTable components.<br /> | |||
The most notable Grid features in @version-minor@ are: | |||
<ul> | |||
<li>Support for multiple rows in the header and footer sections.</li> | |||
<li>Renderer concept for customizing how the data in a given column is represented in the browser.</li> | |||
<li>Support for frozen columns.</li> | |||
<li>Support for inline editing of one row at a time.</li> | |||
<li>Support for components in header and footer cells.</li> | |||
<li>Hardware accelerated, touch optimized scrolling.</li> | |||
</ul></li> | |||
<li>Declarative layout support for initializing a component hierarchy from an HTML file.</li> | |||
<li>Uses GWT 2.7 for improved compilation times when using Super Dev Mode.</li> | |||
<li>@Viewport annotation for declaratively defining a mobile viewport definition for a UI.</li> | |||
<li>Component captions, TabSheet/Accordion tab captions and Calendar event captions can be configured to be displayed as HTML.</li> | |||
<li>Selects use converters when presenting itemids.</li> | |||
<li>Improved performance when server response contains no visual changes (e.g. empty polling responses).</li> | |||
<li>Development time on-the-fly scss compilation cache may now be preserved when redeploying or restarting the server.</li> | |||
<li>Unified JSON library for using the same API in both server-side and client-side code.</li> | |||
<li>Range validators and converters for additional numerical types.</li> | |||
<li>Support for fine grained add/remove item events in in-memory containers.</li> | |||
<li>Column reordering using drag and drop in Grid</li> | |||
<li>Column hiding in Grid</li> | |||
<li>Row details support in Grid</li> | |||
</ul> | |||
<p> | |||
For enhancements introduced in Vaadin 7.3, see the <a | |||
href="http://vaadin.com/download/release/7.3/7.3.0/release-notes.html">Release | |||
Notes for Vaadin 7.3.0</a>. | |||
For enhancements introduced in Vaadin 7.4, see the <a | |||
href="http://vaadin.com/download/release/7.4/7.4.0/release-notes.html">Release | |||
Notes for Vaadin 7.4.0</a>. | |||
</p> | |||
<h3 id="incompatible">Incompatible or Behavior-altering Changes in @version-minor@</h3> | |||
@@ -129,6 +112,7 @@ | |||
</ul> | |||
<h3 id="knownissues">Known Issues and Limitations</h3> | |||
<ul> | |||
<li>The user interface for hiding and unhiding Grid columns is not yet finalized.</li> | |||
<li>Drag'n'drop in a Table doesn't work on touch devices running | |||
Internet Explorer (Windows Phone, Surface) | |||
(<a href="http://dev.vaadin.com/ticket/13737">#13737</a>) |
@@ -1459,4 +1459,124 @@ public class WidgetUtil { | |||
return Logger.getLogger(WidgetUtil.class.getName()); | |||
} | |||
/** | |||
* Returns the thickness of the given element's top border. | |||
* <p> | |||
* The value is determined using computed style when available and | |||
* calculated otherwise. | |||
* | |||
* @since | |||
* @param element | |||
* the element to measure | |||
* @return the top border thickness | |||
*/ | |||
public static double getBorderTopThickness(Element element) { | |||
return getBorderThickness(element, new String[] { "borderTopWidth" }); | |||
} | |||
/** | |||
* Returns the thickness of the given element's bottom border. | |||
* <p> | |||
* The value is determined using computed style when available and | |||
* calculated otherwise. | |||
* | |||
* @since | |||
* @param element | |||
* the element to measure | |||
* @return the bottom border thickness | |||
*/ | |||
public static double getBorderBottomThickness(Element element) { | |||
return getBorderThickness(element, new String[] { "borderBottomWidth" }); | |||
} | |||
/** | |||
* Returns the combined thickness of the given element's top and bottom | |||
* borders. | |||
* <p> | |||
* The value is determined using computed style when available and | |||
* calculated otherwise. | |||
* | |||
* @since | |||
* @param element | |||
* the element to measure | |||
* @return the top and bottom border thickness | |||
*/ | |||
public static double getBorderTopAndBottomThickness(Element element) { | |||
return getBorderThickness(element, new String[] { "borderTopWidth", | |||
"borderBottomWidth" }); | |||
} | |||
/** | |||
* Returns the thickness of the given element's left border. | |||
* <p> | |||
* The value is determined using computed style when available and | |||
* calculated otherwise. | |||
* | |||
* @since | |||
* @param element | |||
* the element to measure | |||
* @return the left border thickness | |||
*/ | |||
public static double getBorderLeftThickness(Element element) { | |||
return getBorderThickness(element, new String[] { "borderLeftWidth" }); | |||
} | |||
/** | |||
* Returns the thickness of the given element's right border. | |||
* <p> | |||
* The value is determined using computed style when available and | |||
* calculated otherwise. | |||
* | |||
* @since | |||
* @param element | |||
* the element to measure | |||
* @return the right border thickness | |||
*/ | |||
public static double getBorderRightThickness(Element element) { | |||
return getBorderThickness(element, new String[] { "borderRightWidth" }); | |||
} | |||
/** | |||
* Returns the thickness of the given element's left and right borders. | |||
* <p> | |||
* The value is determined using computed style when available and | |||
* calculated otherwise. | |||
* | |||
* @since | |||
* @param element | |||
* the element to measure | |||
* @return the top border thickness | |||
*/ | |||
public static double getBorderLeftAndRightThickness(Element element) { | |||
return getBorderThickness(element, new String[] { "borderLeftWidth", | |||
"borderRightWidth" }); | |||
} | |||
private static native double getBorderThickness( | |||
com.google.gwt.dom.client.Element element, String[] borderNames) | |||
/*-{ | |||
if (typeof $wnd.getComputedStyle === 'function') { | |||
var computedStyle = $wnd.getComputedStyle(element); | |||
var width = 0; | |||
for (i=0; i< borderNames.length; i++) { | |||
var borderWidth = computedStyle[borderNames[i]]; | |||
width += parseFloat(borderWidth); | |||
} | |||
return width; | |||
} else { | |||
var parentElement = element.offsetParent; | |||
var cloneElement = element.cloneNode(false); | |||
cloneElement.style.boxSizing ="content-box"; | |||
parentElement.appendChild(cloneElement); | |||
cloneElement.style.height = "10px"; // IE8 wants the height to be set to something... | |||
var heightWithBorder = cloneElement.offsetHeight; | |||
for (i=0; i< borderNames.length; i++) { | |||
cloneElement.style[borderNames[i]] = "0"; | |||
} | |||
var heightWithoutBorder = cloneElement.offsetHeight; | |||
parentElement.removeChild(cloneElement); | |||
return heightWithBorder - heightWithoutBorder; | |||
} | |||
}-*/; | |||
} |
@@ -19,6 +19,7 @@ package com.vaadin.client.connectors; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.HashSet; | |||
import java.util.Iterator; | |||
@@ -31,12 +32,15 @@ import java.util.logging.Logger; | |||
import com.google.gwt.core.client.Scheduler; | |||
import com.google.gwt.core.client.Scheduler.ScheduledCommand; | |||
import com.google.gwt.dom.client.NativeEvent; | |||
import com.google.gwt.user.client.Timer; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ComponentConnector; | |||
import com.vaadin.client.ConnectorHierarchyChangeEvent; | |||
import com.vaadin.client.DeferredWorker; | |||
import com.vaadin.client.MouseEventDetailsBuilder; | |||
import com.vaadin.client.annotations.OnStateChange; | |||
import com.vaadin.client.communication.StateChangeEvent; | |||
import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; | |||
import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; | |||
import com.vaadin.client.data.DataSource.RowHandle; | |||
import com.vaadin.client.renderers.Renderer; | |||
@@ -45,11 +49,16 @@ import com.vaadin.client.ui.AbstractHasComponentsConnector; | |||
import com.vaadin.client.ui.SimpleManagedLayout; | |||
import com.vaadin.client.widget.grid.CellReference; | |||
import com.vaadin.client.widget.grid.CellStyleGenerator; | |||
import com.vaadin.client.widget.grid.DetailsGenerator; | |||
import com.vaadin.client.widget.grid.EditorHandler; | |||
import com.vaadin.client.widget.grid.RowReference; | |||
import com.vaadin.client.widget.grid.RowStyleGenerator; | |||
import com.vaadin.client.widget.grid.events.BodyClickHandler; | |||
import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler; | |||
import com.vaadin.client.widget.grid.events.ColumnReorderEvent; | |||
import com.vaadin.client.widget.grid.events.ColumnReorderHandler; | |||
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; | |||
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; | |||
import com.vaadin.client.widget.grid.events.GridClickEvent; | |||
import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; | |||
import com.vaadin.client.widget.grid.events.SelectAllEvent; | |||
@@ -70,8 +79,10 @@ import com.vaadin.client.widgets.Grid.FooterCell; | |||
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.Connector; | |||
import com.vaadin.shared.data.sort.SortDirection; | |||
import com.vaadin.shared.ui.Connect; | |||
import com.vaadin.shared.ui.grid.DetailsConnectorChange; | |||
import com.vaadin.shared.ui.grid.EditorClientRpc; | |||
import com.vaadin.shared.ui.grid.EditorServerRpc; | |||
import com.vaadin.shared.ui.grid.GridClientRpc; | |||
@@ -101,7 +112,7 @@ import elemental.json.JsonValue; | |||
*/ | |||
@Connect(com.vaadin.ui.Grid.class) | |||
public class GridConnector extends AbstractHasComponentsConnector implements | |||
SimpleManagedLayout { | |||
SimpleManagedLayout, DeferredWorker { | |||
private static final class CustomCellStyleGenerator implements | |||
CellStyleGenerator<JsonObject> { | |||
@@ -167,7 +178,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
/** | |||
* Sets a new renderer for this column object | |||
* | |||
* | |||
* @param rendererConnector | |||
* a renderer connector object | |||
*/ | |||
@@ -362,6 +373,305 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
} | |||
} | |||
private ColumnReorderHandler<JsonObject> columnReorderHandler = new ColumnReorderHandler<JsonObject>() { | |||
@Override | |||
public void onColumnReorder(ColumnReorderEvent<JsonObject> event) { | |||
if (!columnsUpdatedFromState) { | |||
List<Column<?, JsonObject>> columns = getWidget().getColumns(); | |||
final List<String> newColumnOrder = new ArrayList<String>(); | |||
for (Column<?, JsonObject> column : columns) { | |||
if (column instanceof CustomGridColumn) { | |||
newColumnOrder.add(((CustomGridColumn) column).id); | |||
} // the other case would be the multi selection column | |||
} | |||
getRpcProxy(GridServerRpc.class).columnsReordered( | |||
newColumnOrder, columnOrder); | |||
columnOrder = newColumnOrder; | |||
getState().columnOrder = newColumnOrder; | |||
} | |||
} | |||
}; | |||
private ColumnVisibilityChangeHandler<JsonObject> columnVisibilityChangeHandler = new ColumnVisibilityChangeHandler<JsonObject>() { | |||
@Override | |||
public void onVisibilityChange( | |||
ColumnVisibilityChangeEvent<JsonObject> event) { | |||
if (!columnsUpdatedFromState) { | |||
Column<?, JsonObject> column = event.getColumn(); | |||
if (column instanceof CustomGridColumn) { | |||
getRpcProxy(GridServerRpc.class).columnVisibilityChanged( | |||
((CustomGridColumn) column).id, column.isHidden(), | |||
event.isUserOriginated()); | |||
for (GridColumnState state : getState().columns) { | |||
if (state.id.equals(((CustomGridColumn) column).id)) { | |||
state.hidden = event.isHidden(); | |||
break; | |||
} | |||
} | |||
} else { | |||
getLogger().warning( | |||
"Visibility changed for a unknown column type in Grid: " | |||
+ column.toString() + ", type " | |||
+ column.getClass()); | |||
} | |||
} | |||
} | |||
}; | |||
private static class CustomDetailsGenerator implements DetailsGenerator { | |||
private final Map<Integer, ComponentConnector> indexToDetailsMap = new HashMap<Integer, ComponentConnector>(); | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public Widget getDetails(int rowIndex) { | |||
ComponentConnector componentConnector = indexToDetailsMap | |||
.get(rowIndex); | |||
if (componentConnector != null) { | |||
return componentConnector.getWidget(); | |||
} else { | |||
return null; | |||
} | |||
} | |||
public void setDetailsConnectorChanges( | |||
Set<DetailsConnectorChange> changes) { | |||
/* | |||
* To avoid overwriting connectors while moving them about, we'll | |||
* take all the affected connectors, first all remove those that are | |||
* removed or moved, then we add back those that are moved or added. | |||
*/ | |||
/* Remove moved/removed connectors from bookkeeping */ | |||
for (DetailsConnectorChange change : changes) { | |||
Integer oldIndex = change.getOldIndex(); | |||
Connector removedConnector = indexToDetailsMap.remove(oldIndex); | |||
Connector connector = change.getConnector(); | |||
assert removedConnector == null || connector == null | |||
|| removedConnector.equals(connector) : "Index " | |||
+ oldIndex + " points to " + removedConnector | |||
+ " while " + connector + " was expected"; | |||
} | |||
/* Add moved/added connectors to bookkeeping */ | |||
for (DetailsConnectorChange change : changes) { | |||
Integer newIndex = change.getNewIndex(); | |||
ComponentConnector connector = (ComponentConnector) change | |||
.getConnector(); | |||
if (connector != null) { | |||
assert newIndex != null : "An existing connector has a missing new index."; | |||
ComponentConnector prevConnector = indexToDetailsMap.put( | |||
newIndex, connector); | |||
assert prevConnector == null : "Connector collision at index " | |||
+ newIndex | |||
+ " between old " | |||
+ prevConnector | |||
+ " and new " + connector; | |||
} | |||
} | |||
} | |||
} | |||
@SuppressWarnings("boxing") | |||
private static class DetailsConnectorFetcher implements DeferredWorker { | |||
private static final int FETCH_TIMEOUT_MS = 5000; | |||
public interface Listener { | |||
void fetchHasBeenScheduled(int id); | |||
void fetchHasReturned(int id); | |||
} | |||
/** A flag making sure that we don't call scheduleFinally many times. */ | |||
private boolean fetcherHasBeenCalled = false; | |||
/** A rolling counter for unique values. */ | |||
private int detailsFetchCounter = 0; | |||
/** A collection that tracks the amount of requests currently underway. */ | |||
private Set<Integer> pendingFetches = new HashSet<Integer>(5); | |||
private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
int currentFetchId = detailsFetchCounter++; | |||
pendingFetches.add(currentFetchId); | |||
rpc.sendDetailsComponents(currentFetchId); | |||
fetcherHasBeenCalled = false; | |||
if (listener != null) { | |||
listener.fetchHasBeenScheduled(currentFetchId); | |||
} | |||
assert assertRequestDoesNotTimeout(currentFetchId); | |||
} | |||
}; | |||
private DetailsConnectorFetcher.Listener listener = null; | |||
private final GridServerRpc rpc; | |||
public DetailsConnectorFetcher(GridServerRpc rpc) { | |||
assert rpc != null : "RPC was null"; | |||
this.rpc = rpc; | |||
} | |||
public void schedule() { | |||
if (!fetcherHasBeenCalled) { | |||
Scheduler.get().scheduleFinally(lazyDetailsFetcher); | |||
fetcherHasBeenCalled = true; | |||
} | |||
} | |||
public void responseReceived(int fetchId) { | |||
if (fetchId < 0) { | |||
/* Ignore negative fetchIds (they're pushed, not fetched) */ | |||
return; | |||
} | |||
boolean success = pendingFetches.remove(fetchId); | |||
assert success : "Received a response with an unidentified fetch id"; | |||
if (listener != null) { | |||
listener.fetchHasReturned(fetchId); | |||
} | |||
} | |||
@Override | |||
public boolean isWorkPending() { | |||
return fetcherHasBeenCalled || !pendingFetches.isEmpty(); | |||
} | |||
private boolean assertRequestDoesNotTimeout(final int fetchId) { | |||
/* | |||
* This method will not be compiled without asserts enabled. This | |||
* only makes sure that any request does not time out. | |||
* | |||
* TODO Should this be an explicit check? Is it worth the overhead? | |||
*/ | |||
new Timer() { | |||
@Override | |||
public void run() { | |||
assert !pendingFetches.contains(fetchId) : "Fetch id " | |||
+ fetchId + " timed out."; | |||
} | |||
}.schedule(FETCH_TIMEOUT_MS); | |||
return true; | |||
} | |||
public void setListener(DetailsConnectorFetcher.Listener listener) { | |||
// if more are needed, feel free to convert this into a collection. | |||
this.listener = listener; | |||
} | |||
} | |||
/** | |||
* The functionality that makes sure that the scroll position is still kept | |||
* up-to-date even if more details are being fetched lazily. | |||
*/ | |||
private class LazyDetailsScrollAdjuster implements DeferredWorker { | |||
private static final int SCROLL_TO_END_ID = -2; | |||
private static final int NO_SCROLL_SCHEDULED = -1; | |||
private class ScrollStopChecker implements DeferredWorker { | |||
private final ScheduledCommand checkCommand = new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
isScheduled = false; | |||
if (queuedFetches.isEmpty()) { | |||
currentRow = NO_SCROLL_SCHEDULED; | |||
destination = null; | |||
} | |||
} | |||
}; | |||
private boolean isScheduled = false; | |||
public void schedule() { | |||
if (isScheduled) { | |||
return; | |||
} | |||
Scheduler.get().scheduleDeferred(checkCommand); | |||
isScheduled = true; | |||
} | |||
@Override | |||
public boolean isWorkPending() { | |||
return isScheduled; | |||
} | |||
} | |||
private DetailsConnectorFetcher.Listener fetcherListener = new DetailsConnectorFetcher.Listener() { | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public void fetchHasBeenScheduled(int id) { | |||
if (currentRow != NO_SCROLL_SCHEDULED) { | |||
queuedFetches.add(id); | |||
} | |||
} | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public void fetchHasReturned(int id) { | |||
if (currentRow == NO_SCROLL_SCHEDULED | |||
|| queuedFetches.isEmpty()) { | |||
return; | |||
} | |||
queuedFetches.remove(id); | |||
if (currentRow == SCROLL_TO_END_ID) { | |||
getWidget().scrollToEnd(); | |||
} else { | |||
getWidget().scrollToRow(currentRow, destination); | |||
} | |||
/* | |||
* Schedule a deferred call whether we should stop adjusting for | |||
* scrolling. | |||
* | |||
* This is done deferredly just because we can't be absolutely | |||
* certain whether this most recent scrolling won't cascade into | |||
* further lazy details loading (perhaps deferredly). | |||
*/ | |||
scrollStopChecker.schedule(); | |||
} | |||
}; | |||
private int currentRow = NO_SCROLL_SCHEDULED; | |||
private final Set<Integer> queuedFetches = new HashSet<Integer>(); | |||
private final ScrollStopChecker scrollStopChecker = new ScrollStopChecker(); | |||
private ScrollDestination destination; | |||
public LazyDetailsScrollAdjuster() { | |||
detailsConnectorFetcher.setListener(fetcherListener); | |||
} | |||
public void adjustForEnd() { | |||
currentRow = SCROLL_TO_END_ID; | |||
} | |||
public void adjustFor(int row, ScrollDestination destination) { | |||
currentRow = row; | |||
this.destination = destination; | |||
} | |||
@Override | |||
public boolean isWorkPending() { | |||
return currentRow != NO_SCROLL_SCHEDULED | |||
|| !queuedFetches.isEmpty() | |||
|| scrollStopChecker.isWorkPending(); | |||
} | |||
} | |||
/** | |||
* Maps a generated column id to a grid column instance | |||
*/ | |||
@@ -372,13 +682,22 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
private List<String> columnOrder = new ArrayList<String>(); | |||
/** | |||
* updateFromState is set to true when {@link #updateSelectionFromState()} | |||
* makes changes to selection. This flag tells the | |||
* {@code internalSelectionChangeHandler} to not send same data straight | |||
* back to server. Said listener sets it back to false when handling that | |||
* event. | |||
* {@link #selectionUpdatedFromState} is set to true when | |||
* {@link #updateSelectionFromState()} makes changes to selection. This flag | |||
* tells the {@code internalSelectionChangeHandler} to not send same data | |||
* straight back to server. Said listener sets it back to false when | |||
* handling that event. | |||
*/ | |||
private boolean selectionUpdatedFromState; | |||
/** | |||
* {@link #columnsUpdatedFromState} is set to true when | |||
* {@link #updateColumnOrderFromState(List)} is updating the column order | |||
* for the widget. This flag tells the {@link #columnReorderHandler} to not | |||
* send same data straight back to server. After updates, listener sets the | |||
* value back to false. | |||
*/ | |||
private boolean updatedFromState = false; | |||
private boolean columnsUpdatedFromState; | |||
private RpcDataSource dataSource; | |||
@@ -388,7 +707,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
if (event.isBatchedSelection()) { | |||
return; | |||
} | |||
if (!updatedFromState) { | |||
if (!selectionUpdatedFromState) { | |||
for (JsonObject row : event.getRemoved()) { | |||
selectedKeys.remove(dataSource.getRowKey(row)); | |||
} | |||
@@ -400,7 +719,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
getRpcProxy(GridServerRpc.class).select( | |||
new ArrayList<String>(selectedKeys)); | |||
} else { | |||
updatedFromState = false; | |||
selectionUpdatedFromState = false; | |||
} | |||
} | |||
}; | |||
@@ -409,6 +728,35 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
private String lastKnownTheme = null; | |||
private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator(); | |||
private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher( | |||
getRpcProxy(GridServerRpc.class)); | |||
private final DetailsListener detailsListener = new DetailsListener() { | |||
@Override | |||
public void reapplyDetailsVisibility(int rowIndex, JsonObject row) { | |||
if (hasDetailsOpen(row)) { | |||
getWidget().setDetailsVisible(rowIndex, true); | |||
detailsConnectorFetcher.schedule(); | |||
} else { | |||
getWidget().setDetailsVisible(rowIndex, false); | |||
} | |||
} | |||
private boolean hasDetailsOpen(JsonObject row) { | |||
return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) | |||
&& row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE); | |||
} | |||
@Override | |||
public void closeDetails(int rowIndex) { | |||
getWidget().setDetailsVisible(rowIndex, false); | |||
} | |||
}; | |||
private final LazyDetailsScrollAdjuster lazyDetailsScrollAdjuster = new LazyDetailsScrollAdjuster(); | |||
@Override | |||
@SuppressWarnings("unchecked") | |||
public Grid<JsonObject> getWidget() { | |||
@@ -428,6 +776,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
registerRpc(GridClientRpc.class, new GridClientRpc() { | |||
@Override | |||
public void scrollToStart() { | |||
/* | |||
* no need for lazyDetailsScrollAdjuster, because the start is | |||
* always 0, won't change a bit. | |||
*/ | |||
Scheduler.get().scheduleFinally(new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
@@ -438,6 +790,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
@Override | |||
public void scrollToEnd() { | |||
lazyDetailsScrollAdjuster.adjustForEnd(); | |||
Scheduler.get().scheduleFinally(new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
@@ -449,6 +802,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
@Override | |||
public void scrollToRow(final int row, | |||
final ScrollDestination destination) { | |||
lazyDetailsScrollAdjuster.adjustFor(row, destination); | |||
Scheduler.get().scheduleFinally(new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
@@ -461,6 +815,51 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
public void recalculateColumnWidths() { | |||
getWidget().recalculateColumnWidths(); | |||
} | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public void setDetailsConnectorChanges( | |||
Set<DetailsConnectorChange> connectorChanges, int fetchId) { | |||
customDetailsGenerator | |||
.setDetailsConnectorChanges(connectorChanges); | |||
List<DetailsConnectorChange> removedFirst = new ArrayList<DetailsConnectorChange>( | |||
connectorChanges); | |||
Collections.sort(removedFirst, | |||
DetailsConnectorChange.REMOVED_FIRST_COMPARATOR); | |||
// refresh moved/added details rows | |||
for (DetailsConnectorChange change : removedFirst) { | |||
Integer oldIndex = change.getOldIndex(); | |||
Integer newIndex = change.getNewIndex(); | |||
assert oldIndex == null || oldIndex >= 0 : "Got an " | |||
+ "invalid old index: " + oldIndex | |||
+ " (connector: " + change.getConnector() + ")"; | |||
assert newIndex == null || newIndex >= 0 : "Got an " | |||
+ "invalid new index: " + newIndex | |||
+ " (connector: " + change.getConnector() + ")"; | |||
if (oldIndex != null) { | |||
/* Close the old/removed index */ | |||
getWidget().setDetailsVisible(oldIndex, false); | |||
if (change.isShouldStillBeVisible()) { | |||
getWidget().setDetailsVisible(oldIndex, true); | |||
} | |||
} | |||
if (newIndex != null) { | |||
/* | |||
* Since the component was lazy loaded, we need to | |||
* refresh the details by toggling it. | |||
*/ | |||
getWidget().setDetailsVisible(newIndex, false); | |||
getWidget().setDetailsVisible(newIndex, true); | |||
} | |||
} | |||
detailsConnectorFetcher.responseReceived(fetchId); | |||
} | |||
}); | |||
getWidget().addSelectionHandler(internalSelectionChangeHandler); | |||
@@ -503,7 +902,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
}); | |||
getWidget().setEditorHandler(new CustomEditorHandler()); | |||
getWidget().addColumnReorderHandler(columnReorderHandler); | |||
getWidget().addColumnVisibilityChangeHandler( | |||
columnVisibilityChangeHandler); | |||
getWidget().setDetailsGenerator(customDetailsGenerator); | |||
getLayoutManager().registerDependency(this, getWidget().getElement()); | |||
layout(); | |||
} | |||
@@ -522,7 +926,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
if (!columnIdToColumn.containsKey(state.id)) { | |||
addColumnFromStateChangeEvent(state); | |||
} | |||
updateColumnFromState(columnIdToColumn.get(state.id), state); | |||
updateColumnFromStateChangeEvent(state); | |||
} | |||
} | |||
@@ -596,7 +1000,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
columns[i] = columnIdToColumn.get(id); | |||
i++; | |||
} | |||
columnsUpdatedFromState = true; | |||
getWidget().setColumnOrder(columns); | |||
columnsUpdatedFromState = false; | |||
columnOrder = stateColumnOrder; | |||
} | |||
@@ -732,7 +1138,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
*/ | |||
private void updateColumnFromStateChangeEvent(GridColumnState columnState) { | |||
CustomGridColumn column = columnIdToColumn.get(columnState.id); | |||
columnsUpdatedFromState = true; | |||
updateColumnFromState(column, columnState); | |||
columnsUpdatedFromState = false; | |||
} | |||
/** | |||
@@ -788,6 +1197,11 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
column.setRenderer((AbstractRendererConnector<Object>) state.rendererConnector); | |||
column.setSortable(state.sortable); | |||
column.setHidden(state.hidden); | |||
column.setHidable(state.hidable); | |||
column.setHidingToggleCaption(state.hidingToggleCaption); | |||
column.setEditable(state.editable); | |||
column.setEditorConnector((AbstractFieldConnector) state.editorConnector); | |||
} | |||
@@ -891,7 +1305,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
if (changed) { | |||
// At least for now there's no way to send the selected and/or | |||
// deselected row data. Some data is only stored as keys | |||
updatedFromState = true; | |||
selectionUpdatedFromState = true; | |||
getWidget().fireEvent( | |||
new SelectionEvent<JsonObject>(getWidget(), | |||
(List<JsonObject>) null, null, false)); | |||
@@ -994,4 +1408,14 @@ public class GridConnector extends AbstractHasComponentsConnector implements | |||
public void layout() { | |||
getWidget().onResize(); | |||
} | |||
@Override | |||
public boolean isWorkPending() { | |||
return detailsConnectorFetcher.isWorkPending() | |||
|| lazyDetailsScrollAdjuster.isWorkPending(); | |||
} | |||
public DetailsListener getDetailsListener() { | |||
return detailsListener; | |||
} | |||
} |
@@ -17,6 +17,7 @@ | |||
package com.vaadin.client.connectors; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import com.vaadin.client.ServerConnector; | |||
import com.vaadin.client.data.AbstractRemoteDataSource; | |||
@@ -43,6 +44,36 @@ import elemental.json.JsonObject; | |||
@Connect(com.vaadin.data.RpcDataProviderExtension.class) | |||
public class RpcDataSourceConnector extends AbstractExtensionConnector { | |||
/** | |||
* A callback interface to let {@link GridConnector} know that detail | |||
* visibilities might have changed. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
interface DetailsListener { | |||
/** | |||
* A request to verify (and correct) the visibility for a row, given | |||
* updated metadata. | |||
* | |||
* @param rowIndex | |||
* the index of the row that should be checked | |||
* @param row | |||
* the row object to check visibility for | |||
* @see GridState#JSONKEY_DETAILS_VISIBLE | |||
*/ | |||
void reapplyDetailsVisibility(int rowIndex, JsonObject row); | |||
/** | |||
* Closes details for a row. | |||
* | |||
* @param rowIndex | |||
* the index of the row for which to close details | |||
*/ | |||
void closeDetails(int rowIndex); | |||
} | |||
public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> { | |||
protected RpcDataSource() { | |||
@@ -56,27 +87,28 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { | |||
rows.add(rowObject); | |||
} | |||
dataSource.setRowData(firstRow, rows); | |||
RpcDataSource.this.setRowData(firstRow, rows); | |||
} | |||
@Override | |||
public void removeRowData(int firstRow, int count) { | |||
dataSource.removeRowData(firstRow, count); | |||
RpcDataSource.this.removeRowData(firstRow, count); | |||
} | |||
@Override | |||
public void insertRowData(int firstRow, int count) { | |||
dataSource.insertRowData(firstRow, count); | |||
RpcDataSource.this.insertRowData(firstRow, count); | |||
} | |||
@Override | |||
public void resetDataAndSize(int size) { | |||
dataSource.resetDataAndSize(size); | |||
RpcDataSource.this.resetDataAndSize(size); | |||
} | |||
}); | |||
} | |||
private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class); | |||
private DetailsListener detailsListener; | |||
@Override | |||
protected void requestRows(int firstRowIndex, int numberOfRows, | |||
@@ -170,7 +202,29 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { | |||
if (!handle.isPinned()) { | |||
rpcProxy.setPinned(key, false); | |||
} | |||
} | |||
void setDetailsListener(DetailsListener detailsListener) { | |||
this.detailsListener = detailsListener; | |||
} | |||
@Override | |||
protected void setRowData(int firstRowIndex, List<JsonObject> rowData) { | |||
super.setRowData(firstRowIndex, rowData); | |||
/* | |||
* Intercepting details information from the data source, rerouting | |||
* them back to the GridConnector (as a details listener) | |||
*/ | |||
for (int i = 0; i < rowData.size(); i++) { | |||
detailsListener.reapplyDetailsVisibility(firstRowIndex + i, | |||
rowData.get(i)); | |||
} | |||
} | |||
@Override | |||
protected void onDropFromCache(int rowIndex) { | |||
detailsListener.closeDetails(rowIndex); | |||
} | |||
} | |||
@@ -178,6 +232,8 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { | |||
@Override | |||
protected void extend(ServerConnector target) { | |||
((GridConnector) target).setDataSource(dataSource); | |||
GridConnector gridConnector = (GridConnector) target; | |||
dataSource.setDetailsListener(gridConnector.getDetailsListener()); | |||
gridConnector.setDataSource(dataSource); | |||
} | |||
} |
@@ -332,9 +332,23 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { | |||
for (int i = range.getStart(); i < range.getEnd(); i++) { | |||
T removed = indexToRowMap.remove(Integer.valueOf(i)); | |||
keyToIndexMap.remove(getRowKey(removed)); | |||
onDropFromCache(i); | |||
} | |||
} | |||
/** | |||
* A hook that can be overridden to do something whenever a row is dropped | |||
* from the cache. | |||
* | |||
* @since 7.5.0 | |||
* @param rowIndex | |||
* the index of the dropped row | |||
*/ | |||
protected void onDropFromCache(int rowIndex) { | |||
// noop | |||
} | |||
private void handleMissingRows(Range range) { | |||
if (range.isEmpty()) { | |||
return; | |||
@@ -574,6 +588,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { | |||
Profiler.leave("AbstractRemoteDataSource.insertRowData"); | |||
} | |||
@SuppressWarnings("boxing") | |||
private void moveRowFromIndexToIndex(int oldIndex, int newIndex) { | |||
T row = indexToRowMap.remove(oldIndex); | |||
if (indexToRowMap.containsKey(newIndex)) { |
@@ -0,0 +1,241 @@ | |||
/* | |||
* 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.client.ui.dd; | |||
import com.google.gwt.dom.client.NativeEvent; | |||
import com.google.gwt.event.dom.client.KeyCodes; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.user.client.Event; | |||
import com.google.gwt.user.client.Event.NativePreviewEvent; | |||
import com.google.gwt.user.client.Event.NativePreviewHandler; | |||
import com.google.gwt.user.client.ui.RootPanel; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.client.widgets.Grid; | |||
/** | |||
* A simple event handler for elements that can be drag and dropped. Properly | |||
* handles drag start, cancel and end. For example, used in {@link Grid} column | |||
* header reordering. | |||
* <p> | |||
* The showing of the dragged element, drag hints and reacting to drop/cancel is | |||
* delegated to {@link DragAndDropCallback} implementation. | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
public class DragAndDropHandler { | |||
/** | |||
* Callback interface for drag and drop. | |||
*/ | |||
public interface DragAndDropCallback { | |||
/** | |||
* Called when the drag has started. The drag can be canceled by | |||
* returning {@code false}. | |||
* | |||
* @param startEvent | |||
* the original event that started the drag | |||
* @return {@code true} if the drag is OK to start, {@code false} to | |||
* cancel | |||
*/ | |||
boolean onDragStart(NativeEvent startEvent); | |||
/** | |||
* Called on drag. | |||
* | |||
* @param event | |||
* the event related to the drag | |||
*/ | |||
void onDragUpdate(NativePreviewEvent event); | |||
/** | |||
* Called after the has ended on a drop or cancel. | |||
*/ | |||
void onDragEnd(); | |||
/** | |||
* Called when the drag has ended on a drop. | |||
*/ | |||
void onDrop(); | |||
/** | |||
* Called when the drag has been canceled. | |||
*/ | |||
void onDragCancel(); | |||
} | |||
private HandlerRegistration dragStartNativePreviewHandlerRegistration; | |||
private HandlerRegistration dragHandlerRegistration; | |||
private boolean dragging; | |||
private DragAndDropCallback callback; | |||
private final NativePreviewHandler dragHandler = new NativePreviewHandler() { | |||
@Override | |||
public void onPreviewNativeEvent(NativePreviewEvent event) { | |||
if (dragging) { | |||
final int typeInt = event.getTypeInt(); | |||
switch (typeInt) { | |||
case Event.ONKEYDOWN: | |||
int keyCode = event.getNativeEvent().getKeyCode(); | |||
if (keyCode == KeyCodes.KEY_ESCAPE) { | |||
// end drag if ESC is hit | |||
cancelDrag(event); | |||
} | |||
break; | |||
case Event.ONMOUSEMOVE: | |||
case Event.ONTOUCHMOVE: | |||
callback.onDragUpdate(event); | |||
// prevent text selection on IE | |||
event.getNativeEvent().preventDefault(); | |||
break; | |||
case Event.ONTOUCHCANCEL: | |||
cancelDrag(event); | |||
break; | |||
case Event.ONTOUCHEND: | |||
/* Avoid simulated event on drag end */ | |||
event.getNativeEvent().preventDefault(); | |||
//$FALL-THROUGH$ | |||
case Event.ONMOUSEUP: | |||
callback.onDragUpdate(event); | |||
callback.onDrop(); | |||
stopDrag(); | |||
event.cancel(); | |||
break; | |||
default: | |||
break; | |||
} | |||
} else { | |||
stopDrag(); | |||
} | |||
} | |||
}; | |||
/** | |||
* This method can be called to trigger drag and drop on any grid element | |||
* that can be dragged and dropped. | |||
* | |||
* @param dragStartingEvent | |||
* the drag triggering event, usually a {@link Event#ONMOUSEDOWN} | |||
* or {@link Event#ONTOUCHSTART} event on the draggable element | |||
* | |||
* @param callback | |||
* the callback that will handle actual drag and drop related | |||
* operations | |||
*/ | |||
public void onDragStartOnDraggableElement( | |||
final NativeEvent dragStartingEvent, | |||
final DragAndDropCallback callback) { | |||
dragStartNativePreviewHandlerRegistration = Event | |||
.addNativePreviewHandler(new NativePreviewHandler() { | |||
private int startX = WidgetUtil | |||
.getTouchOrMouseClientX(dragStartingEvent); | |||
private int startY = WidgetUtil | |||
.getTouchOrMouseClientY(dragStartingEvent); | |||
@Override | |||
public void onPreviewNativeEvent(NativePreviewEvent event) { | |||
final int typeInt = event.getTypeInt(); | |||
if (typeInt == -1 | |||
&& event.getNativeEvent().getType() | |||
.toLowerCase().contains("pointer")) { | |||
/* | |||
* Ignore PointerEvents since IE10 and IE11 send | |||
* also MouseEvents for backwards compatibility. | |||
*/ | |||
return; | |||
} | |||
switch (typeInt) { | |||
case Event.ONMOUSEOVER: | |||
case Event.ONMOUSEOUT: | |||
// we don't care | |||
break; | |||
case Event.ONKEYDOWN: | |||
case Event.ONKEYPRESS: | |||
case Event.ONKEYUP: | |||
case Event.ONBLUR: | |||
case Event.ONFOCUS: | |||
// don't cancel possible drag start | |||
break; | |||
case Event.ONMOUSEMOVE: | |||
case Event.ONTOUCHMOVE: | |||
int currentX = WidgetUtil | |||
.getTouchOrMouseClientX(event | |||
.getNativeEvent()); | |||
int currentY = WidgetUtil | |||
.getTouchOrMouseClientY(event | |||
.getNativeEvent()); | |||
if (Math.abs(startX - currentX) > 3 | |||
|| Math.abs(startY - currentY) > 3) { | |||
removeNativePreviewHandlerRegistration(); | |||
startDrag(dragStartingEvent, event, callback); | |||
} | |||
break; | |||
default: | |||
// on any other events, clean up this preview | |||
// listener | |||
removeNativePreviewHandlerRegistration(); | |||
break; | |||
} | |||
} | |||
}); | |||
} | |||
private void startDrag(NativeEvent startEvent, | |||
NativePreviewEvent triggerEvent, DragAndDropCallback callback) { | |||
if (callback.onDragStart(startEvent)) { | |||
dragging = true; | |||
// just capture something to prevent text selection in IE | |||
Event.setCapture(RootPanel.getBodyElement()); | |||
this.callback = callback; | |||
dragHandlerRegistration = Event | |||
.addNativePreviewHandler(dragHandler); | |||
callback.onDragUpdate(triggerEvent); | |||
} | |||
} | |||
private void stopDrag() { | |||
dragging = false; | |||
if (dragHandlerRegistration != null) { | |||
dragHandlerRegistration.removeHandler(); | |||
dragHandlerRegistration = null; | |||
} | |||
Event.releaseCapture(RootPanel.getBodyElement()); | |||
if (callback != null) { | |||
callback.onDragEnd(); | |||
callback = null; | |||
} | |||
} | |||
private void cancelDrag(NativePreviewEvent event) { | |||
callback.onDragCancel(); | |||
callback.onDragEnd(); | |||
stopDrag(); | |||
event.cancel(); | |||
event.getNativeEvent().preventDefault(); | |||
} | |||
private void removeNativePreviewHandlerRegistration() { | |||
if (dragStartNativePreviewHandlerRegistration != null) { | |||
dragStartNativePreviewHandlerRegistration.removeHandler(); | |||
dragStartNativePreviewHandlerRegistration = null; | |||
} | |||
} | |||
} |
@@ -16,8 +16,6 @@ | |||
package com.vaadin.client.widget.escalator; | |||
import com.vaadin.client.widgets.Escalator; | |||
/** | |||
* An interface that allows client code to define how a certain row in Escalator | |||
* will be displayed. The contents of an escalator's header, body and footer are |
@@ -17,7 +17,6 @@ | |||
package com.vaadin.client.widget.escalator; | |||
import com.google.gwt.dom.client.TableRowElement; | |||
import com.vaadin.client.widgets.Escalator; | |||
/** | |||
* A representation of a row in an {@link Escalator}. |
@@ -22,16 +22,101 @@ import com.google.gwt.dom.client.TableSectionElement; | |||
/** | |||
* A representation of the rows in each of the sections (header, body and | |||
* footer) in an {@link Escalator}. | |||
* footer) in an {@link com.vaadin.client.widgets.Escalator}. | |||
* | |||
* @since 7.4 | |||
* @author Vaadin Ltd | |||
* @see Escalator#getHeader() | |||
* @see Escalator#getBody() | |||
* @see Escalator#getFooter() | |||
* @see com.vaadin.client.widgets.Escalator#getHeader() | |||
* @see com.vaadin.client.widgets.Escalator#getBody() | |||
* @see com.vaadin.client.widgets.Escalator#getFooter() | |||
* @see SpacerContainer | |||
*/ | |||
public interface RowContainer { | |||
/** | |||
* The row container for the body section in an | |||
* {@link com.vaadin.client.widgets.Escalator}. | |||
* <p> | |||
* The body section can contain both rows and spacers. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
* @see com.vaadin.client.widgets.Escalator#getBody() | |||
*/ | |||
public interface BodyRowContainer extends RowContainer { | |||
/** | |||
* Marks a spacer and its height. | |||
* <p> | |||
* If a spacer is already registered with the given row index, that | |||
* spacer will be updated with the given height. | |||
* <p> | |||
* <em>Note:</em> The row index for a spacer will change if rows are | |||
* inserted or removed above the current position. Spacers will also be | |||
* removed alongside their associated rows | |||
* | |||
* @param rowIndex | |||
* the row index for the spacer to modify. The affected | |||
* spacer is underneath the given index. Use -1 to insert a | |||
* spacer before the first row | |||
* @param height | |||
* the pixel height of the spacer. If {@code height} is | |||
* negative, the affected spacer (if exists) will be removed | |||
* @throws IllegalArgumentException | |||
* if {@code rowIndex} is not a valid row index | |||
* @see #insertRows(int, int) | |||
* @see #removeRows(int, int) | |||
*/ | |||
void setSpacer(int rowIndex, double height) | |||
throws IllegalArgumentException; | |||
/** | |||
* Sets a new spacer updater. | |||
* <p> | |||
* Spacers that are currently visible will be updated, i.e. | |||
* {@link SpacerUpdater#destroy(Spacer) destroyed} with the previous | |||
* one, and {@link SpacerUpdater#init(Spacer) initialized} with the new | |||
* one. | |||
* | |||
* @param spacerUpdater | |||
* the new spacer updater | |||
* @throws IllegalArgumentException | |||
* if {@code spacerUpdater} is {@code null} | |||
*/ | |||
void setSpacerUpdater(SpacerUpdater spacerUpdater) | |||
throws IllegalArgumentException; | |||
/** | |||
* Gets the spacer updater currently in use. | |||
* <p> | |||
* {@link SpacerUpdater#NULL} is the default. | |||
* | |||
* @return the spacer updater currently in use. Never <code>null</code> | |||
*/ | |||
SpacerUpdater getSpacerUpdater(); | |||
/** | |||
* {@inheritDoc} | |||
* <p> | |||
* Any spacers underneath {@code index} will be offset and "pushed" | |||
* down. This also modifies the row index they are associated with. | |||
*/ | |||
@Override | |||
public void insertRows(int index, int numberOfRows) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
/** | |||
* {@inheritDoc} | |||
* <p> | |||
* Any spacers underneath {@code index} will be offset and "pulled" up. | |||
* This also modifies the row index they are associated with. Any | |||
* spacers in the removed range will also be closed and removed. | |||
*/ | |||
@Override | |||
public void removeRows(int index, int numberOfRows) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
} | |||
/** | |||
* An arbitrary pixel height of a row, before any autodetection for the row | |||
* height has been made. |
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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.client.widget.escalator; | |||
import com.google.gwt.dom.client.Element; | |||
/** | |||
* A representation of a spacer element in a | |||
* {@link com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer}. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface Spacer { | |||
/** | |||
* Gets the root element for the spacer content. | |||
* | |||
* @return the root element for the spacer content | |||
*/ | |||
Element getElement(); | |||
/** | |||
* Gets the decorative element for this spacer. | |||
*/ | |||
Element getDecoElement(); | |||
/** | |||
* Gets the row index. | |||
* | |||
* @return the row index. | |||
*/ | |||
int getRow(); | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* 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.client.widget.escalator; | |||
import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; | |||
/** | |||
* An interface that handles the display of content for spacers. | |||
* <p> | |||
* The updater is responsible for making sure all elements are properly | |||
* constructed and cleaned up. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
* @see Spacer | |||
* @see BodyRowContainer | |||
*/ | |||
public interface SpacerUpdater { | |||
/** A spacer updater that does nothing. */ | |||
public static final SpacerUpdater NULL = new SpacerUpdater() { | |||
@Override | |||
public void init(Spacer spacer) { | |||
// NOOP | |||
} | |||
@Override | |||
public void destroy(Spacer spacer) { | |||
// NOOP | |||
} | |||
}; | |||
/** | |||
* Called whenever a spacer should be initialized with content. | |||
* | |||
* @param spacer | |||
* the spacer reference that should be initialized | |||
*/ | |||
void init(Spacer spacer); | |||
/** | |||
* Called whenever a spacer should be cleaned. | |||
* <p> | |||
* The structure to clean up is the same that has been constructed by | |||
* {@link #init(Spacer)}. | |||
* | |||
* @param spacer | |||
* the spacer reference that should be destroyed | |||
*/ | |||
void destroy(Spacer spacer); | |||
} |
@@ -0,0 +1,689 @@ | |||
/* | |||
* 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.client.widget.grid; | |||
import com.google.gwt.animation.client.AnimationScheduler; | |||
import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; | |||
import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NativeEvent; | |||
import com.google.gwt.dom.client.TableElement; | |||
import com.google.gwt.dom.client.TableSectionElement; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.user.client.Event; | |||
import com.google.gwt.user.client.Event.NativePreviewEvent; | |||
import com.google.gwt.user.client.Event.NativePreviewHandler; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.client.widgets.Grid; | |||
/** | |||
* A class for handling automatic scrolling vertically / horizontally in the | |||
* Grid when the cursor is close enough the edge of the body of the grid, | |||
* depending on the scroll direction chosen. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class AutoScroller { | |||
/** | |||
* Callback that notifies when the cursor is on top of a new row or column | |||
* because of the automatic scrolling. | |||
*/ | |||
public interface AutoScrollerCallback { | |||
/** | |||
* Triggered when doing automatic scrolling. | |||
* <p> | |||
* Because the auto scroller currently only supports scrolling in one | |||
* axis, this method is used for both vertical and horizontal scrolling. | |||
* | |||
* @param scrollDiff | |||
* the amount of pixels that have been auto scrolled since | |||
* last call | |||
*/ | |||
void onAutoScroll(int scrollDiff); | |||
/** | |||
* Triggered when the grid scroll has reached the minimum scroll | |||
* position. Depending on the scroll axis, either scrollLeft or | |||
* scrollTop is 0. | |||
*/ | |||
void onAutoScrollReachedMin(); | |||
/** | |||
* Triggered when the grid scroll has reached the max scroll position. | |||
* Depending on the scroll axis, either scrollLeft or scrollTop is at | |||
* its maximum value. | |||
*/ | |||
void onAutoScrollReachedMax(); | |||
} | |||
public enum ScrollAxis { | |||
VERTICAL, HORIZONTAL | |||
} | |||
/** The maximum number of pixels per second to autoscroll. */ | |||
private static final int SCROLL_TOP_SPEED_PX_SEC = 500; | |||
/** | |||
* The minimum area where the grid doesn't scroll while the pointer is | |||
* pressed. | |||
*/ | |||
private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; | |||
/** The size of the autoscroll area, both top/left and bottom/right. */ | |||
private int scrollAreaPX = 100; | |||
/** | |||
* This class's main objective is to listen when to stop autoscrolling, and | |||
* make sure everything stops accordingly. | |||
*/ | |||
private class TouchEventHandler implements NativePreviewHandler { | |||
@Override | |||
public void onPreviewNativeEvent(final NativePreviewEvent event) { | |||
/* | |||
* Remember: targetElement is always where touchstart started, not | |||
* where the finger is pointing currently. | |||
*/ | |||
switch (event.getTypeInt()) { | |||
case Event.ONTOUCHSTART: { | |||
if (event.getNativeEvent().getTouches().length() == 1) { | |||
/* | |||
* Something has dropped a touchend/touchcancel and the | |||
* scroller is most probably running amok. Let's cancel it | |||
* and pretend that everything's going as expected | |||
* | |||
* Because this is a preview, this code is run before start | |||
* event can be passed to the start(...) method. | |||
*/ | |||
stop(); | |||
/* | |||
* Related TODO: investigate why iOS seems to ignore a | |||
* touchend/touchcancel when frames are dropped, and/or if | |||
* something can be done about that. | |||
*/ | |||
} | |||
break; | |||
} | |||
case Event.ONTOUCHMOVE: | |||
event.cancel(); | |||
break; | |||
case Event.ONTOUCHEND: | |||
case Event.ONTOUCHCANCEL: | |||
// TODO investigate if this works as desired | |||
stop(); | |||
break; | |||
} | |||
} | |||
} | |||
/** | |||
* This class's responsibility is to scroll the table while a pointer is | |||
* kept in a scrolling zone. | |||
* <p> | |||
* <em>Techical note:</em> This class is an AnimationCallback because we | |||
* need a timer: when the finger is kept in place while the grid scrolls, we | |||
* still need to be able to make new selections. So, instead of relying on | |||
* events (which won't be fired, since the pointer isn't necessarily | |||
* moving), we do this check on each frame while the pointer is "active" | |||
* (mouse is pressed, finger is on screen). | |||
*/ | |||
private class AutoScrollingFrame implements AnimationCallback { | |||
/** | |||
* If the acceleration gradient area is smaller than this, autoscrolling | |||
* will be disabled (it becomes too quick to accelerate to be usable). | |||
*/ | |||
private static final int GRADIENT_MIN_THRESHOLD_PX = 10; | |||
/** | |||
* The speed at which the gradient area recovers, once scrolling in that | |||
* direction has started. | |||
*/ | |||
private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; | |||
private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; | |||
/** | |||
* The lowest y/x-coordinate on the {@link Event#getClientY() client-y} | |||
* or {@link Event#getClientX() client-x} from where we need to start | |||
* scrolling towards the top/left. | |||
*/ | |||
private int startBound = -1; | |||
/** | |||
* The highest y/x-coordinate on the {@link Event#getClientY() client-y} | |||
* or {@link Event#getClientX() client-x} from where we need to | |||
* scrolling towards the bottom. | |||
*/ | |||
private int endBound = -1; | |||
/** | |||
* The area where the selection acceleration takes place. If < | |||
* {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled | |||
*/ | |||
private final int gradientArea; | |||
/** | |||
* The number of pixels per seconds we currently are scrolling (negative | |||
* is towards the top/left, positive is towards the bottom/right). | |||
*/ | |||
private double scrollSpeed = 0; | |||
private double prevTimestamp = 0; | |||
/** | |||
* This field stores fractions of pixels to scroll, to make sure that | |||
* we're able to scroll less than one px per frame. | |||
*/ | |||
private double pixelsToScroll = 0.0d; | |||
/** Should this animator be running. */ | |||
private boolean running = false; | |||
/** The handle in which this instance is running. */ | |||
private AnimationHandle handle; | |||
/** | |||
* The pointer's pageY (VERTICAL) / pageX (HORIZONTAL) coordinate | |||
* depending on scrolling axis. | |||
*/ | |||
private int scrollingAxisPageCoordinate; | |||
/** @see #doScrollAreaChecks(int) */ | |||
private int finalStartBound; | |||
/** @see #doScrollAreaChecks(int) */ | |||
private int finalEndBound; | |||
private boolean scrollAreaShouldRebound = false; | |||
public AutoScrollingFrame(final int startBound, final int endBound, | |||
final int gradientArea) { | |||
finalStartBound = startBound; | |||
finalEndBound = endBound; | |||
this.gradientArea = gradientArea; | |||
} | |||
@Override | |||
public void execute(final double timestamp) { | |||
final double timeDiff = timestamp - prevTimestamp; | |||
prevTimestamp = timestamp; | |||
reboundScrollArea(timeDiff); | |||
pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); | |||
final int intPixelsToScroll = (int) pixelsToScroll; | |||
pixelsToScroll -= intPixelsToScroll; | |||
if (intPixelsToScroll != 0) { | |||
double scrollPos; | |||
double maxScrollPos; | |||
double newScrollPos; | |||
if (scrollDirection == ScrollAxis.VERTICAL) { | |||
scrollPos = grid.getScrollTop(); | |||
maxScrollPos = getMaxScrollTop(); | |||
} else { | |||
scrollPos = grid.getScrollLeft(); | |||
maxScrollPos = getMaxScrollLeft(); | |||
} | |||
if (intPixelsToScroll > 0 && scrollPos < maxScrollPos | |||
|| intPixelsToScroll < 0 && scrollPos > 0) { | |||
newScrollPos = scrollPos + intPixelsToScroll; | |||
if (scrollDirection == ScrollAxis.VERTICAL) { | |||
grid.setScrollTop(newScrollPos); | |||
} else { | |||
grid.setScrollLeft(newScrollPos); | |||
} | |||
callback.onAutoScroll(intPixelsToScroll); | |||
if (newScrollPos <= 0) { | |||
callback.onAutoScrollReachedMin(); | |||
} else if (newScrollPos >= maxScrollPos) { | |||
callback.onAutoScrollReachedMax(); | |||
} | |||
} | |||
} | |||
reschedule(); | |||
} | |||
/** | |||
* If the scroll are has been offset by the pointer starting out there, | |||
* move it back a bit | |||
*/ | |||
private void reboundScrollArea(double timeDiff) { | |||
if (!scrollAreaShouldRebound) { | |||
return; | |||
} | |||
int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS | |||
* timeDiff); | |||
if (startBound < finalStartBound) { | |||
startBound += reboundPx; | |||
startBound = Math.min(startBound, finalStartBound); | |||
updateScrollSpeed(scrollingAxisPageCoordinate); | |||
} else if (endBound > finalEndBound) { | |||
endBound -= reboundPx; | |||
endBound = Math.max(endBound, finalEndBound); | |||
updateScrollSpeed(scrollingAxisPageCoordinate); | |||
} | |||
} | |||
private void updateScrollSpeed(final int pointerPageCordinate) { | |||
final double ratio; | |||
if (pointerPageCordinate < startBound) { | |||
final double distance = pointerPageCordinate - startBound; | |||
ratio = Math.max(-1, distance / gradientArea); | |||
} | |||
else if (pointerPageCordinate > endBound) { | |||
final double distance = pointerPageCordinate - endBound; | |||
ratio = Math.min(1, distance / gradientArea); | |||
} | |||
else { | |||
ratio = 0; | |||
} | |||
scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; | |||
} | |||
public void start() { | |||
running = true; | |||
reschedule(); | |||
} | |||
public void stop() { | |||
running = false; | |||
if (handle != null) { | |||
handle.cancel(); | |||
handle = null; | |||
} | |||
} | |||
private void reschedule() { | |||
if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { | |||
handle = AnimationScheduler.get().requestAnimationFrame(this, | |||
grid.getElement()); | |||
} | |||
} | |||
public void updatePointerCoords(int pageX, int pageY) { | |||
final int pageCordinate; | |||
if (scrollDirection == ScrollAxis.VERTICAL) { | |||
pageCordinate = pageY; | |||
} else { | |||
pageCordinate = pageX; | |||
} | |||
doScrollAreaChecks(pageCordinate); | |||
updateScrollSpeed(pageCordinate); | |||
scrollingAxisPageCoordinate = pageCordinate; | |||
} | |||
/** | |||
* This method checks whether the first pointer event started in an area | |||
* that would start scrolling immediately, and does some actions | |||
* accordingly. | |||
* <p> | |||
* If it is, that scroll area will be offset "beyond" the pointer (above | |||
* if pointer is towards the top/left, otherwise below/right). | |||
*/ | |||
private void doScrollAreaChecks(int pageCordinate) { | |||
/* | |||
* The first run makes sure that neither scroll position is | |||
* underneath the finger, but offset to either direction from | |||
* underneath the pointer. | |||
*/ | |||
if (startBound == -1) { | |||
startBound = Math.min(finalStartBound, pageCordinate); | |||
endBound = Math.max(finalEndBound, pageCordinate); | |||
} | |||
/* | |||
* Subsequent runs make sure that the scroll area grows (but doesn't | |||
* shrink) with the finger, but no further than the final bound. | |||
*/ | |||
else { | |||
int oldTopBound = startBound; | |||
if (startBound < finalStartBound) { | |||
startBound = Math.max(startBound, | |||
Math.min(finalStartBound, pageCordinate)); | |||
} | |||
int oldBottomBound = endBound; | |||
if (endBound > finalEndBound) { | |||
endBound = Math.min(endBound, | |||
Math.max(finalEndBound, pageCordinate)); | |||
} | |||
final boolean startDidNotMove = oldTopBound == startBound; | |||
final boolean endDidNotMove = oldBottomBound == endBound; | |||
final boolean wasMovement = pageCordinate != scrollingAxisPageCoordinate; | |||
scrollAreaShouldRebound = (startDidNotMove && endDidNotMove && wasMovement); | |||
} | |||
} | |||
} | |||
/** | |||
* This handler makes sure that pointer movements are handled. | |||
* <p> | |||
* Essentially, a native preview handler is registered (so that selection | |||
* gestures can happen outside of the selection column). The handler itself | |||
* makes sure that it's detached when the pointer is "lifted". | |||
*/ | |||
private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { | |||
@Override | |||
public void onPreviewNativeEvent(final NativePreviewEvent event) { | |||
if (autoScroller == null) { | |||
stop(); | |||
return; | |||
} | |||
final NativeEvent nativeEvent = event.getNativeEvent(); | |||
int pageY = 0; | |||
int pageX = 0; | |||
switch (event.getTypeInt()) { | |||
case Event.ONMOUSEMOVE: | |||
case Event.ONTOUCHMOVE: | |||
pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); | |||
pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); | |||
autoScroller.updatePointerCoords(pageX, pageY); | |||
break; | |||
case Event.ONMOUSEUP: | |||
case Event.ONTOUCHEND: | |||
case Event.ONTOUCHCANCEL: | |||
stop(); | |||
break; | |||
} | |||
} | |||
}; | |||
/** The registration info for {@link #scrollPreviewHandler} */ | |||
private HandlerRegistration handlerRegistration; | |||
/** | |||
* The top/left bound, as calculated from the {@link Event#getClientY() | |||
* client-y} or {@link Event#getClientX() client-x} coordinates. | |||
*/ | |||
private double startingBound = -1; | |||
/** | |||
* The bottom/right bound, as calculated from the {@link Event#getClientY() | |||
* client-y} or or {@link Event#getClientX() client-x} coordinates. | |||
*/ | |||
private int endingBound = -1; | |||
/** The size of the autoscroll acceleration area. */ | |||
private int gradientArea; | |||
private Grid<?> grid; | |||
private HandlerRegistration nativePreviewHandlerRegistration; | |||
private ScrollAxis scrollDirection; | |||
private AutoScrollingFrame autoScroller; | |||
private AutoScrollerCallback callback; | |||
/** | |||
* Creates a new instance for scrolling the given grid. | |||
* | |||
* @param grid | |||
* the grid to auto scroll | |||
*/ | |||
public AutoScroller(Grid<?> grid) { | |||
this.grid = grid; | |||
} | |||
/** | |||
* Starts the automatic scrolling detection. | |||
* | |||
* @param startEvent | |||
* the event that starts the automatic scroll | |||
* @param scrollAxis | |||
* the axis along which the scrolling should happen | |||
* @param callback | |||
* the callback for getting info about the automatic scrolling | |||
*/ | |||
public void start(final NativeEvent startEvent, ScrollAxis scrollAxis, | |||
AutoScrollerCallback callback) { | |||
scrollDirection = scrollAxis; | |||
this.callback = callback; | |||
injectNativeHandler(); | |||
start(); | |||
startEvent.preventDefault(); | |||
startEvent.stopPropagation(); | |||
} | |||
/** | |||
* Stops the automatic scrolling. | |||
*/ | |||
public void stop() { | |||
if (handlerRegistration != null) { | |||
handlerRegistration.removeHandler(); | |||
handlerRegistration = null; | |||
} | |||
if (autoScroller != null) { | |||
autoScroller.stop(); | |||
autoScroller = null; | |||
} | |||
removeNativeHandler(); | |||
} | |||
/** | |||
* Set the auto scroll area height or width depending on the scrolling axis. | |||
* This is the amount of pixels from the edge of the grid that the scroll is | |||
* triggered. | |||
* <p> | |||
* Defaults to 100px. | |||
* | |||
* @param px | |||
* the pixel height/width for the auto scroll area depending on | |||
* direction | |||
*/ | |||
public void setScrollArea(int px) { | |||
scrollAreaPX = px; | |||
} | |||
/** | |||
* Returns the size of the auto scroll area in pixels. | |||
* <p> | |||
* Defaults to 100px. | |||
* | |||
* @return size in pixels | |||
*/ | |||
public int getScrollArea() { | |||
return scrollAreaPX; | |||
} | |||
private void start() { | |||
/* | |||
* bounds are updated whenever the autoscroll cycle starts, to make sure | |||
* that the widget hasn't changed in size, moved around, or whatnot. | |||
*/ | |||
updateScrollBounds(); | |||
assert handlerRegistration == null : "handlerRegistration was not null"; | |||
assert autoScroller == null : "autoScroller was not null"; | |||
handlerRegistration = Event | |||
.addNativePreviewHandler(scrollPreviewHandler); | |||
autoScroller = new AutoScrollingFrame((int) Math.ceil(startingBound), | |||
endingBound, gradientArea); | |||
autoScroller.start(); | |||
} | |||
private void updateScrollBounds() { | |||
double startBorder = getBodyClientStart(); | |||
final int endBorder = getBodyClientEnd(); | |||
startBorder += getFrozenColumnsWidth(); | |||
final int scrollCompensation = getScrollCompensation(); | |||
startingBound = scrollCompensation + startBorder + scrollAreaPX; | |||
endingBound = scrollCompensation + endBorder - scrollAreaPX; | |||
gradientArea = scrollAreaPX; | |||
// modify bounds if they're too tightly packed | |||
if (endingBound - startingBound < MIN_NO_AUTOSCROLL_AREA_PX) { | |||
double adjustment = MIN_NO_AUTOSCROLL_AREA_PX | |||
- (endingBound - startingBound); | |||
startingBound -= adjustment / 2; | |||
endingBound += adjustment / 2; | |||
gradientArea -= adjustment / 2; | |||
} | |||
} | |||
private int getScrollCompensation() { | |||
Element cursor = grid.getElement(); | |||
int scroll = 0; | |||
while (cursor != null) { | |||
scroll -= scrollDirection == ScrollAxis.VERTICAL ? cursor | |||
.getScrollTop() : cursor.getScrollLeft(); | |||
cursor = cursor.getParentElement(); | |||
} | |||
return scroll; | |||
} | |||
private void injectNativeHandler() { | |||
removeNativeHandler(); | |||
nativePreviewHandlerRegistration = Event | |||
.addNativePreviewHandler(new TouchEventHandler()); | |||
} | |||
private void removeNativeHandler() { | |||
if (nativePreviewHandlerRegistration != null) { | |||
nativePreviewHandlerRegistration.removeHandler(); | |||
nativePreviewHandlerRegistration = null; | |||
} | |||
} | |||
private TableElement getTableElement() { | |||
final Element root = grid.getElement(); | |||
final Element tablewrapper = Element.as(root.getChild(2)); | |||
if (tablewrapper != null) { | |||
return TableElement.as(tablewrapper.getFirstChildElement()); | |||
} else { | |||
return null; | |||
} | |||
} | |||
private TableSectionElement getTbodyElement() { | |||
TableElement table = getTableElement(); | |||
if (table != null) { | |||
return table.getTBodies().getItem(0); | |||
} else { | |||
return null; | |||
} | |||
} | |||
private TableSectionElement getTheadElement() { | |||
TableElement table = getTableElement(); | |||
if (table != null) { | |||
return table.getTHead(); | |||
} else { | |||
return null; | |||
} | |||
} | |||
private TableSectionElement getTfootElement() { | |||
TableElement table = getTableElement(); | |||
if (table != null) { | |||
return table.getTFoot(); | |||
} else { | |||
return null; | |||
} | |||
} | |||
/** Get the "top" of an element in relation to "client" coordinates. */ | |||
@SuppressWarnings("static-method") | |||
private int getClientTop(final Element e) { | |||
Element cursor = e; | |||
int top = 0; | |||
while (cursor != null) { | |||
top += cursor.getOffsetTop(); | |||
cursor = cursor.getOffsetParent(); | |||
} | |||
return top; | |||
} | |||
/** Get the "left" of an element in relation to "client" coordinates. */ | |||
@SuppressWarnings("static-method") | |||
private int getClientLeft(final Element e) { | |||
Element cursor = e; | |||
int left = 0; | |||
while (cursor != null) { | |||
left += cursor.getOffsetLeft(); | |||
cursor = cursor.getOffsetParent(); | |||
} | |||
return left; | |||
} | |||
private int getBodyClientEnd() { | |||
if (scrollDirection == ScrollAxis.VERTICAL) { | |||
return getClientTop(getTfootElement()) - 1; | |||
} else { | |||
TableSectionElement tbodyElement = getTbodyElement(); | |||
return getClientLeft(tbodyElement) + tbodyElement.getOffsetWidth() | |||
- 1; | |||
} | |||
} | |||
private int getBodyClientStart() { | |||
if (scrollDirection == ScrollAxis.VERTICAL) { | |||
return getClientTop(grid.getElement()) | |||
+ getTheadElement().getOffsetHeight(); | |||
} else { | |||
return getClientLeft(getTbodyElement()); | |||
} | |||
} | |||
private double getFrozenColumnsWidth() { | |||
double value = getMultiSelectColumnWidth(); | |||
for (int i = 0; i < grid.getFrozenColumnCount(); i++) { | |||
value += grid.getColumn(i).getWidthActual(); | |||
} | |||
return value; | |||
} | |||
private double getMultiSelectColumnWidth() { | |||
if (grid.getFrozenColumnCount() >= 0 | |||
&& grid.getSelectionModel().getSelectionColumnRenderer() != null) { | |||
// frozen checkbox column is present | |||
return getTheadElement().getFirstChildElement() | |||
.getFirstChildElement().getOffsetWidth(); | |||
} | |||
return 0.0; | |||
} | |||
private double getMaxScrollLeft() { | |||
return grid.getScrollWidth() | |||
- (getTableElement().getParentElement().getOffsetWidth() - getFrozenColumnsWidth()); | |||
} | |||
private double getMaxScrollTop() { | |||
return grid.getScrollHeight() - getTfootElement().getOffsetHeight() | |||
- getTheadElement().getOffsetHeight(); | |||
} | |||
} |
@@ -32,6 +32,8 @@ import com.vaadin.client.widgets.Grid; | |||
* @since 7.4 | |||
*/ | |||
public class CellReference<T> { | |||
private int columnIndexDOM; | |||
private int columnIndex; | |||
private Grid.Column<?, T> column; | |||
private final RowReference<T> rowReference; | |||
@@ -42,13 +44,20 @@ public class CellReference<T> { | |||
/** | |||
* Sets the identifying information for this cell. | |||
* <p> | |||
* The difference between {@link #columnIndexDOM} and {@link #columnIndex} | |||
* comes from hidden columns. | |||
* | |||
* @param columnIndexDOM | |||
* the index of the column in the DOM | |||
* @param columnIndex | |||
* the index of the column | |||
* @param column | |||
* the column object | |||
*/ | |||
public void set(int columnIndex, Grid.Column<?, T> column) { | |||
public void set(int columnIndexDOM, int columnIndex, | |||
Grid.Column<?, T> column) { | |||
this.columnIndexDOM = columnIndexDOM; | |||
this.columnIndex = columnIndex; | |||
this.column = column; | |||
} | |||
@@ -82,6 +91,9 @@ public class CellReference<T> { | |||
/** | |||
* Gets the index of the column. | |||
* <p> | |||
* <em>NOTE:</em> The index includes hidden columns in the count, unlike | |||
* {@link #getColumnIndexDOM()}. | |||
* | |||
* @return the index of the column | |||
*/ | |||
@@ -89,6 +101,17 @@ public class CellReference<T> { | |||
return columnIndex; | |||
} | |||
/** | |||
* Gets the index of the cell in the DOM. The difference to | |||
* {@link #getColumnIndex()} is caused by hidden columns. | |||
* | |||
* @since 7.5.0 | |||
* @return the index of the column in the DOM | |||
*/ | |||
public int getColumnIndexDOM() { | |||
return columnIndexDOM; | |||
} | |||
/** | |||
* Gets the column objects. | |||
* | |||
@@ -113,7 +136,7 @@ public class CellReference<T> { | |||
* @return the element of the cell | |||
*/ | |||
public TableCellElement getElement() { | |||
return rowReference.getElement().getCells().getItem(columnIndex); | |||
return rowReference.getElement().getCells().getItem(columnIndexDOM); | |||
} | |||
/** |
@@ -0,0 +1,46 @@ | |||
/* | |||
* 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.client.widget.grid; | |||
import com.google.gwt.user.client.ui.Widget; | |||
/** | |||
* A callback interface for generating details for a particular row in Grid. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface DetailsGenerator { | |||
/** A details generator that provides no details */ | |||
public static final DetailsGenerator NULL = new DetailsGenerator() { | |||
@Override | |||
public Widget getDetails(int rowIndex) { | |||
return null; | |||
} | |||
}; | |||
/** | |||
* This method is called for whenever a new details row needs to be | |||
* generated. | |||
* | |||
* @param rowIndex | |||
* the index of the row for which to generate details | |||
* @return the details for the given row, or <code>null</code> to leave the | |||
* details empty. | |||
*/ | |||
Widget getDetails(int rowIndex); | |||
} |
@@ -18,6 +18,7 @@ package com.vaadin.client.widget.grid; | |||
import com.google.gwt.dom.client.TableCellElement; | |||
import com.vaadin.client.widget.escalator.Cell; | |||
import com.vaadin.client.widgets.Grid; | |||
import com.vaadin.client.widgets.Grid.Column; | |||
/** | |||
* A data class which contains information which identifies a cell being the | |||
@@ -48,11 +49,14 @@ public class EventCellReference<T> extends CellReference<T> { | |||
*/ | |||
public void set(Cell targetCell) { | |||
int row = targetCell.getRow(); | |||
int column = targetCell.getColumn(); | |||
int columnIndexDOM = targetCell.getColumn(); | |||
Column<?, T> column = grid.getVisibleColumns().get(columnIndexDOM); | |||
// At least for now we don't need to have the actual TableRowElement | |||
// available. | |||
getRowReference().set(row, grid.getDataSource().getRow(row), null); | |||
set(column, grid.getColumn(column)); | |||
int columnIndex = grid.getColumns().indexOf(column); | |||
set(columnIndexDOM, columnIndex, column); | |||
this.element = targetCell.getElement(); | |||
} |
@@ -49,12 +49,16 @@ public class RendererCellReference extends CellReference<Object> { | |||
* | |||
* @param cell | |||
* the flyweight cell to reference | |||
* @param columnIndex | |||
* the index of the column in the grid, including hidden cells | |||
* @param column | |||
* the column to reference | |||
*/ | |||
public void set(FlyweightCell cell, Grid.Column<?, ?> column) { | |||
public void set(FlyweightCell cell, int columnIndex, | |||
Grid.Column<?, ?> column) { | |||
this.cell = cell; | |||
super.set(cell.getColumn(), (Grid.Column<?, Object>) column); | |||
super.set(cell.getColumn(), columnIndex, | |||
(Grid.Column<?, Object>) column); | |||
} | |||
/** |
@@ -0,0 +1,51 @@ | |||
/* | |||
* 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.client.widget.grid.events; | |||
import com.google.gwt.event.shared.GwtEvent; | |||
/** | |||
* An event for notifying that the columns in the Grid have been reordered. | |||
* | |||
* @param <T> | |||
* The row type of the grid. The row type is the POJO type from where | |||
* the data is retrieved into the column cells. | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnReorderEvent<T> extends GwtEvent<ColumnReorderHandler<T>> { | |||
/** | |||
* Handler type. | |||
*/ | |||
private final static Type<ColumnReorderHandler<?>> TYPE = new Type<ColumnReorderHandler<?>>(); | |||
public static final Type<ColumnReorderHandler<?>> getType() { | |||
return TYPE; | |||
} | |||
@SuppressWarnings({ "rawtypes", "unchecked" }) | |||
@Override | |||
public Type<ColumnReorderHandler<T>> getAssociatedType() { | |||
return (Type) TYPE; | |||
} | |||
@Override | |||
protected void dispatch(ColumnReorderHandler<T> handler) { | |||
handler.onColumnReorder(this); | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
/* | |||
* 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.client.widget.grid.events; | |||
import com.google.gwt.event.shared.EventHandler; | |||
/** | |||
* Handler for a Grid column reorder event, called when the Grid's columns has | |||
* been reordered. | |||
* | |||
* @param <T> | |||
* The row type of the grid. The row type is the POJO type from where | |||
* the data is retrieved into the column cells. | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface ColumnReorderHandler<T> extends EventHandler { | |||
/** | |||
* A column reorder event, fired by Grid when the columns of the Grid have | |||
* been reordered. | |||
* | |||
* @param event | |||
* column reorder event | |||
*/ | |||
public void onColumnReorder(ColumnReorderEvent<T> event); | |||
} |
@@ -0,0 +1,93 @@ | |||
/* | |||
* 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.client.widget.grid.events; | |||
import com.google.gwt.event.shared.GwtEvent; | |||
import com.vaadin.client.widgets.Grid.Column; | |||
/** | |||
* An event for notifying that the columns in the Grid's have changed | |||
* visibility. | |||
* | |||
* @param <T> | |||
* The row type of the grid. The row type is the POJO type from where | |||
* the data is retrieved into the column cells. | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnVisibilityChangeEvent<T> extends | |||
GwtEvent<ColumnVisibilityChangeHandler<T>> { | |||
private final static Type<ColumnVisibilityChangeHandler<?>> TYPE = new Type<ColumnVisibilityChangeHandler<?>>(); | |||
public static final Type<ColumnVisibilityChangeHandler<?>> getType() { | |||
return TYPE; | |||
} | |||
private final Column<?, T> column; | |||
private final boolean userOriginated; | |||
private final boolean hidden; | |||
public ColumnVisibilityChangeEvent(Column<?, T> column, boolean hidden, | |||
boolean userOriginated) { | |||
this.column = column; | |||
this.hidden = hidden; | |||
this.userOriginated = userOriginated; | |||
} | |||
/** | |||
* Returns the column where the visibility change occurred. | |||
* | |||
* @return the column where the visibility change occurred. | |||
*/ | |||
public Column<?, T> getColumn() { | |||
return column; | |||
} | |||
/** | |||
* Was the column set hidden or visible. | |||
* | |||
* @return <code>true</code> if the column was hidden <code>false</code> if | |||
* it was set visible | |||
*/ | |||
public boolean isHidden() { | |||
return hidden; | |||
} | |||
/** | |||
* Is the visibility change triggered by user. | |||
* | |||
* @return <code>true</code> if the change was triggered by user, | |||
* <code>false</code> if not | |||
*/ | |||
public boolean isUserOriginated() { | |||
return userOriginated; | |||
} | |||
@SuppressWarnings({ "rawtypes", "unchecked" }) | |||
@Override | |||
public com.google.gwt.event.shared.GwtEvent.Type<ColumnVisibilityChangeHandler<T>> getAssociatedType() { | |||
return (Type) TYPE; | |||
} | |||
@Override | |||
protected void dispatch(ColumnVisibilityChangeHandler<T> handler) { | |||
handler.onVisibilityChange(this); | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
/* | |||
* 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.client.widget.grid.events; | |||
import com.google.gwt.event.shared.EventHandler; | |||
/** | |||
* Handler for a Grid column visibility change event, called when the Grid's | |||
* columns have changed visibility to hidden or visible. | |||
* | |||
* @param<T> The row type of the grid. The row type is the POJO type from where | |||
* the data is retrieved into the column cells. | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface ColumnVisibilityChangeHandler<T> extends EventHandler { | |||
/** | |||
* A column visibility change event, fired by Grid when a column in the Grid | |||
* has changed visibility. | |||
* | |||
* @param event | |||
* column visibility change event | |||
*/ | |||
public void onVisibilityChange(ColumnVisibilityChangeEvent<T> event); | |||
} |
@@ -25,11 +25,15 @@ import java.util.LinkedHashSet; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.Map.Entry; | |||
import java.util.Set; | |||
import java.util.logging.Logger; | |||
import com.google.gwt.thirdparty.guava.common.collect.BiMap; | |||
import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; | |||
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; | |||
import com.google.gwt.thirdparty.guava.common.collect.Maps; | |||
import com.google.gwt.thirdparty.guava.common.collect.Sets; | |||
import com.vaadin.data.Container.Indexed; | |||
import com.vaadin.data.Container.Indexed.ItemAddEvent; | |||
import com.vaadin.data.Container.Indexed.ItemRemoveEvent; | |||
@@ -45,12 +49,16 @@ import com.vaadin.server.ClientConnector; | |||
import com.vaadin.server.KeyMapper; | |||
import com.vaadin.shared.data.DataProviderRpc; | |||
import com.vaadin.shared.data.DataRequestRpc; | |||
import com.vaadin.shared.ui.grid.DetailsConnectorChange; | |||
import com.vaadin.shared.ui.grid.GridState; | |||
import com.vaadin.shared.ui.grid.Range; | |||
import com.vaadin.shared.util.SharedUtil; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.Grid.CellReference; | |||
import com.vaadin.ui.Grid.CellStyleGenerator; | |||
import com.vaadin.ui.Grid.Column; | |||
import com.vaadin.ui.Grid.DetailsGenerator; | |||
import com.vaadin.ui.Grid.RowReference; | |||
import com.vaadin.ui.Grid.RowStyleGenerator; | |||
import com.vaadin.ui.renderers.Renderer; | |||
@@ -110,11 +118,16 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
} | |||
for (Object itemId : itemsRemoved) { | |||
detailComponentManager.destroyDetails(itemId); | |||
itemIdToKey.remove(itemId); | |||
} | |||
for (Object itemId : itemSet) { | |||
itemIdToKey.put(itemId, getKey(itemId)); | |||
if (visibleDetails.contains(itemId)) { | |||
detailComponentManager.createDetails(itemId, | |||
indexOf(itemId)); | |||
} | |||
} | |||
} | |||
@@ -122,7 +135,7 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
return String.valueOf(rollingIndex++); | |||
} | |||
String getKey(Object itemId) { | |||
public String getKey(Object itemId) { | |||
String key = itemIdToKey.get(itemId); | |||
if (key == null) { | |||
key = nextKey(); | |||
@@ -571,6 +584,270 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
} | |||
} | |||
/** | |||
* A class that makes detail component related internal communication | |||
* possible between {@link RpcDataProviderExtension} and grid. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public static final class DetailComponentManager implements Serializable { | |||
/** | |||
* This map represents all the components that have been requested for | |||
* each item id. | |||
* <p> | |||
* Normally this map is consistent with what is displayed in the | |||
* component hierarchy (and thus the DOM). The only time this map is out | |||
* of sync with the DOM is between the any calls to | |||
* {@link #createDetails(Object, int)} or | |||
* {@link #destroyDetails(Object)}, and | |||
* {@link GridClientRpc#setDetailsConnectorChanges(Set)}. | |||
* <p> | |||
* This is easily checked: if {@link #unattachedComponents} is | |||
* {@link Collection#isEmpty() empty}, then this field is consistent | |||
* with the connector hierarchy. | |||
*/ | |||
private final Map<Object, Component> visibleDetailsComponents = Maps | |||
.newHashMap(); | |||
/** A lookup map for which row contains which details component. */ | |||
private BiMap<Integer, Component> rowIndexToDetails = HashBiMap | |||
.create(); | |||
/** | |||
* A copy of {@link #rowIndexToDetails} from its last stable state. Used | |||
* for creating a diff against {@link #rowIndexToDetails}. | |||
* | |||
* @see #getAndResetConnectorChanges() | |||
*/ | |||
private BiMap<Integer, Component> prevRowIndexToDetails = HashBiMap | |||
.create(); | |||
/** | |||
* A set keeping track on components that have been created, but not | |||
* attached. They should be attached at some later point in time. | |||
* <p> | |||
* This isn't strictly requried, but it's a handy explicit log. You | |||
* could find out the same thing by taking out all the other components | |||
* and checking whether Grid is their parent or not. | |||
*/ | |||
private final Set<Component> unattachedComponents = Sets.newHashSet(); | |||
/** | |||
* Keeps tabs on all the details that did not get a component during | |||
* {@link #createDetails(Object, int)}. | |||
*/ | |||
private final Map<Object, Integer> emptyDetails = Maps.newHashMap(); | |||
private Grid grid; | |||
/** | |||
* Creates a details component by the request of the client side, with | |||
* the help of the user-defined {@link DetailsGenerator}. | |||
* <p> | |||
* Also keeps internal bookkeeping up to date. | |||
* | |||
* @param itemId | |||
* the item id for which to create the details component. | |||
* Assumed not <code>null</code> and that a component is not | |||
* currently present for this item previously | |||
* @param rowIndex | |||
* the row index for {@code itemId} | |||
* @throws IllegalStateException | |||
* if the current details generator provides a component | |||
* that was manually attached, or if the same instance has | |||
* already been provided | |||
*/ | |||
public void createDetails(Object itemId, int rowIndex) | |||
throws IllegalStateException { | |||
assert itemId != null : "itemId was null"; | |||
Integer newRowIndex = Integer.valueOf(rowIndex); | |||
if (visibleDetailsComponents.containsKey(itemId)) { | |||
// Don't overwrite existing components | |||
return; | |||
} | |||
RowReference rowReference = new RowReference(grid); | |||
rowReference.set(itemId); | |||
DetailsGenerator detailsGenerator = grid.getDetailsGenerator(); | |||
Component details = detailsGenerator.getDetails(rowReference); | |||
if (details != null) { | |||
String generatorName = detailsGenerator.getClass().getName(); | |||
if (details.getParent() != null) { | |||
throw new IllegalStateException(generatorName | |||
+ " generated a details component that already " | |||
+ "was attached. (itemId: " + itemId + ", row: " | |||
+ rowIndex + ", component: " + details); | |||
} | |||
if (rowIndexToDetails.containsValue(details)) { | |||
throw new IllegalStateException(generatorName | |||
+ " provided a details component that already " | |||
+ "exists in Grid. (itemId: " + itemId + ", row: " | |||
+ rowIndex + ", component: " + details); | |||
} | |||
visibleDetailsComponents.put(itemId, details); | |||
rowIndexToDetails.put(newRowIndex, details); | |||
unattachedComponents.add(details); | |||
assert !emptyDetails.containsKey(itemId) : "Bookeeping thinks " | |||
+ "itemId is empty even though we just created a " | |||
+ "component for it (" + itemId + ")"; | |||
} else { | |||
assert assertItemIdHasNotMovedAndNothingIsOverwritten(itemId, | |||
newRowIndex); | |||
emptyDetails.put(itemId, newRowIndex); | |||
} | |||
/* | |||
* Don't attach the components here. It's done by | |||
* GridServerRpc.sendDetailsComponents in a separate roundtrip. | |||
*/ | |||
} | |||
private boolean assertItemIdHasNotMovedAndNothingIsOverwritten( | |||
Object itemId, Integer newRowIndex) { | |||
Integer oldRowIndex = emptyDetails.get(itemId); | |||
if (!SharedUtil.equals(oldRowIndex, newRowIndex)) { | |||
assert !emptyDetails.containsKey(itemId) : "Unexpected " | |||
+ "change of empty details row index for itemId " | |||
+ itemId + " from " + oldRowIndex + " to " | |||
+ newRowIndex; | |||
assert !emptyDetails.containsValue(newRowIndex) : "Bookkeeping" | |||
+ " already had another itemId for this empty index " | |||
+ "(index: " + newRowIndex + ", new itemId: " + itemId | |||
+ ")"; | |||
} | |||
return true; | |||
} | |||
/** | |||
* Destroys correctly a details component, by the request of the client | |||
* side. | |||
* <p> | |||
* Also keeps internal bookkeeping up to date. | |||
* | |||
* @param itemId | |||
* the item id for which to destroy the details component | |||
*/ | |||
public void destroyDetails(Object itemId) { | |||
emptyDetails.remove(itemId); | |||
Component removedComponent = visibleDetailsComponents | |||
.remove(itemId); | |||
if (removedComponent == null) { | |||
return; | |||
} | |||
rowIndexToDetails.inverse().remove(removedComponent); | |||
removedComponent.setParent(null); | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Gets all details components that are currently attached to the grid. | |||
* <p> | |||
* Used internally by the Grid object. | |||
* | |||
* @return all details components that are currently attached to the | |||
* grid | |||
*/ | |||
public Collection<Component> getComponents() { | |||
Set<Component> components = new HashSet<Component>( | |||
visibleDetailsComponents.values()); | |||
components.removeAll(unattachedComponents); | |||
return components; | |||
} | |||
/** | |||
* Gets information on how the connectors have changed. | |||
* <p> | |||
* This method only returns the changes that have been made between two | |||
* calls of this method. I.e. Calling this method once will reset the | |||
* state for the next state. | |||
* <p> | |||
* Used internally by the Grid object. | |||
* | |||
* @return information on how the connectors have changed | |||
*/ | |||
public Set<DetailsConnectorChange> getAndResetConnectorChanges() { | |||
Set<DetailsConnectorChange> changes = new HashSet<DetailsConnectorChange>(); | |||
// populate diff with added/changed | |||
for (Entry<Integer, Component> entry : rowIndexToDetails.entrySet()) { | |||
Component component = entry.getValue(); | |||
assert component != null : "rowIndexToDetails contains a null component"; | |||
Integer newIndex = entry.getKey(); | |||
Integer oldIndex = prevRowIndexToDetails.inverse().get( | |||
component); | |||
/* | |||
* only attach components. Detaching already happened in | |||
* destroyDetails. | |||
*/ | |||
if (newIndex != null && oldIndex == null) { | |||
assert unattachedComponents.contains(component) : "unattachedComponents does not contain component for index " | |||
+ newIndex + " (" + component + ")"; | |||
component.setParent(grid); | |||
unattachedComponents.remove(component); | |||
} | |||
if (!SharedUtil.equals(oldIndex, newIndex)) { | |||
changes.add(new DetailsConnectorChange(component, oldIndex, | |||
newIndex, emptyDetails.containsKey(component))); | |||
} | |||
} | |||
// populate diff with removed | |||
for (Entry<Integer, Component> entry : prevRowIndexToDetails | |||
.entrySet()) { | |||
Integer oldIndex = entry.getKey(); | |||
Component component = entry.getValue(); | |||
Integer newIndex = rowIndexToDetails.inverse().get(component); | |||
if (newIndex == null) { | |||
changes.add(new DetailsConnectorChange(null, oldIndex, | |||
null, emptyDetails.containsValue(oldIndex))); | |||
} | |||
} | |||
// reset diff map | |||
prevRowIndexToDetails = HashBiMap.create(rowIndexToDetails); | |||
return changes; | |||
} | |||
public void refresh(Object itemId) { | |||
Component component = visibleDetailsComponents.get(itemId); | |||
Integer rowIndex = null; | |||
if (component != null) { | |||
rowIndex = rowIndexToDetails.inverse().get(component); | |||
destroyDetails(itemId); | |||
} else { | |||
rowIndex = emptyDetails.remove(itemId); | |||
} | |||
assert rowIndex != null : "Given itemId does not map to an " | |||
+ "existing detail row (" + itemId + ")"; | |||
createDetails(itemId, rowIndex.intValue()); | |||
} | |||
void setGrid(Grid grid) { | |||
if (this.grid != null) { | |||
throw new IllegalStateException("Grid may injected only once."); | |||
} | |||
this.grid = grid; | |||
} | |||
} | |||
private final Indexed container; | |||
private final ActiveRowHandler activeRowHandler = new ActiveRowHandler(); | |||
@@ -672,6 +949,14 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
/** Size possibly changed with a bare ItemSetChangeEvent */ | |||
private boolean bareItemSetTriggeredSizeChange = false; | |||
/** | |||
* This map represents all the details that are user-defined as visible. | |||
* This does not reflect the status in the DOM. | |||
*/ | |||
private Set<Object> visibleDetails = new HashSet<Object>(); | |||
private final DetailComponentManager detailComponentManager = new DetailComponentManager(); | |||
/** | |||
* Creates a new data provider using the given container. | |||
* | |||
@@ -814,6 +1099,10 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
rowObject.put(GridState.JSONKEY_DATA, rowData); | |||
rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId)); | |||
if (visibleDetails.contains(itemId)) { | |||
rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true); | |||
} | |||
rowReference.set(itemId); | |||
CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator(); | |||
@@ -863,9 +1152,12 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
* | |||
* @param component | |||
* the remote data grid component to extend | |||
* @param columnKeys | |||
* the key mapper for columns | |||
*/ | |||
public void extend(Grid component, KeyMapper<Object> columnKeys) { | |||
this.columnKeys = columnKeys; | |||
detailComponentManager.setGrid(component); | |||
super.extend(component); | |||
} | |||
@@ -949,6 +1241,10 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
JsonArray rowArray = Json.createArray(); | |||
rowArray.set(0, row); | |||
rpc.setRowData(index, rowArray); | |||
if (isDetailsVisible(itemId)) { | |||
detailComponentManager.createDetails(itemId, index); | |||
} | |||
} | |||
} | |||
@@ -1071,4 +1367,84 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
return Logger.getLogger(RpcDataProviderExtension.class.getName()); | |||
} | |||
/** | |||
* Marks a row's details to be visible or hidden. | |||
* <p> | |||
* If that row is currently in the client side's cache, this information | |||
* will be sent over to the client. | |||
* | |||
* @since 7.5.0 | |||
* @param itemId | |||
* the id of the item of which to change the details visibility | |||
* @param visible | |||
* <code>true</code> to show the details, <code>false</code> to | |||
* hide | |||
*/ | |||
public void setDetailsVisible(Object itemId, boolean visible) { | |||
final boolean modified; | |||
if (visible) { | |||
modified = visibleDetails.add(itemId); | |||
/* | |||
* We don't want to create the component here, since the component | |||
* might be out of view, and thus we don't know where the details | |||
* should end up on the client side. This is also a great thing to | |||
* optimize away, so that in case a lot of things would be opened at | |||
* once, a huge chunk of data doesn't get sent over immediately. | |||
*/ | |||
} else { | |||
modified = visibleDetails.remove(itemId); | |||
/* | |||
* Here we can try to destroy the component no matter what. The | |||
* component has been removed and should be detached from the | |||
* component hierarchy. The details row will be closed on the client | |||
* side automatically. | |||
*/ | |||
detailComponentManager.destroyDetails(itemId); | |||
} | |||
int rowIndex = indexOf(itemId); | |||
boolean modifiedRowIsActive = activeRowHandler.activeRange | |||
.contains(rowIndex); | |||
if (modified && modifiedRowIsActive) { | |||
updateRowData(itemId); | |||
} | |||
} | |||
/** | |||
* Checks whether the details for a row is marked as visible. | |||
* | |||
* @since 7.5.0 | |||
* @param itemId | |||
* the id of the item of which to check the visibility | |||
* @return <code>true</code> iff the detials are visible for the item. This | |||
* might return <code>true</code> even if the row is not currently | |||
* visible in the DOM | |||
*/ | |||
public boolean isDetailsVisible(Object itemId) { | |||
return visibleDetails.contains(itemId); | |||
} | |||
public void refreshDetails() { | |||
for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { | |||
detailComponentManager.refresh(itemId); | |||
} | |||
} | |||
private int indexOf(Object itemId) { | |||
/* | |||
* It would be great if we could optimize this method away, since the | |||
* normal usage of Grid doesn't need any indices to be known. It was | |||
* already optimized away once, maybe we can do away with these as well. | |||
*/ | |||
return container.indexOfId(itemId); | |||
} | |||
/** Gets the detail component manager for this data provider */ | |||
public DetailComponentManager getDetailComponentManager() { | |||
return detailComponentManager; | |||
} | |||
} |
@@ -18,6 +18,7 @@ package com.vaadin.ui; | |||
import java.io.Serializable; | |||
import java.lang.reflect.Method; | |||
import java.lang.reflect.Type; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
@@ -52,10 +53,10 @@ import com.vaadin.data.Item; | |||
import com.vaadin.data.Property; | |||
import com.vaadin.data.RpcDataProviderExtension; | |||
import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; | |||
import com.vaadin.data.RpcDataProviderExtension.DetailComponentManager; | |||
import com.vaadin.data.Validator.InvalidValueException; | |||
import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory; | |||
import com.vaadin.data.fieldgroup.FieldGroup; | |||
import com.vaadin.data.fieldgroup.FieldGroup.BindException; | |||
import com.vaadin.data.fieldgroup.FieldGroup.CommitException; | |||
import com.vaadin.data.fieldgroup.FieldGroupFieldFactory; | |||
import com.vaadin.data.sort.Sort; | |||
@@ -74,6 +75,7 @@ import com.vaadin.event.SortEvent.SortListener; | |||
import com.vaadin.event.SortEvent.SortNotifier; | |||
import com.vaadin.server.AbstractClientConnector; | |||
import com.vaadin.server.AbstractExtension; | |||
import com.vaadin.server.EncodeResult; | |||
import com.vaadin.server.ErrorMessage; | |||
import com.vaadin.server.JsonCodec; | |||
import com.vaadin.server.KeyMapper; | |||
@@ -173,6 +175,120 @@ import elemental.json.JsonValue; | |||
public class Grid extends AbstractComponent implements SelectionNotifier, | |||
SortNotifier, SelectiveRenderer, ItemClickNotifier { | |||
/** | |||
* An event listener for column visibility change events in the Grid. | |||
* | |||
* @since 7.5.0 | |||
*/ | |||
public interface ColumnVisibilityChangeListener extends Serializable { | |||
/** | |||
* Called when a column has become hidden or unhidden. | |||
* | |||
* @param event | |||
*/ | |||
void columnVisibilityChanged(ColumnVisibilityChangeEvent event); | |||
} | |||
/** | |||
* An event that is fired when a column's visibility changes. | |||
* | |||
* @since 7.5.0 | |||
*/ | |||
public static class ColumnVisibilityChangeEvent extends Component.Event { | |||
private final Column column; | |||
private final boolean userOriginated; | |||
private final boolean hidden; | |||
/** | |||
* Constructor for a column visibility change event. | |||
* | |||
* @param source | |||
* the grid from which this event originates | |||
* @param column | |||
* the column that changed its visibility | |||
* @param hidden | |||
* <code>true</code> if the column was hidden, | |||
* <code>false</code> if it became visible | |||
* @param isUserOriginated | |||
* <code>true</code> iff the event was triggered by an UI | |||
* interaction | |||
*/ | |||
public ColumnVisibilityChangeEvent(Grid source, Column column, | |||
boolean hidden, boolean isUserOriginated) { | |||
super(source); | |||
this.column = column; | |||
this.hidden = hidden; | |||
userOriginated = isUserOriginated; | |||
} | |||
/** | |||
* Gets the column that became hidden or visible. | |||
* | |||
* @return the column that became hidden or visible. | |||
* @see Column#isHidden() | |||
*/ | |||
public Column getColumn() { | |||
return column; | |||
} | |||
/** | |||
* Was the column set hidden or visible. | |||
* | |||
* @return <code>true</code> if the column was hidden <code>false</code> | |||
* if it was set visible | |||
*/ | |||
public boolean isHidden() { | |||
return hidden; | |||
} | |||
/** | |||
* Returns <code>true</code> if the column reorder was done by the user, | |||
* <code>false</code> if not and it was triggered by server side code. | |||
* | |||
* @return <code>true</code> if event is a result of user interaction | |||
*/ | |||
public boolean isUserOriginated() { | |||
return userOriginated; | |||
} | |||
} | |||
/** | |||
* A callback interface for generating details for a particular row in Grid. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
* @see DetailsGenerator#NULL | |||
*/ | |||
public interface DetailsGenerator extends Serializable { | |||
/** A details generator that provides no details */ | |||
public DetailsGenerator NULL = new DetailsGenerator() { | |||
@Override | |||
public Component getDetails(RowReference rowReference) { | |||
return null; | |||
} | |||
}; | |||
/** | |||
* This method is called for whenever a new details row needs to be | |||
* generated. | |||
* <p> | |||
* <em>Note:</em> If a component gets generated, it may not be manually | |||
* attached anywhere, nor may it be a reused instance – each | |||
* invocation of this method should produce a unique and isolated | |||
* component instance. Essentially, this should mostly be a | |||
* self-contained fire-and-forget method, as external references to the | |||
* generated component might cause unexpected behavior. | |||
* | |||
* @param rowReference | |||
* the reference for the row for which to generate details | |||
* @return the details for the given row, or <code>null</code> to leave | |||
* the details empty. | |||
*/ | |||
Component getDetails(RowReference rowReference); | |||
} | |||
/** | |||
* Custom field group that allows finding property types before an item has | |||
* been bound. | |||
@@ -342,6 +458,58 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
} | |||
/** | |||
* An event listener for column reorder events in the Grid. | |||
* | |||
* @since 7.5.0 | |||
*/ | |||
public interface ColumnReorderListener extends Serializable { | |||
/** | |||
* Called when the columns of the grid have been reordered. | |||
* | |||
* @param event | |||
* An event providing more information | |||
*/ | |||
void columnReorder(ColumnReorderEvent event); | |||
} | |||
/** | |||
* An event that is fired when the columns are reordered. | |||
* | |||
* @since 7.5.0 | |||
*/ | |||
public static class ColumnReorderEvent extends Component.Event { | |||
/** | |||
* Is the column reorder related to this event initiated by the user | |||
*/ | |||
private final boolean userOriginated; | |||
/** | |||
* | |||
* @param source | |||
* the grid where the event originated from | |||
* @param userOriginated | |||
* <code>true</code> if event is a result of user | |||
* interaction, <code>false</code> if from API call | |||
*/ | |||
public ColumnReorderEvent(Grid source, boolean userOriginated) { | |||
super(source); | |||
this.userOriginated = userOriginated; | |||
} | |||
/** | |||
* Returns <code>true</code> if the column reorder was done by the user, | |||
* <code>false</code> if not and it was triggered by server side code. | |||
* | |||
* @return <code>true</code> if event is a result of user interaction | |||
*/ | |||
public boolean isUserOriginated() { | |||
return userOriginated; | |||
} | |||
} | |||
/** | |||
* Default error handler for the editor | |||
* | |||
@@ -2355,6 +2523,46 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
return this; | |||
} | |||
/** | |||
* Gets the caption of the hiding toggle for this column. | |||
* | |||
* @since | |||
* @see #setHidingToggleCaption(String) | |||
* @return the caption for the hiding toggle for this column | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public String getHidingToggleCaption() throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
return state.hidingToggleCaption; | |||
} | |||
/** | |||
* Sets the caption of the hiding toggle for this column. Shown in the | |||
* toggle for this column in the grid's sidebar when the column is | |||
* {@link #isHidable() hidable}. | |||
* <p> | |||
* By default, before triggering this setter, a user friendly version of | |||
* the column's {@link #getPropertyId() property id} is used. | |||
* <p> | |||
* <em>NOTE:</em> setting this to <code>null</code> or empty string | |||
* might cause the hiding toggle to not render correctly. | |||
* | |||
* @since | |||
* @param hidingToggleCaption | |||
* the text to show in the column hiding toggle | |||
* @return the column itself | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public Column setHidingToggleCaption(String hidingToggleCaption) | |||
throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
state.hidingToggleCaption = hidingToggleCaption; | |||
grid.markAsDirty(); | |||
return this; | |||
} | |||
/** | |||
* Returns the width (in pixels). By default a column is 100px wide. | |||
* | |||
@@ -2885,7 +3093,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
* Getting a field before the editor has been opened depends on special | |||
* support from the {@link FieldGroup} in use. Using this method with a | |||
* user-provided <code>FieldGroup</code> might cause | |||
* {@link BindException} to be thrown. | |||
* {@link com.vaadin.data.fieldgroup.FieldGroup.BindException | |||
* BindException} to be thrown. | |||
* | |||
* @return the bound field; or <code>null</code> if the respective | |||
* column is not editable | |||
@@ -2901,13 +3110,79 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
} | |||
/** | |||
* Hides or shows the column. By default columns are visible before | |||
* explicitly hiding them. | |||
* | |||
* @since 7.5.0 | |||
* @param hidden | |||
* <code>true</code> to hide the column, <code>false</code> | |||
* to show | |||
* @return this column | |||
*/ | |||
public Column setHidden(boolean hidden) { | |||
if (hidden != getState().hidden) { | |||
getState().hidden = hidden; | |||
grid.markAsDirty(); | |||
grid.fireColumnVisibilityChangeEvent(this, hidden, false); | |||
} | |||
return this; | |||
} | |||
/** | |||
* Is this column hidden. Default is {@code false}. | |||
* | |||
* @since 7.5.0 | |||
* @return <code>true</code> if the column is currently hidden, | |||
* <code>false</code> otherwise | |||
*/ | |||
public boolean isHidden() { | |||
return getState().hidden; | |||
} | |||
/** | |||
* Set whether it is possible for the user to hide this column or not. | |||
* Default is {@code false}. | |||
* <p> | |||
* <em>Note:</em> it is still possible to hide the column | |||
* programmatically using {@link #setHidden(boolean)} | |||
* | |||
* @since 7.5.0 | |||
* @param hidable | |||
* <code>true</code> iff the column may be hidable by the | |||
* user via UI interaction | |||
* @return this column | |||
*/ | |||
public Column setHidable(boolean hidable) { | |||
if (hidable != getState().hidable) { | |||
getState().hidable = hidable; | |||
grid.markAsDirty(); | |||
} | |||
return this; | |||
} | |||
/** | |||
* Is it possible for the the user to hide this column. Default is | |||
* {@code false}. | |||
* <p> | |||
* <em>Note:</em> the column can be programmatically hidden using | |||
* {@link #setHidden(boolean)} regardless of the returned value. | |||
* | |||
* @since 7.5.0 | |||
* @return <code>true</code> if the user can hide the column, | |||
* <code>false</code> if not | |||
*/ | |||
public boolean isHidable() { | |||
return getState().hidable; | |||
} | |||
/* | |||
* Writes the design attributes for this column into given element. | |||
* | |||
* @since | |||
* @param design | |||
* Element to write attributes into | |||
* @param designContext | |||
* the design context | |||
* | |||
* @param design Element to write attributes into | |||
* | |||
* @param designContext the design context | |||
*/ | |||
protected void writeDesign(Element design, DesignContext designContext) { | |||
Attributes attributes = design.attributes(); | |||
@@ -2925,6 +3200,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
getMaximumWidth(), def.maxWidth, Double.class); | |||
DesignAttributeHandler.writeAttribute("expand", attributes, | |||
getExpandRatio(), def.expandRatio, Integer.class); | |||
DesignAttributeHandler.writeAttribute("hidable", attributes, | |||
isHidable(), def.hidable, boolean.class); | |||
DesignAttributeHandler.writeAttribute("hidden", attributes, | |||
isHidden(), def.hidden, boolean.class); | |||
DesignAttributeHandler.writeAttribute("hiding-toggle-caption", | |||
attributes, getHidingToggleCaption(), | |||
SharedUtil.propertyIdToHumanFriendly(getPropertyId()), | |||
String.class); | |||
DesignAttributeHandler.writeAttribute("property-id", attributes, | |||
getPropertyId(), null, Object.class); | |||
} | |||
@@ -2950,7 +3233,18 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
setEditable(DesignAttributeHandler.readAttribute("editable", | |||
attributes, boolean.class)); | |||
} | |||
if (design.hasAttr("hidable")) { | |||
setHidable(DesignAttributeHandler.readAttribute("hidable", | |||
attributes, boolean.class)); | |||
} | |||
if (design.hasAttr("hidden")) { | |||
setHidden(DesignAttributeHandler.readAttribute("hidden", | |||
attributes, boolean.class)); | |||
} | |||
if (design.hasAttr("hiding-toggle-caption")) { | |||
setHidingToggleCaption(DesignAttributeHandler.readAttribute( | |||
"hiding-toggle-caption", attributes, String.class)); | |||
} | |||
// Read size info where necessary. | |||
if (design.hasAttr("width")) { | |||
setWidth(DesignAttributeHandler.readAttribute("width", | |||
@@ -3202,12 +3496,30 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler(); | |||
/** | |||
* The user-defined details generator. | |||
* | |||
* @see #setDetailsGenerator(DetailsGenerator) | |||
*/ | |||
private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; | |||
private DetailComponentManager detailComponentManager = null; | |||
private static final Method SELECTION_CHANGE_METHOD = ReflectTools | |||
.findMethod(SelectionListener.class, "select", SelectionEvent.class); | |||
private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools | |||
.findMethod(SortListener.class, "sort", SortEvent.class); | |||
private static final Method COLUMN_REORDER_METHOD = ReflectTools | |||
.findMethod(ColumnReorderListener.class, "columnReorder", | |||
ColumnReorderEvent.class); | |||
private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools | |||
.findMethod(ColumnVisibilityChangeListener.class, | |||
"columnVisibilityChanged", | |||
ColumnVisibilityChangeEvent.class); | |||
/** | |||
* Creates a new Grid with a new {@link IndexedContainer} as the data | |||
* source. | |||
@@ -3402,6 +3714,87 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
fireEvent(new ItemClickEvent(Grid.this, item, itemId, | |||
propertyId, details)); | |||
} | |||
@Override | |||
public void columnsReordered(List<String> newColumnOrder, | |||
List<String> oldColumnOrder) { | |||
final String diffStateKey = "columnOrder"; | |||
ConnectorTracker connectorTracker = getUI() | |||
.getConnectorTracker(); | |||
JsonObject diffState = connectorTracker.getDiffState(Grid.this); | |||
// discard the change if the columns have been reordered from | |||
// the server side, as the server side is always right | |||
if (getState(false).columnOrder.equals(oldColumnOrder)) { | |||
// Don't mark as dirty since client has the state already | |||
getState(false).columnOrder = newColumnOrder; | |||
// write changes to diffState so that possible reverting the | |||
// column order is sent to client | |||
assert diffState.hasKey(diffStateKey) : "Field name has changed"; | |||
Type type = null; | |||
try { | |||
type = (getState(false).getClass().getDeclaredField( | |||
diffStateKey).getGenericType()); | |||
} catch (NoSuchFieldException e) { | |||
e.printStackTrace(); | |||
} catch (SecurityException e) { | |||
e.printStackTrace(); | |||
} | |||
EncodeResult encodeResult = JsonCodec.encode( | |||
getState(false).columnOrder, diffState, type, | |||
connectorTracker); | |||
diffState.put(diffStateKey, encodeResult.getEncodedValue()); | |||
fireColumnReorderEvent(true); | |||
} else { | |||
// make sure the client is reverted to the order that the | |||
// server thinks it is | |||
diffState.remove(diffStateKey); | |||
markAsDirty(); | |||
} | |||
} | |||
@Override | |||
public void columnVisibilityChanged(String id, boolean hidden, | |||
boolean userOriginated) { | |||
final Column column = getColumnByColumnId(id); | |||
final GridColumnState columnState = column.getState(); | |||
if (columnState.hidden != hidden) { | |||
columnState.hidden = hidden; | |||
final String diffStateKey = "columns"; | |||
ConnectorTracker connectorTracker = getUI() | |||
.getConnectorTracker(); | |||
JsonObject diffState = connectorTracker | |||
.getDiffState(Grid.this); | |||
assert diffState.hasKey(diffStateKey) : "Field name has changed"; | |||
Type type = null; | |||
try { | |||
type = (getState(false).getClass().getDeclaredField( | |||
diffStateKey).getGenericType()); | |||
} catch (NoSuchFieldException e) { | |||
e.printStackTrace(); | |||
} catch (SecurityException e) { | |||
e.printStackTrace(); | |||
} | |||
EncodeResult encodeResult = JsonCodec.encode( | |||
getState(false).columns, diffState, type, | |||
connectorTracker); | |||
diffState.put(diffStateKey, encodeResult.getEncodedValue()); | |||
fireColumnVisibilityChangeEvent(column, hidden, | |||
userOriginated); | |||
} | |||
} | |||
@Override | |||
public void sendDetailsComponents(int fetchId) { | |||
getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( | |||
detailComponentManager.getAndResetConnectorChanges(), | |||
fetchId); | |||
} | |||
}); | |||
registerRpc(new EditorServerRpc() { | |||
@@ -3565,6 +3958,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
datasourceExtension = new RpcDataProviderExtension(container); | |||
datasourceExtension.extend(this, columnKeys); | |||
detailComponentManager = datasourceExtension | |||
.getDetailComponentManager(); | |||
/* | |||
* selectionModel == null when the invocation comes from the | |||
* constructor. | |||
@@ -3783,6 +4179,31 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
return columnKeys.get(columnId); | |||
} | |||
/** | |||
* Returns whether column reordering is allowed. Default value is | |||
* <code>false</code>. | |||
* | |||
* @since 7.5.0 | |||
* @return true if reordering is allowed | |||
*/ | |||
public boolean isColumnReorderingAllowed() { | |||
return getState(false).columnReorderingAllowed; | |||
} | |||
/** | |||
* Sets whether or not column reordering is allowed. Default value is | |||
* <code>false</code>. | |||
* | |||
* @since 7.5.0 | |||
* @param columnReorderingAllowed | |||
* specifies whether column reordering is allowed | |||
*/ | |||
public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { | |||
if (isColumnReorderingAllowed() != columnReorderingAllowed) { | |||
getState().columnReorderingAllowed = columnReorderingAllowed; | |||
} | |||
} | |||
@Override | |||
protected GridState getState() { | |||
return (GridState) super.getState(); | |||
@@ -3818,8 +4239,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
header.addColumn(datasourcePropertyId); | |||
footer.addColumn(datasourcePropertyId); | |||
column.setHeaderCaption(SharedUtil.propertyIdToHumanFriendly(String | |||
.valueOf(datasourcePropertyId))); | |||
String humanFriendlyPropertyId = SharedUtil | |||
.propertyIdToHumanFriendly(String.valueOf(datasourcePropertyId)); | |||
column.setHeaderCaption(humanFriendlyPropertyId); | |||
column.setHidingToggleCaption(humanFriendlyPropertyId); | |||
if (datasource instanceof Sortable | |||
&& ((Sortable) datasource).getSortableContainerPropertyIds() | |||
@@ -3910,6 +4333,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
columnOrder.addAll(stateColumnOrder); | |||
} | |||
getState().columnOrder = columnOrder; | |||
fireColumnReorderEvent(false); | |||
} | |||
/** | |||
@@ -3941,6 +4365,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
* columns will be frozen, but the built-in selection checkbox column will | |||
* still be frozen if it's in use. -1 means that not even the selection | |||
* column is frozen. | |||
* <p> | |||
* <em>NOTE:</em> this count includes {@link Column#isHidden() hidden | |||
* columns} in the count. | |||
* | |||
* @see #setFrozenColumnCount(int) | |||
* | |||
@@ -3952,6 +4379,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
/** | |||
* Scrolls to a certain item, using {@link ScrollDestination#ANY}. | |||
* <p> | |||
* If the item has visible details, its size will also be taken into | |||
* account. | |||
* | |||
* @param itemId | |||
* id of item to scroll to. | |||
@@ -3964,6 +4394,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
/** | |||
* Scrolls to a certain item, using user-specified scroll destination. | |||
* <p> | |||
* If the item has visible details, its size will also be taken into | |||
* account. | |||
* | |||
* @param itemId | |||
* id of item to scroll to. | |||
@@ -4376,6 +4809,33 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
removeListener(SelectionEvent.class, listener, SELECTION_CHANGE_METHOD); | |||
} | |||
private void fireColumnReorderEvent(boolean userOriginated) { | |||
fireEvent(new ColumnReorderEvent(this, userOriginated)); | |||
} | |||
/** | |||
* Registers a new column reorder listener. | |||
* | |||
* @since 7.5.0 | |||
* @param listener | |||
* the listener to register | |||
*/ | |||
public void addColumnReorderListener(ColumnReorderListener listener) { | |||
addListener(ColumnReorderEvent.class, listener, COLUMN_REORDER_METHOD); | |||
} | |||
/** | |||
* Removes a previously registered column reorder listener. | |||
* | |||
* @since 7.5.0 | |||
* @param listener | |||
* the listener to remove | |||
*/ | |||
public void removeColumnReorderListener(ColumnReorderListener listener) { | |||
removeListener(ColumnReorderEvent.class, listener, | |||
COLUMN_REORDER_METHOD); | |||
} | |||
/** | |||
* Gets the | |||
* {@link com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper | |||
@@ -4920,6 +5380,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
} | |||
componentList.addAll(getEditorFields()); | |||
componentList.addAll(detailComponentManager.getComponents()); | |||
return componentList.iterator(); | |||
} | |||
@@ -5437,6 +5900,101 @@ public class Grid extends AbstractComponent implements SelectionNotifier, | |||
getRpcProxy(GridClientRpc.class).recalculateColumnWidths(); | |||
} | |||
/** | |||
* Registers a new column visibility change listener | |||
* | |||
* @since 7.5.0 | |||
* @param listener | |||
* the listener to register | |||
*/ | |||
public void addColumnVisibilityChangeListener( | |||
ColumnVisibilityChangeListener listener) { | |||
addListener(ColumnVisibilityChangeEvent.class, listener, | |||
COLUMN_VISIBILITY_METHOD); | |||
} | |||
/** | |||
* Removes a previously registered column visibility change listener | |||
* | |||
* @since 7.5.0 | |||
* @param listener | |||
* the listener to remove | |||
*/ | |||
public void removeColumnVisibilityChangeListener( | |||
ColumnVisibilityChangeListener listener) { | |||
removeListener(ColumnVisibilityChangeEvent.class, listener, | |||
COLUMN_VISIBILITY_METHOD); | |||
} | |||
private void fireColumnVisibilityChangeEvent(Column column, boolean hidden, | |||
boolean isUserOriginated) { | |||
fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden, | |||
isUserOriginated)); | |||
} | |||
/** | |||
* Sets a new details generator for row details. | |||
* <p> | |||
* The currently opened row details will be re-rendered. | |||
* | |||
* @since 7.5.0 | |||
* @param detailsGenerator | |||
* the details generator to set | |||
* @throws IllegalArgumentException | |||
* if detailsGenerator is <code>null</code>; | |||
*/ | |||
public void setDetailsGenerator(DetailsGenerator detailsGenerator) | |||
throws IllegalArgumentException { | |||
if (detailsGenerator == null) { | |||
throw new IllegalArgumentException( | |||
"Details generator may not be null"); | |||
} else if (detailsGenerator == this.detailsGenerator) { | |||
return; | |||
} | |||
this.detailsGenerator = detailsGenerator; | |||
datasourceExtension.refreshDetails(); | |||
getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( | |||
detailComponentManager.getAndResetConnectorChanges(), -1); | |||
} | |||
/** | |||
* Gets the current details generator for row details. | |||
* | |||
* @since 7.5.0 | |||
* @return the detailsGenerator the current details generator | |||
*/ | |||
public DetailsGenerator getDetailsGenerator() { | |||
return detailsGenerator; | |||
} | |||
/** | |||
* Shows or hides the details for a specific item. | |||
* | |||
* @since 7.5.0 | |||
* @param itemId | |||
* the id of the item for which to set details visibility | |||
* @param visible | |||
* <code>true</code> to show the details, or <code>false</code> | |||
* to hide them | |||
*/ | |||
public void setDetailsVisible(Object itemId, boolean visible) { | |||
datasourceExtension.setDetailsVisible(itemId, visible); | |||
} | |||
/** | |||
* Checks whether details are visible for the given item. | |||
* | |||
* @since 7.5.0 | |||
* @param itemId | |||
* the id of the item for which to check details visibility | |||
* @return <code>true</code> iff the details are visible | |||
*/ | |||
public boolean isDetailsVisible(Object itemId) { | |||
return datasourceExtension.isDetailsVisible(itemId); | |||
} | |||
protected SelectionMode getDefaultSelectionMode() { | |||
return SelectionMode.SINGLE; | |||
} |
@@ -28,6 +28,8 @@ public class GridColumnDeclarativeTest extends GridDeclarativeTestBase { | |||
+ " <col sortable=true width='100' property-id='Column1'>" | |||
+ " <col sortable=false max-width='200' expand='2' property-id='Column2'>" | |||
+ " <col sortable=true editable=false min-width='15' expand='1' property-id='Column3'>" | |||
+ " <col sortable=true hidable=true hiding-toggle-caption='col 4' property-id='Column4'>" | |||
+ " <col sortable=true hidden=true property-id='Column5'>" | |||
+ "</colgroup>" // | |||
+ "<thead />" // | |||
+ "</table></v-grid>"; | |||
@@ -37,6 +39,9 @@ public class GridColumnDeclarativeTest extends GridDeclarativeTestBase { | |||
.setExpandRatio(2).setSortable(false); | |||
grid.addColumn("Column3", String.class).setMinimumWidth(15) | |||
.setExpandRatio(1).setEditable(false); | |||
grid.addColumn("Column4", String.class).setHidable(true) | |||
.setHidingToggleCaption("col 4"); | |||
grid.addColumn("Column5", String.class).setHidden(true); | |||
// Remove the default header | |||
grid.removeHeaderRow(grid.getDefaultHeaderRow()); | |||
@@ -53,6 +58,7 @@ public class GridColumnDeclarativeTest extends GridDeclarativeTestBase { | |||
+ " <col sortable=true width='100' property-id='Column1'>" | |||
+ " <col sortable=true max-width='200' expand='2'>" // property-id="property-1" | |||
+ " <col sortable=true min-width='15' expand='1' property-id='Column3'>" | |||
+ " <col sortable=true hidden=true hidable=true hiding-toggle-caption='col 4'>" // property-id="property-3" | |||
+ "</colgroup>" // | |||
+ "</table></v-grid>"; | |||
Grid grid = new Grid(); | |||
@@ -61,6 +67,8 @@ public class GridColumnDeclarativeTest extends GridDeclarativeTestBase { | |||
.setExpandRatio(2); | |||
grid.addColumn("Column3", String.class).setMinimumWidth(15) | |||
.setExpandRatio(1); | |||
grid.addColumn("property-3", String.class).setHidable(true) | |||
.setHidden(true).setHidingToggleCaption("col 4"); | |||
testRead(design, grid); | |||
} |
@@ -38,7 +38,7 @@ public class GridDeclarativeAttributeTest extends DeclarativeTestBase<Grid> { | |||
public void testBasicAttributes() { | |||
String design = "<v-grid editable='true' rows=20 frozen-columns=-1 " | |||
+ "editor-save-caption='Tallenna' editor-cancel-caption='Peruuta'>"; | |||
+ "editor-save-caption='Tallenna' editor-cancel-caption='Peruuta' column-reordering-allowed=true>"; | |||
Grid grid = new Grid(); | |||
grid.setEditorEnabled(true); | |||
@@ -47,6 +47,7 @@ public class GridDeclarativeAttributeTest extends DeclarativeTestBase<Grid> { | |||
grid.setFrozenColumnCount(-1); | |||
grid.setEditorSaveCaption("Tallenna"); | |||
grid.setEditorCancelCaption("Peruuta"); | |||
grid.setColumnReorderingAllowed(true); | |||
testRead(design, grid); | |||
testWrite(design, grid); |
@@ -147,7 +147,12 @@ public class GridDeclarativeTestBase extends DeclarativeTestBase<Grid> { | |||
col2.isSortable()); | |||
assertEquals(baseError + "Editable", col1.isEditable(), | |||
col2.isEditable()); | |||
assertEquals(baseError + "Hidable", col1.isHidable(), | |||
col2.isHidable()); | |||
assertEquals(baseError + "Hidden", col1.isHidden(), col2.isHidden()); | |||
assertEquals(baseError + "HidingToggleCaption", | |||
col1.getHidingToggleCaption(), | |||
col2.getHidingToggleCaption()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,190 @@ | |||
/* | |||
* 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.shared.ui.grid; | |||
import java.io.Serializable; | |||
import java.util.Comparator; | |||
import com.vaadin.shared.Connector; | |||
/** | |||
* A description of an indexing modification for a connector. This is used by | |||
* Grid for internal bookkeeping updates. | |||
* | |||
* @since 7.5.0 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class DetailsConnectorChange implements Serializable { | |||
public static final Comparator<DetailsConnectorChange> REMOVED_FIRST_COMPARATOR = new Comparator<DetailsConnectorChange>() { | |||
@Override | |||
public int compare(DetailsConnectorChange a, DetailsConnectorChange b) { | |||
boolean deleteA = a.getNewIndex() == null; | |||
boolean deleteB = b.getNewIndex() == null; | |||
if (deleteA && !deleteB) { | |||
return -1; | |||
} else if (!deleteA && deleteB) { | |||
return 1; | |||
} else { | |||
return 0; | |||
} | |||
} | |||
}; | |||
private Connector connector; | |||
private Integer oldIndex; | |||
private Integer newIndex; | |||
private boolean shouldStillBeVisible; | |||
/** Create a new connector index change */ | |||
public DetailsConnectorChange() { | |||
} | |||
/** | |||
* Convenience constructor for setting all the fields in one line. | |||
* <p> | |||
* Calling this constructor will also assert that the state of the pojo is | |||
* consistent by internal assumptions. | |||
* | |||
* @param connector | |||
* the changed connector | |||
* @param oldIndex | |||
* the old index | |||
* @param newIndex | |||
* the new index | |||
* @param shouldStillBeVisible | |||
* details should be visible regardless of {@code connector} | |||
*/ | |||
public DetailsConnectorChange(Connector connector, Integer oldIndex, | |||
Integer newIndex, boolean shouldStillBeVisible) { | |||
this.connector = connector; | |||
this.oldIndex = oldIndex; | |||
this.newIndex = newIndex; | |||
this.shouldStillBeVisible = shouldStillBeVisible; | |||
assert assertStateIsOk(); | |||
} | |||
private boolean assertStateIsOk() { | |||
boolean connectorAndNewIndexIsNotNull = connector != null | |||
&& newIndex != null; | |||
boolean connectorAndNewIndexIsNullThenOldIndexIsSet = connector == null | |||
&& newIndex == null && oldIndex != null; | |||
assert (connectorAndNewIndexIsNotNull || connectorAndNewIndexIsNullThenOldIndexIsSet) : "connector: " | |||
+ nullityString(connector) | |||
+ ", oldIndex: " | |||
+ nullityString(oldIndex) | |||
+ ", newIndex: " | |||
+ nullityString(newIndex); | |||
return true; | |||
} | |||
private static String nullityString(Object object) { | |||
return object == null ? "null" : "non-null"; | |||
} | |||
/** | |||
* Gets the old index for the connector. | |||
* <p> | |||
* If <code>null</code>, the connector is recently added. This means that | |||
* {@link #getConnector()} is expected not to return <code>null</code>. | |||
* | |||
* @return the old index for the connector | |||
*/ | |||
public Integer getOldIndex() { | |||
assert assertStateIsOk(); | |||
return oldIndex; | |||
} | |||
/** | |||
* Gets the new index for the connector. | |||
* <p> | |||
* If <code>null</code>, the connector should be removed. This means that | |||
* {@link #getConnector()} is expected to return <code>null</code> as well. | |||
* | |||
* @return the new index for the connector | |||
*/ | |||
public Integer getNewIndex() { | |||
assert assertStateIsOk(); | |||
return newIndex; | |||
} | |||
/** | |||
* Gets the changed connector. | |||
* | |||
* @return the changed connector. Might be <code>null</code> | |||
*/ | |||
public Connector getConnector() { | |||
assert assertStateIsOk(); | |||
return connector; | |||
} | |||
/** | |||
* Sets the changed connector. | |||
* | |||
* @param connector | |||
* the changed connector. May be <code>null</code> | |||
*/ | |||
public void setConnector(Connector connector) { | |||
this.connector = connector; | |||
} | |||
/** | |||
* Sets the old index | |||
* | |||
* @param oldIndex | |||
* the old index. May be <code>null</code> if a new connector is | |||
* being inserted | |||
*/ | |||
public void setOldIndex(Integer oldIndex) { | |||
this.oldIndex = oldIndex; | |||
} | |||
/** | |||
* Sets the new index | |||
* | |||
* @param newIndex | |||
* the new index. May be <code>null</code> if a connector is | |||
* being removed | |||
*/ | |||
public void setNewIndex(Integer newIndex) { | |||
this.newIndex = newIndex; | |||
} | |||
/** | |||
* Checks whether whether the details should remain open, even if connector | |||
* might be <code>null</code>. | |||
* | |||
* @return <code>true</code> iff the details should remain open, even if | |||
* connector might be <code>null</code> | |||
*/ | |||
public boolean isShouldStillBeVisible() { | |||
return shouldStillBeVisible; | |||
} | |||
/** | |||
* Sets whether the details should remain open, even if connector might be | |||
* <code>null</code>. | |||
* | |||
* @param shouldStillBeVisible | |||
* <code>true</code> iff the details should remain open, even if | |||
* connector might be <code>null</code> | |||
*/ | |||
public void setShouldStillBeVisible(boolean shouldStillBeVisible) { | |||
this.shouldStillBeVisible = shouldStillBeVisible; | |||
} | |||
} |
@@ -15,6 +15,8 @@ | |||
*/ | |||
package com.vaadin.shared.ui.grid; | |||
import java.util.Set; | |||
import com.vaadin.shared.communication.ClientRpc; | |||
/** | |||
@@ -26,7 +28,8 @@ import com.vaadin.shared.communication.ClientRpc; | |||
public interface GridClientRpc extends ClientRpc { | |||
/** | |||
* Command client Grid to scroll to a specific data row. | |||
* Command client Grid to scroll to a specific data row and its (optional) | |||
* details. | |||
* | |||
* @param row | |||
* zero-based row index. If the row index is below zero or above | |||
@@ -55,4 +58,18 @@ public interface GridClientRpc extends ClientRpc { | |||
*/ | |||
public void recalculateColumnWidths(); | |||
/** | |||
* Informs the GridConnector on how the indexing of details connectors has | |||
* changed. | |||
* | |||
* @since 7.5.0 | |||
* @param connectorChanges | |||
* the indexing changes of details connectors | |||
* @param fetchId | |||
* the id of the request for fetching the changes. A negative | |||
* number indicates a push (not requested by the client side) | |||
*/ | |||
public void setDetailsConnectorChanges( | |||
Set<DetailsConnectorChange> connectorChanges, int fetchId); | |||
} |
@@ -76,4 +76,13 @@ public class GridColumnState implements Serializable { | |||
* minWidth is less than the calculated width, minWidth will win. | |||
*/ | |||
public double minWidth = GridConstants.DEFAULT_MIN_WIDTH; | |||
/** Is the column currently hidden. */ | |||
public boolean hidden = false; | |||
/** Can the column be hidden by the UI. */ | |||
public boolean hidable = false; | |||
/** The caption for the column hiding toggle. */ | |||
public String hidingToggleCaption; | |||
} |
@@ -47,4 +47,48 @@ public interface GridServerRpc extends ServerRpc { | |||
* mouse event details | |||
*/ | |||
void itemClick(String rowKey, String columnId, MouseEventDetails details); | |||
/** | |||
* Informs the server that the columns of the Grid have been reordered. | |||
* | |||
* @since 7.5.0 | |||
* @param newColumnOrder | |||
* a list of column ids in the new order | |||
* @param oldColumnOrder | |||
* a list of column ids in order before the change | |||
*/ | |||
void columnsReordered(List<String> newColumnOrder, | |||
List<String> oldColumnOrder); | |||
/** | |||
* This is a trigger for Grid to send whatever has changed regarding the | |||
* details components. | |||
* <p> | |||
* The components can't be sent eagerly, since they are generated as a side | |||
* effect in | |||
* {@link com.vaadin.data.RpcDataProviderExtension#beforeClientResponse(boolean)} | |||
* , and that is too late to change the hierarchy. So we need this | |||
* round-trip to work around that limitation. | |||
* | |||
* @since 7.5.0 | |||
* @param fetchId | |||
* an unique identifier for the request | |||
* @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) | |||
*/ | |||
void sendDetailsComponents(int fetchId); | |||
/** | |||
* Informs the server that the column's visibility has been changed. | |||
* | |||
* @since 7.5.0 | |||
* @param id | |||
* the id of the column | |||
* @param hidden | |||
* <code>true</code> if hidden, <code>false</code> if unhidden | |||
* @param userOriginated | |||
* <code>true</code> if triggered by user, <code>false</code> if | |||
* by code | |||
*/ | |||
void columnVisibilityChanged(String id, boolean hidden, | |||
boolean userOriginated); | |||
} |
@@ -102,6 +102,16 @@ public class GridState extends AbstractComponentState { | |||
*/ | |||
public static final String JSONKEY_CELLSTYLES = "cs"; | |||
/** | |||
* The key that tells whether details are visible for the row | |||
* | |||
* @see com.vaadin.ui.Grid#setDetailsGenerator(com.vaadin.ui.Grid.DetailsGenerator) | |||
* @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) | |||
* @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, | |||
* elemental.json.JsonArray) | |||
* */ | |||
public static final String JSONKEY_DETAILS_VISIBLE = "dv"; | |||
/** | |||
* Columns in grid. | |||
*/ | |||
@@ -156,4 +166,8 @@ public class GridState extends AbstractComponentState { | |||
/** The caption for the cancel button in the editor */ | |||
@DelegateToWidget | |||
public String editorCancelCaption = GridConstants.DEFAULT_CANCEL_CAPTION; | |||
/** Whether the columns can be reordered */ | |||
@DelegateToWidget | |||
public boolean columnReorderingAllowed; | |||
} |
@@ -1,367 +0,0 @@ | |||
/* | |||
* 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.testbench.elements; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elementsbase.AbstractElement; | |||
import com.vaadin.testbench.elementsbase.ServerClass; | |||
/** | |||
* TestBench Element API for Grid | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
@ServerClass("com.vaadin.ui.Grid") | |||
public class GridElement extends AbstractComponentElement { | |||
public static class GridCellElement extends AbstractElement { | |||
private static final String FOCUSED_CELL_CLASS_NAME = "-cell-focused"; | |||
private static final String FROZEN_CLASS_NAME = "frozen"; | |||
public boolean isFocused() { | |||
return getAttribute("class").contains(FOCUSED_CELL_CLASS_NAME); | |||
} | |||
public boolean isFrozen() { | |||
return getAttribute("class").contains(FROZEN_CLASS_NAME); | |||
} | |||
} | |||
public static class GridRowElement extends AbstractElement { | |||
private static final String FOCUSED_CLASS_NAME = "-row-focused"; | |||
private static final String SELECTED_CLASS_NAME = "-row-selected"; | |||
public boolean isFocused() { | |||
return getAttribute("class").contains(FOCUSED_CLASS_NAME); | |||
} | |||
@Override | |||
public boolean isSelected() { | |||
return getAttribute("class").contains(SELECTED_CLASS_NAME); | |||
} | |||
} | |||
public static class GridEditorElement extends AbstractElement { | |||
private GridElement grid; | |||
private GridEditorElement setGrid(GridElement grid) { | |||
this.grid = grid; | |||
return this; | |||
} | |||
/** | |||
* Gets the editor field for column in given index. | |||
* | |||
* @param colIndex | |||
* the column index | |||
* @return the editor field for given location | |||
* | |||
* @throws NoSuchElementException | |||
* if {@code isEditable(colIndex) == false} | |||
*/ | |||
public TestBenchElement getField(int colIndex) { | |||
return grid.getSubPart("#editor[" + colIndex + "]"); | |||
} | |||
/** | |||
* Gets whether the column with the given index is editable, that is, | |||
* has an associated editor field. | |||
* | |||
* @param colIndex | |||
* the column index | |||
* @return {@code true} if the column has an editor field, {@code false} | |||
* otherwise | |||
*/ | |||
public boolean isEditable(int colIndex) { | |||
return grid | |||
.isElementPresent(By.vaadin("#editor[" + colIndex + "]")); | |||
} | |||
/** | |||
* Checks whether a field is marked with an error. | |||
* | |||
* @param colIndex | |||
* column index | |||
* @return <code>true</code> iff the field is marked with an error | |||
*/ | |||
public boolean isFieldErrorMarked(int colIndex) { | |||
return getField(colIndex).getAttribute("class").contains("error"); | |||
} | |||
/** | |||
* Saves the fields of this editor. | |||
* <p> | |||
* <em>Note:</em> that this closes the editor making this element | |||
* useless. | |||
*/ | |||
public void save() { | |||
findElement(By.className("v-grid-editor-save")).click(); | |||
} | |||
/** | |||
* Cancels this editor. | |||
* <p> | |||
* <em>Note:</em> that this closes the editor making this element | |||
* useless. | |||
*/ | |||
public void cancel() { | |||
findElement(By.className("v-grid-editor-cancel")).click(); | |||
} | |||
/** | |||
* Gets the error message text, or <code>null</code> if no message is | |||
* present. | |||
*/ | |||
public String getErrorMessage() { | |||
WebElement messageWrapper = findElement(By | |||
.className("v-grid-editor-message")); | |||
List<WebElement> divs = messageWrapper.findElements(By | |||
.tagName("div")); | |||
if (divs.isEmpty()) { | |||
return null; | |||
} else { | |||
return divs.get(0).getText(); | |||
} | |||
} | |||
} | |||
/** | |||
* Scrolls Grid element so that wanted row is displayed | |||
* | |||
* @param index | |||
* Target row | |||
*/ | |||
public void scrollToRow(int index) { | |||
try { | |||
getSubPart("#cell[" + index + "]"); | |||
} catch (NoSuchElementException e) { | |||
// Expected, ignore it. | |||
} | |||
} | |||
/** | |||
* Gets cell element with given row and column index. | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @param colIndex | |||
* Column index | |||
* @return Cell element with given indices. | |||
*/ | |||
public GridCellElement getCell(int rowIndex, int colIndex) { | |||
scrollToRow(rowIndex); | |||
return getSubPart("#cell[" + rowIndex + "][" + colIndex + "]").wrap( | |||
GridCellElement.class); | |||
} | |||
/** | |||
* Gets row element with given row index. | |||
* | |||
* @param index | |||
* Row index | |||
* @return Row element with given index. | |||
*/ | |||
public GridRowElement getRow(int index) { | |||
scrollToRow(index); | |||
return getSubPart("#cell[" + index + "]").wrap(GridRowElement.class); | |||
} | |||
/** | |||
* Gets header cell element with given row and column index. | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @param colIndex | |||
* Column index | |||
* @return Header cell element with given indices. | |||
*/ | |||
public GridCellElement getHeaderCell(int rowIndex, int colIndex) { | |||
return getSubPart("#header[" + rowIndex + "][" + colIndex + "]").wrap( | |||
GridCellElement.class); | |||
} | |||
/** | |||
* Gets footer cell element with given row and column index. | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @param colIndex | |||
* Column index | |||
* @return Footer cell element with given indices. | |||
*/ | |||
public GridCellElement getFooterCell(int rowIndex, int colIndex) { | |||
return getSubPart("#footer[" + rowIndex + "][" + colIndex + "]").wrap( | |||
GridCellElement.class); | |||
} | |||
/** | |||
* Gets list of header cell elements on given row. | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @return Header cell elements on given row. | |||
*/ | |||
public List<GridCellElement> getHeaderCells(int rowIndex) { | |||
List<GridCellElement> headers = new ArrayList<GridCellElement>(); | |||
for (TestBenchElement e : TestBenchElement.wrapElements( | |||
getSubPart("#header[" + rowIndex + "]").findElements( | |||
By.xpath("./th")), getCommandExecutor())) { | |||
headers.add(e.wrap(GridCellElement.class)); | |||
} | |||
return headers; | |||
} | |||
/** | |||
* Gets list of header cell elements on given row. | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @return Header cell elements on given row. | |||
*/ | |||
public List<GridCellElement> getFooterCells(int rowIndex) { | |||
List<GridCellElement> footers = new ArrayList<GridCellElement>(); | |||
for (TestBenchElement e : TestBenchElement.wrapElements( | |||
getSubPart("#footer[" + rowIndex + "]").findElements( | |||
By.xpath("./td")), getCommandExecutor())) { | |||
footers.add(e.wrap(GridCellElement.class)); | |||
} | |||
return footers; | |||
} | |||
/** | |||
* Get header row count | |||
* | |||
* @return Header row count | |||
*/ | |||
public int getHeaderCount() { | |||
return getSubPart("#header").findElements(By.xpath("./tr")).size(); | |||
} | |||
/** | |||
* Get footer row count | |||
* | |||
* @return Footer row count | |||
*/ | |||
public int getFooterCount() { | |||
return getSubPart("#footer").findElements(By.xpath("./tr")).size(); | |||
} | |||
/** | |||
* Get a header row by index | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @return The th element of the row | |||
*/ | |||
public TestBenchElement getHeaderRow(int rowIndex) { | |||
return getSubPart("#header[" + rowIndex + "]"); | |||
} | |||
/** | |||
* Get a footer row by index | |||
* | |||
* @param rowIndex | |||
* Row index | |||
* @return The tr element of the row | |||
*/ | |||
public TestBenchElement getFooterRow(int rowIndex) { | |||
return getSubPart("#footer[" + rowIndex + "]"); | |||
} | |||
/** | |||
* Get the vertical scroll element | |||
* | |||
* @return The element representing the vertical scrollbar | |||
*/ | |||
public TestBenchElement getVerticalScroller() { | |||
List<WebElement> rootElements = findElements(By.xpath("./div")); | |||
return (TestBenchElement) rootElements.get(0); | |||
} | |||
/** | |||
* Get the horizontal scroll element | |||
* | |||
* @return The element representing the horizontal scrollbar | |||
*/ | |||
public TestBenchElement getHorizontalScroller() { | |||
List<WebElement> rootElements = findElements(By.xpath("./div")); | |||
return (TestBenchElement) rootElements.get(1); | |||
} | |||
/** | |||
* Get the header element | |||
* | |||
* @return The thead element | |||
*/ | |||
public TestBenchElement getHeader() { | |||
return getSubPart("#header"); | |||
} | |||
/** | |||
* Get the body element | |||
* | |||
* @return the tbody element | |||
*/ | |||
public TestBenchElement getBody() { | |||
return getSubPart("#cell"); | |||
} | |||
/** | |||
* Get the footer element | |||
* | |||
* @return the tfoot element | |||
*/ | |||
public TestBenchElement getFooter() { | |||
return getSubPart("#footer"); | |||
} | |||
/** | |||
* Get the element wrapping the table element | |||
* | |||
* @return The element that wraps the table element | |||
*/ | |||
public TestBenchElement getTableWrapper() { | |||
List<WebElement> rootElements = findElements(By.xpath("./div")); | |||
return (TestBenchElement) rootElements.get(2); | |||
} | |||
public GridEditorElement getEditor() { | |||
return getSubPart("#editor").wrap(GridEditorElement.class) | |||
.setGrid(this); | |||
} | |||
/** | |||
* Helper function to get Grid subparts wrapped correctly | |||
* | |||
* @param subPartSelector | |||
* SubPart to be used in ComponentLocator | |||
* @return SubPart element wrapped in TestBenchElement class | |||
*/ | |||
private TestBenchElement getSubPart(String subPartSelector) { | |||
return (TestBenchElement) findElement(By.vaadin(subPartSelector)); | |||
} | |||
} |
@@ -0,0 +1,121 @@ | |||
/* | |||
* 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; | |||
import com.vaadin.annotations.Theme; | |||
import com.vaadin.data.Property.ValueChangeEvent; | |||
import com.vaadin.data.Property.ValueChangeListener; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.util.Person; | |||
import com.vaadin.tests.util.PersonContainer; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.CheckBox; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.Grid.DetailsGenerator; | |||
import com.vaadin.ui.Grid.RowReference; | |||
import com.vaadin.ui.Grid.SelectionMode; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.Layout; | |||
import com.vaadin.ui.TextField; | |||
import com.vaadin.ui.UI; | |||
import com.vaadin.ui.VerticalLayout; | |||
import com.vaadin.ui.themes.ValoTheme; | |||
@Theme(ValoTheme.THEME_NAME) | |||
public class GridDetailsLocation extends UI { | |||
private final DetailsGenerator detailsGenerator = new DetailsGenerator() { | |||
@Override | |||
public Component getDetails(RowReference rowReference) { | |||
Person person = (Person) rowReference.getItemId(); | |||
Label label = new Label(person.getFirstName() + " " | |||
+ person.getLastName()); | |||
// currently the decorator row doesn't change its height when the | |||
// content height is different. | |||
label.setHeight("30px"); | |||
return label; | |||
} | |||
}; | |||
private TextField numberTextField; | |||
private Grid grid; | |||
@Override | |||
protected void init(VaadinRequest request) { | |||
Layout layout = new VerticalLayout(); | |||
grid = new Grid(PersonContainer.createWithTestData(1000)); | |||
grid.setSelectionMode(SelectionMode.NONE); | |||
layout.addComponent(grid); | |||
final CheckBox checkbox = new CheckBox("Details generator"); | |||
checkbox.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public void valueChange(ValueChangeEvent event) { | |||
if (checkbox.getValue()) { | |||
grid.setDetailsGenerator(detailsGenerator); | |||
} else { | |||
grid.setDetailsGenerator(DetailsGenerator.NULL); | |||
} | |||
} | |||
}); | |||
layout.addComponent(checkbox); | |||
numberTextField = new TextField("Row"); | |||
numberTextField.setImmediate(true); | |||
layout.addComponent(numberTextField); | |||
layout.addComponent(new Button("Toggle and scroll", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
toggle(); | |||
scrollTo(); | |||
} | |||
})); | |||
layout.addComponent(new Button("Scroll and toggle", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
scrollTo(); | |||
toggle(); | |||
} | |||
})); | |||
setContent(layout); | |||
} | |||
private void toggle() { | |||
Object itemId = getItemId(); | |||
boolean isVisible = grid.isDetailsVisible(itemId); | |||
grid.setDetailsVisible(itemId, !isVisible); | |||
} | |||
private void scrollTo() { | |||
grid.scrollTo(getItemId()); | |||
} | |||
private Object getItemId() { | |||
int row = Integer.parseInt(numberTextField.getValue()); | |||
Object itemId = grid.getContainerDataSource().getIdByIndex(row); | |||
return itemId; | |||
} | |||
} |
@@ -0,0 +1,361 @@ | |||
/* | |||
* 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; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.List; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.JavascriptExecutor; | |||
import org.openqa.selenium.Keys; | |||
import org.openqa.selenium.StaleElementReferenceException; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.testbench.elements.CheckBoxElement; | |||
import com.vaadin.testbench.elements.GridElement.GridRowElement; | |||
import com.vaadin.testbench.elements.TextFieldElement; | |||
import com.vaadin.testbench.parallel.Browser; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
@TestCategory("grid") | |||
public class GridDetailsLocationTest extends MultiBrowserTest { | |||
private static final int detailsDefaultHeight = 51; | |||
private static final int detailsDefinedHeight = 33; | |||
private static final int detailsDefinedHeightIE8 = 31; | |||
private static class Param { | |||
private final int rowIndex; | |||
private final boolean useGenerator; | |||
private final boolean scrollFirstToBottom; | |||
public Param(int rowIndex, boolean useGenerator, | |||
boolean scrollFirstToBottom) { | |||
this.rowIndex = rowIndex; | |||
this.useGenerator = useGenerator; | |||
this.scrollFirstToBottom = scrollFirstToBottom; | |||
} | |||
public int getRowIndex() { | |||
return rowIndex; | |||
} | |||
public boolean useGenerator() { | |||
return useGenerator; | |||
} | |||
public boolean scrollFirstToBottom() { | |||
return scrollFirstToBottom; | |||
} | |||
@Override | |||
public String toString() { | |||
return "Param [rowIndex=" + getRowIndex() + ", useGenerator=" | |||
+ useGenerator() + ", scrollFirstToBottom=" | |||
+ scrollFirstToBottom() + "]"; | |||
} | |||
} | |||
public static Collection<Param> parameters() { | |||
List<Param> data = new ArrayList<Param>(); | |||
int[] params = new int[] { 0, 500, 999 }; | |||
for (int rowIndex : params) { | |||
data.add(new Param(rowIndex, false, false)); | |||
data.add(new Param(rowIndex, true, false)); | |||
data.add(new Param(rowIndex, false, true)); | |||
data.add(new Param(rowIndex, true, true)); | |||
} | |||
return data; | |||
} | |||
@Before | |||
public void setUp() { | |||
setDebug(true); | |||
} | |||
@Test | |||
public void toggleAndScroll() throws Throwable { | |||
for (Param param : parameters()) { | |||
try { | |||
openTestURL(); | |||
useGenerator(param.useGenerator()); | |||
scrollToBottom(param.scrollFirstToBottom()); | |||
// the tested method | |||
toggleAndScroll(param.getRowIndex()); | |||
verifyLocation(param); | |||
} catch (Throwable t) { | |||
throw new Throwable("" + param, t); | |||
} | |||
} | |||
} | |||
@Test | |||
public void scrollAndToggle() throws Throwable { | |||
for (Param param : parameters()) { | |||
try { | |||
openTestURL(); | |||
useGenerator(param.useGenerator()); | |||
scrollToBottom(param.scrollFirstToBottom()); | |||
// the tested method | |||
scrollAndToggle(param.getRowIndex()); | |||
verifyLocation(param); | |||
} catch (Throwable t) { | |||
throw new Throwable("" + param, t); | |||
} | |||
} | |||
} | |||
@Test | |||
public void testDetailsHeightWithNoGenerator() { | |||
openTestURL(); | |||
toggleAndScroll(5); | |||
verifyDetailsRowHeight(5, detailsDefaultHeight, 0); | |||
verifyDetailsDecoratorLocation(5, 0, 0); | |||
toggleAndScroll(0); | |||
verifyDetailsRowHeight(0, detailsDefaultHeight, 0); | |||
verifyDetailsDecoratorLocation(0, 0, 1); | |||
verifyDetailsRowHeight(5, detailsDefaultHeight, 1); | |||
verifyDetailsDecoratorLocation(5, 1, 0); | |||
} | |||
@Test | |||
public void testDetailsHeightWithGenerator() { | |||
openTestURL(); | |||
useGenerator(true); | |||
toggleAndScroll(5); | |||
verifyDetailsRowHeight(5, getDefinedHeight(), 0); | |||
verifyDetailsDecoratorLocation(5, 0, 0); | |||
toggleAndScroll(0); | |||
verifyDetailsRowHeight(0, getDefinedHeight(), 0); | |||
// decorator elements are in DOM in the order they have been added | |||
verifyDetailsDecoratorLocation(0, 0, 1); | |||
verifyDetailsRowHeight(5, getDefinedHeight(), 1); | |||
verifyDetailsDecoratorLocation(5, 1, 0); | |||
} | |||
private int getDefinedHeight() { | |||
boolean ie8 = isIE8(); | |||
return ie8 ? detailsDefinedHeightIE8 : detailsDefinedHeight; | |||
} | |||
private void verifyDetailsRowHeight(int rowIndex, int expectedHeight, | |||
int visibleIndexOfSpacer) { | |||
waitForDetailsVisible(); | |||
WebElement details = getDetailsElement(visibleIndexOfSpacer); | |||
Assert.assertEquals("Wrong details row height", expectedHeight, details | |||
.getSize().getHeight()); | |||
} | |||
private void verifyDetailsDecoratorLocation(int row, | |||
int visibleIndexOfSpacer, int visibleIndexOfDeco) { | |||
WebElement detailsElement = getDetailsElement(visibleIndexOfSpacer); | |||
WebElement detailsDecoElement = getDetailsDecoElement(visibleIndexOfDeco); | |||
GridRowElement rowElement = getGrid().getRow(row); | |||
Assert.assertEquals( | |||
"Details deco top position does not match row top pos", | |||
rowElement.getLocation().getY(), detailsDecoElement | |||
.getLocation().getY()); | |||
Assert.assertEquals( | |||
"Details deco bottom position does not match details bottom pos", | |||
detailsElement.getLocation().getY() | |||
+ detailsElement.getSize().getHeight(), | |||
detailsDecoElement.getLocation().getY() | |||
+ detailsDecoElement.getSize().getHeight()); | |||
} | |||
private void verifyLocation(Param param) { | |||
Assert.assertFalse("Notification was present", | |||
isElementPresent(By.className("v-Notification"))); | |||
TestBenchElement headerRow = getGrid().getHeaderRow(0); | |||
final int topBoundary = headerRow.getLocation().getX() | |||
+ headerRow.getSize().height; | |||
final int bottomBoundary = getGrid().getLocation().getX() | |||
+ getGrid().getSize().getHeight() | |||
- getHorizontalScrollbar().getSize().height; | |||
GridRowElement row = getGrid().getRow(param.getRowIndex()); | |||
final int rowTop = row.getLocation().getX(); | |||
waitForDetailsVisible(); | |||
WebElement details = getDetailsElement(); | |||
final int detailsBottom = details.getLocation().getX() | |||
+ details.getSize().getHeight(); | |||
assertGreaterOrEqual("Row top should be inside grid, gridTop:" | |||
+ topBoundary + " rowTop" + rowTop, topBoundary, rowTop); | |||
assertLessThanOrEqual( | |||
"Decorator bottom should be inside grid, gridBottom:" | |||
+ bottomBoundary + " decoratorBotton:" + detailsBottom, | |||
detailsBottom, bottomBoundary); | |||
verifyDetailsRowHeight(param.getRowIndex(), | |||
param.useGenerator() ? getDefinedHeight() | |||
: detailsDefaultHeight, 0); | |||
verifyDetailsDecoratorLocation(param.getRowIndex(), 0, 0); | |||
Assert.assertFalse("Notification was present", | |||
isElementPresent(By.className("v-Notification"))); | |||
} | |||
private final By locator = By.className("v-grid-spacer"); | |||
private WebElement getDetailsElement() { | |||
return getDetailsElement(0); | |||
} | |||
private WebElement getDetailsElement(int index) { | |||
return findElements(locator).get(index); | |||
} | |||
private WebElement getDetailsDecoElement(int index) { | |||
return findElements(By.className("v-grid-spacer-deco")).get(index); | |||
} | |||
private void waitForDetailsVisible() { | |||
waitUntil(new ExpectedCondition<WebElement>() { | |||
@Override | |||
public WebElement apply(WebDriver driver) { | |||
try { | |||
WebElement detailsElement = getDetailsElement(); | |||
return detailsElement.isDisplayed() | |||
&& detailsElement.getSize().getHeight() > 3 ? detailsElement | |||
: null; | |||
} catch (StaleElementReferenceException e) { | |||
return null; | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return "visibility of element located by " + locator; | |||
} | |||
}, 5); | |||
waitForElementVisible(By.className("v-grid-spacer")); | |||
} | |||
private void scrollToBottom(boolean scrollFirstToBottom) { | |||
if (scrollFirstToBottom) { | |||
executeScript("arguments[0].scrollTop = 9999999", | |||
getVerticalScrollbar()); | |||
} | |||
} | |||
private void useGenerator(boolean use) { | |||
CheckBoxElement checkBox = $(CheckBoxElement.class).first(); | |||
boolean isChecked = isCheckedValo(checkBox); | |||
if (use != isChecked) { | |||
clickValo(checkBox); | |||
} | |||
} | |||
private boolean isIE8() { | |||
DesiredCapabilities desiredCapabilities = getDesiredCapabilities(); | |||
DesiredCapabilities ie8Capabilities = Browser.IE8 | |||
.getDesiredCapabilities(); | |||
return desiredCapabilities.getBrowserName().equals( | |||
ie8Capabilities.getBrowserName()) | |||
&& desiredCapabilities.getVersion().equals( | |||
ie8Capabilities.getVersion()); | |||
} | |||
@SuppressWarnings("boxing") | |||
private boolean isCheckedValo(CheckBoxElement checkBoxElement) { | |||
WebElement checkbox = checkBoxElement.findElement(By.tagName("input")); | |||
Object value = executeScript("return arguments[0].checked;", checkbox); | |||
return (Boolean) value; | |||
} | |||
private void clickValo(CheckBoxElement checkBoxElement) { | |||
checkBoxElement.findElement(By.tagName("label")).click(); | |||
} | |||
private Object executeScript(String string, Object... param) { | |||
return ((JavascriptExecutor) getDriver()).executeScript(string, param); | |||
} | |||
private void scrollAndToggle(int row) { | |||
setRow(row); | |||
getScrollAndToggle().click(); | |||
} | |||
private void toggleAndScroll(int row) { | |||
setRow(row); | |||
getToggleAndScroll().click(); | |||
} | |||
private ButtonElement getScrollAndToggle() { | |||
return $(ButtonElement.class).caption("Scroll and toggle").first(); | |||
} | |||
private ButtonElement getToggleAndScroll() { | |||
return $(ButtonElement.class).caption("Toggle and scroll").first(); | |||
} | |||
private void setRow(int row) { | |||
$(TextFieldElement.class).first().clear(); | |||
$(TextFieldElement.class).first().sendKeys(String.valueOf(row), | |||
Keys.ENTER, Keys.TAB); | |||
} | |||
private CustomGridElement getGrid() { | |||
return $(CustomGridElement.class).first(); | |||
} | |||
private WebElement getVerticalScrollbar() { | |||
WebElement scrollBar = getGrid().findElement( | |||
By.className("v-grid-scroller-vertical")); | |||
return scrollBar; | |||
} | |||
private WebElement getHorizontalScrollbar() { | |||
WebElement scrollBar = getGrid().findElement( | |||
By.className("v-grid-scroller-horizontal")); | |||
return scrollBar; | |||
} | |||
} |
@@ -19,6 +19,8 @@ import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.util.List; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Dimension; | |||
import org.openqa.selenium.JavascriptExecutor; | |||
@@ -29,15 +31,21 @@ import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
@TestCategory("escalator") | |||
@TestCategory("grid") | |||
public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest { | |||
private static final String LOGICAL_ROW_ATTRIBUTE_NAME = "vLogicalRow"; | |||
private static final String SPACER_CSS_CLASS = "v-escalator-spacer"; | |||
protected static final String COLUMNS_AND_ROWS = "Columns and Rows"; | |||
protected static final String COLUMNS = "Columns"; | |||
protected static final String ADD_ONE_COLUMN_TO_BEGINNING = "Add one column to beginning"; | |||
protected static final String ADD_ONE_ROW_TO_BEGINNING = "Add one row to beginning"; | |||
protected static final String ADD_ONE_ROW_TO_END = "Add one row to end"; | |||
protected static final String REMOVE_ONE_COLUMN_FROM_BEGINNING = "Remove one column from beginning"; | |||
protected static final String REMOVE_ONE_ROW_FROM_BEGINNING = "Remove one row from beginning"; | |||
protected static final String REMOVE_ALL_ROWS = "Remove all rows"; | |||
protected static final String REMOVE_50_ROWS_FROM_BOTTOM = "Remove 50 rows from bottom"; | |||
protected static final String REMOVE_50_ROWS_FROM_ALMOST_BOTTOM = "Remove 50 rows from almost bottom"; | |||
protected static final String ADD_ONE_OF_EACH_ROW = "Add one of each row"; | |||
@@ -48,6 +56,8 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest | |||
protected static final String BODY_ROWS = "Body Rows"; | |||
protected static final String FOOTER_ROWS = "Footer Rows"; | |||
protected static final String SCROLL_TO = "Scroll to..."; | |||
protected static final String REMOVE_ALL_INSERT_SCROLL = "Remove all, insert 30 and scroll 40px"; | |||
protected static final String GENERAL = "General"; | |||
@@ -65,6 +75,20 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest | |||
protected static final String COLUMN_SPANNING = "Column spanning"; | |||
protected static final String COLSPAN_NORMAL = "Apply normal colspan"; | |||
protected static final String COLSPAN_NONE = "Apply no colspan"; | |||
protected static final String SET_100PX = "Set 100px"; | |||
protected static final String SPACERS = "Spacers"; | |||
protected static final String FOCUSABLE_UPDATER = "Focusable Updater"; | |||
protected static final String SCROLL_HERE_ANY_0PADDING = "Scroll here (ANY, 0)"; | |||
protected static final String SCROLL_HERE_SPACERBELOW_ANY_0PADDING = "Scroll here row+spacer below (ANY, 0)"; | |||
protected static final String REMOVE = "Remove"; | |||
protected static final String ROW_MINUS1 = "Row -1"; | |||
protected static final String ROW_0 = "Row 0"; | |||
protected static final String ROW_1 = "Row 1"; | |||
protected static final String ROW_25 = "Row 25"; | |||
protected static final String ROW_50 = "Row 50"; | |||
protected static final String ROW_75 = "Row 75"; | |||
protected static final String ROW_99 = "Row 99"; | |||
@Override | |||
protected Class<?> getUIClass() { | |||
@@ -163,15 +187,16 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest | |||
private TestBenchElement getRow(String sectionTag, int row) { | |||
TestBenchElement escalator = getEscalator(); | |||
WebElement tableSection = escalator.findElement(By.tagName(sectionTag)); | |||
By xpath; | |||
String xpathExpression = "tr[not(@class='" + SPACER_CSS_CLASS + "')]"; | |||
if (row >= 0) { | |||
int fromFirst = row + 1; | |||
xpath = By.xpath("tr[" + fromFirst + "]"); | |||
xpathExpression += "[" + fromFirst + "]"; | |||
} else { | |||
int fromLast = Math.abs(row + 1); | |||
xpath = By.xpath("tr[last() - " + fromLast + "]"); | |||
xpathExpression += "[last() - " + fromLast + "]"; | |||
} | |||
By xpath = By.xpath(xpathExpression); | |||
if (tableSection != null | |||
&& ((TestBenchElement) tableSection).isElementPresent(xpath)) { | |||
return (TestBenchElement) tableSection.findElement(xpath); | |||
@@ -235,17 +260,26 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest | |||
} | |||
protected void scrollVerticallyTo(int px) { | |||
executeScript("arguments[0].scrollTop = " + px, getVerticalScrollbar()); | |||
getVerticalScrollbar().scroll(px); | |||
} | |||
protected TestBenchElement getVerticalScrollbar() { | |||
protected long getScrollTop() { | |||
return ((Long) executeScript("return arguments[0].scrollTop;", | |||
getVerticalScrollbar())).longValue(); | |||
} | |||
private TestBenchElement getVerticalScrollbar() { | |||
return (TestBenchElement) getEscalator().findElement( | |||
By.className("v-escalator-scroller-vertical")); | |||
} | |||
protected void scrollHorizontallyTo(int px) { | |||
executeScript("arguments[0].scrollLeft = " + px, | |||
getHorizontalScrollbar()); | |||
getHorizontalScrollbar().scrollLeft(px); | |||
} | |||
protected long getScrollLeft() { | |||
return ((Long) executeScript("return arguments[0].scrollLeft;", | |||
getHorizontalScrollbar())).longValue(); | |||
} | |||
protected TestBenchElement getHorizontalScrollbar() { | |||
@@ -260,4 +294,29 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest | |||
protected void populate() { | |||
selectMenuPath(GENERAL, POPULATE_COLUMN_ROW); | |||
} | |||
private List<WebElement> getSpacers() { | |||
return getEscalator().findElements(By.className(SPACER_CSS_CLASS)); | |||
} | |||
protected boolean spacersAreFoundInDom() { | |||
List<WebElement> spacers = getSpacers(); | |||
return spacers != null && !spacers.isEmpty(); | |||
} | |||
@SuppressWarnings("boxing") | |||
protected WebElement getSpacer(int logicalRowIndex) { | |||
List<WebElement> spacers = getSpacers(); | |||
System.out.println("size: " + spacers.size()); | |||
for (WebElement spacer : spacers) { | |||
System.out.println(spacer + ", " + logicalRowIndex); | |||
Boolean isInDom = (Boolean) executeScript("return arguments[0]['" | |||
+ LOGICAL_ROW_ATTRIBUTE_NAME + "'] === arguments[1]", | |||
spacer, logicalRowIndex); | |||
if (isInDom) { | |||
return spacer; | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -15,13 +15,15 @@ | |||
*/ | |||
package com.vaadin.tests.components.grid.basicfeatures; | |||
import java.util.List; | |||
import org.openqa.selenium.Dimension; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.interactions.Actions; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.GridElement; | |||
import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; | |||
/** | |||
* GridBasicClientFeatures. | |||
@@ -79,13 +81,27 @@ public abstract class GridBasicClientFeaturesTest extends GridBasicFeaturesTest | |||
} | |||
@Override | |||
protected GridElement getGridElement() { | |||
protected CustomGridElement getGridElement() { | |||
if (composite) { | |||
// Composite requires the basic client features widget for subparts | |||
return ((TestBenchElement) findElement(By | |||
.vaadin("//TestWidgetComponent"))).wrap(GridElement.class); | |||
.vaadin("//TestWidgetComponent"))) | |||
.wrap(CustomGridElement.class); | |||
} else { | |||
return super.getGridElement(); | |||
} | |||
} | |||
@Override | |||
protected void assertColumnHeaderOrder(int... indices) { | |||
List<TestBenchElement> headers = getGridHeaderRowCells(); | |||
for (int i = 0; i < indices.length; i++) { | |||
assertColumnHeader("HEADER (0," + indices[i] + ")", headers.get(i)); | |||
} | |||
} | |||
protected void toggleColumnReorder() { | |||
selectMenuPath("Component", "State", "Column Reordering"); | |||
} | |||
} |
@@ -46,10 +46,17 @@ import com.vaadin.tests.components.AbstractComponentTest; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.CssLayout; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.Grid.CellReference; | |||
import com.vaadin.ui.Grid.CellStyleGenerator; | |||
import com.vaadin.ui.Grid.Column; | |||
import com.vaadin.ui.Grid.ColumnReorderEvent; | |||
import com.vaadin.ui.Grid.ColumnReorderListener; | |||
import com.vaadin.ui.Grid.ColumnVisibilityChangeEvent; | |||
import com.vaadin.ui.Grid.ColumnVisibilityChangeListener; | |||
import com.vaadin.ui.Grid.DetailsGenerator; | |||
import com.vaadin.ui.Grid.FooterCell; | |||
import com.vaadin.ui.Grid.HeaderCell; | |||
import com.vaadin.ui.Grid.HeaderRow; | |||
@@ -58,6 +65,9 @@ import com.vaadin.ui.Grid.RowReference; | |||
import com.vaadin.ui.Grid.RowStyleGenerator; | |||
import com.vaadin.ui.Grid.SelectionMode; | |||
import com.vaadin.ui.Grid.SelectionModel; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.Notification; | |||
import com.vaadin.ui.Panel; | |||
import com.vaadin.ui.renderers.DateRenderer; | |||
import com.vaadin.ui.renderers.HtmlRenderer; | |||
import com.vaadin.ui.renderers.NumberRenderer; | |||
@@ -109,6 +119,74 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
} | |||
}; | |||
private ColumnReorderListener columnReorderListener = new ColumnReorderListener() { | |||
@Override | |||
public void columnReorder(ColumnReorderEvent event) { | |||
log("Columns reordered, userOriginated: " | |||
+ event.isUserOriginated()); | |||
} | |||
}; | |||
private ColumnVisibilityChangeListener columnVisibilityListener = new ColumnVisibilityChangeListener() { | |||
@Override | |||
public void columnVisibilityChanged(ColumnVisibilityChangeEvent event) { | |||
log("Visibility changed: "// | |||
+ "propertyId: " + event.getColumn().getPropertyId() // | |||
+ ", isHidden: " + event.getColumn().isHidden() // | |||
+ ", userOriginated: " + event.isUserOriginated()); | |||
} | |||
}; | |||
private Panel detailsPanel; | |||
private final DetailsGenerator detailedDetailsGenerator = new DetailsGenerator() { | |||
@Override | |||
public Component getDetails(final RowReference rowReference) { | |||
CssLayout cssLayout = new CssLayout(); | |||
cssLayout.setHeight("200px"); | |||
cssLayout.setWidth("100%"); | |||
Item item = rowReference.getItem(); | |||
for (Object propertyId : item.getItemPropertyIds()) { | |||
Property<?> prop = item.getItemProperty(propertyId); | |||
String string = prop.getValue().toString(); | |||
cssLayout.addComponent(new Label(string)); | |||
} | |||
final int rowIndex = grid.getContainerDataSource().indexOfId( | |||
rowReference.getItemId()); | |||
ClickListener clickListener = new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
Notification.show("You clicked on the " | |||
+ "button in the details for " + "row " + rowIndex); | |||
} | |||
}; | |||
cssLayout.addComponent(new Button("Press me", clickListener)); | |||
return cssLayout; | |||
} | |||
}; | |||
private final DetailsGenerator watchingDetailsGenerator = new DetailsGenerator() { | |||
private int id = 0; | |||
@Override | |||
public Component getDetails(RowReference rowReference) { | |||
return new Label("You are watching item id " | |||
+ rowReference.getItemId() + " (" + (id++) + ")"); | |||
} | |||
}; | |||
private final DetailsGenerator hierarchicalDetailsGenerator = new DetailsGenerator() { | |||
@Override | |||
public Component getDetails(RowReference rowReference) { | |||
detailsPanel = new Panel(); | |||
detailsPanel.setContent(new Label("One")); | |||
return detailsPanel; | |||
} | |||
}; | |||
@Override | |||
@SuppressWarnings("unchecked") | |||
protected Grid constructComponent() { | |||
@@ -213,7 +291,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
// Set varying column widths | |||
for (int col = 0; col < COLUMNS; col++) { | |||
grid.getColumn(getColumnProperty(col)).setWidth(100 + col * 50); | |||
Column column = grid.getColumn(getColumnProperty(col)); | |||
column.setWidth(100 + col * 50); | |||
column.setHidable(isColumnHidableByDefault(col)); | |||
} | |||
grid.addSortListener(new SortListener() { | |||
@@ -248,10 +328,35 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
addFilterActions(); | |||
addInternalActions(); | |||
createDetailsActions(); | |||
this.grid = grid; | |||
return grid; | |||
} | |||
protected boolean isColumnHidableByDefault(int col) { | |||
return false; | |||
} | |||
protected boolean isColumnHiddenByDefault(int col) { | |||
return false; | |||
} | |||
private void addInternalActions() { | |||
createClickAction("Update column order without updating client", | |||
"Internals", new Command<Grid, Void>() { | |||
@Override | |||
public void execute(Grid grid, Void value, Object data) { | |||
List<Column> columns = grid.getColumns(); | |||
grid.setColumnOrder(columns.get(1).getPropertyId(), | |||
columns.get(0).getPropertyId()); | |||
grid.getUI().getConnectorTracker().markClean(grid); | |||
} | |||
}, null); | |||
} | |||
private void addFilterActions() { | |||
createClickAction("Column 1 starts with \"(23\"", "Filter", | |||
new Command<Grid, Void>() { | |||
@@ -494,6 +599,29 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
} | |||
}); | |||
createBooleanAction("ColumnReorderListener", "State", false, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, Boolean value, Object data) { | |||
if (value) { | |||
grid.addColumnReorderListener(columnReorderListener); | |||
} else { | |||
grid.removeColumnReorderListener(columnReorderListener); | |||
} | |||
} | |||
}); | |||
createBooleanAction("ColumnVisibilityChangeListener", "State", false, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, Boolean value, Object data) { | |||
if (value) { | |||
grid.addColumnVisibilityChangeListener(columnVisibilityListener); | |||
} else { | |||
grid.removeColumnVisibilityChangeListener(columnVisibilityListener); | |||
} | |||
} | |||
}); | |||
createBooleanAction("Single select allow deselect", "State", | |||
singleSelectAllowDeselect, new Command<Grid, Boolean>() { | |||
@@ -508,6 +636,14 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
} | |||
} | |||
}); | |||
createBooleanAction("Column Reordering Allowed", "State", false, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid c, Boolean value, Object data) { | |||
c.setColumnReorderingAllowed(value); | |||
} | |||
}); | |||
} | |||
protected void createHeaderActions() { | |||
@@ -630,9 +766,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
}, null); | |||
} | |||
@SuppressWarnings("boxing") | |||
protected void createColumnActions() { | |||
createCategory("Columns", null); | |||
for (int c = 0; c < COLUMNS; c++) { | |||
final int index = c; | |||
createCategory(getColumnProperty(c), "Columns"); | |||
@@ -640,16 +776,55 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
createClickAction("Add / Remove", getColumnProperty(c), | |||
new Command<Grid, String>() { | |||
boolean wasHidable; | |||
boolean wasHidden; | |||
String wasColumnHidingToggleCaption; | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
String columnProperty = getColumnProperty((Integer) data); | |||
if (grid.getColumn(columnProperty) == null) { | |||
grid.addColumn(columnProperty); | |||
Column column = grid.getColumn(columnProperty); | |||
if (column == null) { | |||
column = grid.addColumn(columnProperty); | |||
column.setHidable(wasHidable); | |||
column.setHidden(wasHidden); | |||
column.setHidingToggleCaption(wasColumnHidingToggleCaption); | |||
} else { | |||
wasHidable = column.isHidable(); | |||
wasHidden = column.isHidden(); | |||
wasColumnHidingToggleCaption = column | |||
.getHidingToggleCaption(); | |||
grid.removeColumn(columnProperty); | |||
} | |||
} | |||
}, null, c); | |||
createClickAction("Move left", getColumnProperty(c), | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
final String columnProperty = getColumnProperty((Integer) data); | |||
List<Column> cols = grid.getColumns(); | |||
List<Object> reordered = new ArrayList<Object>(); | |||
boolean addAsLast = false; | |||
for (int i = 0; i < cols.size(); i++) { | |||
Column col = cols.get(i); | |||
if (col.getPropertyId().equals(columnProperty)) { | |||
if (i == 0) { | |||
addAsLast = true; | |||
} else { | |||
reordered.add(i - 1, columnProperty); | |||
} | |||
} else { | |||
reordered.add(col.getPropertyId()); | |||
} | |||
} | |||
if (addAsLast) { | |||
reordered.add(columnProperty); | |||
} | |||
grid.setColumnOrder(reordered.toArray()); | |||
} | |||
}, null, c); | |||
createBooleanAction("Sortable", getColumnProperty(c), true, | |||
new Command<Grid, Boolean>() { | |||
@@ -663,6 +838,37 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
} | |||
}, c); | |||
createBooleanAction("Hidable", getColumnProperty(c), | |||
isColumnHidableByDefault(c), new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid c, Boolean hidable, | |||
Object propertyId) { | |||
grid.getColumn(propertyId).setHidable(hidable); | |||
} | |||
}, getColumnProperty(c)); | |||
createBooleanAction("Hidden", getColumnProperty(c), | |||
isColumnHiddenByDefault(c), new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid c, Boolean hidden, | |||
Object propertyId) { | |||
grid.getColumn(propertyId).setHidden(hidden); | |||
} | |||
}, getColumnProperty(c)); | |||
createClickAction("Change hiding toggle caption", | |||
getColumnProperty(c), new Command<Grid, String>() { | |||
int count = 0; | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
final String columnProperty = getColumnProperty((Integer) data); | |||
grid.getColumn(columnProperty) | |||
.setHidingToggleCaption( | |||
columnProperty + " caption " | |||
+ count++); | |||
} | |||
}, null, c); | |||
createCategory("Column " + c + " Width", getColumnProperty(c)); | |||
createClickAction("Auto", "Column " + c + " Width", | |||
@@ -680,7 +886,6 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
createClickAction("25.5px", "Column " + c + " Width", | |||
new Command<Grid, Void>() { | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public void execute(Grid grid, Void value, | |||
Object columnIndex) { | |||
grid.getColumns().get((Integer) columnIndex) | |||
@@ -778,6 +983,17 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
}, c); | |||
} | |||
createBooleanAction("All columns hidable", "Columns", false, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid c, Boolean value, Object data) { | |||
for (Column col : grid.getColumns()) { | |||
col.setHidable(value); | |||
} | |||
} | |||
}); | |||
} | |||
private static String getColumnProperty(int c) { | |||
@@ -1051,6 +1267,67 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
}, null); | |||
} | |||
private void createDetailsActions() { | |||
Command<Grid, DetailsGenerator> swapDetailsGenerator = new Command<Grid, DetailsGenerator>() { | |||
@Override | |||
public void execute(Grid c, DetailsGenerator generator, Object data) { | |||
grid.setDetailsGenerator(generator); | |||
} | |||
}; | |||
Command<Grid, Boolean> openOrCloseItemId = new Command<Grid, Boolean>() { | |||
@Override | |||
@SuppressWarnings("boxing") | |||
public void execute(Grid g, Boolean visible, Object itemId) { | |||
g.setDetailsVisible(itemId, visible); | |||
} | |||
}; | |||
createCategory("Generators", "Details"); | |||
createClickAction("NULL", "Generators", swapDetailsGenerator, | |||
DetailsGenerator.NULL); | |||
createClickAction("\"Watching\"", "Generators", swapDetailsGenerator, | |||
watchingDetailsGenerator); | |||
createClickAction("Detailed", "Generators", swapDetailsGenerator, | |||
detailedDetailsGenerator); | |||
createClickAction("Hierarchical", "Generators", swapDetailsGenerator, | |||
hierarchicalDetailsGenerator); | |||
createClickAction("- Change Component", "Generators", | |||
new Command<Grid, Void>() { | |||
@Override | |||
public void execute(Grid c, Void value, Object data) { | |||
Label label = (Label) detailsPanel.getContent(); | |||
if (label.getValue().equals("One")) { | |||
detailsPanel.setContent(new Label("Two")); | |||
} else { | |||
detailsPanel.setContent(new Label("One")); | |||
} | |||
} | |||
}, null); | |||
createClickAction("Toggle firstItemId", "Details", | |||
new Command<Grid, Void>() { | |||
@Override | |||
public void execute(Grid g, Void value, Object data) { | |||
Object firstItemId = g.getContainerDataSource() | |||
.firstItemId(); | |||
boolean toggle = g.isDetailsVisible(firstItemId); | |||
g.setDetailsVisible(firstItemId, !toggle); | |||
g.setDetailsVisible(firstItemId, toggle); | |||
} | |||
}, null); | |||
createBooleanAction("Open firstItemId", "Details", false, | |||
openOrCloseItemId, ds.firstItemId()); | |||
createBooleanAction("Open 1", "Details", false, openOrCloseItemId, | |||
ds.getIdByIndex(1)); | |||
createBooleanAction("Open 995", "Details", false, openOrCloseItemId, | |||
ds.getIdByIndex(995)); | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 12829; |
@@ -15,6 +15,9 @@ | |||
*/ | |||
package com.vaadin.tests.components.grid.basicfeatures; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -25,13 +28,18 @@ import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.interactions.Actions; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.GridElement; | |||
import com.vaadin.testbench.elements.GridElement.GridCellElement; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
@TestCategory("grid") | |||
public abstract class GridBasicFeaturesTest extends MultiBrowserTest { | |||
public enum CellSide { | |||
LEFT, RIGHT; | |||
} | |||
@Override | |||
protected boolean requireWindowFocusForIE() { | |||
return true; | |||
@@ -59,9 +67,9 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { | |||
} | |||
} | |||
protected GridElement getGridElement() { | |||
protected CustomGridElement getGridElement() { | |||
return ((TestBenchElement) findElement(By.id("testComponent"))) | |||
.wrap(GridElement.class); | |||
.wrap(CustomGridElement.class); | |||
} | |||
protected void scrollGridVerticallyTo(double px) { | |||
@@ -69,6 +77,11 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { | |||
getGridVerticalScrollbar()); | |||
} | |||
protected void scrollGridHorizontallyTo(double px) { | |||
executeScript("arguments[0].scrollLeft = " + px, | |||
getGridHorizontalScrollbar()); | |||
} | |||
protected int getGridVerticalScrollPos() { | |||
return ((Number) executeScript("return arguments[0].scrollTop", | |||
getGridVerticalScrollbar())).intValue(); | |||
@@ -117,6 +130,12 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { | |||
By.xpath("//div[contains(@class, \"v-grid-scroller-vertical\")]")); | |||
} | |||
protected WebElement getGridHorizontalScrollbar() { | |||
return getDriver() | |||
.findElement( | |||
By.xpath("//div[contains(@class, \"v-grid-scroller-horizontal\")]")); | |||
} | |||
/** | |||
* Reloads the page without restartApplication. This occasionally breaks | |||
* stuff. | |||
@@ -127,4 +146,118 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { | |||
testUrl = testUrl.replace("?&", "?"); | |||
driver.get(testUrl); | |||
} | |||
protected void focusCell(int row, int column) { | |||
getGridElement().getCell(row, column).click(); | |||
} | |||
protected void setFrozenColumns(int numberOfFrozenColumns) { | |||
selectMenuPath("Component", "State", "Frozen column count", | |||
Integer.toString(numberOfFrozenColumns)); | |||
} | |||
protected void assertColumnHeaderOrder(int... indices) { | |||
List<TestBenchElement> headers = getGridHeaderRowCells(); | |||
for (int i = 0; i < indices.length; i++) { | |||
assertColumnHeader("Column " + indices[i], headers.get(i)); | |||
} | |||
} | |||
protected void assertColumnHeader(String expectedHeaderCaption, | |||
TestBenchElement testBenchElement) { | |||
assertEquals(expectedHeaderCaption.toLowerCase(), testBenchElement | |||
.getText().toLowerCase()); | |||
} | |||
protected GridCellElement getDefaultColumnHeader(int index) { | |||
List<GridCellElement> headerRowCells = getGridElement().getHeaderCells( | |||
0); | |||
return headerRowCells.get(index); | |||
} | |||
protected void dragAndDropDefaultColumnHeader(int draggedColumnHeaderIndex, | |||
int onTopOfColumnHeaderIndex, CellSide cellSide) { | |||
GridCellElement columnHeader = getDefaultColumnHeader(onTopOfColumnHeaderIndex); | |||
new Actions(getDriver()) | |||
.clickAndHold(getDefaultColumnHeader(draggedColumnHeaderIndex)) | |||
.moveToElement( | |||
columnHeader, | |||
getHorizontalOffsetForDragAndDrop(columnHeader, | |||
cellSide), 0).release().perform(); | |||
} | |||
private int getHorizontalOffsetForDragAndDrop(GridCellElement columnHeader, | |||
CellSide cellSide) { | |||
if (cellSide == CellSide.LEFT) { | |||
return 5; | |||
} else { | |||
int half = columnHeader.getSize().getWidth() / 2; | |||
return half + (half / 2); | |||
} | |||
} | |||
protected void dragAndDropColumnHeader(int headerRow, | |||
int draggedColumnHeaderIndex, int onTopOfColumnHeaderIndex, | |||
CellSide cellSide) { | |||
GridCellElement headerCell = getGridElement().getHeaderCell(headerRow, | |||
onTopOfColumnHeaderIndex); | |||
new Actions(getDriver()) | |||
.clickAndHold( | |||
getGridElement().getHeaderCell(headerRow, | |||
draggedColumnHeaderIndex)) | |||
.moveToElement( | |||
headerCell, | |||
getHorizontalOffsetForDragAndDrop(headerCell, cellSide), | |||
0).release().perform(); | |||
} | |||
protected void dragAndDropColumnHeader(int headerRow, | |||
int draggedColumnHeaderIndex, int onTopOfColumnHeaderIndex, | |||
int horizontalOffset) { | |||
GridCellElement headerCell = getGridElement().getHeaderCell(headerRow, | |||
onTopOfColumnHeaderIndex); | |||
new Actions(getDriver()) | |||
.clickAndHold( | |||
getGridElement().getHeaderCell(headerRow, | |||
draggedColumnHeaderIndex)) | |||
.moveToElement(headerCell, horizontalOffset, 0).release() | |||
.perform(); | |||
} | |||
protected void assertColumnIsSorted(int index) { | |||
WebElement columnHeader = getDefaultColumnHeader(index); | |||
assertTrue(columnHeader.getAttribute("class").contains("sort")); | |||
} | |||
protected void assertFocusedCell(int row, int column) { | |||
assertTrue(getGridElement().getCell(row, column).getAttribute("class") | |||
.contains("focused")); | |||
} | |||
protected WebElement getSidebar() { | |||
List<WebElement> elements = findElements(By.className("v-grid-sidebar")); | |||
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 = getSidebar(); | |||
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; | |||
} | |||
} |
@@ -0,0 +1,994 @@ | |||
/* | |||
* 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; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.List; | |||
import org.junit.Before; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Keys; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.interactions.Actions; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.GridElement.GridCellElement; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
@TestCategory("grid") | |||
public class GridColumnHidingTest extends GridBasicClientFeaturesTest { | |||
private static final String CAPTION_0_1 = "Join column cells 0, 1"; | |||
private static final String CAPTION_1_2 = "Join columns 1, 2"; | |||
private static final String CAPTION_3_4_5 = "Join columns 3, 4, 5"; | |||
private static final String CAPTION_ALL = "Join all columns"; | |||
@Before | |||
public void before() { | |||
openTestURL(); | |||
} | |||
@Test | |||
public void testColumnHiding_hidingColumnsFromAPI_works() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(1); | |||
toggleHideColumnAPI(2); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(4, 5, 6, 7, 8); | |||
} | |||
@Test | |||
public void testColumnHiding_unhidingColumnsFromAPI_works() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(1); | |||
toggleHideColumnAPI(2); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 4, 5, 6, 7, 8); | |||
toggleHideColumnAPI(1); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 2, 4, 5, 6); | |||
} | |||
@Test | |||
public void testColumnHiding_hidingUnhidingFromAPI_works() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); | |||
} | |||
@Test | |||
public void testColumnHiding_changeVisibilityAPI_triggersClientSideEvent() { | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
selectMenuPath("Component", "Internals", "Listeners", | |||
"Add Column Visibility Change listener"); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 3, 4); | |||
WebElement webElement = findElement(By.id("columnvisibility")); | |||
int counter = Integer.parseInt(webElement.getAttribute("counter")); | |||
int columnIndex = Integer.parseInt(webElement | |||
.getAttribute("columnindex")); | |||
boolean userOriginated = Boolean.parseBoolean(webElement | |||
.getAttribute("useroriginated")); | |||
boolean hidden = Boolean.parseBoolean(webElement | |||
.getAttribute("ishidden")); | |||
assertNotNull("no event fired", webElement); | |||
assertEquals(1, counter); | |||
assertEquals(2, columnIndex); | |||
assertEquals(false, userOriginated); | |||
assertEquals(true, hidden); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
webElement = findElement(By.id("columnvisibility")); | |||
counter = Integer.parseInt(webElement.getAttribute("counter")); | |||
columnIndex = Integer.parseInt(webElement.getAttribute("columnIndex")); | |||
userOriginated = Boolean.parseBoolean(webElement | |||
.getAttribute("userOriginated")); | |||
hidden = Boolean.parseBoolean(webElement.getAttribute("ishidden")); | |||
assertNotNull("no event fired", webElement); | |||
assertEquals(2, counter); | |||
assertEquals(2, columnIndex); | |||
assertEquals(false, userOriginated); | |||
assertEquals(false, hidden); | |||
} | |||
@Test | |||
public void testColumnHiding_changeVisibilityToggle_triggersClientSideEvent() { | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
selectMenuPath("Component", "Internals", "Listeners", | |||
"Add Column Visibility Change listener"); | |||
toggleHidableColumnAPI(2); | |||
clickSidebarOpenButton(); | |||
getColumnHidingToggle(2).click(); | |||
assertColumnHeaderOrder(0, 1, 3, 4); | |||
WebElement webElement = findElement(By.id("columnvisibility")); | |||
int counter = Integer.parseInt(webElement.getAttribute("counter")); | |||
int columnIndex = Integer.parseInt(webElement | |||
.getAttribute("columnindex")); | |||
boolean userOriginated = Boolean.parseBoolean(webElement | |||
.getAttribute("useroriginated")); | |||
boolean hidden = Boolean.parseBoolean(webElement | |||
.getAttribute("ishidden")); | |||
assertNotNull("no event fired", webElement); | |||
assertEquals(1, counter); | |||
assertEquals(2, columnIndex); | |||
assertEquals(true, userOriginated); | |||
assertEquals(true, hidden); | |||
getColumnHidingToggle(2).click(); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
webElement = findElement(By.id("columnvisibility")); | |||
counter = Integer.parseInt(webElement.getAttribute("counter")); | |||
columnIndex = Integer.parseInt(webElement.getAttribute("columnIndex")); | |||
userOriginated = Boolean.parseBoolean(webElement | |||
.getAttribute("userOriginated")); | |||
hidden = Boolean.parseBoolean(webElement.getAttribute("ishidden")); | |||
assertNotNull("no event fired", webElement); | |||
assertEquals(2, counter); | |||
assertEquals(2, columnIndex); | |||
assertEquals(true, userOriginated); | |||
assertEquals(false, hidden); | |||
} | |||
@Test | |||
public void testColumnHidability_onTriggerColumnHidability_showsSidebarButton() { | |||
WebElement sidebar = getSidebar(); | |||
assertNull(sidebar); | |||
toggleHidableColumnAPI(0); | |||
sidebar = getSidebar(); | |||
assertNotNull(sidebar); | |||
} | |||
@Test | |||
public void testColumnHidability_triggeringColumnHidabilityWithSeveralColumns_showsAndHidesSiderbarButton() { | |||
verifySidebarNotVisible(); | |||
toggleHidableColumnAPI(3); | |||
toggleHidableColumnAPI(4); | |||
verifySidebarVisible(); | |||
toggleHidableColumnAPI(3); | |||
verifySidebarVisible(); | |||
toggleHidableColumnAPI(4); | |||
verifySidebarNotVisible(); | |||
} | |||
@Test | |||
public void testColumnHidability_clickingSidebarButton_opensClosesSidebar() { | |||
toggleHidableColumnAPI(0); | |||
verifySidebarClosed(); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
clickSidebarOpenButton(); | |||
verifySidebarClosed(); | |||
} | |||
@Test | |||
public void testColumnHidability_settingColumnHidable_showsToggleInSidebar() { | |||
toggleHidableColumnAPI(0); | |||
verifySidebarClosed(); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(0, false); | |||
} | |||
@Test | |||
public void testColumnHiding_hidingColumnWithToggle_works() { | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
toggleHidableColumnAPI(0); | |||
verifySidebarClosed(); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(0, false); | |||
getColumnHidingToggle(0).click(); | |||
verifyColumnHidingOption(0, true); | |||
assertColumnHeaderOrder(1, 2, 3, 4); | |||
getColumnHidingToggle(0).click(); | |||
verifyColumnHidingOption(0, false); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
} | |||
@Test | |||
public void testColumnHiding_updatingHiddenWhileSidebarClosed_updatesToggleValue() { | |||
toggleHidableColumnAPI(0); | |||
toggleHidableColumnAPI(3); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 1, 2, 4); | |||
verifySidebarClosed(); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(0, false); | |||
verifyColumnHidingOption(3, true); | |||
clickSidebarOpenButton(); | |||
verifySidebarClosed(); | |||
toggleHideColumnAPI(0); | |||
toggleHideColumnAPI(3); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(0, true); | |||
verifyColumnHidingOption(3, false); | |||
} | |||
@Test | |||
public void testColumnHiding_hidingMultipleColumnsWithToggle_hidesColumns() { | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
toggleHideColumnAPI(1); | |||
toggleHidableColumnAPI(0); | |||
toggleHidableColumnAPI(1); | |||
toggleHidableColumnAPI(2); | |||
toggleHidableColumnAPI(3); | |||
toggleHidableColumnAPI(4); | |||
verifySidebarClosed(); | |||
assertColumnHeaderOrder(0, 2, 3, 4); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(0, false); | |||
verifyColumnHidingOption(1, true); | |||
verifyColumnHidingOption(2, false); | |||
verifyColumnHidingOption(3, false); | |||
verifyColumnHidingOption(4, false); | |||
// must be done in a funny order so that the header indexes won't break | |||
// (because of data source uses counter) | |||
getColumnHidingToggle(1).click(); | |||
getColumnHidingToggle(2).click(); | |||
getColumnHidingToggle(3).click(); | |||
getColumnHidingToggle(4).click(); | |||
getColumnHidingToggle(0).click(); | |||
verifyColumnHidingOption(0, true); | |||
verifyColumnHidingOption(1, false); | |||
verifyColumnHidingOption(2, true); | |||
verifyColumnHidingOption(3, true); | |||
verifyColumnHidingOption(4, true); | |||
assertColumnHeaderOrder(1, 5, 6, 7); | |||
getColumnHidingToggle(0).click(); | |||
getColumnHidingToggle(2).click(); | |||
getColumnHidingToggle(1).click(); | |||
verifyColumnHidingOption(0, false); | |||
verifyColumnHidingOption(1, true); | |||
verifyColumnHidingOption(2, false); | |||
assertColumnHeaderOrder(0, 2, 5, 6); | |||
} | |||
@Test | |||
public void testColumnHidability_changingHidabilityWhenSidebarClosed_addsRemovesToggles() { | |||
toggleHideColumnAPI(0); | |||
toggleHideColumnAPI(4); | |||
assertColumnHeaderOrder(1, 2, 3, 5); | |||
toggleHidableColumnAPI(0); | |||
toggleHidableColumnAPI(3); | |||
toggleHidableColumnAPI(4); | |||
verifySidebarClosed(); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(0, true); | |||
verifyColumnHidingOption(3, false); | |||
verifyColumnHidingOption(4, true); | |||
clickSidebarOpenButton(); | |||
verifySidebarClosed(); | |||
toggleHidableColumnAPI(0); | |||
toggleHidableColumnAPI(3); | |||
verifySidebarClosed(); | |||
clickSidebarOpenButton(); | |||
verifySidebarOpened(); | |||
verifyColumnHidingOption(4, true); | |||
assertNull(getColumnHidingToggle(0)); | |||
assertNull(getColumnHidingToggle(3)); | |||
} | |||
@Test | |||
public void testColumnHidability_togglingHidability_placesTogglesInRightOrder() { | |||
toggleHidableColumnAPI(3); | |||
toggleHidableColumnAPI(2); | |||
clickSidebarOpenButton(); | |||
verifyColumnHidingTogglesOrder(2, 3); | |||
toggleHidableColumnAPI(1); | |||
toggleHidableColumnAPI(2); | |||
toggleHidableColumnAPI(6); | |||
toggleHidableColumnAPI(0); | |||
verifyColumnHidingTogglesOrder(0, 1, 3, 6); | |||
clickSidebarOpenButton(); | |||
toggleHidableColumnAPI(2); | |||
toggleHidableColumnAPI(4); | |||
toggleHidableColumnAPI(7); | |||
clickSidebarOpenButton(); | |||
verifyColumnHidingTogglesOrder(0, 1, 2, 3, 4, 6, 7); | |||
} | |||
@Test | |||
public void testColumnHidability_reorderingColumns_updatesColumnToggleOrder() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
toggleHidableColumnAPI(0); | |||
toggleHidableColumnAPI(1); | |||
toggleHidableColumnAPI(3); | |||
toggleHidableColumnAPI(4); | |||
clickSidebarOpenButton(); | |||
verifyColumnHidingTogglesOrder(0, 1, 3, 4); | |||
clickSidebarOpenButton(); | |||
toggleColumnReorder(); | |||
dragAndDropColumnHeader(0, 3, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
clickSidebarOpenButton(); | |||
verifyColumnHidingTogglesOrder(3, 0, 1, 4); | |||
clickSidebarOpenButton(); | |||
dragAndDropColumnHeader(0, 1, 3, CellSide.RIGHT); | |||
dragAndDropColumnHeader(0, 4, 0, CellSide.LEFT); | |||
dragAndDropColumnHeader(0, 3, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(2, 4, 3, 1, 0); | |||
clickSidebarOpenButton(); | |||
verifyColumnHidingTogglesOrder(4, 3, 1, 0); | |||
} | |||
@Test | |||
public void testColumnHidingAndReorder_reorderingOverHiddenColumn_orderIsKept() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
toggleColumnReorder(); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
dragAndDropColumnHeader(0, 1, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(2, 1, 3, 4, 5); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(0, 2, 1, 3, 4, 5); | |||
toggleHideColumnAPI(1); | |||
assertColumnHeaderOrder(0, 2, 3, 4, 5); | |||
// right side of hidden column | |||
dragAndDropColumnHeader(0, 0, 2, CellSide.LEFT); | |||
assertColumnHeaderOrder(2, 0, 3, 4, 5); | |||
toggleHideColumnAPI(1); | |||
assertColumnHeaderOrder(2, 1, 0, 3, 4, 5); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(2, 1, 3, 4, 5); | |||
// left side of hidden column | |||
dragAndDropColumnHeader(0, 0, 1, CellSide.RIGHT); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(1, 0, 2, 3, 4, 5); | |||
} | |||
@Test | |||
public void testColumnHidingAndReorder_reorderingWithMultipleHiddenColumns_works() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
toggleColumnReorder(); | |||
toggleHideColumnAPI(2); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 1, 4, 5, 6); | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
assertColumnHeaderOrder(1, 0, 4, 5, 6); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(1, 3, 0, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(1, 2, 3, 0, 4, 5, 6); | |||
toggleHideColumnAPI(0); | |||
toggleHideColumnAPI(4); | |||
assertColumnHeaderOrder(1, 2, 3, 5, 6); | |||
dragAndDropDefaultColumnHeader(4, 3, CellSide.LEFT); | |||
assertColumnHeaderOrder(1, 2, 3, 6, 5); | |||
dragAndDropDefaultColumnHeader(4, 2, CellSide.RIGHT); | |||
assertColumnHeaderOrder(1, 2, 3, 5, 6); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(1, 2, 3, 0, 5, 6); | |||
toggleHideColumnAPI(4); | |||
assertColumnHeaderOrder(1, 2, 3, 0, 4, 5, 6); | |||
} | |||
@Test | |||
public void testReorderingHiddenColumns_movingHiddenColumn_indexIsUpdated() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
toggleHideColumnAPI(2); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 1, 4, 5, 6); | |||
moveColumnLeft(3); | |||
assertColumnHeaderOrder(0, 1, 4, 5, 6); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(0, 1, 3, 2, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 1, 4, 5, 6); | |||
moveColumnLeft(2); | |||
moveColumnLeft(2); | |||
moveColumnLeft(2); | |||
assertColumnHeaderOrder(0, 1, 4, 5, 6); | |||
toggleHideColumnAPI(2); | |||
assertColumnHeaderOrder(2, 0, 1, 4, 5, 6); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(2, 0, 1, 3, 4, 5, 6); | |||
} | |||
// keyboard actions not working in client side test case? | |||
@Test | |||
@Ignore | |||
public void testNavigationWithHiddenColumns_navigatingOverHiddenColumn_goesToNextVisibleColumn() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
toggleHideColumnAPI(2); | |||
toggleHideColumnAPI(3); | |||
assertColumnHeaderOrder(0, 1, 4, 5, 6); | |||
getGridElement().getCell(2, 4).click(); | |||
GridCellElement cell = getGridElement().getCell(2, 4); | |||
assertTrue(cell.isFocused()); | |||
new Actions(getDriver()).sendKeys(Keys.ARROW_LEFT); | |||
cell = getGridElement().getCell(2, 1); | |||
assertTrue(cell.isFocused()); | |||
new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT); | |||
cell = getGridElement().getCell(2, 4); | |||
assertTrue(cell.isFocused()); | |||
} | |||
@Test | |||
public void testNavigationWithHiddenColumns_hiddenFirstAndLastColumn_keepsNavigation() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
toggleHideColumnAPI(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); | |||
getGridElement().getCell(2, 1).click(); | |||
assertTrue(getGridElement().getCell(2, 1).isFocused()); | |||
new Actions(getDriver()).sendKeys(Keys.ARROW_LEFT); | |||
GridCellElement cell = getGridElement().getCell(2, 1); | |||
assertTrue(cell.isFocused()); | |||
scrollGridHorizontallyTo(10000); | |||
// | |||
getGridElement().getHeaderCell(0, 9).click(); | |||
cell = getGridElement().getHeaderCell(0, 9); | |||
assertTrue(cell.isFocused()); | |||
toggleHideColumnAPI(10); | |||
toggleHideColumnAPI(11); | |||
new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT); | |||
new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT); | |||
toggleHideColumnAPI(10); | |||
toggleHideColumnAPI(11); | |||
cell = getGridElement().getHeaderCell(0, 9); | |||
assertTrue(cell.isFocused()); | |||
} | |||
@Test | |||
public void testFrozenColumnHiding_lastFrozenColumnHidden_isFrozenWhenMadeVisible() { | |||
toggleFrozenColumns(2); | |||
toggleHidableColumnAPI(0); | |||
toggleHidableColumnAPI(1); | |||
getSidebarOpenButton().click(); | |||
verifyColumnIsFrozen(0); | |||
verifyColumnIsFrozen(1); | |||
verifyColumnIsNotFrozen(2); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
getColumnHidingToggle(1).click(); | |||
verifyColumnIsFrozen(0); | |||
// the grid element indexing doesn't take hidden columns into account! | |||
verifyColumnIsNotFrozen(1); | |||
assertColumnHeaderOrder(0, 2, 3); | |||
getColumnHidingToggle(0).click(); | |||
verifyColumnIsNotFrozen(0); | |||
assertColumnHeaderOrder(2, 3, 4); | |||
getColumnHidingToggle(0).click(); | |||
assertColumnHeaderOrder(0, 2, 3); | |||
verifyColumnIsFrozen(0); | |||
verifyColumnIsNotFrozen(1); | |||
getColumnHidingToggle(1).click(); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
verifyColumnIsFrozen(0); | |||
verifyColumnIsFrozen(1); | |||
verifyColumnIsNotFrozen(2); | |||
} | |||
@Test | |||
public void testFrozenColumnHiding_columnHiddenFrozenCountChanged_columnIsFrozenWhenVisible() { | |||
toggleHidableColumnAPI(1); | |||
toggleHidableColumnAPI(2); | |||
getSidebarOpenButton().click(); | |||
getColumnHidingToggle(1).click(); | |||
getColumnHidingToggle(2).click(); | |||
assertColumnHeaderOrder(0, 3, 4); | |||
toggleFrozenColumns(3); | |||
verifyColumnIsFrozen(0); | |||
// the grid element indexing doesn't take hidden columns into account! | |||
verifyColumnIsNotFrozen(1); | |||
verifyColumnIsNotFrozen(2); | |||
getColumnHidingToggle(2).click(); | |||
verifyColumnIsFrozen(0); | |||
verifyColumnIsFrozen(1); | |||
verifyColumnIsNotFrozen(2); | |||
verifyColumnIsNotFrozen(3); | |||
getColumnHidingToggle(1).click(); | |||
verifyColumnIsFrozen(0); | |||
verifyColumnIsFrozen(1); | |||
verifyColumnIsFrozen(2); | |||
verifyColumnIsNotFrozen(3); | |||
verifyColumnIsNotFrozen(4); | |||
} | |||
@Test | |||
public void testSpannedCells_hidingColumnInBeginning_rendersSpannedCellCorrectly() { | |||
loadSpannedCellsFixture(); | |||
verifySpannedCellsFixtureStart(); | |||
toggleHideColumnAPI(0); | |||
verifyNumberOfCellsInHeader(0, 7); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 6); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 2, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 0, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 1); | |||
verifyHeaderCellColspan(1, 2, 3); | |||
verifyHeaderCellColspan(2, 1, 2); | |||
toggleHideColumnAPI(0); | |||
verifySpannedCellsFixtureStart(); | |||
toggleHideColumnAPI(1); | |||
verifyNumberOfCellsInHeader(0, 7); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 7); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 2, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 1); | |||
verifyHeaderCellColspan(1, 2, 3); | |||
verifyHeaderCellColspan(2, 1, 1); | |||
toggleHideColumnAPI(3); | |||
verifyNumberOfCellsInHeader(0, 6); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 6); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 2, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 1); | |||
verifyHeaderCellColspan(1, 2, 2); | |||
verifyHeaderCellColspan(2, 1, 1); | |||
toggleHideColumnAPI(1); | |||
verifyNumberOfCellsInHeader(0, 7); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 6); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 3, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 2); | |||
verifyHeaderCellColspan(1, 3, 2); | |||
verifyHeaderCellColspan(2, 1, 2); | |||
toggleHideColumnAPI(3); | |||
verifySpannedCellsFixtureStart(); | |||
} | |||
@Test | |||
public void testSpannedCells_hidingColumnInMiddle_rendersSpannedCellCorrectly() { | |||
loadSpannedCellsFixture(); | |||
verifySpannedCellsFixtureStart(); | |||
toggleHideColumnAPI(4); | |||
verifyNumberOfCellsInHeader(0, 7); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 6); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 3, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 2); | |||
verifyHeaderCellColspan(1, 3, 2); | |||
verifyHeaderCellColspan(2, 1, 2); | |||
toggleHideColumnAPI(4); | |||
verifySpannedCellsFixtureStart(); | |||
} | |||
@Test | |||
public void testSpannedCells_hidingColumnInEnd_rendersSpannedCellCorrectly() { | |||
loadSpannedCellsFixture(); | |||
verifySpannedCellsFixtureStart(); | |||
toggleHideColumnAPI(1); | |||
verifyNumberOfCellsInHeader(0, 7); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 7); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 2, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 1, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 1); | |||
verifyHeaderCellColspan(1, 2, 3); | |||
verifyHeaderCellColspan(2, 1, 1); | |||
toggleHideColumnAPI(1); | |||
verifySpannedCellsFixtureStart(); | |||
toggleHideColumnAPI(2); | |||
verifyNumberOfCellsInHeader(0, 7); | |||
verifyNumberOfCellsInHeader(1, 4); | |||
verifyNumberOfCellsInHeader(2, 7); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 3, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 2); | |||
verifyHeaderCellColspan(1, 3, 3); | |||
verifyHeaderCellColspan(2, 1, 1); | |||
toggleHideColumnAPI(5); | |||
verifyNumberOfCellsInHeader(0, 6); | |||
verifyNumberOfCellsInHeader(1, 4); | |||
verifyNumberOfCellsInHeader(2, 6); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 3, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 2); | |||
verifyHeaderCellColspan(1, 3, 2); | |||
verifyHeaderCellColspan(2, 1, 1); | |||
toggleHideColumnAPI(5); | |||
toggleHideColumnAPI(2); | |||
verifySpannedCellsFixtureStart(); | |||
} | |||
@Test | |||
public void testSpannedCells_spanningCellOverHiddenColumn_rendersSpannedCellCorrectly() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
appendHeaderRow(); | |||
toggleHideColumnAPI(4); | |||
toggleHideColumnAPI(8); | |||
toggleHideColumnAPI(9); | |||
toggleHideColumnAPI(10); | |||
toggleHideColumnAPI(11); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 5, 6, 7); | |||
verifyNumberOfCellsInHeader(1, 7); | |||
mergeHeaderCellsTwoThreeFour(2); | |||
verifyNumberOfCellsInHeader(1, 6); | |||
verifyHeaderCellContent(1, 3, CAPTION_3_4_5); | |||
verifyHeaderCellColspan(1, 3, 2); | |||
} | |||
@Test | |||
public void testSpannedCells_spanningCellAllHiddenColumns_rendersSpannedCellCorrectly() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
appendHeaderRow(); | |||
toggleHideColumnAPI(3); | |||
toggleHideColumnAPI(4); | |||
toggleHideColumnAPI(5); | |||
toggleHideColumnAPI(8); | |||
toggleHideColumnAPI(9); | |||
toggleHideColumnAPI(10); | |||
toggleHideColumnAPI(11); | |||
assertColumnHeaderOrder(0, 1, 2, 6, 7); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
mergeHeaderCellsTwoThreeFour(2); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyHeaderCellColspan(1, 0, 1); | |||
verifyHeaderCellColspan(1, 1, 1); | |||
verifyHeaderCellColspan(1, 2, 1); | |||
verifyHeaderCellColspan(1, 3, 1); | |||
verifyHeaderCellColspan(1, 4, 1); | |||
} | |||
private void loadSpannedCellsFixture() { | |||
selectMenuPath("Component", "State", "Width", "1000px"); | |||
appendHeaderRow(); | |||
appendHeaderRow(); | |||
appendHeaderRow(); | |||
mergeHeaderCellsTwoThreeFour(2); | |||
mergeHeaderCellsZeroOne(2); | |||
mergeHeaderCellsOneTwo(3); | |||
mergeHeaderCellsAll(4); | |||
toggleHideColumnAPI(8); | |||
toggleHideColumnAPI(9); | |||
toggleHideColumnAPI(10); | |||
toggleHideColumnAPI(11); | |||
} | |||
private void verifySpannedCellsFixtureStart() { | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6, 7); | |||
verifyNumberOfCellsInHeader(0, 8); | |||
verifyNumberOfCellsInHeader(1, 5); | |||
verifyNumberOfCellsInHeader(2, 7); | |||
verifyNumberOfCellsInHeader(3, 1); | |||
verifyHeaderCellContent(1, 0, CAPTION_0_1); | |||
verifyHeaderCellContent(1, 3, CAPTION_3_4_5); | |||
verifyHeaderCellContent(2, 1, CAPTION_1_2); | |||
verifyHeaderCellContent(3, 0, CAPTION_ALL); | |||
verifyHeaderCellColspan(1, 0, 2); | |||
verifyHeaderCellColspan(1, 3, 3); | |||
verifyHeaderCellColspan(2, 1, 2); | |||
} | |||
private void toggleFrozenColumns(int count) { | |||
selectMenuPath("Component", "State", "Frozen column count", count | |||
+ " columns"); | |||
} | |||
private void verifyHeaderCellColspan(int row, int column, int colspan) { | |||
assertEquals(Integer.valueOf(colspan), Integer.valueOf(Integer | |||
.parseInt(getGridElement().getHeaderCell(row, column) | |||
.getAttribute("colspan")))); | |||
} | |||
private void verifyNumberOfCellsInHeader(int row, int numberOfCells) { | |||
int size = 0; | |||
for (TestBenchElement cell : getGridElement().getHeaderCells(row)) { | |||
if (cell.isDisplayed()) { | |||
size++; | |||
} | |||
} | |||
assertEquals(numberOfCells, size); | |||
} | |||
private void verifyHeaderCellContent(int row, int column, String content) { | |||
GridCellElement headerCell = getGridElement() | |||
.getHeaderCell(row, column); | |||
assertEquals(content.toLowerCase(), headerCell.getText().toLowerCase()); | |||
assertTrue(headerCell.isDisplayed()); | |||
} | |||
private void verifyColumnIsFrozen(int index) { | |||
assertTrue(getGridElement().getHeaderCell(0, index).isFrozen()); | |||
} | |||
private void verifyColumnIsNotFrozen(int index) { | |||
assertFalse(getGridElement().getHeaderCell(0, index).isFrozen()); | |||
} | |||
private void verifyColumnHidingTogglesOrder(int... indices) { | |||
WebElement sidebar = getSidebar(); | |||
List<WebElement> elements = sidebar.findElements(By | |||
.className("column-hiding-toggle")); | |||
for (int i = 0; i < indices.length; i++) { | |||
WebElement e = elements.get(i); | |||
assertTrue(("Header (0," + indices[i] + ")").equalsIgnoreCase(e | |||
.getText())); | |||
} | |||
} | |||
private void verifyColumnHidingOption(int columnIndex, boolean hidden) { | |||
WebElement columnHidingToggle = getColumnHidingToggle(columnIndex); | |||
assertEquals(hidden, | |||
columnHidingToggle.getAttribute("class").contains("hidden")); | |||
} | |||
private void verifySidebarOpened() { | |||
WebElement sidebar = getSidebar(); | |||
assertTrue(sidebar.getAttribute("class").contains("opened")); | |||
} | |||
private void verifySidebarClosed() { | |||
WebElement sidebar = getSidebar(); | |||
assertFalse(sidebar.getAttribute("class").contains("opened")); | |||
} | |||
private void verifySidebarNotVisible() { | |||
WebElement sidebar = getSidebar(); | |||
assertNull(sidebar); | |||
} | |||
private void verifySidebarVisible() { | |||
WebElement sidebar = getSidebar(); | |||
assertNotNull(sidebar); | |||
} | |||
@Override | |||
protected WebElement getSidebar() { | |||
List<WebElement> elements = findElements(By.className("v-grid-sidebar")); | |||
return elements.isEmpty() ? null : elements.get(0); | |||
} | |||
@Override | |||
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. | |||
*/ | |||
@Override | |||
protected WebElement getColumnHidingToggle(int columnIndex) { | |||
WebElement sidebar = getSidebar(); | |||
List<WebElement> elements = sidebar.findElements(By | |||
.className("column-hiding-toggle")); | |||
for (WebElement e : elements) { | |||
if (("Header (0," + columnIndex + ")") | |||
.equalsIgnoreCase(e.getText())) { | |||
return e; | |||
} | |||
} | |||
return null; | |||
} | |||
private void clickSidebarOpenButton() { | |||
getSidebarOpenButton().click(); | |||
} | |||
private void moveColumnLeft(int index) { | |||
selectMenuPath("Component", "Columns", "Column " + index, | |||
"Move column left"); | |||
} | |||
private void toggleHidableColumnAPI(int columnIndex) { | |||
selectMenuPath("Component", "Columns", "Column " + columnIndex, | |||
"Hidable"); | |||
} | |||
private void toggleHideColumnAPI(int columnIndex) { | |||
selectMenuPath("Component", "Columns", "Column " + columnIndex, | |||
"Hidden"); | |||
} | |||
private void appendHeaderRow() { | |||
selectMenuPath("Component", "Header", "Append row"); | |||
} | |||
private void mergeHeaderCellsZeroOne(int row) { | |||
selectMenuPath("Component", "Header", "Row " + row, CAPTION_0_1); | |||
} | |||
private void mergeHeaderCellsOneTwo(int row) { | |||
selectMenuPath("Component", "Header", "Row " + row, CAPTION_1_2); | |||
} | |||
private void mergeHeaderCellsTwoThreeFour(int row) { | |||
selectMenuPath("Component", "Header", "Row " + row, CAPTION_3_4_5); | |||
} | |||
private void mergeHeaderCellsAll(int row) { | |||
selectMenuPath("Component", "Header", "Row " + row, CAPTION_ALL); | |||
} | |||
} |
@@ -0,0 +1,649 @@ | |||
/* | |||
* 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; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.interactions.Actions; | |||
import com.vaadin.testbench.elements.GridElement.GridCellElement; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
/** | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
@TestCategory("grid") | |||
public class GridColumnReorderTest extends GridBasicClientFeaturesTest { | |||
@Before | |||
public void before() { | |||
openTestURL(); | |||
} | |||
@Test | |||
public void columnReorderEventTriggered() { | |||
final int firstIndex = 3; | |||
final int secondIndex = 4; | |||
final String firstHeaderText = getGridElement().getHeaderCell(0, | |||
firstIndex).getText(); | |||
final String secondHeaderText = getGridElement().getHeaderCell(0, | |||
secondIndex).getText(); | |||
selectMenuPath("Component", "Internals", "Listeners", | |||
"Add ColumnReorder listener"); | |||
selectMenuPath("Component", "Columns", "Column " + secondIndex, | |||
"Move column left"); | |||
// columns 3 and 4 should have swapped to 4 and 3 | |||
GridCellElement headerCell = getGridElement().getHeaderCell(0, | |||
firstIndex); | |||
assertEquals(secondHeaderText, headerCell.getText()); | |||
headerCell = getGridElement().getHeaderCell(0, secondIndex); | |||
assertEquals(firstHeaderText, headerCell.getText()); | |||
// the reorder event should have typed the order to this label | |||
WebElement columnReorderElement = findElement(By.id("columnreorder")); | |||
int eventIndex = Integer.parseInt(columnReorderElement | |||
.getAttribute("columns")); | |||
assertEquals(1, eventIndex); | |||
// trigger another event | |||
selectMenuPath("Component", "Columns", "Column " + secondIndex, | |||
"Move column left"); | |||
columnReorderElement = findElement(By.id("columnreorder")); | |||
eventIndex = Integer.parseInt(columnReorderElement | |||
.getAttribute("columns")); | |||
assertEquals(2, eventIndex); | |||
} | |||
@Test | |||
public void testColumnReorder_onReorder_columnReorderEventTriggered() { | |||
final int firstIndex = 3; | |||
final int secondIndex = 4; | |||
final String firstHeaderText = getGridElement().getHeaderCell(0, | |||
firstIndex).getText(); | |||
final String secondHeaderText = getGridElement().getHeaderCell(0, | |||
secondIndex).getText(); | |||
selectMenuPath("Component", "Internals", "Listeners", | |||
"Add ColumnReorder listener"); | |||
selectMenuPath("Component", "Columns", "Column " + secondIndex, | |||
"Move column left"); | |||
// columns 3 and 4 should have swapped to 4 and 3 | |||
GridCellElement headerCell = getGridElement().getHeaderCell(0, | |||
firstIndex); | |||
assertEquals(secondHeaderText, headerCell.getText()); | |||
headerCell = getGridElement().getHeaderCell(0, secondIndex); | |||
assertEquals(firstHeaderText, headerCell.getText()); | |||
// the reorder event should have typed the order to this label | |||
WebElement columnReorderElement = findElement(By.id("columnreorder")); | |||
int eventIndex = Integer.parseInt(columnReorderElement | |||
.getAttribute("columns")); | |||
assertEquals(1, eventIndex); | |||
// trigger another event | |||
selectMenuPath("Component", "Columns", "Column " + secondIndex, | |||
"Move column left"); | |||
columnReorderElement = findElement(By.id("columnreorder")); | |||
eventIndex = Integer.parseInt(columnReorderElement | |||
.getAttribute("columns")); | |||
assertEquals(2, eventIndex); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingSortedColumn_sortIndicatorShownOnDraggedElement() { | |||
// given | |||
toggleColumnReorder(); | |||
toggleSortableColumn(0); | |||
sortColumn(0); | |||
// when | |||
startDragButDontDropOnDefaultColumnHeader(0); | |||
// then | |||
WebElement draggedElement = getDraggedHeaderElement(); | |||
assertTrue(draggedElement.getAttribute("class").contains("sort")); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingSortedColumn_sortStays() { | |||
// given | |||
toggleColumnReorder(); | |||
toggleSortableColumn(0); | |||
sortColumn(0); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
// then | |||
assertColumnIsSorted(1); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingFocusedHeader_focusShownOnDraggedElement() { | |||
// given | |||
toggleColumnReorder(); | |||
focusDefaultHeader(0); | |||
// when | |||
startDragButDontDropOnDefaultColumnHeader(0); | |||
// then | |||
WebElement draggedElement = getDraggedHeaderElement(); | |||
assertTrue(draggedElement.getAttribute("class").contains("focused")); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingFocusedHeader_focusIsKeptOnHeader() { | |||
// given | |||
toggleColumnReorder(); | |||
focusDefaultHeader(0); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 3, CellSide.LEFT); | |||
// then | |||
WebElement defaultColumnHeader = getDefaultColumnHeader(2); | |||
String attribute = defaultColumnHeader.getAttribute("class"); | |||
assertTrue(attribute.contains("focused")); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingFocusedCellColumn_focusIsKeptOnCell() { | |||
// given | |||
toggleColumnReorder(); | |||
focusCell(2, 2); | |||
// when | |||
dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); | |||
// then | |||
assertFocusedCell(2, 0); | |||
} | |||
@Test | |||
public void testColumnReorderWithHiddenColumn_draggingFocusedCellColumnOverHiddenColumn_focusIsKeptOnCell() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Columns", "Column 1", "Hidden"); | |||
focusCell(2, 2); | |||
assertFocusedCell(2, 2); | |||
// when | |||
dragAndDropDefaultColumnHeader(1, 0, CellSide.LEFT); | |||
// then | |||
assertFocusedCell(2, 2); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
// then | |||
assertFocusedCell(2, 2); | |||
} | |||
@Test | |||
public void testColumnReorder_dragColumnFromRightToLeftOfFocusedCellColumn_focusIsKept() { | |||
// given | |||
toggleColumnReorder(); | |||
focusCell(1, 3); | |||
// when | |||
dragAndDropDefaultColumnHeader(4, 1, CellSide.LEFT); | |||
// then | |||
assertFocusedCell(1, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_dragColumnFromLeftToRightOfFocusedCellColumn_focusIsKept() { | |||
// given | |||
toggleColumnReorder(); | |||
focusCell(4, 2); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 4, CellSide.LEFT); | |||
// then | |||
assertFocusedCell(4, 1); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingHeaderRowThatHasColumnHeadersSpanned_cantDropInsideSpannedHeaderFromOutside() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
int horizontalOffset = (getGridElement().getHeaderCell(1, 1).getSize() | |||
.getWidth() / 2) - 10; | |||
dragAndDropColumnHeader(1, 3, 1, horizontalOffset); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 1, 2, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_anotherRowHasColumnHeadersSpanned_cantDropInsideSpannedHeaderFromOutside() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
int horizontalOffset = (getGridElement().getHeaderCell(1, 1).getSize() | |||
.getWidth() / 2) + 10; | |||
dragAndDropColumnHeader(0, 0, 2, horizontalOffset); | |||
// then | |||
assertColumnHeaderOrder(1, 2, 0, 3, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellInsideSpannedHeader_cantBeDroppedOutsideSpannedArea() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(0, 2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(0, 2, 1, 3, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellInsideTwoCrossingSpanningHeaders_cantTouchThis() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join column cells 0, 1"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
dragAndDropColumnHeader(0, 3, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
// when | |||
dragAndDropColumnHeader(0, 2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellsInsideSpannedHeaderAndBlockedByOtherSpannedCells_cantTouchThose() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join column cells 0, 1"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
dragAndDropColumnHeader(0, 3, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
// when then | |||
dragAndDropColumnHeader(0, 1, 3, CellSide.LEFT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
dragAndDropColumnHeader(1, 2, 1, CellSide.LEFT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
dragAndDropColumnHeader(2, 1, 2, CellSide.RIGHT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellsInsideSpannedHeaderAndBlockedByOtherSpannedCells_reorderingLimited() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
dragAndDropColumnHeader(0, 0, 4, CellSide.RIGHT); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
// when then | |||
dragAndDropColumnHeader(0, 1, 4, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
dragAndDropColumnHeader(0, 2, 4, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
dragAndDropColumnHeader(0, 3, 4, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 5, 4); | |||
dragAndDropColumnHeader(0, 4, 2, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
dragAndDropColumnHeader(2, 3, 4, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 5, 4); | |||
dragAndDropColumnHeader(2, 4, 2, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
assertColumnHeaderOrder(1, 2, 3, 4, 5); | |||
} | |||
@Test | |||
public void testColumnReorder_cellsInsideTwoAdjacentSpannedHeaders_reorderingLimited() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
dragAndDropColumnHeader(0, 0, 4, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
dragAndDropColumnHeader(0, 1, 4, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(1, 3, 4, 5, 2); | |||
// when then | |||
dragAndDropColumnHeader(0, 1, 4, CellSide.LEFT); | |||
assertColumnHeaderOrder(1, 4, 3, 5, 2); | |||
dragAndDropColumnHeader(0, 2, 4, CellSide.LEFT); | |||
assertColumnHeaderOrder(1, 4, 3, 5, 2); | |||
dragAndDropColumnHeader(0, 2, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(1, 3, 4, 5, 2); | |||
} | |||
@Test | |||
public void testColumnReorder_footerHasSpannedCells_cantDropInside() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Footer", "Append row"); | |||
selectMenuPath("Component", "Footer", "Row 1", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(0, 3, 1, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 1, 2, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellInsideASpannedFooter_cantBeDroppedOutsideSpannedArea() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Footer", "Append row"); | |||
selectMenuPath("Component", "Footer", "Row 1", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(0, 2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(0, 2, 1, 3, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellInsideTwoCrossingSpanningFooters_cantTouchThis() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Footer", "Append row"); | |||
selectMenuPath("Component", "Footer", "Append row"); | |||
selectMenuPath("Component", "Footer", "Row 1", "Join column cells 0, 1"); | |||
selectMenuPath("Component", "Footer", "Row 2", "Join columns 1, 2"); | |||
dragAndDropColumnHeader(0, 3, 0, CellSide.LEFT); | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
// when | |||
dragAndDropColumnHeader(0, 2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(3, 0, 1, 2, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_cellsInsideTwoAdjacentSpannedHeaderAndFooter_reorderingLimited() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Footer", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
dragAndDropColumnHeader(0, 0, 5, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
dragAndDropColumnHeader(0, 1, 5, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
selectMenuPath("Component", "Footer", "Row 1", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(1, 3, 4, 5, 2); | |||
// when then | |||
dragAndDropColumnHeader(0, 1, 3, CellSide.RIGHT); | |||
assertColumnHeaderOrder(1, 4, 3, 5, 2); | |||
dragAndDropColumnHeader(0, 2, 4, CellSide.RIGHT); | |||
assertColumnHeaderOrder(1, 4, 3, 5, 2); | |||
dragAndDropColumnHeader(0, 2, 0, CellSide.RIGHT); | |||
assertColumnHeaderOrder(1, 3, 4, 5, 2); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingASpannedCell_dragWorksNormally() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(1, 1, 4, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 1, 2, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_twoEqualSpannedCells_bothCanBeDragged() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(1, 1, 4, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 1, 2, 4); | |||
// when | |||
dragAndDropColumnHeader(2, 3, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(1, 2, 0, 3, 4); | |||
} | |||
@Test | |||
public void testColumReorder_twoCrossingSpanningHeaders_neitherCanBeDragged() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join column cells 0, 1"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(1, 1, 4, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(2, 0, 3, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
} | |||
@Test | |||
public void testColumnReorder_spannedCellHasAnotherSpannedCellInside_canBeDraggedNormally() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
dragAndDropColumnHeader(1, 3, 1, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 3, 4, 5, 1); | |||
// when | |||
dragAndDropColumnHeader(1, 1, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(3, 4, 5, 0, 1); | |||
} | |||
@Test | |||
public void testColumnReorder_spannedCellInsideAnotherSpanned_canBeDraggedWithBoundaries() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
dragAndDropColumnHeader(1, 3, 1, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 3, 4, 5, 1); | |||
// when | |||
dragAndDropColumnHeader(2, 1, 3, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 5, 3, 4, 1); | |||
// when | |||
dragAndDropColumnHeader(2, 2, 0, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 4, 5, 1); | |||
} | |||
@Test | |||
public void testColumnReorder_cellInsideAndNextToSpannedCells_canBeDraggedWithBoundaries() { | |||
// given | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
dragAndDropColumnHeader(1, 3, 1, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 3, 4, 5, 1); | |||
// when | |||
dragAndDropColumnHeader(2, 3, 0, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 5, 3, 4, 1); | |||
// when | |||
dragAndDropColumnHeader(2, 1, 4, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 4, 5, 1); | |||
} | |||
@Test | |||
public void testColumnReorder_multipleSpannedCells_dragWorksNormally() { | |||
toggleColumnReorder(); | |||
selectMenuPath("Component", "State", "Width", "750px"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(1, 3, 1, CellSide.RIGHT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 3, 4, 5, 1); | |||
// when | |||
scrollGridHorizontallyTo(100); | |||
dragAndDropColumnHeader(2, 4, 2, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
// when | |||
dragAndDropColumnHeader(0, 0, 3, CellSide.LEFT); | |||
scrollGridHorizontallyTo(0); | |||
// then | |||
assertColumnHeaderOrder(1, 2, 0, 3, 4); | |||
} | |||
private void toggleSortableColumn(int index) { | |||
selectMenuPath("Component", "Columns", "Column " + index, "Sortable"); | |||
} | |||
private void startDragButDontDropOnDefaultColumnHeader(int index) { | |||
new Actions(getDriver()) | |||
.clickAndHold(getGridHeaderRowCells().get(index)) | |||
.moveByOffset(100, 0).perform(); | |||
} | |||
private void sortColumn(int index) { | |||
getGridHeaderRowCells().get(index).click(); | |||
} | |||
private void focusDefaultHeader(int index) { | |||
getGridHeaderRowCells().get(index).click(); | |||
} | |||
private WebElement getDraggedHeaderElement() { | |||
return findElement(By.className("dragged-column-header")); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
/* | |||
* 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; | |||
public class GridSidebarFeatures extends GridBasicFeatures { | |||
@Override | |||
protected boolean isColumnHidableByDefault(int col) { | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,241 @@ | |||
/* | |||
* 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.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotEquals; | |||
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 java.util.List; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.shared.ui.grid.Range; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testbench.ElementQuery; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.NotificationElement; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; | |||
public class GridDetailsClientTest extends GridBasicClientFeaturesTest { | |||
private static final String[] SET_GENERATOR = new String[] { "Component", | |||
"Row details", "Set generator" }; | |||
private static final String[] SET_FAULTY_GENERATOR = new String[] { | |||
"Component", "Row details", "Set faulty generator" }; | |||
private static final String[] SET_EMPTY_GENERATOR = new String[] { | |||
"Component", "Row details", "Set empty generator" }; | |||
@Before | |||
public void setUp() { | |||
setDebug(true); | |||
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() { | |||
toggleDetailsFor(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); | |||
toggleDetailsFor(1); | |||
TestBenchElement details = getGridElement().getDetails(1); | |||
assertTrue("Unexpected details content", | |||
details.getText().startsWith("Row: 1.")); | |||
} | |||
@Test | |||
public void openDetailsThenAppyRenderer() { | |||
toggleDetailsFor(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); | |||
toggleDetailsFor(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.")); | |||
} | |||
@Test | |||
public void errorUpdaterShowsErrorNotification() { | |||
assertFalse("No notifications should've been at the start", | |||
$(NotificationElement.class).exists()); | |||
toggleDetailsFor(1); | |||
selectMenuPath(SET_FAULTY_GENERATOR); | |||
ElementQuery<NotificationElement> notification = $(NotificationElement.class); | |||
assertTrue("Was expecting an error notification here", | |||
notification.exists()); | |||
notification.first().close(); | |||
assertEquals("The error details element should be empty", "", | |||
getGridElement().getDetails(1).getText()); | |||
} | |||
@Test | |||
public void updaterStillWorksAfterError() { | |||
toggleDetailsFor(1); | |||
selectMenuPath(SET_FAULTY_GENERATOR); | |||
$(NotificationElement.class).first().close(); | |||
selectMenuPath(SET_GENERATOR); | |||
assertNotEquals( | |||
"New details should've been generated even after error", "", | |||
getGridElement().getDetails(1).getText()); | |||
} | |||
@Test | |||
public void updaterRendersExpectedWidgets() { | |||
selectMenuPath(SET_GENERATOR); | |||
toggleDetailsFor(1); | |||
TestBenchElement detailsElement = getGridElement().getDetails(1); | |||
assertNotNull(detailsElement.findElement(By.className("gwt-Label"))); | |||
assertNotNull(detailsElement.findElement(By.className("gwt-Button"))); | |||
} | |||
@Test | |||
public void widgetsInUpdaterWorkAsExpected() { | |||
selectMenuPath(SET_GENERATOR); | |||
toggleDetailsFor(1); | |||
TestBenchElement detailsElement = getGridElement().getDetails(1); | |||
WebElement button = detailsElement.findElement(By | |||
.className("gwt-Button")); | |||
button.click(); | |||
WebElement label = detailsElement | |||
.findElement(By.className("gwt-Label")); | |||
assertEquals("clicked", label.getText()); | |||
} | |||
@Test | |||
public void emptyGenerator() { | |||
selectMenuPath(SET_EMPTY_GENERATOR); | |||
toggleDetailsFor(1); | |||
assertEquals("empty generator did not produce an empty details row", | |||
"", getGridElement().getDetails(1).getText()); | |||
} | |||
@Test(expected = NoSuchElementException.class) | |||
public void removeDetailsRow() { | |||
selectMenuPath(SET_GENERATOR); | |||
toggleDetailsFor(1); | |||
toggleDetailsFor(1); | |||
getGridElement().getDetails(1); | |||
} | |||
@Test | |||
public void rowElementClassNames() { | |||
toggleDetailsFor(0); | |||
toggleDetailsFor(1); | |||
List<WebElement> elements = getGridElement().findElements( | |||
By.className("v-grid-spacer")); | |||
assertEquals("v-grid-spacer", elements.get(0).getAttribute("class")); | |||
assertEquals("v-grid-spacer stripe", | |||
elements.get(1).getAttribute("class")); | |||
} | |||
@Test | |||
public void scrollDownToRowWithDetails() { | |||
toggleDetailsFor(100); | |||
scrollToRow(100, ScrollDestination.ANY); | |||
Range validScrollRange = Range.between(1700, 1715); | |||
assertTrue(validScrollRange.contains(getGridVerticalScrollPos())); | |||
} | |||
@Test | |||
public void scrollUpToRowWithDetails() { | |||
toggleDetailsFor(100); | |||
scrollGridVerticallyTo(999999); | |||
scrollToRow(100, ScrollDestination.ANY); | |||
Range validScrollRange = Range.between(1990, 2010); | |||
assertTrue(validScrollRange.contains(getGridVerticalScrollPos())); | |||
} | |||
@Test | |||
public void cannotScrollBeforeTop() { | |||
toggleDetailsFor(1); | |||
scrollToRow(0, ScrollDestination.END); | |||
assertEquals(0, getGridVerticalScrollPos()); | |||
} | |||
@Test | |||
public void cannotScrollAfterBottom() { | |||
toggleDetailsFor(999); | |||
scrollToRow(999, ScrollDestination.START); | |||
Range expectedRange = Range.withLength(19680, 20); | |||
assertTrue(expectedRange.contains(getGridVerticalScrollPos())); | |||
} | |||
private void scrollToRow(int rowIndex, ScrollDestination destination) { | |||
selectMenuPath(new String[] { "Component", "State", "Scroll to...", | |||
"Row " + rowIndex + "...", "Destination " + destination }); | |||
} | |||
private void toggleDetailsFor(int rowIndex) { | |||
selectMenuPath(new String[] { "Component", "Row details", | |||
"Toggle details for...", "Row " + rowIndex }); | |||
} | |||
} |
@@ -0,0 +1,156 @@ | |||
/* | |||
* 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 java.util.List; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; | |||
import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; | |||
public class GridSidebarContentTest extends GridBasicClientFeaturesTest { | |||
@Test | |||
public void testSidebarWithHidableColumn() { | |||
openTestURL(); | |||
CustomGridElement gridElement = getGridElement(); | |||
Assert.assertEquals("Sidebar should not be initially present", 0, | |||
countBySelector(".v-grid-sidebar")); | |||
selectMenuPath("Component", "Columns", "Column 0", "Hidable"); | |||
gridElement.findElement(By.className("v-grid-sidebar-button")).click(); | |||
WebElement toggle = gridElement.findElement(By | |||
.className("column-hiding-toggle")); | |||
Assert.assertEquals("Column 0 should be togglable", "Header (0,0)", | |||
toggle.getText()); | |||
selectMenuPath("Component", "Columns", "Column 0", "Hidable"); | |||
Assert.assertEquals("Sidebar should disappear without toggable column", | |||
0, countBySelector(".v-grid-sidebar")); | |||
} | |||
@Test | |||
public void testAddingCustomSidebarItem() { | |||
openTestURL(); | |||
CustomGridElement gridElement = getGridElement(); | |||
selectMenuPath("Component", "Sidebar", "Add item to end"); | |||
gridElement.findElement(By.className("v-grid-sidebar-button")).click(); | |||
WebElement sidebarItem = gridElement.findElement(By | |||
.cssSelector(".v-grid-sidebar-content .gwt-MenuItem")); | |||
sidebarItem.click(); | |||
Assert.assertEquals("Sidebar should be closed after clicking item 0", | |||
0, countBySelector(".v-grid-sidebar-content")); | |||
} | |||
@Test | |||
public void testProgrammaticSidebarToggle() { | |||
openTestURL(); | |||
selectMenuPath("Component", "Columns", "Column 0", "Hidable"); | |||
selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); | |||
Assert.assertEquals("Sidebar be open", 1, | |||
countBySelector(".v-grid-sidebar-content")); | |||
selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); | |||
Assert.assertEquals("Sidebar be closed", 0, | |||
countBySelector(".v-grid-sidebar-content")); | |||
} | |||
@Test | |||
public void testBasicSidebarOrder() { | |||
openTestURL(); | |||
CustomGridElement gridElement = getGridElement(); | |||
// First add custom content | |||
selectMenuPath("Component", "Sidebar", "Add separator to end"); | |||
selectMenuPath("Component", "Sidebar", "Add item to end"); | |||
// Then make one column togglable | |||
selectMenuPath("Component", "Columns", "Column 0", "Hidable"); | |||
selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); | |||
assertSidebarMenuItems("Header (0,0)", null, "Custom menu item 0"); | |||
} | |||
@Test | |||
public void testSidebarOrderAbuse() { | |||
openTestURL(); | |||
CustomGridElement gridElement = getGridElement(); | |||
selectMenuPath("Component", "Columns", "Column 0", "Hidable"); | |||
selectMenuPath("Component", "Columns", "Column 1", "Hidable"); | |||
// Inserts a menu item between the two visibility toggles | |||
selectMenuPath("Component", "Sidebar", "Add item before index 1"); | |||
selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); | |||
// Total order enforcement not implemented at this point. Test can be | |||
// updated when it is. | |||
assertSidebarMenuItems("Header (0,0)", "Custom menu item 0", | |||
"Header (0,1)"); | |||
selectMenuPath("Component", "Columns", "Column 2", "Hidable"); | |||
// Adding a new togglable column should have restored the expected order | |||
assertSidebarMenuItems("Header (0,0)", "Header (0,1)", "Header (0,2)", | |||
"Custom menu item 0"); | |||
} | |||
private void assertSidebarMenuItems(String... items) { | |||
List<WebElement> menuItems = getGridElement().findElements( | |||
By.cssSelector(".v-grid-sidebar-content td")); | |||
Assert.assertEquals("Expected " + items.length + " menu items", | |||
items.length, menuItems.size()); | |||
for (int i = 0; i < items.length; i++) { | |||
String expectedItem = items[i]; | |||
if (expectedItem == null) { | |||
Assert.assertEquals("Item " + i + " should be a separator", | |||
"gwt-MenuItemSeparator", | |||
menuItems.get(i).getAttribute("class")); | |||
} else { | |||
Assert.assertEquals("Unexpected content for item " + i, | |||
expectedItem, menuItems.get(i).getText()); | |||
} | |||
} | |||
} | |||
private int countBySelector(String cssSelector) { | |||
return getGridElement().findElements(By.cssSelector(cssSelector)) | |||
.size(); | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* 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.element; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.GridElement; | |||
import com.vaadin.testbench.elementsbase.ServerClass; | |||
@ServerClass("com.vaadin.ui.Grid") | |||
public class CustomGridElement extends GridElement { | |||
/** | |||
* 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. <code>null</code> | |||
* if no widget is defined for the detials row | |||
* @throws NoSuchElementException | |||
* if the given details row is currently not open | |||
*/ | |||
public TestBenchElement getDetails(int rowIndex) | |||
throws NoSuchElementException { | |||
return getSubPart("#details[" + rowIndex + "]"); | |||
} | |||
private TestBenchElement getSubPart(String subPartSelector) { | |||
return (TestBenchElement) findElement(By.vaadin(subPartSelector)); | |||
} | |||
} |
@@ -66,10 +66,8 @@ public class EscalatorBasicsTest extends EscalatorBasicClientFeaturesTest { | |||
selectMenuPath(GENERAL, DETACH_ESCALATOR); | |||
selectMenuPath(GENERAL, ATTACH_ESCALATOR); | |||
assertEquals("Vertical scroll position", "50", getVerticalScrollbar() | |||
.getAttribute("scrollTop")); | |||
assertEquals("Horizontal scroll position", "50", | |||
getHorizontalScrollbar().getAttribute("scrollLeft")); | |||
assertEquals("Vertical scroll position", 50, getScrollTop()); | |||
assertEquals("Horizontal scroll position", 50, getScrollLeft()); | |||
assertEquals("First cell of first visible row", "Row 2: 0,2", | |||
getBodyCell(0, 0).getText()); |
@@ -0,0 +1,583 @@ | |||
/* | |||
* 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.escalator; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import org.junit.Before; | |||
import org.junit.ComparisonFailure; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Keys; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.shared.ui.grid.Range; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.NotificationElement; | |||
import com.vaadin.testbench.parallel.BrowserUtil; | |||
import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; | |||
@SuppressWarnings("boxing") | |||
public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { | |||
//@formatter:off | |||
// separate strings made so that eclipse can show the concatenated string by hovering the mouse over the constant | |||
// translate3d(0px, 40px, 123px); | |||
// translate3d(24px, 15.251px, 0); | |||
// translate(0, 40px); | |||
private final static String TRANSLATE_VALUE_REGEX = | |||
"translate(?:3d|)" // "translate" or "translate3d" | |||
+ "\\(" // literal "(" | |||
+ "(" // start capturing the x argument | |||
+ "[0-9]+" // the integer part of the value | |||
+ "(?:" // start of the subpixel part of the value | |||
+ "\\.[0-9]" // if we have a period, there must be at least one number after it | |||
+ "[0-9]*" // any amount of accuracy afterwards is fine | |||
+ ")?" // the subpixel part is optional | |||
+ ")" | |||
+ "(?:px)?" // we don't care if the values are suffixed by "px" or not. | |||
+ ", " | |||
+ "(" // start capturing the y argument | |||
+ "[0-9]+" // the integer part of the value | |||
+ "(?:" // start of the subpixel part of the value | |||
+ "\\.[0-9]" // if we have a period, there must be at least one number after it | |||
+ "[0-9]*" // any amount of accuracy afterwards is fine | |||
+ ")?" // the subpixel part is optional | |||
+ ")" | |||
+ "(?:px)?" // we don't care if the values are suffixed by "px" or not. | |||
+ "(?:, .*?)?" // the possible z argument, uninteresting (translate doesn't have one, translate3d does) | |||
+ "\\)" // literal ")" | |||
+ ";?"; // optional ending semicolon | |||
// 40px; | |||
// 12.34px | |||
private final static String PIXEL_VALUE_REGEX = | |||
"(" // capture the pixel value | |||
+ "[0-9]+" // the pixel argument | |||
+ "(?:" // start of the subpixel part of the value | |||
+ "\\.[0-9]" // if we have a period, there must be at least one number after it | |||
+ "[0-9]*" // any amount of accuracy afterwards is fine | |||
+ ")?" // the subpixel part is optional | |||
+ ")" | |||
+ "(?:px)?" // optional "px" string | |||
+ ";?"; // optional semicolon | |||
//@formatter:on | |||
// also matches "-webkit-transform"; | |||
private final static Pattern TRANSFORM_CSS_PATTERN = Pattern | |||
.compile("transform: (.*?);"); | |||
private final static Pattern TOP_CSS_PATTERN = Pattern.compile( | |||
"top: ([0-9]+(?:\\.[0-9]+)?(?:px)?);?", Pattern.CASE_INSENSITIVE); | |||
private final static Pattern LEFT_CSS_PATTERN = Pattern.compile( | |||
"left: ([0-9]+(?:\\.[0-9]+)?(?:px)?);?", Pattern.CASE_INSENSITIVE); | |||
private final static Pattern TRANSLATE_VALUE_PATTERN = Pattern | |||
.compile(TRANSLATE_VALUE_REGEX); | |||
private final static Pattern PIXEL_VALUE_PATTERN = Pattern.compile( | |||
PIXEL_VALUE_REGEX, Pattern.CASE_INSENSITIVE); | |||
@Before | |||
public void before() { | |||
setDebug(true); | |||
openTestURL(); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, "Set 20px default height"); | |||
populate(); | |||
} | |||
@Test | |||
public void openVisibleSpacer() { | |||
assertFalse("No spacers should be shown at the start", | |||
spacersAreFoundInDom()); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
assertNotNull("Spacer should be shown after setting it", getSpacer(1)); | |||
} | |||
@Test | |||
public void closeVisibleSpacer() { | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, REMOVE); | |||
assertNull("Spacer should not exist after removing it", getSpacer(1)); | |||
} | |||
@Test | |||
public void spacerPushesVisibleRowsDown() { | |||
double oldTop = getElementTop(getBodyRow(2)); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
double newTop = getElementTop(getBodyRow(2)); | |||
assertGreater("Row below a spacer was not pushed down", newTop, oldTop); | |||
} | |||
@Test | |||
public void addingRowAboveSpacerPushesItDown() { | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
double oldTop = getElementTop(getSpacer(1)); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
double newTop = getElementTop(getSpacer(2)); | |||
assertGreater("Spacer should've been pushed down (oldTop: " + oldTop | |||
+ ", newTop: " + newTop + ")", newTop, oldTop); | |||
} | |||
@Test | |||
public void addingRowBelowSpacerDoesNotPushItDown() { | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
double oldTop = getElementTop(getSpacer(1)); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_END); | |||
double newTop = getElementTop(getSpacer(1)); | |||
assertEquals("Spacer should've not been pushed down", newTop, oldTop, | |||
WidgetUtil.PIXEL_EPSILON); | |||
} | |||
@Test | |||
public void addingRowBelowSpacerIsActuallyRenderedBelowWhenEscalatorIsEmpty() { | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
double spacerTop = getElementTop(getSpacer(1)); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_END); | |||
double rowTop = getElementTop(getBodyRow(2)); | |||
assertEquals("Next row should've been rendered below the spacer", | |||
spacerTop + 100, rowTop, WidgetUtil.PIXEL_EPSILON); | |||
} | |||
@Test | |||
public void addSpacerAtBottomThenScrollThere() { | |||
selectMenuPath(FEATURES, SPACERS, ROW_99, SET_100PX); | |||
scrollVerticallyTo(999999); | |||
assertFalse("Did not expect a notification", | |||
$(NotificationElement.class).exists()); | |||
} | |||
@Test | |||
public void scrollToBottomThenAddSpacerThere() { | |||
scrollVerticallyTo(999999); | |||
long oldBottomScrollTop = getScrollTop(); | |||
selectMenuPath(FEATURES, SPACERS, ROW_99, SET_100PX); | |||
assertEquals("Adding a spacer underneath the current viewport should " | |||
+ "not scroll anywhere", oldBottomScrollTop, getScrollTop()); | |||
assertFalse("Got an unexpected notification", | |||
$(NotificationElement.class).exists()); | |||
scrollVerticallyTo(999999); | |||
assertFalse("Got an unexpected notification", | |||
$(NotificationElement.class).exists()); | |||
assertGreater("Adding a spacer should've made the scrollbar scroll " | |||
+ "further", getScrollTop(), oldBottomScrollTop); | |||
} | |||
@Test | |||
public void removingRowAboveSpacerMovesSpacerUp() { | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
WebElement spacer = getSpacer(1); | |||
double originalElementTop = getElementTop(spacer); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, | |||
REMOVE_ONE_ROW_FROM_BEGINNING); | |||
assertLessThan("spacer should've moved up", getElementTop(spacer), | |||
originalElementTop); | |||
assertNull("No spacer for row 1 should be found after removing the " | |||
+ "top row", getSpacer(1)); | |||
} | |||
@Test | |||
public void removingSpacedRowRemovesSpacer() { | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
assertTrue("Spacer should've been found in the DOM", | |||
spacersAreFoundInDom()); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, | |||
REMOVE_ONE_ROW_FROM_BEGINNING); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, | |||
REMOVE_ONE_ROW_FROM_BEGINNING); | |||
assertFalse("No spacers should be in the DOM after removing " | |||
+ "associated spacer", spacersAreFoundInDom()); | |||
} | |||
@Test | |||
public void spacersAreFixedInViewport_firstFreezeThenScroll() { | |||
selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
assertEquals("Spacer's left position should've been 0 at the " | |||
+ "beginning", 0d, getElementLeft(getSpacer(1)), | |||
WidgetUtil.PIXEL_EPSILON); | |||
int scrollTo = 10; | |||
scrollHorizontallyTo(scrollTo); | |||
assertEquals("Spacer's left position should've been " + scrollTo | |||
+ " after scrolling " + scrollTo + "px", scrollTo, | |||
getElementLeft(getSpacer(1)), WidgetUtil.PIXEL_EPSILON); | |||
} | |||
@Test | |||
public void spacersAreFixedInViewport_firstScrollThenFreeze() { | |||
selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN); | |||
int scrollTo = 10; | |||
scrollHorizontallyTo(scrollTo); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
assertEquals("Spacer's left position should've been " + scrollTo | |||
+ " after scrolling " + scrollTo + "px", scrollTo, | |||
getElementLeft(getSpacer(1)), WidgetUtil.PIXEL_EPSILON); | |||
} | |||
@Test | |||
public void addingMinusOneSpacerDoesNotScrollWhenScrolledAtTop() { | |||
scrollVerticallyTo(5); | |||
selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX); | |||
assertEquals( | |||
"No scroll adjustment should've happened when adding the -1 spacer", | |||
5, getScrollTop()); | |||
} | |||
@Test | |||
public void removingMinusOneSpacerScrolls() { | |||
scrollVerticallyTo(5); | |||
selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, REMOVE); | |||
assertEquals("Scroll adjustment should've happened when removing the " | |||
+ "-1 spacer", 0, getScrollTop()); | |||
} | |||
@Test | |||
public void scrollToRowWorksProperlyWithSpacers() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
/* | |||
* we check for row -2 instead of -1, because escalator has the one row | |||
* buffered underneath the footer | |||
*/ | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_75); | |||
Thread.sleep(500); | |||
assertEquals("Row 75: 0,75", getBodyCell(-2, 0).getText()); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_25); | |||
Thread.sleep(500); | |||
try { | |||
assertEquals("Row 25: 0,25", getBodyCell(0, 0).getText()); | |||
} catch (ComparisonFailure retryForIE10andIE11) { | |||
/* | |||
* This seems to be some kind of subpixel/off-by-one-pixel error. | |||
* Everything's scrolled correctly, but Escalator still loads one | |||
* row above to the DOM, underneath the header. It's there, but it's | |||
* not visible. We'll allow for that one pixel error. | |||
*/ | |||
assertEquals("Row 24: 0,24", getBodyCell(0, 0).getText()); | |||
} | |||
} | |||
@Test | |||
public void scrollToSpacerFromAbove() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); | |||
// Browsers might vary with a few pixels. | |||
Range allowableScrollRange = Range.between(765, 780); | |||
int scrollTop = (int) getScrollTop(); | |||
assertTrue("Scroll position was not " + allowableScrollRange + ", but " | |||
+ scrollTop, allowableScrollRange.contains(scrollTop)); | |||
} | |||
@Test | |||
public void scrollToSpacerFromBelow() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
scrollVerticallyTo(999999); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); | |||
// Browsers might vary with a few pixels. | |||
Range allowableScrollRange = Range.between(1015, 1025); | |||
int scrollTop = (int) getScrollTop(); | |||
assertTrue("Scroll position was not " + allowableScrollRange + ", but " | |||
+ scrollTop, allowableScrollRange.contains(scrollTop)); | |||
} | |||
@Test | |||
public void scrollToSpacerAlreadyInViewport() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
scrollVerticallyTo(1000); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); | |||
assertEquals(getScrollTop(), 1000); | |||
} | |||
@Test | |||
public void scrollToRowAndSpacerFromAbove() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, | |||
SCROLL_HERE_SPACERBELOW_ANY_0PADDING); | |||
// Browsers might vary with a few pixels. | |||
Range allowableScrollRange = Range.between(765, 780); | |||
int scrollTop = (int) getScrollTop(); | |||
assertTrue("Scroll position was not " + allowableScrollRange + ", but " | |||
+ scrollTop, allowableScrollRange.contains(scrollTop)); | |||
} | |||
@Test | |||
public void scrollToRowAndSpacerFromBelow() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
scrollVerticallyTo(999999); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, | |||
SCROLL_HERE_SPACERBELOW_ANY_0PADDING); | |||
// Browsers might vary with a few pixels. | |||
Range allowableScrollRange = Range.between(995, 1005); | |||
int scrollTop = (int) getScrollTop(); | |||
assertTrue("Scroll position was not " + allowableScrollRange + ", but " | |||
+ scrollTop, allowableScrollRange.contains(scrollTop)); | |||
} | |||
@Test | |||
public void scrollToRowAndSpacerAlreadyInViewport() throws Exception { | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
scrollVerticallyTo(950); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, | |||
SCROLL_HERE_SPACERBELOW_ANY_0PADDING); | |||
assertEquals(getScrollTop(), 950); | |||
} | |||
@Test | |||
public void domCanBeSortedWithFocusInSpacer() throws InterruptedException { | |||
// Firefox behaves badly with focus-related tests - skip it. | |||
if (BrowserUtil.isFirefox(super.getDesiredCapabilities())) { | |||
return; | |||
} | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
WebElement inputElement = getEscalator().findElement( | |||
By.tagName("input")); | |||
inputElement.click(); | |||
scrollVerticallyTo(30); | |||
// Sleep needed because of all the JS we're doing, and to let | |||
// the DOM reordering to take place. | |||
Thread.sleep(500); | |||
assertFalse("Error message detected", $(NotificationElement.class) | |||
.exists()); | |||
} | |||
@Test | |||
public void spacersAreInsertedInCorrectDomPosition() { | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
WebElement tbody = getEscalator().findElement(By.tagName("tbody")); | |||
WebElement spacer = getChild(tbody, 2); | |||
String cssClass = spacer.getAttribute("class"); | |||
assertTrue("element index 2 was not a spacer (class=\"" + cssClass | |||
+ "\")", cssClass.contains("-spacer")); | |||
} | |||
@Test | |||
public void spacersAreInCorrectDomPositionAfterScroll() { | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
scrollVerticallyTo(32); // roughly one row's worth | |||
WebElement tbody = getEscalator().findElement(By.tagName("tbody")); | |||
WebElement spacer = getChild(tbody, 1); | |||
String cssClass = spacer.getAttribute("class"); | |||
assertTrue("element index 1 was not a spacer (class=\"" + cssClass | |||
+ "\")", cssClass.contains("-spacer")); | |||
} | |||
@Test | |||
public void spacerScrolledIntoViewGetsFocus() { | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); | |||
tryToTabIntoFocusUpdaterElement(); | |||
assertEquals("input", getFocusedElement().getTagName()); | |||
} | |||
@Test | |||
public void spacerScrolledOutOfViewDoesNotGetFocus() { | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); | |||
tryToTabIntoFocusUpdaterElement(); | |||
assertNotEquals("input", getFocusedElement().getTagName()); | |||
} | |||
@Test | |||
public void spacerOpenedInViewGetsFocus() { | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
tryToTabIntoFocusUpdaterElement(); | |||
WebElement focusedElement = getFocusedElement(); | |||
assertEquals("input", focusedElement.getTagName()); | |||
} | |||
@Test | |||
public void spacerOpenedOutOfViewDoesNotGetFocus() { | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
tryToTabIntoFocusUpdaterElement(); | |||
assertNotEquals("input", getFocusedElement().getTagName()); | |||
} | |||
@Test | |||
public void spacerOpenedInViewAndScrolledOutAndBackAgainGetsFocus() { | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_50); | |||
selectMenuPath(FEATURES, SPACERS, ROW_1, SCROLL_HERE_ANY_0PADDING); | |||
tryToTabIntoFocusUpdaterElement(); | |||
assertEquals("input", getFocusedElement().getTagName()); | |||
} | |||
@Test | |||
public void spacerOpenedOutOfViewAndScrolledInAndBackAgainDoesNotGetFocus() { | |||
selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); | |||
selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); | |||
selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_0); | |||
tryToTabIntoFocusUpdaterElement(); | |||
assertNotEquals("input", getFocusedElement().getTagName()); | |||
} | |||
private void tryToTabIntoFocusUpdaterElement() { | |||
((TestBenchElement) findElement(By.className("gwt-MenuBar"))).focus(); | |||
WebElement focusedElement = getFocusedElement(); | |||
focusedElement.sendKeys(Keys.TAB); | |||
} | |||
private WebElement getChild(WebElement parent, int childIndex) { | |||
return (WebElement) executeScript("return arguments[0].children[" | |||
+ childIndex + "];", parent); | |||
} | |||
private static double[] getElementDimensions(WebElement element) { | |||
/* | |||
* we need to parse the style attribute, since using getCssValue gets a | |||
* normalized value that is harder to parse. | |||
*/ | |||
String style = element.getAttribute("style"); | |||
String transform = getTransformFromStyle(style); | |||
if (transform != null) { | |||
return getTranslateValues(transform); | |||
} | |||
double[] result = new double[] { -1, -1 }; | |||
String left = getLeftFromStyle(style); | |||
if (left != null) { | |||
result[0] = getPixelValue(left); | |||
} | |||
String top = getTopFromStyle(style); | |||
if (top != null) { | |||
result[1] = getPixelValue(top); | |||
} | |||
if (result[0] != -1 && result[1] != -1) { | |||
return result; | |||
} else { | |||
throw new IllegalArgumentException("Could not parse the position " | |||
+ "information from the CSS \"" + style + "\""); | |||
} | |||
} | |||
private static double getElementTop(WebElement element) { | |||
return getElementDimensions(element)[1]; | |||
} | |||
private static double getElementLeft(WebElement element) { | |||
return getElementDimensions(element)[0]; | |||
} | |||
private static String getTransformFromStyle(String style) { | |||
return getFromStyle(TRANSFORM_CSS_PATTERN, style); | |||
} | |||
private static String getTopFromStyle(String style) { | |||
return getFromStyle(TOP_CSS_PATTERN, style); | |||
} | |||
private static String getLeftFromStyle(String style) { | |||
return getFromStyle(LEFT_CSS_PATTERN, style); | |||
} | |||
private static String getFromStyle(Pattern pattern, String style) { | |||
Matcher matcher = pattern.matcher(style); | |||
if (matcher.find()) { | |||
assertEquals("wrong amount of groups matched in " + style, 1, | |||
matcher.groupCount()); | |||
return matcher.group(1); | |||
} else { | |||
return null; | |||
} | |||
} | |||
/** | |||
* @return {@code [0] == x}, {@code [1] == y} | |||
*/ | |||
private static double[] getTranslateValues(String translate) { | |||
Matcher matcher = TRANSLATE_VALUE_PATTERN.matcher(translate); | |||
assertTrue("no matches for " + translate + " against " | |||
+ TRANSLATE_VALUE_PATTERN, matcher.find()); | |||
assertEquals("wrong amout of groups matched in " + translate, 2, | |||
matcher.groupCount()); | |||
return new double[] { Double.parseDouble(matcher.group(1)), | |||
Double.parseDouble(matcher.group(2)) }; | |||
} | |||
private static double getPixelValue(String top) { | |||
Matcher matcher = PIXEL_VALUE_PATTERN.matcher(top); | |||
assertTrue("no matches for \"" + top + "\" against " | |||
+ PIXEL_VALUE_PATTERN, matcher.find()); | |||
assertEquals("wrong amount of groups matched in " + top, 1, | |||
matcher.groupCount()); | |||
return Double.parseDouble(matcher.group(1)); | |||
} | |||
} |
@@ -0,0 +1,347 @@ | |||
/* | |||
* 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.server; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.List; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; | |||
/** | |||
* Tests that Grid columns can be reordered by user with drag and drop #16643. | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridColumnReorderTest extends GridBasicFeaturesTest { | |||
private static final String[] COLUMN_REORDERING_PATH = { "Component", | |||
"State", "Column Reordering Allowed" }; | |||
private static final String[] COLUMN_REORDER_LISTENER_PATH = { "Component", | |||
"State", "ColumnReorderListener" }; | |||
@Before | |||
public void setUp() { | |||
setDebug(true); | |||
} | |||
@Test | |||
public void testColumnReordering_firstColumnDroppedOnThird_dropOnLeftSide() { | |||
// given | |||
openTestURL(); | |||
assertColumnHeaderOrder(0, 1, 2); | |||
toggleColumnReordering(); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(1, 0, 2); | |||
} | |||
@Test | |||
public void testColumnReordering_firstColumnDroppedOnThird_dropOnRightSide() { | |||
// given | |||
openTestURL(); | |||
assertColumnHeaderOrder(0, 1, 2); | |||
toggleColumnReordering(); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(1, 2, 0); | |||
} | |||
@Test | |||
public void testColumnReordering_reorderingTwiceBackForth_reordered() { | |||
// given | |||
openTestURL(); | |||
selectMenuPath("Component", "Size", "Width", "800px"); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4); | |||
toggleColumnReordering(); | |||
// when | |||
dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(2, 0, 1, 3, 4); | |||
// when | |||
dragAndDropDefaultColumnHeader(1, 3, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(2, 1, 3, 0); | |||
} | |||
@Test | |||
public void testColumnReordering_notEnabled_noReordering() { | |||
// given | |||
openTestURL(); | |||
assertColumnHeaderOrder(0, 1, 2); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(0, 1, 2); | |||
} | |||
@Test | |||
public void testColumnReordering_userChangesRevertedByServer_columnsAreUpdated() { | |||
// given | |||
openTestURL(); | |||
assertColumnHeaderOrder(0, 1, 2); | |||
toggleColumnReordering(); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
assertColumnHeaderOrder(1, 0, 2); | |||
moveColumnManuallyLeftByOne(0); | |||
// then | |||
assertColumnHeaderOrder(0, 1, 2); | |||
} | |||
@Test | |||
public void testColumnReordering_concurrentUpdatesFromServer_columnOrderFromServerUsed() { | |||
// given | |||
openTestURL(); | |||
assertColumnHeaderOrder(0, 1, 2); | |||
toggleColumnReordering(); | |||
// when | |||
selectMenuPath(new String[] { "Component", "Internals", | |||
"Update column order without updating client" }); | |||
dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(1, 0, 2); | |||
} | |||
@Test | |||
public void testColumnReordering_triggersReorderEvent_isUserInitiated() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
// when | |||
toggleColumnReorderListener(); | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
// then | |||
assertColumnReorderEvent(true); | |||
} | |||
@Test | |||
public void testColumnReordering_addAndRemoveListener_registerUnRegisterWorks() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
assertNoColumnReorderEvent(); | |||
// when | |||
toggleColumnReorderListener(); | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.RIGHT); | |||
// then | |||
assertColumnReorderEvent(true); | |||
// when | |||
toggleColumnReorderListener(); | |||
dragAndDropDefaultColumnHeader(0, 3, CellSide.LEFT); | |||
// then | |||
assertNoColumnReorderEvent(); | |||
} | |||
@Test | |||
public void testColumnReorderingEvent_serverSideReorder_triggersReorderEvent() { | |||
openTestURL(); | |||
// when | |||
toggleColumnReorderListener(); | |||
moveColumnManuallyLeftByOne(3); | |||
// then | |||
assertColumnReorderEvent(false); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingFrozenColumns_impossible() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
setFrozenColumns(2); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
// when | |||
dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
assertTrue(getGridElement().getHeaderCell(0, 0).isFrozen()); | |||
assertTrue(getGridElement().getHeaderCell(0, 1).isFrozen()); | |||
assertFalse(getGridElement().getHeaderCell(0, 2).isFrozen()); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingColumnOnTopOfFrozenColumn_columnDroppedRightOfFrozenColumns() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
setFrozenColumns(1); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
// when | |||
dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); | |||
// then | |||
assertColumnHeaderOrder(0, 2, 1, 3); | |||
} | |||
@Test | |||
public void testColumnReorder_draggingColumnLeftOfMultiSelectionColumn_columnDroppedRight() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
selectMenuPath("Component", "State", "Selection mode", "multi"); | |||
List<TestBenchElement> gridHeaderRowCells = getGridHeaderRowCells(); | |||
assertTrue(gridHeaderRowCells.get(0).getText().equals("")); | |||
assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); | |||
assertColumnHeader("Column 1", gridHeaderRowCells.get(2)); | |||
assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); | |||
// when | |||
dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); | |||
// then | |||
gridHeaderRowCells = getGridHeaderRowCells(); | |||
assertTrue(gridHeaderRowCells.get(0).getText().equals("")); | |||
assertColumnHeader("Column 1", gridHeaderRowCells.get(1)); | |||
assertColumnHeader("Column 0", gridHeaderRowCells.get(2)); | |||
assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); | |||
} | |||
@Test | |||
public void testColumnReorder_multiSelectionAndFrozenColumns_columnDroppedRight() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
selectMenuPath("Component", "State", "Selection mode", "multi"); | |||
setFrozenColumns(1); | |||
List<TestBenchElement> gridHeaderRowCells = getGridHeaderRowCells(); | |||
assertTrue(gridHeaderRowCells.get(0).getText().equals("")); | |||
assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); | |||
assertColumnHeader("Column 1", gridHeaderRowCells.get(2)); | |||
assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); | |||
// when | |||
dragAndDropDefaultColumnHeader(3, 0, CellSide.LEFT); | |||
// then | |||
gridHeaderRowCells = getGridHeaderRowCells(); | |||
assertTrue(gridHeaderRowCells.get(0).getText().equals("")); | |||
assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); | |||
assertColumnHeader("Column 2", gridHeaderRowCells.get(2)); | |||
assertColumnHeader("Column 1", gridHeaderRowCells.get(3)); | |||
} | |||
@Test | |||
public void testColumnReordering_multiSelectionColumnNotFrozen_stillCantDropLeftSide() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
selectMenuPath("Component", "State", "Selection mode", "multi"); | |||
setFrozenColumns(-1); | |||
List<TestBenchElement> gridHeaderRowCells = getGridHeaderRowCells(); | |||
assertTrue(gridHeaderRowCells.get(0).getText().equals("")); | |||
assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); | |||
assertColumnHeader("Column 1", gridHeaderRowCells.get(2)); | |||
assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); | |||
// when | |||
dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); | |||
// then | |||
gridHeaderRowCells = getGridHeaderRowCells(); | |||
assertTrue(gridHeaderRowCells.get(0).getText().equals("")); | |||
assertColumnHeader("Column 1", gridHeaderRowCells.get(1)); | |||
assertColumnHeader("Column 0", gridHeaderRowCells.get(2)); | |||
assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); | |||
} | |||
@Test | |||
public void testColumnReordering_twoHeaderRows_dndReorderingPossibleFromFirstRow() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
// when | |||
dragAndDropColumnHeader(0, 0, 2, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(1, 2, 0, 3); | |||
} | |||
@Test | |||
public void testColumnReordering_twoHeaderRows_dndReorderingPossibleFromSecondRow() { | |||
// given | |||
openTestURL(); | |||
toggleColumnReordering(); | |||
selectMenuPath("Component", "Header", "Append row"); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
// when | |||
dragAndDropColumnHeader(1, 0, 2, CellSide.RIGHT); | |||
// then | |||
assertColumnHeaderOrder(1, 2, 0, 3); | |||
} | |||
private void toggleColumnReordering() { | |||
selectMenuPath(COLUMN_REORDERING_PATH); | |||
} | |||
private void toggleColumnReorderListener() { | |||
selectMenuPath(COLUMN_REORDER_LISTENER_PATH); | |||
} | |||
private void moveColumnManuallyLeftByOne(int index) { | |||
selectMenuPath(new String[] { "Component", "Columns", | |||
"Column " + index, "Move left" }); | |||
} | |||
private void assertColumnReorderEvent(boolean userOriginated) { | |||
final String logRow = getLogRow(0); | |||
assertTrue(logRow.contains("Columns reordered, userOriginated: " | |||
+ userOriginated)); | |||
} | |||
private void assertNoColumnReorderEvent() { | |||
final String logRow = getLogRow(0); | |||
assertFalse(logRow.contains("Columns reordered")); | |||
} | |||
} |
@@ -0,0 +1,280 @@ | |||
/* | |||
* 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.server; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertTrue; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; | |||
@TestCategory("grid") | |||
public class GridColumnVisibilityTest extends GridBasicFeaturesTest { | |||
private static final String[] TOGGLE_LISTENER = new String[] { "Component", | |||
"State", "ColumnVisibilityChangeListener" }; | |||
private static final String[] TOGGLE_HIDE_COLUMN_0 = new String[] { | |||
"Component", "Columns", "Column 0", "Hidden" }; | |||
private static final String COLUMN_0_BECAME_HIDDEN_MSG = "Visibility " | |||
+ "changed: propertyId: Column 0, isHidden: true"; | |||
private static final String COLUMN_0_BECAME_UNHIDDEN_MSG = "Visibility " | |||
+ "changed: propertyId: Column 0, isHidden: false"; | |||
private static final String USER_ORIGINATED_TRUE = "userOriginated: true"; | |||
private static final String USER_ORIGINATED_FALSE = "userOriginated: false"; | |||
@Before | |||
public void setUp() { | |||
openTestURL(); | |||
} | |||
@Test | |||
public void columnIsNotShownWhenHidden() { | |||
assertEquals("column 0", getGridElement().getHeaderCell(0, 0).getText() | |||
.toLowerCase()); | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
assertEquals("column 1", getGridElement().getHeaderCell(0, 0).getText() | |||
.toLowerCase()); | |||
} | |||
@Test | |||
public void columnIsShownWhenUnhidden() { | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
assertEquals("column 0", getGridElement().getHeaderCell(0, 0).getText() | |||
.toLowerCase()); | |||
} | |||
@Test | |||
public void registeringListener() { | |||
assertFalse(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); | |||
selectMenuPath(TOGGLE_LISTENER); | |||
assertFalse(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); | |||
assertTrue(logContainsText(USER_ORIGINATED_FALSE)); | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
assertTrue(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); | |||
assertTrue(logContainsText(USER_ORIGINATED_FALSE)); | |||
} | |||
@Test | |||
public void deregisteringListener() { | |||
selectMenuPath(TOGGLE_LISTENER); | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
selectMenuPath(TOGGLE_LISTENER); | |||
selectMenuPath(TOGGLE_HIDE_COLUMN_0); | |||
assertFalse(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); | |||
} | |||
@Test | |||
public void testColumnHiding_userOriginated_correctParams() { | |||
selectMenuPath(TOGGLE_LISTENER); | |||
toggleColumnHidable(0); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
getSidebarOpenButton().click(); | |||
getColumnHidingToggle(0).click(); | |||
getSidebarOpenButton().click(); | |||
assertColumnHeaderOrder(1, 2, 3); | |||
assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); | |||
assertTrue(logContainsText(USER_ORIGINATED_TRUE)); | |||
getSidebarOpenButton().click(); | |||
getColumnHidingToggle(0).click(); | |||
getSidebarOpenButton().click(); | |||
assertColumnHeaderOrder(0, 1, 2, 3); | |||
assertTrue(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); | |||
assertTrue(logContainsText(USER_ORIGINATED_TRUE)); | |||
getSidebarOpenButton().click(); | |||
getColumnHidingToggle(0).click(); | |||
getSidebarOpenButton().click(); | |||
assertColumnHeaderOrder(1, 2, 3); | |||
assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); | |||
assertTrue(logContainsText(USER_ORIGINATED_TRUE)); | |||
} | |||
@Test | |||
public void testColumnHiding_whenHidableColumnRemoved_toggleRemoved() { | |||
toggleColumnHidable(0); | |||
toggleColumnHidable(1); | |||
getSidebarOpenButton().click(); | |||
assertNotNull(getColumnHidingToggle(0)); | |||
addRemoveColumn(0); | |||
assertNull(getColumnHidingToggle(0)); | |||
} | |||
@Test | |||
public void testColumnHiding_whenHidableColumnAdded_toggleWithCorrectCaptionAdded() { | |||
selectMenuPath("Component", "Size", "Width", "100%"); | |||
toggleColumnHidable(0); | |||
toggleColumnHidable(1); | |||
toggleColumnHidingToggleCaptionChange(0); | |||
getSidebarOpenButton().click(); | |||
assertEquals("Column 0 caption 0", getColumnHidingToggle(0).getText()); | |||
getSidebarOpenButton().click(); | |||
addRemoveColumn(0); | |||
addRemoveColumn(4); | |||
addRemoveColumn(5); | |||
addRemoveColumn(6); | |||
addRemoveColumn(7); | |||
addRemoveColumn(8); | |||
addRemoveColumn(9); | |||
addRemoveColumn(10); | |||
assertColumnHeaderOrder(1, 2, 3, 11); | |||
getSidebarOpenButton().click(); | |||
assertNull(getColumnHidingToggle(0)); | |||
getSidebarOpenButton().click(); | |||
addRemoveColumn(0); | |||
assertColumnHeaderOrder(1, 2, 3, 11, 0); | |||
getSidebarOpenButton().click(); | |||
assertEquals("Column 0 caption 0", getColumnHidingToggle(0).getText()); | |||
} | |||
@Test | |||
public void testColumnHidingToggleCaption_settingToggleCaption_updatesToggle() { | |||
toggleColumnHidable(1); | |||
getSidebarOpenButton().click(); | |||
assertEquals("column 1", getGridElement().getHeaderCell(0, 1).getText() | |||
.toLowerCase()); | |||
assertEquals("Column 1", getColumnHidingToggle(1).getText()); | |||
toggleColumnHidingToggleCaptionChange(1); | |||
assertEquals("column 1", getGridElement().getHeaderCell(0, 1).getText() | |||
.toLowerCase()); | |||
assertEquals("Column 1 caption 0", getColumnHidingToggle(1).getText()); | |||
toggleColumnHidingToggleCaptionChange(1); | |||
assertEquals("Column 1 caption 1", getColumnHidingToggle(1).getText()); | |||
} | |||
@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"); | |||
assertEquals("Column 1", getColumnHidingToggle(1).getText()); | |||
} | |||
private void toggleColumnHidingToggleCaptionChange(int index) { | |||
selectMenuPath("Component", "Columns", "Column " + index, | |||
"Change hiding toggle caption"); | |||
} | |||
@Test | |||
public void testFrozenColumnHiding_hiddenColumnMadeFrozen_frozenWhenMadeVisible() { | |||
selectMenuPath("Component", "Size", "Width", "100%"); | |||
toggleColumnHidable(0); | |||
toggleColumnHidable(1); | |||
getSidebarOpenButton().click(); | |||
getColumnHidingToggle(0).click(); | |||
getColumnHidingToggle(1).click(); | |||
assertColumnHeaderOrder(2, 3, 4, 5); | |||
setFrozenColumns(2); | |||
verifyColumnNotFrozen(0); | |||
verifyColumnNotFrozen(1); | |||
getColumnHidingToggle(0).click(); | |||
assertColumnHeaderOrder(0, 2, 3, 4, 5); | |||
verifyColumnFrozen(0); | |||
verifyColumnNotFrozen(1); | |||
getColumnHidingToggle(1).click(); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5); | |||
verifyColumnFrozen(0); | |||
verifyColumnFrozen(1); | |||
verifyColumnNotFrozen(2); | |||
} | |||
@Test | |||
public void testFrozenColumnHiding_hiddenFrozenColumnUnfrozen_notFrozenWhenMadeVisible() { | |||
selectMenuPath("Component", "Size", "Width", "100%"); | |||
toggleColumnHidable(0); | |||
toggleColumnHidable(1); | |||
setFrozenColumns(2); | |||
verifyColumnFrozen(0); | |||
verifyColumnFrozen(1); | |||
verifyColumnNotFrozen(2); | |||
verifyColumnNotFrozen(3); | |||
getSidebarOpenButton().click(); | |||
getColumnHidingToggle(0).click(); | |||
getColumnHidingToggle(1).click(); | |||
assertColumnHeaderOrder(2, 3, 4, 5); | |||
verifyColumnNotFrozen(0); | |||
verifyColumnNotFrozen(1); | |||
setFrozenColumns(0); | |||
verifyColumnNotFrozen(0); | |||
verifyColumnNotFrozen(1); | |||
getColumnHidingToggle(0).click(); | |||
assertColumnHeaderOrder(0, 2, 3, 4, 5); | |||
verifyColumnNotFrozen(0); | |||
verifyColumnNotFrozen(1); | |||
getColumnHidingToggle(1).click(); | |||
assertColumnHeaderOrder(0, 1, 2, 3, 4, 5); | |||
verifyColumnNotFrozen(0); | |||
verifyColumnNotFrozen(1); | |||
verifyColumnNotFrozen(2); | |||
} | |||
private void verifyColumnFrozen(int index) { | |||
assertTrue(getGridElement().getHeaderCell(0, index).isFrozen()); | |||
} | |||
private void verifyColumnNotFrozen(int index) { | |||
assertFalse(getGridElement().getHeaderCell(0, index).isFrozen()); | |||
} | |||
private void toggleColumnHidable(int index) { | |||
selectMenuPath("Component", "Columns", "Column " + index, "Hidable"); | |||
} | |||
private void addRemoveColumn(int index) { | |||
selectMenuPath("Component", "Columns", "Column " + index, | |||
"Add / Remove"); | |||
} | |||
} |
@@ -0,0 +1,306 @@ | |||
/* | |||
* 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.server; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import org.junit.Before; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.NotificationElement; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; | |||
public class GridDetailsServerTest extends GridBasicFeaturesTest { | |||
/** | |||
* The reason to why last item details wasn't selected is that since it will | |||
* exist only after the viewport has been scrolled into view, we wouldn't be | |||
* able to scroll that particular details row into view, making tests | |||
* awkward with two scroll commands back to back. | |||
*/ | |||
private static final int ALMOST_LAST_INDEX = 995; | |||
private static final String[] OPEN_ALMOST_LAST_ITEM_DETAILS = new String[] { | |||
"Component", "Details", "Open " + ALMOST_LAST_INDEX }; | |||
private static final String[] OPEN_FIRST_ITEM_DETAILS = new String[] { | |||
"Component", "Details", "Open firstItemId" }; | |||
private static final String[] TOGGLE_FIRST_ITEM_DETAILS = new String[] { | |||
"Component", "Details", "Toggle firstItemId" }; | |||
private static final String[] DETAILS_GENERATOR_NULL = new String[] { | |||
"Component", "Details", "Generators", "NULL" }; | |||
private static final String[] DETAILS_GENERATOR_WATCHING = new String[] { | |||
"Component", "Details", "Generators", "\"Watching\"" }; | |||
private static final String[] DETAILS_GENERATOR_HIERARCHICAL = new String[] { | |||
"Component", "Details", "Generators", "Hierarchical" }; | |||
private static final String[] CHANGE_HIERARCHY = new String[] { | |||
"Component", "Details", "Generators", "- Change Component" }; | |||
@Before | |||
public void setUp() { | |||
openTestURL(); | |||
} | |||
@Test | |||
public void openVisibleDetails() { | |||
try { | |||
getGridElement().getDetails(0); | |||
fail("Expected NoSuchElementException"); | |||
} catch (NoSuchElementException ignore) { | |||
// expected | |||
} | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
assertNotNull("details should've opened", getGridElement() | |||
.getDetails(0)); | |||
} | |||
@Test(expected = NoSuchElementException.class) | |||
public void closeVisibleDetails() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
getGridElement().getDetails(0); | |||
} | |||
@Test | |||
public void openVisiblePopulatedDetails() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
assertNotNull("details should've populated", getGridElement() | |||
.getDetails(0).findElement(By.className("v-widget"))); | |||
} | |||
@Test(expected = NoSuchElementException.class) | |||
public void closeVisiblePopulatedDetails() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
getGridElement().getDetails(0); | |||
} | |||
@Test | |||
public void openDetailsOutsideOfActiveRange() throws InterruptedException { | |||
getGridElement().scroll(10000); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
getGridElement().scroll(0); | |||
Thread.sleep(50); | |||
assertNotNull("details should've been opened", getGridElement() | |||
.getDetails(0)); | |||
} | |||
@Test(expected = NoSuchElementException.class) | |||
public void closeDetailsOutsideOfActiveRange() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
getGridElement().scroll(10000); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
getGridElement().scroll(0); | |||
getGridElement().getDetails(0); | |||
} | |||
@Test | |||
public void componentIsVisibleClientSide() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
TestBenchElement details = getGridElement().getDetails(0); | |||
assertNotNull("No widget detected inside details", | |||
details.findElement(By.className("v-widget"))); | |||
} | |||
@Test | |||
public void openingDetailsTwice() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // close | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open | |||
TestBenchElement details = getGridElement().getDetails(0); | |||
assertNotNull("No widget detected inside details", | |||
details.findElement(By.className("v-widget"))); | |||
} | |||
@Test(expected = NoSuchElementException.class) | |||
public void scrollingDoesNotCreateAFloodOfDetailsRows() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
// scroll somewhere to hit uncached rows | |||
getGridElement().scrollToRow(101); | |||
// this should throw | |||
getGridElement().getDetails(100); | |||
} | |||
@Test | |||
public void openingDetailsOutOfView() { | |||
getGridElement().scrollToRow(500); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
getGridElement().scrollToRow(0); | |||
// if this fails, it'll fail before the assertNotNull | |||
assertNotNull("unexpected null details row", getGridElement() | |||
.getDetails(0)); | |||
} | |||
@Test | |||
public void togglingAVisibleDetailsRowWithOneRoundtrip() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open | |||
assertTrue("Unexpected generator content", | |||
getGridElement().getDetails(0).getText().endsWith("(0)")); | |||
selectMenuPath(TOGGLE_FIRST_ITEM_DETAILS); | |||
assertTrue("New component was not displayed in the client", | |||
getGridElement().getDetails(0).getText().endsWith("(1)")); | |||
} | |||
@Test | |||
public void almostLastItemIdIsRendered() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_ALMOST_LAST_ITEM_DETAILS); | |||
scrollGridVerticallyTo(100000); | |||
TestBenchElement details = getGridElement().getDetails( | |||
ALMOST_LAST_INDEX); | |||
assertNotNull(details); | |||
assertTrue("Unexpected details content", | |||
details.getText().endsWith(ALMOST_LAST_INDEX + " (0)")); | |||
} | |||
@Test | |||
public void hierarchyChangesWorkInDetails() { | |||
selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
assertEquals("One", getGridElement().getDetails(0).getText()); | |||
selectMenuPath(CHANGE_HIERARCHY); | |||
assertEquals("Two", getGridElement().getDetails(0).getText()); | |||
} | |||
@Ignore("This use case is not currently supported by Grid. If the detail " | |||
+ "is out of view, the component is detached from the UI and a " | |||
+ "new instance is generated when scrolled back. Support will " | |||
+ "maybe be incorporated at a later time") | |||
@Test | |||
public void hierarchyChangesWorkInDetailsWhileOutOfView() { | |||
selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
scrollGridVerticallyTo(10000); | |||
selectMenuPath(CHANGE_HIERARCHY); | |||
scrollGridVerticallyTo(0); | |||
assertEquals("Two", getGridElement().getDetails(0).getText()); | |||
} | |||
@Test | |||
public void swappingDetailsGenerators_noDetailsShown() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(DETAILS_GENERATOR_NULL); | |||
assertFalse("Got some errors", $(NotificationElement.class).exists()); | |||
} | |||
@Test | |||
public void swappingDetailsGenerators_shownDetails() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
assertTrue("Details should be empty at the start", getGridElement() | |||
.getDetails(0).getText().isEmpty()); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
assertFalse("Details should not be empty after swapping generator", | |||
getGridElement().getDetails(0).getText().isEmpty()); | |||
} | |||
@Test | |||
public void swappingDetailsGenerators_whileDetailsScrolledOut_showNever() { | |||
scrollGridVerticallyTo(1000); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
assertFalse("Got some errors", $(NotificationElement.class).exists()); | |||
} | |||
@Test | |||
public void swappingDetailsGenerators_whileDetailsScrolledOut_showAfter() { | |||
scrollGridVerticallyTo(1000); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
scrollGridVerticallyTo(0); | |||
assertFalse("Got some errors", $(NotificationElement.class).exists()); | |||
assertNotNull("Could not find a details", getGridElement() | |||
.getDetails(0)); | |||
} | |||
@Test | |||
public void swappingDetailsGenerators_whileDetailsScrolledOut_showBefore() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
scrollGridVerticallyTo(1000); | |||
assertFalse("Got some errors", $(NotificationElement.class).exists()); | |||
assertNotNull("Could not find a details", getGridElement() | |||
.getDetails(0)); | |||
} | |||
@Test | |||
public void swappingDetailsGenerators_whileDetailsScrolledOut_showBeforeAndAfter() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
scrollGridVerticallyTo(1000); | |||
scrollGridVerticallyTo(0); | |||
assertFalse("Got some errors", $(NotificationElement.class).exists()); | |||
assertNotNull("Could not find a details", getGridElement() | |||
.getDetails(0)); | |||
} | |||
@Test | |||
public void nullDetailComponentToggling() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(DETAILS_GENERATOR_NULL); | |||
try { | |||
assertTrue("Details should be empty with null component", | |||
getGridElement().getDetails(0).getText().isEmpty()); | |||
} catch (NoSuchElementException e) { | |||
fail("Expected to find a details row with empty content"); | |||
} | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
assertFalse("Details should be not empty with details component", | |||
getGridElement().getDetails(0).getText().isEmpty()); | |||
} | |||
@Test | |||
public void noAssertErrorsOnEmptyDetailsAndScrollDown() { | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
scrollGridVerticallyTo(500); | |||
assertFalse(logContainsText("AssertionError")); | |||
} | |||
@Test | |||
public void noAssertErrorsOnPopulatedDetailsAndScrollDown() { | |||
selectMenuPath(DETAILS_GENERATOR_WATCHING); | |||
selectMenuPath(OPEN_FIRST_ITEM_DETAILS); | |||
scrollGridVerticallyTo(500); | |||
assertFalse(logContainsText("AssertionError")); | |||
} | |||
} |
@@ -0,0 +1,84 @@ | |||
/* | |||
* 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.server; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.interactions.Actions; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridSidebarFeatures; | |||
public class GridSidebarThemeTest extends GridBasicFeaturesTest { | |||
@Test | |||
public void testValo() throws Exception { | |||
runTestSequence("valo"); | |||
} | |||
@Test | |||
public void testValoDark() throws Exception { | |||
runTestSequence("tests-valo-dark"); | |||
} | |||
@Override | |||
protected Class<?> getUIClass() { | |||
return GridSidebarFeatures.class; | |||
} | |||
private void runTestSequence(String theme) throws IOException { | |||
openTestURL("theme=" + theme); | |||
compareScreen(theme + "|SidebarClosed"); | |||
getSidebarOpenButton().click(); | |||
compareScreen(theme + "|SidebarOpen"); | |||
new Actions(getDriver()).moveToElement(getColumnHidingToggle(2), 5, 5) | |||
.perform(); | |||
compareScreen(theme + "|OnMouseOverNotHiddenToggle"); | |||
getColumnHidingToggle(2).click(); | |||
getColumnHidingToggle(3).click(); | |||
getColumnHidingToggle(6).click(); | |||
new Actions(getDriver()).moveToElement(getSidebarOpenButton()) | |||
.perform(); | |||
; | |||
compareScreen(theme + "|TogglesTriggered"); | |||
new Actions(getDriver()).moveToElement(getColumnHidingToggle(2)) | |||
.perform(); | |||
; | |||
compareScreen(theme + "|OnMouseOverHiddenToggle"); | |||
getSidebarOpenButton().click(); | |||
compareScreen(theme + "|SidebarClosed2"); | |||
} | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
// phantom JS looks wrong from the beginning, so not tested | |||
return getBrowsersExcludingPhantomJS(); | |||
} | |||
} |
@@ -19,7 +19,6 @@ import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import org.openqa.selenium.support.ui.ExpectedConditions; | |||
@@ -83,10 +82,4 @@ public class LoadingIndicatorTest extends GridBasicFeaturesTest { | |||
}); | |||
} | |||
private boolean isLoadingIndicatorVisible() { | |||
WebElement loadingIndicator = findElement(By | |||
.className("v-loading-indicator")); | |||
return loadingIndicator.isDisplayed(); | |||
} | |||
} |
@@ -938,4 +938,24 @@ public abstract class AbstractTB3Test extends ParallelTest { | |||
protected void click(CheckBoxElement checkbox) { | |||
checkbox.findElement(By.xpath("input")).click(); | |||
} | |||
protected boolean isLoadingIndicatorVisible() { | |||
WebElement loadingIndicator = findElement(By | |||
.className("v-loading-indicator")); | |||
return loadingIndicator.isDisplayed(); | |||
} | |||
protected void waitUntilLoadingIndicatorNotVisible() { | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
WebElement loadingIndicator = input.findElement(By | |||
.className("v-loading-indicator")); | |||
return !loadingIndicator.isDisplayed(); | |||
} | |||
}); | |||
} | |||
} |
@@ -32,10 +32,14 @@ public class PersonContainer extends BeanItemContainer<Person> implements | |||
} | |||
public static PersonContainer createWithTestData() { | |||
return createWithTestData(100); | |||
} | |||
public static PersonContainer createWithTestData(int size) { | |||
PersonContainer c = null; | |||
Random r = new Random(0); | |||
c = new PersonContainer(); | |||
for (int i = 0; i < 100; i++) { | |||
for (int i = 0; i < size; i++) { | |||
Person p = new Person(); | |||
p.setFirstName(TestDataGenerator.getFirstName(r)); | |||
p.setLastName(TestDataGenerator.getLastName(r)); |
@@ -6,13 +6,18 @@ import java.util.List; | |||
import com.google.gwt.core.client.Duration; | |||
import com.google.gwt.core.client.Scheduler.ScheduledCommand; | |||
import com.google.gwt.dom.client.TableCellElement; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.ui.Composite; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.vaadin.client.widget.escalator.EscalatorUpdater; | |||
import com.vaadin.client.widget.escalator.FlyweightCell; | |||
import com.vaadin.client.widget.escalator.Row; | |||
import com.vaadin.client.widget.escalator.RowContainer; | |||
import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; | |||
import com.vaadin.client.widget.escalator.Spacer; | |||
import com.vaadin.client.widget.escalator.SpacerUpdater; | |||
import com.vaadin.client.widgets.Escalator; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
public class EscalatorBasicClientFeaturesWidget extends | |||
PureGWTTestApplication<Escalator> { | |||
@@ -303,6 +308,7 @@ public class EscalatorBasicClientFeaturesWidget extends | |||
createColumnsAndRowsMenu(); | |||
createFrozenMenu(); | |||
createColspanMenu(); | |||
createSpacerMenu(); | |||
} | |||
private void createFrozenMenu() { | |||
@@ -567,6 +573,26 @@ public class EscalatorBasicClientFeaturesWidget extends | |||
escalator.setScrollTop(40); | |||
} | |||
}, menupath); | |||
String[] scrollToRowMenuPath = new String[menupath.length + 1]; | |||
System.arraycopy(menupath, 0, scrollToRowMenuPath, 0, menupath.length); | |||
scrollToRowMenuPath[scrollToRowMenuPath.length - 1] = "Scroll to..."; | |||
for (int i = 0; i < 100; i += 25) { | |||
final int rowIndex = i; | |||
addMenuCommand("Row " + i, new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.scrollToRow(rowIndex, ScrollDestination.ANY, 0); | |||
} | |||
}, scrollToRowMenuPath); | |||
} | |||
addMenuCommand("Set 20px default height", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.getBody().setDefaultRowHeight(20); | |||
} | |||
}, menupath); | |||
} | |||
private void createRowsMenu(final RowContainer container, String[] menupath) { | |||
@@ -612,6 +638,94 @@ public class EscalatorBasicClientFeaturesWidget extends | |||
}, menupath); | |||
} | |||
private void createSpacerMenu() { | |||
String[] menupath = { "Features", "Spacers" }; | |||
addMenuCommand("Swap Spacer Updater", new ScheduledCommand() { | |||
private final SpacerUpdater CUSTOM = new SpacerUpdater() { | |||
@Override | |||
public void destroy(Spacer spacer) { | |||
spacer.getElement().setInnerText(""); | |||
} | |||
@Override | |||
public void init(Spacer spacer) { | |||
spacer.getElement().setInnerText( | |||
"Spacer for row " + spacer.getRow()); | |||
} | |||
}; | |||
@Override | |||
public void execute() { | |||
BodyRowContainer body = escalator.getBody(); | |||
if (SpacerUpdater.NULL.equals(body.getSpacerUpdater())) { | |||
body.setSpacerUpdater(CUSTOM); | |||
} else { | |||
body.setSpacerUpdater(SpacerUpdater.NULL); | |||
} | |||
} | |||
}, menupath); | |||
addMenuCommand("Focusable Updater", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.getBody().setSpacerUpdater(new SpacerUpdater() { | |||
@Override | |||
public void init(Spacer spacer) { | |||
spacer.getElement().appendChild(DOM.createInputText()); | |||
} | |||
@Override | |||
public void destroy(Spacer spacer) { | |||
spacer.getElement().removeAllChildren(); | |||
} | |||
}); | |||
} | |||
}, menupath); | |||
createSpacersMenuForRow(-1, menupath); | |||
createSpacersMenuForRow(1, menupath); | |||
createSpacersMenuForRow(50, menupath); | |||
createSpacersMenuForRow(99, menupath); | |||
} | |||
private void createSpacersMenuForRow(final int rowIndex, String[] menupath) { | |||
menupath = new String[] { menupath[0], menupath[1], "Row " + rowIndex }; | |||
addMenuCommand("Set 100px", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.getBody().setSpacer(rowIndex, 100); | |||
} | |||
}, menupath); | |||
addMenuCommand("Set 50px", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.getBody().setSpacer(rowIndex, 50); | |||
} | |||
}, menupath); | |||
addMenuCommand("Remove", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.getBody().setSpacer(rowIndex, -1); | |||
} | |||
}, menupath); | |||
addMenuCommand("Scroll here (ANY, 0)", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.scrollToSpacer(rowIndex, ScrollDestination.ANY, 0); | |||
} | |||
}, menupath); | |||
addMenuCommand("Scroll here row+spacer below (ANY, 0)", | |||
new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
escalator.scrollToRowAndSpacer(rowIndex, | |||
ScrollDestination.ANY, 0); | |||
} | |||
}, menupath); | |||
} | |||
private void insertRows(final RowContainer container, int offset, int number) { | |||
if (container == escalator.getBody()) { | |||
data.insertRows(offset, number); |
@@ -24,6 +24,8 @@ import com.vaadin.client.widget.escalator.Cell; | |||
import com.vaadin.client.widget.escalator.ColumnConfiguration; | |||
import com.vaadin.client.widget.escalator.EscalatorUpdater; | |||
import com.vaadin.client.widget.escalator.RowContainer; | |||
import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; | |||
import com.vaadin.client.widget.escalator.SpacerUpdater; | |||
import com.vaadin.client.widgets.Escalator; | |||
import com.vaadin.tests.widgetset.client.grid.EscalatorBasicClientFeaturesWidget.LogWidget; | |||
@@ -97,6 +99,33 @@ public class EscalatorProxy extends Escalator { | |||
} | |||
} | |||
private class BodyRowContainerProxy extends RowContainerProxy implements | |||
BodyRowContainer { | |||
private BodyRowContainer rowContainer; | |||
public BodyRowContainerProxy(BodyRowContainer rowContainer) { | |||
super(rowContainer); | |||
this.rowContainer = rowContainer; | |||
} | |||
@Override | |||
public void setSpacer(int rowIndex, double height) | |||
throws IllegalArgumentException { | |||
rowContainer.setSpacer(rowIndex, height); | |||
} | |||
@Override | |||
public void setSpacerUpdater(SpacerUpdater spacerUpdater) | |||
throws IllegalArgumentException { | |||
rowContainer.setSpacerUpdater(spacerUpdater); | |||
} | |||
@Override | |||
public SpacerUpdater getSpacerUpdater() { | |||
return rowContainer.getSpacerUpdater(); | |||
} | |||
} | |||
private class RowContainerProxy implements RowContainer { | |||
private final RowContainer rowContainer; | |||
@@ -176,7 +205,7 @@ public class EscalatorProxy extends Escalator { | |||
} | |||
private RowContainer headerProxy = null; | |||
private RowContainer bodyProxy = null; | |||
private BodyRowContainer bodyProxy = null; | |||
private RowContainer footerProxy = null; | |||
private ColumnConfiguration columnProxy = null; | |||
private LogWidget logWidget; | |||
@@ -198,9 +227,9 @@ public class EscalatorProxy extends Escalator { | |||
} | |||
@Override | |||
public RowContainer getBody() { | |||
public BodyRowContainer getBody() { | |||
if (bodyProxy == null) { | |||
bodyProxy = new RowContainerProxy(super.getBody()); | |||
bodyProxy = new BodyRowContainerProxy(super.getBody()); | |||
} | |||
return bodyProxy; | |||
} |
@@ -33,9 +33,13 @@ import com.google.gwt.user.client.Timer; | |||
import com.google.gwt.user.client.Window; | |||
import com.google.gwt.user.client.ui.Button; | |||
import com.google.gwt.user.client.ui.Composite; | |||
import com.google.gwt.user.client.ui.FlowPanel; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.google.gwt.user.client.ui.Label; | |||
import com.google.gwt.user.client.ui.MenuItem; | |||
import com.google.gwt.user.client.ui.MenuItemSeparator; | |||
import com.google.gwt.user.client.ui.TextBox; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.data.DataSource; | |||
import com.vaadin.client.data.DataSource.RowHandle; | |||
import com.vaadin.client.renderers.DateRenderer; | |||
@@ -46,6 +50,7 @@ import com.vaadin.client.renderers.TextRenderer; | |||
import com.vaadin.client.ui.VLabel; | |||
import com.vaadin.client.widget.grid.CellReference; | |||
import com.vaadin.client.widget.grid.CellStyleGenerator; | |||
import com.vaadin.client.widget.grid.DetailsGenerator; | |||
import com.vaadin.client.widget.grid.EditorHandler; | |||
import com.vaadin.client.widget.grid.RendererCellReference; | |||
import com.vaadin.client.widget.grid.RowReference; | |||
@@ -55,6 +60,10 @@ import com.vaadin.client.widget.grid.datasources.ListSorter; | |||
import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; | |||
import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; | |||
import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; | |||
import com.vaadin.client.widget.grid.events.ColumnReorderEvent; | |||
import com.vaadin.client.widget.grid.events.ColumnReorderHandler; | |||
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; | |||
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; | |||
import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; | |||
import com.vaadin.client.widget.grid.events.FooterKeyPressHandler; | |||
import com.vaadin.client.widget.grid.events.FooterKeyUpHandler; | |||
@@ -73,6 +82,7 @@ import com.vaadin.client.widgets.Grid.Column; | |||
import com.vaadin.client.widgets.Grid.FooterRow; | |||
import com.vaadin.client.widgets.Grid.HeaderRow; | |||
import com.vaadin.client.widgets.Grid.SelectionMode; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
import com.vaadin.tests.widgetset.client.grid.GridBasicClientFeaturesWidget.Data; | |||
/** | |||
@@ -400,6 +410,8 @@ public class GridBasicClientFeaturesWidget extends | |||
createEditorMenu(); | |||
createInternalsMenu(); | |||
createDataSourceMenu(); | |||
createDetailsMenu(); | |||
createSidebarMenu(); | |||
grid.getElement().getStyle().setZIndex(0); | |||
@@ -444,6 +456,71 @@ public class GridBasicClientFeaturesWidget extends | |||
}); | |||
} | |||
}, listenersPath); | |||
addMenuCommand("Add ColumnReorder listener", new ScheduledCommand() { | |||
private HandlerRegistration columnReorderHandler = null; | |||
@Override | |||
public void execute() { | |||
if (columnReorderHandler != null) { | |||
return; | |||
} | |||
final Label columnOrderLabel = new Label(); | |||
columnOrderLabel.getElement().setId("columnreorder"); | |||
addLineEnd(columnOrderLabel, 300); | |||
columnReorderHandler = grid | |||
.addColumnReorderHandler(new ColumnReorderHandler<List<Data>>() { | |||
private int eventIndex = 0; | |||
@Override | |||
public void onColumnReorder( | |||
ColumnReorderEvent<List<Data>> event) { | |||
columnOrderLabel.getElement().setAttribute( | |||
"columns", "" + (++eventIndex)); | |||
} | |||
}); | |||
} | |||
}, listenersPath); | |||
addMenuCommand("Add Column Visibility Change listener", | |||
new ScheduledCommand() { | |||
private HandlerRegistration columnVisibilityHandler = null; | |||
@Override | |||
public void execute() { | |||
if (columnVisibilityHandler != null) { | |||
return; | |||
} | |||
final Label columnOrderLabel = new Label(); | |||
columnOrderLabel.getElement().setId("columnvisibility"); | |||
addLineEnd(columnOrderLabel, 250); | |||
ColumnVisibilityChangeHandler handler = new ColumnVisibilityChangeHandler<List<Data>>() { | |||
private int eventIndex = 0; | |||
@Override | |||
public void onVisibilityChange( | |||
ColumnVisibilityChangeEvent<List<Data>> event) { | |||
columnOrderLabel.getElement().setAttribute( | |||
"counter", "" + (++eventIndex)); | |||
columnOrderLabel.getElement().setAttribute( | |||
"useroriginated", | |||
(Boolean.toString(event | |||
.isUserOriginated()))); | |||
columnOrderLabel.getElement().setAttribute( | |||
"ishidden", | |||
(Boolean.toString(event.isHidden()))); | |||
columnOrderLabel.getElement().setAttribute( | |||
"columnindex", | |||
"" | |||
+ grid.getColumns().indexOf( | |||
event.getColumn())); | |||
} | |||
}; | |||
columnVisibilityHandler = grid | |||
.addColumnVisibilityChangeHandler(handler); | |||
} | |||
}, listenersPath); | |||
} | |||
private void createStateMenu() { | |||
@@ -658,6 +735,79 @@ public class GridBasicClientFeaturesWidget extends | |||
grid.setColumnOrder(columns.toArray(new Column[columns.size()])); | |||
} | |||
}, "Component", "State"); | |||
addMenuCommand("Column Reordering", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setColumnReorderingAllowed(!grid | |||
.isColumnReorderingAllowed()); | |||
} | |||
}, "Component", "State"); | |||
addMenuCommand("250px", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setWidth("250px"); | |||
} | |||
}, "Component", "State", "Width"); | |||
addMenuCommand("500px", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setWidth("500px"); | |||
} | |||
}, "Component", "State", "Width"); | |||
addMenuCommand("750px", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setWidth("750px"); | |||
} | |||
}, "Component", "State", "Width"); | |||
addMenuCommand("1000px", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setWidth("1000px"); | |||
} | |||
}, "Component", "State", "Width"); | |||
createScrollToRowMenu(); | |||
} | |||
private void createScrollToRowMenu() { | |||
String[] menupath = new String[] { "Component", "State", | |||
"Scroll to...", null }; | |||
for (int i = 0; i < ROWS; i += 100) { | |||
menupath[3] = "Row " + i + "..."; | |||
for (final ScrollDestination scrollDestination : ScrollDestination | |||
.values()) { | |||
final int row = i; | |||
addMenuCommand("Destination " + scrollDestination, | |||
new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.scrollToRow(row, scrollDestination); | |||
} | |||
}, menupath); | |||
} | |||
} | |||
int i = ROWS - 1; | |||
menupath[3] = "Row " + i + "..."; | |||
for (final ScrollDestination scrollDestination : ScrollDestination | |||
.values()) { | |||
final int row = i; | |||
addMenuCommand("Destination " + scrollDestination, | |||
new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.scrollToRow(row, scrollDestination); | |||
} | |||
}, menupath); | |||
} | |||
} | |||
private void createColumnsMenu() { | |||
@@ -671,7 +821,18 @@ public class GridBasicClientFeaturesWidget extends | |||
column.setSortable(!column.isSortable()); | |||
} | |||
}, "Component", "Columns", "Column " + i); | |||
addMenuCommand("Hidden", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
column.setHidden(!column.isHidden()); | |||
} | |||
}, "Component", "Columns", "Column " + i); | |||
addMenuCommand("Hidable", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
column.setHidable(!column.isHidable()); | |||
} | |||
}, "Component", "Columns", "Column " + i); | |||
addMenuCommand("auto", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
@@ -768,6 +929,25 @@ public class GridBasicClientFeaturesWidget extends | |||
}); | |||
} | |||
}, "Component", "Columns", "Column " + i); | |||
addMenuCommand("Move column left", new ScheduledCommand() { | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
public void execute() { | |||
List<Column<?, List<Data>>> cols = grid.getColumns(); | |||
ArrayList<Column> reordered = new ArrayList<Column>(cols); | |||
final int index = cols.indexOf(column); | |||
if (index == 0) { | |||
Column<?, List<Data>> col = reordered.remove(0); | |||
reordered.add(col); | |||
} else { | |||
Column<?, List<Data>> col = reordered.remove(index); | |||
reordered.add(index - 1, col); | |||
} | |||
grid.setColumnOrder(reordered.toArray(new Column[reordered | |||
.size()])); | |||
} | |||
}, "Component", "Columns", "Column " + i); | |||
} | |||
} | |||
@@ -1223,4 +1403,166 @@ public class GridBasicClientFeaturesWidget extends | |||
String coords = "(" + object + ", " + column + ")"; | |||
label.setText(coords + " " + output); | |||
} | |||
private void createDetailsMenu() { | |||
String[] menupath = new String[] { "Component", "Row details" }; | |||
addMenuCommand("Set generator", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setDetailsGenerator(new DetailsGenerator() { | |||
@Override | |||
public Widget getDetails(int rowIndex) { | |||
FlowPanel panel = new FlowPanel(); | |||
final Label label = new Label("Row: " + rowIndex + "."); | |||
Button button = new Button("Button", | |||
new ClickHandler() { | |||
@Override | |||
public void onClick(ClickEvent event) { | |||
label.setText("clicked"); | |||
} | |||
}); | |||
panel.add(label); | |||
panel.add(button); | |||
return panel; | |||
} | |||
}); | |||
} | |||
}, menupath); | |||
addMenuCommand("Set faulty generator", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setDetailsGenerator(new DetailsGenerator() { | |||
@Override | |||
public Widget getDetails(int rowIndex) { | |||
throw new RuntimeException("This is by design."); | |||
} | |||
}); | |||
} | |||
}, menupath); | |||
addMenuCommand("Set empty generator", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setDetailsGenerator(new DetailsGenerator() { | |||
/* | |||
* While this is functionally equivalent to the NULL | |||
* generator, it's good to be explicit, since the behavior | |||
* isn't strictly tied between them. NULL generator might be | |||
* changed to render something different by default, and an | |||
* empty generator might behave differently also in the | |||
* future. | |||
*/ | |||
@Override | |||
public Widget getDetails(int rowIndex) { | |||
return null; | |||
} | |||
}); | |||
} | |||
}, menupath); | |||
String[] togglemenupath = new String[] { menupath[0], menupath[1], | |||
"Toggle details for..." }; | |||
for (int i : new int[] { 0, 1, 100, 200, 300, 400, 500, 600, 700, 800, | |||
900, 999 }) { | |||
final int rowIndex = i; | |||
addMenuCommand("Row " + rowIndex, new ScheduledCommand() { | |||
boolean visible = false; | |||
@Override | |||
public void execute() { | |||
visible = !visible; | |||
grid.setDetailsVisible(rowIndex, visible); | |||
} | |||
}, togglemenupath); | |||
} | |||
} | |||
private static Logger getLogger() { | |||
return Logger.getLogger(GridBasicClientFeaturesWidget.class.getName()); | |||
} | |||
private void createSidebarMenu() { | |||
String[] menupath = new String[] { "Component", "Sidebar" }; | |||
final List<MenuItem> customMenuItems = new ArrayList<MenuItem>(); | |||
final List<MenuItemSeparator> separators = new ArrayList<MenuItemSeparator>(); | |||
addMenuCommand("Add item to end", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
MenuItem item = createSidebarMenuItem(customMenuItems.size()); | |||
customMenuItems.add(item); | |||
grid.getSidebarMenu().addItem(item); | |||
} | |||
}, menupath); | |||
addMenuCommand("Add item before index 1", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
MenuItem item = createSidebarMenuItem(customMenuItems.size()); | |||
customMenuItems.add(item); | |||
grid.getSidebarMenu().insertItem(item, 1); | |||
} | |||
}, menupath); | |||
addMenuCommand("Remove last added item", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.getSidebarMenu().removeItem( | |||
customMenuItems.remove(customMenuItems.size() - 1)); | |||
} | |||
}, menupath); | |||
addMenuCommand("Add separator to end", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
MenuItemSeparator separator = new MenuItemSeparator(); | |||
separators.add(separator); | |||
grid.getSidebarMenu().addSeparator(separator); | |||
} | |||
}, menupath); | |||
addMenuCommand("Add separator before index 1", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
MenuItemSeparator separator = new MenuItemSeparator(); | |||
separators.add(separator); | |||
grid.getSidebarMenu().insertSeparator(separator, 1); | |||
} | |||
}, menupath); | |||
addMenuCommand("Remove last added separator", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.getSidebarMenu().removeSeparator( | |||
separators.remove(separators.size() - 1)); | |||
} | |||
}, menupath); | |||
addMenuCommand("Toggle sidebar visibility", new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
grid.setSidebarOpen(!grid.isSidebarOpen()); | |||
} | |||
}, menupath); | |||
} | |||
private MenuItem createSidebarMenuItem(final int index) { | |||
final MenuItem menuItem = new MenuItem("Custom menu item " + index, | |||
new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
if (index % 2 == 0) { | |||
grid.setSidebarOpen(false); | |||
} | |||
getLogger().info("Menu item " + index + " selected"); | |||
} | |||
}); | |||
return menuItem; | |||
} | |||
} |
@@ -55,6 +55,7 @@ | |||
<include name="com/vaadin/client/renderers/*.java" /> | |||
<include name="com/vaadin/client/ui/SubPartAware.java" /> | |||
<include name="com/vaadin/client/ui/VProgressBar.java" /> | |||
<include name="com/vaadin/client/ui/dd/DragAndDropHandler.java" /> | |||
<include name="com/vaadin/client/VSchedulerImpl.java" /> | |||
<include name="com/vaadin/shared/ui/grid/*.java" /> |