Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.lang.reflect.Type;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.Comparator;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Iterator;
  28. import java.util.LinkedHashSet;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Objects;
  32. import java.util.Optional;
  33. import java.util.Set;
  34. import java.util.function.BinaryOperator;
  35. import java.util.function.Function;
  36. import java.util.stream.Collectors;
  37. import java.util.stream.Stream;
  38. import com.vaadin.event.ConnectorEvent;
  39. import com.vaadin.event.ContextClickEvent;
  40. import com.vaadin.event.EventListener;
  41. import com.vaadin.server.EncodeResult;
  42. import com.vaadin.server.JsonCodec;
  43. import com.vaadin.server.SerializableComparator;
  44. import com.vaadin.server.SerializableFunction;
  45. import com.vaadin.server.data.SortOrder;
  46. import com.vaadin.shared.MouseEventDetails;
  47. import com.vaadin.shared.Registration;
  48. import com.vaadin.shared.data.DataCommunicatorConstants;
  49. import com.vaadin.shared.data.sort.SortDirection;
  50. import com.vaadin.shared.ui.grid.ColumnState;
  51. import com.vaadin.shared.ui.grid.GridConstants;
  52. import com.vaadin.shared.ui.grid.GridConstants.Section;
  53. import com.vaadin.shared.ui.grid.GridServerRpc;
  54. import com.vaadin.shared.ui.grid.GridState;
  55. import com.vaadin.shared.ui.grid.HeightMode;
  56. import com.vaadin.shared.ui.grid.SectionState;
  57. import com.vaadin.shared.util.SharedUtil;
  58. import com.vaadin.ui.Grid.FooterRow;
  59. import com.vaadin.ui.components.grid.Footer;
  60. import com.vaadin.ui.components.grid.Header;
  61. import com.vaadin.ui.components.grid.Header.Row;
  62. import com.vaadin.ui.renderers.AbstractRenderer;
  63. import com.vaadin.ui.renderers.Renderer;
  64. import com.vaadin.ui.renderers.TextRenderer;
  65. import com.vaadin.util.ReflectTools;
  66. import elemental.json.Json;
  67. import elemental.json.JsonObject;
  68. import elemental.json.JsonValue;
  69. /**
  70. * A grid component for displaying tabular data.
  71. *
  72. * @author Vaadin Ltd
  73. * @since 8.0
  74. *
  75. * @param <T>
  76. * the grid bean type
  77. */
  78. public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
  79. @Deprecated
  80. private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod(
  81. ColumnReorderListener.class, "columnReorder",
  82. ColumnReorderEvent.class);
  83. @Deprecated
  84. private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(
  85. ColumnResizeListener.class, "columnResize",
  86. ColumnResizeEvent.class);
  87. @Deprecated
  88. private static final Method ITEM_CLICK_METHOD = ReflectTools
  89. .findMethod(ItemClickListener.class, "accept", ItemClick.class);
  90. @Deprecated
  91. private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools
  92. .findMethod(ColumnVisibilityChangeListener.class,
  93. "columnVisibilityChanged",
  94. ColumnVisibilityChangeEvent.class);
  95. /**
  96. * An event listener for column reorder events in the Grid.
  97. */
  98. @FunctionalInterface
  99. public interface ColumnReorderListener extends Serializable {
  100. /**
  101. * Called when the columns of the grid have been reordered.
  102. *
  103. * @param event
  104. * An event providing more information
  105. */
  106. void columnReorder(ColumnReorderEvent event);
  107. }
  108. /**
  109. * An event listener for column resize events in the Grid.
  110. */
  111. @FunctionalInterface
  112. public interface ColumnResizeListener extends Serializable {
  113. /**
  114. * Called when the columns of the grid have been resized.
  115. *
  116. * @param event
  117. * An event providing more information
  118. */
  119. void columnResize(ColumnResizeEvent event);
  120. }
  121. /**
  122. * An event that is fired when the columns are reordered.
  123. */
  124. public static class ColumnReorderEvent extends Component.Event {
  125. private final boolean userOriginated;
  126. /**
  127. *
  128. * @param source
  129. * the grid where the event originated from
  130. * @param userOriginated
  131. * <code>true</code> if event is a result of user
  132. * interaction, <code>false</code> if from API call
  133. */
  134. public ColumnReorderEvent(Grid source, boolean userOriginated) {
  135. super(source);
  136. this.userOriginated = userOriginated;
  137. }
  138. /**
  139. * Returns <code>true</code> if the column reorder was done by the user,
  140. * <code>false</code> if not and it was triggered by server side code.
  141. *
  142. * @return <code>true</code> if event is a result of user interaction
  143. */
  144. public boolean isUserOriginated() {
  145. return userOriginated;
  146. }
  147. }
  148. /**
  149. * An event that is fired when a column is resized, either programmatically
  150. * or by the user.
  151. */
  152. public static class ColumnResizeEvent extends Component.Event {
  153. private final Column<?, ?> column;
  154. private final boolean userOriginated;
  155. /**
  156. *
  157. * @param source
  158. * the grid where the event originated from
  159. * @param userOriginated
  160. * <code>true</code> if event is a result of user
  161. * interaction, <code>false</code> if from API call
  162. */
  163. public ColumnResizeEvent(Grid<?> source, Column<?, ?> column,
  164. boolean userOriginated) {
  165. super(source);
  166. this.column = column;
  167. this.userOriginated = userOriginated;
  168. }
  169. /**
  170. * Returns the column that was resized.
  171. *
  172. * @return the resized column.
  173. */
  174. public Column<?, ?> getColumn() {
  175. return column;
  176. }
  177. /**
  178. * Returns <code>true</code> if the column resize was done by the user,
  179. * <code>false</code> if not and it was triggered by server side code.
  180. *
  181. * @return <code>true</code> if event is a result of user interaction
  182. */
  183. public boolean isUserOriginated() {
  184. return userOriginated;
  185. }
  186. }
  187. /**
  188. * An event fired when an item in the Grid has been clicked.
  189. *
  190. * @param <T>
  191. * the grid bean type
  192. */
  193. public static class ItemClick<T> extends ConnectorEvent {
  194. private final T item;
  195. private final Column<T, ?> column;
  196. private final MouseEventDetails mouseEventDetails;
  197. /**
  198. * Creates a new {@code ItemClick} event containing the given item and
  199. * Column originating from the given Grid.
  200. *
  201. */
  202. public ItemClick(Grid<T> source, Column<T, ?> column, T item,
  203. MouseEventDetails mouseEventDetails) {
  204. super(source);
  205. this.column = column;
  206. this.item = item;
  207. this.mouseEventDetails = mouseEventDetails;
  208. }
  209. /**
  210. * Returns the clicked item.
  211. *
  212. * @return the clicked item
  213. */
  214. public T getItem() {
  215. return item;
  216. }
  217. /**
  218. * Returns the clicked column.
  219. *
  220. * @return the clicked column
  221. */
  222. public Column<T, ?> getColumn() {
  223. return column;
  224. }
  225. /**
  226. * Returns the source Grid.
  227. *
  228. * @return the grid
  229. */
  230. @Override
  231. public Grid<T> getSource() {
  232. return (Grid<T>) super.getSource();
  233. }
  234. /**
  235. * Returns the mouse event details.
  236. *
  237. * @return the mouse event details
  238. */
  239. public MouseEventDetails getMouseEventDetails() {
  240. return mouseEventDetails;
  241. }
  242. }
  243. /**
  244. * A listener for item click events.
  245. *
  246. * @param <T>
  247. * the grid bean type
  248. *
  249. * @see ItemClick
  250. * @see Registration
  251. */
  252. @FunctionalInterface
  253. public interface ItemClickListener<T> extends EventListener<ItemClick<T>> {
  254. /**
  255. * Invoked when this listener receives a item click event from a Grid to
  256. * which it has been added.
  257. *
  258. * @param event
  259. * the received event, not null
  260. */
  261. @Override
  262. public void accept(ItemClick<T> event);
  263. }
  264. /**
  265. * ContextClickEvent for the Grid Component.
  266. *
  267. * @param <T>
  268. * the grid bean type
  269. */
  270. public static class GridContextClickEvent<T> extends ContextClickEvent {
  271. private final T item;
  272. private final int rowIndex;
  273. private final Column<?, ?> column;
  274. private final Section section;
  275. /**
  276. * Creates a new context click event.
  277. *
  278. * @param source
  279. * the grid where the context click occurred
  280. * @param mouseEventDetails
  281. * details about mouse position
  282. * @param section
  283. * the section of the grid which was clicked
  284. * @param rowIndex
  285. * the index of the row which was clicked
  286. * @param item
  287. * the item which was clicked
  288. * @param column
  289. * the column which was clicked
  290. */
  291. public GridContextClickEvent(Grid<T> source,
  292. MouseEventDetails mouseEventDetails, Section section,
  293. int rowIndex, T item, Column<?, ?> column) {
  294. super(source, mouseEventDetails);
  295. this.item = item;
  296. this.section = section;
  297. this.column = column;
  298. this.rowIndex = rowIndex;
  299. }
  300. /**
  301. * Returns the item of context clicked row.
  302. *
  303. * @return item of clicked row; <code>null</code> if header or footer
  304. */
  305. public T getItem() {
  306. return item;
  307. }
  308. /**
  309. * Returns the clicked column.
  310. *
  311. * @return the clicked column
  312. */
  313. public Column<?, ?> getColumn() {
  314. return column;
  315. }
  316. /**
  317. * Return the clicked section of Grid.
  318. *
  319. * @return section of grid
  320. */
  321. public Section getSection() {
  322. return section;
  323. }
  324. /**
  325. * Returns the clicked row index.
  326. * <p>
  327. * Header and Footer rows for index can be fetched with
  328. * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
  329. *
  330. * @return row index in section
  331. */
  332. public int getRowIndex() {
  333. return rowIndex;
  334. }
  335. @Override
  336. public Grid<T> getComponent() {
  337. return (Grid<T>) super.getComponent();
  338. }
  339. }
  340. /**
  341. * An event listener for column visibility change events in the Grid.
  342. *
  343. * @since 7.5.0
  344. */
  345. @FunctionalInterface
  346. public interface ColumnVisibilityChangeListener extends Serializable {
  347. /**
  348. * Called when a column has become hidden or unhidden.
  349. *
  350. * @param event
  351. */
  352. void columnVisibilityChanged(ColumnVisibilityChangeEvent event);
  353. }
  354. /**
  355. * An event that is fired when a column's visibility changes.
  356. *
  357. * @since 7.5.0
  358. */
  359. public static class ColumnVisibilityChangeEvent extends Component.Event {
  360. private final Column<?, ?> column;
  361. private final boolean userOriginated;
  362. private final boolean hidden;
  363. /**
  364. * Constructor for a column visibility change event.
  365. *
  366. * @param source
  367. * the grid from which this event originates
  368. * @param column
  369. * the column that changed its visibility
  370. * @param hidden
  371. * <code>true</code> if the column was hidden,
  372. * <code>false</code> if it became visible
  373. * @param isUserOriginated
  374. * <code>true</code> iff the event was triggered by an UI
  375. * interaction
  376. */
  377. public ColumnVisibilityChangeEvent(Grid<?> source, Column<?, ?> column,
  378. boolean hidden, boolean isUserOriginated) {
  379. super(source);
  380. this.column = column;
  381. this.hidden = hidden;
  382. userOriginated = isUserOriginated;
  383. }
  384. /**
  385. * Gets the column that became hidden or visible.
  386. *
  387. * @return the column that became hidden or visible.
  388. * @see Column#isHidden()
  389. */
  390. public Column<?, ?> getColumn() {
  391. return column;
  392. }
  393. /**
  394. * Was the column set hidden or visible.
  395. *
  396. * @return <code>true</code> if the column was hidden <code>false</code>
  397. * if it was set visible
  398. */
  399. public boolean isHidden() {
  400. return hidden;
  401. }
  402. /**
  403. * Returns <code>true</code> if the column reorder was done by the user,
  404. * <code>false</code> if not and it was triggered by server side code.
  405. *
  406. * @return <code>true</code> if event is a result of user interaction
  407. */
  408. public boolean isUserOriginated() {
  409. return userOriginated;
  410. }
  411. }
  412. /**
  413. * A callback interface for generating description texts for an item.
  414. *
  415. * @param <T>
  416. * the grid bean type
  417. */
  418. @FunctionalInterface
  419. public interface DescriptionGenerator<T>
  420. extends SerializableFunction<T, String> {
  421. }
  422. /**
  423. * A callback interface for generating details for a particular row in Grid.
  424. *
  425. * @param <T>
  426. * the grid bean type
  427. */
  428. @FunctionalInterface
  429. public interface DetailsGenerator<T>
  430. extends Function<T, Component>, Serializable {
  431. }
  432. /**
  433. * A helper base class for creating extensions for the Grid component.
  434. *
  435. * @param <T>
  436. */
  437. public abstract static class AbstractGridExtension<T>
  438. extends AbstractListingExtension<T> {
  439. @Override
  440. public void extend(AbstractListing<T, ?> grid) {
  441. if (!(grid instanceof Grid)) {
  442. throw new IllegalArgumentException(
  443. getClass().getSimpleName() + " can only extend Grid");
  444. }
  445. super.extend(grid);
  446. }
  447. /**
  448. * Adds given component to the connector hierarchy of Grid.
  449. *
  450. * @param c
  451. * the component to add
  452. */
  453. protected void addComponentToGrid(Component c) {
  454. getParent().addExtensionComponent(c);
  455. }
  456. /**
  457. * Removes given component from the connector hierarchy of Grid.
  458. *
  459. * @param c
  460. * the component to remove
  461. */
  462. protected void removeComponentFromGrid(Component c) {
  463. getParent().removeExtensionComponent(c);
  464. }
  465. @Override
  466. public Grid<T> getParent() {
  467. return (Grid<T>) super.getParent();
  468. }
  469. }
  470. private final class GridServerRpcImpl implements GridServerRpc {
  471. @Override
  472. public void sort(String[] columnIds, SortDirection[] directions,
  473. boolean isUserOriginated) {
  474. assert columnIds.length == directions.length : "Column and sort direction counts don't match.";
  475. sortOrder.clear();
  476. if (columnIds.length == 0) {
  477. // Grid is not sorted anymore.
  478. getDataCommunicator()
  479. .setBackEndSorting(Collections.emptyList());
  480. getDataCommunicator().setInMemorySorting(null);
  481. return;
  482. }
  483. for (int i = 0; i < columnIds.length; ++i) {
  484. Column<T, ?> column = columnKeys.get(columnIds[i]);
  485. sortOrder.add(new SortOrder<>(column, directions[i]));
  486. }
  487. // Set sort orders
  488. // In-memory comparator
  489. BinaryOperator<SerializableComparator<T>> operator = (comparator1,
  490. comparator2) -> SerializableComparator.asInstance(
  491. (Comparator<T> & Serializable) comparator1
  492. .thenComparing(comparator2));
  493. SerializableComparator<T> comparator = sortOrder.stream()
  494. .map(order -> order.getSorted()
  495. .getComparator(order.getDirection()))
  496. .reduce((x, y) -> 0, operator);
  497. getDataCommunicator().setInMemorySorting(comparator);
  498. // Back-end sort properties
  499. List<SortOrder<String>> sortProperties = new ArrayList<>();
  500. sortOrder.stream()
  501. .map(order -> order.getSorted()
  502. .getSortOrder(order.getDirection()))
  503. .forEach(s -> s.forEach(sortProperties::add));
  504. getDataCommunicator().setBackEndSorting(sortProperties);
  505. }
  506. @Override
  507. public void itemClick(String rowKey, String columnId,
  508. MouseEventDetails details) {
  509. Column<T, ?> column = columnKeys.containsKey(columnId)
  510. ? columnKeys.get(columnId) : null;
  511. T item = getDataCommunicator().getKeyMapper().get(rowKey);
  512. fireEvent(new ItemClick<>(Grid.this, column, item, details));
  513. }
  514. @Override
  515. public void contextClick(int rowIndex, String rowKey, String columnId,
  516. Section section, MouseEventDetails details) {
  517. T item = null;
  518. if (rowKey != null) {
  519. item = getDataCommunicator().getKeyMapper().get(rowKey);
  520. }
  521. fireEvent(new GridContextClickEvent<>(Grid.this, details, section,
  522. rowIndex, item, getColumn(columnId)));
  523. }
  524. @Override
  525. public void columnsReordered(List<String> newColumnOrder,
  526. List<String> oldColumnOrder) {
  527. final String diffStateKey = "columnOrder";
  528. ConnectorTracker connectorTracker = getUI().getConnectorTracker();
  529. JsonObject diffState = connectorTracker.getDiffState(Grid.this);
  530. // discard the change if the columns have been reordered from
  531. // the server side, as the server side is always right
  532. if (getState(false).columnOrder.equals(oldColumnOrder)) {
  533. // Don't mark as dirty since client has the state already
  534. getState(false).columnOrder = newColumnOrder;
  535. // write changes to diffState so that possible reverting the
  536. // column order is sent to client
  537. assert diffState
  538. .hasKey(diffStateKey) : "Field name has changed";
  539. Type type = null;
  540. try {
  541. type = (getState(false).getClass()
  542. .getDeclaredField(diffStateKey).getGenericType());
  543. } catch (NoSuchFieldException e) {
  544. e.printStackTrace();
  545. } catch (SecurityException e) {
  546. e.printStackTrace();
  547. }
  548. EncodeResult encodeResult = JsonCodec.encode(
  549. getState(false).columnOrder, diffState, type,
  550. connectorTracker);
  551. diffState.put(diffStateKey, encodeResult.getEncodedValue());
  552. fireColumnReorderEvent(true);
  553. } else {
  554. // make sure the client is reverted to the order that the
  555. // server thinks it is
  556. diffState.remove(diffStateKey);
  557. markAsDirty();
  558. }
  559. }
  560. @Override
  561. public void columnVisibilityChanged(String id, boolean hidden) {
  562. Column<T, ?> column = getColumn(id);
  563. ColumnState columnState = column.getState(false);
  564. if (columnState.hidden != hidden) {
  565. columnState.hidden = hidden;
  566. fireColumnVisibilityChangeEvent(column, hidden, true);
  567. }
  568. }
  569. @Override
  570. public void columnResized(String id, double pixels) {
  571. final Column<T, ?> column = getColumn(id);
  572. if (column != null && column.isResizable()) {
  573. column.getState().width = pixels;
  574. fireColumnResizeEvent(column, true);
  575. markAsDirty();
  576. }
  577. }
  578. }
  579. /**
  580. * Class for managing visible details rows.
  581. *
  582. * @param <T>
  583. * the grid bean type
  584. */
  585. public static class DetailsManager<T> extends AbstractGridExtension<T> {
  586. private Set<T> visibleDetails = new HashSet<>();
  587. private Map<T, Component> components = new HashMap<>();
  588. private DetailsGenerator<T> generator;
  589. /**
  590. * Sets the details component generator.
  591. *
  592. * @param generator
  593. * the generator for details components
  594. */
  595. public void setDetailsGenerator(DetailsGenerator<T> generator) {
  596. if (this.generator != generator) {
  597. removeAllComponents();
  598. }
  599. this.generator = generator;
  600. visibleDetails.forEach(this::refresh);
  601. }
  602. @Override
  603. public void remove() {
  604. removeAllComponents();
  605. super.remove();
  606. }
  607. private void removeAllComponents() {
  608. // Clean up old components
  609. components.values().forEach(this::removeComponentFromGrid);
  610. components.clear();
  611. }
  612. @Override
  613. public void generateData(T data, JsonObject jsonObject) {
  614. if (generator == null || !visibleDetails.contains(data)) {
  615. return;
  616. }
  617. if (!components.containsKey(data)) {
  618. Component detailsComponent = generator.apply(data);
  619. Objects.requireNonNull(detailsComponent,
  620. "Details generator can't create null components");
  621. if (detailsComponent.getParent() != null) {
  622. throw new IllegalStateException(
  623. "Details component was already attached");
  624. }
  625. addComponentToGrid(detailsComponent);
  626. components.put(data, detailsComponent);
  627. }
  628. jsonObject.put(GridState.JSONKEY_DETAILS_VISIBLE,
  629. components.get(data).getConnectorId());
  630. }
  631. @Override
  632. public void destroyData(T data) {
  633. // No clean up needed. Components are removed when hiding details
  634. // and/or changing details generator
  635. }
  636. /**
  637. * Sets the visibility of details component for given item.
  638. *
  639. * @param data
  640. * the item to show details for
  641. * @param visible
  642. * {@code true} if details component should be visible;
  643. * {@code false} if it should be hidden
  644. */
  645. public void setDetailsVisible(T data, boolean visible) {
  646. boolean refresh = false;
  647. if (!visible) {
  648. refresh = visibleDetails.remove(data);
  649. if (components.containsKey(data)) {
  650. removeComponentFromGrid(components.remove(data));
  651. }
  652. } else {
  653. refresh = visibleDetails.add(data);
  654. }
  655. if (refresh) {
  656. refresh(data);
  657. }
  658. }
  659. /**
  660. * Returns the visibility of details component for given item.
  661. *
  662. * @param data
  663. * the item to show details for
  664. *
  665. * @return {@code true} if details component should be visible;
  666. * {@code false} if it should be hidden
  667. */
  668. public boolean isDetailsVisible(T data) {
  669. return visibleDetails.contains(data);
  670. }
  671. @Override
  672. public Grid<T> getParent() {
  673. return super.getParent();
  674. }
  675. }
  676. /**
  677. * This extension manages the configuration and data communication for a
  678. * Column inside of a Grid component.
  679. *
  680. * @param <T>
  681. * the grid bean type
  682. * @param <V>
  683. * the column value type
  684. */
  685. public static class Column<T, V> extends AbstractGridExtension<T> {
  686. private final SerializableFunction<T, ? extends V> valueProvider;
  687. private SerializableFunction<SortDirection, Stream<SortOrder<String>>> sortOrderProvider;
  688. private SerializableComparator<T> comparator;
  689. private StyleGenerator<T> styleGenerator = item -> null;
  690. private DescriptionGenerator<T> descriptionGenerator;
  691. /**
  692. * Constructs a new Column configuration with given header caption,
  693. * renderer and value provider.
  694. *
  695. * @param caption
  696. * the header caption
  697. * @param valueProvider
  698. * the function to get values from items
  699. * @param renderer
  700. * the type of value
  701. */
  702. protected Column(String caption,
  703. SerializableFunction<T, ? extends V> valueProvider,
  704. Renderer<V> renderer) {
  705. Objects.requireNonNull(caption, "Header caption can't be null");
  706. Objects.requireNonNull(valueProvider,
  707. "Value provider can't be null");
  708. Objects.requireNonNull(renderer, "Renderer can't be null");
  709. ColumnState state = getState();
  710. this.valueProvider = valueProvider;
  711. state.renderer = renderer;
  712. state.caption = caption;
  713. sortOrderProvider = d -> Stream.of();
  714. // Add the renderer as a child extension of this extension, thus
  715. // ensuring the renderer will be unregistered when this column is
  716. // removed
  717. addExtension(renderer);
  718. Class<V> valueType = renderer.getPresentationType();
  719. if (Comparable.class.isAssignableFrom(valueType)) {
  720. comparator = (a, b) -> {
  721. @SuppressWarnings("unchecked")
  722. Comparable<V> comp = (Comparable<V>) valueProvider.apply(a);
  723. return comp.compareTo(valueProvider.apply(b));
  724. };
  725. state.sortable = true;
  726. } else if (Number.class.isAssignableFrom(valueType)) {
  727. /*
  728. * Value type will be Number whenever using NumberRenderer.
  729. * Provide explicit comparison support in this case even though
  730. * Number itself isn't Comparable.
  731. */
  732. comparator = (a, b) -> {
  733. return compareNumbers((Number) valueProvider.apply(a),
  734. (Number) valueProvider.apply(b));
  735. };
  736. state.sortable = true;
  737. } else {
  738. state.sortable = false;
  739. }
  740. }
  741. @SuppressWarnings("unchecked")
  742. private static int compareNumbers(Number a, Number b) {
  743. assert a.getClass() == b.getClass();
  744. // Most Number implementations are Comparable
  745. if (a instanceof Comparable && a.getClass().isInstance(b)) {
  746. return ((Comparable<Number>) a).compareTo(b);
  747. } else if (a.equals(b)) {
  748. return 0;
  749. } else {
  750. // Fall back to comparing based on potentially truncated values
  751. int compare = Long.compare(a.longValue(), b.longValue());
  752. if (compare == 0) {
  753. // This might still produce 0 even though the values are not
  754. // equals, but there's nothing more we can do about that
  755. compare = Double.compare(a.doubleValue(), b.doubleValue());
  756. }
  757. return compare;
  758. }
  759. }
  760. @Override
  761. public void generateData(T data, JsonObject jsonObject) {
  762. ColumnState state = getState(false);
  763. String communicationId = getConnectorId();
  764. assert communicationId != null : "No communication ID set for column "
  765. + state.caption;
  766. @SuppressWarnings("unchecked")
  767. Renderer<V> renderer = (Renderer<V>) state.renderer;
  768. JsonObject obj = getDataObject(jsonObject,
  769. DataCommunicatorConstants.DATA);
  770. V providerValue = valueProvider.apply(data);
  771. JsonValue rendererValue = renderer.encode(providerValue);
  772. obj.put(communicationId, rendererValue);
  773. String style = styleGenerator.apply(data);
  774. if (style != null && !style.isEmpty()) {
  775. JsonObject styleObj = getDataObject(jsonObject,
  776. GridState.JSONKEY_CELLSTYLES);
  777. styleObj.put(communicationId, style);
  778. }
  779. if (descriptionGenerator != null) {
  780. String description = descriptionGenerator.apply(data);
  781. if (description != null && !description.isEmpty()) {
  782. JsonObject descriptionObj = getDataObject(jsonObject,
  783. GridState.JSONKEY_CELLDESCRIPTION);
  784. descriptionObj.put(communicationId, description);
  785. }
  786. }
  787. }
  788. /**
  789. * Gets a data object with the given key from the given JsonObject. If
  790. * there is no object with the key, this method creates a new
  791. * JsonObject.
  792. *
  793. * @param jsonObject
  794. * the json object
  795. * @param key
  796. * the key where the desired data object is stored
  797. * @return data object for the given key
  798. */
  799. private JsonObject getDataObject(JsonObject jsonObject, String key) {
  800. if (!jsonObject.hasKey(key)) {
  801. jsonObject.put(key, Json.createObject());
  802. }
  803. return jsonObject.getObject(key);
  804. }
  805. @Override
  806. protected ColumnState getState() {
  807. return getState(true);
  808. }
  809. @Override
  810. protected ColumnState getState(boolean markAsDirty) {
  811. return (ColumnState) super.getState(markAsDirty);
  812. }
  813. /**
  814. * This method extends the given Grid with this Column.
  815. *
  816. * @param grid
  817. * the grid to extend
  818. */
  819. private void extend(Grid<T> grid) {
  820. super.extend(grid);
  821. }
  822. /**
  823. * Returns the identifier used with this Column in communication.
  824. *
  825. * @return the identifier string
  826. */
  827. public String getId() {
  828. return getState(false).id;
  829. }
  830. /**
  831. * Sets the identifier to use with this Column in communication.
  832. *
  833. * @param id
  834. * the identifier string
  835. */
  836. private void setId(String id) {
  837. Objects.requireNonNull(id, "Communication ID can't be null");
  838. getState().id = id;
  839. }
  840. /**
  841. * Sets whether the user can sort this column or not.
  842. *
  843. * @param sortable
  844. * {@code true} if the column can be sorted by the user;
  845. * {@code false} if not
  846. * @return this column
  847. */
  848. public Column<T, V> setSortable(boolean sortable) {
  849. getState().sortable = sortable;
  850. return this;
  851. }
  852. /**
  853. * Gets whether the user can sort this column or not.
  854. *
  855. * @return {@code true} if the column can be sorted by the user;
  856. * {@code false} if not
  857. */
  858. public boolean isSortable() {
  859. return getState(false).sortable;
  860. }
  861. /**
  862. * Sets the header caption for this column.
  863. *
  864. * @param caption
  865. * the header caption, not null
  866. *
  867. * @return this column
  868. */
  869. public Column<T, V> setCaption(String caption) {
  870. Objects.requireNonNull(caption, "Header caption can't be null");
  871. getState().caption = caption;
  872. HeaderRow row = getParent().getDefaultHeaderRow();
  873. if (row != null) {
  874. row.getCell(getId()).setText(caption);
  875. }
  876. return this;
  877. }
  878. /**
  879. * Gets the header caption for this column.
  880. *
  881. * @return header caption
  882. */
  883. public String getCaption() {
  884. return getState(false).caption;
  885. }
  886. /**
  887. * Sets a comparator to use with in-memory sorting with this column.
  888. * Sorting with a back-end is done using
  889. * {@link Column#setSortProperty(String...)}.
  890. *
  891. * @param comparator
  892. * the comparator to use when sorting data in this column
  893. * @return this column
  894. */
  895. public Column<T, V> setComparator(
  896. SerializableComparator<T> comparator) {
  897. Objects.requireNonNull(comparator, "Comparator can't be null");
  898. this.comparator = comparator;
  899. return this;
  900. }
  901. /**
  902. * Gets the comparator to use with in-memory sorting for this column
  903. * when sorting in the given direction.
  904. *
  905. * @param sortDirection
  906. * the direction this column is sorted by
  907. * @return comparator for this column
  908. */
  909. public SerializableComparator<T> getComparator(
  910. SortDirection sortDirection) {
  911. Objects.requireNonNull(comparator,
  912. "No comparator defined for sorted column.");
  913. boolean reverse = sortDirection != SortDirection.ASCENDING;
  914. return reverse ? (t1, t2) -> comparator.reversed().compare(t1, t2)
  915. : comparator;
  916. }
  917. /**
  918. * Sets strings describing back end properties to be used when sorting
  919. * this column. This method is a short hand for
  920. * {@link #setSortBuilder(Function)} that takes an array of strings and
  921. * uses the same sorting direction for all of them.
  922. *
  923. * @param properties
  924. * the array of strings describing backend properties
  925. * @return this column
  926. */
  927. public Column<T, V> setSortProperty(String... properties) {
  928. Objects.requireNonNull(properties, "Sort properties can't be null");
  929. sortOrderProvider = dir -> Arrays.stream(properties)
  930. .map(s -> new SortOrder<>(s, dir));
  931. return this;
  932. }
  933. /**
  934. * Sets the sort orders when sorting this column. The sort order
  935. * provider is a function which provides {@link SortOrder} objects to
  936. * describe how to sort by this column.
  937. *
  938. * @param provider
  939. * the function to use when generating sort orders with the
  940. * given direction
  941. * @return this column
  942. */
  943. public Column<T, V> setSortOrderProvider(
  944. SerializableFunction<SortDirection, Stream<SortOrder<String>>> provider) {
  945. Objects.requireNonNull(provider,
  946. "Sort order provider can't be null");
  947. sortOrderProvider = provider;
  948. return this;
  949. }
  950. /**
  951. * Gets the sort orders to use with back-end sorting for this column
  952. * when sorting in the given direction.
  953. *
  954. * @param direction
  955. * the sorting direction
  956. * @return stream of sort orders
  957. */
  958. public Stream<SortOrder<String>> getSortOrder(SortDirection direction) {
  959. return sortOrderProvider.apply(direction);
  960. }
  961. /**
  962. * Sets the style generator that is used for generating class names for
  963. * cells in this column. Returning null from the generator results in no
  964. * custom style name being set.
  965. *
  966. * @param cellStyleGenerator
  967. * the cell style generator to set, not null
  968. * @return this column
  969. * @throws NullPointerException
  970. * if {@code cellStyleGenerator} is {@code null}
  971. */
  972. public Column<T, V> setStyleGenerator(
  973. StyleGenerator<T> cellStyleGenerator) {
  974. Objects.requireNonNull(cellStyleGenerator,
  975. "Cell style generator must not be null");
  976. this.styleGenerator = cellStyleGenerator;
  977. getParent().getDataCommunicator().reset();
  978. return this;
  979. }
  980. /**
  981. * Gets the style generator that is used for generating styles for
  982. * cells.
  983. *
  984. * @return the cell style generator
  985. */
  986. public StyleGenerator<T> getStyleGenerator() {
  987. return styleGenerator;
  988. }
  989. /**
  990. * Sets the description generator that is used for generating
  991. * descriptions for cells in this column.
  992. *
  993. * @param cellDescriptionGenerator
  994. * the cell description generator to set, or
  995. * <code>null</code> to remove a previously set generator
  996. * @return this column
  997. */
  998. public Column<T, V> setDescriptionGenerator(
  999. DescriptionGenerator<T> cellDescriptionGenerator) {
  1000. this.descriptionGenerator = cellDescriptionGenerator;
  1001. getParent().getDataCommunicator().reset();
  1002. return this;
  1003. }
  1004. /**
  1005. * Gets the description generator that is used for generating
  1006. * descriptions for cells.
  1007. *
  1008. * @return the cell description generator, or <code>null</code> if no
  1009. * generator is set
  1010. */
  1011. public DescriptionGenerator<T> getDescriptionGenerator() {
  1012. return descriptionGenerator;
  1013. }
  1014. /**
  1015. * Sets the ratio with which the column expands.
  1016. * <p>
  1017. * By default, all columns expand equally (treated as if all of them had
  1018. * an expand ratio of 1). Once at least one column gets a defined expand
  1019. * ratio, the implicit expand ratio is removed, and only the defined
  1020. * expand ratios are taken into account.
  1021. * <p>
  1022. * If a column has a defined width ({@link #setWidth(double)}), it
  1023. * overrides this method's effects.
  1024. * <p>
  1025. * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
  1026. * and 2, respectively. The column with a <strong>ratio of 0 is exactly
  1027. * as wide as its contents requires</strong>. The column with a ratio of
  1028. * 1 is as wide as it needs, <strong>plus a third of any excess
  1029. * space</strong>, because we have 3 parts total, and this column
  1030. * reserves only one of those. The column with a ratio of 2, is as wide
  1031. * as it needs to be, <strong>plus two thirds</strong> of the excess
  1032. * width.
  1033. *
  1034. * @param expandRatio
  1035. * the expand ratio of this column. {@code 0} to not have it
  1036. * expand at all. A negative number to clear the expand
  1037. * value.
  1038. * @throws IllegalStateException
  1039. * if the column is no longer attached to any grid
  1040. * @see #setWidth(double)
  1041. */
  1042. public Column<T, V> setExpandRatio(int expandRatio)
  1043. throws IllegalStateException {
  1044. checkColumnIsAttached();
  1045. if (expandRatio != getExpandRatio()) {
  1046. getState().expandRatio = expandRatio;
  1047. getParent().markAsDirty();
  1048. }
  1049. return this;
  1050. }
  1051. /**
  1052. * Returns the column's expand ratio.
  1053. *
  1054. * @return the column's expand ratio
  1055. * @see #setExpandRatio(int)
  1056. */
  1057. public int getExpandRatio() {
  1058. return getState(false).expandRatio;
  1059. }
  1060. /**
  1061. * Clears the expand ratio for this column.
  1062. * <p>
  1063. * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
  1064. *
  1065. * @throws IllegalStateException
  1066. * if the column is no longer attached to any grid
  1067. */
  1068. public Column<T, V> clearExpandRatio() throws IllegalStateException {
  1069. return setExpandRatio(-1);
  1070. }
  1071. /**
  1072. * Returns the width (in pixels). By default a column is 100px wide.
  1073. *
  1074. * @return the width in pixels of the column
  1075. * @throws IllegalStateException
  1076. * if the column is no longer attached to any grid
  1077. */
  1078. public double getWidth() throws IllegalStateException {
  1079. checkColumnIsAttached();
  1080. return getState(false).width;
  1081. }
  1082. /**
  1083. * Sets the width (in pixels).
  1084. * <p>
  1085. * This overrides any configuration set by any of
  1086. * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
  1087. * {@link #setMaximumWidth(double)}.
  1088. *
  1089. * @param pixelWidth
  1090. * the new pixel width of the column
  1091. * @return the column itself
  1092. *
  1093. * @throws IllegalStateException
  1094. * if the column is no longer attached to any grid
  1095. * @throws IllegalArgumentException
  1096. * thrown if pixel width is less than zero
  1097. */
  1098. public Column<T, V> setWidth(double pixelWidth)
  1099. throws IllegalStateException, IllegalArgumentException {
  1100. checkColumnIsAttached();
  1101. if (pixelWidth < 0) {
  1102. throw new IllegalArgumentException(
  1103. "Pixel width should be greated than 0 (in " + toString()
  1104. + ")");
  1105. }
  1106. if (pixelWidth != getWidth()) {
  1107. getState().width = pixelWidth;
  1108. getParent().markAsDirty();
  1109. getParent().fireColumnResizeEvent(this, false);
  1110. }
  1111. return this;
  1112. }
  1113. /**
  1114. * Returns whether this column has an undefined width.
  1115. *
  1116. * @since 7.6
  1117. * @return whether the width is undefined
  1118. * @throws IllegalStateException
  1119. * if the column is no longer attached to any grid
  1120. */
  1121. public boolean isWidthUndefined() {
  1122. checkColumnIsAttached();
  1123. return getState(false).width < 0;
  1124. }
  1125. /**
  1126. * Marks the column width as undefined. An undefined width means the
  1127. * grid is free to resize the column based on the cell contents and
  1128. * available space in the grid.
  1129. *
  1130. * @return the column itself
  1131. */
  1132. public Column<T, V> setWidthUndefined() {
  1133. checkColumnIsAttached();
  1134. if (!isWidthUndefined()) {
  1135. getState().width = -1;
  1136. getParent().markAsDirty();
  1137. getParent().fireColumnResizeEvent(this, false);
  1138. }
  1139. return this;
  1140. }
  1141. /**
  1142. * Sets the minimum width for this column.
  1143. * <p>
  1144. * This defines the minimum guaranteed pixel width of the column
  1145. * <em>when it is set to expand</em>.
  1146. *
  1147. * @throws IllegalStateException
  1148. * if the column is no longer attached to any grid
  1149. * @see #setExpandRatio(int)
  1150. */
  1151. public Column<T, V> setMinimumWidth(double pixels)
  1152. throws IllegalStateException {
  1153. checkColumnIsAttached();
  1154. final double maxwidth = getMaximumWidth();
  1155. if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
  1156. throw new IllegalArgumentException("New minimum width ("
  1157. + pixels + ") was greater than maximum width ("
  1158. + maxwidth + ")");
  1159. }
  1160. getState().minWidth = pixels;
  1161. getParent().markAsDirty();
  1162. return this;
  1163. }
  1164. /**
  1165. * Return the minimum width for this column.
  1166. *
  1167. * @return the minimum width for this column
  1168. * @see #setMinimumWidth(double)
  1169. */
  1170. public double getMinimumWidth() {
  1171. return getState(false).minWidth;
  1172. }
  1173. /**
  1174. * Sets the maximum width for this column.
  1175. * <p>
  1176. * This defines the maximum allowed pixel width of the column <em>when
  1177. * it is set to expand</em>.
  1178. *
  1179. * @param pixels
  1180. * the maximum width
  1181. * @throws IllegalStateException
  1182. * if the column is no longer attached to any grid
  1183. * @see #setExpandRatio(int)
  1184. */
  1185. public Column<T, V> setMaximumWidth(double pixels) {
  1186. checkColumnIsAttached();
  1187. final double minwidth = getMinimumWidth();
  1188. if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
  1189. throw new IllegalArgumentException("New maximum width ("
  1190. + pixels + ") was less than minimum width (" + minwidth
  1191. + ")");
  1192. }
  1193. getState().maxWidth = pixels;
  1194. getParent().markAsDirty();
  1195. return this;
  1196. }
  1197. /**
  1198. * Returns the maximum width for this column.
  1199. *
  1200. * @return the maximum width for this column
  1201. * @see #setMaximumWidth(double)
  1202. */
  1203. public double getMaximumWidth() {
  1204. return getState(false).maxWidth;
  1205. }
  1206. /**
  1207. * Sets whether this column can be resized by the user.
  1208. *
  1209. * @since 7.6
  1210. * @param resizable
  1211. * {@code true} if this column should be resizable,
  1212. * {@code false} otherwise
  1213. * @throws IllegalStateException
  1214. * if the column is no longer attached to any grid
  1215. */
  1216. public Column<T, V> setResizable(boolean resizable) {
  1217. checkColumnIsAttached();
  1218. if (resizable != isResizable()) {
  1219. getState().resizable = resizable;
  1220. getParent().markAsDirty();
  1221. }
  1222. return this;
  1223. }
  1224. /**
  1225. * Gets the caption of the hiding toggle for this column.
  1226. *
  1227. * @since 7.5.0
  1228. * @see #setHidingToggleCaption(String)
  1229. * @return the caption for the hiding toggle for this column
  1230. */
  1231. public String getHidingToggleCaption() {
  1232. return getState(false).hidingToggleCaption;
  1233. }
  1234. /**
  1235. * Sets the caption of the hiding toggle for this column. Shown in the
  1236. * toggle for this column in the grid's sidebar when the column is
  1237. * {@link #isHidable() hidable}.
  1238. * <p>
  1239. * The default value is <code>null</code>, and in that case the column's
  1240. * {@link #getHeaderCaption() header caption} is used.
  1241. * <p>
  1242. * <em>NOTE:</em> setting this to empty string might cause the hiding
  1243. * toggle to not render correctly.
  1244. *
  1245. * @since 7.5.0
  1246. * @param hidingToggleCaption
  1247. * the text to show in the column hiding toggle
  1248. * @return the column itself
  1249. */
  1250. public Column<T, V> setHidingToggleCaption(String hidingToggleCaption) {
  1251. if (hidingToggleCaption != getHidingToggleCaption()) {
  1252. getState().hidingToggleCaption = hidingToggleCaption;
  1253. }
  1254. return this;
  1255. }
  1256. /**
  1257. * Hides or shows the column. By default columns are visible before
  1258. * explicitly hiding them.
  1259. *
  1260. * @since 7.5.0
  1261. * @param hidden
  1262. * <code>true</code> to hide the column, <code>false</code>
  1263. * to show
  1264. * @return this column
  1265. * @throws IllegalStateException
  1266. * if the column is no longer attached to any grid
  1267. */
  1268. public Column<T, V> setHidden(boolean hidden) {
  1269. checkColumnIsAttached();
  1270. if (hidden != isHidden()) {
  1271. getState().hidden = hidden;
  1272. getParent().fireColumnVisibilityChangeEvent(this, hidden,
  1273. false);
  1274. }
  1275. return this;
  1276. }
  1277. /**
  1278. * Returns whether this column is hidden. Default is {@code false}.
  1279. *
  1280. * @since 7.5.0
  1281. * @return <code>true</code> if the column is currently hidden,
  1282. * <code>false</code> otherwise
  1283. */
  1284. public boolean isHidden() {
  1285. return getState(false).hidden;
  1286. }
  1287. /**
  1288. * Sets whether this column can be hidden by the user. Hidable columns
  1289. * can be hidden and shown via the sidebar menu.
  1290. *
  1291. * @since 7.5.0
  1292. * @param hidable
  1293. * <code>true</code> iff the column may be hidable by the
  1294. * user via UI interaction
  1295. * @return this column
  1296. */
  1297. public Column<T, V> setHidable(boolean hidable) {
  1298. if (hidable != isHidable()) {
  1299. getState().hidable = hidable;
  1300. }
  1301. return this;
  1302. }
  1303. /**
  1304. * Returns whether this column can be hidden by the user. Default is
  1305. * {@code false}.
  1306. * <p>
  1307. * <em>Note:</em> the column can be programmatically hidden using
  1308. * {@link #setHidden(boolean)} regardless of the returned value.
  1309. *
  1310. * @since 7.5.0
  1311. * @return <code>true</code> if the user can hide the column,
  1312. * <code>false</code> if not
  1313. */
  1314. public boolean isHidable() {
  1315. return getState(false).hidable;
  1316. }
  1317. /**
  1318. * Returns whether this column can be resized by the user. Default is
  1319. * {@code true}.
  1320. * <p>
  1321. * <em>Note:</em> the column can be programmatically resized using
  1322. * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
  1323. * of the returned value.
  1324. *
  1325. * @since 7.6
  1326. * @return {@code true} if this column is resizable, {@code false}
  1327. * otherwise
  1328. */
  1329. public boolean isResizable() {
  1330. return getState(false).resizable;
  1331. }
  1332. /**
  1333. * Checks if column is attached and throws an
  1334. * {@link IllegalStateException} if it is not
  1335. *
  1336. * @throws IllegalStateException
  1337. * if the column is no longer attached to any grid
  1338. */
  1339. protected void checkColumnIsAttached() throws IllegalStateException {
  1340. if (getParent() == null) {
  1341. throw new IllegalStateException(
  1342. "Column is no longer attached to a grid.");
  1343. }
  1344. }
  1345. }
  1346. private final class SingleSelection extends AbstractSingleSelection {
  1347. private T selectedItem = null;
  1348. SingleSelection() {
  1349. addDataGenerator((item, json) -> {
  1350. if (isSelected(item)) {
  1351. json.put(DataCommunicatorConstants.SELECTED, true);
  1352. }
  1353. });
  1354. }
  1355. @Override
  1356. public Optional<T> getSelectedItem() {
  1357. return Optional.ofNullable(selectedItem);
  1358. }
  1359. @Override
  1360. protected boolean isKeySelected(String key) {
  1361. return Objects.equals(key, getSelectedKey());
  1362. }
  1363. @Override
  1364. protected String getSelectedKey() {
  1365. return itemToKey(selectedItem);
  1366. }
  1367. @Override
  1368. protected void doSetSelectedKey(String key) {
  1369. if (selectedItem != null) {
  1370. getDataCommunicator().refresh(selectedItem);
  1371. }
  1372. selectedItem = keyToItem(key);
  1373. if (selectedItem != null) {
  1374. getDataCommunicator().refresh(selectedItem);
  1375. }
  1376. }
  1377. }
  1378. /**
  1379. * A header row in a Grid.
  1380. */
  1381. public interface HeaderRow extends Serializable {
  1382. /**
  1383. * Returns the cell on this row corresponding to the given column id.
  1384. *
  1385. * @param columnId
  1386. * the id of the column whose header cell to get, not null
  1387. * @return the header cell
  1388. * @throws IllegalArgumentException
  1389. * if there is no such column in the grid
  1390. */
  1391. public HeaderCell getCell(String columnId);
  1392. /**
  1393. * Returns the cell on this row corresponding to the given column.
  1394. *
  1395. * @param column
  1396. * the column whose header cell to get, not null
  1397. * @return the header cell
  1398. * @throws IllegalArgumentException
  1399. * if there is no such column in the grid
  1400. */
  1401. public default HeaderCell getCell(Column<?, ?> column) {
  1402. return getCell(column.getId());
  1403. }
  1404. }
  1405. /**
  1406. * An individual cell on a Grid header row.
  1407. */
  1408. public interface HeaderCell extends Serializable {
  1409. /**
  1410. * Returns the textual caption of this cell.
  1411. *
  1412. * @return the header caption
  1413. */
  1414. public String getText();
  1415. /**
  1416. * Sets the textual caption of this cell.
  1417. *
  1418. * @param text
  1419. * the header caption to set, not null
  1420. */
  1421. public void setText(String text);
  1422. }
  1423. /**
  1424. * A footer row in a Grid.
  1425. */
  1426. public interface FooterRow extends Serializable {
  1427. /**
  1428. * Returns the cell on this row corresponding to the given column id.
  1429. *
  1430. * @param columnId
  1431. * the id of the column whose footer cell to get, not null
  1432. * @return the footer cell
  1433. * @throws IllegalArgumentException
  1434. * if there is no such column in the grid
  1435. */
  1436. public FooterCell getCell(String columnId);
  1437. /**
  1438. * Returns the cell on this row corresponding to the given column.
  1439. *
  1440. * @param column
  1441. * the column whose footer cell to get, not null
  1442. * @return the footer cell
  1443. * @throws IllegalArgumentException
  1444. * if there is no such column in the grid
  1445. */
  1446. public default FooterCell getCell(Column<?, ?> column) {
  1447. return getCell(column.getId());
  1448. }
  1449. }
  1450. /**
  1451. * An individual cell on a Grid footer row.
  1452. */
  1453. public interface FooterCell extends Serializable {
  1454. /**
  1455. * Returns the textual caption of this cell.
  1456. *
  1457. * @return the footer caption
  1458. */
  1459. public String getText();
  1460. /**
  1461. * Sets the textual caption of this cell.
  1462. *
  1463. * @param text
  1464. * the footer caption to set, not null
  1465. */
  1466. public void setText(String text);
  1467. }
  1468. private class HeaderImpl extends Header {
  1469. @Override
  1470. protected SectionState getState(boolean markAsDirty) {
  1471. return Grid.this.getState(markAsDirty).header;
  1472. }
  1473. @Override
  1474. protected Collection<Column<T, ?>> getColumns() {
  1475. return Grid.this.getColumns();
  1476. }
  1477. };
  1478. private class FooterImpl extends Footer {
  1479. @Override
  1480. protected SectionState getState(boolean markAsDirty) {
  1481. return Grid.this.getState(markAsDirty).footer;
  1482. }
  1483. @Override
  1484. protected Collection<Column<T, ?>> getColumns() {
  1485. return Grid.this.getColumns();
  1486. }
  1487. };
  1488. private Set<Column<T, ?>> columnSet = new LinkedHashSet<>();
  1489. private Map<String, Column<T, ?>> columnKeys = new HashMap<>();
  1490. private List<SortOrder<Column<T, ?>>> sortOrder = new ArrayList<>();
  1491. private DetailsManager<T> detailsManager;
  1492. private Set<Component> extensionComponents = new HashSet<>();
  1493. private StyleGenerator<T> styleGenerator = item -> null;
  1494. private DescriptionGenerator<T> descriptionGenerator;
  1495. private Header header = new HeaderImpl();
  1496. private Footer footer = new FooterImpl();
  1497. private int counter = 0;
  1498. /**
  1499. * Constructor for the {@link Grid} component.
  1500. */
  1501. public Grid() {
  1502. setSelectionModel(new SingleSelection());
  1503. registerRpc(new GridServerRpcImpl());
  1504. setDefaultHeaderRow(appendHeaderRow());
  1505. detailsManager = new DetailsManager<>();
  1506. addExtension(detailsManager);
  1507. addDataGenerator(detailsManager);
  1508. addDataGenerator((item, json) -> {
  1509. String styleName = styleGenerator.apply(item);
  1510. if (styleName != null && !styleName.isEmpty()) {
  1511. json.put(GridState.JSONKEY_ROWSTYLE, styleName);
  1512. }
  1513. if (descriptionGenerator != null) {
  1514. String description = descriptionGenerator.apply(item);
  1515. if (description != null && !description.isEmpty()) {
  1516. json.put(GridState.JSONKEY_ROWDESCRIPTION, description);
  1517. }
  1518. }
  1519. });
  1520. }
  1521. public <V> void fireColumnVisibilityChangeEvent(Column<T, V> column,
  1522. boolean hidden, boolean userOriginated) {
  1523. fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden,
  1524. userOriginated));
  1525. }
  1526. /**
  1527. * Adds a new column to this {@link Grid} with given identifier, typed
  1528. * renderer and value provider.
  1529. *
  1530. * @param identifier
  1531. * the identifier in camel case for the new column
  1532. * @param valueProvider
  1533. * the value provider
  1534. * @param renderer
  1535. * the column value class
  1536. * @param <V>
  1537. * the column value type
  1538. *
  1539. * @return the new column
  1540. * @throws IllegalArgumentException
  1541. * if the same identifier is used for multiple columns
  1542. *
  1543. * @see {@link AbstractRenderer}
  1544. */
  1545. public <V> Column<T, V> addColumn(String identifier,
  1546. SerializableFunction<T, ? extends V> valueProvider,
  1547. AbstractRenderer<? super T, V> renderer)
  1548. throws IllegalArgumentException {
  1549. if (columnKeys.containsKey(identifier)) {
  1550. throw new IllegalArgumentException(
  1551. "Multiple columns with the same identifier: " + identifier);
  1552. }
  1553. final Column<T, V> column = new Column<>(
  1554. SharedUtil.camelCaseToHumanFriendly(identifier), valueProvider,
  1555. renderer);
  1556. addColumn(identifier, column);
  1557. return column;
  1558. }
  1559. /**
  1560. * Adds a new text column to this {@link Grid} with given identifier and
  1561. * string value provider. The column will use a {@link TextRenderer}.
  1562. *
  1563. * @param identifier
  1564. * the header caption
  1565. * @param valueProvider
  1566. * the value provider
  1567. *
  1568. * @return the new column
  1569. * @throws IllegalArgumentException
  1570. * if the same identifier is used for multiple columns
  1571. */
  1572. public Column<T, String> addColumn(String identifier,
  1573. SerializableFunction<T, String> valueProvider) {
  1574. return addColumn(identifier, valueProvider, new TextRenderer());
  1575. }
  1576. /**
  1577. * Adds a new text column to this {@link Grid} with string value provider.
  1578. * The column will use a {@link TextRenderer}. Identifier for the column is
  1579. * generated automatically.
  1580. *
  1581. * @param valueProvider
  1582. * the value provider
  1583. *
  1584. * @return the new column
  1585. */
  1586. public Column<T, String> addColumn(
  1587. SerializableFunction<T, String> valueProvider) {
  1588. return addColumn(getGeneratedIdentifier(), valueProvider,
  1589. new TextRenderer());
  1590. }
  1591. /**
  1592. * Adds a new column to this {@link Grid} with typed renderer and value
  1593. * provider. Identifier for the column is generated automatically.
  1594. *
  1595. * @param valueProvider
  1596. * the value provider
  1597. * @param renderer
  1598. * the column value class
  1599. * @param <V>
  1600. * the column value type
  1601. *
  1602. * @return the new column
  1603. *
  1604. * @see {@link AbstractRenderer}
  1605. */
  1606. public <V> Column<T, V> addColumn(
  1607. SerializableFunction<T, ? extends V> valueProvider,
  1608. AbstractRenderer<? super T, V> renderer) {
  1609. return addColumn(getGeneratedIdentifier(), valueProvider, renderer);
  1610. }
  1611. private void addColumn(String identifier, Column<T, ?> column) {
  1612. if (getColumns().contains(column)) {
  1613. return;
  1614. }
  1615. column.extend(this);
  1616. columnSet.add(column);
  1617. columnKeys.put(identifier, column);
  1618. column.setId(identifier);
  1619. addDataGenerator(column);
  1620. getState().columnOrder.add(identifier);
  1621. getHeader().addColumn(identifier);
  1622. if (getDefaultHeaderRow() != null) {
  1623. getDefaultHeaderRow().getCell(identifier)
  1624. .setText(column.getCaption());
  1625. }
  1626. }
  1627. /**
  1628. * Removes the given column from this {@link Grid}.
  1629. *
  1630. * @param column
  1631. * the column to remove
  1632. */
  1633. public void removeColumn(Column<T, ?> column) {
  1634. if (columnSet.remove(column)) {
  1635. columnKeys.remove(column.getId());
  1636. removeDataGenerator(column);
  1637. getHeader().removeColumn(column.getId());
  1638. column.remove();
  1639. }
  1640. }
  1641. /**
  1642. * Sets the details component generator.
  1643. *
  1644. * @param generator
  1645. * the generator for details components
  1646. */
  1647. public void setDetailsGenerator(DetailsGenerator<T> generator) {
  1648. this.detailsManager.setDetailsGenerator(generator);
  1649. }
  1650. /**
  1651. * Sets the visibility of details component for given item.
  1652. *
  1653. * @param data
  1654. * the item to show details for
  1655. * @param visible
  1656. * {@code true} if details component should be visible;
  1657. * {@code false} if it should be hidden
  1658. */
  1659. public void setDetailsVisible(T data, boolean visible) {
  1660. detailsManager.setDetailsVisible(data, visible);
  1661. }
  1662. /**
  1663. * Returns the visibility of details component for given item.
  1664. *
  1665. * @param data
  1666. * the item to show details for
  1667. *
  1668. * @return {@code true} if details component should be visible;
  1669. * {@code false} if it should be hidden
  1670. */
  1671. public boolean isDetailsVisible(T data) {
  1672. return detailsManager.isDetailsVisible(data);
  1673. }
  1674. /**
  1675. * Gets an unmodifiable collection of all columns currently in this
  1676. * {@link Grid}.
  1677. *
  1678. * @return unmodifiable collection of columns
  1679. */
  1680. public List<Column<T, ?>> getColumns() {
  1681. return Collections.unmodifiableList(getState(false).columnOrder.stream()
  1682. .map(this::getColumn).collect(Collectors.toList()));
  1683. }
  1684. /**
  1685. * Gets a {@link Column} of this grid by its identifying string.
  1686. *
  1687. * @param columnId
  1688. * the identifier of the column to get
  1689. * @return the column corresponding to the given column id
  1690. */
  1691. public Column<T, ?> getColumn(String columnId) {
  1692. return columnKeys.get(columnId);
  1693. }
  1694. @Override
  1695. public Iterator<Component> iterator() {
  1696. return Collections.unmodifiableSet(extensionComponents).iterator();
  1697. }
  1698. /**
  1699. * Sets the number of frozen columns in this grid. Setting the count to 0
  1700. * means that no data columns will be frozen, but the built-in selection
  1701. * checkbox column will still be frozen if it's in use. Setting the count to
  1702. * -1 will also disable the selection column.
  1703. * <p>
  1704. * The default value is 0.
  1705. *
  1706. * @param numberOfColumns
  1707. * the number of columns that should be frozen
  1708. *
  1709. * @throws IllegalArgumentException
  1710. * if the column count is less than -1 or greater than the
  1711. * number of visible columns
  1712. */
  1713. public void setFrozenColumnCount(int numberOfColumns) {
  1714. if (numberOfColumns < -1 || numberOfColumns > columnSet.size()) {
  1715. throw new IllegalArgumentException(
  1716. "count must be between -1 and the current number of columns ("
  1717. + columnSet.size() + "): " + numberOfColumns);
  1718. }
  1719. getState().frozenColumnCount = numberOfColumns;
  1720. }
  1721. /**
  1722. * Gets the number of frozen columns in this grid. 0 means that no data
  1723. * columns will be frozen, but the built-in selection checkbox column will
  1724. * still be frozen if it's in use. -1 means that not even the selection
  1725. * column is frozen.
  1726. * <p>
  1727. * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
  1728. * columns} in the count.
  1729. *
  1730. * @see #setFrozenColumnCount(int)
  1731. *
  1732. * @return the number of frozen columns
  1733. */
  1734. public int getFrozenColumnCount() {
  1735. return getState(false).frozenColumnCount;
  1736. }
  1737. /**
  1738. * Sets the number of rows that should be visible in Grid's body. This
  1739. * method will set the height mode to be {@link HeightMode#ROW}.
  1740. *
  1741. * @param rows
  1742. * The height in terms of number of rows displayed in Grid's
  1743. * body. If Grid doesn't contain enough rows, white space is
  1744. * displayed instead. If <code>null</code> is given, then Grid's
  1745. * height is undefined
  1746. * @throws IllegalArgumentException
  1747. * if {@code rows} is zero or less
  1748. * @throws IllegalArgumentException
  1749. * if {@code rows} is {@link Double#isInfinite(double) infinite}
  1750. * @throws IllegalArgumentException
  1751. * if {@code rows} is {@link Double#isNaN(double) NaN}
  1752. */
  1753. public void setHeightByRows(double rows) {
  1754. if (rows <= 0.0d) {
  1755. throw new IllegalArgumentException(
  1756. "More than zero rows must be shown.");
  1757. } else if (Double.isInfinite(rows)) {
  1758. throw new IllegalArgumentException(
  1759. "Grid doesn't support infinite heights");
  1760. } else if (Double.isNaN(rows)) {
  1761. throw new IllegalArgumentException("NaN is not a valid row count");
  1762. }
  1763. getState().heightMode = HeightMode.ROW;
  1764. getState().heightByRows = rows;
  1765. }
  1766. /**
  1767. * Gets the amount of rows in Grid's body that are shown, while
  1768. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  1769. *
  1770. * @return the amount of rows that are being shown in Grid's body
  1771. * @see #setHeightByRows(double)
  1772. */
  1773. public double getHeightByRows() {
  1774. return getState(false).heightByRows;
  1775. }
  1776. /**
  1777. * {@inheritDoc}
  1778. * <p>
  1779. * <em>Note:</em> This method will set the height mode to be
  1780. * {@link HeightMode#CSS}.
  1781. *
  1782. * @see #setHeightMode(HeightMode)
  1783. */
  1784. @Override
  1785. public void setHeight(float height, Unit unit) {
  1786. getState().heightMode = HeightMode.CSS;
  1787. super.setHeight(height, unit);
  1788. }
  1789. /**
  1790. * Defines the mode in which the Grid widget's height is calculated.
  1791. * <p>
  1792. * If {@link HeightMode#CSS} is given, Grid will respect the values given
  1793. * via a {@code setHeight}-method, and behave as a traditional Component.
  1794. * <p>
  1795. * If {@link HeightMode#ROW} is given, Grid will make sure that the body
  1796. * will display as many rows as {@link #getHeightByRows()} defines.
  1797. * <em>Note:</em> If headers/footers are inserted or removed, the widget
  1798. * will resize itself to still display the required amount of rows in its
  1799. * body. It also takes the horizontal scrollbar into account.
  1800. *
  1801. * @param heightMode
  1802. * the mode in to which Grid should be set
  1803. */
  1804. public void setHeightMode(HeightMode heightMode) {
  1805. /*
  1806. * This method is a workaround for the fact that Vaadin re-applies
  1807. * widget dimensions (height/width) on each state change event. The
  1808. * original design was to have setHeight and setHeightByRow be equals,
  1809. * and whichever was called the latest was considered in effect.
  1810. *
  1811. * But, because of Vaadin always calling setHeight on the widget, this
  1812. * approach doesn't work.
  1813. */
  1814. getState().heightMode = heightMode;
  1815. }
  1816. /**
  1817. * Returns the current {@link HeightMode} the Grid is in.
  1818. * <p>
  1819. * Defaults to {@link HeightMode#CSS}.
  1820. *
  1821. * @return the current HeightMode
  1822. */
  1823. public HeightMode getHeightMode() {
  1824. return getState(false).heightMode;
  1825. }
  1826. /**
  1827. * Sets the style generator that is used for generating class names for rows
  1828. * in this grid. Returning null from the generator results in no custom
  1829. * style name being set.
  1830. *
  1831. * @see StyleGenerator
  1832. *
  1833. * @param styleGenerator
  1834. * the row style generator to set, not null
  1835. * @throws NullPointerException
  1836. * if {@code styleGenerator} is {@code null}
  1837. */
  1838. public void setStyleGenerator(StyleGenerator<T> styleGenerator) {
  1839. Objects.requireNonNull(styleGenerator,
  1840. "Style generator must not be null");
  1841. this.styleGenerator = styleGenerator;
  1842. getDataCommunicator().reset();
  1843. }
  1844. /**
  1845. * Gets the style generator that is used for generating class names for
  1846. * rows.
  1847. *
  1848. * @see StyleGenerator
  1849. *
  1850. * @return the row style generator
  1851. */
  1852. public StyleGenerator<T> getStyleGenerator() {
  1853. return styleGenerator;
  1854. }
  1855. /**
  1856. * Sets the description generator that is used for generating descriptions
  1857. * for rows.
  1858. *
  1859. * @param descriptionGenerator
  1860. * the row description generator to set, or <code>null</code> to
  1861. * remove a previously set generator
  1862. */
  1863. public void setDescriptionGenerator(
  1864. DescriptionGenerator<T> descriptionGenerator) {
  1865. this.descriptionGenerator = descriptionGenerator;
  1866. getDataCommunicator().reset();
  1867. }
  1868. /**
  1869. * Gets the description generator that is used for generating descriptions
  1870. * for rows.
  1871. *
  1872. * @return the row description generator, or <code>null</code> if no
  1873. * generator is set
  1874. */
  1875. public DescriptionGenerator<T> getDescriptionGenerator() {
  1876. return descriptionGenerator;
  1877. }
  1878. //
  1879. // HEADER AND FOOTER
  1880. //
  1881. /**
  1882. * Returns the header row at the given index.
  1883. *
  1884. * @param index
  1885. * the index of the row, where the topmost row has index zero
  1886. * @return the header row at the index
  1887. * @throws IndexOutOfBoundsException
  1888. * if {@code rowIndex < 0 || rowIndex >= getHeaderRowCount()}
  1889. */
  1890. public HeaderRow getHeaderRow(int index) {
  1891. return getHeader().getRow(index);
  1892. }
  1893. /**
  1894. * Gets the number of rows in the header section.
  1895. *
  1896. * @return the number of header rows
  1897. */
  1898. public int getHeaderRowCount() {
  1899. return header.getRowCount();
  1900. }
  1901. /**
  1902. * Inserts a new row at the given position to the header section. Shifts the
  1903. * row currently at that position and any subsequent rows down (adds one to
  1904. * their indices). Inserting at {@link #getHeaderRowCount()} appends the row
  1905. * at the bottom of the header.
  1906. *
  1907. * @param index
  1908. * the index at which to insert the row, where the topmost row
  1909. * has index zero
  1910. * @return the inserted header row
  1911. *
  1912. * @throws IndexOutOfBoundsException
  1913. * if {@code rowIndex < 0 || rowIndex > getHeaderRowCount()}
  1914. *
  1915. * @see #appendHeaderRow()
  1916. * @see #prependHeaderRow()
  1917. * @see #removeHeaderRow(HeaderRow)
  1918. * @see #removeHeaderRow(int)
  1919. */
  1920. public HeaderRow addHeaderRowAt(int index) {
  1921. return getHeader().addRowAt(index);
  1922. }
  1923. /**
  1924. * Adds a new row at the bottom of the header section.
  1925. *
  1926. * @return the appended header row
  1927. *
  1928. * @see #prependHeaderRow()
  1929. * @see #addHeaderRowAt(int)
  1930. * @see #removeHeaderRow(HeaderRow)
  1931. * @see #removeHeaderRow(int)
  1932. */
  1933. public HeaderRow appendHeaderRow() {
  1934. return addHeaderRowAt(getHeaderRowCount());
  1935. }
  1936. /**
  1937. * Adds a new row at the top of the header section.
  1938. *
  1939. * @return the prepended header row
  1940. *
  1941. * @see #appendHeaderRow()
  1942. * @see #addHeaderRowAt(int)
  1943. * @see #removeHeaderRow(HeaderRow)
  1944. * @see #removeHeaderRow(int)
  1945. */
  1946. public HeaderRow prependHeaderRow() {
  1947. return addHeaderRowAt(0);
  1948. }
  1949. /**
  1950. * Removes the given row from the header section. Removing a default row
  1951. * sets the Grid to have no default row.
  1952. *
  1953. * @param row
  1954. * the header row to be removed, not null
  1955. *
  1956. * @throws IllegalArgumentException
  1957. * if the header does not contain the row
  1958. *
  1959. * @see #removeHeaderRow(int)
  1960. * @see #addHeaderRowAt(int)
  1961. * @see #appendHeaderRow()
  1962. * @see #prependHeaderRow()
  1963. */
  1964. public void removeHeaderRow(HeaderRow row) {
  1965. getHeader().removeRow(row);
  1966. }
  1967. /**
  1968. * Removes the row at the given position from the header section.
  1969. *
  1970. * @param index
  1971. * the index of the row to remove, where the topmost row has
  1972. * index zero
  1973. *
  1974. * @throws IndexOutOfBoundsException
  1975. * if {@code index < 0 || index >= getHeaderRowCount()}
  1976. *
  1977. * @see #removeHeaderRow(HeaderRow)
  1978. * @see #addHeaderRowAt(int)
  1979. * @see #appendHeaderRow()
  1980. * @see #prependHeaderRow()
  1981. */
  1982. public void removeHeaderRow(int index) {
  1983. getHeader().removeRow(index);
  1984. }
  1985. /**
  1986. * Returns the current default row of the header.
  1987. *
  1988. * @return the default row or null if no default row set
  1989. *
  1990. * @see #setDefaultHeaderRow(HeaderRow)
  1991. */
  1992. public HeaderRow getDefaultHeaderRow() {
  1993. return header.getDefaultRow();
  1994. }
  1995. /**
  1996. * Sets the default row of the header. The default row is a special header
  1997. * row that displays column captions and sort indicators. By default Grid
  1998. * has a single row which is also the default row. When a header row is set
  1999. * as the default row, any existing cell content is replaced by the column
  2000. * captions.
  2001. *
  2002. * @param row
  2003. * the new default row, or null for no default row
  2004. *
  2005. * @throws IllegalArgumentException
  2006. * if the header does not contain the row
  2007. */
  2008. public void setDefaultHeaderRow(HeaderRow row) {
  2009. header.setDefaultRow((Row) row);
  2010. }
  2011. /**
  2012. * Returns the header section of this grid. The default header contains a
  2013. * single row, set as the {@linkplain #setDefaultHeaderRow(HeaderRow)
  2014. * default row}.
  2015. *
  2016. * @return the header section
  2017. */
  2018. protected Header getHeader() {
  2019. return header;
  2020. }
  2021. /**
  2022. * Returns the footer row at the given index.
  2023. *
  2024. * @param index
  2025. * the index of the row, where the topmost row has index zero
  2026. * @return the footer row at the index
  2027. * @throws IndexOutOfBoundsException
  2028. * if {@code rowIndex < 0 || rowIndex >= getFooterRowCount()}
  2029. */
  2030. public FooterRow getFooterRow(int index) {
  2031. return getFooter().getRow(index);
  2032. }
  2033. /**
  2034. * Gets the number of rows in the footer section.
  2035. *
  2036. * @return the number of footer rows
  2037. */
  2038. public int getFooterRowCount() {
  2039. return getFooter().getRowCount();
  2040. }
  2041. /**
  2042. * Inserts a new row at the given position to the footer section. Shifts the
  2043. * row currently at that position and any subsequent rows down (adds one to
  2044. * their indices). Inserting at {@link #getFooterRowCount()} appends the row
  2045. * at the bottom of the footer.
  2046. *
  2047. * @param index
  2048. * the index at which to insert the row, where the topmost row
  2049. * has index zero
  2050. * @return the inserted footer row
  2051. *
  2052. * @throws IndexOutOfBoundsException
  2053. * if {@code rowIndex < 0 || rowIndex > getFooterRowCount()}
  2054. *
  2055. * @see #appendFooterRow()
  2056. * @see #prependFooterRow()
  2057. * @see #removeFooterRow(FooterRow)
  2058. * @see #removeFooterRow(int)
  2059. */
  2060. public FooterRow addFooterRowAt(int index) {
  2061. return getFooter().addRowAt(index);
  2062. }
  2063. /**
  2064. * Adds a new row at the bottom of the footer section.
  2065. *
  2066. * @return the appended footer row
  2067. *
  2068. * @see #prependFooterRow()
  2069. * @see #addFooterRowAt(int)
  2070. * @see #removeFooterRow(FooterRow)
  2071. * @see #removeFooterRow(int)
  2072. */
  2073. public FooterRow appendFooterRow() {
  2074. return addFooterRowAt(getFooterRowCount());
  2075. }
  2076. /**
  2077. * Adds a new row at the top of the footer section.
  2078. *
  2079. * @return the prepended footer row
  2080. *
  2081. * @see #appendFooterRow()
  2082. * @see #addFooterRowAt(int)
  2083. * @see #removeFooterRow(FooterRow)
  2084. * @see #removeFooterRow(int)
  2085. */
  2086. public FooterRow prependFooterRow() {
  2087. return addFooterRowAt(0);
  2088. }
  2089. /**
  2090. * Removes the given row from the footer section. Removing a default row
  2091. * sets the Grid to have no default row.
  2092. *
  2093. * @param row
  2094. * the footer row to be removed, not null
  2095. *
  2096. * @throws IllegalArgumentException
  2097. * if the footer does not contain the row
  2098. *
  2099. * @see #removeFooterRow(int)
  2100. * @see #addFooterRowAt(int)
  2101. * @see #appendFooterRow()
  2102. * @see #prependFooterRow()
  2103. */
  2104. public void removeFooterRow(FooterRow row) {
  2105. getFooter().removeRow(row);
  2106. }
  2107. /**
  2108. * Removes the row at the given position from the footer section.
  2109. *
  2110. * @param index
  2111. * the index of the row to remove, where the topmost row has
  2112. * index zero
  2113. *
  2114. * @throws IndexOutOfBoundsException
  2115. * if {@code index < 0 || index >= getFooterRowCount()}
  2116. *
  2117. * @see #removeFooterRow(FooterRow)
  2118. * @see #addFooterRowAt(int)
  2119. * @see #appendFooterRow()
  2120. * @see #prependFooterRow()
  2121. */
  2122. public void removeFooterRow(int index) {
  2123. getFooter().removeRow(index);
  2124. }
  2125. /**
  2126. * Returns the footer section of this grid. The default footer contains a
  2127. * single row, set as the {@linkplain #setDefaultFooterRow(FooterRow)
  2128. * default row}.
  2129. *
  2130. * @return the footer section
  2131. */
  2132. protected Footer getFooter() {
  2133. return footer;
  2134. }
  2135. /**
  2136. * Registers a new column reorder listener.
  2137. *
  2138. * @param listener
  2139. * the listener to register, not null
  2140. * @return a registration for the listener
  2141. */
  2142. public Registration addColumnReorderListener(
  2143. ColumnReorderListener listener) {
  2144. addListener(ColumnReorderEvent.class, listener, COLUMN_REORDER_METHOD);
  2145. return () -> removeListener(ColumnReorderEvent.class, listener,
  2146. COLUMN_REORDER_METHOD);
  2147. }
  2148. /**
  2149. * Registers a new column resize listener.
  2150. *
  2151. * @param listener
  2152. * the listener to register, not null
  2153. * @return a registration for the listener
  2154. */
  2155. public Registration addColumnResizeListener(ColumnResizeListener listener) {
  2156. addListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
  2157. return () -> removeListener(ColumnResizeEvent.class, listener,
  2158. COLUMN_RESIZE_METHOD);
  2159. }
  2160. /**
  2161. * Adds an item click listener. The listener is called when an item of this
  2162. * {@code Grid} is clicked.
  2163. *
  2164. * @param listener
  2165. * the item click listener, not null
  2166. * @return a registration for the listener
  2167. */
  2168. public Registration addItemClickListener(
  2169. ItemClickListener<? super T> listener) {
  2170. addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClick.class,
  2171. listener, ITEM_CLICK_METHOD);
  2172. return () -> removeListener(ItemClick.class, listener);
  2173. }
  2174. /**
  2175. * Registers a new column visibility change listener.
  2176. *
  2177. * @param listener
  2178. * the listener to register, not null
  2179. * @return a registration for the listener
  2180. */
  2181. public Registration addColumnVisibilityChangeListener(
  2182. ColumnVisibilityChangeListener listener) {
  2183. addListener(ColumnVisibilityChangeEvent.class, listener,
  2184. COLUMN_VISIBILITY_METHOD);
  2185. return () -> removeListener(ColumnVisibilityChangeEvent.class, listener,
  2186. COLUMN_VISIBILITY_METHOD);
  2187. }
  2188. /**
  2189. * Returns whether column reordering is allowed. Default value is
  2190. * <code>false</code>.
  2191. *
  2192. * @return true if reordering is allowed
  2193. */
  2194. public boolean isColumnReorderingAllowed() {
  2195. return getState(false).columnReorderingAllowed;
  2196. }
  2197. /**
  2198. * Sets whether or not column reordering is allowed. Default value is
  2199. * <code>false</code>.
  2200. *
  2201. * @param columnReorderingAllowed
  2202. * specifies whether column reordering is allowed
  2203. */
  2204. public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
  2205. if (isColumnReorderingAllowed() != columnReorderingAllowed) {
  2206. getState().columnReorderingAllowed = columnReorderingAllowed;
  2207. }
  2208. }
  2209. /**
  2210. * Sets the columns and their order for the grid. Columns currently in this
  2211. * grid that are not present in columns are removed. Similarly, any new
  2212. * column in columns will be added to this grid.
  2213. *
  2214. * @param columns
  2215. * the columns to set
  2216. */
  2217. public void setColumns(Column<T, ?>... columns) {
  2218. List<Column<T, ?>> currentColumns = getColumns();
  2219. Set<Column<T, ?>> removeColumns = new HashSet<>(currentColumns);
  2220. Set<Column<T, ?>> addColumns = Arrays.stream(columns)
  2221. .collect(Collectors.toSet());
  2222. removeColumns.removeAll(addColumns);
  2223. removeColumns.stream().forEach(this::removeColumn);
  2224. addColumns.removeAll(currentColumns);
  2225. addColumns.stream().forEach(c -> addColumn(getIdentifier(c), c));
  2226. setColumnOrder(columns);
  2227. }
  2228. private String getIdentifier(Column<T, ?> column) {
  2229. return columnKeys.entrySet().stream()
  2230. .filter(entry -> entry.getValue().equals(column))
  2231. .map(entry -> entry.getKey()).findFirst()
  2232. .orElse(getGeneratedIdentifier());
  2233. }
  2234. private String getGeneratedIdentifier() {
  2235. String columnId = "generatedColumn" + counter;
  2236. counter = counter + 1;
  2237. return columnId;
  2238. }
  2239. /**
  2240. * Sets a new column order for the grid. All columns which are not ordered
  2241. * here will remain in the order they were before as the last columns of
  2242. * grid.
  2243. *
  2244. * @param columns
  2245. * the columns in the order they should be
  2246. */
  2247. public void setColumnOrder(Column<T, ?>... columns) {
  2248. List<String> columnOrder = new ArrayList<>();
  2249. for (Column<T, ?> column : columns) {
  2250. if (columnSet.contains(column)) {
  2251. columnOrder.add(column.getId());
  2252. } else {
  2253. throw new IllegalArgumentException(
  2254. "setColumnOrder should not be called "
  2255. + "with columns that are not in the grid.");
  2256. }
  2257. }
  2258. List<String> stateColumnOrder = getState().columnOrder;
  2259. if (stateColumnOrder.size() != columnOrder.size()) {
  2260. stateColumnOrder.removeAll(columnOrder);
  2261. columnOrder.addAll(stateColumnOrder);
  2262. }
  2263. getState().columnOrder = columnOrder;
  2264. fireColumnReorderEvent(false);
  2265. }
  2266. @Override
  2267. protected GridState getState() {
  2268. return getState(true);
  2269. }
  2270. @Override
  2271. protected GridState getState(boolean markAsDirty) {
  2272. return (GridState) super.getState(markAsDirty);
  2273. }
  2274. private void addExtensionComponent(Component c) {
  2275. if (extensionComponents.add(c)) {
  2276. c.setParent(this);
  2277. markAsDirty();
  2278. }
  2279. }
  2280. private void removeExtensionComponent(Component c) {
  2281. if (extensionComponents.remove(c)) {
  2282. c.setParent(null);
  2283. markAsDirty();
  2284. }
  2285. }
  2286. private void fireColumnReorderEvent(boolean userOriginated) {
  2287. fireEvent(new ColumnReorderEvent(this, userOriginated));
  2288. }
  2289. private void fireColumnResizeEvent(Column<?, ?> column,
  2290. boolean userOriginated) {
  2291. fireEvent(new ColumnResizeEvent(this, column, userOriginated));
  2292. }
  2293. }