Change-Id: Ia9d156009a3f1b4e61f12eb415040670a52d7876tags/7.2.0.beta1
@@ -15,8 +15,10 @@ | |||
@import "inlinedatefield/inlinedatefield.scss"; | |||
@import "dragwrapper/dragwrapper.scss"; | |||
@import "embedded/embedded.scss"; | |||
@import "escalator/escalator.scss"; | |||
@import "fonts/fonts.scss"; | |||
@import "formlayout/formlayout.scss"; | |||
@import "grid/grid.scss"; | |||
@import "gridlayout/gridlayout.scss"; | |||
@import "label/label.scss"; | |||
@import "link/link.scss"; | |||
@@ -90,7 +92,9 @@ $line-height: normal; | |||
@include base-inline-datefield; | |||
@include base-dragwrapper; | |||
@include base-embedded; | |||
@include base-escalator; | |||
@include base-formlayout; | |||
@include base-grid; | |||
@include base-gridlayout; | |||
@include base-label; | |||
@include base-link; |
@@ -0,0 +1,117 @@ | |||
@mixin base-escalator($primaryStyleName : v-escalator) { | |||
$background-color: white; | |||
$border-color: #aaa; | |||
.#{$primaryStyleName} { | |||
position: relative; | |||
background-color: $background-color; | |||
} | |||
.#{$primaryStyleName}-scroller { | |||
position: absolute; | |||
overflow: auto; | |||
z-index: 20; | |||
} | |||
.#{$primaryStyleName}-scroller-horizontal { | |||
left: 0; /* Left position adjusted to align with frozen columns */ | |||
right: 0; | |||
bottom: 0; | |||
overflow-y: hidden; | |||
-ms-overflow-y: hidden; | |||
} | |||
.#{$primaryStyleName}-scroller-vertical { | |||
right: 0; | |||
top: 0; /* this will be overridden by code, but it's a good default behavior */ | |||
bottom: 0; /* this will be overridden by code, but it's a good default behavior */ | |||
overflow-x: hidden; | |||
-ms-overflow-x: hidden; | |||
} | |||
.#{$primaryStyleName}-tablewrapper { | |||
position: absolute; | |||
overflow: hidden; | |||
} | |||
.#{$primaryStyleName}-tablewrapper > table { | |||
border-spacing: 0; | |||
table-layout: fixed; | |||
width: inherit; /* a decent default fallback */ | |||
} | |||
.#{$primaryStyleName}-header, | |||
.#{$primaryStyleName}-body, | |||
.#{$primaryStyleName}-footer { | |||
position: absolute; | |||
left: 0; | |||
width: inherit; | |||
z-index: 10; | |||
} | |||
.#{$primaryStyleName}-header { top: 0; } | |||
.#{$primaryStyleName}-footer { bottom: 0; } | |||
.#{$primaryStyleName}-body { | |||
z-index: 0; | |||
top: 0; | |||
.#{$primaryStyleName}-row { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.#{$primaryStyleName}-row { | |||
display: block; | |||
.v-ie8 & { | |||
/* IE8 doesn't let table rows be longer than body only with display block. Moar hax. */ | |||
float: left; | |||
clear: left; | |||
/* | |||
* The inline style of margin-top from the <tbody> to offset the header's dimension is, | |||
* for some strange reason, inherited into each contained <tr>. | |||
* We need to cancel it: | |||
*/ | |||
margin-top: 0; | |||
} | |||
> td, > th { | |||
/* IE8 likes the bgcolor here instead of on the row */ | |||
background-color: $background-color; | |||
} | |||
} | |||
.#{$primaryStyleName}-row { | |||
width: inherit; | |||
} | |||
.#{$primaryStyleName}-cell { | |||
display: block; | |||
float: left; | |||
border: 1px solid $border-color; | |||
padding: 2px; | |||
white-space: nowrap; | |||
-moz-box-sizing: border-box; | |||
box-sizing: border-box; | |||
overflow:hidden; | |||
/* | |||
* Because Vaadin changes the font size after the initial render, we | |||
* need to mention the font size here explicitly, otherwise automatic | |||
* row height detection gets broken. | |||
*/ | |||
font-size: $font-size; | |||
} | |||
.#{$primaryStyleName}-cell.frozen { | |||
position: relative; | |||
z-index: 0; | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
@mixin base-grid($primaryStyleName : v-grid) { | |||
@include base-escalator($primaryStyleName); | |||
} |
@@ -32,3 +32,7 @@ | |||
.popup-style .v-datefield-calendarpanel-body { | |||
background: yellow; | |||
} | |||
#escalator .v-escalator-body .v-escalator-cell { | |||
height: 50px; | |||
} |
@@ -0,0 +1,323 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import com.google.gwt.core.client.Scheduler; | |||
import com.google.gwt.core.client.Scheduler.ScheduledCommand; | |||
import com.vaadin.client.Profiler; | |||
import com.vaadin.shared.ui.grid.Range; | |||
/** | |||
* Base implementation for data sources that fetch data from a remote system. | |||
* This class takes care of caching data and communicating with the data source | |||
* user. An implementation of this class should override | |||
* {@link #requestRows(int, int)} to trigger asynchronously loading of data. | |||
* When data is received from the server, new row data should be passed to | |||
* {@link #setRowData(int, List)}. {@link #setEstimatedSize(int)} should be used | |||
* based on estimations of how many rows are available. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @param <T> | |||
* the row type | |||
*/ | |||
public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { | |||
private boolean requestPending = false; | |||
private boolean coverageCheckPending = false; | |||
private Range requestedAvailability = Range.between(0, 0); | |||
private Range cached = Range.between(0, 0); | |||
private final HashMap<Integer, T> rowCache = new HashMap<Integer, T>(); | |||
private DataChangeHandler dataChangeHandler; | |||
private int estimatedSize; | |||
private final ScheduledCommand coverageChecker = new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
coverageCheckPending = false; | |||
checkCacheCoverage(); | |||
} | |||
}; | |||
/** | |||
* Sets the estimated number of rows in the data source. | |||
* | |||
* @param estimatedSize | |||
* the estimated number of available rows | |||
*/ | |||
protected void setEstimatedSize(int estimatedSize) { | |||
// TODO update dataChangeHandler if size changes | |||
this.estimatedSize = estimatedSize; | |||
} | |||
private void ensureCoverageCheck() { | |||
if (!coverageCheckPending) { | |||
coverageCheckPending = true; | |||
Scheduler.get().scheduleDeferred(coverageChecker); | |||
} | |||
} | |||
@Override | |||
public void ensureAvailability(int firstRowIndex, int numberOfRows) { | |||
requestedAvailability = Range.withLength(firstRowIndex, numberOfRows); | |||
/* | |||
* Don't request any data right away since the data might be included in | |||
* a message that has been received but not yet fully processed. | |||
*/ | |||
ensureCoverageCheck(); | |||
} | |||
private void checkCacheCoverage() { | |||
if (requestPending) { | |||
// Anyone clearing requestPending should run this method again | |||
return; | |||
} | |||
Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage"); | |||
if (!requestedAvailability.intersects(cached) || cached.isEmpty()) { | |||
/* | |||
* Simple case: no overlap between cached data and needed data. | |||
* Clear the cache and request new data | |||
*/ | |||
rowCache.clear(); | |||
cached = Range.between(0, 0); | |||
handleMissingRows(requestedAvailability); | |||
} else { | |||
discardStaleCacheEntries(); | |||
// Might need more rows -> request them | |||
Range[] availabilityPartition = requestedAvailability | |||
.partitionWith(cached); | |||
handleMissingRows(availabilityPartition[0]); | |||
handleMissingRows(availabilityPartition[2]); | |||
} | |||
Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage"); | |||
} | |||
private void discardStaleCacheEntries() { | |||
Range[] cacheParition = cached.partitionWith(requestedAvailability); | |||
dropFromCache(cacheParition[0]); | |||
cached = cacheParition[1]; | |||
dropFromCache(cacheParition[2]); | |||
} | |||
private void dropFromCache(Range range) { | |||
for (int i = range.getStart(); i < range.getEnd(); i++) { | |||
rowCache.remove(Integer.valueOf(i)); | |||
} | |||
} | |||
private void handleMissingRows(Range range) { | |||
if (range.isEmpty()) { | |||
return; | |||
} | |||
requestPending = true; | |||
requestRows(range.getStart(), range.length()); | |||
} | |||
/** | |||
* Triggers fetching rows from the remote data source. | |||
* {@link #setRowData(int, List)} should be invoked with data for the | |||
* requested rows when they have been received. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first row to fetch | |||
* @param numberOfRows | |||
* the number of rows to fetch | |||
*/ | |||
protected abstract void requestRows(int firstRowIndex, int numberOfRows); | |||
@Override | |||
public int getEstimatedSize() { | |||
return estimatedSize; | |||
} | |||
@Override | |||
public T getRow(int rowIndex) { | |||
return rowCache.get(Integer.valueOf(rowIndex)); | |||
} | |||
@Override | |||
public void setDataChangeHandler(DataChangeHandler dataChangeHandler) { | |||
this.dataChangeHandler = dataChangeHandler; | |||
if (dataChangeHandler != null && !cached.isEmpty()) { | |||
// Push currently cached data to the implementation | |||
dataChangeHandler.dataUpdated(cached.getStart(), cached.length()); | |||
} | |||
} | |||
/** | |||
* Informs this data source that updated data has been sent from the server. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first received row | |||
* @param rowData | |||
* a list of rows, starting from <code>firstRowIndex</code> | |||
*/ | |||
protected void setRowData(int firstRowIndex, List<T> rowData) { | |||
requestPending = false; | |||
Profiler.enter("AbstractRemoteDataSource.setRowData"); | |||
Range received = Range.withLength(firstRowIndex, rowData.size()); | |||
Range[] partition = received.partitionWith(requestedAvailability); | |||
Range newUsefulData = partition[1]; | |||
if (!newUsefulData.isEmpty()) { | |||
// Update the parts that are actually inside | |||
for (int i = newUsefulData.getStart(); i < newUsefulData.getEnd(); i++) { | |||
rowCache.put(Integer.valueOf(i), rowData.get(i - firstRowIndex)); | |||
} | |||
if (dataChangeHandler != null) { | |||
Profiler.enter("AbstractRemoteDataSource.setRowData notify dataChangeHandler"); | |||
dataChangeHandler.dataUpdated(newUsefulData.getStart(), | |||
newUsefulData.length()); | |||
Profiler.leave("AbstractRemoteDataSource.setRowData notify dataChangeHandler"); | |||
} | |||
// Potentially extend the range | |||
if (cached.isEmpty()) { | |||
cached = newUsefulData; | |||
} else { | |||
discardStaleCacheEntries(); | |||
/* | |||
* everything might've become stale so we need to re-check for | |||
* emptiness. | |||
*/ | |||
if (!cached.isEmpty()) { | |||
cached = cached.combineWith(newUsefulData); | |||
} else { | |||
cached = newUsefulData; | |||
} | |||
} | |||
} | |||
if (!partition[0].isEmpty() || !partition[2].isEmpty()) { | |||
/* | |||
* FIXME | |||
* | |||
* Got data that we might need in a moment if the container is | |||
* updated before the widget settings. Support for this will be | |||
* implemented later on. | |||
*/ | |||
} | |||
// Eventually check whether all needed rows are now available | |||
ensureCoverageCheck(); | |||
Profiler.leave("AbstractRemoteDataSource.setRowData"); | |||
} | |||
/** | |||
* Informs this data source that the server has removed data. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first removed row | |||
* @param count | |||
* the number of removed rows, starting from | |||
* <code>firstRowIndex</code> | |||
*/ | |||
protected void removeRowData(int firstRowIndex, int count) { | |||
Profiler.enter("AbstractRemoteDataSource.removeRowData"); | |||
// pack the cached data | |||
for (int i = 0; i < count; i++) { | |||
Integer oldIndex = Integer.valueOf(firstRowIndex + count + i); | |||
if (rowCache.containsKey(oldIndex)) { | |||
Integer newIndex = Integer.valueOf(firstRowIndex + i); | |||
rowCache.put(newIndex, rowCache.remove(oldIndex)); | |||
} | |||
} | |||
Range removedRange = Range.withLength(firstRowIndex, count); | |||
if (removedRange.intersects(cached)) { | |||
Range[] partitions = cached.partitionWith(removedRange); | |||
Range remainsBefore = partitions[0]; | |||
Range transposedRemainsAfter = partitions[2].offsetBy(-removedRange | |||
.length()); | |||
cached = remainsBefore.combineWith(transposedRemainsAfter); | |||
} | |||
estimatedSize -= count; | |||
dataChangeHandler.dataRemoved(firstRowIndex, count); | |||
checkCacheCoverage(); | |||
Profiler.leave("AbstractRemoteDataSource.removeRowData"); | |||
} | |||
/** | |||
* Informs this data source that new data has been inserted from the server. | |||
* | |||
* @param firstRowIndex | |||
* the destination index of the new row data | |||
* @param count | |||
* the number of rows inserted | |||
*/ | |||
protected void insertRowData(int firstRowIndex, int count) { | |||
Profiler.enter("AbstractRemoteDataSource.insertRowData"); | |||
if (cached.contains(firstRowIndex)) { | |||
int oldCacheEnd = cached.getEnd(); | |||
/* | |||
* We need to invalidate the cache from the inserted row onwards, | |||
* since the cache wants to be a contiguous range. It doesn't | |||
* support holes. | |||
* | |||
* If holes were supported, we could shift the higher part of | |||
* "cached" and leave a hole the size of "count" in the middle. | |||
*/ | |||
cached = cached.splitAt(firstRowIndex)[0]; | |||
for (int i = firstRowIndex; i < oldCacheEnd; i++) { | |||
rowCache.remove(Integer.valueOf(i)); | |||
} | |||
} | |||
else if (firstRowIndex < cached.getStart()) { | |||
Range oldCached = cached; | |||
cached = cached.offsetBy(count); | |||
for (int i = 0; i < rowCache.size(); i++) { | |||
Integer oldIndex = Integer.valueOf(oldCached.getEnd() - i); | |||
Integer newIndex = Integer.valueOf(cached.getEnd() - i); | |||
rowCache.put(newIndex, rowCache.remove(oldIndex)); | |||
} | |||
} | |||
estimatedSize += count; | |||
dataChangeHandler.dataAdded(firstRowIndex, count); | |||
checkCacheCoverage(); | |||
Profiler.leave("AbstractRemoteDataSource.insertRowData"); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
/** | |||
* Callback interface used by {@link DataSource} to inform its user about | |||
* updates to the data. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface DataChangeHandler { | |||
/** | |||
* Called when the contents of the data source has changed. If the number of | |||
* rows has changed or if rows have been moved around, | |||
* {@link #dataAdded(int, int)} or {@link #dataRemoved(int, int)} should | |||
* ideally be used instead. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first changed row | |||
* @param numberOfRows | |||
* the number of changed rows | |||
*/ | |||
public void dataUpdated(int firstRowIndex, int numberOfRows); | |||
/** | |||
* Called when rows have been removed from the data source. | |||
* | |||
* @param firstRowIndex | |||
* the index that the first removed row had prior to removal | |||
* @param numberOfRows | |||
* the number of removed rows | |||
*/ | |||
public void dataRemoved(int firstRowIndex, int numberOfRows); | |||
/** | |||
* Called when the new rows have been added to the container. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first added row | |||
* @param numberOfRows | |||
* the number of added rows | |||
*/ | |||
public void dataAdded(int firstRowIndex, int numberOfRows); | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
/** | |||
* Source of data for widgets showing lazily loaded data based on indexable | |||
* items (e.g. rows) of a specified type. The data source is a lazy view into a | |||
* larger data set. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @param <T> | |||
* the row type | |||
*/ | |||
public interface DataSource<T> { | |||
/** | |||
* Informs the data source that data for the given range is needed. A data | |||
* source only has one active region at a time, so calling this method | |||
* discards the previously set range. | |||
* <p> | |||
* This method triggers lazy loading of data if necessary. The change | |||
* handler registered using {@link #setDataChangeHandler(DataChangeHandler)} | |||
* is informed when new data has been loaded. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first needed row | |||
* @param numberOfRows | |||
* the number of needed rows | |||
*/ | |||
public void ensureAvailability(int firstRowIndex, int numberOfRows); | |||
/** | |||
* Retrieves the data for the row at the given index. If the row data is not | |||
* available, returns <code>null</code>. | |||
* <p> | |||
* This method does not trigger loading of unavailable data. | |||
* {@link #ensureAvailability(int, int)} should be used to signal what data | |||
* will be needed. | |||
* | |||
* @param rowIndex | |||
* the index of the row to retrieve data for | |||
* @return data for the row; or <code>null</code> if no data is available | |||
*/ | |||
public T getRow(int rowIndex); | |||
/** | |||
* Returns the current best guess for the number of rows in the container. | |||
* | |||
* @return the current estimation of the container size | |||
*/ | |||
public int getEstimatedSize(); | |||
/** | |||
* Sets a data change handler to inform when data is updated, added or | |||
* removed. | |||
* | |||
* @param dataChangeHandler | |||
* the data change handler | |||
*/ | |||
public void setDataChangeHandler(DataChangeHandler dataChangeHandler); | |||
} |
@@ -0,0 +1,81 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
import java.util.List; | |||
import com.vaadin.client.ServerConnector; | |||
import com.vaadin.client.extensions.AbstractExtensionConnector; | |||
import com.vaadin.client.ui.grid.GridConnector; | |||
import com.vaadin.shared.data.DataProviderRpc; | |||
import com.vaadin.shared.data.DataProviderState; | |||
import com.vaadin.shared.data.DataRequestRpc; | |||
import com.vaadin.shared.ui.Connect; | |||
/** | |||
* Connects a Vaadin server-side container data source to a Grid. This is | |||
* currently implemented as an Extension hardcoded to support a specific | |||
* connector type. This will be changed once framework support for something | |||
* more flexible has been implemented. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
@Connect(com.vaadin.data.RpcDataProviderExtension.class) | |||
public class RpcDataSourceConnector extends AbstractExtensionConnector { | |||
private final AbstractRemoteDataSource<String[]> dataSource = new AbstractRemoteDataSource<String[]>() { | |||
@Override | |||
protected void requestRows(int firstRowIndex, int numberOfRows) { | |||
getRpcProxy(DataRequestRpc.class).requestRows(firstRowIndex, | |||
numberOfRows); | |||
} | |||
}; | |||
@Override | |||
protected void extend(ServerConnector target) { | |||
dataSource.setEstimatedSize(getState().containerSize); | |||
((GridConnector) target).getWidget().setDataSource(dataSource); | |||
registerRpc(DataProviderRpc.class, new DataProviderRpc() { | |||
@Override | |||
public void setRowData(int firstRow, List<String[]> rows) { | |||
dataSource.setRowData(firstRow, rows); | |||
} | |||
@Override | |||
public void removeRowData(int firstRow, int count) { | |||
dataSource.removeRowData(firstRow, count); | |||
} | |||
@Override | |||
public void insertRowData(int firstRow, int count) { | |||
dataSource.insertRowData(firstRow, count); | |||
} | |||
}); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.client.ui.AbstractConnector#getState() | |||
*/ | |||
@Override | |||
public DataProviderState getState() { | |||
return (DataProviderState) super.getState(); | |||
} | |||
} |
@@ -0,0 +1,75 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.user.client.ui.HasOneWidget; | |||
/** | |||
* A representation of a single cell. | |||
* <p> | |||
* A Cell instance will be provided to the {@link EscalatorUpdater} responsible | |||
* for rendering the cells in a certain {@link RowContainer}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface Cell extends HasOneWidget { | |||
/** | |||
* Gets the index of the row this cell is in. | |||
* | |||
* @return the index of the row this cell is in | |||
*/ | |||
public int getRow(); | |||
/** | |||
* Gets the index of the column this cell is in. | |||
* | |||
* @return the index of the column this cell is in | |||
*/ | |||
public int getColumn(); | |||
/** | |||
* Gets the root element for this cell. The {@link EscalatorUpdater} may | |||
* update the class names of the element, add inline styles and freely | |||
* modify the contents. | |||
* <p> | |||
* Avoid modifying the dimensions, positioning or colspan of the cell | |||
* element. | |||
* | |||
* @return The root element for this cell. Never <code>null</code>. | |||
*/ | |||
public Element getElement(); | |||
/** | |||
* Sets the column span of the cell. | |||
* <p> | |||
* This will overwrite any possible "colspan" attribute in the current | |||
* element (i.e. the object returned by {@link #getElement()}). This will | |||
* also handle internal bookkeeping, skip the rendering of any affected | |||
* adjacent cells, and make sure that the current cell's dimensions are | |||
* handled correctly. | |||
* | |||
* @param numberOfCells | |||
* the number of cells to span to the right, or <code>1</code> to | |||
* unset any column spans | |||
* @throws IllegalArgumentException | |||
* if <code>numberOfCells < 1</code> | |||
*/ | |||
public void setColSpan(int numberOfCells) throws IllegalArgumentException; | |||
} |
@@ -0,0 +1,146 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
/** | |||
* A representation of the columns in an instance of {@link Escalator}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see Escalator#getColumnConfiguration() | |||
*/ | |||
public interface ColumnConfiguration { | |||
/** | |||
* Removes columns at a certain index. | |||
* <p> | |||
* If any of the removed columns were frozen, the number of frozen columns | |||
* will be reduced by the number of the removed columns that were frozen. | |||
* | |||
* @param index | |||
* the index of the first column to be removed | |||
* @param numberOfColumns | |||
* the number of rows to remove, starting from the index | |||
* @throws IndexOutOfBoundsException | |||
* if any integer in the range | |||
* <code>[index..(index+numberOfColumns)]</code> is not an | |||
* existing column index. | |||
* @throws IllegalArgumentException | |||
* if <code>numberOfColumns</code> is less than 1. | |||
*/ | |||
public void removeColumns(int index, int numberOfColumns) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
/** | |||
* Adds columns at a certain index. | |||
* <p> | |||
* The new columns will be inserted between the column at the index, and the | |||
* column before (an index of 0 means that the columns are inserted at the | |||
* beginning). Therefore, the columns at the index and afterwards will be | |||
* moved to the right. | |||
* <p> | |||
* The contents of the inserted columns will be queried from the respective | |||
* cell renderers in the header, body and footer. | |||
* <p> | |||
* If there are frozen columns and the first added column is to the left of | |||
* the last frozen column, the number of frozen columns will be increased by | |||
* the number of inserted columns. | |||
* <p> | |||
* <em>Note:</em> Only the contents of the inserted columns will be | |||
* rendered. If inserting new columns affects the contents of existing | |||
* columns, {@link RowContainer#refreshRows(int, int)} needs to be called as | |||
* appropriate. | |||
* | |||
* @param index | |||
* the index of the column before which new columns are inserted, | |||
* or {@link #getColumnCount()} to add new columns at the end | |||
* @param numberOfColumns | |||
* the number of columns to insert after the <code>index</code> | |||
* @throws IndexOutOfBoundsException | |||
* if <code>index</code> is not an integer in the range | |||
* <code>[0..{@link #getColumnCount()}]</code> | |||
* @throws IllegalArgumentException | |||
* if {@code numberOfColumns} is less than 1. | |||
*/ | |||
public void insertColumns(int index, int numberOfColumns) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
/** | |||
* Returns the number of columns in the escalator. | |||
* | |||
* @return the number of columns in the escalator | |||
*/ | |||
public int getColumnCount(); | |||
/** | |||
* Sets the number of leftmost columns that are not affected by horizontal | |||
* scrolling. | |||
* | |||
* @param count | |||
* the number of columns to freeze | |||
* | |||
* @throws IllegalArgumentException | |||
* if the column count is < 0 or > the number of columns | |||
* | |||
*/ | |||
public void setFrozenColumnCount(int count) throws IllegalArgumentException; | |||
/** | |||
* Get the number of leftmost columns that are not affected by horizontal | |||
* scrolling. | |||
* | |||
* @return the number of frozen columns | |||
*/ | |||
public int getFrozenColumnCount(); | |||
/** | |||
* Sets (or unsets) an explicit width for a column. | |||
* | |||
* @param index | |||
* the index of the column for which to set a width | |||
* @param px | |||
* the number of pixels the indicated column should be, or a | |||
* negative number to let the escalator decide | |||
* @throws IllegalArgumentException | |||
* if <code>index</code> is not a valid column index | |||
*/ | |||
public void setColumnWidth(int index, int px) | |||
throws IllegalArgumentException; | |||
/** | |||
* Returns the user-defined width of a column. | |||
* | |||
* @param index | |||
* the index of the column for which to retrieve the width | |||
* @return the column's width in pixels, or a negative number if the width | |||
* is implicitly decided by the escalator | |||
* @throws IllegalArgumentException | |||
* if <code>index</code> is not a valid column index | |||
*/ | |||
public int getColumnWidth(int index) throws IllegalArgumentException; | |||
/** | |||
* Returns the actual width of a column. | |||
* | |||
* @param index | |||
* the index of the column for which to retrieve the width | |||
* @return the column's actual width in pixels | |||
* @throws IllegalArgumentException | |||
* if <code>index</code> is not a valid column index | |||
*/ | |||
public int getColumnWidthActual(int index) throws IllegalArgumentException; | |||
} |
@@ -0,0 +1,184 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.vaadin.client.ui.grid.renderers.TextRenderer; | |||
/** | |||
* Column groups are used to group columns together for adding common auxiliary | |||
* headers and footers. Columns groups are added to {@link ColumnGroupRow | |||
* ColumnGroupRows}. | |||
* | |||
* @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.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnGroup<T> { | |||
/** | |||
* The text shown in the header | |||
*/ | |||
private String header; | |||
/** | |||
* The text shown in the footer | |||
*/ | |||
private String footer; | |||
/** | |||
* Renders the header cells for the column group | |||
*/ | |||
private Renderer<String> headerRenderer = new TextRenderer(); | |||
/** | |||
* Renders the footer cells for the column group | |||
*/ | |||
private Renderer<String> footerRenderer = new TextRenderer(); | |||
/** | |||
* The columns included in the group when also accounting for subgroup | |||
* columns | |||
*/ | |||
private final List<GridColumn<?, T>> columns; | |||
/** | |||
* The grid associated with the column group | |||
*/ | |||
private final Grid<T> grid; | |||
/** | |||
* Constructs a new column group | |||
*/ | |||
ColumnGroup(Grid<T> grid, Collection<GridColumn<?, T>> columns) { | |||
if (columns == null) { | |||
throw new IllegalArgumentException( | |||
"columns cannot be null. Pass an empty list instead."); | |||
} | |||
this.grid = grid; | |||
this.columns = Collections | |||
.unmodifiableList(new ArrayList<GridColumn<?, T>>(columns)); | |||
} | |||
/** | |||
* Gets the header text. | |||
* | |||
* @return the header text | |||
*/ | |||
public String getHeaderCaption() { | |||
return header; | |||
} | |||
/** | |||
* Sets the text shown in the header. | |||
* | |||
* @param header | |||
* the header to set | |||
*/ | |||
public void setHeaderCaption(String header) { | |||
this.header = header; | |||
grid.refreshHeader(); | |||
} | |||
/** | |||
* Gets the text shown in the footer. | |||
* | |||
* @return the text in the footer | |||
*/ | |||
public String getFooterCaption() { | |||
return footer; | |||
} | |||
/** | |||
* Sets the text displayed in the footer. | |||
* | |||
* @param footer | |||
* the footer to set | |||
*/ | |||
public void setFooterCaption(String footer) { | |||
this.footer = footer; | |||
grid.refreshFooter(); | |||
} | |||
/** | |||
* Returns all column in this group. It includes the subgroups columns as | |||
* well. | |||
* | |||
* @return unmodifiable list of columns | |||
*/ | |||
public List<GridColumn<?, T>> getColumns() { | |||
return columns; | |||
} | |||
/** | |||
* Returns the renderer used for rendering the header cells | |||
* | |||
* @return a renderer that renders header cells | |||
*/ | |||
public Renderer<String> getHeaderRenderer() { | |||
return headerRenderer; | |||
} | |||
/** | |||
* Sets the renderer that renders header cells. | |||
* | |||
* @param renderer | |||
* The renderer to use for rendering header cells. Must not be | |||
* null. | |||
* @throws IllegalArgumentException | |||
* thrown when renderer is null | |||
*/ | |||
public void setHeaderRenderer(Renderer<String> renderer) { | |||
if (renderer == null) { | |||
throw new IllegalArgumentException("Renderer cannot be null."); | |||
} | |||
this.headerRenderer = renderer; | |||
grid.refreshHeader(); | |||
} | |||
/** | |||
* Returns the renderer used for rendering the footer cells | |||
* | |||
* @return a renderer that renders footer cells | |||
*/ | |||
public Renderer<String> getFooterRenderer() { | |||
return footerRenderer; | |||
} | |||
/** | |||
* Sets the renderer that renders footer cells. | |||
* | |||
* @param renderer | |||
* The renderer to use for rendering footer cells. Must not be | |||
* null. | |||
* @throws IllegalArgumentException | |||
* thrown when renderer is null | |||
*/ | |||
public void setFooterRenderer(Renderer<String> renderer) { | |||
if (renderer == null) { | |||
throw new IllegalArgumentException("Renderer cannot be null."); | |||
} | |||
this.footerRenderer = renderer; | |||
grid.refreshFooter(); | |||
} | |||
} |
@@ -0,0 +1,243 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
/** | |||
* A column group row represents an auxiliary header or footer row added to the | |||
* grid. A column group row includes column groups that group columns together. | |||
* | |||
* @param <T> | |||
* Row type | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnGroupRow<T> { | |||
/** | |||
* The column groups in this row | |||
*/ | |||
private List<ColumnGroup<T>> groups = new ArrayList<ColumnGroup<T>>(); | |||
/** | |||
* The grid associated with the column row | |||
*/ | |||
private final Grid<T> grid; | |||
/** | |||
* Is the header shown | |||
*/ | |||
private boolean headerVisible = true; | |||
/** | |||
* Is the footer shown | |||
*/ | |||
private boolean footerVisible = false; | |||
/** | |||
* Constructs a new column group row | |||
* | |||
* @param grid | |||
* Grid associated with this column | |||
* | |||
*/ | |||
ColumnGroupRow(Grid<T> grid) { | |||
this.grid = grid; | |||
} | |||
/** | |||
* Add a new group to the row by using column instances. | |||
* | |||
* @param columns | |||
* The columns that should belong to the group | |||
* @return a column group representing the collection of columns added to | |||
* the group. | |||
*/ | |||
public ColumnGroup<T> addGroup(GridColumn<?, T>... columns) | |||
throws IllegalArgumentException { | |||
for (GridColumn<?, T> column : columns) { | |||
if (isColumnGrouped(column)) { | |||
throw new IllegalArgumentException("Column " | |||
+ String.valueOf(column.getHeaderCaption()) | |||
+ " already belongs to another group."); | |||
} | |||
} | |||
validateNewGroupProperties(Arrays.asList(columns)); | |||
ColumnGroup<T> group = new ColumnGroup<T>(grid, Arrays.asList(columns)); | |||
groups.add(group); | |||
grid.refreshHeader(); | |||
grid.refreshFooter(); | |||
return group; | |||
} | |||
private void validateNewGroupProperties(Collection<GridColumn<?, T>> columns) { | |||
int rowIndex = grid.getColumnGroupRows().indexOf(this); | |||
int parentRowIndex = rowIndex - 1; | |||
// Get the parent row of this row. | |||
ColumnGroupRow<T> parentRow = null; | |||
if (parentRowIndex > -1) { | |||
parentRow = grid.getColumnGroupRows().get(parentRowIndex); | |||
} | |||
if (parentRow == null) { | |||
// A parentless row is always valid and is usually the first row | |||
// added to the grid | |||
return; | |||
} | |||
for (GridColumn<?, T> column : columns) { | |||
if (parentRow.hasColumnBeenGrouped(column)) { | |||
/* | |||
* If a property has been grouped in the parent row then all of | |||
* the properties in the parent group also needs to be included | |||
* in the child group for the groups to be valid | |||
*/ | |||
ColumnGroup parentGroup = parentRow.getGroupForColumn(column); | |||
if (!columns.containsAll(parentGroup.getColumns())) { | |||
throw new IllegalArgumentException( | |||
"Grouped properties overlaps previous grouping bounderies"); | |||
} | |||
} | |||
} | |||
} | |||
private boolean hasColumnBeenGrouped(GridColumn<?, T> column) { | |||
return getGroupForColumn(column) != null; | |||
} | |||
private ColumnGroup<T> getGroupForColumn(GridColumn<?, T> column) { | |||
for (ColumnGroup<T> group : groups) { | |||
if (group.getColumns().contains(column)) { | |||
return group; | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Add a new group to the row by using other already greated groups | |||
* | |||
* @param groups | |||
* The subgroups of the group. | |||
* @return a column group representing the collection of columns added to | |||
* the group. | |||
* | |||
*/ | |||
public ColumnGroup<T> addGroup(ColumnGroup<T>... groups) | |||
throws IllegalArgumentException { | |||
assert groups != null : "groups cannot be null"; | |||
Set<GridColumn<?, T>> columns = new HashSet<GridColumn<?, T>>(); | |||
for (ColumnGroup<T> group : groups) { | |||
columns.addAll(group.getColumns()); | |||
} | |||
validateNewGroupProperties(columns); | |||
ColumnGroup<T> group = new ColumnGroup<T>(grid, columns); | |||
this.groups.add(group); | |||
grid.refreshHeader(); | |||
grid.refreshFooter(); | |||
return group; | |||
} | |||
/** | |||
* Removes a group from the row. | |||
* | |||
* @param group | |||
* The group to remove | |||
*/ | |||
public void removeGroup(ColumnGroup<T> group) { | |||
groups.remove(group); | |||
grid.refreshHeader(); | |||
grid.refreshFooter(); | |||
} | |||
/** | |||
* Get the groups in the row | |||
* | |||
* @return unmodifiable list of groups in this row | |||
*/ | |||
public List<ColumnGroup<T>> getGroups() { | |||
return Collections.unmodifiableList(groups); | |||
} | |||
/** | |||
* Is the header visible for the row. | |||
* | |||
* @return <code>true</code> if header is visible | |||
*/ | |||
public boolean isHeaderVisible() { | |||
return headerVisible; | |||
} | |||
/** | |||
* Sets the header visible for the row. | |||
* | |||
* @param visible | |||
* should the header be shown | |||
*/ | |||
public void setHeaderVisible(boolean visible) { | |||
headerVisible = visible; | |||
grid.refreshHeader(); | |||
} | |||
/** | |||
* Is the footer visible for the row. | |||
* | |||
* @return <code>true</code> if footer is visible | |||
*/ | |||
public boolean isFooterVisible() { | |||
return footerVisible; | |||
} | |||
/** | |||
* Sets the footer visible for the row. | |||
* | |||
* @param visible | |||
* should the footer be shown | |||
*/ | |||
public void setFooterVisible(boolean visible) { | |||
footerVisible = visible; | |||
grid.refreshFooter(); | |||
} | |||
/** | |||
* Iterates all the column groups and checks if the columns alread has been | |||
* added to a group. | |||
*/ | |||
private boolean isColumnGrouped(GridColumn<?, T> column) { | |||
for (ColumnGroup<T> group : groups) { | |||
if (group.getColumns().contains(column)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
/** | |||
* A functional 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 rendered by their respective updaters. | |||
* <p> | |||
* The updater is responsible for internally handling all remote communication, | |||
* should the displayed data need to be fetched remotely. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see RowContainer#setEscalatorUpdater(EscalatorUpdater) | |||
* @see Escalator#getHeader() | |||
* @see Escalator#getBody() | |||
* @see Escalator#getFooter() | |||
*/ | |||
public interface EscalatorUpdater { | |||
/** An {@link EscalatorUpdater} that doesn't render anything. */ | |||
public static final EscalatorUpdater NULL = new EscalatorUpdater() { | |||
@Override | |||
public void updateCells(final Row row, | |||
final Iterable<Cell> cellsToUpdate) { | |||
// NOOP | |||
} | |||
}; | |||
/** | |||
* Renders a row contained in a row container. | |||
* <p> | |||
* <em>Note:</em> If rendering of cells is deferred (e.g. because | |||
* asynchronous data retrieval), this method is responsible for explicitly | |||
* displaying some placeholder data (empty content is valid). Because the | |||
* cells (and rows) in an escalator are recycled, failing to reset a cell | |||
* will lead to invalid data being displayed in the escalator. | |||
* <p> | |||
* For performance reasons, the escalator will never autonomously clear any | |||
* data in a cell. | |||
* | |||
* @param row | |||
* information about the row to update. <em>Note:</em> You should | |||
* not store nor reuse this reference | |||
* @param cellsToUpdate | |||
* a collection of cells which need to be updated. <em>Note:</em> | |||
* You should neither store nor reuse the reference to the list, | |||
* nor to the individual cells | |||
*/ | |||
public void updateCells(Row row, Iterable<Cell> cellsToUpdate); | |||
} |
@@ -0,0 +1,205 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import java.util.List; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Style.Display; | |||
import com.google.gwt.dom.client.Style.Unit; | |||
import com.google.gwt.user.client.ui.IsWidget; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ui.grid.FlyweightRow.CellIterator; | |||
/** | |||
* An internal implementation of the {@link Cell} interface. | |||
* <p> | |||
* These instances are populated into a {@link FlyweightRow} instance, and | |||
* intended to be reused when rendering cells in an escalator. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see FlyweightRow#getCells() | |||
* @see FlyweightRow#addCells(int, int) | |||
* @see FlyweightRow#removeCells(int, int) | |||
*/ | |||
class FlyweightCell implements Cell { | |||
static final String COLSPAN_ATTR = "colSpan"; | |||
private final int column; | |||
private final FlyweightRow row; | |||
private CellIterator currentIterator = null; | |||
private final Escalator escalator; | |||
public FlyweightCell(final FlyweightRow row, final int column, | |||
Escalator escalator) { | |||
this.row = row; | |||
this.column = column; | |||
this.escalator = escalator; | |||
} | |||
@Override | |||
public int getRow() { | |||
assertSetup(); | |||
return row.getRow(); | |||
} | |||
@Override | |||
public int getColumn() { | |||
assertSetup(); | |||
return column; | |||
} | |||
@Override | |||
public Element getElement() { | |||
return (Element) row.getElement().getChild(column); | |||
} | |||
void setup(final CellIterator cellIterator) { | |||
currentIterator = cellIterator; | |||
final Element e = getElement(); | |||
e.setPropertyInt(COLSPAN_ATTR, 1); | |||
e.getStyle().setWidth(row.getColumnWidth(column), Unit.PX); | |||
e.getStyle().clearDisplay(); | |||
} | |||
/** | |||
* Tear down the state of the Cell. | |||
* <p> | |||
* This is an internal check method, to prevent retrieving uninitialized | |||
* data by calling {@link #getRow()}, {@link #getColumn()} or | |||
* {@link #getElement()} at an improper time. | |||
* <p> | |||
* This should only be used with asserts (" | |||
* <code>assert flyweightCell.teardown()</code> ") so that the code is never | |||
* run when asserts aren't enabled. | |||
* | |||
* @return always <code>true</code> | |||
* @see FlyweightRow#teardown() | |||
*/ | |||
boolean teardown() { | |||
currentIterator = null; | |||
return true; | |||
} | |||
/** | |||
* Asserts that the flyweight cell has properly been set up before trying to | |||
* access any of its data. | |||
*/ | |||
private void assertSetup() { | |||
assert currentIterator != null : "FlyweightCell was not properly " | |||
+ "initialized. This is either a bug in Grid/Escalator " | |||
+ "or a Cell reference has been stored and reused " | |||
+ "inappropriately."; | |||
} | |||
@Override | |||
public void setColSpan(final int numberOfCells) { | |||
/*- | |||
* This will default to 1 if unset, as per DOM specifications: | |||
* http://www.w3.org/TR/html5/tabular-data.html#attributes-common-to-td-and-th-elements | |||
*/ | |||
final int prevColSpan = getElement().getPropertyInt(COLSPAN_ATTR); | |||
if (numberOfCells == 1 && prevColSpan == 1) { | |||
return; | |||
} | |||
getElement().setPropertyInt(COLSPAN_ATTR, numberOfCells); | |||
adjustCellWidthForSpan(numberOfCells); | |||
hideOrRevealAdjacentCellElements(numberOfCells, prevColSpan); | |||
currentIterator.setSkipNext(numberOfCells - 1); | |||
} | |||
private void adjustCellWidthForSpan(final int numberOfCells) { | |||
final int cellsToTheRight = currentIterator.rawPeekNext( | |||
numberOfCells - 1).size(); | |||
final int selfWidth = row.getColumnWidth(column); | |||
int widthsOfColumnsToTheRight = 0; | |||
for (int i = 0; i < cellsToTheRight; i++) { | |||
widthsOfColumnsToTheRight += row.getColumnWidth(column + i + 1); | |||
} | |||
getElement().getStyle().setWidth(selfWidth + widthsOfColumnsToTheRight, | |||
Unit.PX); | |||
} | |||
private void hideOrRevealAdjacentCellElements(final int numberOfCells, | |||
final int prevColSpan) { | |||
final int affectedCellsNumber = Math.max(prevColSpan, numberOfCells); | |||
final List<FlyweightCell> affectedCells = currentIterator | |||
.rawPeekNext(affectedCellsNumber - 1); | |||
if (prevColSpan < numberOfCells) { | |||
for (int i = 0; i < affectedCells.size(); i++) { | |||
affectedCells.get(prevColSpan + i - 1).getElement().getStyle() | |||
.setDisplay(Display.NONE); | |||
} | |||
} else if (prevColSpan > numberOfCells) { | |||
for (int i = 0; i < affectedCells.size(); i++) { | |||
affectedCells.get(numberOfCells + i - 1).getElement() | |||
.getStyle().clearDisplay(); | |||
} | |||
} | |||
} | |||
@Override | |||
public Widget getWidget() { | |||
return Escalator.getWidgetFromCell(getElement()); | |||
} | |||
@Override | |||
public void setWidget(Widget widget) { | |||
Widget oldWidget = getWidget(); | |||
// Validate | |||
if (oldWidget == widget) { | |||
return; | |||
} | |||
// Detach old child. | |||
if (oldWidget != null) { | |||
// Orphan. | |||
Escalator.setParent(oldWidget, null); | |||
// Physical detach. | |||
getElement().removeChild(oldWidget.getElement()); | |||
} | |||
// Remove any previous text nodes from previous | |||
// setInnerText/setInnerHTML | |||
getElement().removeAllChildren(); | |||
// Attach new child. | |||
if (widget != null) { | |||
// Detach new child from old parent. | |||
widget.removeFromParent(); | |||
// Physical attach. | |||
getElement().appendChild(widget.getElement()); | |||
Escalator.setParent(widget, escalator); | |||
} | |||
} | |||
@Override | |||
public void setWidget(IsWidget w) { | |||
setWidget(Widget.asWidgetOrNull(w)); | |||
} | |||
} |
@@ -0,0 +1,217 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Node; | |||
/** | |||
* An internal implementation of the {@link Row} interface. | |||
* <p> | |||
* There is only one instance per Escalator. This is designed to be re-used when | |||
* rendering rows. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see Escalator.AbstractRowContainer#refreshRow(Node, int) | |||
*/ | |||
class FlyweightRow implements Row { | |||
static class CellIterator implements Iterator<Cell> { | |||
/** A defensive copy of the cells in the current row. */ | |||
private final ArrayList<FlyweightCell> cells; | |||
private int cursor = 0; | |||
private int skipNext = 0; | |||
public CellIterator(final Collection<FlyweightCell> cells) { | |||
this.cells = new ArrayList<FlyweightCell>(cells); | |||
} | |||
@Override | |||
public boolean hasNext() { | |||
return cursor + skipNext < cells.size(); | |||
} | |||
@Override | |||
public FlyweightCell next() { | |||
// if we needed to skip some cells since the last invocation. | |||
for (int i = 0; i < skipNext; i++) { | |||
cells.remove(cursor); | |||
} | |||
skipNext = 0; | |||
final FlyweightCell cell = cells.get(cursor++); | |||
cell.setup(this); | |||
return cell; | |||
} | |||
@Override | |||
public void remove() { | |||
throw new UnsupportedOperationException( | |||
"Cannot remove cells via iterator"); | |||
} | |||
/** | |||
* Sets the number of cells to skip when {@link #next()} is called the | |||
* next time. Cell hiding is also handled eagerly in this method. | |||
* | |||
* @param colspan | |||
* the number of cells to skip on next invocation of | |||
* {@link #next()} | |||
*/ | |||
public void setSkipNext(final int colspan) { | |||
assert colspan > 0 : "Number of cells didn't make sense: " | |||
+ colspan; | |||
skipNext = colspan; | |||
} | |||
/** | |||
* Gets the next <code>n</code> cells in the iterator, ignoring any | |||
* possibly spanned cells. | |||
* | |||
* @param n | |||
* the number of next cells to retrieve | |||
* @return A list of next <code>n</code> cells, or less if there aren't | |||
* enough cells to retrieve | |||
*/ | |||
public List<FlyweightCell> rawPeekNext(final int n) { | |||
final int from = Math.min(cursor, cells.size()); | |||
final int to = Math.min(cursor + n, cells.size()); | |||
return cells.subList(from, to); | |||
} | |||
} | |||
private static final int BLANK = Integer.MIN_VALUE; | |||
private int row; | |||
private Element element; | |||
private int[] columnWidths = null; | |||
private final Escalator escalator; | |||
private final List<FlyweightCell> cells = new ArrayList<FlyweightCell>(); | |||
public FlyweightRow(final Escalator escalator) { | |||
this.escalator = escalator; | |||
} | |||
@Override | |||
public Escalator getEscalator() { | |||
return escalator; | |||
} | |||
void setup(final Element e, final int row, int[] columnWidths) { | |||
element = e; | |||
this.row = row; | |||
this.columnWidths = columnWidths; | |||
} | |||
/** | |||
* Tear down the state of the Row. | |||
* <p> | |||
* This is an internal check method, to prevent retrieving uninitialized | |||
* data by calling {@link #getRow()}, {@link #getElement()} or | |||
* {@link #getCells()} at an improper time. | |||
* <p> | |||
* This should only be used with asserts (" | |||
* <code>assert flyweightRow.teardown()</code> ") so that the code is never | |||
* run when asserts aren't enabled. | |||
* | |||
* @return always <code>true</code> | |||
*/ | |||
boolean teardown() { | |||
element = null; | |||
row = BLANK; | |||
columnWidths = null; | |||
for (final FlyweightCell cell : cells) { | |||
assert cell.teardown(); | |||
} | |||
return true; | |||
} | |||
@Override | |||
public int getRow() { | |||
assertSetup(); | |||
return row; | |||
} | |||
@Override | |||
public Element getElement() { | |||
assertSetup(); | |||
return element; | |||
} | |||
void addCells(final int index, final int numberOfColumns) { | |||
for (int i = 0; i < numberOfColumns; i++) { | |||
final int col = index + i; | |||
cells.add(col, new FlyweightCell(this, col, escalator)); | |||
} | |||
updateRestOfCells(index + numberOfColumns); | |||
} | |||
void removeCells(final int index, final int numberOfColumns) { | |||
for (int i = 0; i < numberOfColumns; i++) { | |||
cells.remove(index); | |||
} | |||
updateRestOfCells(index); | |||
} | |||
private void updateRestOfCells(final int startPos) { | |||
// update the column number for the cells to the right | |||
for (int col = startPos; col < cells.size(); col++) { | |||
cells.set(col, new FlyweightCell(this, col, escalator)); | |||
} | |||
} | |||
/** | |||
* Get flyweight cells for the client code to render. | |||
* | |||
* @return a list of {@link FlyweightCell FlyweightCells}. They are | |||
* generified into {@link Cell Cells}, because Java's generics | |||
* system isn't expressive enough. | |||
* @see #setup(Element, int) | |||
* @see #teardown() | |||
*/ | |||
Iterable<Cell> getCells() { | |||
assertSetup(); | |||
return new Iterable<Cell>() { | |||
@Override | |||
public Iterator<Cell> iterator() { | |||
return new CellIterator(cells); | |||
} | |||
}; | |||
} | |||
/** | |||
* Asserts that the flyweight row has properly been set up before trying to | |||
* access any of its data. | |||
*/ | |||
private void assertSetup() { | |||
assert element != null && row != BLANK && columnWidths != null : "Flyweight row was not " | |||
+ "properly initialized. Make sure the setup-method is " | |||
+ "called before retrieving data. This is either a bug " | |||
+ "in Escalator, or the instance of the flyweight row " | |||
+ "has been stored and accessed."; | |||
} | |||
int getColumnWidth(int column) { | |||
assertSetup(); | |||
return columnWidths[column]; | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
/** | |||
* Represents a column in the {@link Grid}. | |||
* | |||
* @param <C> | |||
* The column type | |||
* | |||
* @param <T> | |||
* The row type | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public abstract class GridColumn<C, T> extends Grid.AbstractGridColumn<C, T> { | |||
/* | |||
* This class is a convenience class so you do not have to reference | |||
* Grid.AbstractGridColumn in your production code. The real implementation | |||
* should be in the abstract class. | |||
*/ | |||
/** | |||
* Constructs a new column. | |||
*/ | |||
public GridColumn() { | |||
super(); | |||
} | |||
/** | |||
* Constructs a new column with a custom renderer. | |||
* | |||
* @param renderer | |||
* The renderer to use for rendering the cells | |||
*/ | |||
public GridColumn(Renderer<C> renderer) { | |||
super(renderer); | |||
} | |||
} |
@@ -0,0 +1,277 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.HashSet; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import com.vaadin.client.communication.StateChangeEvent; | |||
import com.vaadin.client.ui.AbstractComponentConnector; | |||
import com.vaadin.shared.ui.Connect; | |||
import com.vaadin.shared.ui.grid.ColumnGroupRowState; | |||
import com.vaadin.shared.ui.grid.ColumnGroupState; | |||
import com.vaadin.shared.ui.grid.GridClientRpc; | |||
import com.vaadin.shared.ui.grid.GridColumnState; | |||
import com.vaadin.shared.ui.grid.GridServerRpc; | |||
import com.vaadin.shared.ui.grid.GridState; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
/** | |||
* Connects the client side {@link Grid} widget with the server side | |||
* {@link com.vaadin.ui.components.grid.Grid} component. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
@Connect(com.vaadin.ui.components.grid.Grid.class) | |||
public class GridConnector extends AbstractComponentConnector { | |||
/** | |||
* Custom implementation of the custom grid column using a String[] to | |||
* represent the cell value and String as a column type. | |||
*/ | |||
private class CustomGridColumn extends GridColumn<String, String[]> { | |||
private final int columnIndex; | |||
public CustomGridColumn(int columnIndex) { | |||
this.columnIndex = columnIndex; | |||
} | |||
@Override | |||
public String getValue(String[] obj) { | |||
return obj[columnIndex]; | |||
} | |||
} | |||
/** | |||
* Maps a generated column id to a grid column instance | |||
*/ | |||
private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>(); | |||
@Override | |||
protected Grid<String[]> createWidget() { | |||
// FIXME Shouldn't be needed after #12873 has been fixed. | |||
return new Grid<String[]>(); | |||
} | |||
@Override | |||
@SuppressWarnings("unchecked") | |||
public Grid<String[]> getWidget() { | |||
return (Grid<String[]>) super.getWidget(); | |||
} | |||
@Override | |||
public GridState getState() { | |||
return (GridState) super.getState(); | |||
} | |||
@Override | |||
protected void init() { | |||
super.init(); | |||
getWidget().addRowVisibilityChangeHandler( | |||
new RowVisibilityChangeHandler() { | |||
@Override | |||
public void onRowVisibilityChange( | |||
RowVisibilityChangeEvent event) { | |||
getRpcProxy(GridServerRpc.class).setVisibleRows( | |||
event.getFirstVisibleRow(), | |||
event.getVisibleRowCount()); | |||
} | |||
}); | |||
registerRpc(GridClientRpc.class, new GridClientRpc() { | |||
@Override | |||
public void scrollToStart() { | |||
getWidget().scrollToStart(); | |||
} | |||
@Override | |||
public void scrollToEnd() { | |||
getWidget().scrollToEnd(); | |||
} | |||
@Override | |||
public void scrollToRow(int row, ScrollDestination destination) { | |||
getWidget().scrollToRow(row, destination); | |||
} | |||
}); | |||
} | |||
@Override | |||
public void onStateChanged(StateChangeEvent stateChangeEvent) { | |||
super.onStateChanged(stateChangeEvent); | |||
// Column updates | |||
if (stateChangeEvent.hasPropertyChanged("columns")) { | |||
int totalColumns = getState().columns.size(); | |||
// Remove old columns | |||
purgeRemovedColumns(); | |||
int currentColumns = getWidget().getColumnCount(); | |||
// Add new columns | |||
for (int columnIndex = currentColumns; columnIndex < totalColumns; columnIndex++) { | |||
addColumnFromStateChangeEvent(columnIndex); | |||
} | |||
// Update old columns | |||
for (int columnIndex = 0; columnIndex < currentColumns; columnIndex++) { | |||
// FIXME Currently updating all column header / footers when a | |||
// change in made in one column. When the framework supports | |||
// quering a specific item in a list then it should do so here. | |||
updateColumnFromStateChangeEvent(columnIndex); | |||
} | |||
} | |||
// Header | |||
if (stateChangeEvent.hasPropertyChanged("columnHeadersVisible")) { | |||
getWidget() | |||
.setColumnHeadersVisible(getState().columnHeadersVisible); | |||
} | |||
// Footer | |||
if (stateChangeEvent.hasPropertyChanged("columnFootersVisible")) { | |||
getWidget() | |||
.setColumnFootersVisible(getState().columnFootersVisible); | |||
} | |||
// Column row groups | |||
if (stateChangeEvent.hasPropertyChanged("columnGroupRows")) { | |||
updateColumnGroupsFromStateChangeEvent(); | |||
} | |||
if (stateChangeEvent.hasPropertyChanged("lastFrozenColumnId")) { | |||
String frozenColId = getState().lastFrozenColumnId; | |||
if (frozenColId != null) { | |||
CustomGridColumn column = columnIdToColumn.get(frozenColId); | |||
assert column != null : "Column to be frozen could not be found (id:" | |||
+ frozenColId + ")"; | |||
getWidget().setLastFrozenColumn(column); | |||
} else { | |||
getWidget().setLastFrozenColumn(null); | |||
} | |||
} | |||
} | |||
/** | |||
* Updates a column from a state change event. | |||
* | |||
* @param columnIndex | |||
* The index of the column to update | |||
*/ | |||
private void updateColumnFromStateChangeEvent(int columnIndex) { | |||
GridColumn<?, String[]> column = getWidget().getColumn(columnIndex); | |||
GridColumnState columnState = getState().columns.get(columnIndex); | |||
updateColumnFromState(column, columnState); | |||
} | |||
/** | |||
* Adds a new column to the grid widget from a state change event | |||
* | |||
* @param columnIndex | |||
* The index of the column, according to how it | |||
*/ | |||
private void addColumnFromStateChangeEvent(int columnIndex) { | |||
GridColumnState state = getState().columns.get(columnIndex); | |||
CustomGridColumn column = new CustomGridColumn(columnIndex); | |||
updateColumnFromState(column, state); | |||
columnIdToColumn.put(state.id, column); | |||
getWidget().addColumn(column, columnIndex); | |||
} | |||
/** | |||
* Updates the column values from a state | |||
* | |||
* @param column | |||
* The column to update | |||
* @param state | |||
* The state to get the data from | |||
*/ | |||
private static void updateColumnFromState(GridColumn<?, String[]> column, | |||
GridColumnState state) { | |||
column.setVisible(state.visible); | |||
column.setHeaderCaption(state.header); | |||
column.setFooterCaption(state.footer); | |||
column.setWidth(state.width); | |||
} | |||
/** | |||
* Removes any orphan columns that has been removed from the state from the | |||
* grid | |||
*/ | |||
private void purgeRemovedColumns() { | |||
// Get columns still registered in the state | |||
Set<String> columnsInState = new HashSet<String>(); | |||
for (GridColumnState columnState : getState().columns) { | |||
columnsInState.add(columnState.id); | |||
} | |||
// Remove column no longer in state | |||
Iterator<String> columnIdIterator = columnIdToColumn.keySet() | |||
.iterator(); | |||
while (columnIdIterator.hasNext()) { | |||
String id = columnIdIterator.next(); | |||
if (!columnsInState.contains(id)) { | |||
CustomGridColumn column = columnIdToColumn.get(id); | |||
columnIdIterator.remove(); | |||
getWidget().removeColumn(column); | |||
} | |||
} | |||
} | |||
/** | |||
* Updates the column groups from a state change | |||
*/ | |||
private void updateColumnGroupsFromStateChangeEvent() { | |||
// FIXME When something changes the header/footer rows will be | |||
// re-created. At some point we should optimize this so partial updates | |||
// can be made on the header/footer. | |||
for (ColumnGroupRow<String[]> row : getWidget().getColumnGroupRows()) { | |||
getWidget().removeColumnGroupRow(row); | |||
} | |||
for (ColumnGroupRowState rowState : getState().columnGroupRows) { | |||
ColumnGroupRow<String[]> row = getWidget().addColumnGroupRow(); | |||
row.setFooterVisible(rowState.footerVisible); | |||
row.setHeaderVisible(rowState.headerVisible); | |||
for (ColumnGroupState groupState : rowState.groups) { | |||
List<GridColumn<String, String[]>> columns = new ArrayList<GridColumn<String, String[]>>(); | |||
for (String columnId : groupState.columns) { | |||
CustomGridColumn column = columnIdToColumn.get(columnId); | |||
columns.add(column); | |||
} | |||
ColumnGroup<String[]> group = row.addGroup(columns | |||
.toArray(new GridColumn[columns.size()])); | |||
group.setFooterCaption(groupState.footer); | |||
group.setHeaderCaption(groupState.header); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,118 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Style.Unit; | |||
/** | |||
* A functional interface that can be used for positioning elements in the DOM. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
interface PositionFunction { | |||
/** | |||
* A position function using "transform: translate3d(x,y,z)" to position | |||
* elements in the DOM. | |||
*/ | |||
public static class Translate3DPosition implements PositionFunction { | |||
@Override | |||
public void set(Element e, double x, double y) { | |||
e.getStyle().setProperty("transform", | |||
"translate3d(" + x + "px, " + y + "px, 0)"); | |||
} | |||
@Override | |||
public void reset(Element e) { | |||
e.getStyle().clearProperty("transform"); | |||
} | |||
} | |||
/** | |||
* A position function using "transform: translate(x,y)" to position | |||
* elements in the DOM. | |||
*/ | |||
public static class TranslatePosition implements PositionFunction { | |||
@Override | |||
public void set(Element e, double x, double y) { | |||
e.getStyle().setProperty("transform", | |||
"translate(" + x + "px," + y + "px)"); | |||
} | |||
@Override | |||
public void reset(Element e) { | |||
e.getStyle().clearProperty("transform"); | |||
} | |||
} | |||
/** | |||
* A position function using "-webkit-transform: translate3d(x,y,z)" to | |||
* position elements in the DOM. | |||
*/ | |||
public static class WebkitTranslate3DPosition implements PositionFunction { | |||
@Override | |||
public void set(Element e, double x, double y) { | |||
e.getStyle().setProperty("webkitTransform", | |||
"translate3d(" + x + "px," + y + "px,0)"); | |||
} | |||
@Override | |||
public void reset(Element e) { | |||
e.getStyle().clearProperty("webkitTransform"); | |||
} | |||
} | |||
/** | |||
* A position function using "left: x" and "top: y" to position elements in | |||
* the DOM. | |||
*/ | |||
public static class AbsolutePosition implements PositionFunction { | |||
@Override | |||
public void set(Element e, double x, double y) { | |||
e.getStyle().setLeft(x, Unit.PX); | |||
e.getStyle().setTop(y, Unit.PX); | |||
} | |||
@Override | |||
public void reset(Element e) { | |||
e.getStyle().clearLeft(); | |||
e.getStyle().clearTop(); | |||
} | |||
} | |||
/** | |||
* Position an element in an (x,y) coordinate system in the DOM. | |||
* | |||
* @param e | |||
* the element to position. Never <code>null</code>. | |||
* @param x | |||
* the x coordinate, in pixels | |||
* @param y | |||
* the y coordinate, in pixels | |||
*/ | |||
void set(Element e, double x, double y); | |||
/** | |||
* Resets any previously applied positioning, clearing the used style | |||
* attributes. | |||
* | |||
* @param e | |||
* the element for which to reset the positioning | |||
*/ | |||
void reset(Element e); | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
/** | |||
* Renderer for rending a value <T> into cell. | |||
* <p> | |||
* You can add a renderer to any column by overring the | |||
* {@link GridColumn#getRenderer()} method and returning your own renderer. You | |||
* can retrieve the cell element using {@link Cell#getElement()}. | |||
* | |||
* @param <T> | |||
* The column type | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface Renderer<T> { | |||
/** | |||
* Called whenever the {@link Grid} updates a cell | |||
* | |||
* @param cell | |||
* The cell that gets updated | |||
* | |||
* @param data | |||
* The column data object | |||
*/ | |||
public void renderCell(Cell cell, T data); | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import com.google.gwt.dom.client.Element; | |||
/** | |||
* A representation of a row in an {@link Escalator}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface Row { | |||
/** | |||
* Gets the escalator containing the row. | |||
* | |||
* @return the escalator containing the row | |||
*/ | |||
public Escalator getEscalator(); | |||
/** | |||
* Gets the row index. | |||
* | |||
* @return the row index | |||
*/ | |||
public int getRow(); | |||
/** | |||
* Gets the root element for this row. | |||
* <p> | |||
* The {@link EscalatorUpdater} may update the class names of the element | |||
* and add inline styles, but may not modify the contained DOM structure. | |||
* <p> | |||
* If you wish to modify the cells within this row element, access them via | |||
* the <code>List<{@link Cell}></code> objects passed in to | |||
* {@code EscalatorUpdater.updateCells(Row, List)} | |||
* | |||
* @return the root element of the row | |||
*/ | |||
public Element getElement(); | |||
} |
@@ -0,0 +1,156 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
/** | |||
* A representation of the rows in each of the sections (header, body and | |||
* footer) in an {@link Escalator}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see Escalator#getHeader() | |||
* @see Escalator#getBody() | |||
* @see Escalator#getFooter() | |||
*/ | |||
public interface RowContainer { | |||
/** | |||
* An arbitrary pixel height of a row, before any autodetection for the row | |||
* height has been made. | |||
* */ | |||
public static final int INITIAL_DEFAULT_ROW_HEIGHT = 20; | |||
/** | |||
* Returns the current {@link EscalatorUpdater} used to render cells. | |||
* | |||
* @return the current escalator updater | |||
*/ | |||
public EscalatorUpdater getEscalatorUpdater(); | |||
/** | |||
* Sets the {@link EscalatorUpdater} to use when displaying data in the | |||
* escalator. | |||
* | |||
* @param escalatorUpdater | |||
* the escalator updater to use to render cells. May not be | |||
* <code>null</code> | |||
* @throws IllegalArgumentException | |||
* if {@code cellRenderer} is <code>null</code> | |||
* @see EscalatorUpdater#NULL | |||
*/ | |||
public void setEscalatorUpdater(EscalatorUpdater escalatorUpdater) | |||
throws IllegalArgumentException; | |||
/** | |||
* Removes rows at a certain index in the current row container. | |||
* | |||
* @param index | |||
* the index of the first row to be removed | |||
* @param numberOfRows | |||
* the number of rows to remove, starting from the index | |||
* @throws IndexOutOfBoundsException | |||
* if any integer number in the range | |||
* <code>[index..(index+numberOfRows)]</code> is not an existing | |||
* row index | |||
* @throws IllegalArgumentException | |||
* if {@code numberOfRows} is less than 1. | |||
*/ | |||
public void removeRows(int index, int numberOfRows) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
/** | |||
* Adds rows at a certain index in this row container. | |||
* <p> | |||
* The new rows will be inserted between the row at the index, and the row | |||
* before (an index of 0 means that the rows are inserted at the beginning). | |||
* Therefore, the rows currently at the index and afterwards will be moved | |||
* downwards. | |||
* <p> | |||
* The contents of the inserted rows will subsequently be queried from the | |||
* escalator updater. | |||
* <p> | |||
* <em>Note:</em> Only the contents of the inserted rows will be rendered. | |||
* If inserting new rows affects the contents of existing rows, | |||
* {@link #refreshRows(int, int)} needs to be called for those rows | |||
* separately. | |||
* | |||
* @param index | |||
* the index of the row before which new rows are inserted, or | |||
* {@link #getRowCount()} to add rows at the end | |||
* @param numberOfRows | |||
* the number of rows to insert after the <code>index</code> | |||
* @see #setEscalatorUpdater(EscalatorUpdater) | |||
* @throws IndexOutOfBoundsException | |||
* if <code>index</code> is not an integer in the range | |||
* <code>[0..{@link #getRowCount()}]</code> | |||
* @throws IllegalArgumentException | |||
* if {@code numberOfRows} is less than 1. | |||
*/ | |||
public void insertRows(int index, int numberOfRows) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
/** | |||
* Refreshes a range of rows in the current row container. | |||
* <p> | |||
* The data for the refreshed rows are queried from the current cell | |||
* renderer. | |||
* | |||
* @param index | |||
* the index of the first row that will be updated | |||
* @param numberOfRows | |||
* the number of rows to update, starting from the index | |||
* @see #setEscalatorUpdater(EscalatorUpdater) | |||
* @throws IndexOutOfBoundsException | |||
* if any integer number in the range | |||
* <code>[index..(index+numberOfColumns)]</code> is not an | |||
* existing column index. | |||
* @throws IllegalArgumentException | |||
* if {@code numberOfRows} is less than 1. | |||
*/ | |||
public void refreshRows(int index, int numberOfRows) | |||
throws IndexOutOfBoundsException, IllegalArgumentException; | |||
/** | |||
* Gets the number of rows in the current row container. | |||
* | |||
* @return the number of rows in the current row container | |||
*/ | |||
public int getRowCount(); | |||
/** | |||
* The default height of the rows in this RowContainer. | |||
* | |||
* @param px | |||
* the default height in pixels of the rows in this RowContainer | |||
* @throws IllegalArgumentException | |||
* if <code>px < 1</code> | |||
* @see #getDefaultRowHeight() | |||
*/ | |||
public void setDefaultRowHeight(int px) throws IllegalArgumentException; | |||
/** | |||
* Returns the default height of the rows in this RowContainer. | |||
* <p> | |||
* This value will be equal to {@link #INITIAL_DEFAULT_ROW_HEIGHT} if the | |||
* {@link Escalator} has not yet had a chance to autodetect the row height, | |||
* or no explicit value has yet given via {@link #setDefaultRowHeight(int)} | |||
* | |||
* @return the default height of the rows in this RowContainer, in pixels | |||
* @see #setDefaultRowHeight(int) | |||
*/ | |||
public int getDefaultRowHeight(); | |||
} |
@@ -0,0 +1,90 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import com.google.gwt.event.shared.GwtEvent; | |||
/** | |||
* Event fired when the range of visible rows changes e.g. because of scrolling. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class RowVisibilityChangeEvent extends | |||
GwtEvent<RowVisibilityChangeHandler> { | |||
/** | |||
* The type of this event. | |||
*/ | |||
public static final Type<RowVisibilityChangeHandler> TYPE = new Type<RowVisibilityChangeHandler>(); | |||
private final int firstVisibleRow; | |||
private final int visibleRowCount; | |||
/** | |||
* Creates a new row visibility change event | |||
* | |||
* @param firstVisibleRow | |||
* the index of the first visible row | |||
* @param visibleRowCount | |||
* the number of visible rows | |||
*/ | |||
public RowVisibilityChangeEvent(int firstVisibleRow, int visibleRowCount) { | |||
this.firstVisibleRow = firstVisibleRow; | |||
this.visibleRowCount = visibleRowCount; | |||
} | |||
/** | |||
* Gets the index of the first row that is at least partially visible. | |||
* | |||
* @return the index of the first visible row | |||
*/ | |||
public int getFirstVisibleRow() { | |||
return firstVisibleRow; | |||
} | |||
/** | |||
* Gets the number of at least partially visible rows. | |||
* | |||
* @return the number of visible rows | |||
*/ | |||
public int getVisibleRowCount() { | |||
return visibleRowCount; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.google.gwt.event.shared.GwtEvent#getAssociatedType() | |||
*/ | |||
@Override | |||
public Type<RowVisibilityChangeHandler> getAssociatedType() { | |||
return TYPE; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.google.gwt.event.shared.GwtEvent#dispatch(com.google.gwt.event.shared | |||
* .EventHandler) | |||
*/ | |||
@Override | |||
protected void dispatch(RowVisibilityChangeHandler handler) { | |||
handler.onRowVisibilityChange(this); | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import com.google.gwt.event.shared.EventHandler; | |||
/** | |||
* Event handler that gets notified when the range of visible rows changes e.g. | |||
* because of scrolling. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface RowVisibilityChangeHandler extends EventHandler { | |||
/** | |||
* Called when the range of visible rows changes e.g. because of scrolling. | |||
* | |||
* @param event | |||
* the row visibility change event describing the change | |||
*/ | |||
void onRowVisibilityChange(RowVisibilityChangeEvent event); | |||
} |
@@ -0,0 +1,403 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Style.Overflow; | |||
import com.google.gwt.dom.client.Style.Unit; | |||
import com.google.gwt.user.client.DOM; | |||
/** | |||
* An element-like bundle representing a configurable and visual scrollbar in | |||
* one axis. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see VerticalScrollbarBundle | |||
* @see HorizontalScrollbarBundle | |||
*/ | |||
abstract class ScrollbarBundle { | |||
/** | |||
* The pixel size for OSX's invisible scrollbars. | |||
* <p> | |||
* Touch devices don't show a scrollbar at all, so the scrollbar size is | |||
* irrelevant in their case. There doesn't seem to be any other popular | |||
* platforms that has scrollbars similar to OSX. Thus, this behavior is | |||
* tailored for OSX only, until additional platforms start behaving this | |||
* way. | |||
*/ | |||
private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13; | |||
/** | |||
* A representation of a single vertical scrollbar. | |||
* | |||
* @see VerticalScrollbarBundle#getElement() | |||
*/ | |||
final static class VerticalScrollbarBundle extends ScrollbarBundle { | |||
@Override | |||
public void setStylePrimaryName(String primaryStyleName) { | |||
super.setStylePrimaryName(primaryStyleName); | |||
root.addClassName(primaryStyleName + "-scroller-vertical"); | |||
} | |||
@Override | |||
protected void internalSetScrollPos(int px) { | |||
root.setScrollTop(px); | |||
} | |||
@Override | |||
protected int internalGetScrollPos() { | |||
return root.getScrollTop(); | |||
} | |||
@Override | |||
protected void internalSetScrollSize(int px) { | |||
scrollSizeElement.getStyle().setHeight(px, Unit.PX); | |||
} | |||
@Override | |||
public int getScrollSize() { | |||
return scrollSizeElement.getOffsetHeight(); | |||
} | |||
@Override | |||
protected void internalSetOffsetSize(int px) { | |||
root.getStyle().setHeight(px, Unit.PX); | |||
} | |||
@Override | |||
public int getOffsetSize() { | |||
return root.getOffsetHeight(); | |||
} | |||
@Override | |||
protected void internalSetScrollbarThickness(int px) { | |||
root.getStyle().setWidth(px, Unit.PX); | |||
scrollSizeElement.getStyle().setWidth(px, Unit.PX); | |||
} | |||
@Override | |||
protected int internalGetScrollbarThickness() { | |||
return root.getOffsetWidth(); | |||
} | |||
@Override | |||
protected void forceScrollbar(boolean enable) { | |||
if (enable) { | |||
root.getStyle().setOverflowY(Overflow.SCROLL); | |||
} else { | |||
root.getStyle().clearOverflowY(); | |||
} | |||
} | |||
} | |||
/** | |||
* A representation of a single horizontal scrollbar. | |||
* | |||
* @see HorizontalScrollbarBundle#getElement() | |||
*/ | |||
final static class HorizontalScrollbarBundle extends ScrollbarBundle { | |||
@Override | |||
public void setStylePrimaryName(String primaryStyleName) { | |||
super.setStylePrimaryName(primaryStyleName); | |||
root.addClassName(primaryStyleName + "-scroller-horizontal"); | |||
} | |||
@Override | |||
protected void internalSetScrollPos(int px) { | |||
root.setScrollLeft(px); | |||
} | |||
@Override | |||
protected int internalGetScrollPos() { | |||
return root.getScrollLeft(); | |||
} | |||
@Override | |||
protected void internalSetScrollSize(int px) { | |||
scrollSizeElement.getStyle().setWidth(px, Unit.PX); | |||
} | |||
@Override | |||
public int getScrollSize() { | |||
return scrollSizeElement.getOffsetWidth(); | |||
} | |||
@Override | |||
protected void internalSetOffsetSize(int px) { | |||
root.getStyle().setWidth(px, Unit.PX); | |||
} | |||
@Override | |||
public int getOffsetSize() { | |||
return root.getOffsetWidth(); | |||
} | |||
@Override | |||
protected void internalSetScrollbarThickness(int px) { | |||
root.getStyle().setHeight(px, Unit.PX); | |||
scrollSizeElement.getStyle().setHeight(px, Unit.PX); | |||
} | |||
@Override | |||
protected int internalGetScrollbarThickness() { | |||
return root.getOffsetHeight(); | |||
} | |||
@Override | |||
protected void forceScrollbar(boolean enable) { | |||
if (enable) { | |||
root.getStyle().setOverflowX(Overflow.SCROLL); | |||
} else { | |||
root.getStyle().clearOverflowX(); | |||
} | |||
} | |||
} | |||
protected final Element root = DOM.createDiv(); | |||
protected final Element scrollSizeElement = DOM.createDiv(); | |||
protected boolean isInvisibleScrollbar = false; | |||
private int scrollPos = 0; | |||
private int maxScrollPos = 0; | |||
private ScrollbarBundle() { | |||
root.appendChild(scrollSizeElement); | |||
} | |||
/** | |||
* Sets the primary style name | |||
* | |||
* @param primaryStyleName | |||
* The primary style name to use | |||
*/ | |||
public void setStylePrimaryName(String primaryStyleName) { | |||
root.setClassName(primaryStyleName + "-scroller"); | |||
} | |||
/** | |||
* Gets the root element of this scrollbar-composition. | |||
* | |||
* @return the root element | |||
*/ | |||
public final Element getElement() { | |||
return root; | |||
} | |||
/** | |||
* Modifies the scroll position of this scrollbar by a number of pixels | |||
* | |||
* @param delta | |||
* the delta in pixels to change the scroll position by | |||
*/ | |||
public final void setScrollPosByDelta(int delta) { | |||
if (delta != 0) { | |||
setScrollPos(getScrollPos() + delta); | |||
} | |||
} | |||
/** | |||
* Modifies {@link #root root's} dimensions in the axis the scrollbar is | |||
* representing. | |||
* | |||
* @param px | |||
* the new size of {@link #root} in the dimension this scrollbar | |||
* is representing | |||
*/ | |||
protected abstract void internalSetOffsetSize(int px); | |||
/** | |||
* Sets the length of the scrollbar. | |||
* | |||
* @param px | |||
* the length of the scrollbar in pixels | |||
*/ | |||
public final void setOffsetSize(int px) { | |||
internalSetOffsetSize(px); | |||
forceScrollbar(showsScrollHandle()); | |||
recalculateMaxScrollPos(); | |||
} | |||
/** | |||
* Force the scrollbar to be visible with CSS. In practice, this means to | |||
* set either <code>overflow-x</code> or <code>overflow-y</code> to " | |||
* <code>scroll</code>" in the scrollbar's direction. | |||
* <p> | |||
* This is an IE8 workaround, since it doesn't always show scrollbars with | |||
* <code>overflow: auto</code> enabled. | |||
*/ | |||
protected abstract void forceScrollbar(boolean enable); | |||
/** | |||
* Gets the length of the scrollbar | |||
* | |||
* @return the length of the scrollbar in pixels | |||
*/ | |||
public abstract int getOffsetSize(); | |||
/** | |||
* Sets the scroll position of the scrollbar in the axis the scrollbar is | |||
* representing. | |||
* | |||
* @param px | |||
* the new scroll position in pixels | |||
*/ | |||
public final void setScrollPos(int px) { | |||
int oldScrollPos = scrollPos; | |||
scrollPos = Math.max(0, Math.min(maxScrollPos, px)); | |||
if (oldScrollPos != scrollPos) { | |||
internalSetScrollPos(px); | |||
} | |||
} | |||
protected abstract void internalSetScrollPos(int px); | |||
/** | |||
* Gets the scroll position of the scrollbar in the axis the scrollbar is | |||
* representing. | |||
* | |||
* @return the new scroll position in pixels | |||
*/ | |||
public final int getScrollPos() { | |||
assert internalGetScrollPos() == scrollPos : "calculated scroll position (" | |||
+ scrollPos | |||
+ ") did not match the DOM element scroll position (" | |||
+ internalGetScrollPos() + ")"; | |||
return scrollPos; | |||
} | |||
protected abstract int internalGetScrollPos(); | |||
/** | |||
* Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in | |||
* such a way that the scrollbar is able to scroll a certain number of | |||
* pixels in the axis it is representing. | |||
* | |||
* @param px | |||
* the new size of {@link #scrollSizeElement} in the dimension | |||
* this scrollbar is representing | |||
*/ | |||
protected abstract void internalSetScrollSize(int px); | |||
/** | |||
* Sets the amount of pixels the scrollbar needs to be able to scroll | |||
* through. | |||
* | |||
* @param px | |||
* the number of pixels the scrollbar should be able to scroll | |||
* through | |||
*/ | |||
public final void setScrollSize(int px) { | |||
internalSetScrollSize(px); | |||
forceScrollbar(showsScrollHandle()); | |||
recalculateMaxScrollPos(); | |||
} | |||
/** | |||
* Gets the amount of pixels the scrollbar needs to be able to scroll | |||
* through. | |||
* | |||
* @return the number of pixels the scrollbar should be able to scroll | |||
* through | |||
*/ | |||
public abstract int getScrollSize(); | |||
/** | |||
* Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the | |||
* opposite axis to what the scrollbar is representing. | |||
* | |||
* @param px | |||
* the dimension that {@link #scrollSizeElement} should take in | |||
* the opposite axis to what the scrollbar is representing | |||
*/ | |||
protected abstract void internalSetScrollbarThickness(int px); | |||
/** | |||
* Sets the scrollbar's thickness. | |||
* <p> | |||
* If the thickness is set to 0, the scrollbar will be treated as an | |||
* "invisible" scrollbar. This means, the DOM structure will be given a | |||
* non-zero size, but {@link #getScrollbarThickness()} will still return the | |||
* value 0. | |||
* | |||
* @param px | |||
* the scrollbar's thickness in pixels | |||
*/ | |||
public final void setScrollbarThickness(int px) { | |||
isInvisibleScrollbar = (px == 0); | |||
internalSetScrollbarThickness(px != 0 ? px | |||
: OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX); | |||
} | |||
/** | |||
* Gets the scrollbar's thickness as defined in the DOM. | |||
* | |||
* @return the scrollbar's thickness as defined in the DOM, in pixels | |||
*/ | |||
protected abstract int internalGetScrollbarThickness(); | |||
/** | |||
* Gets the scrollbar's thickness. | |||
* <p> | |||
* This value will differ from the value in the DOM, if the thickness was | |||
* set to 0 with {@link #setScrollbarThickness(int)}, as the scrollbar is | |||
* then treated as "invisible." | |||
* | |||
* @return the scrollbar's thickness in pixels | |||
*/ | |||
public final int getScrollbarThickness() { | |||
if (!isInvisibleScrollbar) { | |||
return internalGetScrollbarThickness(); | |||
} else { | |||
return 0; | |||
} | |||
} | |||
/** | |||
* Checks whether the scrollbar's handle is visible. | |||
* <p> | |||
* In other words, this method checks whether the contents is larger than | |||
* can visually fit in the element. | |||
* | |||
* @return <code>true</code> iff the scrollbar's handle is visible | |||
*/ | |||
public boolean showsScrollHandle() { | |||
return getOffsetSize() < getScrollSize(); | |||
} | |||
public void recalculateMaxScrollPos() { | |||
int scrollSize = getScrollSize(); | |||
int offsetSize = getOffsetSize(); | |||
maxScrollPos = Math.max(0, scrollSize - offsetSize); | |||
// make sure that the correct max scroll position is maintained. | |||
setScrollPos(scrollPos); | |||
} | |||
/** | |||
* This is a method that JSNI can call to synchronize the object state from | |||
* the DOM. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private final void updateScrollPosFromDom() { | |||
scrollPos = internalGetScrollPos(); | |||
} | |||
} |
@@ -0,0 +1,357 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid.datasources; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.ListIterator; | |||
import com.vaadin.client.data.DataChangeHandler; | |||
import com.vaadin.client.data.DataSource; | |||
/** | |||
* A simple list based on an in-memory data source for simply adding a list of | |||
* row pojos to the grid. Based on a wrapped list instance which supports adding | |||
* and removing of items. | |||
* | |||
* <p> | |||
* Usage: | |||
* | |||
* <pre> | |||
* ListDataSource<Integer> ds = new ListDataSource<Integer>(1, 2, 3, 4); | |||
* | |||
* // Add item to the data source | |||
* ds.asList().add(5); | |||
* | |||
* // Remove item from the data source | |||
* ds.asList().remove(3); | |||
* | |||
* // Add multiple items | |||
* ds.asList().addAll(Arrays.asList(5, 6, 7)); | |||
* </pre> | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ListDataSource<T> implements DataSource<T> { | |||
/** | |||
* Wraps the datasource list and notifies the change handler of changing to | |||
* the list | |||
*/ | |||
private class ListWrapper implements List<T> { | |||
@Override | |||
public int size() { | |||
return ds.size(); | |||
} | |||
@Override | |||
public boolean isEmpty() { | |||
return ds.isEmpty(); | |||
} | |||
@Override | |||
public boolean contains(Object o) { | |||
return contains(o); | |||
} | |||
@Override | |||
public Iterator<T> iterator() { | |||
return new ListWrapperIterator(ds.iterator()); | |||
} | |||
@Override | |||
public Object[] toArray() { | |||
return ds.toArray(); | |||
} | |||
@Override | |||
public <T> T[] toArray(T[] a) { | |||
return toArray(a); | |||
} | |||
@Override | |||
public boolean add(T e) { | |||
if (ds.add(e)) { | |||
if (changeHandler != null) { | |||
changeHandler.dataAdded(ds.size() - 1, 1); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public boolean remove(Object o) { | |||
int index = ds.indexOf(o); | |||
if (ds.remove(o)) { | |||
if (changeHandler != null) { | |||
changeHandler.dataRemoved(index, 1); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public boolean containsAll(Collection<?> c) { | |||
return ds.containsAll(c); | |||
} | |||
@Override | |||
public boolean addAll(Collection<? extends T> c) { | |||
int idx = ds.size(); | |||
if (ds.addAll(c)) { | |||
if (changeHandler != null) { | |||
changeHandler.dataAdded(idx, c.size()); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public boolean addAll(int index, Collection<? extends T> c) { | |||
if (ds.addAll(index, c)) { | |||
if (changeHandler != null) { | |||
changeHandler.dataAdded(index, c.size()); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public boolean removeAll(Collection<?> c) { | |||
if (ds.removeAll(c)) { | |||
if (changeHandler != null) { | |||
// Have to update the whole list as the removal does not | |||
// have to be a continuous range | |||
changeHandler.dataUpdated(0, ds.size()); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public boolean retainAll(Collection<?> c) { | |||
if (ds.retainAll(c)) { | |||
if (changeHandler != null) { | |||
// Have to update the whole list as the retain does not | |||
// have to be a continuous range | |||
changeHandler.dataUpdated(0, ds.size()); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public void clear() { | |||
int size = ds.size(); | |||
ds.clear(); | |||
if (changeHandler != null) { | |||
changeHandler.dataRemoved(0, size); | |||
} | |||
} | |||
@Override | |||
public T get(int index) { | |||
return ds.get(index); | |||
} | |||
@Override | |||
public T set(int index, T element) { | |||
T prev = ds.set(index, element); | |||
if (changeHandler != null) { | |||
changeHandler.dataUpdated(index, 1); | |||
} | |||
return prev; | |||
} | |||
@Override | |||
public void add(int index, T element) { | |||
ds.add(index, element); | |||
if (changeHandler != null) { | |||
changeHandler.dataAdded(index, 1); | |||
} | |||
} | |||
@Override | |||
public T remove(int index) { | |||
T removed = ds.remove(index); | |||
if (changeHandler != null) { | |||
changeHandler.dataRemoved(index, 1); | |||
} | |||
return removed; | |||
} | |||
@Override | |||
public int indexOf(Object o) { | |||
return ds.indexOf(o); | |||
} | |||
@Override | |||
public int lastIndexOf(Object o) { | |||
return ds.lastIndexOf(o); | |||
} | |||
@Override | |||
public ListIterator<T> listIterator() { | |||
// TODO could be implemented by a custom iterator. | |||
throw new UnsupportedOperationException( | |||
"List iterators not supported at this time."); | |||
} | |||
@Override | |||
public ListIterator<T> listIterator(int index) { | |||
// TODO could be implemented by a custom iterator. | |||
throw new UnsupportedOperationException( | |||
"List iterators not supported at this time."); | |||
} | |||
@Override | |||
public List<T> subList(int fromIndex, int toIndex) { | |||
throw new UnsupportedOperationException("Sub lists not supported."); | |||
} | |||
} | |||
/** | |||
* Iterator returned by {@link ListWrapper} | |||
*/ | |||
private class ListWrapperIterator implements Iterator<T> { | |||
private final Iterator<T> iterator; | |||
/** | |||
* Constructs a new iterator | |||
*/ | |||
public ListWrapperIterator(Iterator<T> iterator) { | |||
this.iterator = iterator; | |||
} | |||
@Override | |||
public boolean hasNext() { | |||
return iterator.hasNext(); | |||
} | |||
@Override | |||
public T next() { | |||
return iterator.next(); | |||
} | |||
@Override | |||
public void remove() { | |||
throw new UnsupportedOperationException( | |||
"Iterator.remove() is not supported by this iterator."); | |||
} | |||
} | |||
/** | |||
* Datasource for providing row pojo's | |||
*/ | |||
private final List<T> ds; | |||
/** | |||
* Wrapper that wraps the data source | |||
*/ | |||
private final ListWrapper wrapper; | |||
/** | |||
* Handler for listening to changes in the underlying list. | |||
*/ | |||
private DataChangeHandler changeHandler; | |||
/** | |||
* Constructs a new list data source. | |||
* <p> | |||
* Note: Modifications to the original list will not be reflected in the | |||
* data source after the data source has been constructed. To add or remove | |||
* items to the data source after it has been constructed use | |||
* {@link ListDataSource#asList()}. | |||
* | |||
* | |||
* @param datasource | |||
* The list to use for providing the data to the grid | |||
*/ | |||
public ListDataSource(List<T> datasource) { | |||
if (datasource == null) { | |||
throw new IllegalArgumentException("datasource cannot be null"); | |||
} | |||
ds = new ArrayList<T>(datasource); | |||
wrapper = new ListWrapper(); | |||
} | |||
/** | |||
* Constructs a data source with a set of rows. You can dynamically add and | |||
* remove rows from the data source via the list you get from | |||
* {@link ListDataSource#asList()} | |||
* | |||
* @param rows | |||
* The rows to initially add to the data source | |||
*/ | |||
public ListDataSource(T... rows) { | |||
if (rows == null) { | |||
ds = new ArrayList<T>(); | |||
} else { | |||
ds = new ArrayList<T>(Arrays.asList(rows)); | |||
} | |||
wrapper = new ListWrapper(); | |||
} | |||
@Override | |||
public void ensureAvailability(int firstRowIndex, int numberOfRows) { | |||
if (firstRowIndex >= ds.size()) { | |||
throw new IllegalStateException( | |||
"Trying to fetch rows outside of array"); | |||
} | |||
} | |||
@Override | |||
public T getRow(int rowIndex) { | |||
return ds.get(rowIndex); | |||
} | |||
@Override | |||
public int getEstimatedSize() { | |||
return ds.size(); | |||
} | |||
@Override | |||
public void setDataChangeHandler(DataChangeHandler dataChangeHandler) { | |||
this.changeHandler = dataChangeHandler; | |||
} | |||
/** | |||
* Gets the list that backs this datasource. Any changes made to this list | |||
* will be reflected in the datasource. | |||
* <p> | |||
* Note: The list is not the same list as passed into the data source via | |||
* the constructor. | |||
* | |||
* @return Returns a list implementation that wraps the real list that backs | |||
* the data source and provides events for the data source | |||
* listeners. | |||
*/ | |||
public List<T> asList() { | |||
return wrapper; | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid.renderers; | |||
import java.util.Date; | |||
import com.google.gwt.i18n.client.DateTimeFormat; | |||
import com.google.gwt.i18n.client.TimeZone; | |||
import com.vaadin.client.ui.grid.Cell; | |||
import com.vaadin.client.ui.grid.Renderer; | |||
/** | |||
* A renderer for rendering dates into cells | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class DateRenderer implements Renderer<Date> { | |||
private DateTimeFormat format = DateTimeFormat.getShortDateTimeFormat(); | |||
private TimeZone timeZone = TimeZone.createTimeZone(new Date() | |||
.getTimezoneOffset()); | |||
@Override | |||
public void renderCell(Cell cell, Date date) { | |||
String dateStr = format.format(date, timeZone); | |||
cell.getElement().setInnerText(dateStr); | |||
} | |||
/** | |||
* Gets the format of how the date is formatted. | |||
* | |||
* @return the format | |||
* @see <a | |||
* href="http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html">GWT | |||
* documentation on DateTimeFormat</a> | |||
*/ | |||
public DateTimeFormat getFormat() { | |||
return format; | |||
} | |||
/** | |||
* Sets the format used for formatting the dates. | |||
* | |||
* @param format | |||
* the format to set | |||
* @see <a | |||
* href="http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html">GWT | |||
* documentation on DateTimeFormat</a> | |||
*/ | |||
public void setFormat(DateTimeFormat format) { | |||
if (format == null) { | |||
throw new IllegalArgumentException("Format should not be null"); | |||
} | |||
this.format = format; | |||
} | |||
/** | |||
* Returns the time zone of the date. | |||
* | |||
* @return the time zone | |||
*/ | |||
public TimeZone getTimeZone() { | |||
return timeZone; | |||
} | |||
/** | |||
* Sets the time zone of the the date. By default uses the time zone of the | |||
* browser. | |||
* | |||
* @param timeZone | |||
* the timeZone to set | |||
*/ | |||
public void setTimeZone(TimeZone timeZone) { | |||
if (timeZone == null) { | |||
throw new IllegalArgumentException("Timezone should not be null"); | |||
} | |||
this.timeZone = timeZone; | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid.renderers; | |||
import com.google.gwt.safehtml.shared.SafeHtml; | |||
import com.google.gwt.safehtml.shared.SafeHtmlUtils; | |||
import com.vaadin.client.ui.grid.Cell; | |||
import com.vaadin.client.ui.grid.Renderer; | |||
/** | |||
* Renders a string as HTML into a cell. | |||
* <p> | |||
* The html string is rendered as is without any escaping. It is up to the | |||
* developer to ensure that the html string honors the {@link SafeHtml} | |||
* contract. For more information see | |||
* {@link SafeHtmlUtils#fromSafeConstant(String)}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @see SafeHtmlUtils#fromSafeConstant(String) | |||
*/ | |||
public class HtmlRenderer implements Renderer<String> { | |||
@Override | |||
public void renderCell(Cell cell, String htmlString) { | |||
cell.getElement().setInnerSafeHtml( | |||
SafeHtmlUtils.fromSafeConstant(htmlString)); | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid.renderers; | |||
import com.google.gwt.i18n.client.NumberFormat; | |||
import com.vaadin.client.ui.grid.Cell; | |||
import com.vaadin.client.ui.grid.Renderer; | |||
/** | |||
* Renders a number into a cell using a specific {@link NumberFormat}. By | |||
* default uses the default number format returned by | |||
* {@link NumberFormat#getDecimalFormat()}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
* @param <T> | |||
* The number type to render. | |||
*/ | |||
public class NumberRenderer<T extends Number> implements Renderer<T> { | |||
private NumberFormat format = NumberFormat.getDecimalFormat(); | |||
/** | |||
* Gets the number format that the number should be formatted in. | |||
* | |||
* @return the number format used to render the number | |||
*/ | |||
public NumberFormat getFormat() { | |||
return format; | |||
} | |||
/** | |||
* Sets the number format to use for formatting the number. | |||
* | |||
* @param format | |||
* the format to use | |||
* @throws IllegalArgumentException | |||
* when the format is null | |||
*/ | |||
public void setFormat(NumberFormat format) throws IllegalArgumentException { | |||
if (format == null) { | |||
throw new IllegalArgumentException("Format cannot be null"); | |||
} | |||
this.format = format; | |||
} | |||
@Override | |||
public void renderCell(Cell cell, Number number) { | |||
cell.getElement().setInnerText(format.format(number)); | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid.renderers; | |||
import com.vaadin.client.ui.grid.Cell; | |||
import com.vaadin.client.ui.grid.Renderer; | |||
/** | |||
* Renderer that renders text into a cell. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class TextRenderer implements Renderer<String> { | |||
@Override | |||
public void renderCell(Cell cell, String text) { | |||
cell.getElement().setInnerText(text); | |||
} | |||
} |
@@ -0,0 +1,178 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import static org.junit.Assert.assertEquals; | |||
import java.util.Arrays; | |||
import org.easymock.EasyMock; | |||
import org.junit.Test; | |||
import com.vaadin.client.data.DataChangeHandler; | |||
import com.vaadin.client.ui.grid.datasources.ListDataSource; | |||
/** | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ListDataSourceTest { | |||
@Test | |||
public void testDataSourceConstruction() throws Exception { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
assertEquals(4, ds.getEstimatedSize()); | |||
assertEquals(0, (int) ds.getRow(0)); | |||
assertEquals(1, (int) ds.getRow(1)); | |||
assertEquals(2, (int) ds.getRow(2)); | |||
assertEquals(3, (int) ds.getRow(3)); | |||
ds = new ListDataSource<Integer>(Arrays.asList(0, 1, 2, 3)); | |||
assertEquals(4, ds.getEstimatedSize()); | |||
assertEquals(0, (int) ds.getRow(0)); | |||
assertEquals(1, (int) ds.getRow(1)); | |||
assertEquals(2, (int) ds.getRow(2)); | |||
assertEquals(3, (int) ds.getRow(3)); | |||
} | |||
@Test | |||
public void testListAddOperation() throws Exception { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
DataChangeHandler handler = EasyMock | |||
.createNiceMock(DataChangeHandler.class); | |||
ds.setDataChangeHandler(handler); | |||
handler.dataAdded(4, 1); | |||
EasyMock.expectLastCall(); | |||
EasyMock.replay(handler); | |||
ds.asList().add(4); | |||
assertEquals(5, ds.getEstimatedSize()); | |||
assertEquals(0, (int) ds.getRow(0)); | |||
assertEquals(1, (int) ds.getRow(1)); | |||
assertEquals(2, (int) ds.getRow(2)); | |||
assertEquals(3, (int) ds.getRow(3)); | |||
assertEquals(4, (int) ds.getRow(4)); | |||
} | |||
@Test | |||
public void testListAddAllOperation() throws Exception { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
DataChangeHandler handler = EasyMock | |||
.createNiceMock(DataChangeHandler.class); | |||
ds.setDataChangeHandler(handler); | |||
handler.dataAdded(4, 3); | |||
EasyMock.expectLastCall(); | |||
EasyMock.replay(handler); | |||
ds.asList().addAll(Arrays.asList(4, 5, 6)); | |||
assertEquals(7, ds.getEstimatedSize()); | |||
assertEquals(0, (int) ds.getRow(0)); | |||
assertEquals(1, (int) ds.getRow(1)); | |||
assertEquals(2, (int) ds.getRow(2)); | |||
assertEquals(3, (int) ds.getRow(3)); | |||
assertEquals(4, (int) ds.getRow(4)); | |||
assertEquals(5, (int) ds.getRow(5)); | |||
assertEquals(6, (int) ds.getRow(6)); | |||
} | |||
@Test | |||
public void testListRemoveOperation() throws Exception { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
DataChangeHandler handler = EasyMock | |||
.createNiceMock(DataChangeHandler.class); | |||
ds.setDataChangeHandler(handler); | |||
handler.dataRemoved(3, 1); | |||
EasyMock.expectLastCall(); | |||
EasyMock.replay(handler); | |||
ds.asList().remove(2); | |||
assertEquals(3, ds.getEstimatedSize()); | |||
assertEquals(0, (int) ds.getRow(0)); | |||
assertEquals(1, (int) ds.getRow(1)); | |||
assertEquals(3, (int) ds.getRow(2)); | |||
} | |||
@Test | |||
public void testListRemoveAllOperation() throws Exception { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
DataChangeHandler handler = EasyMock | |||
.createNiceMock(DataChangeHandler.class); | |||
ds.setDataChangeHandler(handler); | |||
handler.dataRemoved(0, 3); | |||
EasyMock.expectLastCall(); | |||
EasyMock.replay(handler); | |||
ds.asList().removeAll(Arrays.asList(0, 2, 3)); | |||
assertEquals(1, ds.getEstimatedSize()); | |||
assertEquals(1, (int) ds.getRow(0)); | |||
} | |||
@Test | |||
public void testListClearOperation() throws Exception { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
DataChangeHandler handler = EasyMock | |||
.createNiceMock(DataChangeHandler.class); | |||
ds.setDataChangeHandler(handler); | |||
handler.dataRemoved(0, 4); | |||
EasyMock.expectLastCall(); | |||
EasyMock.replay(handler); | |||
ds.asList().clear(); | |||
assertEquals(0, ds.getEstimatedSize()); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void testFetchingNonExistantItem() { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
ds.ensureAvailability(5, 1); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void testUnsupportedIteratorRemove() { | |||
ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); | |||
ds.asList().iterator().remove(); | |||
} | |||
} |
@@ -0,0 +1,104 @@ | |||
/* | |||
* Copyright 2000-2013 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.grid; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import org.junit.Test; | |||
import com.vaadin.shared.ui.grid.Range; | |||
@SuppressWarnings("static-method") | |||
public class PartitioningTest { | |||
@Test | |||
public void selfRangeTest() { | |||
final Range range = Range.between(0, 10); | |||
final Range[] partitioning = range.partitionWith(range); | |||
assertTrue("before is empty", partitioning[0].isEmpty()); | |||
assertTrue("inside is self", partitioning[1].equals(range)); | |||
assertTrue("after is empty", partitioning[2].isEmpty()); | |||
} | |||
@Test | |||
public void beforeRangeTest() { | |||
final Range beforeRange = Range.between(0, 10); | |||
final Range afterRange = Range.between(10, 20); | |||
final Range[] partitioning = beforeRange.partitionWith(afterRange); | |||
assertTrue("before is self", partitioning[0].equals(beforeRange)); | |||
assertTrue("inside is empty", partitioning[1].isEmpty()); | |||
assertTrue("after is empty", partitioning[2].isEmpty()); | |||
} | |||
@Test | |||
public void afterRangeTest() { | |||
final Range beforeRange = Range.between(0, 10); | |||
final Range afterRange = Range.between(10, 20); | |||
final Range[] partitioning = afterRange.partitionWith(beforeRange); | |||
assertTrue("before is empty", partitioning[0].isEmpty()); | |||
assertTrue("inside is empty", partitioning[1].isEmpty()); | |||
assertTrue("after is self", partitioning[2].equals(afterRange)); | |||
} | |||
@Test | |||
public void beforeAndInsideRangeTest() { | |||
final Range beforeRange = Range.between(0, 10); | |||
final Range afterRange = Range.between(5, 15); | |||
final Range[] partitioning = beforeRange.partitionWith(afterRange); | |||
assertEquals("before", Range.between(0, 5), partitioning[0]); | |||
assertEquals("inside", Range.between(5, 10), partitioning[1]); | |||
assertTrue("after is empty", partitioning[2].isEmpty()); | |||
} | |||
@Test | |||
public void insideRangeTest() { | |||
final Range fullRange = Range.between(0, 20); | |||
final Range insideRange = Range.between(5, 15); | |||
final Range[] partitioning = insideRange.partitionWith(fullRange); | |||
assertTrue("before is empty", partitioning[0].isEmpty()); | |||
assertEquals("inside", Range.between(5, 15), partitioning[1]); | |||
assertTrue("after is empty", partitioning[2].isEmpty()); | |||
} | |||
@Test | |||
public void insideAndBelowTest() { | |||
final Range beforeRange = Range.between(0, 10); | |||
final Range afterRange = Range.between(5, 15); | |||
final Range[] partitioning = afterRange.partitionWith(beforeRange); | |||
assertTrue("before is empty", partitioning[0].isEmpty()); | |||
assertEquals("inside", Range.between(5, 10), partitioning[1]); | |||
assertEquals("after", Range.between(10, 15), partitioning[2]); | |||
} | |||
@Test | |||
public void aboveAndBelowTest() { | |||
final Range fullRange = Range.between(0, 20); | |||
final Range insideRange = Range.between(5, 15); | |||
final Range[] partitioning = fullRange.partitionWith(insideRange); | |||
assertEquals("before", Range.between(0, 5), partitioning[0]); | |||
assertEquals("inside", Range.between(5, 15), partitioning[1]); | |||
assertEquals("after", Range.between(15, 20), partitioning[2]); | |||
} | |||
} |
@@ -0,0 +1,148 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Indexed; | |||
import com.vaadin.server.AbstractExtension; | |||
import com.vaadin.shared.data.DataProviderRpc; | |||
import com.vaadin.shared.data.DataProviderState; | |||
import com.vaadin.shared.data.DataRequestRpc; | |||
import com.vaadin.ui.components.grid.Grid; | |||
/** | |||
* Provides Vaadin server-side container data source to a | |||
* {@link com.vaadin.client.ui.grid.GridConnector}. This is currently | |||
* implemented as an Extension hardcoded to support a specific connector type. | |||
* This will be changed once framework support for something more flexible has | |||
* been implemented. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class RpcDataProviderExtension extends AbstractExtension { | |||
private final Indexed container; | |||
/** | |||
* Creates a new data provider using the given container. | |||
* | |||
* @param container | |||
* the container to make available | |||
*/ | |||
public RpcDataProviderExtension(Indexed container) { | |||
this.container = container; | |||
// TODO support for reacting to events from the container added later | |||
registerRpc(new DataRequestRpc() { | |||
@Override | |||
public void requestRows(int firstRow, int numberOfRows) { | |||
pushRows(firstRow, numberOfRows); | |||
} | |||
}); | |||
getState().containerSize = container.size(); | |||
} | |||
private void pushRows(int firstRow, int numberOfRows) { | |||
List<?> itemIds = container.getItemIds(firstRow, numberOfRows); | |||
Collection<?> propertyIds = container.getContainerPropertyIds(); | |||
List<String[]> rows = new ArrayList<String[]>(itemIds.size()); | |||
for (Object itemId : itemIds) { | |||
rows.add(getRowData(propertyIds, itemId)); | |||
} | |||
getRpcProxy(DataProviderRpc.class).setRowData(firstRow, rows); | |||
} | |||
private String[] getRowData(Collection<?> propertyIds, Object itemId) { | |||
Item item = container.getItem(itemId); | |||
String[] row = new String[propertyIds.size()]; | |||
int i = 0; | |||
for (Object propertyId : propertyIds) { | |||
Object value = item.getItemProperty(propertyId).getValue(); | |||
String stringValue = String.valueOf(value); | |||
row[i++] = stringValue; | |||
} | |||
return row; | |||
} | |||
@Override | |||
protected DataProviderState getState() { | |||
return (DataProviderState) super.getState(); | |||
} | |||
/** | |||
* Makes the data source available to the given {@link Grid} component. | |||
* | |||
* @param component | |||
* the remote data grid component to extend | |||
*/ | |||
public void extend(Grid component) { | |||
super.extend(component); | |||
} | |||
/** | |||
* Informs the client side that new rows have been inserted into the data | |||
* source. | |||
* | |||
* @param index | |||
* the index at which new rows have been inserted | |||
* @param count | |||
* the number of rows inserted at <code>index</code> | |||
*/ | |||
public void insertRowData(int index, int count) { | |||
getState().containerSize += count; | |||
getRpcProxy(DataProviderRpc.class).insertRowData(index, count); | |||
} | |||
/** | |||
* Informs the client side that rows have been removed from the data source. | |||
* | |||
* @param firstIndex | |||
* the index of the first row removed | |||
* @param count | |||
* the number of rows removed | |||
*/ | |||
public void removeRowData(int firstIndex, int count) { | |||
getState().containerSize -= count; | |||
getRpcProxy(DataProviderRpc.class).removeRowData(firstIndex, count); | |||
} | |||
/** | |||
* Informs the client side that data of a row has been modified in the data | |||
* source. | |||
* | |||
* @param index | |||
* the index of the row that was updated | |||
*/ | |||
public void updateRowData(int index) { | |||
/* | |||
* TODO: ignore duplicate requests for the same index during the same | |||
* roundtrip. | |||
*/ | |||
Object itemId = container.getIdByIndex(index); | |||
String[] row = getRowData(container.getContainerPropertyIds(), itemId); | |||
getRpcProxy(DataProviderRpc.class).setRowData(index, | |||
Collections.singletonList(row)); | |||
} | |||
} |
@@ -0,0 +1,165 @@ | |||
/* | |||
* Copyright 2000-2013 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.ui.components.grid; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.vaadin.shared.ui.grid.ColumnGroupState; | |||
/** | |||
* Column groups are used to group columns together for adding common auxiliary | |||
* headers and footers. Columns groups are added to {@link ColumnGroupRow}'s. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnGroup implements Serializable { | |||
/** | |||
* List of property ids belonging to this group | |||
*/ | |||
private List<Object> columns; | |||
/** | |||
* The grid the column group is associated with | |||
*/ | |||
private final Grid grid; | |||
/** | |||
* The column group row the column group is attached to | |||
*/ | |||
private final ColumnGroupRow row; | |||
/** | |||
* The common state between the server and the client | |||
*/ | |||
private final ColumnGroupState state; | |||
/** | |||
* Constructs a new column group | |||
* | |||
* @param grid | |||
* the grid the column group is associated with | |||
* @param state | |||
* the state representing the data of the grid. Sent to the | |||
* client | |||
* @param propertyIds | |||
* the property ids of the columns that belongs to the group | |||
* @param groups | |||
* the sub groups who should be included in this group | |||
* | |||
*/ | |||
ColumnGroup(Grid grid, ColumnGroupRow row, ColumnGroupState state, | |||
List<Object> propertyIds) { | |||
if (propertyIds == null) { | |||
throw new IllegalArgumentException( | |||
"propertyIds cannot be null. Use empty list instead."); | |||
} | |||
this.state = state; | |||
this.row = row; | |||
columns = Collections.unmodifiableList(new ArrayList<Object>( | |||
propertyIds)); | |||
this.grid = grid; | |||
} | |||
/** | |||
* Sets the text displayed in the header of the column group. | |||
* | |||
* @param header | |||
* the text displayed in the header of the column | |||
*/ | |||
public void setHeaderCaption(String header) { | |||
checkGroupIsAttached(); | |||
state.header = header; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Sets the text displayed in the header of the column group. | |||
* | |||
* @return the text displayed in the header of the column | |||
*/ | |||
public String getHeaderCaption() { | |||
checkGroupIsAttached(); | |||
return state.header; | |||
} | |||
/** | |||
* Sets the text displayed in the footer of the column group. | |||
* | |||
* @param footer | |||
* the text displayed in the footer of the column | |||
*/ | |||
public void setFooterCaption(String footer) { | |||
checkGroupIsAttached(); | |||
state.footer = footer; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* The text displayed in the footer of the column group. | |||
* | |||
* @return the text displayed in the footer of the column | |||
*/ | |||
public String getFooterCaption() { | |||
checkGroupIsAttached(); | |||
return state.footer; | |||
} | |||
/** | |||
* Is a property id in this group or in some sub group of this group. | |||
* | |||
* @param propertyId | |||
* the property id to check for | |||
* @return <code>true</code> if the property id is included in this group. | |||
*/ | |||
public boolean isColumnInGroup(Object propertyId) { | |||
if (columns.contains(propertyId)) { | |||
return true; | |||
} | |||
return false; | |||
} | |||
/** | |||
* Returns a list of property ids where all also the child groups property | |||
* ids are included. | |||
* | |||
* @return a unmodifiable list with all the columns in the group. Includes | |||
* any subgroup columns as well. | |||
*/ | |||
public List<Object> getColumns() { | |||
return columns; | |||
} | |||
/** | |||
* Checks if column group is attached to a row and throws an | |||
* {@link IllegalStateException} if it is not. | |||
* | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
protected void checkGroupIsAttached() throws IllegalStateException { | |||
if (!row.getState().groups.contains(state)) { | |||
throw new IllegalStateException( | |||
"Column Group has been removed from the row."); | |||
} | |||
} | |||
} |
@@ -0,0 +1,303 @@ | |||
/* | |||
* Copyright 2000-2013 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.ui.components.grid; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.vaadin.server.KeyMapper; | |||
import com.vaadin.shared.ui.grid.ColumnGroupRowState; | |||
import com.vaadin.shared.ui.grid.ColumnGroupState; | |||
/** | |||
* A column group row represents an auxiliary header or footer row added to the | |||
* grid. A column group row includes column groups that group columns together. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnGroupRow implements Serializable { | |||
/** | |||
* The common state shared between the client and server | |||
*/ | |||
private final ColumnGroupRowState state; | |||
/** | |||
* The column groups in this row | |||
*/ | |||
private List<ColumnGroup> groups = new ArrayList<ColumnGroup>(); | |||
/** | |||
* Grid that the group row belongs to | |||
*/ | |||
private final Grid grid; | |||
/** | |||
* The column keys used to identify the column on the client side | |||
*/ | |||
private final KeyMapper<Object> columnKeys; | |||
/** | |||
* Constructs a new column group | |||
* | |||
* @param grid | |||
* The grid that the column group is associated to | |||
* @param state | |||
* The shared state which contains the data shared between server | |||
* and client | |||
* @param columnKeys | |||
* The column key mapper for converting property ids to client | |||
* side column identifiers | |||
*/ | |||
ColumnGroupRow(Grid grid, ColumnGroupRowState state, | |||
KeyMapper<Object> columnKeys) { | |||
this.grid = grid; | |||
this.columnKeys = columnKeys; | |||
this.state = state; | |||
} | |||
/** | |||
* Gets the shared state for the column group row. Used internally to send | |||
* the group row to the client. | |||
* | |||
* @return The current state of the row | |||
*/ | |||
ColumnGroupRowState getState() { | |||
return state; | |||
} | |||
/** | |||
* Add a new group to the row by using property ids for the columns. | |||
* | |||
* @param propertyIds | |||
* The property ids of the columns that should be included in the | |||
* group. A column can only belong in group on a row at a time. | |||
* @return a column group representing the collection of columns added to | |||
* the group | |||
*/ | |||
public ColumnGroup addGroup(Object... propertyIds) | |||
throws IllegalArgumentException { | |||
assert propertyIds != null : "propertyIds cannot be null."; | |||
for (Object propertyId : propertyIds) { | |||
if (hasColumnBeenGrouped(propertyId)) { | |||
throw new IllegalArgumentException("Column " | |||
+ String.valueOf(propertyId) | |||
+ " already belongs to another group."); | |||
} | |||
} | |||
validateNewGroupProperties(Arrays.asList(propertyIds)); | |||
ColumnGroupState state = new ColumnGroupState(); | |||
for (Object propertyId : propertyIds) { | |||
assert propertyId != null : "null items in columns array not supported."; | |||
state.columns.add(columnKeys.key(propertyId)); | |||
} | |||
this.state.groups.add(state); | |||
ColumnGroup group = new ColumnGroup(grid, this, state, | |||
Arrays.asList(propertyIds)); | |||
groups.add(group); | |||
grid.markAsDirty(); | |||
return group; | |||
} | |||
private void validateNewGroupProperties(List<Object> propertyIds) | |||
throws IllegalArgumentException { | |||
/* | |||
* Validate parent grouping | |||
*/ | |||
int rowIndex = grid.getColumnGroupRows().indexOf(this); | |||
int parentRowIndex = rowIndex - 1; | |||
// Get the parent row of this row. | |||
ColumnGroupRow parentRow = null; | |||
if (parentRowIndex > -1) { | |||
parentRow = grid.getColumnGroupRows().get(parentRowIndex); | |||
} | |||
if (parentRow == null) { | |||
// A parentless row is always valid and is usually the first row | |||
// added to the grid | |||
return; | |||
} | |||
for (Object id : propertyIds) { | |||
if (parentRow.hasColumnBeenGrouped(id)) { | |||
/* | |||
* If a property has been grouped in the parent row then all of | |||
* the properties in the parent group also needs to be included | |||
* in the child group for the groups to be valid | |||
*/ | |||
ColumnGroup parentGroup = parentRow.getGroupForProperty(id); | |||
if (!propertyIds.containsAll(parentGroup.getColumns())) { | |||
throw new IllegalArgumentException( | |||
"Grouped properties overlaps previous grouping bounderies"); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Add a new group to the row by using column instances. | |||
* | |||
* @param columns | |||
* the columns that should belong to the group | |||
* @return a column group representing the collection of columns added to | |||
* the group | |||
*/ | |||
public ColumnGroup addGroup(GridColumn... columns) | |||
throws IllegalArgumentException { | |||
assert columns != null : "columns cannot be null"; | |||
List<Object> propertyIds = new ArrayList<Object>(); | |||
for (GridColumn column : columns) { | |||
assert column != null : "null items in columns array not supported."; | |||
String columnId = column.getState().id; | |||
Object propertyId = grid.getPropertyIdByColumnId(columnId); | |||
propertyIds.add(propertyId); | |||
} | |||
return addGroup(propertyIds.toArray()); | |||
} | |||
/** | |||
* Add a new group to the row by using other already greated groups | |||
* | |||
* @param groups | |||
* the subgroups of the group | |||
* @return a column group representing the collection of columns added to | |||
* the group | |||
* | |||
*/ | |||
public ColumnGroup addGroup(ColumnGroup... groups) | |||
throws IllegalArgumentException { | |||
assert groups != null : "groups cannot be null"; | |||
// Gather all groups columns into one list | |||
List<Object> propertyIds = new ArrayList<Object>(); | |||
for (ColumnGroup group : groups) { | |||
propertyIds.addAll(group.getColumns()); | |||
} | |||
validateNewGroupProperties(propertyIds); | |||
ColumnGroupState state = new ColumnGroupState(); | |||
ColumnGroup group = new ColumnGroup(grid, this, state, propertyIds); | |||
this.groups.add(group); | |||
// Update state | |||
for (Object propertyId : group.getColumns()) { | |||
state.columns.add(columnKeys.key(propertyId)); | |||
} | |||
this.state.groups.add(state); | |||
grid.markAsDirty(); | |||
return group; | |||
} | |||
/** | |||
* Removes a group from the row. Does not remove the group from subgroups, | |||
* to remove it from the subgroup invoke removeGroup on the subgroup. | |||
* | |||
* @param group | |||
* the group to remove | |||
*/ | |||
public void removeGroup(ColumnGroup group) { | |||
int index = groups.indexOf(group); | |||
groups.remove(index); | |||
state.groups.remove(index); | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Get the groups in the row. | |||
* | |||
* @return unmodifiable list of groups in this row | |||
*/ | |||
public List<ColumnGroup> getGroups() { | |||
return Collections.unmodifiableList(groups); | |||
} | |||
/** | |||
* Checks if a property id has been added to a group in this row. | |||
* | |||
* @param propertyId | |||
* the property id to check for | |||
* @return <code>true</code> if the column is included in a group | |||
*/ | |||
private boolean hasColumnBeenGrouped(Object propertyId) { | |||
return getGroupForProperty(propertyId) != null; | |||
} | |||
private ColumnGroup getGroupForProperty(Object propertyId) { | |||
for (ColumnGroup group : groups) { | |||
if (group.isColumnInGroup(propertyId)) { | |||
return group; | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Is the header visible for the row. | |||
* | |||
* @return <code>true</code> if header is visible | |||
*/ | |||
public boolean isHeaderVisible() { | |||
return state.headerVisible; | |||
} | |||
/** | |||
* Sets the header visible for the row. | |||
* | |||
* @param visible | |||
* should the header be shown | |||
*/ | |||
public void setHeaderVisible(boolean visible) { | |||
state.headerVisible = visible; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Is the footer visible for the row. | |||
* | |||
* @return <code>true</code> if footer is visible | |||
*/ | |||
public boolean isFooterVisible() { | |||
return state.footerVisible; | |||
} | |||
/** | |||
* Sets the footer visible for the row. | |||
* | |||
* @param visible | |||
* should the footer be shown | |||
*/ | |||
public void setFooterVisible(boolean visible) { | |||
state.footerVisible = visible; | |||
grid.markAsDirty(); | |||
} | |||
} |
@@ -0,0 +1,861 @@ | |||
/* | |||
* Copyright 2000-2013 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.ui.components.grid; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.HashSet; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import com.vaadin.data.Container; | |||
import com.vaadin.data.Container.Indexed.ItemAddEvent; | |||
import com.vaadin.data.Container.Indexed.ItemRemoveEvent; | |||
import com.vaadin.data.Container.ItemSetChangeEvent; | |||
import com.vaadin.data.Container.ItemSetChangeListener; | |||
import com.vaadin.data.Container.ItemSetChangeNotifier; | |||
import com.vaadin.data.Container.PropertySetChangeEvent; | |||
import com.vaadin.data.Container.PropertySetChangeListener; | |||
import com.vaadin.data.Container.PropertySetChangeNotifier; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.Property; | |||
import com.vaadin.data.Property.ValueChangeEvent; | |||
import com.vaadin.data.Property.ValueChangeListener; | |||
import com.vaadin.data.Property.ValueChangeNotifier; | |||
import com.vaadin.data.RpcDataProviderExtension; | |||
import com.vaadin.server.KeyMapper; | |||
import com.vaadin.shared.ui.grid.ColumnGroupRowState; | |||
import com.vaadin.shared.ui.grid.GridClientRpc; | |||
import com.vaadin.shared.ui.grid.GridColumnState; | |||
import com.vaadin.shared.ui.grid.GridServerRpc; | |||
import com.vaadin.shared.ui.grid.GridState; | |||
import com.vaadin.shared.ui.grid.Range; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
import com.vaadin.ui.AbstractComponent; | |||
import com.vaadin.ui.Component; | |||
/** | |||
* Data grid component | |||
* | |||
* <h3>Lazy loading</h3> TODO To be revised when the data data source | |||
* implementation has been don. | |||
* | |||
* <h3>Columns</h3> The grid columns are based on the property ids of the | |||
* underlying data source. Each property id represents one column in the grid. | |||
* To retrive a column in the grid you can use {@link Grid#getColumn(Object)} | |||
* with the property id of the column. A grid column contains properties like | |||
* the width, the footer and header captions of the column. | |||
* | |||
* <h3>Auxiliary headers and footers</h3> TODO To be revised when column | |||
* grouping is implemented. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class Grid extends AbstractComponent { | |||
/** | |||
* A helper class that handles the client-side Escalator logic relating to | |||
* making sure that whatever is currently visible to the user, is properly | |||
* initialized and otherwise handled on the server side (as far as | |||
* requried). | |||
* <p> | |||
* This bookeeping includes, but is not limited to: | |||
* <ul> | |||
* <li>listening to the currently visible {@link Property Properties'} value | |||
* changes on the server side and sending those back to the client; and | |||
* <li>attaching and detaching {@link Component Components} from the Vaadin | |||
* Component hierarchy. | |||
* </ul> | |||
*/ | |||
private final class ActiveRowHandler { | |||
/** | |||
* A map from itemId to the value change listener used for all of its | |||
* properties | |||
*/ | |||
private final Map<Object, GridValueChangeListener> valueChangeListeners = new HashMap<Object, GridValueChangeListener>(); | |||
/** | |||
* The currently active range. Practically, it's the range of row | |||
* indices being displayed currently. | |||
*/ | |||
private Range activeRange = Range.withLength(0, 0); | |||
/** | |||
* A hook for making sure that appropriate data is "active". All other | |||
* rows should be "inactive". | |||
* <p> | |||
* "Active" can mean different things in different contexts. For | |||
* example, only the Properties in the active range need | |||
* ValueChangeListeners. Also, whenever a row with a Component becomes | |||
* active, it needs to be attached (and conversely, when inactive, it | |||
* needs to be detached). | |||
* | |||
* @param firstActiveRow | |||
* the first active row | |||
* @param activeRowCount | |||
* the number of active rows | |||
*/ | |||
public void setActiveRows(int firstActiveRow, int activeRowCount) { | |||
final Range newActiveRange = Range.withLength(firstActiveRow, | |||
activeRowCount); | |||
// TODO [[Components]] attach and detach components | |||
/*- | |||
* Example | |||
* | |||
* New Range: [3, 4, 5, 6, 7] | |||
* Old Range: [1, 2, 3, 4, 5] | |||
* Result: [1, 2][3, 4, 5] [] | |||
*/ | |||
final Range[] depractionPartition = activeRange | |||
.partitionWith(newActiveRange); | |||
removeValueChangeListeners(depractionPartition[0]); | |||
removeValueChangeListeners(depractionPartition[2]); | |||
/*- | |||
* Example | |||
* | |||
* Old Range: [1, 2, 3, 4, 5] | |||
* New Range: [3, 4, 5, 6, 7] | |||
* Result: [] [3, 4, 5][6, 7] | |||
*/ | |||
final Range[] activationPartition = newActiveRange | |||
.partitionWith(activeRange); | |||
addValueChangeListeners(activationPartition[0]); | |||
addValueChangeListeners(activationPartition[2]); | |||
activeRange = newActiveRange; | |||
} | |||
private void addValueChangeListeners(Range range) { | |||
for (int i = range.getStart(); i < range.getEnd(); i++) { | |||
final Object itemId = datasource.getIdByIndex(i); | |||
final Item item = datasource.getItem(itemId); | |||
if (valueChangeListeners.containsKey(itemId)) { | |||
/* | |||
* This might occur when items are removed from above the | |||
* viewport, the escalator scrolls up to compensate, but the | |||
* same items remain in the view: It looks as if one row was | |||
* scrolled, when in fact the whole viewport was shifted up. | |||
*/ | |||
continue; | |||
} | |||
GridValueChangeListener listener = new GridValueChangeListener( | |||
itemId); | |||
valueChangeListeners.put(itemId, listener); | |||
for (final Object propertyId : item.getItemPropertyIds()) { | |||
final Property<?> property = item | |||
.getItemProperty(propertyId); | |||
if (property instanceof ValueChangeNotifier) { | |||
((ValueChangeNotifier) property) | |||
.addValueChangeListener(listener); | |||
} | |||
} | |||
} | |||
} | |||
private void removeValueChangeListeners(Range range) { | |||
for (int i = range.getStart(); i < range.getEnd(); i++) { | |||
final Object itemId = datasource.getIdByIndex(i); | |||
final Item item = datasource.getItem(itemId); | |||
final GridValueChangeListener listener = valueChangeListeners | |||
.remove(itemId); | |||
if (listener != null) { | |||
for (final Object propertyId : item.getItemPropertyIds()) { | |||
final Property<?> property = item | |||
.getItemProperty(propertyId); | |||
/* | |||
* Because listener != null, we can be certain that this | |||
* property is a ValueChangeNotifier: It wouldn't be | |||
* inserted in addValueChangeListeners if the property | |||
* wasn't a suitable type. I.e. No need for "instanceof" | |||
* check. | |||
*/ | |||
((ValueChangeNotifier) property) | |||
.removeValueChangeListener(listener); | |||
} | |||
} | |||
} | |||
} | |||
public void clear() { | |||
removeValueChangeListeners(activeRange); | |||
/* | |||
* we're doing an assert for emptiness there (instead of a | |||
* carte-blanche ".clear()"), to be absolutely sure that everything | |||
* is cleaned up properly, and that we have no dangling listeners. | |||
*/ | |||
assert valueChangeListeners.isEmpty() : "GridValueChangeListeners are leaking"; | |||
activeRange = Range.withLength(0, 0); | |||
} | |||
/** | |||
* Manages removed properties in active rows. | |||
* | |||
* @param removedPropertyIds | |||
* the property ids that have been removed from the container | |||
*/ | |||
public void propertiesRemoved(Collection<Object> removedPropertyIds) { | |||
/* | |||
* no-op, for now. | |||
* | |||
* The Container should be responsible for cleaning out any | |||
* ValueChangeListeners from removed Properties. Components will | |||
* benefit from this, however. | |||
*/ | |||
} | |||
/** | |||
* Manages added properties in active rows. | |||
* | |||
* @param addedPropertyIds | |||
* the property ids that have been added to the container | |||
*/ | |||
public void propertiesAdded(Collection<Object> addedPropertyIds) { | |||
for (int i = activeRange.getStart(); i < activeRange.getEnd(); i++) { | |||
final Object itemId = datasource.getIdByIndex(i); | |||
final Item item = datasource.getItem(itemId); | |||
final GridValueChangeListener listener = valueChangeListeners | |||
.get(itemId); | |||
assert (listener != null) : "a listener should've been pre-made by addValueChangeListeners"; | |||
for (final Object propertyId : addedPropertyIds) { | |||
final Property<?> property = item | |||
.getItemProperty(propertyId); | |||
if (property instanceof ValueChangeNotifier) { | |||
((ValueChangeNotifier) property) | |||
.addValueChangeListener(listener); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Handles the insertion of rows. | |||
* <p> | |||
* This method's responsibilities are to: | |||
* <ul> | |||
* <li>shift the internal bookkeeping by <code>count</code> if the | |||
* insertion happens above currently active range | |||
* <li>ignore rows inserted below the currently active range | |||
* <li>shift (and deactivate) rows pushed out of view | |||
* <li>activate rows that are inserted in the current viewport | |||
* </ul> | |||
* | |||
* @param firstIndex | |||
* the index of the first inserted rows | |||
* @param count | |||
* the number of rows inserted at <code>firstIndex</code> | |||
*/ | |||
public void insertRows(int firstIndex, int count) { | |||
if (firstIndex < activeRange.getStart()) { | |||
activeRange = activeRange.offsetBy(count); | |||
} else if (firstIndex < activeRange.getEnd()) { | |||
final Range deprecatedRange = Range.withLength( | |||
activeRange.getEnd(), count); | |||
removeValueChangeListeners(deprecatedRange); | |||
final Range freshRange = Range.between(firstIndex, count); | |||
addValueChangeListeners(freshRange); | |||
} else { | |||
// out of view, noop | |||
} | |||
} | |||
/** | |||
* Removes a single item by its id. | |||
* | |||
* @param itemId | |||
* the id of the removed id. <em>Note:</em> this item does | |||
* not exist anymore in the datasource | |||
*/ | |||
public void removeItemId(Object itemId) { | |||
final GridValueChangeListener removedListener = valueChangeListeners | |||
.remove(itemId); | |||
if (removedListener != null) { | |||
/* | |||
* We removed an item from somewhere in the visible range, so we | |||
* make the active range shorter. The empty hole will be filled | |||
* by the client-side code when it asks for more information. | |||
*/ | |||
activeRange = Range.withLength(activeRange.getStart(), | |||
activeRange.length() - 1); | |||
} | |||
} | |||
} | |||
/** | |||
* A class to listen to changes in property values in the Container added | |||
* with {@link Grid#setContainerDatasource(Container.Indexed)}, and notifies | |||
* the data source to update the client-side representation of the modified | |||
* item. | |||
* <p> | |||
* One instance of this class can (and should) be reused for all the | |||
* properties in an item, since this class will inform that the entire row | |||
* needs to be re-evaluated (in contrast to a property-based change | |||
* management) | |||
* <p> | |||
* Since there's no Container-wide possibility to listen to any kind of | |||
* value changes, an instance of this class needs to be attached to each and | |||
* every Item's Property in the container. | |||
* | |||
* @see Grid#addValueChangeListener(Container, Object, Object) | |||
* @see Grid#valueChangeListeners | |||
*/ | |||
private class GridValueChangeListener implements ValueChangeListener { | |||
private final Object itemId; | |||
public GridValueChangeListener(Object itemId) { | |||
/* | |||
* Using an assert instead of an exception throw, just to optimize | |||
* prematurely | |||
*/ | |||
assert itemId != null : "null itemId not accepted"; | |||
this.itemId = itemId; | |||
} | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
datasourceExtension.updateRowData(datasource.indexOfId(itemId)); | |||
} | |||
} | |||
/** | |||
* The data source attached to the grid | |||
*/ | |||
private Container.Indexed datasource; | |||
/** | |||
* Property id to column instance mapping | |||
*/ | |||
private final Map<Object, GridColumn> columns = new HashMap<Object, GridColumn>(); | |||
/** | |||
* Key generator for column server-to-client communication | |||
*/ | |||
private final KeyMapper<Object> columnKeys = new KeyMapper<Object>(); | |||
/** | |||
* The column groups added to the grid | |||
*/ | |||
private final List<ColumnGroupRow> columnGroupRows = new ArrayList<ColumnGroupRow>(); | |||
/** | |||
* Property listener for listening to changes in data source properties. | |||
*/ | |||
private final PropertySetChangeListener propertyListener = new PropertySetChangeListener() { | |||
@Override | |||
public void containerPropertySetChange(PropertySetChangeEvent event) { | |||
Collection<?> properties = new HashSet<Object>(event.getContainer() | |||
.getContainerPropertyIds()); | |||
// Cleanup columns that are no longer in grid | |||
List<Object> removedColumns = new LinkedList<Object>(); | |||
for (Object columnId : columns.keySet()) { | |||
if (!properties.contains(columnId)) { | |||
removedColumns.add(columnId); | |||
} | |||
} | |||
for (Object columnId : removedColumns) { | |||
GridColumn column = columns.remove(columnId); | |||
columnKeys.remove(columnId); | |||
getState().columns.remove(column.getState()); | |||
} | |||
activeRowHandler.propertiesRemoved(removedColumns); | |||
// Add new columns | |||
HashSet<Object> addedPropertyIds = new HashSet<Object>(); | |||
for (Object propertyId : properties) { | |||
if (!columns.containsKey(propertyId)) { | |||
appendColumn(propertyId); | |||
addedPropertyIds.add(propertyId); | |||
} | |||
} | |||
activeRowHandler.propertiesAdded(addedPropertyIds); | |||
Object frozenPropertyId = columnKeys | |||
.get(getState(false).lastFrozenColumnId); | |||
if (!columns.containsKey(frozenPropertyId)) { | |||
setLastFrozenPropertyId(null); | |||
} | |||
} | |||
}; | |||
private ItemSetChangeListener itemListener = new ItemSetChangeListener() { | |||
@Override | |||
public void containerItemSetChange(ItemSetChangeEvent event) { | |||
if (event instanceof ItemAddEvent) { | |||
ItemAddEvent addEvent = (ItemAddEvent) event; | |||
int firstIndex = addEvent.getFirstIndex(); | |||
int count = addEvent.getAddedItemsCount(); | |||
datasourceExtension.insertRowData(firstIndex, count); | |||
activeRowHandler.insertRows(firstIndex, count); | |||
} | |||
else if (event instanceof ItemRemoveEvent) { | |||
ItemRemoveEvent removeEvent = (ItemRemoveEvent) event; | |||
int firstIndex = removeEvent.getFirstIndex(); | |||
int count = removeEvent.getRemovedItemsCount(); | |||
datasourceExtension.removeRowData(firstIndex, count); | |||
/* | |||
* Unfortunately, there's no sane way of getting the rest of the | |||
* removed itemIds. | |||
* | |||
* Fortunately, the only time _currently_ an event with more | |||
* than one removed item seems to be when calling | |||
* AbstractInMemoryContainer.removeAllElements(). Otherwise, | |||
* it's only removing one item at a time. | |||
* | |||
* We _could_ have a backup of all the itemIds, and compare to | |||
* that one, but we really really don't want to go there. | |||
*/ | |||
activeRowHandler.removeItemId(removeEvent.getFirstItemId()); | |||
} | |||
else { | |||
// TODO no diff info available, redraw everything | |||
throw new UnsupportedOperationException("bare " | |||
+ "ItemSetChangeEvents are currently " | |||
+ "not supported, use a container that " | |||
+ "uses AddItemEvents and RemoveItemEvents."); | |||
} | |||
} | |||
}; | |||
private RpcDataProviderExtension datasourceExtension; | |||
private final ActiveRowHandler activeRowHandler = new ActiveRowHandler(); | |||
/** | |||
* Creates a new Grid using the given datasource. | |||
* | |||
* @param datasource | |||
* the data source for the grid | |||
*/ | |||
public Grid(Container.Indexed datasource) { | |||
setContainerDatasource(datasource); | |||
registerRpc(new GridServerRpc() { | |||
@Override | |||
public void setVisibleRows(int firstVisibleRow, int visibleRowCount) { | |||
activeRowHandler | |||
.setActiveRows(firstVisibleRow, visibleRowCount); | |||
} | |||
}); | |||
} | |||
/** | |||
* Sets the grid data source. | |||
* | |||
* @param container | |||
* The container data source. Cannot be null. | |||
* @throws IllegalArgumentException | |||
* if the data source is null | |||
*/ | |||
public void setContainerDatasource(Container.Indexed container) { | |||
if (container == null) { | |||
throw new IllegalArgumentException( | |||
"Cannot set the datasource to null"); | |||
} | |||
if (datasource == container) { | |||
return; | |||
} | |||
// Remove old listeners | |||
if (datasource instanceof PropertySetChangeNotifier) { | |||
((PropertySetChangeNotifier) datasource) | |||
.removePropertySetChangeListener(propertyListener); | |||
} | |||
if (datasource instanceof ItemSetChangeNotifier) { | |||
((ItemSetChangeNotifier) datasource) | |||
.removeItemSetChangeListener(itemListener); | |||
} | |||
activeRowHandler.clear(); | |||
if (datasourceExtension != null) { | |||
removeExtension(datasourceExtension); | |||
} | |||
datasource = container; | |||
datasourceExtension = new RpcDataProviderExtension(container); | |||
datasourceExtension.extend(this); | |||
// Listen to changes in properties and remove columns if needed | |||
if (datasource instanceof PropertySetChangeNotifier) { | |||
((PropertySetChangeNotifier) datasource) | |||
.addPropertySetChangeListener(propertyListener); | |||
} | |||
if (datasource instanceof ItemSetChangeNotifier) { | |||
((ItemSetChangeNotifier) datasource) | |||
.addItemSetChangeListener(itemListener); | |||
} | |||
/* | |||
* activeRowHandler will be updated by the client-side request that | |||
* occurs on container change - no need to actively re-insert any | |||
* ValueChangeListeners at this point. | |||
*/ | |||
getState().columns.clear(); | |||
setLastFrozenPropertyId(null); | |||
// Add columns | |||
for (Object propertyId : datasource.getContainerPropertyIds()) { | |||
if (!columns.containsKey(propertyId)) { | |||
GridColumn column = appendColumn(propertyId); | |||
// Add by default property id as column header | |||
column.setHeaderCaption(String.valueOf(propertyId)); | |||
} | |||
} | |||
} | |||
/** | |||
* Returns the grid data source. | |||
* | |||
* @return the container data source of the grid | |||
*/ | |||
public Container.Indexed getContainerDatasource() { | |||
return datasource; | |||
} | |||
/** | |||
* Returns a column based on the property id | |||
* | |||
* @param propertyId | |||
* the property id of the column | |||
* @return the column or <code>null</code> if not found | |||
*/ | |||
public GridColumn getColumn(Object propertyId) { | |||
return columns.get(propertyId); | |||
} | |||
/** | |||
* Sets the header rows visible. | |||
* | |||
* @param visible | |||
* <code>true</code> if the header rows should be visible | |||
*/ | |||
public void setColumnHeadersVisible(boolean visible) { | |||
getState().columnHeadersVisible = visible; | |||
} | |||
/** | |||
* Are the header rows visible? | |||
* | |||
* @return <code>true</code> if the headers of the columns are visible | |||
*/ | |||
public boolean isColumnHeadersVisible() { | |||
return getState(false).columnHeadersVisible; | |||
} | |||
/** | |||
* Sets the footer rows visible. | |||
* | |||
* @param visible | |||
* <code>true</code> if the footer rows should be visible | |||
*/ | |||
public void setColumnFootersVisible(boolean visible) { | |||
getState().columnFootersVisible = visible; | |||
} | |||
/** | |||
* Are the footer rows visible. | |||
* | |||
* @return <code>true</code> if the footer rows should be visible | |||
*/ | |||
public boolean isColumnFootersVisible() { | |||
return getState(false).columnFootersVisible; | |||
} | |||
/** | |||
* <p> | |||
* Adds a new column group to the grid. | |||
* | |||
* <p> | |||
* Column group rows are rendered in the header and footer of the grid. | |||
* Column group rows are made up of column groups which groups together | |||
* columns for adding a common auxiliary header or footer for the columns. | |||
* </p> | |||
* </p> | |||
* | |||
* <p> | |||
* Example usage: | |||
* | |||
* <pre> | |||
* // Add a new column group row to the grid | |||
* ColumnGroupRow row = grid.addColumnGroupRow(); | |||
* | |||
* // Group "Column1" and "Column2" together to form a header in the row | |||
* ColumnGroup column12 = row.addGroup("Column1", "Column2"); | |||
* | |||
* // Set a common header for "Column1" and "Column2" | |||
* column12.setHeader("Column 1&2"); | |||
* </pre> | |||
* | |||
* </p> | |||
* | |||
* @return a column group instance you can use to add column groups | |||
*/ | |||
public ColumnGroupRow addColumnGroupRow() { | |||
ColumnGroupRowState state = new ColumnGroupRowState(); | |||
ColumnGroupRow row = new ColumnGroupRow(this, state, columnKeys); | |||
columnGroupRows.add(row); | |||
getState().columnGroupRows.add(state); | |||
return row; | |||
} | |||
/** | |||
* Adds a new column group to the grid at a specific index | |||
* | |||
* @param rowIndex | |||
* the index of the row | |||
* @return a column group instance you can use to add column groups | |||
*/ | |||
public ColumnGroupRow addColumnGroupRow(int rowIndex) { | |||
ColumnGroupRowState state = new ColumnGroupRowState(); | |||
ColumnGroupRow row = new ColumnGroupRow(this, state, columnKeys); | |||
columnGroupRows.add(rowIndex, row); | |||
getState().columnGroupRows.add(rowIndex, state); | |||
return row; | |||
} | |||
/** | |||
* Removes a column group. | |||
* | |||
* @param row | |||
* the row to remove | |||
*/ | |||
public void removeColumnGroupRow(ColumnGroupRow row) { | |||
columnGroupRows.remove(row); | |||
getState().columnGroupRows.remove(row.getState()); | |||
} | |||
/** | |||
* Gets the column group rows. | |||
* | |||
* @return an unmodifiable list of column group rows | |||
*/ | |||
public List<ColumnGroupRow> getColumnGroupRows() { | |||
return Collections.unmodifiableList(new ArrayList<ColumnGroupRow>( | |||
columnGroupRows)); | |||
} | |||
/** | |||
* Used internally by the {@link Grid} to get a {@link GridColumn} by | |||
* referencing its generated state id. Also used by {@link GridColumn} to | |||
* verify if it has been detached from the {@link Grid}. | |||
* | |||
* @param columnId | |||
* the client id generated for the column when the column is | |||
* added to the grid | |||
* @return the column with the id or <code>null</code> if not found | |||
*/ | |||
GridColumn getColumnByColumnId(String columnId) { | |||
Object propertyId = getPropertyIdByColumnId(columnId); | |||
return getColumn(propertyId); | |||
} | |||
/** | |||
* Used internally by the {@link Grid} to get a property id by referencing | |||
* the columns generated state id. | |||
* | |||
* @param columnId | |||
* The state id of the column | |||
* @return The column instance or null if not found | |||
*/ | |||
Object getPropertyIdByColumnId(String columnId) { | |||
return columnKeys.get(columnId); | |||
} | |||
@Override | |||
protected GridState getState() { | |||
return (GridState) super.getState(); | |||
} | |||
@Override | |||
protected GridState getState(boolean markAsDirty) { | |||
return (GridState) super.getState(markAsDirty); | |||
} | |||
/** | |||
* Creates a new column based on a property id and appends it as the last | |||
* column. | |||
* | |||
* @param datasourcePropertyId | |||
* The property id of a property in the datasource | |||
*/ | |||
private GridColumn appendColumn(Object datasourcePropertyId) { | |||
if (datasourcePropertyId == null) { | |||
throw new IllegalArgumentException("Property id cannot be null"); | |||
} | |||
assert datasource.getContainerPropertyIds().contains( | |||
datasourcePropertyId) : "Datasource should contain the property id"; | |||
GridColumnState columnState = new GridColumnState(); | |||
columnState.id = columnKeys.key(datasourcePropertyId); | |||
getState().columns.add(columnState); | |||
GridColumn column = new GridColumn(this, columnState); | |||
columns.put(datasourcePropertyId, column); | |||
return column; | |||
} | |||
/** | |||
* Sets (or unsets) the rightmost frozen column in the grid. | |||
* <p> | |||
* All columns up to and including the given column will be frozen in place | |||
* when the grid is scrolled sideways. | |||
* | |||
* @param lastFrozenColumn | |||
* the rightmost column to freeze, or <code>null</code> to not | |||
* have any columns frozen | |||
* @throws IllegalArgumentException | |||
* if {@code lastFrozenColumn} is not a column from this grid | |||
*/ | |||
void setLastFrozenColumn(GridColumn lastFrozenColumn) { | |||
/* | |||
* TODO: If and when Grid supports column reordering or insertion of | |||
* columns before other columns, make sure to mention that adding | |||
* columns before lastFrozenColumn will change the frozen column count | |||
*/ | |||
if (lastFrozenColumn == null) { | |||
getState().lastFrozenColumnId = null; | |||
} else if (columns.containsValue(lastFrozenColumn)) { | |||
getState().lastFrozenColumnId = lastFrozenColumn.getState().id; | |||
} else { | |||
throw new IllegalArgumentException( | |||
"The given column isn't attached to this grid"); | |||
} | |||
} | |||
/** | |||
* Sets (or unsets) the rightmost frozen column in the grid. | |||
* <p> | |||
* All columns up to and including the indicated property will be frozen in | |||
* place when the grid is scrolled sideways. | |||
* <p> | |||
* <em>Note:</em> If the container used by this grid supports a propertyId | |||
* <code>null</code>, it can never be defined as the last frozen column, as | |||
* a <code>null</code> parameter will always reset the frozen columns in | |||
* Grid. | |||
* | |||
* @param propertyId | |||
* the property id corresponding to the column that should be the | |||
* last frozen column, or <code>null</code> to not have any | |||
* columns frozen. | |||
* @throws IllegalArgumentException | |||
* if {@code lastFrozenColumn} is not a column from this grid | |||
*/ | |||
public void setLastFrozenPropertyId(Object propertyId) { | |||
final GridColumn column; | |||
if (propertyId == null) { | |||
column = null; | |||
} else { | |||
column = getColumn(propertyId); | |||
if (column == null) { | |||
throw new IllegalArgumentException( | |||
"property id does not exist."); | |||
} | |||
} | |||
setLastFrozenColumn(column); | |||
} | |||
/** | |||
* Gets the rightmost frozen column in the grid. | |||
* <p> | |||
* <em>Note:</em> Most often, this method returns the very value set with | |||
* {@link #setLastFrozenPropertyId(Object)}. This value, however, can be | |||
* reset to <code>null</code> if the column is detached from this grid. | |||
* | |||
* @return the rightmost frozen column in the grid, or <code>null</code> if | |||
* no columns are frozen. | |||
*/ | |||
public Object getLastFrozenPropertyId() { | |||
return columnKeys.get(getState().lastFrozenColumnId); | |||
} | |||
/** | |||
* Scrolls to a certain item, using {@link ScrollDestination#ANY}. | |||
* | |||
* @param itemId | |||
* id of item to scroll to. | |||
* @throws IllegalArgumentException | |||
* if the provided id is not recognized by the data source. | |||
*/ | |||
public void scrollToItem(Object itemId) throws IllegalArgumentException { | |||
scrollToItem(itemId, ScrollDestination.ANY); | |||
} | |||
/** | |||
* Scrolls to a certain item, using user-specified scroll destination. | |||
* | |||
* @param itemId | |||
* id of item to scroll to. | |||
* @param destination | |||
* value specifying desired position of scrolled-to row. | |||
* @throws IllegalArgumentException | |||
* if the provided id is not recognized by the data source. | |||
*/ | |||
public void scrollToItem(Object itemId, ScrollDestination destination) | |||
throws IllegalArgumentException { | |||
int row = datasource.indexOfId(itemId); | |||
if (row == -1) { | |||
throw new IllegalArgumentException( | |||
"Item with specified ID does not exist in data source"); | |||
} | |||
GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class); | |||
clientRPC.scrollToRow(row, destination); | |||
} | |||
/** | |||
* Scrolls to the beginning of the first data row. | |||
*/ | |||
public void scrollToStart() { | |||
GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class); | |||
clientRPC.scrollToStart(); | |||
} | |||
/** | |||
* Scrolls to the end of the last data row. | |||
*/ | |||
public void scrollToEnd() { | |||
GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class); | |||
clientRPC.scrollToEnd(); | |||
} | |||
} |
@@ -0,0 +1,216 @@ | |||
/* | |||
* Copyright 2000-2013 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.ui.components.grid; | |||
import java.io.Serializable; | |||
import com.vaadin.shared.ui.grid.GridColumnState; | |||
/** | |||
* A column in the grid. Can be obtained by calling | |||
* {@link Grid#getColumn(Object propertyId)}. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridColumn implements Serializable { | |||
/** | |||
* The state of the column shared to the client | |||
*/ | |||
private final GridColumnState state; | |||
/** | |||
* The grid this column is associated with | |||
*/ | |||
private final Grid grid; | |||
/** | |||
* Internally used constructor. | |||
* | |||
* @param grid | |||
* The grid this column belongs to. Should not be null. | |||
* @param state | |||
* the shared state of this column | |||
*/ | |||
GridColumn(Grid grid, GridColumnState state) { | |||
this.grid = grid; | |||
this.state = state; | |||
} | |||
/** | |||
* Returns the serializable state of this column that is sent to the client | |||
* side connector. | |||
* | |||
* @return the internal state of the column | |||
*/ | |||
GridColumnState getState() { | |||
return state; | |||
} | |||
/** | |||
* Returns the caption of the header. By default the header caption is the | |||
* property id of the column. | |||
* | |||
* @return the text in the header | |||
* | |||
* @throws IllegalStateException | |||
* if the column no longer is attached to the grid | |||
*/ | |||
public String getHeaderCaption() throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
return state.header; | |||
} | |||
/** | |||
* Sets the caption of the header. | |||
* | |||
* @param caption | |||
* the text to show in the caption | |||
* | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public void setHeaderCaption(String caption) throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
state.header = caption; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Returns the caption of the footer. By default the captions are | |||
* <code>null</code>. | |||
* | |||
* @return the text in the footer | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public String getFooterCaption() throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
return state.footer; | |||
} | |||
/** | |||
* Sets the caption of the footer. | |||
* | |||
* @param caption | |||
* the text to show in the caption | |||
* | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public void setFooterCaption(String caption) throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
state.footer = caption; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Returns the width (in pixels). By default a column is 100px wide. | |||
* | |||
* @return the width in pixels of the column | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public int getWidth() throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
return state.width; | |||
} | |||
/** | |||
* Sets the width (in pixels). | |||
* | |||
* @param pixelWidth | |||
* the new pixel width of the column | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
* @throws IllegalArgumentException | |||
* thrown if pixel width is less than zero | |||
*/ | |||
public void setWidth(int pixelWidth) throws IllegalStateException, | |||
IllegalArgumentException { | |||
checkColumnIsAttached(); | |||
if (pixelWidth < 0) { | |||
throw new IllegalArgumentException( | |||
"Pixel width should be greated than 0"); | |||
} | |||
state.width = pixelWidth; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Marks the column width as undefined meaning that the grid is free to | |||
* resize the column based on the cell contents and available space in the | |||
* grid. | |||
*/ | |||
public void setWidthUndefined() { | |||
checkColumnIsAttached(); | |||
state.width = -1; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Is this column visible in the grid. By default all columns are visible. | |||
* | |||
* @return <code>true</code> if the column is visible | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public boolean isVisible() throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
return state.visible; | |||
} | |||
/** | |||
* Set the visibility of this column | |||
* | |||
* @param visible | |||
* is the column visible | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
public void setVisible(boolean visible) throws IllegalStateException { | |||
checkColumnIsAttached(); | |||
state.visible = visible; | |||
grid.markAsDirty(); | |||
} | |||
/** | |||
* Checks if column is attached and throws an {@link IllegalStateException} | |||
* if it is not | |||
* | |||
* @throws IllegalStateException | |||
* if the column is no longer attached to any grid | |||
*/ | |||
protected void checkColumnIsAttached() throws IllegalStateException { | |||
if (grid.getColumnByColumnId(state.id) == null) { | |||
throw new IllegalStateException("Column no longer exists."); | |||
} | |||
} | |||
/** | |||
* Sets this column as the last frozen column in its grid. | |||
* | |||
* @throws IllegalArgumentException | |||
* if the column is no longer attached to any grid | |||
* @see Grid#setLastFrozenColumn(GridColumn) | |||
*/ | |||
public void setLastFrozenColumn() { | |||
checkColumnIsAttached(); | |||
grid.setLastFrozenColumn(this); | |||
} | |||
} |
@@ -0,0 +1,265 @@ | |||
/* | |||
* Copyright 2000-2013 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.server.component.grid; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.lang.reflect.Field; | |||
import java.lang.reflect.Method; | |||
import java.util.List; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.IndexedContainer; | |||
import com.vaadin.server.KeyMapper; | |||
import com.vaadin.shared.ui.grid.GridState; | |||
import com.vaadin.ui.components.grid.ColumnGroup; | |||
import com.vaadin.ui.components.grid.ColumnGroupRow; | |||
import com.vaadin.ui.components.grid.Grid; | |||
/** | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridColumnGroups { | |||
private Grid grid; | |||
private GridState state; | |||
private Method getStateMethod; | |||
private Field columnIdGeneratorField; | |||
private KeyMapper<Object> columnIdMapper; | |||
@Before | |||
public void setup() throws Exception { | |||
IndexedContainer ds = new IndexedContainer(); | |||
for (int c = 0; c < 10; c++) { | |||
ds.addContainerProperty("column" + c, String.class, ""); | |||
} | |||
grid = new Grid(ds); | |||
getStateMethod = Grid.class.getDeclaredMethod("getState"); | |||
getStateMethod.setAccessible(true); | |||
state = (GridState) getStateMethod.invoke(grid); | |||
columnIdGeneratorField = Grid.class.getDeclaredField("columnKeys"); | |||
columnIdGeneratorField.setAccessible(true); | |||
columnIdMapper = (KeyMapper<Object>) columnIdGeneratorField.get(grid); | |||
} | |||
@Test | |||
public void testColumnGroupRows() throws Exception { | |||
// No column group rows by default | |||
List<ColumnGroupRow> rows = grid.getColumnGroupRows(); | |||
assertEquals(0, rows.size()); | |||
// Add some rows | |||
ColumnGroupRow row1 = grid.addColumnGroupRow(); | |||
ColumnGroupRow row3 = grid.addColumnGroupRow(); | |||
ColumnGroupRow row2 = grid.addColumnGroupRow(1); | |||
rows = grid.getColumnGroupRows(); | |||
assertEquals(3, rows.size()); | |||
assertEquals(row1, rows.get(0)); | |||
assertEquals(row2, rows.get(1)); | |||
assertEquals(row3, rows.get(2)); | |||
// Header should be visible by default, footer should not | |||
assertTrue(row1.isHeaderVisible()); | |||
assertFalse(row1.isFooterVisible()); | |||
row1.setHeaderVisible(false); | |||
assertFalse(row1.isHeaderVisible()); | |||
row1.setHeaderVisible(true); | |||
assertTrue(row1.isHeaderVisible()); | |||
row1.setFooterVisible(true); | |||
assertTrue(row1.isFooterVisible()); | |||
row1.setFooterVisible(false); | |||
assertFalse(row1.isFooterVisible()); | |||
row1.setHeaderVisible(true); | |||
row1.setFooterVisible(true); | |||
assertTrue(row1.isHeaderVisible()); | |||
assertTrue(row1.isFooterVisible()); | |||
row1.setHeaderVisible(false); | |||
row1.setFooterVisible(false); | |||
assertFalse(row1.isHeaderVisible()); | |||
assertFalse(row1.isFooterVisible()); | |||
} | |||
@Test | |||
public void testColumnGroupsInState() throws Exception { | |||
// Add a new row | |||
ColumnGroupRow row = grid.addColumnGroupRow(); | |||
assertTrue(state.columnGroupRows.size() == 1); | |||
// Add a group by property id | |||
ColumnGroup columns12 = row.addGroup("column1", "column2"); | |||
assertTrue(state.columnGroupRows.get(0).groups.size() == 1); | |||
// Set header of column | |||
columns12.setHeaderCaption("Column12"); | |||
assertEquals("Column12", | |||
state.columnGroupRows.get(0).groups.get(0).header); | |||
// Set footer of column | |||
columns12.setFooterCaption("Footer12"); | |||
assertEquals("Footer12", | |||
state.columnGroupRows.get(0).groups.get(0).footer); | |||
// Add another group by column instance | |||
ColumnGroup columns34 = row.addGroup(grid.getColumn("column3"), | |||
grid.getColumn("column4")); | |||
assertTrue(state.columnGroupRows.get(0).groups.size() == 2); | |||
// add another group row | |||
ColumnGroupRow row2 = grid.addColumnGroupRow(); | |||
assertTrue(state.columnGroupRows.size() == 2); | |||
// add a group by combining the two previous groups | |||
ColumnGroup columns1234 = row2.addGroup(columns12, columns34); | |||
assertTrue(columns1234.getColumns().size() == 4); | |||
// Insert a group as the second group | |||
ColumnGroupRow newRow2 = grid.addColumnGroupRow(1); | |||
assertTrue(state.columnGroupRows.size() == 3); | |||
} | |||
@Test | |||
public void testAddingColumnGroups() throws Exception { | |||
ColumnGroupRow row = grid.addColumnGroupRow(); | |||
// By property id | |||
ColumnGroup columns01 = row.addGroup("column0", "column1"); | |||
assertEquals(2, columns01.getColumns().size()); | |||
assertEquals("column0", columns01.getColumns().get(0)); | |||
assertTrue(columns01.isColumnInGroup("column0")); | |||
assertEquals("column1", columns01.getColumns().get(1)); | |||
assertTrue(columns01.isColumnInGroup("column1")); | |||
// By grid column | |||
ColumnGroup columns23 = row.addGroup(grid.getColumn("column2"), | |||
grid.getColumn("column3")); | |||
assertEquals(2, columns23.getColumns().size()); | |||
assertEquals("column2", columns23.getColumns().get(0)); | |||
assertTrue(columns23.isColumnInGroup("column2")); | |||
assertEquals("column3", columns23.getColumns().get(1)); | |||
assertTrue(columns23.isColumnInGroup("column3")); | |||
// Combine groups | |||
ColumnGroupRow row2 = grid.addColumnGroupRow(); | |||
ColumnGroup columns0123 = row2.addGroup(columns01, columns23); | |||
assertEquals(4, columns0123.getColumns().size()); | |||
assertEquals("column0", columns0123.getColumns().get(0)); | |||
assertTrue(columns0123.isColumnInGroup("column0")); | |||
assertEquals("column1", columns0123.getColumns().get(1)); | |||
assertTrue(columns0123.isColumnInGroup("column1")); | |||
assertEquals("column2", columns0123.getColumns().get(2)); | |||
assertTrue(columns0123.isColumnInGroup("column2")); | |||
assertEquals("column3", columns0123.getColumns().get(3)); | |||
assertTrue(columns0123.isColumnInGroup("column3")); | |||
} | |||
@Test | |||
public void testColumnGroupHeadersAndFooters() throws Exception { | |||
ColumnGroupRow row = grid.addColumnGroupRow(); | |||
ColumnGroup group = row.addGroup("column1", "column2"); | |||
// Header | |||
assertNull(group.getHeaderCaption()); | |||
group.setHeaderCaption("My header"); | |||
assertEquals("My header", group.getHeaderCaption()); | |||
group.setHeaderCaption(null); | |||
assertNull(group.getHeaderCaption()); | |||
// Footer | |||
assertNull(group.getFooterCaption()); | |||
group.setFooterCaption("My footer"); | |||
assertEquals("My footer", group.getFooterCaption()); | |||
group.setFooterCaption(null); | |||
assertNull(group.getFooterCaption()); | |||
} | |||
@Test | |||
public void testColumnGroupDetachment() throws Exception { | |||
ColumnGroupRow row = grid.addColumnGroupRow(); | |||
ColumnGroup group = row.addGroup("column1", "column2"); | |||
// Remove group | |||
row.removeGroup(group); | |||
try { | |||
group.setHeaderCaption("Header"); | |||
fail("Should throw exception for setting header caption on detached group"); | |||
} catch (IllegalStateException ise) { | |||
} | |||
try { | |||
group.setFooterCaption("Footer"); | |||
fail("Should throw exception for setting footer caption on detached group"); | |||
} catch (IllegalStateException ise) { | |||
} | |||
} | |||
@Test | |||
public void testColumnGroupLimits() throws Exception { | |||
ColumnGroupRow row = grid.addColumnGroupRow(); | |||
row.addGroup("column1", "column2"); | |||
row.addGroup("column3", "column4"); | |||
try { | |||
row.addGroup("column2", "column3"); | |||
fail("Adding a group with already grouped properties should throw exception"); | |||
} catch (IllegalArgumentException iae) { | |||
} | |||
ColumnGroupRow row2 = grid.addColumnGroupRow(); | |||
try { | |||
row2.addGroup("column2", "column3"); | |||
fail("Adding a group that breaks previous grouping boundaries should throw exception"); | |||
} catch (IllegalArgumentException iae) { | |||
} | |||
// This however should not throw an exception as it spans completely | |||
// over the parent rows groups | |||
row2.addGroup("column1", "column2", "column3", "column4"); | |||
} | |||
} |
@@ -0,0 +1,228 @@ | |||
/* | |||
* Copyright 2000-2013 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.server.component.grid; | |||
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 static org.junit.Assert.fail; | |||
import java.lang.reflect.Field; | |||
import java.lang.reflect.Method; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.IndexedContainer; | |||
import com.vaadin.server.KeyMapper; | |||
import com.vaadin.shared.ui.grid.GridColumnState; | |||
import com.vaadin.shared.ui.grid.GridState; | |||
import com.vaadin.ui.components.grid.Grid; | |||
import com.vaadin.ui.components.grid.GridColumn; | |||
public class GridColumns { | |||
private Grid grid; | |||
private GridState state; | |||
private Method getStateMethod; | |||
private Field columnIdGeneratorField; | |||
private KeyMapper<Object> columnIdMapper; | |||
@Before | |||
public void setup() throws Exception { | |||
IndexedContainer ds = new IndexedContainer(); | |||
for (int c = 0; c < 10; c++) { | |||
ds.addContainerProperty("column" + c, String.class, ""); | |||
} | |||
grid = new Grid(ds); | |||
getStateMethod = Grid.class.getDeclaredMethod("getState"); | |||
getStateMethod.setAccessible(true); | |||
state = (GridState) getStateMethod.invoke(grid); | |||
columnIdGeneratorField = Grid.class.getDeclaredField("columnKeys"); | |||
columnIdGeneratorField.setAccessible(true); | |||
columnIdMapper = (KeyMapper<Object>) columnIdGeneratorField.get(grid); | |||
} | |||
@Test | |||
public void testColumnGeneration() throws Exception { | |||
for (Object propertyId : grid.getContainerDatasource() | |||
.getContainerPropertyIds()) { | |||
// All property ids should get a column | |||
GridColumn column = grid.getColumn(propertyId); | |||
assertNotNull(column); | |||
// Property id should be the column header by default | |||
assertEquals(propertyId.toString(), column.getHeaderCaption()); | |||
} | |||
} | |||
@Test | |||
public void testModifyingColumnProperties() throws Exception { | |||
// Modify first column | |||
GridColumn column = grid.getColumn("column1"); | |||
assertNotNull(column); | |||
column.setFooterCaption("CustomFooter"); | |||
assertEquals("CustomFooter", column.getFooterCaption()); | |||
assertEquals(column.getFooterCaption(), | |||
getColumnState("column1").footer); | |||
column.setHeaderCaption("CustomHeader"); | |||
assertEquals("CustomHeader", column.getHeaderCaption()); | |||
assertEquals(column.getHeaderCaption(), | |||
getColumnState("column1").header); | |||
column.setVisible(false); | |||
assertFalse(column.isVisible()); | |||
assertFalse(getColumnState("column1").visible); | |||
column.setVisible(true); | |||
assertTrue(column.isVisible()); | |||
assertTrue(getColumnState("column1").visible); | |||
column.setWidth(100); | |||
assertEquals(100, column.getWidth()); | |||
assertEquals(column.getWidth(), getColumnState("column1").width); | |||
try { | |||
column.setWidth(-1); | |||
fail("Setting width to -1 should throw exception"); | |||
} catch (IllegalArgumentException iae) { | |||
} | |||
assertEquals(100, column.getWidth()); | |||
assertEquals(100, getColumnState("column1").width); | |||
} | |||
@Test | |||
public void testRemovingColumn() throws Exception { | |||
GridColumn column = grid.getColumn("column1"); | |||
assertNotNull(column); | |||
// Remove column | |||
grid.getContainerDatasource().removeContainerProperty("column1"); | |||
try { | |||
column.setHeaderCaption("asd"); | |||
fail("Succeeded in modifying a detached column"); | |||
} catch (IllegalStateException ise) { | |||
// Detached state should throw exception | |||
} | |||
try { | |||
column.setFooterCaption("asd"); | |||
fail("Succeeded in modifying a detached column"); | |||
} catch (IllegalStateException ise) { | |||
// Detached state should throw exception | |||
} | |||
try { | |||
column.setVisible(false); | |||
fail("Succeeded in modifying a detached column"); | |||
} catch (IllegalStateException ise) { | |||
// Detached state should throw exception | |||
} | |||
try { | |||
column.setWidth(123); | |||
fail("Succeeded in modifying a detached column"); | |||
} catch (IllegalStateException ise) { | |||
// Detached state should throw exception | |||
} | |||
assertNull(grid.getColumn("column1")); | |||
assertNull(getColumnState("column1")); | |||
} | |||
@Test | |||
public void testAddingColumn() throws Exception { | |||
grid.getContainerDatasource().addContainerProperty("columnX", | |||
String.class, ""); | |||
GridColumn column = grid.getColumn("columnX"); | |||
assertNotNull(column); | |||
} | |||
@Test | |||
public void testHeaderVisiblility() throws Exception { | |||
assertTrue(grid.isColumnHeadersVisible()); | |||
assertTrue(state.columnHeadersVisible); | |||
grid.setColumnHeadersVisible(false); | |||
assertFalse(grid.isColumnHeadersVisible()); | |||
assertFalse(state.columnHeadersVisible); | |||
grid.setColumnHeadersVisible(true); | |||
assertTrue(grid.isColumnHeadersVisible()); | |||
assertTrue(state.columnHeadersVisible); | |||
} | |||
@Test | |||
public void testFooterVisibility() throws Exception { | |||
assertFalse(grid.isColumnFootersVisible()); | |||
assertFalse(state.columnFootersVisible); | |||
grid.setColumnFootersVisible(false); | |||
assertFalse(grid.isColumnFootersVisible()); | |||
assertFalse(state.columnFootersVisible); | |||
grid.setColumnFootersVisible(true); | |||
assertTrue(grid.isColumnFootersVisible()); | |||
assertTrue(state.columnFootersVisible); | |||
} | |||
@Test | |||
public void testFrozenColumnByPropertyId() { | |||
assertNull("Grid should not start with a frozen column", | |||
grid.getLastFrozenPropertyId()); | |||
Object propertyId = grid.getContainerDatasource() | |||
.getContainerPropertyIds().iterator().next(); | |||
grid.setLastFrozenPropertyId(propertyId); | |||
assertEquals(propertyId, grid.getLastFrozenPropertyId()); | |||
grid.getContainerDatasource().removeContainerProperty(propertyId); | |||
assertNull(grid.getLastFrozenPropertyId()); | |||
} | |||
private GridColumnState getColumnState(Object propertyId) { | |||
String columnId = columnIdMapper.key(propertyId); | |||
for (GridColumnState columnState : state.columns) { | |||
if (columnState.id.equals(columnId)) { | |||
return columnState; | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
import java.util.List; | |||
import com.vaadin.shared.communication.ClientRpc; | |||
/** | |||
* RPC interface used for pushing container data to the client. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface DataProviderRpc extends ClientRpc { | |||
/** | |||
* Sends updated row data to a client. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first updated row | |||
* @param rowData | |||
* the updated row data | |||
*/ | |||
public void setRowData(int firstRowIndex, List<String[]> rowData); | |||
/** | |||
* Informs the client to remove row data. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first removed row | |||
* @param count | |||
* the number of rows removed from <code>firstRowIndex</code> and | |||
* onwards | |||
*/ | |||
public void removeRowData(int firstRowIndex, int count); | |||
/** | |||
* Informs the client to insert new row data. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first new row | |||
* @param count | |||
* the number of rows inserted at <code>firstRowIndex</code> | |||
*/ | |||
public void insertRowData(int firstRowIndex, int count); | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
import com.vaadin.shared.communication.SharedState; | |||
/** | |||
* Shared state used by client-side data sources. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class DataProviderState extends SharedState { | |||
/** | |||
* The size of the container. | |||
*/ | |||
public int containerSize; | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* Copyright 2000-2013 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.data; | |||
import com.vaadin.shared.communication.ServerRpc; | |||
/** | |||
* RPC interface used for requesting container data to the client. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface DataRequestRpc extends ServerRpc { | |||
/** | |||
* Request rows from the server. | |||
* | |||
* @param firstRowIndex | |||
* the index of the first requested row | |||
* @param numberOfRows | |||
* the number of requested rows | |||
*/ | |||
public void requestRows(int firstRowIndex, int numberOfRows); | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* Copyright 2000-2013 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.ArrayList; | |||
import java.util.List; | |||
/** | |||
* The column group row data shared between the server and client | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnGroupRowState implements Serializable { | |||
/** | |||
* The groups that has been added to the row | |||
*/ | |||
public List<ColumnGroupState> groups = new ArrayList<ColumnGroupState>(); | |||
/** | |||
* Is the header shown | |||
*/ | |||
public boolean headerVisible = true; | |||
/** | |||
* Is the footer shown | |||
*/ | |||
public boolean footerVisible = false; | |||
} |
@@ -0,0 +1,45 @@ | |||
/* | |||
* Copyright 2000-2013 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.ArrayList; | |||
import java.util.List; | |||
/** | |||
* The column group data shared between the server and the client | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ColumnGroupState implements Serializable { | |||
/** | |||
* The columns that is included in the group | |||
*/ | |||
public List<String> columns = new ArrayList<String>(); | |||
/** | |||
* The header text of the group | |||
*/ | |||
public String header; | |||
/** | |||
* The footer text of the group | |||
*/ | |||
public String footer; | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* Copyright 2000-2013 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 com.vaadin.shared.communication.ClientRpc; | |||
/** | |||
* Server-to-client RPC interface for the Grid component. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface GridClientRpc extends ClientRpc { | |||
/** | |||
* Command client Grid to scroll to a specific data row. | |||
* | |||
* @param row | |||
* zero-based row index. If the row index is below zero or above | |||
* the row count of the client-side data source, a client-side | |||
* exception will be triggered. Since this exception has no | |||
* handling by default, an out-of-bounds value will cause a | |||
* client-side crash. | |||
* @param destination | |||
* desired placement of scrolled-to row. See the documentation | |||
* for {@link ScrollDestination} for more information. | |||
*/ | |||
public void scrollToRow(int row, ScrollDestination destination); | |||
/** | |||
* Command client Grid to scroll to the first row. | |||
*/ | |||
public void scrollToStart(); | |||
/** | |||
* Command client Grid to scroll to the last row. | |||
*/ | |||
public void scrollToEnd(); | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* Copyright 2000-2013 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; | |||
/** | |||
* Column state DTO for transferring column properties from the server to the | |||
* client | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridColumnState implements Serializable { | |||
/** | |||
* Id used by grid connector to map server side column with client side | |||
* column | |||
*/ | |||
public String id; | |||
/** | |||
* Header caption for the column | |||
*/ | |||
public String header; | |||
/** | |||
* Footer caption for the column | |||
*/ | |||
public String footer; | |||
/** | |||
* Has the column been hidden. By default the column is visible. | |||
*/ | |||
public boolean visible = true; | |||
/** | |||
* Column width in pixels. Default column width is 100px. | |||
*/ | |||
public int width = 100; | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* Copyright 2000-2013 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; | |||
/** | |||
* Container class for common constants and default values used by the Grid | |||
* component. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public final class GridConstants { | |||
/** | |||
* Default padding in pixels when scrolling programmatically, without an | |||
* explicitly defined padding value. | |||
*/ | |||
public static final int DEFAULT_PADDING = 0; | |||
} |
@@ -0,0 +1,39 @@ | |||
/* | |||
* Copyright 2000-2013 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 com.vaadin.shared.communication.ServerRpc; | |||
/** | |||
* TODO | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface GridServerRpc extends ServerRpc { | |||
/** | |||
* TODO | |||
* | |||
* @param firstVisibleRow | |||
* the index of the first visible row | |||
* @param visibleRowCount | |||
* the number of rows visible, counted from | |||
* <code>firstVisibleRow</code> | |||
*/ | |||
void setVisibleRows(int firstVisibleRow, int visibleRowCount); | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* Copyright 2000-2013 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.util.ArrayList; | |||
import java.util.List; | |||
import com.vaadin.shared.AbstractComponentState; | |||
/** | |||
* The shared state for the {@link com.vaadin.ui.components.grid.Grid} component | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridState extends AbstractComponentState { | |||
{ | |||
// FIXME Grid currently does not support undefined size | |||
width = "400px"; | |||
height = "400px"; | |||
} | |||
/** | |||
* Columns in grid. Column order implicitly deferred from list order. | |||
*/ | |||
public List<GridColumnState> columns = new ArrayList<GridColumnState>(); | |||
/** | |||
* Is the column header row visible | |||
*/ | |||
public boolean columnHeadersVisible = true; | |||
/** | |||
* Is the column footer row visible | |||
*/ | |||
public boolean columnFootersVisible = false; | |||
/** | |||
* The column groups added to the grid | |||
*/ | |||
public List<ColumnGroupRowState> columnGroupRows = new ArrayList<ColumnGroupRowState>(); | |||
/** | |||
* The id for the last frozen column. | |||
* | |||
* @see GridColumnState#id | |||
*/ | |||
public String lastFrozenColumnId = null; | |||
} |
@@ -0,0 +1,378 @@ | |||
/* | |||
* Copyright 2000-2013 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; | |||
/** | |||
* An immutable representation of a range, marked by start and end points. | |||
* <p> | |||
* The range is treated as inclusive at the start, and exclusive at the end. | |||
* I.e. the range [0..1[ has the length 1, and represents one integer: 0. | |||
* <p> | |||
* The range is considered {@link #isEmpty() empty} if the start is the same as | |||
* the end. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public final class Range { | |||
private final int start; | |||
private final int end; | |||
/** | |||
* Creates a range object representing a single integer. | |||
* | |||
* @param integer | |||
* the number to represent as a range | |||
* @return the range represented by <code>integer</code> | |||
*/ | |||
public static Range withOnly(final int integer) { | |||
return new Range(integer, integer + 1); | |||
} | |||
/** | |||
* Creates a range between two integers. | |||
* <p> | |||
* The range start is <em>inclusive</em> and the end is <em>exclusive</em>. | |||
* So, a range "between" 0 and 5 represents the numbers 0, 1, 2, 3 and 4, | |||
* but not 5. | |||
* | |||
* @param start | |||
* the start of the the range, inclusive | |||
* @param end | |||
* the end of the range, exclusive | |||
* @return a range representing <code>[start..end[</code> | |||
* @throws IllegalArgumentException | |||
* if <code>start > end</code> | |||
*/ | |||
public static Range between(final int start, final int end) | |||
throws IllegalArgumentException { | |||
return new Range(start, end); | |||
} | |||
/** | |||
* Creates a range from a start point, with a given length. | |||
* | |||
* @param start | |||
* the first integer to include in the range | |||
* @param length | |||
* the length of the resulting range | |||
* @return a range starting from <code>start</code>, with | |||
* <code>length</code> number of integers following | |||
* @throws IllegalArgumentException | |||
* if length < 0 | |||
*/ | |||
public static Range withLength(final int start, final int length) | |||
throws IllegalArgumentException { | |||
if (length < 0) { | |||
/* | |||
* The constructor of Range will throw an exception if start > | |||
* start+length (i.e. if length is negative). We're throwing the | |||
* same exception type, just with a more descriptive message. | |||
*/ | |||
throw new IllegalArgumentException("length must not be negative"); | |||
} | |||
return new Range(start, start + length); | |||
} | |||
/** | |||
* Creates a new range between two numbers: <code>[start..end[</code>. | |||
* | |||
* @param start | |||
* the start integer, inclusive | |||
* @param end | |||
* the end integer, exclusive | |||
* @throws IllegalArgumentException | |||
* if <code>start > end</code> | |||
*/ | |||
private Range(final int start, final int end) | |||
throws IllegalArgumentException { | |||
if (start > end) { | |||
throw new IllegalArgumentException( | |||
"start must not be greater than end"); | |||
} | |||
this.start = start; | |||
this.end = end; | |||
} | |||
/** | |||
* Returns the <em>inclusive</em> start point of this range. | |||
* | |||
* @return the start point of this range | |||
*/ | |||
public int getStart() { | |||
return start; | |||
} | |||
/** | |||
* Returns the <em>exclusive</em> end point of this range. | |||
* | |||
* @return the end point of this range | |||
*/ | |||
public int getEnd() { | |||
return end; | |||
} | |||
/** | |||
* The number of integers contained in the range. | |||
* | |||
* @return the number of integers contained in the range | |||
*/ | |||
public int length() { | |||
return getEnd() - getStart(); | |||
} | |||
/** | |||
* Checks whether the range has no elements between the start and end. | |||
* | |||
* @return <code>true</code> iff the range contains no elements. | |||
*/ | |||
public boolean isEmpty() { | |||
return getStart() >= getEnd(); | |||
} | |||
/** | |||
* Checks whether this range and another range are at least partially | |||
* covering the same values. | |||
* | |||
* @param other | |||
* the other range to check against | |||
* @return <code>true</code> if this and <code>other</code> intersect | |||
*/ | |||
public boolean intersects(final Range other) { | |||
return getStart() < other.getEnd() && other.getStart() < getEnd(); | |||
} | |||
/** | |||
* Checks whether an integer is found within this range. | |||
* | |||
* @param integer | |||
* an integer to test for presence in this range | |||
* @return <code>true</code> iff <code>integer</code> is in this range | |||
*/ | |||
public boolean contains(final int integer) { | |||
return getStart() <= integer && integer < getEnd(); | |||
} | |||
/** | |||
* Checks whether this range is a subset of another range. | |||
* | |||
* @return <code>true</code> iff <code>other</code> completely wraps this | |||
* range | |||
*/ | |||
public boolean isSubsetOf(final Range other) { | |||
return other.getStart() <= getStart() && getEnd() <= other.getEnd(); | |||
} | |||
/** | |||
* Overlay this range with another one, and partition the ranges according | |||
* to how they position relative to each other. | |||
* <p> | |||
* The three partitions are returned as a three-element Range array: | |||
* <ul> | |||
* <li>Elements in this range that occur before elements in | |||
* <code>other</code>. | |||
* <li>Elements that are shared between the two ranges. | |||
* <li>Elements in this range that occur after elements in | |||
* <code>other</code>. | |||
* </ul> | |||
* | |||
* @param other | |||
* the other range to act as delimiters. | |||
* @return a three-element Range array of partitions depicting the elements | |||
* before (index 0), shared/inside (index 1) and after (index 2). | |||
*/ | |||
public Range[] partitionWith(final Range other) { | |||
final Range[] splitBefore = splitAt(other.getStart()); | |||
final Range rangeBefore = splitBefore[0]; | |||
final Range[] splitAfter = splitBefore[1].splitAt(other.getEnd()); | |||
final Range rangeInside = splitAfter[0]; | |||
final Range rangeAfter = splitAfter[1]; | |||
return new Range[] { rangeBefore, rangeInside, rangeAfter }; | |||
} | |||
/** | |||
* Get a range that is based on this one, but offset by a number. | |||
* | |||
* @param offset | |||
* the number to offset by | |||
* @return a copy of this range, offset by <code>offset</code> | |||
*/ | |||
public Range offsetBy(final int offset) { | |||
if (offset == 0) { | |||
return this; | |||
} else { | |||
return new Range(start + offset, end + offset); | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return getClass().getSimpleName() + " [" + getStart() + ".." + getEnd() | |||
+ "[" + (isEmpty() ? " (empty)" : ""); | |||
} | |||
@Override | |||
public int hashCode() { | |||
final int prime = 31; | |||
int result = 1; | |||
result = prime * result + end; | |||
result = prime * result + start; | |||
return result; | |||
} | |||
@Override | |||
public boolean equals(final Object obj) { | |||
if (this == obj) { | |||
return true; | |||
} | |||
if (obj == null) { | |||
return false; | |||
} | |||
if (getClass() != obj.getClass()) { | |||
return false; | |||
} | |||
final Range other = (Range) obj; | |||
if (end != other.end) { | |||
return false; | |||
} | |||
if (start != other.start) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
/** | |||
* Checks whether this range starts before the start of another range. | |||
* | |||
* @param other | |||
* the other range to compare against | |||
* @return <code>true</code> iff this range starts before the | |||
* <code>other</code> | |||
*/ | |||
public boolean startsBefore(final Range other) { | |||
return getStart() < other.getStart(); | |||
} | |||
/** | |||
* Checks whether this range ends before the start of another range. | |||
* | |||
* @param other | |||
* the other range to compare against | |||
* @return <code>true</code> iff this range ends before the | |||
* <code>other</code> | |||
*/ | |||
public boolean endsBefore(final Range other) { | |||
return getEnd() <= other.getStart(); | |||
} | |||
/** | |||
* Checks whether this range ends after the end of another range. | |||
* | |||
* @param other | |||
* the other range to compare against | |||
* @return <code>true</code> iff this range ends after the | |||
* <code>other</code> | |||
*/ | |||
public boolean endsAfter(final Range other) { | |||
return getEnd() > other.getEnd(); | |||
} | |||
/** | |||
* Checks whether this range starts after the end of another range. | |||
* | |||
* @param other | |||
* the other range to compare against | |||
* @return <code>true</code> iff this range starts after the | |||
* <code>other</code> | |||
*/ | |||
public boolean startsAfter(final Range other) { | |||
return getStart() >= other.getEnd(); | |||
} | |||
/** | |||
* Split the range into two at a certain integer. | |||
* <p> | |||
* <em>Example:</em> <code>[5..10[.splitAt(7) == [5..7[, [7..10[</code> | |||
* | |||
* @param integer | |||
* the integer at which to split the range into two | |||
* @return an array of two ranges, with <code>[start..integer[</code> in the | |||
* first element, and <code>[integer..end[</code> in the second | |||
* element. | |||
* <p> | |||
* If {@code integer} is less than {@code start}, [empty, | |||
* {@code this} ] is returned. if <code>integer</code> is equal to | |||
* or greater than {@code end}, [{@code this}, empty] is returned | |||
* instead. | |||
*/ | |||
public Range[] splitAt(final int integer) { | |||
if (integer < start) { | |||
return new Range[] { Range.withLength(start, 0), this }; | |||
} else if (integer >= end) { | |||
return new Range[] { this, Range.withLength(end, 0) }; | |||
} else { | |||
return new Range[] { new Range(start, integer), | |||
new Range(integer, end) }; | |||
} | |||
} | |||
/** | |||
* Split the range into two after a certain number of integers into the | |||
* range. | |||
* <p> | |||
* Calling this method is equivalent to calling | |||
* <code>{@link #splitAt(int) splitAt}({@link #getStart()}+length);</code> | |||
* <p> | |||
* <em>Example:</em> | |||
* <code>[5..10[.splitAtFromStart(2) == [5..7[, [7..10[</code> | |||
* | |||
* @param length | |||
* the length at which to split this range into two | |||
* @return an array of two ranges, having the <code>length</code>-first | |||
* elements of this range, and the second range having the rest. If | |||
* <code>length</code> ≤ 0, the first element will be empty, and | |||
* the second element will be this range. If <code>length</code> | |||
* ≥ {@link #length()}, the first element will be this range, | |||
* and the second element will be empty. | |||
*/ | |||
public Range[] splitAtFromStart(final int length) { | |||
return splitAt(getStart() + length); | |||
} | |||
/** | |||
* Combines two ranges to create a range containing all values in both | |||
* ranges, provided there are no gaps between the ranges. | |||
* | |||
* @param other | |||
* the range to combine with this range | |||
* | |||
* @return the combined range | |||
* | |||
* @throws IllegalArgumentException | |||
* if the two ranges aren't connected | |||
*/ | |||
public Range combineWith(Range other) throws IllegalArgumentException { | |||
if (getStart() > other.getEnd() || other.getStart() > getEnd()) { | |||
throw new IllegalArgumentException("There is a gap between " + this | |||
+ " and " + other); | |||
} | |||
return Range.between(Math.min(getStart(), other.getStart()), | |||
Math.max(getEnd(), other.getEnd())); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* Copyright 2000-2013 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; | |||
/** | |||
* Enumeration, specifying the destinations that are supported when scrolling | |||
* rows or columns into view. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public enum ScrollDestination { | |||
/** | |||
* Scroll as little as possible to show the target element. If the element | |||
* fits into view, this works as START or END depending on the current | |||
* scroll position. If the element does not fit into view, this works as | |||
* START. | |||
*/ | |||
ANY, | |||
/** | |||
* Scrolls so that the element is shown at the start of the viewport. The | |||
* viewport will, however, not scroll beyond its contents. | |||
*/ | |||
START, | |||
/** | |||
* Scrolls so that the element is shown in the middle of the viewport. The | |||
* viewport will, however, not scroll beyond its contents, given more | |||
* elements than what the viewport is able to show at once. Under no | |||
* circumstances will the viewport scroll before its first element. | |||
*/ | |||
MIDDLE, | |||
/** | |||
* Scrolls so that the element is shown at the end of the viewport. The | |||
* viewport will, however, not scroll before its first element. | |||
*/ | |||
END | |||
} |
@@ -0,0 +1,318 @@ | |||
/* | |||
* Copyright 2000-2013 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 static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import org.junit.Test; | |||
@SuppressWarnings("static-method") | |||
public class RangeTest { | |||
@Test(expected = IllegalArgumentException.class) | |||
public void startAfterEndTest() { | |||
Range.between(10, 9); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void negativeLengthTest() { | |||
Range.withLength(10, -1); | |||
} | |||
@Test | |||
public void constructorEquivalenceTest() { | |||
assertEquals("10 == [10,11[", Range.withOnly(10), Range.between(10, 11)); | |||
assertEquals("[10,20[ == 10, length 10", Range.between(10, 20), | |||
Range.withLength(10, 10)); | |||
assertEquals("10 == 10, length 1", Range.withOnly(10), | |||
Range.withLength(10, 1)); | |||
} | |||
@Test | |||
public void boundsTest() { | |||
{ | |||
final Range range = Range.between(0, 10); | |||
assertEquals("between(0, 10) start", 0, range.getStart()); | |||
assertEquals("between(0, 10) end", 10, range.getEnd()); | |||
} | |||
{ | |||
final Range single = Range.withOnly(10); | |||
assertEquals("withOnly(10) start", 10, single.getStart()); | |||
assertEquals("withOnly(10) end", 11, single.getEnd()); | |||
} | |||
{ | |||
final Range length = Range.withLength(10, 5); | |||
assertEquals("withLength(10, 5) start", 10, length.getStart()); | |||
assertEquals("withLength(10, 5) end", 15, length.getEnd()); | |||
} | |||
} | |||
@Test | |||
@SuppressWarnings("boxing") | |||
public void equalsTest() { | |||
final Range range1 = Range.between(0, 10); | |||
final Range range2 = Range.withLength(0, 11); | |||
assertTrue("null", !range1.equals(null)); | |||
assertTrue("reflexive", range1.equals(range1)); | |||
assertEquals("symmetric", range1.equals(range2), range2.equals(range1)); | |||
} | |||
@Test | |||
public void containsTest() { | |||
final int start = 0; | |||
final int end = 10; | |||
final Range range = Range.between(start, end); | |||
assertTrue("start should be contained", range.contains(start)); | |||
assertTrue("start-1 should not be contained", | |||
!range.contains(start - 1)); | |||
assertTrue("end should not be contained", !range.contains(end)); | |||
assertTrue("end-1 should be contained", range.contains(end - 1)); | |||
assertTrue("[0..10[ contains 5", Range.between(0, 10).contains(5)); | |||
assertTrue("empty range does not contain 5", !Range.between(5, 5) | |||
.contains(5)); | |||
} | |||
@Test | |||
public void emptyTest() { | |||
assertTrue("[0..0[ should be empty", Range.between(0, 0).isEmpty()); | |||
assertTrue("Range of length 0 should be empty", Range.withLength(0, 0) | |||
.isEmpty()); | |||
assertTrue("[0..1[ should not be empty", !Range.between(0, 1).isEmpty()); | |||
assertTrue("Range of length 1 should not be empty", | |||
!Range.withLength(0, 1).isEmpty()); | |||
} | |||
@Test | |||
public void splitTest() { | |||
final Range startRange = Range.between(0, 10); | |||
final Range[] splitRanges = startRange.splitAt(5); | |||
assertEquals("[0..10[ split at 5, lower", Range.between(0, 5), | |||
splitRanges[0]); | |||
assertEquals("[0..10[ split at 5, upper", Range.between(5, 10), | |||
splitRanges[1]); | |||
} | |||
@Test | |||
public void split_valueBefore() { | |||
Range range = Range.between(10, 20); | |||
Range[] splitRanges = range.splitAt(5); | |||
assertEquals(Range.between(10, 10), splitRanges[0]); | |||
assertEquals(range, splitRanges[1]); | |||
} | |||
@Test | |||
public void split_valueAfter() { | |||
Range range = Range.between(10, 20); | |||
Range[] splitRanges = range.splitAt(25); | |||
assertEquals(range, splitRanges[0]); | |||
assertEquals(Range.between(20, 20), splitRanges[1]); | |||
} | |||
@Test | |||
public void emptySplitTest() { | |||
final Range range = Range.between(5, 10); | |||
final Range[] split1 = range.splitAt(0); | |||
assertTrue("split1, [0]", split1[0].isEmpty()); | |||
assertEquals("split1, [1]", range, split1[1]); | |||
final Range[] split2 = range.splitAt(15); | |||
assertEquals("split2, [0]", range, split2[0]); | |||
assertTrue("split2, [1]", split2[1].isEmpty()); | |||
} | |||
@Test | |||
public void lengthTest() { | |||
assertEquals("withLength length", 5, Range.withLength(10, 5).length()); | |||
assertEquals("between length", 5, Range.between(10, 15).length()); | |||
assertEquals("withOnly 10 length", 1, Range.withOnly(10).length()); | |||
} | |||
@Test | |||
public void intersectsTest() { | |||
assertTrue("[0..10[ intersects [5..15[", Range.between(0, 10) | |||
.intersects(Range.between(5, 15))); | |||
assertTrue("[0..10[ does not intersect [10..20[", !Range.between(0, 10) | |||
.intersects(Range.between(10, 20))); | |||
} | |||
@Test | |||
public void intersects_emptyInside() { | |||
assertTrue("[5..5[ does intersect with [0..10[", Range.between(5, 5) | |||
.intersects(Range.between(0, 10))); | |||
assertTrue("[0..10[ does intersect with [5..5[", Range.between(0, 10) | |||
.intersects(Range.between(5, 5))); | |||
} | |||
@Test | |||
public void intersects_emptyOutside() { | |||
assertTrue("[15..15[ does not intersect with [0..10[", | |||
!Range.between(15, 15).intersects(Range.between(0, 10))); | |||
assertTrue("[0..10[ does not intersect with [15..15[", | |||
!Range.between(0, 10).intersects(Range.between(15, 15))); | |||
} | |||
@Test | |||
public void subsetTest() { | |||
assertTrue("[5..10[ is subset of [0..20[", Range.between(5, 10) | |||
.isSubsetOf(Range.between(0, 20))); | |||
final Range range = Range.between(0, 10); | |||
assertTrue("range is subset of self", range.isSubsetOf(range)); | |||
assertTrue("[0..10[ is not subset of [5..15[", !Range.between(0, 10) | |||
.isSubsetOf(Range.between(5, 15))); | |||
} | |||
@Test | |||
public void offsetTest() { | |||
assertEquals(Range.between(5, 15), Range.between(0, 10).offsetBy(5)); | |||
} | |||
@Test | |||
public void rangeStartsBeforeTest() { | |||
final Range former = Range.between(0, 5); | |||
final Range latter = Range.between(1, 5); | |||
assertTrue("former should starts before latter", | |||
former.startsBefore(latter)); | |||
assertTrue("latter shouldn't start before latter", | |||
!latter.startsBefore(former)); | |||
assertTrue("no overlap allowed", | |||
!Range.between(0, 5).startsBefore(Range.between(0, 10))); | |||
} | |||
@Test | |||
public void rangeStartsAfterTest() { | |||
final Range former = Range.between(0, 5); | |||
final Range latter = Range.between(5, 10); | |||
assertTrue("latter should start after former", | |||
latter.startsAfter(former)); | |||
assertTrue("former shouldn't start after latter", | |||
!former.startsAfter(latter)); | |||
assertTrue("no overlap allowed", | |||
!Range.between(5, 10).startsAfter(Range.between(0, 6))); | |||
} | |||
@Test | |||
public void rangeEndsBeforeTest() { | |||
final Range former = Range.between(0, 5); | |||
final Range latter = Range.between(5, 10); | |||
assertTrue("latter should end before former", former.endsBefore(latter)); | |||
assertTrue("former shouldn't end before latter", | |||
!latter.endsBefore(former)); | |||
assertTrue("no overlap allowed", | |||
!Range.between(5, 10).endsBefore(Range.between(9, 15))); | |||
} | |||
@Test | |||
public void rangeEndsAfterTest() { | |||
final Range former = Range.between(1, 5); | |||
final Range latter = Range.between(1, 6); | |||
assertTrue("latter should end after former", latter.endsAfter(former)); | |||
assertTrue("former shouldn't end after latter", | |||
!former.endsAfter(latter)); | |||
assertTrue("no overlap allowed", | |||
!Range.between(0, 10).endsAfter(Range.between(5, 10))); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void combine_notOverlappingFirstSmaller() { | |||
Range.between(0, 10).combineWith(Range.between(11, 20)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void combine_notOverlappingSecondLarger() { | |||
Range.between(11, 20).combineWith(Range.between(0, 10)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void combine_firstEmptyNotOverlapping() { | |||
Range.between(15, 15).combineWith(Range.between(0, 10)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void combine_secondEmptyNotOverlapping() { | |||
Range.between(0, 10).combineWith(Range.between(15, 15)); | |||
} | |||
@Test | |||
public void combine_barelyOverlapping() { | |||
Range r1 = Range.between(0, 10); | |||
Range r2 = Range.between(10, 20); | |||
// Test both ways, should give the same result | |||
Range combined1 = r1.combineWith(r2); | |||
Range combined2 = r2.combineWith(r1); | |||
assertEquals(combined1, combined2); | |||
assertEquals(0, combined1.getStart()); | |||
assertEquals(20, combined1.getEnd()); | |||
} | |||
@Test | |||
public void combine_subRange() { | |||
Range r1 = Range.between(0, 10); | |||
Range r2 = Range.between(2, 8); | |||
// Test both ways, should give the same result | |||
Range combined1 = r1.combineWith(r2); | |||
Range combined2 = r2.combineWith(r1); | |||
assertEquals(combined1, combined2); | |||
assertEquals(r1, combined1); | |||
} | |||
@Test | |||
public void combine_intersecting() { | |||
Range r1 = Range.between(0, 10); | |||
Range r2 = Range.between(5, 15); | |||
// Test both ways, should give the same result | |||
Range combined1 = r1.combineWith(r2); | |||
Range combined2 = r2.combineWith(r1); | |||
assertEquals(combined1, combined2); | |||
assertEquals(0, combined1.getStart()); | |||
assertEquals(15, combined1.getEnd()); | |||
} | |||
@Test | |||
public void combine_emptyInside() { | |||
Range r1 = Range.between(0, 10); | |||
Range r2 = Range.between(5, 5); | |||
// Test both ways, should give the same result | |||
Range combined1 = r1.combineWith(r2); | |||
Range combined2 = r2.combineWith(r1); | |||
assertEquals(combined1, combined2); | |||
assertEquals(r1, combined1); | |||
} | |||
} |
@@ -0,0 +1,176 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head profile="http://selenium-ide.openqa.org/profiles/test-case"> | |||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |||
<link rel="selenium.base" href="http://localhost:8888/" /> | |||
<title>BasicEscalator</title> | |||
</head> | |||
<body> | |||
<table cellpadding="1" cellspacing="1" border="1"> | |||
<thead> | |||
<tr><td rowspan="1" colspan="3">BasicEscalator</td></tr> | |||
</thead><tbody> | |||
<tr> | |||
<td>open</td> | |||
<td>/run/com.vaadin.tests.components.grid.BasicEscalator?restartApplication</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]</td> | |||
<td>Row 0: 0,0 (0)</td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[17]/domChild[9]</td> | |||
<td>Cell: 9,17 (179)</td> | |||
</tr> | |||
<tr> | |||
<td>verifyTextNotPresent</td> | |||
<td>Cell: 0,100</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyTextNotPresent</td> | |||
<td>Cell: 0,101</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[0]/VTextField[0]</td> | |||
<td>0</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[1]/VTextField[0]</td> | |||
<td>1</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[18]/domChild[0]</td> | |||
<td>Row 0: 0,100 (190)</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[0]/VTextField[0]</td> | |||
<td>11</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[17]/domChild[0]</td> | |||
<td>Row 11: 0,101 (200)</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[0]/VTextField[0]</td> | |||
<td>0</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[1]/VTextField[0]</td> | |||
<td>100</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VHorizontalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[16]/domChild[0]</td> | |||
<td>Row 0: 0,102 (210)</td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[1]/domChild[0]</td> | |||
<td>Row 16: 0,118 (370)</td> | |||
</tr> | |||
<tr> | |||
<td>scroll</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[0]</td> | |||
<td>1109</td> | |||
</tr> | |||
<tr> | |||
<td>verifyTextPresent</td> | |||
<td>Row 56: 0,158</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyTextPresent</td> | |||
<td>Row 72: 0,174</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>scroll</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[0]</td> | |||
<td>3690</td> | |||
</tr> | |||
<tr> | |||
<td>verifyTextPresent</td> | |||
<td>Row 201: 0,99</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VHorizontalLayout[0]/Slot[0]/VTextField[0]</td> | |||
<td>201</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VHorizontalLayout[0]/Slot[1]/VTextField[0]</td> | |||
<td>1</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VHorizontalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[1]/domChild[0]</td> | |||
<td>Row 200: 0,98 (960)</td> | |||
</tr> | |||
<tr> | |||
<td>verifyTextNotPresent</td> | |||
<td>Row 201:</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VHorizontalLayout[0]/Slot[0]/VTextField[0]</td> | |||
<td>0</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VHorizontalLayout[0]/Slot[1]/VTextField[0]</td> | |||
<td>2</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VHorizontalLayout[0]/Slot[2]/VButton[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[16]/domChild[0]</td> | |||
<td>Row 184: 10,82 (974)</td> | |||
</tr> | |||
<tr> | |||
<td>verifyText</td> | |||
<td>vaadin=runcomvaadintestscomponentsgridBasicEscalator::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[1]/domChild[0]</td> | |||
<td>Row 200: 10,98 (1006)</td> | |||
</tr> | |||
</tbody></table> | |||
</body> | |||
</html> |
@@ -0,0 +1,312 @@ | |||
/* | |||
* Copyright 2000-2013 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.Random; | |||
import com.vaadin.annotations.Widgetset; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.tests.widgetset.TestingWidgetSet; | |||
import com.vaadin.tests.widgetset.server.grid.TestGrid; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Layout; | |||
import com.vaadin.ui.NativeSelect; | |||
import com.vaadin.ui.TextField; | |||
/** | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
@Widgetset(TestingWidgetSet.NAME) | |||
public class BasicEscalator extends AbstractTestUI { | |||
public static final String ESCALATOR = "escalator"; | |||
public static final String INSERT_ROWS_OFFSET = "iro"; | |||
public static final String INSERT_ROWS_AMOUNT = "ira"; | |||
public static final String INSERT_ROWS_BUTTON = "irb"; | |||
private final Random random = new Random(); | |||
@Override | |||
protected void setup(final VaadinRequest request) { | |||
final TestGrid grid = new TestGrid(); | |||
grid.setId(ESCALATOR); | |||
addComponent(grid); | |||
final Layout insertRowsLayout = new HorizontalLayout(); | |||
final TextField insertRowsOffset = new TextField(); | |||
insertRowsOffset.setId(INSERT_ROWS_OFFSET); | |||
insertRowsLayout.addComponent(insertRowsOffset); | |||
final TextField insertRowsAmount = new TextField(); | |||
insertRowsAmount.setId(INSERT_ROWS_AMOUNT); | |||
insertRowsLayout.addComponent(insertRowsAmount); | |||
insertRowsLayout.addComponent(new Button("insert rows", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
final int offset = Integer.parseInt(insertRowsOffset | |||
.getValue()); | |||
final int amount = Integer.parseInt(insertRowsAmount | |||
.getValue()); | |||
grid.insertRows(offset, amount); | |||
} | |||
}) { | |||
{ | |||
setId(INSERT_ROWS_BUTTON); | |||
} | |||
}); | |||
addComponent(insertRowsLayout); | |||
final Layout removeRowsLayout = new HorizontalLayout(); | |||
final TextField removeRowsOffset = new TextField(); | |||
removeRowsLayout.addComponent(removeRowsOffset); | |||
final TextField removeRowsAmount = new TextField(); | |||
removeRowsLayout.addComponent(removeRowsAmount); | |||
removeRowsLayout.addComponent(new Button("remove rows", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
final int offset = Integer.parseInt(removeRowsOffset | |||
.getValue()); | |||
final int amount = Integer.parseInt(removeRowsAmount | |||
.getValue()); | |||
grid.removeRows(offset, amount); | |||
} | |||
})); | |||
addComponent(removeRowsLayout); | |||
final Layout insertColumnsLayout = new HorizontalLayout(); | |||
final TextField insertColumnsOffset = new TextField(); | |||
insertColumnsLayout.addComponent(insertColumnsOffset); | |||
final TextField insertColumnsAmount = new TextField(); | |||
insertColumnsLayout.addComponent(insertColumnsAmount); | |||
insertColumnsLayout.addComponent(new Button("insert columns", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
final int offset = Integer.parseInt(insertColumnsOffset | |||
.getValue()); | |||
final int amount = Integer.parseInt(insertColumnsAmount | |||
.getValue()); | |||
grid.insertColumns(offset, amount); | |||
} | |||
})); | |||
addComponent(insertColumnsLayout); | |||
final Layout removeColumnsLayout = new HorizontalLayout(); | |||
final TextField removeColumnsOffset = new TextField(); | |||
removeColumnsLayout.addComponent(removeColumnsOffset); | |||
final TextField removeColumnsAmount = new TextField(); | |||
removeColumnsLayout.addComponent(removeColumnsAmount); | |||
removeColumnsLayout.addComponent(new Button("remove columns", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
final int offset = Integer.parseInt(removeColumnsOffset | |||
.getValue()); | |||
final int amount = Integer.parseInt(removeColumnsAmount | |||
.getValue()); | |||
grid.removeColumns(offset, amount); | |||
} | |||
})); | |||
addComponent(removeColumnsLayout); | |||
final HorizontalLayout rowScroll = new HorizontalLayout(); | |||
final NativeSelect destination = new NativeSelect(); | |||
destination.setNullSelectionAllowed(false); | |||
destination.addItem("any"); | |||
destination.setValue("any"); | |||
destination.addItem("start"); | |||
destination.addItem("end"); | |||
destination.addItem("middle"); | |||
rowScroll.addComponent(destination); | |||
final TextField rowIndex = new TextField(); | |||
rowScroll.addComponent(rowIndex); | |||
final TextField rowPadding = new TextField(); | |||
rowScroll.addComponent(rowPadding); | |||
rowScroll.addComponent(new Button("scroll to row", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
int index; | |||
try { | |||
index = Integer.parseInt(rowIndex.getValue()); | |||
} catch (NumberFormatException e) { | |||
index = 0; | |||
} | |||
int padding; | |||
try { | |||
padding = Integer.parseInt(rowPadding.getValue()); | |||
} catch (NumberFormatException e) { | |||
padding = 0; | |||
} | |||
grid.scrollToRow(index, | |||
(String) destination.getValue(), padding); | |||
} | |||
})); | |||
addComponent(rowScroll); | |||
final HorizontalLayout colScroll = new HorizontalLayout(); | |||
final NativeSelect colDestination = new NativeSelect(); | |||
colDestination.setNullSelectionAllowed(false); | |||
colDestination.addItem("any"); | |||
colDestination.setValue("any"); | |||
colDestination.addItem("start"); | |||
colDestination.addItem("end"); | |||
colDestination.addItem("middle"); | |||
colScroll.addComponent(colDestination); | |||
final TextField colIndex = new TextField(); | |||
colScroll.addComponent(colIndex); | |||
final TextField colPadding = new TextField(); | |||
colScroll.addComponent(colPadding); | |||
colScroll.addComponent(new Button("scroll to column", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
int index; | |||
try { | |||
index = Integer.parseInt(colIndex.getValue()); | |||
} catch (NumberFormatException e) { | |||
index = 0; | |||
} | |||
int padding; | |||
try { | |||
padding = Integer.parseInt(colPadding.getValue()); | |||
} catch (NumberFormatException e) { | |||
padding = 0; | |||
} | |||
grid.scrollToColumn(index, | |||
(String) colDestination.getValue(), padding); | |||
} | |||
})); | |||
addComponent(colScroll); | |||
final TextField freezeCount = new TextField(); | |||
freezeCount.setConverter(Integer.class); | |||
freezeCount.setNullRepresentation(""); | |||
addComponent(new HorizontalLayout(freezeCount, new Button( | |||
"set frozen columns", new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
grid.setFrozenColumns(((Integer) freezeCount | |||
.getConvertedValue()).intValue()); | |||
freezeCount.setValue(null); | |||
} | |||
}))); | |||
addComponent(new Button("Resize randomly", new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
int width = random.nextInt(300) + 500; | |||
int height = random.nextInt(300) + 200; | |||
grid.setWidth(width + "px"); | |||
grid.setHeight(height + "px"); | |||
} | |||
})); | |||
addComponent(new Button("Random headers count", | |||
new Button.ClickListener() { | |||
private int headers = 1; | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
int diff = 0; | |||
while (diff == 0) { | |||
final int nextHeaders = random.nextInt(4); | |||
diff = nextHeaders - headers; | |||
headers = nextHeaders; | |||
} | |||
if (diff > 0) { | |||
grid.insertHeaders(0, diff); | |||
} else if (diff < 0) { | |||
grid.removeHeaders(0, -diff); | |||
} | |||
} | |||
})); | |||
addComponent(new Button("Random footers count", | |||
new Button.ClickListener() { | |||
private int footers = 1; | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
int diff = 0; | |||
while (diff == 0) { | |||
final int nextFooters = random.nextInt(4); | |||
diff = nextFooters - footers; | |||
footers = nextFooters; | |||
} | |||
if (diff > 0) { | |||
grid.insertFooters(0, diff); | |||
} else if (diff < 0) { | |||
grid.removeFooters(0, -diff); | |||
} | |||
} | |||
})); | |||
final Layout resizeColumnsLayout = new HorizontalLayout(); | |||
final TextField resizeColumnIndex = new TextField(); | |||
resizeColumnsLayout.addComponent(resizeColumnIndex); | |||
final TextField resizeColumnPx = new TextField(); | |||
resizeColumnsLayout.addComponent(resizeColumnPx); | |||
resizeColumnsLayout.addComponent(new Button("resize column", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(final ClickEvent event) { | |||
final int index = Integer.parseInt(resizeColumnIndex | |||
.getValue()); | |||
final int px = Integer.parseInt(resizeColumnPx | |||
.getValue()); | |||
grid.setColumnWidth(index, px); | |||
} | |||
})); | |||
addComponent(resizeColumnsLayout); | |||
addComponent(new Button("Autoresize columns", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
grid.calculateColumnWidths(); | |||
} | |||
})); | |||
addComponent(new Button("Randomize row heights", | |||
new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
grid.randomizeDefaultRowHeight(); | |||
} | |||
})); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return null; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* Copyright 2000-2013 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 org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class BasicEscalatorTest extends MultiBrowserTest { | |||
@Test | |||
public void testNormalHeight() throws Exception { | |||
openTestURL(); | |||
compareScreen("normalHeight"); | |||
} | |||
@Test | |||
public void testModifiedHeight() throws Exception { | |||
openTestURLWithTheme("reindeer-tests"); | |||
compareScreen("modifiedHeight"); | |||
} | |||
private WebElement getFirstBodyRowCell() { | |||
return getDriver().findElement( | |||
By.xpath("//tbody/tr[@class='v-escalator-row'][1]/td[1]")); | |||
} | |||
private void openTestURLWithTheme(String themeName) { | |||
String testUrl = getTestUrl(); | |||
testUrl += (testUrl.contains("?")) ? "&" : "?"; | |||
testUrl += "theme=" + themeName; | |||
getDriver().get(testUrl); | |||
} | |||
} |
@@ -0,0 +1,343 @@ | |||
/* | |||
* Copyright 2000-2013 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.LinkedHashMap; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.util.IndexedContainer; | |||
import com.vaadin.tests.components.AbstractComponentTest; | |||
import com.vaadin.ui.components.grid.ColumnGroup; | |||
import com.vaadin.ui.components.grid.ColumnGroupRow; | |||
import com.vaadin.ui.components.grid.Grid; | |||
import com.vaadin.ui.components.grid.GridColumn; | |||
/** | |||
* Tests the basic features like columns, footers and headers | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
private final int COLUMNS = 10; | |||
private int columnGroupRows = 0; | |||
private final int ROWS = 1000; | |||
private IndexedContainer ds; | |||
@Override | |||
protected Grid constructComponent() { | |||
// Build data source | |||
ds = new IndexedContainer(); | |||
for (int col = 0; col < COLUMNS; col++) { | |||
ds.addContainerProperty(getColumnProperty(col), String.class, ""); | |||
} | |||
for (int row = 0; row < ROWS; row++) { | |||
Item item = ds.addItem(Integer.valueOf(row)); | |||
for (int col = 0; col < COLUMNS; col++) { | |||
item.getItemProperty(getColumnProperty(col)).setValue( | |||
"(" + row + ", " + col + ")"); | |||
} | |||
} | |||
// Create grid | |||
Grid grid = new Grid(ds); | |||
// Add footer values (header values are automatically created) | |||
for (int col = 0; col < COLUMNS; col++) { | |||
grid.getColumn(getColumnProperty(col)).setFooterCaption( | |||
"Footer " + col); | |||
} | |||
// Set varying column widths | |||
for (int col = 0; col < COLUMNS; col++) { | |||
grid.getColumn("Column" + col).setWidth(100 + col * 50); | |||
} | |||
createGridActions(); | |||
createColumnActions(); | |||
createHeaderActions(); | |||
createFooterActions(); | |||
createColumnGroupActions(); | |||
createRowActions(); | |||
return grid; | |||
} | |||
protected void createGridActions() { | |||
LinkedHashMap<String, String> primaryStyleNames = new LinkedHashMap<String, String>(); | |||
primaryStyleNames.put("v-grid", "v-grid"); | |||
primaryStyleNames.put("v-escalator", "v-escalator"); | |||
primaryStyleNames.put("my-grid", "my-grid"); | |||
createMultiClickAction("Primary style name", "State", | |||
primaryStyleNames, new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
grid.setPrimaryStyleName(value); | |||
} | |||
}, primaryStyleNames.get("v-grid")); | |||
} | |||
protected void createHeaderActions() { | |||
createCategory("Headers", null); | |||
createBooleanAction("Visible", "Headers", true, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, Boolean value, Object data) { | |||
grid.setColumnHeadersVisible(value); | |||
} | |||
}); | |||
} | |||
protected void createFooterActions() { | |||
createCategory("Footers", null); | |||
createBooleanAction("Visible", "Footers", false, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, Boolean value, Object data) { | |||
grid.setColumnFootersVisible(value); | |||
} | |||
}); | |||
} | |||
protected void createColumnActions() { | |||
createCategory("Columns", null); | |||
for (int c = 0; c < COLUMNS; c++) { | |||
createCategory(getColumnProperty(c), "Columns"); | |||
createBooleanAction("Visible", getColumnProperty(c), true, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, Boolean value, | |||
Object columnIndex) { | |||
Object propertyId = (new ArrayList(grid | |||
.getContainerDatasource() | |||
.getContainerPropertyIds()) | |||
.get((Integer) columnIndex)); | |||
GridColumn column = grid.getColumn(propertyId); | |||
column.setVisible(!column.isVisible()); | |||
} | |||
}, c); | |||
createClickAction("Remove", getColumnProperty(c), | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
grid.getContainerDatasource() | |||
.removeContainerProperty("Column" + data); | |||
} | |||
}, null, c); | |||
createClickAction("Freeze", getColumnProperty(c), | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
grid.setLastFrozenPropertyId("Column" + data); | |||
} | |||
}, null, c); | |||
createCategory("Column" + c + " Width", getColumnProperty(c)); | |||
createClickAction("Auto", "Column" + c + " Width", | |||
new Command<Grid, Integer>() { | |||
@Override | |||
public void execute(Grid grid, Integer value, | |||
Object columnIndex) { | |||
Object propertyId = (new ArrayList(grid | |||
.getContainerDatasource() | |||
.getContainerPropertyIds()) | |||
.get((Integer) columnIndex)); | |||
GridColumn column = grid.getColumn(propertyId); | |||
column.setWidthUndefined(); | |||
} | |||
}, -1, c); | |||
for (int w = 50; w < 300; w += 50) { | |||
createClickAction(w + "px", "Column" + c + " Width", | |||
new Command<Grid, Integer>() { | |||
@Override | |||
public void execute(Grid grid, Integer value, | |||
Object columnIndex) { | |||
Object propertyId = (new ArrayList(grid | |||
.getContainerDatasource() | |||
.getContainerPropertyIds()) | |||
.get((Integer) columnIndex)); | |||
GridColumn column = grid.getColumn(propertyId); | |||
column.setWidth(value); | |||
} | |||
}, w, c); | |||
} | |||
} | |||
} | |||
private static String getColumnProperty(int c) { | |||
return "Column" + c; | |||
} | |||
protected void createColumnGroupActions() { | |||
createCategory("Column groups", null); | |||
createClickAction("Add group row", "Column groups", | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid grid, String value, Object data) { | |||
final ColumnGroupRow row = grid.addColumnGroupRow(); | |||
columnGroupRows++; | |||
createCategory("Column group row " + columnGroupRows, | |||
"Column groups"); | |||
createBooleanAction("Header Visible", | |||
"Column group row " + columnGroupRows, true, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, | |||
Boolean value, Object columnIndex) { | |||
row.setHeaderVisible(value); | |||
} | |||
}, row); | |||
createBooleanAction("Footer Visible", | |||
"Column group row " + columnGroupRows, false, | |||
new Command<Grid, Boolean>() { | |||
@Override | |||
public void execute(Grid grid, | |||
Boolean value, Object columnIndex) { | |||
row.setFooterVisible(value); | |||
} | |||
}, row); | |||
for (int i = 0; i < COLUMNS; i++) { | |||
final int columnIndex = i; | |||
createClickAction("Group Column " + columnIndex | |||
+ " & " + (columnIndex + 1), | |||
"Column group row " + columnGroupRows, | |||
new Command<Grid, Integer>() { | |||
@Override | |||
public void execute(Grid c, | |||
Integer value, Object data) { | |||
final ColumnGroup group = row | |||
.addGroup( | |||
"Column" + value, | |||
"Column" | |||
+ (value + 1)); | |||
group.setHeaderCaption("Column " | |||
+ value + " & " | |||
+ (value + 1)); | |||
group.setFooterCaption("Column " | |||
+ value + " & " | |||
+ (value + 1)); | |||
} | |||
}, i, row); | |||
} | |||
} | |||
}, null, null); | |||
} | |||
protected void createRowActions() { | |||
createCategory("Body rows", null); | |||
createClickAction("Add first row", "Body rows", | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid c, String value, Object data) { | |||
Item item = ds.addItemAt(0, new Object()); | |||
for (int i = 0; i < COLUMNS; i++) { | |||
item.getItemProperty(getColumnProperty(i)) | |||
.setValue("newcell: " + i); | |||
} | |||
} | |||
}, null); | |||
createClickAction("Remove first row", "Body rows", | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid c, String value, Object data) { | |||
Object firstItemId = ds.getIdByIndex(0); | |||
ds.removeItem(firstItemId); | |||
} | |||
}, null); | |||
createClickAction("Modify first row (getItemProperty)", "Body rows", | |||
new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid c, String value, Object data) { | |||
Object firstItemId = ds.getIdByIndex(0); | |||
Item item = ds.getItem(firstItemId); | |||
for (int i = 0; i < COLUMNS; i++) { | |||
item.getItemProperty(getColumnProperty(i)) | |||
.setValue("modified: " + i); | |||
} | |||
} | |||
}, null); | |||
createClickAction("Modify first row (getContainerProperty)", | |||
"Body rows", new Command<Grid, String>() { | |||
@Override | |||
public void execute(Grid c, String value, Object data) { | |||
Object firstItemId = ds.getIdByIndex(0); | |||
for (Object containerPropertyId : ds | |||
.getContainerPropertyIds()) { | |||
ds.getContainerProperty(firstItemId, | |||
containerPropertyId).setValue( | |||
"modified: " + containerPropertyId); | |||
} | |||
} | |||
}, null); | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 12829; | |||
} | |||
@Override | |||
protected Class<Grid> getTestClass() { | |||
return Grid.class; | |||
} | |||
} |
@@ -0,0 +1,373 @@ | |||
/* | |||
* Copyright 2000-2013 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 static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.interactions.Actions; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
/** | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridBasicFeaturesTest extends MultiBrowserTest { | |||
@Test | |||
public void testColumnHeaderCaptions() throws Exception { | |||
openTestURL(); | |||
// Column headers should be visible | |||
List<WebElement> cells = getGridHeaderRowCells(); | |||
assertEquals(10, cells.size()); | |||
assertEquals("Column0", cells.get(0).getText()); | |||
assertEquals("Column1", cells.get(1).getText()); | |||
assertEquals("Column2", cells.get(2).getText()); | |||
} | |||
@Test | |||
public void testColumnFooterCaptions() throws Exception { | |||
openTestURL(); | |||
// footer row should by default be hidden | |||
List<WebElement> cells = getGridFooterRowCells(); | |||
assertEquals(0, cells.size()); | |||
// Open footer row | |||
selectMenuPath("Component", "Footers", "Visible"); | |||
// Footers should now be visible | |||
cells = getGridFooterRowCells(); | |||
assertEquals(10, cells.size()); | |||
assertEquals("Footer 0", cells.get(0).getText()); | |||
assertEquals("Footer 1", cells.get(1).getText()); | |||
assertEquals("Footer 2", cells.get(2).getText()); | |||
} | |||
@Test | |||
public void testColumnGroupHeaders() throws Exception { | |||
openTestURL(); | |||
// Hide column headers for this test | |||
selectMenuPath("Component", "Headers", "Visible"); | |||
List<WebElement> cells = getGridHeaderRowCells(); | |||
// header row should be empty | |||
assertEquals(0, cells.size()); | |||
// add a group row | |||
selectMenuPath("Component", "Column groups", "Add group row"); | |||
// Empty group row cells should be present | |||
cells = getGridHeaderRowCells(); | |||
assertEquals(10, cells.size()); | |||
// Group columns 0 & 1 | |||
selectMenuPath("Component", "Column groups", "Column group row 1", | |||
"Group Column 0 & 1"); | |||
cells = getGridHeaderRowCells(); | |||
assertEquals("Column 0 & 1", cells.get(0).getText()); | |||
} | |||
@Test | |||
public void testColumnGroupFooters() throws Exception { | |||
openTestURL(); | |||
// add a group row | |||
selectMenuPath("Component", "Column groups", "Add group row"); | |||
// Set footer visible | |||
selectMenuPath("Component", "Column groups", "Column group row 1", | |||
"Footer Visible"); | |||
// Group columns 0 & 1 | |||
selectMenuPath("Component", "Column groups", "Column group row 1", | |||
"Group Column 0 & 1"); | |||
List<WebElement> cells = getGridFooterRowCells(); | |||
assertEquals("Column 0 & 1", cells.get(0).getText()); | |||
} | |||
@Test | |||
public void testGroupingSameColumnsOnRowThrowsException() throws Exception { | |||
openTestURL(); | |||
// add a group row | |||
selectMenuPath("Component", "Column groups", "Add group row"); | |||
// Group columns 0 & 1 | |||
selectMenuPath("Component", "Column groups", "Column group row 1", | |||
"Group Column 0 & 1"); | |||
// Group columns 1 & 2 shoud fail | |||
selectMenuPath("Component", "Column groups", "Column group row 1", | |||
"Group Column 1 & 2"); | |||
assertTrue(getLogRow(0) | |||
.contains( | |||
"Exception occured, java.lang.IllegalArgumentExceptionColumn Column1 already belongs to another group.")); | |||
} | |||
@Test | |||
public void testHidingColumn() throws Exception { | |||
openTestURL(); | |||
// Column 0 should be visible | |||
List<WebElement> cells = getGridHeaderRowCells(); | |||
assertEquals("Column0", cells.get(0).getText()); | |||
// Hide column 0 | |||
selectMenuPath("Component", "Columns", "Column0", "Visible"); | |||
// Column 1 should now be the first cell | |||
cells = getGridHeaderRowCells(); | |||
assertEquals("Column1", cells.get(0).getText()); | |||
} | |||
@Test | |||
public void testRemovingColumn() throws Exception { | |||
openTestURL(); | |||
// Column 0 should be visible | |||
List<WebElement> cells = getGridHeaderRowCells(); | |||
assertEquals("Column0", cells.get(0).getText()); | |||
// Hide column 0 | |||
selectMenuPath("Component", "Columns", "Column0", "Remove"); | |||
// Column 1 should now be the first cell | |||
cells = getGridHeaderRowCells(); | |||
assertEquals("Column1", cells.get(0).getText()); | |||
} | |||
@Test | |||
public void testFreezingColumn() throws Exception { | |||
openTestURL(); | |||
// Freeze column 2 | |||
selectMenuPath("Component", "Columns", "Column2", "Freeze"); | |||
WebElement cell = getBodyCellByRowAndColumn(1, 1); | |||
assertTrue(cell.getAttribute("class").contains("frozen")); | |||
cell = getBodyCellByRowAndColumn(1, 2); | |||
assertTrue(cell.getAttribute("class").contains("frozen")); | |||
} | |||
@Test | |||
public void testInitialColumnWidths() throws Exception { | |||
openTestURL(); | |||
// Default borders and margins implemented by escalator | |||
int cellBorder = 1 + 1; | |||
int cellMargin = 2 + 2; | |||
WebElement cell = getBodyCellByRowAndColumn(1, 1); | |||
assertEquals((100 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
cell = getBodyCellByRowAndColumn(1, 2); | |||
assertEquals((150 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
cell = getBodyCellByRowAndColumn(1, 3); | |||
assertEquals((200 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
} | |||
@Test | |||
public void testColumnWidths() throws Exception { | |||
openTestURL(); | |||
// Default borders and margins implemented by escalator | |||
int cellBorder = 1 + 1; | |||
int cellMargin = 2 + 2; | |||
// Default column width is 100px | |||
WebElement cell = getBodyCellByRowAndColumn(1, 1); | |||
assertEquals((100 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
// Set first column to be 200px wide | |||
selectMenuPath("Component", "Columns", "Column0", "Column0 Width", | |||
"200px"); | |||
cell = getBodyCellByRowAndColumn(1, 1); | |||
assertEquals((200 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
// Set second column to be 150px wide | |||
selectMenuPath("Component", "Columns", "Column1", "Column1 Width", | |||
"150px"); | |||
cell = getBodyCellByRowAndColumn(1, 2); | |||
assertEquals((150 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
// Set first column to be auto sized (defaults to 100px currently) | |||
selectMenuPath("Component", "Columns", "Column0", "Column0 Width", | |||
"Auto"); | |||
cell = getBodyCellByRowAndColumn(1, 1); | |||
assertEquals((100 - cellBorder - cellMargin) + "px", | |||
cell.getCssValue("width")); | |||
} | |||
@Test | |||
public void testPrimaryStyleNames() throws Exception { | |||
openTestURL(); | |||
// v-grid is default primary style namea | |||
assertPrimaryStylename("v-grid"); | |||
selectMenuPath("Component", "State", "Primary style name", | |||
"v-escalator"); | |||
assertPrimaryStylename("v-escalator"); | |||
selectMenuPath("Component", "State", "Primary style name", "my-grid"); | |||
assertPrimaryStylename("my-grid"); | |||
selectMenuPath("Component", "State", "Primary style name", "v-grid"); | |||
assertPrimaryStylename("v-grid"); | |||
} | |||
/** | |||
* Test that the current view is updated when a server-side container change | |||
* occurs (without scrolling back and forth) | |||
*/ | |||
@Test | |||
public void testItemSetChangeEvent() throws Exception { | |||
openTestURL(); | |||
final By newRow = By.xpath("//td[text()='newcell: 0']"); | |||
assertTrue("Unexpected initial state", !elementIsFound(newRow)); | |||
selectMenuPath("Component", "Body rows", "Add first row"); | |||
assertTrue("Add row failed", elementIsFound(newRow)); | |||
selectMenuPath("Component", "Body rows", "Remove first row"); | |||
assertTrue("Remove row failed", !elementIsFound(newRow)); | |||
} | |||
/** | |||
* Test that the current view is updated when a property's value is reflect | |||
* to the client, when the value is modified server-side. | |||
*/ | |||
@Test | |||
public void testPropertyValueChangeEvent() throws Exception { | |||
openTestURL(); | |||
assertEquals("Unexpected cell initial state", "(0, 0)", | |||
getBodyCellByRowAndColumn(1, 1).getText()); | |||
selectMenuPath("Component", "Body rows", | |||
"Modify first row (getItemProperty)"); | |||
assertEquals("(First) modification with getItemProperty failed", | |||
"modified: 0", getBodyCellByRowAndColumn(1, 1).getText()); | |||
selectMenuPath("Component", "Body rows", | |||
"Modify first row (getContainerProperty)"); | |||
assertEquals("(Second) modification with getItemProperty failed", | |||
"modified: Column0", getBodyCellByRowAndColumn(1, 1).getText()); | |||
} | |||
private boolean elementIsFound(By locator) { | |||
try { | |||
return driver.findElement(locator) != null; | |||
} catch (NoSuchElementException e) { | |||
return false; | |||
} | |||
} | |||
private void assertPrimaryStylename(String stylename) { | |||
assertTrue(getGridElement().getAttribute("class").contains(stylename)); | |||
String tableWrapperStyleName = getTableWrapper().getAttribute("class"); | |||
assertTrue(tableWrapperStyleName.contains(stylename + "-tablewrapper")); | |||
String hscrollStyleName = getHorizontalScroller().getAttribute("class"); | |||
assertTrue(hscrollStyleName.contains(stylename + "-scroller")); | |||
assertTrue(hscrollStyleName | |||
.contains(stylename + "-scroller-horizontal")); | |||
String vscrollStyleName = getVerticalScroller().getAttribute("class"); | |||
assertTrue(vscrollStyleName.contains(stylename + "-scroller")); | |||
assertTrue(vscrollStyleName.contains(stylename + "-scroller-vertical")); | |||
} | |||
private WebElement getBodyCellByRowAndColumn(int row, int column) { | |||
return getDriver().findElement( | |||
By.xpath("//div[@id='testComponent']//tbody/tr[" + row | |||
+ "]/td[" + column + "]")); | |||
} | |||
private void selectSubMenu(String menuCaption) { | |||
selectMenu(menuCaption); | |||
new Actions(getDriver()).moveByOffset(100, 0).build().perform(); | |||
} | |||
private void selectMenu(String menuCaption) { | |||
getDriver().findElement( | |||
By.xpath("//span[text() = '" + menuCaption + "']")).click(); | |||
} | |||
private void selectMenuPath(String... menuCaptions) { | |||
selectMenu(menuCaptions[0]); | |||
for (int i = 1; i < menuCaptions.length; i++) { | |||
selectSubMenu(menuCaptions[i]); | |||
} | |||
} | |||
private WebElement getVerticalScroller() { | |||
return getDriver().findElement( | |||
By.xpath("//div[@id='testComponent']/div[1]")); | |||
} | |||
private WebElement getHorizontalScroller() { | |||
return getDriver().findElement( | |||
By.xpath("//div[@id='testComponent']/div[2]")); | |||
} | |||
private WebElement getTableWrapper() { | |||
return getDriver().findElement( | |||
By.xpath("//div[@id='testComponent']/div[3]")); | |||
} | |||
private WebElement getGridElement() { | |||
return getDriver().findElement(By.id("testComponent")); | |||
} | |||
private List<WebElement> getGridHeaderRowCells() { | |||
return getDriver().findElements( | |||
By.xpath("//div[@id='testComponent']//thead//th")); | |||
} | |||
private List<WebElement> getGridFooterRowCells() { | |||
return getDriver().findElements( | |||
By.xpath("//div[@id='testComponent']//tfoot//td")); | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* Copyright 2000-2013 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.data.util.IndexedContainer; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.components.grid.ColumnGroup; | |||
import com.vaadin.ui.components.grid.ColumnGroupRow; | |||
import com.vaadin.ui.components.grid.Grid; | |||
import com.vaadin.ui.components.grid.GridColumn; | |||
/** | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridColumnGroups extends AbstractTestUI { | |||
private final int COLUMNS = 4; | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
// Setup grid | |||
IndexedContainer ds = new IndexedContainer(); | |||
for (int col = 0; col < COLUMNS; col++) { | |||
ds.addContainerProperty("Column" + col, String.class, ""); | |||
} | |||
Grid grid = new Grid(ds); | |||
addComponent(grid); | |||
/*- | |||
* --------------------------------------------- | |||
* | Header 1 | <- Auxiliary row 2 | |||
* |-------------------------------------------| | |||
* | Header 2 | Header 3 | <- Auxiliary row 1 | |||
* |-------------------------------------------| | |||
* | Column 1 | Column 2 | Column 3 | Column 4 | <- Column headers | |||
* --------------------------------------------| | |||
* | ... | ... | ... | ... | | |||
* | ... | ... | ... | ... | | |||
* --------------------------------------------| | |||
* | Column 1 | Column 2 | Column 3 | Column 4 | <- Column footers | |||
* --------------------------------------------| | |||
* | Footer 2 | Footer 3 | <- Auxiliary row 1 | |||
* --------------------------------------------| | |||
* | Footer 1 | <- Auxiliary row 2 | |||
* --------------------------------------------- | |||
-*/ | |||
// Set column footers (headers are generated automatically) | |||
grid.setColumnFootersVisible(true); | |||
for (Object propertyId : ds.getContainerPropertyIds()) { | |||
GridColumn column = grid.getColumn(propertyId); | |||
column.setFooterCaption(String.valueOf(propertyId)); | |||
} | |||
// First auxiliary row | |||
ColumnGroupRow auxRow1 = grid.addColumnGroupRow(); | |||
// Using property id to create a column group | |||
ColumnGroup columns12 = auxRow1.addGroup("Column0", "Column1"); | |||
columns12.setHeaderCaption("Header 2"); | |||
columns12.setFooterCaption("Footer 2"); | |||
// Using grid columns to create a column group | |||
GridColumn column3 = grid.getColumn("Column2"); | |||
GridColumn column4 = grid.getColumn("Column3"); | |||
ColumnGroup columns34 = auxRow1.addGroup(column3, column4); | |||
columns34.setHeaderCaption("Header 3"); | |||
columns34.setFooterCaption("Footer 3"); | |||
// Second auxiliary row | |||
ColumnGroupRow auxRow2 = grid.addColumnGroupRow(); | |||
// Using previous groups to create a column group | |||
ColumnGroup columns1234 = auxRow2.addGroup(columns12, columns34); | |||
columns1234.setHeaderCaption("Header 1"); | |||
columns1234.setFooterCaption("Footer 1"); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Grid should support headers and footer groups"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 12894; | |||
} | |||
} |
@@ -0,0 +1,115 @@ | |||
/* | |||
* Copyright 2000-2013 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.data.Item; | |||
import com.vaadin.data.util.IndexedContainer; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.VerticalLayout; | |||
import com.vaadin.ui.components.grid.Grid; | |||
/** | |||
* | |||
*/ | |||
@SuppressWarnings("serial") | |||
public class GridScrolling extends AbstractTestUI { | |||
private Grid grid; | |||
private IndexedContainer ds; | |||
@Override | |||
@SuppressWarnings("unchecked") | |||
protected void setup(VaadinRequest request) { | |||
// Build data source | |||
ds = new IndexedContainer(); | |||
for (int col = 0; col < 5; col++) { | |||
ds.addContainerProperty("col" + col, String.class, ""); | |||
} | |||
for (int row = 0; row < 65536; row++) { | |||
Item item = ds.addItem(Integer.valueOf(row)); | |||
for (int col = 0; col < 5; col++) { | |||
item.getItemProperty("col" + col).setValue( | |||
"(" + row + ", " + col + ")"); | |||
} | |||
} | |||
grid = new Grid(ds); | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
hl.addComponent(grid); | |||
hl.setMargin(true); | |||
hl.setSpacing(true); | |||
VerticalLayout vl = new VerticalLayout(); | |||
vl.setSpacing(true); | |||
// Add scroll buttons | |||
Button scrollUpButton = new Button("Top", new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
grid.scrollToStart(); | |||
} | |||
}); | |||
scrollUpButton.setSizeFull(); | |||
vl.addComponent(scrollUpButton); | |||
for (int i = 1; i < 7; ++i) { | |||
final int row = (ds.size() / 7) * i; | |||
Button scrollButton = new Button("Scroll to row " + row, | |||
new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
grid.scrollToItem(Integer.valueOf(row), | |||
ScrollDestination.MIDDLE); | |||
} | |||
}); | |||
scrollButton.setSizeFull(); | |||
vl.addComponent(scrollButton); | |||
} | |||
Button scrollDownButton = new Button("Bottom", new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
grid.scrollToEnd(); | |||
} | |||
}); | |||
scrollDownButton.setSizeFull(); | |||
vl.addComponent(scrollDownButton); | |||
hl.addComponent(vl); | |||
addComponent(hl); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Test Grid programmatic scrolling features"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 13327; | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
/* | |||
* Copyright 2000-2013 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.widgetset.client.grid; | |||
import com.vaadin.shared.communication.ClientRpc; | |||
public interface TestGridClientRpc extends ClientRpc { | |||
void insertRows(int offset, int amount); | |||
void removeRows(int offset, int amount); | |||
void insertColumns(int offset, int amount); | |||
void removeColumns(int offset, int amount); | |||
void scrollToRow(int index, String destination, int padding); | |||
void scrollToColumn(int index, String destination, int padding); | |||
void setFrozenColumns(int frozenColumns); | |||
void insertHeaders(int index, int amount); | |||
void removeHeaders(int index, int amount); | |||
void insertFooters(int index, int amount); | |||
void removeFooters(int index, int amount); | |||
void setColumnWidth(int index, int px); | |||
void calculateColumnWidths(); | |||
void randomRowHeight(); | |||
} |
@@ -0,0 +1,138 @@ | |||
/* | |||
* Copyright 2000-2013 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.widgetset.client.grid; | |||
import com.google.gwt.user.client.Random; | |||
import com.vaadin.client.ui.AbstractComponentConnector; | |||
import com.vaadin.shared.ui.Connect; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
import com.vaadin.tests.widgetset.server.grid.TestGrid; | |||
/** | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
@Connect(TestGrid.class) | |||
public class TestGridConnector extends AbstractComponentConnector { | |||
@Override | |||
protected void init() { | |||
super.init(); | |||
registerRpc(TestGridClientRpc.class, new TestGridClientRpc() { | |||
@Override | |||
public void insertRows(int offset, int amount) { | |||
getWidget().insertRows(offset, amount); | |||
} | |||
@Override | |||
public void removeRows(int offset, int amount) { | |||
getWidget().removeRows(offset, amount); | |||
} | |||
@Override | |||
public void removeColumns(int offset, int amount) { | |||
getWidget().removeColumns(offset, amount); | |||
} | |||
@Override | |||
public void insertColumns(int offset, int amount) { | |||
getWidget().insertColumns(offset, amount); | |||
} | |||
@Override | |||
public void scrollToRow(int index, String destination, int padding) { | |||
getWidget().scrollToRow(index, getDestination(destination), | |||
padding); | |||
} | |||
@Override | |||
public void scrollToColumn(int index, String destination, | |||
int padding) { | |||
getWidget().scrollToColumn(index, getDestination(destination), | |||
padding); | |||
} | |||
private ScrollDestination getDestination(String destination) { | |||
final ScrollDestination d; | |||
if (destination.equals("start")) { | |||
d = ScrollDestination.START; | |||
} else if (destination.equals("middle")) { | |||
d = ScrollDestination.MIDDLE; | |||
} else if (destination.equals("end")) { | |||
d = ScrollDestination.END; | |||
} else { | |||
d = ScrollDestination.ANY; | |||
} | |||
return d; | |||
} | |||
@Override | |||
public void setFrozenColumns(int frozenColumns) { | |||
getWidget().getColumnConfiguration().setFrozenColumnCount( | |||
frozenColumns); | |||
} | |||
@Override | |||
public void insertHeaders(int index, int amount) { | |||
getWidget().getHeader().insertRows(index, amount); | |||
} | |||
@Override | |||
public void removeHeaders(int index, int amount) { | |||
getWidget().getHeader().removeRows(index, amount); | |||
} | |||
@Override | |||
public void insertFooters(int index, int amount) { | |||
getWidget().getFooter().insertRows(index, amount); | |||
} | |||
@Override | |||
public void removeFooters(int index, int amount) { | |||
getWidget().getFooter().removeRows(index, amount); | |||
} | |||
@Override | |||
public void setColumnWidth(int index, int px) { | |||
getWidget().getColumnConfiguration().setColumnWidth(index, px); | |||
} | |||
@Override | |||
public void calculateColumnWidths() { | |||
getWidget().calculateColumnWidths(); | |||
} | |||
@Override | |||
public void randomRowHeight() { | |||
getWidget().getHeader().setDefaultRowHeight( | |||
Random.nextInt(20) + 20); | |||
getWidget().getBody().setDefaultRowHeight( | |||
Random.nextInt(20) + 20); | |||
getWidget().getFooter().setDefaultRowHeight( | |||
Random.nextInt(20) + 20); | |||
} | |||
}); | |||
} | |||
@Override | |||
public VTestGrid getWidget() { | |||
return (VTestGrid) super.getWidget(); | |||
} | |||
@Override | |||
public TestGridState getState() { | |||
return (TestGridState) super.getState(); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* Copyright 2000-2013 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.widgetset.client.grid; | |||
import com.vaadin.shared.AbstractComponentState; | |||
/** | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class TestGridState extends AbstractComponentState { | |||
public static final String DEFAULT_HEIGHT = "400.0px"; | |||
/* TODO: this should be "100%" before setting final. */ | |||
public static final String DEFAULT_WIDTH = "800.0px"; | |||
} |
@@ -0,0 +1,220 @@ | |||
package com.vaadin.tests.widgetset.client.grid; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import com.google.gwt.user.client.ui.Composite; | |||
import com.vaadin.client.ui.grid.Cell; | |||
import com.vaadin.client.ui.grid.ColumnConfiguration; | |||
import com.vaadin.client.ui.grid.Escalator; | |||
import com.vaadin.client.ui.grid.EscalatorUpdater; | |||
import com.vaadin.client.ui.grid.Row; | |||
import com.vaadin.client.ui.grid.RowContainer; | |||
import com.vaadin.shared.ui.grid.ScrollDestination; | |||
public class VTestGrid extends Composite { | |||
private static class Data { | |||
private int columnCounter = 0; | |||
private int rowCounter = 0; | |||
private final List<Integer> columns = new ArrayList<Integer>(); | |||
private final List<Integer> rows = new ArrayList<Integer>(); | |||
@SuppressWarnings("boxing") | |||
public void insertRows(final int offset, final int amount) { | |||
final List<Integer> newRows = new ArrayList<Integer>(); | |||
for (int i = 0; i < amount; i++) { | |||
newRows.add(rowCounter++); | |||
} | |||
rows.addAll(offset, newRows); | |||
} | |||
@SuppressWarnings("boxing") | |||
public void insertColumns(final int offset, final int amount) { | |||
final List<Integer> newColumns = new ArrayList<Integer>(); | |||
for (int i = 0; i < amount; i++) { | |||
newColumns.add(columnCounter++); | |||
} | |||
columns.addAll(offset, newColumns); | |||
} | |||
public EscalatorUpdater createHeaderUpdater() { | |||
return new EscalatorUpdater() { | |||
@Override | |||
public void updateCells(final Row row, | |||
final Iterable<Cell> cellsToUpdate) { | |||
for (final Cell cell : cellsToUpdate) { | |||
if (cell.getColumn() % 3 == 0) { | |||
cell.setColSpan(2); | |||
} | |||
final Integer columnName = columns | |||
.get(cell.getColumn()); | |||
cell.getElement().setInnerText("Header " + columnName); | |||
} | |||
} | |||
}; | |||
} | |||
public EscalatorUpdater createFooterUpdater() { | |||
return new EscalatorUpdater() { | |||
@Override | |||
public void updateCells(final Row row, | |||
final Iterable<Cell> cellsToUpdate) { | |||
for (final Cell cell : cellsToUpdate) { | |||
if (cell.getColumn() % 3 == 1) { | |||
cell.setColSpan(2); | |||
} | |||
final Integer columnName = columns | |||
.get(cell.getColumn()); | |||
cell.getElement().setInnerText("Footer " + columnName); | |||
} | |||
} | |||
}; | |||
} | |||
public EscalatorUpdater createBodyUpdater() { | |||
return new EscalatorUpdater() { | |||
private int i = 0; | |||
public void renderCell(final Cell cell) { | |||
final Integer columnName = columns.get(cell.getColumn()); | |||
final Integer rowName = rows.get(cell.getRow()); | |||
final String cellInfo = columnName + "," + rowName + " (" | |||
+ i + ")"; | |||
if (cell.getColumn() > 0) { | |||
cell.getElement().setInnerText("Cell: " + cellInfo); | |||
} else { | |||
cell.getElement().setInnerText( | |||
"Row " + cell.getRow() + ": " + cellInfo); | |||
} | |||
if (cell.getColumn() % 3 == cell.getRow() % 3) { | |||
cell.setColSpan(3); | |||
} | |||
final double c = i * .1; | |||
final int r = (int) ((Math.cos(c) + 1) * 128); | |||
final int g = (int) ((Math.cos(c / Math.PI) + 1) * 128); | |||
final int b = (int) ((Math.cos(c / (Math.PI * 2)) + 1) * 128); | |||
cell.getElement() | |||
.getStyle() | |||
.setBackgroundColor( | |||
"rgb(" + r + "," + g + "," + b + ")"); | |||
if ((r * .8 + g * 1.3 + b * .9) / 3 < 127) { | |||
cell.getElement().getStyle().setColor("white"); | |||
} else { | |||
cell.getElement().getStyle().clearColor(); | |||
} | |||
i++; | |||
} | |||
@Override | |||
public void updateCells(final Row row, | |||
final Iterable<Cell> cellsToUpdate) { | |||
for (final Cell cell : cellsToUpdate) { | |||
renderCell(cell); | |||
} | |||
} | |||
}; | |||
} | |||
public void removeRows(final int offset, final int amount) { | |||
for (int i = 0; i < amount; i++) { | |||
rows.remove(offset); | |||
} | |||
} | |||
public void removeColumns(final int offset, final int amount) { | |||
for (int i = 0; i < amount; i++) { | |||
columns.remove(offset); | |||
} | |||
} | |||
} | |||
private final Escalator escalator = new Escalator(); | |||
private final Data data = new Data(); | |||
public VTestGrid() { | |||
initWidget(escalator); | |||
final RowContainer header = escalator.getHeader(); | |||
header.setEscalatorUpdater(data.createHeaderUpdater()); | |||
header.insertRows(0, 1); | |||
final RowContainer footer = escalator.getFooter(); | |||
footer.setEscalatorUpdater(data.createFooterUpdater()); | |||
footer.insertRows(0, 1); | |||
escalator.getBody().setEscalatorUpdater(data.createBodyUpdater()); | |||
insertRows(0, 100); | |||
insertColumns(0, 10); | |||
setWidth(TestGridState.DEFAULT_WIDTH); | |||
setHeight(TestGridState.DEFAULT_HEIGHT); | |||
} | |||
public void insertRows(final int offset, final int number) { | |||
data.insertRows(offset, number); | |||
escalator.getBody().insertRows(offset, number); | |||
} | |||
public void insertColumns(final int offset, final int number) { | |||
data.insertColumns(offset, number); | |||
escalator.getColumnConfiguration().insertColumns(offset, number); | |||
} | |||
public ColumnConfiguration getColumnConfiguration() { | |||
return escalator.getColumnConfiguration(); | |||
} | |||
public void scrollToRow(final int index, | |||
final ScrollDestination destination, final int padding) { | |||
escalator.scrollToRow(index, destination, padding); | |||
} | |||
public void scrollToColumn(final int index, | |||
final ScrollDestination destination, final int padding) { | |||
escalator.scrollToColumn(index, destination, padding); | |||
} | |||
public void removeRows(final int offset, final int amount) { | |||
data.removeRows(offset, amount); | |||
escalator.getBody().removeRows(offset, amount); | |||
} | |||
public void removeColumns(final int offset, final int amount) { | |||
data.removeColumns(offset, amount); | |||
escalator.getColumnConfiguration().removeColumns(offset, amount); | |||
} | |||
@Override | |||
public void setWidth(String width) { | |||
escalator.setWidth(width); | |||
} | |||
@Override | |||
public void setHeight(String height) { | |||
escalator.setHeight(height); | |||
} | |||
public RowContainer getHeader() { | |||
return escalator.getHeader(); | |||
} | |||
public RowContainer getBody() { | |||
return escalator.getBody(); | |||
} | |||
public RowContainer getFooter() { | |||
return escalator.getFooter(); | |||
} | |||
public void calculateColumnWidths() { | |||
escalator.calculateColumnWidths(); | |||
} | |||
} |
@@ -0,0 +1,96 @@ | |||
/* | |||
* Copyright 2000-2013 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.widgetset.server.grid; | |||
import com.vaadin.tests.widgetset.client.grid.TestGridClientRpc; | |||
import com.vaadin.tests.widgetset.client.grid.TestGridState; | |||
import com.vaadin.ui.AbstractComponent; | |||
/** | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class TestGrid extends AbstractComponent { | |||
public TestGrid() { | |||
setWidth(TestGridState.DEFAULT_WIDTH); | |||
setHeight(TestGridState.DEFAULT_HEIGHT); | |||
} | |||
@Override | |||
protected TestGridState getState() { | |||
return (TestGridState) super.getState(); | |||
} | |||
public void insertRows(int offset, int amount) { | |||
rpc().insertRows(offset, amount); | |||
} | |||
public void removeRows(int offset, int amount) { | |||
rpc().removeRows(offset, amount); | |||
} | |||
public void insertColumns(int offset, int amount) { | |||
rpc().insertColumns(offset, amount); | |||
} | |||
public void removeColumns(int offset, int amount) { | |||
rpc().removeColumns(offset, amount); | |||
} | |||
private TestGridClientRpc rpc() { | |||
return getRpcProxy(TestGridClientRpc.class); | |||
} | |||
public void scrollToRow(int index, String destination, int padding) { | |||
rpc().scrollToRow(index, destination, padding); | |||
} | |||
public void scrollToColumn(int index, String destination, int padding) { | |||
rpc().scrollToColumn(index, destination, padding); | |||
} | |||
public void setFrozenColumns(int frozenColumns) { | |||
rpc().setFrozenColumns(frozenColumns); | |||
} | |||
public void insertHeaders(int index, int amount) { | |||
rpc().insertHeaders(index, amount); | |||
} | |||
public void removeHeaders(int index, int amount) { | |||
rpc().removeHeaders(index, amount); | |||
} | |||
public void insertFooters(int index, int amount) { | |||
rpc().insertFooters(index, amount); | |||
} | |||
public void removeFooters(int index, int amount) { | |||
rpc().removeFooters(index, amount); | |||
} | |||
public void setColumnWidth(int index, int px) { | |||
rpc().setColumnWidth(index, px); | |||
} | |||
public void calculateColumnWidths() { | |||
rpc().calculateColumnWidths(); | |||
} | |||
public void randomizeDefaultRowHeight() { | |||
rpc().randomRowHeight(); | |||
} | |||
} |