View Javadoc
1   package org.vaadin.extension.gridscroll;
2   
3   import java.lang.reflect.Method;
4   
5   import org.vaadin.extension.gridscroll.shared.ColumnResizeCompensationMode;
6   import org.vaadin.extension.gridscroll.shared.GridScrollExtensionClientRPC;
7   import org.vaadin.extension.gridscroll.shared.GridScrollExtensionServerRPC;
8   import org.vaadin.extension.gridscroll.shared.GridScrollExtensionState;
9   
10  import com.vaadin.event.ConnectorEventListener;
11  import com.vaadin.server.AbstractExtension;
12  import com.vaadin.shared.Registration;
13  import com.vaadin.ui.Grid;
14  import com.vaadin.ui.UI;
15  import com.vaadin.ui.Grid.Column;
16  import com.vaadin.util.ReflectTools;
17  
18  /**
19   * GridScrollExtension is multi purpose Grid enhancement package. It features 
20   * additional events (resize, scroll, etc.), methods for pixelwise scrolling and
21   * methods for componsating empty space appearing to Grid right side when columns
22   * or Grid is being resized. 
23   * 
24   * @param <T> Bean type of the Grid
25   * 
26   * @author Tatu Lund
27   *
28   */
29  @SuppressWarnings("serial")
30  public class GridScrollExtension<T> extends AbstractExtension {
31  	private int lastXPosition;
32  	private int lastYPosition;
33  	private double[] columnWidths;
34  	private Grid<T> grid;
35  	private int lastWidth;
36  	private int lastHeight;
37  	private boolean restorePosition = true;	
38  	
39  	/**
40  	 * A listener interface for {@link GridScrolledEvent}
41  	 * 
42  	 * @param <T> Bean type of the Grid
43  	 */
44  	public interface GridScrolledListener<T> extends ConnectorEventListener {
45  		Method GRID_SCROLLED_METHOD = ReflectTools.findMethod(
46  				GridScrolledListener.class, "gridScrolled", GridScrolledEvent.class);
47  		public void gridScrolled(GridScrolledEvent<T> event);
48  	}
49  	
50  	/**
51  	 * A Listener interface for {@link GridRenderedEvent}
52  	 *
53  	 * @param <T> Bean type of the Grid
54  	 */
55  	public interface GridRenderedListener<T> extends ConnectorEventListener {
56  		Method GRID_RENDERED_METHOD = ReflectTools.findMethod(
57  				GridRenderedListener.class, "gridRendered", GridRenderedEvent.class);
58  		public void gridRendered(GridRenderedEvent<T> event);
59  	}
60  
61  	/**
62  	 * A Listener interface for {@link GridResizedEvent}
63  	 * 
64  	 * @param <T> Bean type of the Grid
65  	 */
66  	public interface GridResizedListener<T> extends ConnectorEventListener {
67  		Method GRID_RESIZED_METHOD = ReflectTools.findMethod(
68  				GridResizedListener.class, "gridResized", GridResizedEvent.class);
69  		public void gridResized(GridResizedEvent<T> event);
70  	}
71  	
72  	/**
73  	 * A Listener interface for {@link GridColumnsResizedEvent}
74  	 * 
75  	 * @param <T> Bean type of the Grid
76  	 */
77  	public interface GridColumnsResizedListener<T> extends ConnectorEventListener {
78  		Method GRID_COLUMNS_RESIZED_METHOD = ReflectTools.findMethod(
79  				GridColumnsResizedListener.class, "gridColumnsResized", GridColumnsResizedEvent.class);
80  		public void gridColumnsResized(GridColumnsResizedEvent<T> event);
81  	}
82  	
83  	/**
84  	 * Constructor method for the extension
85  	 * 
86  	 * @param grid Grid to be extended
87  	 */	
88  	public GridScrollExtension(Grid<T> grid) {
89  		this.extend(grid);
90  		this.grid = grid;
91  		registerRpc(new GridScrollExtensionServerRPC() {
92  
93  			@Override
94  			public void ping() {
95  			}
96  
97  			@Override
98  			public void reportPosition(int Xposition, int Yposition) {
99  				if (restorePosition && (Xposition == -1 || Yposition == -1))  {
100 					restoreScrollPosition();
101 				} else {
102 					lastXPosition = Xposition;
103 					lastYPosition = Yposition;
104 					fireEvent(new GridScrolledEvent<T>(grid));
105 				}
106 			}
107 
108 			@Override
109 			public void reportColumns(double[] widths, int column) {
110 				columnWidths = widths;				
111 				fireEvent(new GridColumnsResizedEvent<T>(grid,column));
112 				// This is awful, but needed to overcome race condition with faulty 
113 				// internal mechanics of the Grid
114 				if (column != -1) {
115 					UI ui = UI.getCurrent();
116 					Thread t = new Thread(() -> {
117 						try {
118 							Thread.sleep(50);
119 						} catch (InterruptedException e) {
120 						}
121 						ui.access(() -> {
122 							grid.getColumns().get(column).setWidth(widths[column]);
123 						});
124 					});
125 					t.start();
126 				}
127 			}
128 
129 			@Override
130 			public void reportSize(int width, int height) {
131 				lastWidth = width;
132 				lastHeight = height;
133 				fireEvent(new GridResizedEvent<T>(grid));
134 			}
135 
136 			@Override
137 			public void gridInitialColumnWidthsCalculated() {
138 				fireEvent(new GridRenderedEvent<T>(grid)); 
139 			}
140 		});
141 	}
142 	
143 	
144 	/**
145 	 * Add a new GridRenderedListener
146 	 * The GridRenderedEvent event is fired once after Grid's initial column width calculation is complete
147 	 * 
148 	 * @since 2.2.0
149 	 * 
150 	 * @param listener A GridRenderedListener to be added
151 	 */
152 	public Registration addGridRenderedListener(GridRenderedListener<T> listener) {
153 		return addListener(GridRenderedEvent.class, listener, GridRenderedListener.GRID_RENDERED_METHOD);
154 	}
155 	
156 	/**
157 	 * Add a new {@link GridResizedListener}
158 	 * The {@link GridResizedEvent} event is fired every time Grid size has been changed 
159      * when {@link GridScrollExtension#setColumnResizeCompensationMode(org.vaadin.extension.gridscroll.shared.ColumnResizeCompensationMode)}
160      * with {@link org.vaadin.extension.gridscroll.shared.ColumnResizeCompensationMode#RESIZE_GRID} has been applied.
161 	 * 
162 	 * @since 2.2.0
163 	 * 
164 	 * @param listener A GridResizedListener to be added
165 	 */
166 	public Registration addGridResizedListener(GridResizedListener<T> listener) {
167 		return addListener(GridResizedEvent.class, listener, GridResizedListener.GRID_RESIZED_METHOD);
168 	}
169 
170 	/**
171 	 * Add a new {@link GridColumnsResizedListener}
172 	 * The {@link GridColumnsResizedEvent} event is fired every time Grid column sizes has been changed
173 	 * 
174 	 * Note, there is similar event in Grid, but that is fired before you can fetch real column widths, this
175 	 * event is fired after widths are available, hence you can get correct widths
176 	 * 
177 	 * @since 2.2.0
178 	 * 
179 	 * @param listener A GridColumnsResizedListener to be added
180 	 */
181 	public Registration addGridColumnsResizedListener(GridColumnsResizedListener<T> listener) {
182 		return addListener(GridColumnsResizedEvent.class, listener, GridColumnsResizedListener.GRID_COLUMNS_RESIZED_METHOD);
183 	}
184 	
185 	/**
186 	 * Add a new {@link GridScrolledListener}
187 	 * The {@link GridScrolledEvent} event is fired when Grid scroll position changes
188 	 * 
189 	 * @since 2.2.0
190 	 *  
191 	 * @param listener A GridScrolledListener to be added
192 	 */
193 	public Registration addGridScrolledListener(GridScrolledListener<T> listener) {
194 		return addListener(GridScrolledEvent.class, listener, GridScrolledListener.GRID_SCROLLED_METHOD);
195 	}
196 	
197 	
198 	/**
199 	 * Recalculate the Grid's width and adjust it to according to actual column widths
200 	 * 
201 	 * Programmatic change of column widths do not trigger column resize event, hence you
202 	 * need to call this if you want to refit Grid 
203 	 * 
204 	 * @since 2.2.0
205 	 */
206 	public void adjustGridWidth() {
207 		getClientRPC().recalculateGridWidth();		
208 	}
209 	
210 	/**
211 	 * Adjust last the width of the last column to occupy remaining space (if such exist)
212 	 * 
213 	 * Programmatic change of column widths do not trigger column resize event, hence you
214 	 * need to call this if you want to refit Grid 
215 	 */
216 	public void adjustLastColumnWidth() {
217 		getClientRPC().adjustLastColumn();		
218 	}
219 	
220 	/**
221 	 * Get actual width of the column by column reference
222 	 * Note: There is small delay after Grid has been attached before real widths are available
223 	 * 
224 	 * @since 2.1.0
225 	 * 
226 	 * @param column The column reference
227 	 * @return Actual width of the column in pixels double value
228 	 */
229 	public double getColumnWidth(Column<?,?> column) {
230 		double width = 0;
231 		int i = 0;
232 		if (columnWidths == null) return -1.0;
233 		if (column.isHidden()) return -1.0;
234 		for (Column<?, ?> col : grid.getColumns()) {
235 			if (col == column) width = columnWidths[i];
236 			if (!col.isHidden()) i++;
237 		}
238 		return width;		
239 	}
240 	
241 	/**
242 	 * Get actual width of the column by columnId 
243 	 * Note: There is small delay after Grid has been attached before real widths are available
244 	 * 
245 	 * @since 2.1.0
246      *
247 	 * @param columnId Id string / property name of the column
248 	 * @return Actual width of the column in pixels double value
249 	 */
250 	public double getColumnWidth(String columnId) {
251 		double width = 0;
252 		int i = 0;
253 		if (columnWidths == null) return -1.0;
254 		for (Column<?, ?> column : grid.getColumns()) {
255 			if (column.getId().equals(columnId)) width = columnWidths[i];
256 			i++;
257 		}
258 		return width;
259 	}
260 
261 	/**
262 	 * Get actual width of the column by index 
263 	 * Note: There is small delay after Grid has been attached before real widths are available
264 	 * 
265 	 * @since 2.1.0
266      *
267 	 * @param i Index of the column
268 	 * @return Actual width of the column in pixels double value
269 	 */
270 	public double getColumnWidth(int i) {
271 		if (columnWidths == null) return -1.0;
272 		if (i > grid.getColumns().size()) return -1.0;
273 		return columnWidths[i];
274 	}
275 
276 	/**
277 	 * Make Grid to scroll to the last known scroll position
278 	 * 
279 	 */
280 	public void restoreScrollPosition() {
281 		setScrollPosition(lastXPosition, lastYPosition);
282 	}
283 
284 	/**
285 	 * Get X scroll position of the Grid
286 	 * 
287 	 * @return Scroll position X coordinate in pixels as int
288 	 */	
289 	public int getLastXPosition() {
290 		return lastXPosition;
291 	}
292 
293 	/**
294 	 * Get Y scroll position of the Grid
295 	 * 
296 	 * @return Scroll position Y coordinate in pixels as int
297 	 */
298 	public int getLastYPosition() {
299 		return lastYPosition;
300 	}
301 
302 	/**
303 	 * Get actual width
304 	 * 
305 	 * @since 2.2.0
306 	 * 
307 	 * @return The last reported actual width of the Grid 
308 	 */
309 	public int getWidth() {
310 		return lastWidth;
311 	}
312 	
313 	/**
314 	 * Get actual height
315 	 * 
316 	 * @since 2.2.0
317 	 * 
318 	 * @return The last reported actual height of the Grid 
319 	 */
320 	public int getHeight() {
321 		return lastHeight;
322 	}
323 	
324 	/**
325 	 * Set new scroll position in pixels and scroll grid to that position
326 	 * 
327 	 * @param Xposition The new y scroll position
328 	 * @param Yposition The new y scroll position
329 	 */
330 	public void setScrollPosition(int Xposition, int Yposition) {
331 		getClientRPC().setScrollPosition(Xposition, Yposition);
332 		lastXPosition = Xposition;
333 		lastYPosition = Yposition;
334 	}
335 
336 	private GridScrollExtensionClientRPC getClientRPC() {
337 		return getRpcProxy(GridScrollExtensionClientRPC.class);
338 	}
339 
340 	/**
341 	 * Set Grid to resize itself according to column widths automatically
342 	 * 
343 	 * @deprecated since 2.3.0, use {@link #setColumnResizeCompensationMode(ColumnResizeCompensationMode)} instead.
344 	 * 
345 	 * @param autoResizeWidth If true Grid resizes itself to column widths 
346 	 */
347 	@Deprecated
348 	public void setAutoResizeWidth(boolean autoResizeWidth) {
349 		if (autoResizeWidth) getState().compensationMode = ColumnResizeCompensationMode.RESIZE_GRID;
350 		else getState().compensationMode = ColumnResizeCompensationMode.NONE;
351 	}
352 	
353 	/**
354 	 * @deprecated since 2.3.4, please use {@link #setColumnResizeCompensationMode(ColumnResizeCompensationMode)} instead.
355 	 */
356 	@Deprecated
357 	public void setColumnResizeComponesationMode(ColumnResizeCompensationMode mode) {
358 		this.setColumnResizeCompensationMode(mode);
359 	}
360 
361 	/**
362 	 * Set how Grid should be adjusted when columns are being resized by user
363 	 * ColumnResizeCompensationMode.RESIZE_GRID will adjust Grid width
364 	 * and ColumnResizeCompensationMode.RESIZE_COLUMN the last column.
365 	 * Default is ColumnResizeCompensationMode.NONE.
366 	 *
367 	 * Note: ColumnResizeCompensationMode.RESIZE_COLUM takes effect also when
368 	 * Grid is being resized.
369 	 *
370 	 * Note: When ColumnResizeCompensationMode.RESIZE_COLUM is used, the {@link com.vaadin.ui.Grid.Column#setMaximumWidth(double)}
371 	 * cannot be used with the last column
372 	 * 
373 	 * @since 2.3.0
374 	 *
375 	 * @param mode ColumnResizeCompensationMode
376 	 */
377 	public void setColumnResizeCompensationMode(ColumnResizeCompensationMode mode) {
378 		getState().compensationMode = mode;
379 	}
380 
381 	/**
382 	 * @deprecated since 2.3.4, please use {@link #getColumnResizeCompensationMode()} instead.
383 	 */
384 	@Deprecated
385 	public ColumnResizeCompensationMode getColumnResizeComponesationMode() {
386 		return this.getColumnResizeCompensationMode();
387 	}
388 
389 	/**
390 	 * Get the current compensation mode
391 	 *
392 	 * @since 2.3.0
393 	 *
394 	 * @return The current compensation mode
395 	 */
396 	public ColumnResizeCompensationMode getColumnResizeCompensationMode() {
397 		return getState().compensationMode;
398 	}
399 	
400     @Override
401     public GridScrollExtensionState getState() {
402         return (GridScrollExtensionState) super.getState();
403     }
404 
405     /**
406      * By default extension restores the last known position in order to make Grid recover
407      * position during {@link com.vaadin.ui.TabSheet.Tab} changes. There is a side effect
408      * of this, that {@link Grid#scrollTo(int)} does not work when calling right after Grid
409      * has been created. This feature can be turned off if needed by setting this to false.
410      * 
411      * @since 2.3.3
412      * 
413      * @param restorePosition Use false to disable automatic position restore feature
414      */
415     public void setRestorePosition(boolean restorePosition) {
416     	this.restorePosition = restorePosition;
417     }
418 }