You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Grid.java 110KB

Migrate 7.7.5 branch patches to v8. (#7969) * Prevent adding several scrollbar handlers (#19189). Change-Id: Ib0cc6c6835aab6d263f153362a328bcf2be7bc5c * Prevent adding several scrollbar handlers (#19189). * Keep expand ratio for last row/column when reducing grid layout size (#20297) Change-Id: Iff53a803596f4fc1eae8e4bfa307b9c1f4df961a * Fixed drag and drop failure when message dragged from email client (#20451) When dragging message form email client on Windows, item.webkitGetAsEntry() might return null creating NPE on the client side. Added additional checks for this situation. Change-Id: I569f7e6d0d7b137f24be53d1fbce384695ae8c73 * Change expected pre-release version number pattern in publish report Change-Id: Icdacecc490d2490ea9e262f5c5736c1dece2a89d * Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50 # Conflicts: # client/src/main/java/com/vaadin/client/ui/VTextField.java # uitest/src/main/java/com/vaadin/tests/components/textfield/TextChangeEvents.java * Fixed touch scrolling issue in Surface and WP devices (#18737) Fixed by using pointerevents instead of touchevents when the browser is IE11, or Edge. Also added touch-action: none; css rules into escalator.css to prevent default touch behaviour on IE11 and Edge. Does not affect IE8 to IE10 browsers, behaviour on those will stay the same as before the fix. No new unit tests since we do not have automatic touch testing possibilities yet. Please test manually with Surface: IE11 and Edge, use for example uitest: com.vaadin.tests.components.grid.basics.GridBasicsomponents.grid.basics.GridBasics Change-Id: Iddbf1852e6ffafc855f749d6f4ebb235ed0f5703 * Add lazy/simple resize mode to Grid (#20108) Change-Id: I47427efc28c350382dba8c1f50fd332a3f4585e4 # Conflicts: # client/src/main/java/com/vaadin/client/connectors/GridConnector.java # client/src/main/java/com/vaadin/client/widgets/Grid.java # server/src/main/java/com/vaadin/ui/Grid.java # shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java # themes/src/main/themes/VAADIN/themes/base/grid/grid.scss # uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java Change-Id: Ieca56121875198ed559a41c143b28926e2695433 * Fix NPE in case some items don't contain all properties of Grid. This could occur in when parent is a different entity than its children in hierarchical data. Change-Id: Icd53b5b5e5544a3680d0cd99702ab78224b2dc08 # Conflicts: # server/src/main/java/com/vaadin/data/fieldgroup/FieldGroup.java # server/src/main/java/com/vaadin/ui/Grid.java * Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50 # Conflicts: # client/src/main/java/com/vaadin/client/ui/VTextField.java # uitest/src/test/java/com/vaadin/tests/components/textfield/TextChangeEventsTest.java * Add lazy/simple resize mode to Grid (#20108) Change-Id: I47427efc28c350382dba8c1f50fd332a3f4585e4 * Removed V8 VTextField unused import, forgotten @RunLocally. * Don't rely on selenium "sendKeys" behavior. * Revert "Change expected pre-release version number pattern in publish report" This reverts commit 8df27b952dddb691aead6a633c5b3724c98bf343. * Migrate TextField/TextArea patch from 7.7 to master (modern components) Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50
7 years ago
Migrate 7.7.5 branch patches to v8. (#7969) * Prevent adding several scrollbar handlers (#19189). Change-Id: Ib0cc6c6835aab6d263f153362a328bcf2be7bc5c * Prevent adding several scrollbar handlers (#19189). * Keep expand ratio for last row/column when reducing grid layout size (#20297) Change-Id: Iff53a803596f4fc1eae8e4bfa307b9c1f4df961a * Fixed drag and drop failure when message dragged from email client (#20451) When dragging message form email client on Windows, item.webkitGetAsEntry() might return null creating NPE on the client side. Added additional checks for this situation. Change-Id: I569f7e6d0d7b137f24be53d1fbce384695ae8c73 * Change expected pre-release version number pattern in publish report Change-Id: Icdacecc490d2490ea9e262f5c5736c1dece2a89d * Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50 # Conflicts: # client/src/main/java/com/vaadin/client/ui/VTextField.java # uitest/src/main/java/com/vaadin/tests/components/textfield/TextChangeEvents.java * Fixed touch scrolling issue in Surface and WP devices (#18737) Fixed by using pointerevents instead of touchevents when the browser is IE11, or Edge. Also added touch-action: none; css rules into escalator.css to prevent default touch behaviour on IE11 and Edge. Does not affect IE8 to IE10 browsers, behaviour on those will stay the same as before the fix. No new unit tests since we do not have automatic touch testing possibilities yet. Please test manually with Surface: IE11 and Edge, use for example uitest: com.vaadin.tests.components.grid.basics.GridBasicsomponents.grid.basics.GridBasics Change-Id: Iddbf1852e6ffafc855f749d6f4ebb235ed0f5703 * Add lazy/simple resize mode to Grid (#20108) Change-Id: I47427efc28c350382dba8c1f50fd332a3f4585e4 # Conflicts: # client/src/main/java/com/vaadin/client/connectors/GridConnector.java # client/src/main/java/com/vaadin/client/widgets/Grid.java # server/src/main/java/com/vaadin/ui/Grid.java # shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java # themes/src/main/themes/VAADIN/themes/base/grid/grid.scss # uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java Change-Id: Ieca56121875198ed559a41c143b28926e2695433 * Fix NPE in case some items don't contain all properties of Grid. This could occur in when parent is a different entity than its children in hierarchical data. Change-Id: Icd53b5b5e5544a3680d0cd99702ab78224b2dc08 # Conflicts: # server/src/main/java/com/vaadin/data/fieldgroup/FieldGroup.java # server/src/main/java/com/vaadin/ui/Grid.java * Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50 # Conflicts: # client/src/main/java/com/vaadin/client/ui/VTextField.java # uitest/src/test/java/com/vaadin/tests/components/textfield/TextChangeEventsTest.java * Add lazy/simple resize mode to Grid (#20108) Change-Id: I47427efc28c350382dba8c1f50fd332a3f4585e4 * Removed V8 VTextField unused import, forgotten @RunLocally. * Don't rely on selenium "sendKeys" behavior. * Revert "Change expected pre-release version number pattern in publish report" This reverts commit 8df27b952dddb691aead6a633c5b3724c98bf343. * Migrate TextField/TextArea patch from 7.7 to master (modern components) Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50
7 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220
  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.UUID;
  35. import java.util.function.BinaryOperator;
  36. import java.util.function.Function;
  37. import java.util.stream.Collectors;
  38. import java.util.stream.Stream;
  39. import org.jsoup.nodes.Attributes;
  40. import org.jsoup.nodes.Element;
  41. import org.jsoup.select.Elements;
  42. import com.vaadin.data.Binder;
  43. import com.vaadin.data.Listing;
  44. import com.vaadin.data.ValueProvider;
  45. import com.vaadin.data.provider.DataCommunicator;
  46. import com.vaadin.data.provider.DataProvider;
  47. import com.vaadin.data.provider.Query;
  48. import com.vaadin.data.provider.SortOrder;
  49. import com.vaadin.event.ConnectorEvent;
  50. import com.vaadin.event.ContextClickEvent;
  51. import com.vaadin.event.SortEvent;
  52. import com.vaadin.event.SortEvent.SortListener;
  53. import com.vaadin.event.SortEvent.SortNotifier;
  54. import com.vaadin.event.selection.MultiSelectionListener;
  55. import com.vaadin.event.selection.SelectionListener;
  56. import com.vaadin.event.selection.SingleSelectionListener;
  57. import com.vaadin.server.EncodeResult;
  58. import com.vaadin.server.Extension;
  59. import com.vaadin.server.JsonCodec;
  60. import com.vaadin.server.SerializableComparator;
  61. import com.vaadin.server.SerializableFunction;
  62. import com.vaadin.shared.MouseEventDetails;
  63. import com.vaadin.shared.Registration;
  64. import com.vaadin.shared.data.DataCommunicatorConstants;
  65. import com.vaadin.shared.data.sort.SortDirection;
  66. import com.vaadin.shared.ui.grid.AbstractGridExtensionState;
  67. import com.vaadin.shared.ui.grid.ColumnResizeMode;
  68. import com.vaadin.shared.ui.grid.ColumnState;
  69. import com.vaadin.shared.ui.grid.DetailsManagerState;
  70. import com.vaadin.shared.ui.grid.GridConstants;
  71. import com.vaadin.shared.ui.grid.GridConstants.Section;
  72. import com.vaadin.shared.ui.grid.GridServerRpc;
  73. import com.vaadin.shared.ui.grid.GridState;
  74. import com.vaadin.shared.ui.grid.GridStaticCellType;
  75. import com.vaadin.shared.ui.grid.HeightMode;
  76. import com.vaadin.shared.ui.grid.SectionState;
  77. import com.vaadin.ui.components.grid.ColumnReorderListener;
  78. import com.vaadin.ui.components.grid.ColumnResizeListener;
  79. import com.vaadin.ui.components.grid.ColumnVisibilityChangeListener;
  80. import com.vaadin.ui.components.grid.DescriptionGenerator;
  81. import com.vaadin.ui.components.grid.DetailsGenerator;
  82. import com.vaadin.ui.components.grid.Editor;
  83. import com.vaadin.ui.components.grid.EditorComponentGenerator;
  84. import com.vaadin.ui.components.grid.EditorImpl;
  85. import com.vaadin.ui.components.grid.Footer;
  86. import com.vaadin.ui.components.grid.FooterCell;
  87. import com.vaadin.ui.components.grid.FooterRow;
  88. import com.vaadin.ui.components.grid.GridSelectionModel;
  89. import com.vaadin.ui.components.grid.Header;
  90. import com.vaadin.ui.components.grid.Header.Row;
  91. import com.vaadin.ui.components.grid.HeaderCell;
  92. import com.vaadin.ui.components.grid.HeaderRow;
  93. import com.vaadin.ui.components.grid.ItemClickListener;
  94. import com.vaadin.ui.components.grid.MultiSelectionModel;
  95. import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
  96. import com.vaadin.ui.components.grid.NoSelectionModel;
  97. import com.vaadin.ui.components.grid.SingleSelectionModel;
  98. import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
  99. import com.vaadin.ui.components.grid.SortOrderProvider;
  100. import com.vaadin.ui.declarative.DesignAttributeHandler;
  101. import com.vaadin.ui.declarative.DesignContext;
  102. import com.vaadin.ui.declarative.DesignException;
  103. import com.vaadin.ui.declarative.DesignFormatter;
  104. import com.vaadin.ui.renderers.AbstractRenderer;
  105. import com.vaadin.ui.renderers.HtmlRenderer;
  106. import com.vaadin.ui.renderers.Renderer;
  107. import com.vaadin.ui.renderers.TextRenderer;
  108. import com.vaadin.util.ReflectTools;
  109. import elemental.json.Json;
  110. import elemental.json.JsonObject;
  111. import elemental.json.JsonValue;
  112. /**
  113. * A grid component for displaying tabular data.
  114. *
  115. * @author Vaadin Ltd
  116. * @since 8.0
  117. *
  118. * @param <T>
  119. * the grid bean type
  120. */
  121. public class Grid<T> extends AbstractListing<T> implements HasComponents,
  122. Listing<T, DataProvider<T, ?>>, SortNotifier<Grid.Column<T, ?>> {
  123. @Deprecated
  124. private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod(
  125. ColumnReorderListener.class, "columnReorder",
  126. ColumnReorderEvent.class);
  127. private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools
  128. .findMethod(SortListener.class, "sort", SortEvent.class);
  129. @Deprecated
  130. private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(
  131. ColumnResizeListener.class, "columnResize",
  132. ColumnResizeEvent.class);
  133. @Deprecated
  134. private static final Method ITEM_CLICK_METHOD = ReflectTools
  135. .findMethod(ItemClickListener.class, "itemClick", ItemClick.class);
  136. @Deprecated
  137. private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools
  138. .findMethod(ColumnVisibilityChangeListener.class,
  139. "columnVisibilityChanged",
  140. ColumnVisibilityChangeEvent.class);
  141. /**
  142. * Selection mode representing the built-in selection models in grid.
  143. * <p>
  144. * These enums can be used in {@link Grid#setSelectionMode(SelectionMode)}
  145. * to easily switch between the build-in selection models.
  146. *
  147. * @see Grid#setSelectionMode(SelectionMode)
  148. * @see Grid#setSelectionModel(GridSelectionModel)
  149. */
  150. public enum SelectionMode {
  151. /**
  152. * Single selection mode that maps to build-in
  153. * {@link SingleSelectionModel}.
  154. *
  155. * @see SingleSelectionModelImpl
  156. */
  157. SINGLE {
  158. @Override
  159. protected <T> GridSelectionModel<T> createModel() {
  160. return new SingleSelectionModelImpl<>();
  161. }
  162. },
  163. /**
  164. * Multiselection mode that maps to build-in {@link MultiSelectionModel}
  165. * .
  166. *
  167. * @see MultiSelectionModelImpl
  168. */
  169. MULTI {
  170. @Override
  171. protected <T> GridSelectionModel<T> createModel() {
  172. return new MultiSelectionModelImpl<>();
  173. }
  174. },
  175. /**
  176. * Selection model that doesn't allow selection.
  177. *
  178. * @see NoSelectionModel
  179. */
  180. NONE {
  181. @Override
  182. protected <T> GridSelectionModel<T> createModel() {
  183. return new NoSelectionModel<>();
  184. }
  185. };
  186. /**
  187. * Creates the selection model to use with this enum.
  188. *
  189. * @param <T>
  190. * the type of items in the grid
  191. * @return the selection model
  192. */
  193. protected abstract <T> GridSelectionModel<T> createModel();
  194. }
  195. /**
  196. * An event that is fired when the columns are reordered.
  197. */
  198. public static class ColumnReorderEvent extends Component.Event {
  199. private final boolean userOriginated;
  200. /**
  201. *
  202. * @param source
  203. * the grid where the event originated from
  204. * @param userOriginated
  205. * <code>true</code> if event is a result of user
  206. * interaction, <code>false</code> if from API call
  207. */
  208. public ColumnReorderEvent(Grid source, boolean userOriginated) {
  209. super(source);
  210. this.userOriginated = userOriginated;
  211. }
  212. /**
  213. * Returns <code>true</code> if the column reorder was done by the user,
  214. * <code>false</code> if not and it was triggered by server side code.
  215. *
  216. * @return <code>true</code> if event is a result of user interaction
  217. */
  218. public boolean isUserOriginated() {
  219. return userOriginated;
  220. }
  221. }
  222. /**
  223. * An event that is fired when a column is resized, either programmatically
  224. * or by the user.
  225. */
  226. public static class ColumnResizeEvent extends Component.Event {
  227. private final Column<?, ?> column;
  228. private final boolean userOriginated;
  229. /**
  230. *
  231. * @param source
  232. * the grid where the event originated from
  233. * @param userOriginated
  234. * <code>true</code> if event is a result of user
  235. * interaction, <code>false</code> if from API call
  236. */
  237. public ColumnResizeEvent(Grid<?> source, Column<?, ?> column,
  238. boolean userOriginated) {
  239. super(source);
  240. this.column = column;
  241. this.userOriginated = userOriginated;
  242. }
  243. /**
  244. * Returns the column that was resized.
  245. *
  246. * @return the resized column.
  247. */
  248. public Column<?, ?> getColumn() {
  249. return column;
  250. }
  251. /**
  252. * Returns <code>true</code> if the column resize was done by the user,
  253. * <code>false</code> if not and it was triggered by server side code.
  254. *
  255. * @return <code>true</code> if event is a result of user interaction
  256. */
  257. public boolean isUserOriginated() {
  258. return userOriginated;
  259. }
  260. }
  261. /**
  262. * An event fired when an item in the Grid has been clicked.
  263. *
  264. * @param <T>
  265. * the grid bean type
  266. */
  267. public static class ItemClick<T> extends ConnectorEvent {
  268. private final T item;
  269. private final Column<T, ?> column;
  270. private final MouseEventDetails mouseEventDetails;
  271. /**
  272. * Creates a new {@code ItemClick} event containing the given item and
  273. * Column originating from the given Grid.
  274. *
  275. */
  276. public ItemClick(Grid<T> source, Column<T, ?> column, T item,
  277. MouseEventDetails mouseEventDetails) {
  278. super(source);
  279. this.column = column;
  280. this.item = item;
  281. this.mouseEventDetails = mouseEventDetails;
  282. }
  283. /**
  284. * Returns the clicked item.
  285. *
  286. * @return the clicked item
  287. */
  288. public T getItem() {
  289. return item;
  290. }
  291. /**
  292. * Returns the clicked column.
  293. *
  294. * @return the clicked column
  295. */
  296. public Column<T, ?> getColumn() {
  297. return column;
  298. }
  299. /**
  300. * Returns the source Grid.
  301. *
  302. * @return the grid
  303. */
  304. @Override
  305. public Grid<T> getSource() {
  306. return (Grid<T>) super.getSource();
  307. }
  308. /**
  309. * Returns the mouse event details.
  310. *
  311. * @return the mouse event details
  312. */
  313. public MouseEventDetails getMouseEventDetails() {
  314. return mouseEventDetails;
  315. }
  316. }
  317. /**
  318. * ContextClickEvent for the Grid Component.
  319. *
  320. * @param <T>
  321. * the grid bean type
  322. */
  323. public static class GridContextClickEvent<T> extends ContextClickEvent {
  324. private final T item;
  325. private final int rowIndex;
  326. private final Column<?, ?> column;
  327. private final Section section;
  328. /**
  329. * Creates a new context click event.
  330. *
  331. * @param source
  332. * the grid where the context click occurred
  333. * @param mouseEventDetails
  334. * details about mouse position
  335. * @param section
  336. * the section of the grid which was clicked
  337. * @param rowIndex
  338. * the index of the row which was clicked
  339. * @param item
  340. * the item which was clicked
  341. * @param column
  342. * the column which was clicked
  343. */
  344. public GridContextClickEvent(Grid<T> source,
  345. MouseEventDetails mouseEventDetails, Section section,
  346. int rowIndex, T item, Column<?, ?> column) {
  347. super(source, mouseEventDetails);
  348. this.item = item;
  349. this.section = section;
  350. this.column = column;
  351. this.rowIndex = rowIndex;
  352. }
  353. /**
  354. * Returns the item of context clicked row.
  355. *
  356. * @return item of clicked row; <code>null</code> if header or footer
  357. */
  358. public T getItem() {
  359. return item;
  360. }
  361. /**
  362. * Returns the clicked column.
  363. *
  364. * @return the clicked column
  365. */
  366. public Column<?, ?> getColumn() {
  367. return column;
  368. }
  369. /**
  370. * Return the clicked section of Grid.
  371. *
  372. * @return section of grid
  373. */
  374. public Section getSection() {
  375. return section;
  376. }
  377. /**
  378. * Returns the clicked row index.
  379. * <p>
  380. * Header and Footer rows for index can be fetched with
  381. * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
  382. *
  383. * @return row index in section
  384. */
  385. public int getRowIndex() {
  386. return rowIndex;
  387. }
  388. @Override
  389. public Grid<T> getComponent() {
  390. return (Grid<T>) super.getComponent();
  391. }
  392. }
  393. /**
  394. * An event that is fired when a column's visibility changes.
  395. *
  396. * @since 7.5.0
  397. */
  398. public static class ColumnVisibilityChangeEvent extends Component.Event {
  399. private final Column<?, ?> column;
  400. private final boolean userOriginated;
  401. private final boolean hidden;
  402. /**
  403. * Constructor for a column visibility change event.
  404. *
  405. * @param source
  406. * the grid from which this event originates
  407. * @param column
  408. * the column that changed its visibility
  409. * @param hidden
  410. * <code>true</code> if the column was hidden,
  411. * <code>false</code> if it became visible
  412. * @param isUserOriginated
  413. * <code>true</code> iff the event was triggered by an UI
  414. * interaction
  415. */
  416. public ColumnVisibilityChangeEvent(Grid<?> source, Column<?, ?> column,
  417. boolean hidden, boolean isUserOriginated) {
  418. super(source);
  419. this.column = column;
  420. this.hidden = hidden;
  421. userOriginated = isUserOriginated;
  422. }
  423. /**
  424. * Gets the column that became hidden or visible.
  425. *
  426. * @return the column that became hidden or visible.
  427. * @see Column#isHidden()
  428. */
  429. public Column<?, ?> getColumn() {
  430. return column;
  431. }
  432. /**
  433. * Was the column set hidden or visible.
  434. *
  435. * @return <code>true</code> if the column was hidden <code>false</code>
  436. * if it was set visible
  437. */
  438. public boolean isHidden() {
  439. return hidden;
  440. }
  441. /**
  442. * Returns <code>true</code> if the column reorder was done by the user,
  443. * <code>false</code> if not and it was triggered by server side code.
  444. *
  445. * @return <code>true</code> if event is a result of user interaction
  446. */
  447. public boolean isUserOriginated() {
  448. return userOriginated;
  449. }
  450. }
  451. /**
  452. * A helper base class for creating extensions for the Grid component.
  453. *
  454. * @param <T>
  455. */
  456. public abstract static class AbstractGridExtension<T>
  457. extends AbstractListingExtension<T> {
  458. @Override
  459. public void extend(AbstractListing<T> grid) {
  460. if (!(grid instanceof Grid)) {
  461. throw new IllegalArgumentException(
  462. getClass().getSimpleName() + " can only extend Grid");
  463. }
  464. super.extend(grid);
  465. }
  466. /**
  467. * Adds given component to the connector hierarchy of Grid.
  468. *
  469. * @param c
  470. * the component to add
  471. */
  472. protected void addComponentToGrid(Component c) {
  473. getParent().addExtensionComponent(c);
  474. }
  475. /**
  476. * Removes given component from the connector hierarchy of Grid.
  477. *
  478. * @param c
  479. * the component to remove
  480. */
  481. protected void removeComponentFromGrid(Component c) {
  482. getParent().removeExtensionComponent(c);
  483. }
  484. @Override
  485. public Grid<T> getParent() {
  486. return (Grid<T>) super.getParent();
  487. }
  488. @Override
  489. protected AbstractGridExtensionState getState() {
  490. return (AbstractGridExtensionState) super.getState();
  491. }
  492. @Override
  493. protected AbstractGridExtensionState getState(boolean markAsDirty) {
  494. return (AbstractGridExtensionState) super.getState(markAsDirty);
  495. }
  496. protected String getInternalIdForColumn(Column<T, ?> column) {
  497. return getParent().getInternalIdForColumn(column);
  498. }
  499. }
  500. private final class GridServerRpcImpl implements GridServerRpc {
  501. @Override
  502. public void sort(String[] columnInternalIds, SortDirection[] directions,
  503. boolean isUserOriginated) {
  504. assert columnInternalIds.length == directions.length : "Column and sort direction counts don't match.";
  505. List<SortOrder<Column<T, ?>>> list = new ArrayList<>(
  506. directions.length);
  507. for (int i = 0; i < columnInternalIds.length; ++i) {
  508. Column<T, ?> column = columnKeys.get(columnInternalIds[i]);
  509. list.add(new SortOrder<>(column, directions[i]));
  510. }
  511. setSortOrder(list, isUserOriginated);
  512. }
  513. @Override
  514. public void itemClick(String rowKey, String columnInternalId,
  515. MouseEventDetails details) {
  516. Column<T, ?> column = getColumnByInternalId(columnInternalId);
  517. T item = getDataCommunicator().getKeyMapper().get(rowKey);
  518. fireEvent(new ItemClick<>(Grid.this, column, item, details));
  519. }
  520. @Override
  521. public void contextClick(int rowIndex, String rowKey,
  522. String columnInternalId, Section section,
  523. MouseEventDetails details) {
  524. T item = null;
  525. if (rowKey != null) {
  526. item = getDataCommunicator().getKeyMapper().get(rowKey);
  527. }
  528. fireEvent(new GridContextClickEvent<>(Grid.this, details, section,
  529. rowIndex, item, getColumnByInternalId(columnInternalId)));
  530. }
  531. @Override
  532. public void columnsReordered(List<String> newColumnOrder,
  533. List<String> oldColumnOrder) {
  534. final String diffStateKey = "columnOrder";
  535. ConnectorTracker connectorTracker = getUI().getConnectorTracker();
  536. JsonObject diffState = connectorTracker.getDiffState(Grid.this);
  537. // discard the change if the columns have been reordered from
  538. // the server side, as the server side is always right
  539. if (getState(false).columnOrder.equals(oldColumnOrder)) {
  540. // Don't mark as dirty since client has the state already
  541. getState(false).columnOrder = newColumnOrder;
  542. // write changes to diffState so that possible reverting the
  543. // column order is sent to client
  544. assert diffState
  545. .hasKey(diffStateKey) : "Field name has changed";
  546. Type type = null;
  547. try {
  548. type = getState(false).getClass()
  549. .getDeclaredField(diffStateKey).getGenericType();
  550. } catch (NoSuchFieldException | SecurityException e) {
  551. e.printStackTrace();
  552. }
  553. EncodeResult encodeResult = JsonCodec.encode(
  554. getState(false).columnOrder, diffState, type,
  555. connectorTracker);
  556. diffState.put(diffStateKey, encodeResult.getEncodedValue());
  557. fireColumnReorderEvent(true);
  558. } else {
  559. // make sure the client is reverted to the order that the
  560. // server thinks it is
  561. diffState.remove(diffStateKey);
  562. markAsDirty();
  563. }
  564. }
  565. @Override
  566. public void columnVisibilityChanged(String internalId, boolean hidden) {
  567. Column<T, ?> column = getColumnByInternalId(internalId);
  568. ColumnState columnState = column.getState(false);
  569. if (columnState.hidden != hidden) {
  570. columnState.hidden = hidden;
  571. fireColumnVisibilityChangeEvent(column, hidden, true);
  572. }
  573. }
  574. @Override
  575. public void columnResized(String internalId, double pixels) {
  576. final Column<T, ?> column = getColumnByInternalId(internalId);
  577. if (column != null && column.isResizable()) {
  578. column.getState().width = pixels;
  579. fireColumnResizeEvent(column, true);
  580. }
  581. }
  582. }
  583. /**
  584. * Class for managing visible details rows.
  585. *
  586. * @param <T>
  587. * the grid bean type
  588. */
  589. public static class DetailsManager<T> extends AbstractGridExtension<T> {
  590. private final Set<T> visibleDetails = new HashSet<>();
  591. private final Map<T, Component> components = new HashMap<>();
  592. private DetailsGenerator<T> generator;
  593. /**
  594. * Sets the details component generator.
  595. *
  596. * @param generator
  597. * the generator for details components
  598. */
  599. public void setDetailsGenerator(DetailsGenerator<T> generator) {
  600. if (this.generator != generator) {
  601. removeAllComponents();
  602. }
  603. this.generator = generator;
  604. visibleDetails.forEach(this::refresh);
  605. }
  606. @Override
  607. public void remove() {
  608. removeAllComponents();
  609. super.remove();
  610. }
  611. private void removeAllComponents() {
  612. // Clean up old components
  613. components.values().forEach(this::removeComponentFromGrid);
  614. components.clear();
  615. }
  616. @Override
  617. public void generateData(T data, JsonObject jsonObject) {
  618. if (generator == null || !visibleDetails.contains(data)) {
  619. return;
  620. }
  621. if (!components.containsKey(data)) {
  622. Component detailsComponent = generator.apply(data);
  623. Objects.requireNonNull(detailsComponent,
  624. "Details generator can't create null components");
  625. if (detailsComponent.getParent() != null) {
  626. throw new IllegalStateException(
  627. "Details component was already attached");
  628. }
  629. addComponentToGrid(detailsComponent);
  630. components.put(data, detailsComponent);
  631. }
  632. jsonObject.put(GridState.JSONKEY_DETAILS_VISIBLE,
  633. components.get(data).getConnectorId());
  634. }
  635. @Override
  636. public void destroyData(T data) {
  637. // No clean up needed. Components are removed when hiding details
  638. // and/or changing details generator
  639. }
  640. /**
  641. * Sets the visibility of details component for given item.
  642. *
  643. * @param data
  644. * the item to show details for
  645. * @param visible
  646. * {@code true} if details component should be visible;
  647. * {@code false} if it should be hidden
  648. */
  649. public void setDetailsVisible(T data, boolean visible) {
  650. boolean refresh = false;
  651. if (!visible) {
  652. refresh = visibleDetails.remove(data);
  653. if (components.containsKey(data)) {
  654. removeComponentFromGrid(components.remove(data));
  655. }
  656. } else {
  657. refresh = visibleDetails.add(data);
  658. }
  659. if (refresh) {
  660. refresh(data);
  661. }
  662. }
  663. /**
  664. * Returns the visibility of details component for given item.
  665. *
  666. * @param data
  667. * the item to show details for
  668. *
  669. * @return {@code true} if details component should be visible;
  670. * {@code false} if it should be hidden
  671. */
  672. public boolean isDetailsVisible(T data) {
  673. return visibleDetails.contains(data);
  674. }
  675. @Override
  676. public Grid<T> getParent() {
  677. return super.getParent();
  678. }
  679. @Override
  680. protected DetailsManagerState getState() {
  681. return (DetailsManagerState) super.getState();
  682. }
  683. @Override
  684. protected DetailsManagerState getState(boolean markAsDirty) {
  685. return (DetailsManagerState) super.getState(markAsDirty);
  686. }
  687. }
  688. /**
  689. * This extension manages the configuration and data communication for a
  690. * Column inside of a Grid component.
  691. *
  692. * @param <T>
  693. * the grid bean type
  694. * @param <V>
  695. * the column value type
  696. */
  697. public static class Column<T, V> extends AbstractGridExtension<T> {
  698. private final SerializableFunction<T, ? extends V> valueProvider;
  699. private SortOrderProvider sortOrderProvider;
  700. private SerializableComparator<T> comparator;
  701. private StyleGenerator<T> styleGenerator = item -> null;
  702. private DescriptionGenerator<T> descriptionGenerator;
  703. private EditorComponentGenerator<T> componentGenerator;
  704. private String userId;
  705. /**
  706. * Constructs a new Column configuration with given header caption,
  707. * renderer and value provider.
  708. *
  709. * @param valueProvider
  710. * the function to get values from items
  711. * @param renderer
  712. * the type of value
  713. */
  714. protected Column(String caption,
  715. ValueProvider<T, ? extends V> valueProvider,
  716. Renderer<V> renderer) {
  717. Objects.requireNonNull(valueProvider,
  718. "Value provider can't be null");
  719. Objects.requireNonNull(renderer, "Renderer can't be null");
  720. ColumnState state = getState();
  721. this.valueProvider = valueProvider;
  722. state.renderer = renderer;
  723. state.caption = "";
  724. sortOrderProvider = d -> Stream.of();
  725. // Add the renderer as a child extension of this extension, thus
  726. // ensuring the renderer will be unregistered when this column is
  727. // removed
  728. addExtension(renderer);
  729. Class<V> valueType = renderer.getPresentationType();
  730. if (Comparable.class.isAssignableFrom(valueType)) {
  731. comparator = (a, b) -> {
  732. @SuppressWarnings("unchecked")
  733. Comparable<V> comp = (Comparable<V>) valueProvider.apply(a);
  734. return comp.compareTo(valueProvider.apply(b));
  735. };
  736. state.sortable = true;
  737. } else if (Number.class.isAssignableFrom(valueType)) {
  738. /*
  739. * Value type will be Number whenever using NumberRenderer.
  740. * Provide explicit comparison support in this case even though
  741. * Number itself isn't Comparable.
  742. */
  743. comparator = (a, b) -> {
  744. return compareNumbers((Number) valueProvider.apply(a),
  745. (Number) valueProvider.apply(b));
  746. };
  747. state.sortable = true;
  748. } else {
  749. state.sortable = false;
  750. }
  751. }
  752. @SuppressWarnings("unchecked")
  753. private static int compareNumbers(Number a, Number b) {
  754. assert a.getClass() == b.getClass();
  755. // Most Number implementations are Comparable
  756. if (a instanceof Comparable && a.getClass().isInstance(b)) {
  757. return ((Comparable<Number>) a).compareTo(b);
  758. } else if (a.equals(b)) {
  759. return 0;
  760. } else {
  761. // Fall back to comparing based on potentially truncated values
  762. int compare = Long.compare(a.longValue(), b.longValue());
  763. if (compare == 0) {
  764. // This might still produce 0 even though the values are not
  765. // equals, but there's nothing more we can do about that
  766. compare = Double.compare(a.doubleValue(), b.doubleValue());
  767. }
  768. return compare;
  769. }
  770. }
  771. @Override
  772. public void generateData(T data, JsonObject jsonObject) {
  773. ColumnState state = getState(false);
  774. String communicationId = getConnectorId();
  775. assert communicationId != null : "No communication ID set for column "
  776. + state.caption;
  777. @SuppressWarnings("unchecked")
  778. Renderer<V> renderer = (Renderer<V>) state.renderer;
  779. JsonObject obj = getDataObject(jsonObject,
  780. DataCommunicatorConstants.DATA);
  781. V providerValue = valueProvider.apply(data);
  782. JsonValue rendererValue = renderer.encode(providerValue);
  783. obj.put(communicationId, rendererValue);
  784. String style = styleGenerator.apply(data);
  785. if (style != null && !style.isEmpty()) {
  786. JsonObject styleObj = getDataObject(jsonObject,
  787. GridState.JSONKEY_CELLSTYLES);
  788. styleObj.put(communicationId, style);
  789. }
  790. if (descriptionGenerator != null) {
  791. String description = descriptionGenerator.apply(data);
  792. if (description != null && !description.isEmpty()) {
  793. JsonObject descriptionObj = getDataObject(jsonObject,
  794. GridState.JSONKEY_CELLDESCRIPTION);
  795. descriptionObj.put(communicationId, description);
  796. }
  797. }
  798. }
  799. /**
  800. * Gets a data object with the given key from the given JsonObject. If
  801. * there is no object with the key, this method creates a new
  802. * JsonObject.
  803. *
  804. * @param jsonObject
  805. * the json object
  806. * @param key
  807. * the key where the desired data object is stored
  808. * @return data object for the given key
  809. */
  810. private JsonObject getDataObject(JsonObject jsonObject, String key) {
  811. if (!jsonObject.hasKey(key)) {
  812. jsonObject.put(key, Json.createObject());
  813. }
  814. return jsonObject.getObject(key);
  815. }
  816. @Override
  817. protected ColumnState getState() {
  818. return getState(true);
  819. }
  820. @Override
  821. protected ColumnState getState(boolean markAsDirty) {
  822. return (ColumnState) super.getState(markAsDirty);
  823. }
  824. /**
  825. * This method extends the given Grid with this Column.
  826. *
  827. * @param grid
  828. * the grid to extend
  829. */
  830. private void extend(Grid<T> grid) {
  831. super.extend(grid);
  832. }
  833. /**
  834. * Returns the identifier used with this Column in communication.
  835. *
  836. * @return the identifier string
  837. */
  838. private String getInternalId() {
  839. return getState(false).internalId;
  840. }
  841. /**
  842. * Sets the identifier to use with this Column in communication.
  843. *
  844. * @param id
  845. * the identifier string
  846. */
  847. private void setInternalId(String id) {
  848. Objects.requireNonNull(id, "Communication ID can't be null");
  849. getState().internalId = id;
  850. }
  851. /**
  852. * Returns the user-defined identifier for this column.
  853. *
  854. * @return the identifier string
  855. */
  856. public String getId() {
  857. return userId;
  858. }
  859. /**
  860. * Sets the user-defined identifier to map this column. The identifier
  861. * can be used for example in {@link Grid#getColumn(String)}.
  862. *
  863. * @param id
  864. * the identifier string
  865. */
  866. public Column<T, V> setId(String id) {
  867. Objects.requireNonNull(id, "Column identifier cannot be null");
  868. if (this.userId != null) {
  869. throw new IllegalStateException(
  870. "Column identifier cannot be changed");
  871. }
  872. this.userId = id;
  873. getParent().setColumnId(id, this);
  874. return this;
  875. }
  876. /**
  877. * Sets whether the user can sort this column or not.
  878. *
  879. * @param sortable
  880. * {@code true} if the column can be sorted by the user;
  881. * {@code false} if not
  882. * @return this column
  883. */
  884. public Column<T, V> setSortable(boolean sortable) {
  885. getState().sortable = sortable;
  886. return this;
  887. }
  888. /**
  889. * Gets whether the user can sort this column or not.
  890. *
  891. * @return {@code true} if the column can be sorted by the user;
  892. * {@code false} if not
  893. */
  894. public boolean isSortable() {
  895. return getState(false).sortable;
  896. }
  897. /**
  898. * Sets the header caption for this column.
  899. *
  900. * @param caption
  901. * the header caption, not null
  902. *
  903. * @return this column
  904. */
  905. public Column<T, V> setCaption(String caption) {
  906. Objects.requireNonNull(caption, "Header caption can't be null");
  907. getState().caption = caption;
  908. HeaderRow row = getParent().getDefaultHeaderRow();
  909. if (row != null) {
  910. row.getCell(this).setText(caption);
  911. }
  912. return this;
  913. }
  914. /**
  915. * Gets the header caption for this column.
  916. *
  917. * @return header caption
  918. */
  919. public String getCaption() {
  920. return getState(false).caption;
  921. }
  922. /**
  923. * Sets a comparator to use with in-memory sorting with this column.
  924. * Sorting with a back-end is done using
  925. * {@link Column#setSortProperty(String...)}.
  926. *
  927. * @param comparator
  928. * the comparator to use when sorting data in this column
  929. * @return this column
  930. */
  931. public Column<T, V> setComparator(
  932. SerializableComparator<T> comparator) {
  933. Objects.requireNonNull(comparator, "Comparator can't be null");
  934. this.comparator = comparator;
  935. return this;
  936. }
  937. /**
  938. * Gets the comparator to use with in-memory sorting for this column
  939. * when sorting in the given direction.
  940. *
  941. * @param sortDirection
  942. * the direction this column is sorted by
  943. * @return comparator for this column
  944. */
  945. public SerializableComparator<T> getComparator(
  946. SortDirection sortDirection) {
  947. Objects.requireNonNull(comparator,
  948. "No comparator defined for sorted column.");
  949. boolean reverse = sortDirection != SortDirection.ASCENDING;
  950. return reverse ? (t1, t2) -> comparator.reversed().compare(t1, t2)
  951. : comparator;
  952. }
  953. /**
  954. * Sets strings describing back end properties to be used when sorting
  955. * this column. This method is a short hand for
  956. * {@link #setSortBuilder(Function)} that takes an array of strings and
  957. * uses the same sorting direction for all of them.
  958. *
  959. * @param properties
  960. * the array of strings describing backend properties
  961. * @return this column
  962. */
  963. public Column<T, V> setSortProperty(String... properties) {
  964. Objects.requireNonNull(properties, "Sort properties can't be null");
  965. sortOrderProvider = dir -> Arrays.stream(properties)
  966. .map(s -> new SortOrder<>(s, dir));
  967. return this;
  968. }
  969. /**
  970. * Sets the sort orders when sorting this column. The sort order
  971. * provider is a function which provides {@link SortOrder} objects to
  972. * describe how to sort by this column.
  973. *
  974. * @param provider
  975. * the function to use when generating sort orders with the
  976. * given direction
  977. * @return this column
  978. */
  979. public Column<T, V> setSortOrderProvider(SortOrderProvider provider) {
  980. Objects.requireNonNull(provider,
  981. "Sort order provider can't be null");
  982. sortOrderProvider = provider;
  983. return this;
  984. }
  985. /**
  986. * Gets the sort orders to use with back-end sorting for this column
  987. * when sorting in the given direction.
  988. *
  989. * @param direction
  990. * the sorting direction
  991. * @return stream of sort orders
  992. */
  993. public Stream<SortOrder<String>> getSortOrder(SortDirection direction) {
  994. return sortOrderProvider.apply(direction);
  995. }
  996. /**
  997. * Sets the style generator that is used for generating class names for
  998. * cells in this column. Returning null from the generator results in no
  999. * custom style name being set.
  1000. *
  1001. * @param cellStyleGenerator
  1002. * the cell style generator to set, not null
  1003. * @return this column
  1004. * @throws NullPointerException
  1005. * if {@code cellStyleGenerator} is {@code null}
  1006. */
  1007. public Column<T, V> setStyleGenerator(
  1008. StyleGenerator<T> cellStyleGenerator) {
  1009. Objects.requireNonNull(cellStyleGenerator,
  1010. "Cell style generator must not be null");
  1011. this.styleGenerator = cellStyleGenerator;
  1012. getParent().getDataCommunicator().reset();
  1013. return this;
  1014. }
  1015. /**
  1016. * Gets the style generator that is used for generating styles for
  1017. * cells.
  1018. *
  1019. * @return the cell style generator
  1020. */
  1021. public StyleGenerator<T> getStyleGenerator() {
  1022. return styleGenerator;
  1023. }
  1024. /**
  1025. * Sets the description generator that is used for generating
  1026. * descriptions for cells in this column.
  1027. *
  1028. * @param cellDescriptionGenerator
  1029. * the cell description generator to set, or
  1030. * <code>null</code> to remove a previously set generator
  1031. * @return this column
  1032. */
  1033. public Column<T, V> setDescriptionGenerator(
  1034. DescriptionGenerator<T> cellDescriptionGenerator) {
  1035. this.descriptionGenerator = cellDescriptionGenerator;
  1036. getParent().getDataCommunicator().reset();
  1037. return this;
  1038. }
  1039. /**
  1040. * Gets the description generator that is used for generating
  1041. * descriptions for cells.
  1042. *
  1043. * @return the cell description generator, or <code>null</code> if no
  1044. * generator is set
  1045. */
  1046. public DescriptionGenerator<T> getDescriptionGenerator() {
  1047. return descriptionGenerator;
  1048. }
  1049. /**
  1050. * Sets the ratio with which the column expands.
  1051. * <p>
  1052. * By default, all columns expand equally (treated as if all of them had
  1053. * an expand ratio of 1). Once at least one column gets a defined expand
  1054. * ratio, the implicit expand ratio is removed, and only the defined
  1055. * expand ratios are taken into account.
  1056. * <p>
  1057. * If a column has a defined width ({@link #setWidth(double)}), it
  1058. * overrides this method's effects.
  1059. * <p>
  1060. * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
  1061. * and 2, respectively. The column with a <strong>ratio of 0 is exactly
  1062. * as wide as its contents requires</strong>. The column with a ratio of
  1063. * 1 is as wide as it needs, <strong>plus a third of any excess
  1064. * space</strong>, because we have 3 parts total, and this column
  1065. * reserves only one of those. The column with a ratio of 2, is as wide
  1066. * as it needs to be, <strong>plus two thirds</strong> of the excess
  1067. * width.
  1068. *
  1069. * @param expandRatio
  1070. * the expand ratio of this column. {@code 0} to not have it
  1071. * expand at all. A negative number to clear the expand
  1072. * value.
  1073. * @throws IllegalStateException
  1074. * if the column is no longer attached to any grid
  1075. * @see #setWidth(double)
  1076. */
  1077. public Column<T, V> setExpandRatio(int expandRatio)
  1078. throws IllegalStateException {
  1079. checkColumnIsAttached();
  1080. if (expandRatio != getExpandRatio()) {
  1081. getState().expandRatio = expandRatio;
  1082. getParent().markAsDirty();
  1083. }
  1084. return this;
  1085. }
  1086. /**
  1087. * Returns the column's expand ratio.
  1088. *
  1089. * @return the column's expand ratio
  1090. * @see #setExpandRatio(int)
  1091. */
  1092. public int getExpandRatio() {
  1093. return getState(false).expandRatio;
  1094. }
  1095. /**
  1096. * Clears the expand ratio for this column.
  1097. * <p>
  1098. * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
  1099. *
  1100. * @throws IllegalStateException
  1101. * if the column is no longer attached to any grid
  1102. */
  1103. public Column<T, V> clearExpandRatio() throws IllegalStateException {
  1104. return setExpandRatio(-1);
  1105. }
  1106. /**
  1107. * Returns the width (in pixels). By default a column is 100px wide.
  1108. *
  1109. * @return the width in pixels of the column
  1110. * @throws IllegalStateException
  1111. * if the column is no longer attached to any grid
  1112. */
  1113. public double getWidth() throws IllegalStateException {
  1114. checkColumnIsAttached();
  1115. return getState(false).width;
  1116. }
  1117. /**
  1118. * Sets the width (in pixels).
  1119. * <p>
  1120. * This overrides any configuration set by any of
  1121. * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
  1122. * {@link #setMaximumWidth(double)}.
  1123. *
  1124. * @param pixelWidth
  1125. * the new pixel width of the column
  1126. * @return the column itself
  1127. *
  1128. * @throws IllegalStateException
  1129. * if the column is no longer attached to any grid
  1130. * @throws IllegalArgumentException
  1131. * thrown if pixel width is less than zero
  1132. */
  1133. public Column<T, V> setWidth(double pixelWidth)
  1134. throws IllegalStateException, IllegalArgumentException {
  1135. checkColumnIsAttached();
  1136. if (pixelWidth < 0) {
  1137. throw new IllegalArgumentException(
  1138. "Pixel width should be greated than 0 (in " + toString()
  1139. + ")");
  1140. }
  1141. if (pixelWidth != getWidth()) {
  1142. getState().width = pixelWidth;
  1143. getParent().markAsDirty();
  1144. getParent().fireColumnResizeEvent(this, false);
  1145. }
  1146. return this;
  1147. }
  1148. /**
  1149. * Returns whether this column has an undefined width.
  1150. *
  1151. * @since 7.6
  1152. * @return whether the width is undefined
  1153. * @throws IllegalStateException
  1154. * if the column is no longer attached to any grid
  1155. */
  1156. public boolean isWidthUndefined() {
  1157. checkColumnIsAttached();
  1158. return getState(false).width < 0;
  1159. }
  1160. /**
  1161. * Marks the column width as undefined. An undefined width means the
  1162. * grid is free to resize the column based on the cell contents and
  1163. * available space in the grid.
  1164. *
  1165. * @return the column itself
  1166. */
  1167. public Column<T, V> setWidthUndefined() {
  1168. checkColumnIsAttached();
  1169. if (!isWidthUndefined()) {
  1170. getState().width = -1;
  1171. getParent().markAsDirty();
  1172. getParent().fireColumnResizeEvent(this, false);
  1173. }
  1174. return this;
  1175. }
  1176. /**
  1177. * Sets the minimum width for this column.
  1178. * <p>
  1179. * This defines the minimum guaranteed pixel width of the column
  1180. * <em>when it is set to expand</em>.
  1181. *
  1182. * @throws IllegalStateException
  1183. * if the column is no longer attached to any grid
  1184. * @see #setExpandRatio(int)
  1185. */
  1186. public Column<T, V> setMinimumWidth(double pixels)
  1187. throws IllegalStateException {
  1188. checkColumnIsAttached();
  1189. final double maxwidth = getMaximumWidth();
  1190. if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
  1191. throw new IllegalArgumentException("New minimum width ("
  1192. + pixels + ") was greater than maximum width ("
  1193. + maxwidth + ")");
  1194. }
  1195. getState().minWidth = pixels;
  1196. getParent().markAsDirty();
  1197. return this;
  1198. }
  1199. /**
  1200. * Return the minimum width for this column.
  1201. *
  1202. * @return the minimum width for this column
  1203. * @see #setMinimumWidth(double)
  1204. */
  1205. public double getMinimumWidth() {
  1206. return getState(false).minWidth;
  1207. }
  1208. /**
  1209. * Sets the maximum width for this column.
  1210. * <p>
  1211. * This defines the maximum allowed pixel width of the column <em>when
  1212. * it is set to expand</em>.
  1213. *
  1214. * @param pixels
  1215. * the maximum width
  1216. * @throws IllegalStateException
  1217. * if the column is no longer attached to any grid
  1218. * @see #setExpandRatio(int)
  1219. */
  1220. public Column<T, V> setMaximumWidth(double pixels) {
  1221. checkColumnIsAttached();
  1222. final double minwidth = getMinimumWidth();
  1223. if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
  1224. throw new IllegalArgumentException("New maximum width ("
  1225. + pixels + ") was less than minimum width (" + minwidth
  1226. + ")");
  1227. }
  1228. getState().maxWidth = pixels;
  1229. getParent().markAsDirty();
  1230. return this;
  1231. }
  1232. /**
  1233. * Returns the maximum width for this column.
  1234. *
  1235. * @return the maximum width for this column
  1236. * @see #setMaximumWidth(double)
  1237. */
  1238. public double getMaximumWidth() {
  1239. return getState(false).maxWidth;
  1240. }
  1241. /**
  1242. * Sets whether this column can be resized by the user.
  1243. *
  1244. * @since 7.6
  1245. * @param resizable
  1246. * {@code true} if this column should be resizable,
  1247. * {@code false} otherwise
  1248. * @throws IllegalStateException
  1249. * if the column is no longer attached to any grid
  1250. */
  1251. public Column<T, V> setResizable(boolean resizable) {
  1252. checkColumnIsAttached();
  1253. if (resizable != isResizable()) {
  1254. getState().resizable = resizable;
  1255. getParent().markAsDirty();
  1256. }
  1257. return this;
  1258. }
  1259. /**
  1260. * Gets the caption of the hiding toggle for this column.
  1261. *
  1262. * @since 7.5.0
  1263. * @see #setHidingToggleCaption(String)
  1264. * @return the caption for the hiding toggle for this column
  1265. */
  1266. public String getHidingToggleCaption() {
  1267. return getState(false).hidingToggleCaption;
  1268. }
  1269. /**
  1270. * Sets the caption of the hiding toggle for this column. Shown in the
  1271. * toggle for this column in the grid's sidebar when the column is
  1272. * {@link #isHidable() hidable}.
  1273. * <p>
  1274. * The default value is <code>null</code>, and in that case the column's
  1275. * {@link #getHeaderCaption() header caption} is used.
  1276. * <p>
  1277. * <em>NOTE:</em> setting this to empty string might cause the hiding
  1278. * toggle to not render correctly.
  1279. *
  1280. * @since 7.5.0
  1281. * @param hidingToggleCaption
  1282. * the text to show in the column hiding toggle
  1283. * @return the column itself
  1284. */
  1285. public Column<T, V> setHidingToggleCaption(String hidingToggleCaption) {
  1286. if (hidingToggleCaption != getHidingToggleCaption()) {
  1287. getState().hidingToggleCaption = hidingToggleCaption;
  1288. }
  1289. return this;
  1290. }
  1291. /**
  1292. * Hides or shows the column. By default columns are visible before
  1293. * explicitly hiding them.
  1294. *
  1295. * @since 7.5.0
  1296. * @param hidden
  1297. * <code>true</code> to hide the column, <code>false</code>
  1298. * to show
  1299. * @return this column
  1300. * @throws IllegalStateException
  1301. * if the column is no longer attached to any grid
  1302. */
  1303. public Column<T, V> setHidden(boolean hidden) {
  1304. checkColumnIsAttached();
  1305. if (hidden != isHidden()) {
  1306. getState().hidden = hidden;
  1307. getParent().fireColumnVisibilityChangeEvent(this, hidden,
  1308. false);
  1309. }
  1310. return this;
  1311. }
  1312. /**
  1313. * Returns whether this column is hidden. Default is {@code false}.
  1314. *
  1315. * @since 7.5.0
  1316. * @return <code>true</code> if the column is currently hidden,
  1317. * <code>false</code> otherwise
  1318. */
  1319. public boolean isHidden() {
  1320. return getState(false).hidden;
  1321. }
  1322. /**
  1323. * Sets whether this column can be hidden by the user. Hidable columns
  1324. * can be hidden and shown via the sidebar menu.
  1325. *
  1326. * @since 7.5.0
  1327. * @param hidable
  1328. * <code>true</code> iff the column may be hidable by the
  1329. * user via UI interaction
  1330. * @return this column
  1331. */
  1332. public Column<T, V> setHidable(boolean hidable) {
  1333. if (hidable != isHidable()) {
  1334. getState().hidable = hidable;
  1335. }
  1336. return this;
  1337. }
  1338. /**
  1339. * Returns whether this column can be hidden by the user. Default is
  1340. * {@code false}.
  1341. * <p>
  1342. * <em>Note:</em> the column can be programmatically hidden using
  1343. * {@link #setHidden(boolean)} regardless of the returned value.
  1344. *
  1345. * @since 7.5.0
  1346. * @return <code>true</code> if the user can hide the column,
  1347. * <code>false</code> if not
  1348. */
  1349. public boolean isHidable() {
  1350. return getState(false).hidable;
  1351. }
  1352. /**
  1353. * Returns whether this column can be resized by the user. Default is
  1354. * {@code true}.
  1355. * <p>
  1356. * <em>Note:</em> the column can be programmatically resized using
  1357. * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
  1358. * of the returned value.
  1359. *
  1360. * @since 7.6
  1361. * @return {@code true} if this column is resizable, {@code false}
  1362. * otherwise
  1363. */
  1364. public boolean isResizable() {
  1365. return getState(false).resizable;
  1366. }
  1367. /**
  1368. * Sets whether this Column has a component displayed in Editor or not.
  1369. *
  1370. * @param editable
  1371. * {@code true} if column is editable; {@code false} if not
  1372. * @return this column
  1373. *
  1374. * @see #setEditorComponent(Component)
  1375. * @see #setEditorComponentGenerator(EditorComponentGenerator)
  1376. */
  1377. public Column<T, V> setEditable(boolean editable) {
  1378. Objects.requireNonNull(componentGenerator,
  1379. "Column has no editor component defined");
  1380. getState().editable = editable;
  1381. return this;
  1382. }
  1383. /**
  1384. * Gets whether this Column has a component displayed in Editor or not.
  1385. *
  1386. * @return {@code true} if the column displays an editor component;
  1387. * {@code false} if not
  1388. */
  1389. public boolean isEditable() {
  1390. return getState(false).editable;
  1391. }
  1392. /**
  1393. * Sets a static editor component for this column.
  1394. * <p>
  1395. * <strong>Note:</strong> The same component cannot be used for multiple
  1396. * columns.
  1397. *
  1398. * @param component
  1399. * the editor component
  1400. * @return this column
  1401. *
  1402. * @see Editor#getBinder()
  1403. * @see Editor#setBinder(Binder)
  1404. * @see #setEditorComponentGenerator(EditorComponentGenerator)
  1405. */
  1406. public Column<T, V> setEditorComponent(Component component) {
  1407. Objects.requireNonNull(component,
  1408. "null is not a valid editor field");
  1409. return setEditorComponentGenerator(t -> component);
  1410. }
  1411. /**
  1412. * Sets a component generator to provide an editor component for this
  1413. * Column. This method can be used to generate any dynamic component to
  1414. * be displayed in the editor row.
  1415. * <p>
  1416. * <strong>Note:</strong> The same component cannot be used for multiple
  1417. * columns.
  1418. *
  1419. * @param componentGenerator
  1420. * the editor component generator
  1421. * @return this column
  1422. *
  1423. * @see EditorComponentGenerator
  1424. * @see #setEditorComponent(Component)
  1425. */
  1426. public Column<T, V> setEditorComponentGenerator(
  1427. EditorComponentGenerator<T> componentGenerator) {
  1428. Objects.requireNonNull(componentGenerator);
  1429. this.componentGenerator = componentGenerator;
  1430. return setEditable(true);
  1431. }
  1432. /**
  1433. * Gets the editor component generator for this Column.
  1434. *
  1435. * @return editor component generator
  1436. *
  1437. * @see EditorComponentGenerator
  1438. */
  1439. public EditorComponentGenerator<T> getEditorComponentGenerator() {
  1440. return componentGenerator;
  1441. }
  1442. /**
  1443. * Checks if column is attached and throws an
  1444. * {@link IllegalStateException} if it is not.
  1445. *
  1446. * @throws IllegalStateException
  1447. * if the column is no longer attached to any grid
  1448. */
  1449. protected void checkColumnIsAttached() throws IllegalStateException {
  1450. if (getParent() == null) {
  1451. throw new IllegalStateException(
  1452. "Column is no longer attached to a grid.");
  1453. }
  1454. }
  1455. /**
  1456. * Writes the design attributes for this column into given element.
  1457. *
  1458. * @since 7.5.0
  1459. *
  1460. * @param element
  1461. * Element to write attributes into
  1462. *
  1463. * @param designContext
  1464. * the design context
  1465. */
  1466. protected void writeDesign(Element element,
  1467. DesignContext designContext) {
  1468. Attributes attributes = element.attributes();
  1469. ColumnState defaultState = new ColumnState();
  1470. if (getId() == null) {
  1471. setId("column" + getParent().getColumns().indexOf(this));
  1472. }
  1473. DesignAttributeHandler.writeAttribute("column-id", attributes,
  1474. getId(), null, String.class, designContext);
  1475. // Sortable is a special attribute that depends on the data
  1476. // provider.
  1477. DesignAttributeHandler.writeAttribute("sortable", attributes,
  1478. isSortable(), null, boolean.class, designContext);
  1479. DesignAttributeHandler.writeAttribute("editable", attributes,
  1480. isEditable(), defaultState.editable, boolean.class,
  1481. designContext);
  1482. DesignAttributeHandler.writeAttribute("resizable", attributes,
  1483. isResizable(), defaultState.resizable, boolean.class,
  1484. designContext);
  1485. DesignAttributeHandler.writeAttribute("hidable", attributes,
  1486. isHidable(), defaultState.hidable, boolean.class,
  1487. designContext);
  1488. DesignAttributeHandler.writeAttribute("hidden", attributes,
  1489. isHidden(), defaultState.hidden, boolean.class,
  1490. designContext);
  1491. DesignAttributeHandler.writeAttribute("hiding-toggle-caption",
  1492. attributes, getHidingToggleCaption(),
  1493. defaultState.hidingToggleCaption, String.class,
  1494. designContext);
  1495. DesignAttributeHandler.writeAttribute("width", attributes,
  1496. getWidth(), defaultState.width, Double.class,
  1497. designContext);
  1498. DesignAttributeHandler.writeAttribute("min-width", attributes,
  1499. getMinimumWidth(), defaultState.minWidth, Double.class,
  1500. designContext);
  1501. DesignAttributeHandler.writeAttribute("max-width", attributes,
  1502. getMaximumWidth(), defaultState.maxWidth, Double.class,
  1503. designContext);
  1504. DesignAttributeHandler.writeAttribute("expand", attributes,
  1505. getExpandRatio(), defaultState.expandRatio, Integer.class,
  1506. designContext);
  1507. }
  1508. /**
  1509. * Reads the design attributes for this column from given element.
  1510. *
  1511. * @since 7.5.0
  1512. * @param design
  1513. * Element to read attributes from
  1514. * @param designContext
  1515. * the design context
  1516. */
  1517. protected void readDesign(Element design, DesignContext designContext) {
  1518. Attributes attributes = design.attributes();
  1519. if (design.hasAttr("sortable")) {
  1520. setSortable(DesignAttributeHandler.readAttribute("sortable",
  1521. attributes, boolean.class));
  1522. } else {
  1523. setSortable(false);
  1524. }
  1525. if (design.hasAttr("editable")) {
  1526. /*
  1527. * This is a fake editor just to have something (otherwise
  1528. * "setEditable" throws an exception.
  1529. *
  1530. * Let's use TextField here because we support only Strings as
  1531. * inline data type. It will work incorrectly for other types
  1532. * but we don't support them anyway.
  1533. */
  1534. setEditorComponentGenerator(item -> new TextField(
  1535. Optional.ofNullable(valueProvider.apply(item))
  1536. .map(Object::toString).orElse("")));
  1537. setEditable(DesignAttributeHandler.readAttribute("editable",
  1538. attributes, boolean.class));
  1539. }
  1540. if (design.hasAttr("resizable")) {
  1541. setResizable(DesignAttributeHandler.readAttribute("resizable",
  1542. attributes, boolean.class));
  1543. }
  1544. if (design.hasAttr("hidable")) {
  1545. setHidable(DesignAttributeHandler.readAttribute("hidable",
  1546. attributes, boolean.class));
  1547. }
  1548. if (design.hasAttr("hidden")) {
  1549. setHidden(DesignAttributeHandler.readAttribute("hidden",
  1550. attributes, boolean.class));
  1551. }
  1552. if (design.hasAttr("hiding-toggle-caption")) {
  1553. setHidingToggleCaption(DesignAttributeHandler.readAttribute(
  1554. "hiding-toggle-caption", attributes, String.class));
  1555. }
  1556. // Read size info where necessary.
  1557. if (design.hasAttr("width")) {
  1558. setWidth(DesignAttributeHandler.readAttribute("width",
  1559. attributes, Double.class));
  1560. }
  1561. if (design.hasAttr("min-width")) {
  1562. setMinimumWidth(DesignAttributeHandler
  1563. .readAttribute("min-width", attributes, Double.class));
  1564. }
  1565. if (design.hasAttr("max-width")) {
  1566. setMaximumWidth(DesignAttributeHandler
  1567. .readAttribute("max-width", attributes, Double.class));
  1568. }
  1569. if (design.hasAttr("expand")) {
  1570. if (design.attr("expand").isEmpty()) {
  1571. setExpandRatio(1);
  1572. } else {
  1573. setExpandRatio(DesignAttributeHandler.readAttribute(
  1574. "expand", attributes, Integer.class));
  1575. }
  1576. }
  1577. }
  1578. }
  1579. private class HeaderImpl extends Header {
  1580. @Override
  1581. protected Grid<T> getGrid() {
  1582. return Grid.this;
  1583. }
  1584. @Override
  1585. protected SectionState getState(boolean markAsDirty) {
  1586. return Grid.this.getState(markAsDirty).header;
  1587. }
  1588. @Override
  1589. protected Column<?, ?> getColumnByInternalId(String internalId) {
  1590. return getGrid().getColumnByInternalId(internalId);
  1591. }
  1592. @Override
  1593. @SuppressWarnings("unchecked")
  1594. protected String getInternalIdForColumn(Column<?, ?> column) {
  1595. return getGrid().getInternalIdForColumn((Column<T, ?>) column);
  1596. }
  1597. };
  1598. private class FooterImpl extends Footer {
  1599. @Override
  1600. protected Grid<T> getGrid() {
  1601. return Grid.this;
  1602. }
  1603. @Override
  1604. protected SectionState getState(boolean markAsDirty) {
  1605. return Grid.this.getState(markAsDirty).footer;
  1606. }
  1607. @Override
  1608. protected Column<?, ?> getColumnByInternalId(String internalId) {
  1609. return getGrid().getColumnByInternalId(internalId);
  1610. }
  1611. @Override
  1612. @SuppressWarnings("unchecked")
  1613. protected String getInternalIdForColumn(Column<?, ?> column) {
  1614. return getGrid().getInternalIdForColumn((Column<T, ?>) column);
  1615. }
  1616. };
  1617. private final Set<Column<T, ?>> columnSet = new LinkedHashSet<>();
  1618. private final Map<String, Column<T, ?>> columnKeys = new HashMap<>();
  1619. private final Map<String, Column<T, ?>> columnIds = new HashMap<>();
  1620. private final List<SortOrder<Column<T, ?>>> sortOrder = new ArrayList<>();
  1621. private final DetailsManager<T> detailsManager;
  1622. private final Set<Component> extensionComponents = new HashSet<>();
  1623. private StyleGenerator<T> styleGenerator = item -> null;
  1624. private DescriptionGenerator<T> descriptionGenerator;
  1625. private final Header header = new HeaderImpl();
  1626. private final Footer footer = new FooterImpl();
  1627. private int counter = 0;
  1628. private GridSelectionModel<T> selectionModel;
  1629. private Editor<T> editor;
  1630. /**
  1631. * Constructor for the {@link Grid} component.
  1632. */
  1633. public Grid() {
  1634. registerRpc(new GridServerRpcImpl());
  1635. setDefaultHeaderRow(appendHeaderRow());
  1636. setSelectionModel(new SingleSelectionModelImpl<>());
  1637. detailsManager = new DetailsManager<>();
  1638. addExtension(detailsManager);
  1639. addDataGenerator(detailsManager);
  1640. editor = createEditor();
  1641. if (editor instanceof Extension) {
  1642. addExtension((Extension) editor);
  1643. }
  1644. addDataGenerator((item, json) -> {
  1645. String styleName = styleGenerator.apply(item);
  1646. if (styleName != null && !styleName.isEmpty()) {
  1647. json.put(GridState.JSONKEY_ROWSTYLE, styleName);
  1648. }
  1649. if (descriptionGenerator != null) {
  1650. String description = descriptionGenerator.apply(item);
  1651. if (description != null && !description.isEmpty()) {
  1652. json.put(GridState.JSONKEY_ROWDESCRIPTION, description);
  1653. }
  1654. }
  1655. });
  1656. }
  1657. public <V> void fireColumnVisibilityChangeEvent(Column<T, V> column,
  1658. boolean hidden, boolean userOriginated) {
  1659. fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden,
  1660. userOriginated));
  1661. }
  1662. /**
  1663. * Adds a new text column to this {@link Grid} with a value provider. The
  1664. * column will use a {@link TextRenderer}. The value is converted to a
  1665. * String using {@link Object#toString()}. Sorting in memory is executed by
  1666. * comparing the String values.
  1667. *
  1668. * @param valueProvider
  1669. * the value provider
  1670. *
  1671. * @return the new column
  1672. */
  1673. public Column<T, String> addColumn(ValueProvider<T, String> valueProvider) {
  1674. return addColumn(t -> String.valueOf(valueProvider.apply(t)),
  1675. new TextRenderer());
  1676. }
  1677. /**
  1678. * Adds a new column to this {@link Grid} with typed renderer and value
  1679. * provider.
  1680. *
  1681. * @param valueProvider
  1682. * the value provider
  1683. * @param renderer
  1684. * the column value class
  1685. * @param <V>
  1686. * the column value type
  1687. *
  1688. * @return the new column
  1689. *
  1690. * @see AbstractRenderer
  1691. */
  1692. public <V> Column<T, V> addColumn(
  1693. ValueProvider<T, ? extends V> valueProvider,
  1694. AbstractRenderer<? super T, V> renderer) {
  1695. String generatedIdentifier = getGeneratedIdentifier();
  1696. Column<T, V> column = new Column<>("Column " + generatedIdentifier,
  1697. valueProvider, renderer);
  1698. addColumn(generatedIdentifier, column);
  1699. return column;
  1700. }
  1701. private void addColumn(String identifier, Column<T, ?> column) {
  1702. if (getColumns().contains(column)) {
  1703. return;
  1704. }
  1705. column.extend(this);
  1706. columnSet.add(column);
  1707. columnKeys.put(identifier, column);
  1708. column.setInternalId(identifier);
  1709. addDataGenerator(column);
  1710. getState().columnOrder.add(identifier);
  1711. getHeader().addColumn(identifier);
  1712. if (getDefaultHeaderRow() != null) {
  1713. getDefaultHeaderRow().getCell(column).setText(column.getCaption());
  1714. }
  1715. }
  1716. /**
  1717. * Removes the given column from this {@link Grid}.
  1718. *
  1719. * @param column
  1720. * the column to remove
  1721. */
  1722. public void removeColumn(Column<T, ?> column) {
  1723. if (columnSet.remove(column)) {
  1724. String columnId = column.getInternalId();
  1725. columnKeys.remove(columnId);
  1726. columnIds.remove(column.getId());
  1727. column.remove();
  1728. getHeader().removeColumn(columnId);
  1729. getFooter().removeColumn(columnId);
  1730. getState(true).columnOrder.remove(columnId);
  1731. }
  1732. }
  1733. /**
  1734. * Sets the details component generator.
  1735. *
  1736. * @param generator
  1737. * the generator for details components
  1738. */
  1739. public void setDetailsGenerator(DetailsGenerator<T> generator) {
  1740. this.detailsManager.setDetailsGenerator(generator);
  1741. }
  1742. /**
  1743. * Sets the visibility of details component for given item.
  1744. *
  1745. * @param data
  1746. * the item to show details for
  1747. * @param visible
  1748. * {@code true} if details component should be visible;
  1749. * {@code false} if it should be hidden
  1750. */
  1751. public void setDetailsVisible(T data, boolean visible) {
  1752. detailsManager.setDetailsVisible(data, visible);
  1753. }
  1754. /**
  1755. * Returns the visibility of details component for given item.
  1756. *
  1757. * @param data
  1758. * the item to show details for
  1759. *
  1760. * @return {@code true} if details component should be visible;
  1761. * {@code false} if it should be hidden
  1762. */
  1763. public boolean isDetailsVisible(T data) {
  1764. return detailsManager.isDetailsVisible(data);
  1765. }
  1766. /**
  1767. * Gets an unmodifiable collection of all columns currently in this
  1768. * {@link Grid}.
  1769. *
  1770. * @return unmodifiable collection of columns
  1771. */
  1772. public List<Column<T, ?>> getColumns() {
  1773. return Collections.unmodifiableList(getState(false).columnOrder.stream()
  1774. .map(columnKeys::get).collect(Collectors.toList()));
  1775. }
  1776. /**
  1777. * Gets a {@link Column} of this grid by its identifying string.
  1778. *
  1779. * @param columnId
  1780. * the identifier of the column to get
  1781. * @return the column corresponding to the given column identifier
  1782. */
  1783. public Column<T, ?> getColumn(String columnId) {
  1784. return columnIds.get(columnId);
  1785. }
  1786. @Override
  1787. public Iterator<Component> iterator() {
  1788. Set<Component> componentSet = new LinkedHashSet<>(extensionComponents);
  1789. Header header = getHeader();
  1790. for (int i = 0; i < header.getRowCount(); ++i) {
  1791. HeaderRow row = header.getRow(i);
  1792. getColumns().forEach(column -> {
  1793. HeaderCell cell = row.getCell(column);
  1794. if (cell.getCellType() == GridStaticCellType.WIDGET) {
  1795. componentSet.add(cell.getComponent());
  1796. }
  1797. });
  1798. }
  1799. Footer footer = getFooter();
  1800. for (int i = 0; i < footer.getRowCount(); ++i) {
  1801. FooterRow row = footer.getRow(i);
  1802. getColumns().forEach(column -> {
  1803. FooterCell cell = row.getCell(column);
  1804. if (cell.getCellType() == GridStaticCellType.WIDGET) {
  1805. componentSet.add(cell.getComponent());
  1806. }
  1807. });
  1808. }
  1809. return Collections.unmodifiableSet(componentSet).iterator();
  1810. }
  1811. /**
  1812. * Sets the number of frozen columns in this grid. Setting the count to 0
  1813. * means that no data columns will be frozen, but the built-in selection
  1814. * checkbox column will still be frozen if it's in use. Setting the count to
  1815. * -1 will also disable the selection column.
  1816. * <p>
  1817. * The default value is 0.
  1818. *
  1819. * @param numberOfColumns
  1820. * the number of columns that should be frozen
  1821. *
  1822. * @throws IllegalArgumentException
  1823. * if the column count is less than -1 or greater than the
  1824. * number of visible columns
  1825. */
  1826. public void setFrozenColumnCount(int numberOfColumns) {
  1827. if (numberOfColumns < -1 || numberOfColumns > columnSet.size()) {
  1828. throw new IllegalArgumentException(
  1829. "count must be between -1 and the current number of columns ("
  1830. + columnSet.size() + "): " + numberOfColumns);
  1831. }
  1832. getState().frozenColumnCount = numberOfColumns;
  1833. }
  1834. /**
  1835. * Gets the number of frozen columns in this grid. 0 means that no data
  1836. * columns will be frozen, but the built-in selection checkbox column will
  1837. * still be frozen if it's in use. -1 means that not even the selection
  1838. * column is frozen.
  1839. * <p>
  1840. * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
  1841. * columns} in the count.
  1842. *
  1843. * @see #setFrozenColumnCount(int)
  1844. *
  1845. * @return the number of frozen columns
  1846. */
  1847. public int getFrozenColumnCount() {
  1848. return getState(false).frozenColumnCount;
  1849. }
  1850. /**
  1851. * Sets the number of rows that should be visible in Grid's body. This
  1852. * method will set the height mode to be {@link HeightMode#ROW}.
  1853. *
  1854. * @param rows
  1855. * The height in terms of number of rows displayed in Grid's
  1856. * body. If Grid doesn't contain enough rows, white space is
  1857. * displayed instead. If <code>null</code> is given, then Grid's
  1858. * height is undefined
  1859. * @throws IllegalArgumentException
  1860. * if {@code rows} is zero or less
  1861. * @throws IllegalArgumentException
  1862. * if {@code rows} is {@link Double#isInfinite(double) infinite}
  1863. * @throws IllegalArgumentException
  1864. * if {@code rows} is {@link Double#isNaN(double) NaN}
  1865. */
  1866. public void setHeightByRows(double rows) {
  1867. if (rows <= 0.0d) {
  1868. throw new IllegalArgumentException(
  1869. "More than zero rows must be shown.");
  1870. } else if (Double.isInfinite(rows)) {
  1871. throw new IllegalArgumentException(
  1872. "Grid doesn't support infinite heights");
  1873. } else if (Double.isNaN(rows)) {
  1874. throw new IllegalArgumentException("NaN is not a valid row count");
  1875. }
  1876. getState().heightMode = HeightMode.ROW;
  1877. getState().heightByRows = rows;
  1878. }
  1879. /**
  1880. * Gets the amount of rows in Grid's body that are shown, while
  1881. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  1882. *
  1883. * @return the amount of rows that are being shown in Grid's body
  1884. * @see #setHeightByRows(double)
  1885. */
  1886. public double getHeightByRows() {
  1887. return getState(false).heightByRows;
  1888. }
  1889. /**
  1890. * {@inheritDoc}
  1891. * <p>
  1892. * <em>Note:</em> This method will set the height mode to be
  1893. * {@link HeightMode#CSS}.
  1894. *
  1895. * @see #setHeightMode(HeightMode)
  1896. */
  1897. @Override
  1898. public void setHeight(float height, Unit unit) {
  1899. getState().heightMode = HeightMode.CSS;
  1900. super.setHeight(height, unit);
  1901. }
  1902. /**
  1903. * Defines the mode in which the Grid widget's height is calculated.
  1904. * <p>
  1905. * If {@link HeightMode#CSS} is given, Grid will respect the values given
  1906. * via a {@code setHeight}-method, and behave as a traditional Component.
  1907. * <p>
  1908. * If {@link HeightMode#ROW} is given, Grid will make sure that the body
  1909. * will display as many rows as {@link #getHeightByRows()} defines.
  1910. * <em>Note:</em> If headers/footers are inserted or removed, the widget
  1911. * will resize itself to still display the required amount of rows in its
  1912. * body. It also takes the horizontal scrollbar into account.
  1913. *
  1914. * @param heightMode
  1915. * the mode in to which Grid should be set
  1916. */
  1917. public void setHeightMode(HeightMode heightMode) {
  1918. /*
  1919. * This method is a workaround for the fact that Vaadin re-applies
  1920. * widget dimensions (height/width) on each state change event. The
  1921. * original design was to have setHeight and setHeightByRow be equals,
  1922. * and whichever was called the latest was considered in effect.
  1923. *
  1924. * But, because of Vaadin always calling setHeight on the widget, this
  1925. * approach doesn't work.
  1926. */
  1927. getState().heightMode = heightMode;
  1928. }
  1929. /**
  1930. * Returns the current {@link HeightMode} the Grid is in.
  1931. * <p>
  1932. * Defaults to {@link HeightMode#CSS}.
  1933. *
  1934. * @return the current HeightMode
  1935. */
  1936. public HeightMode getHeightMode() {
  1937. return getState(false).heightMode;
  1938. }
  1939. /**
  1940. * Sets the style generator that is used for generating class names for rows
  1941. * in this grid. Returning null from the generator results in no custom
  1942. * style name being set.
  1943. *
  1944. * @see StyleGenerator
  1945. *
  1946. * @param styleGenerator
  1947. * the row style generator to set, not null
  1948. * @throws NullPointerException
  1949. * if {@code styleGenerator} is {@code null}
  1950. */
  1951. public void setStyleGenerator(StyleGenerator<T> styleGenerator) {
  1952. Objects.requireNonNull(styleGenerator,
  1953. "Style generator must not be null");
  1954. this.styleGenerator = styleGenerator;
  1955. getDataCommunicator().reset();
  1956. }
  1957. /**
  1958. * Gets the style generator that is used for generating class names for
  1959. * rows.
  1960. *
  1961. * @see StyleGenerator
  1962. *
  1963. * @return the row style generator
  1964. */
  1965. public StyleGenerator<T> getStyleGenerator() {
  1966. return styleGenerator;
  1967. }
  1968. /**
  1969. * Sets the description generator that is used for generating descriptions
  1970. * for rows.
  1971. *
  1972. * @param descriptionGenerator
  1973. * the row description generator to set, or <code>null</code> to
  1974. * remove a previously set generator
  1975. */
  1976. public void setDescriptionGenerator(
  1977. DescriptionGenerator<T> descriptionGenerator) {
  1978. this.descriptionGenerator = descriptionGenerator;
  1979. getDataCommunicator().reset();
  1980. }
  1981. /**
  1982. * Gets the description generator that is used for generating descriptions
  1983. * for rows.
  1984. *
  1985. * @return the row description generator, or <code>null</code> if no
  1986. * generator is set
  1987. */
  1988. public DescriptionGenerator<T> getDescriptionGenerator() {
  1989. return descriptionGenerator;
  1990. }
  1991. //
  1992. // HEADER AND FOOTER
  1993. //
  1994. /**
  1995. * Returns the header row at the given index.
  1996. *
  1997. * @param index
  1998. * the index of the row, where the topmost row has index zero
  1999. * @return the header row at the index
  2000. * @throws IndexOutOfBoundsException
  2001. * if {@code rowIndex < 0 || rowIndex >= getHeaderRowCount()}
  2002. */
  2003. public HeaderRow getHeaderRow(int index) {
  2004. return getHeader().getRow(index);
  2005. }
  2006. /**
  2007. * Gets the number of rows in the header section.
  2008. *
  2009. * @return the number of header rows
  2010. */
  2011. public int getHeaderRowCount() {
  2012. return header.getRowCount();
  2013. }
  2014. /**
  2015. * Inserts a new row at the given position to the header section. Shifts the
  2016. * row currently at that position and any subsequent rows down (adds one to
  2017. * their indices). Inserting at {@link #getHeaderRowCount()} appends the row
  2018. * at the bottom of the header.
  2019. *
  2020. * @param index
  2021. * the index at which to insert the row, where the topmost row
  2022. * has index zero
  2023. * @return the inserted header row
  2024. *
  2025. * @throws IndexOutOfBoundsException
  2026. * if {@code rowIndex < 0 || rowIndex > getHeaderRowCount()}
  2027. *
  2028. * @see #appendHeaderRow()
  2029. * @see #prependHeaderRow()
  2030. * @see #removeHeaderRow(HeaderRow)
  2031. * @see #removeHeaderRow(int)
  2032. */
  2033. public HeaderRow addHeaderRowAt(int index) {
  2034. return getHeader().addRowAt(index);
  2035. }
  2036. /**
  2037. * Adds a new row at the bottom of the header section.
  2038. *
  2039. * @return the appended header row
  2040. *
  2041. * @see #prependHeaderRow()
  2042. * @see #addHeaderRowAt(int)
  2043. * @see #removeHeaderRow(HeaderRow)
  2044. * @see #removeHeaderRow(int)
  2045. */
  2046. public HeaderRow appendHeaderRow() {
  2047. return addHeaderRowAt(getHeaderRowCount());
  2048. }
  2049. /**
  2050. * Adds a new row at the top of the header section.
  2051. *
  2052. * @return the prepended header row
  2053. *
  2054. * @see #appendHeaderRow()
  2055. * @see #addHeaderRowAt(int)
  2056. * @see #removeHeaderRow(HeaderRow)
  2057. * @see #removeHeaderRow(int)
  2058. */
  2059. public HeaderRow prependHeaderRow() {
  2060. return addHeaderRowAt(0);
  2061. }
  2062. /**
  2063. * Removes the given row from the header section. Removing a default row
  2064. * sets the Grid to have no default row.
  2065. *
  2066. * @param row
  2067. * the header row to be removed, not null
  2068. *
  2069. * @throws IllegalArgumentException
  2070. * if the header does not contain the row
  2071. *
  2072. * @see #removeHeaderRow(int)
  2073. * @see #addHeaderRowAt(int)
  2074. * @see #appendHeaderRow()
  2075. * @see #prependHeaderRow()
  2076. */
  2077. public void removeHeaderRow(HeaderRow row) {
  2078. getHeader().removeRow(row);
  2079. }
  2080. /**
  2081. * Removes the row at the given position from the header section.
  2082. *
  2083. * @param index
  2084. * the index of the row to remove, where the topmost row has
  2085. * index zero
  2086. *
  2087. * @throws IndexOutOfBoundsException
  2088. * if {@code index < 0 || index >= getHeaderRowCount()}
  2089. *
  2090. * @see #removeHeaderRow(HeaderRow)
  2091. * @see #addHeaderRowAt(int)
  2092. * @see #appendHeaderRow()
  2093. * @see #prependHeaderRow()
  2094. */
  2095. public void removeHeaderRow(int index) {
  2096. getHeader().removeRow(index);
  2097. }
  2098. /**
  2099. * Returns the current default row of the header.
  2100. *
  2101. * @return the default row or null if no default row set
  2102. *
  2103. * @see #setDefaultHeaderRow(HeaderRow)
  2104. */
  2105. public HeaderRow getDefaultHeaderRow() {
  2106. return header.getDefaultRow();
  2107. }
  2108. /**
  2109. * Sets the default row of the header. The default row is a special header
  2110. * row that displays column captions and sort indicators. By default Grid
  2111. * has a single row which is also the default row. When a header row is set
  2112. * as the default row, any existing cell content is replaced by the column
  2113. * captions.
  2114. *
  2115. * @param row
  2116. * the new default row, or null for no default row
  2117. *
  2118. * @throws IllegalArgumentException
  2119. * if the header does not contain the row
  2120. */
  2121. public void setDefaultHeaderRow(HeaderRow row) {
  2122. header.setDefaultRow((Row) row);
  2123. }
  2124. /**
  2125. * Returns the header section of this grid. The default header contains a
  2126. * single row, set as the {@linkplain #setDefaultHeaderRow(HeaderRow)
  2127. * default row}.
  2128. *
  2129. * @return the header section
  2130. */
  2131. protected Header getHeader() {
  2132. return header;
  2133. }
  2134. /**
  2135. * Returns the footer row at the given index.
  2136. *
  2137. * @param index
  2138. * the index of the row, where the topmost row has index zero
  2139. * @return the footer row at the index
  2140. * @throws IndexOutOfBoundsException
  2141. * if {@code rowIndex < 0 || rowIndex >= getFooterRowCount()}
  2142. */
  2143. public FooterRow getFooterRow(int index) {
  2144. return getFooter().getRow(index);
  2145. }
  2146. /**
  2147. * Gets the number of rows in the footer section.
  2148. *
  2149. * @return the number of footer rows
  2150. */
  2151. public int getFooterRowCount() {
  2152. return getFooter().getRowCount();
  2153. }
  2154. /**
  2155. * Inserts a new row at the given position to the footer section. Shifts the
  2156. * row currently at that position and any subsequent rows down (adds one to
  2157. * their indices). Inserting at {@link #getFooterRowCount()} appends the row
  2158. * at the bottom of the footer.
  2159. *
  2160. * @param index
  2161. * the index at which to insert the row, where the topmost row
  2162. * has index zero
  2163. * @return the inserted footer row
  2164. *
  2165. * @throws IndexOutOfBoundsException
  2166. * if {@code rowIndex < 0 || rowIndex > getFooterRowCount()}
  2167. *
  2168. * @see #appendFooterRow()
  2169. * @see #prependFooterRow()
  2170. * @see #removeFooterRow(FooterRow)
  2171. * @see #removeFooterRow(int)
  2172. */
  2173. public FooterRow addFooterRowAt(int index) {
  2174. return getFooter().addRowAt(index);
  2175. }
  2176. /**
  2177. * Adds a new row at the bottom of the footer section.
  2178. *
  2179. * @return the appended footer row
  2180. *
  2181. * @see #prependFooterRow()
  2182. * @see #addFooterRowAt(int)
  2183. * @see #removeFooterRow(FooterRow)
  2184. * @see #removeFooterRow(int)
  2185. */
  2186. public FooterRow appendFooterRow() {
  2187. return addFooterRowAt(getFooterRowCount());
  2188. }
  2189. /**
  2190. * Adds a new row at the top of the footer section.
  2191. *
  2192. * @return the prepended footer row
  2193. *
  2194. * @see #appendFooterRow()
  2195. * @see #addFooterRowAt(int)
  2196. * @see #removeFooterRow(FooterRow)
  2197. * @see #removeFooterRow(int)
  2198. */
  2199. public FooterRow prependFooterRow() {
  2200. return addFooterRowAt(0);
  2201. }
  2202. /**
  2203. * Removes the given row from the footer section. Removing a default row
  2204. * sets the Grid to have no default row.
  2205. *
  2206. * @param row
  2207. * the footer row to be removed, not null
  2208. *
  2209. * @throws IllegalArgumentException
  2210. * if the footer does not contain the row
  2211. *
  2212. * @see #removeFooterRow(int)
  2213. * @see #addFooterRowAt(int)
  2214. * @see #appendFooterRow()
  2215. * @see #prependFooterRow()
  2216. */
  2217. public void removeFooterRow(FooterRow row) {
  2218. getFooter().removeRow(row);
  2219. }
  2220. /**
  2221. * Removes the row at the given position from the footer section.
  2222. *
  2223. * @param index
  2224. * the index of the row to remove, where the topmost row has
  2225. * index zero
  2226. *
  2227. * @throws IndexOutOfBoundsException
  2228. * if {@code index < 0 || index >= getFooterRowCount()}
  2229. *
  2230. * @see #removeFooterRow(FooterRow)
  2231. * @see #addFooterRowAt(int)
  2232. * @see #appendFooterRow()
  2233. * @see #prependFooterRow()
  2234. */
  2235. public void removeFooterRow(int index) {
  2236. getFooter().removeRow(index);
  2237. }
  2238. /**
  2239. * Returns the footer section of this grid. The default footer contains a
  2240. * single row, set as the {@linkplain #setDefaultFooterRow(FooterRow)
  2241. * default row}.
  2242. *
  2243. * @return the footer section
  2244. */
  2245. protected Footer getFooter() {
  2246. return footer;
  2247. }
  2248. /**
  2249. * Registers a new column reorder listener.
  2250. *
  2251. * @param listener
  2252. * the listener to register, not null
  2253. * @return a registration for the listener
  2254. */
  2255. public Registration addColumnReorderListener(
  2256. ColumnReorderListener listener) {
  2257. return addListener(ColumnReorderEvent.class, listener,
  2258. COLUMN_REORDER_METHOD);
  2259. }
  2260. /**
  2261. * Registers a new column resize listener.
  2262. *
  2263. * @param listener
  2264. * the listener to register, not null
  2265. * @return a registration for the listener
  2266. */
  2267. public Registration addColumnResizeListener(ColumnResizeListener listener) {
  2268. return addListener(ColumnResizeEvent.class, listener,
  2269. COLUMN_RESIZE_METHOD);
  2270. }
  2271. /**
  2272. * Adds an item click listener. The listener is called when an item of this
  2273. * {@code Grid} is clicked.
  2274. *
  2275. * @param listener
  2276. * the item click listener, not null
  2277. * @return a registration for the listener
  2278. */
  2279. public Registration addItemClickListener(
  2280. ItemClickListener<? super T> listener) {
  2281. return addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClick.class,
  2282. listener, ITEM_CLICK_METHOD);
  2283. }
  2284. /**
  2285. * Registers a new column visibility change listener.
  2286. *
  2287. * @param listener
  2288. * the listener to register, not null
  2289. * @return a registration for the listener
  2290. */
  2291. public Registration addColumnVisibilityChangeListener(
  2292. ColumnVisibilityChangeListener listener) {
  2293. return addListener(ColumnVisibilityChangeEvent.class, listener,
  2294. COLUMN_VISIBILITY_METHOD);
  2295. }
  2296. /**
  2297. * Returns whether column reordering is allowed. Default value is
  2298. * <code>false</code>.
  2299. *
  2300. * @return true if reordering is allowed
  2301. */
  2302. public boolean isColumnReorderingAllowed() {
  2303. return getState(false).columnReorderingAllowed;
  2304. }
  2305. /**
  2306. * Sets whether or not column reordering is allowed. Default value is
  2307. * <code>false</code>.
  2308. *
  2309. * @param columnReorderingAllowed
  2310. * specifies whether column reordering is allowed
  2311. */
  2312. public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
  2313. if (isColumnReorderingAllowed() != columnReorderingAllowed) {
  2314. getState().columnReorderingAllowed = columnReorderingAllowed;
  2315. }
  2316. }
  2317. /**
  2318. * Sets the columns and their order for the grid. Columns currently in this
  2319. * grid that are not present in columns are removed. Similarly, any new
  2320. * column in columns will be added to this grid.
  2321. *
  2322. * @param columns
  2323. * the columns to set
  2324. */
  2325. public void setColumns(Column<T, ?>... columns) {
  2326. List<Column<T, ?>> currentColumns = getColumns();
  2327. Set<Column<T, ?>> removeColumns = new HashSet<>(currentColumns);
  2328. Set<Column<T, ?>> addColumns = Arrays.stream(columns)
  2329. .collect(Collectors.toSet());
  2330. removeColumns.removeAll(addColumns);
  2331. removeColumns.stream().forEach(this::removeColumn);
  2332. addColumns.removeAll(currentColumns);
  2333. addColumns.stream().forEach(c -> addColumn(getIdentifier(c), c));
  2334. setColumnOrder(columns);
  2335. }
  2336. private String getIdentifier(Column<T, ?> column) {
  2337. return columnKeys.entrySet().stream()
  2338. .filter(entry -> entry.getValue().equals(column))
  2339. .map(entry -> entry.getKey()).findFirst()
  2340. .orElse(getGeneratedIdentifier());
  2341. }
  2342. private String getGeneratedIdentifier() {
  2343. String columnId = "" + counter;
  2344. counter++;
  2345. return columnId;
  2346. }
  2347. /**
  2348. * Sets a new column order for the grid. All columns which are not ordered
  2349. * here will remain in the order they were before as the last columns of
  2350. * grid.
  2351. *
  2352. * @param columns
  2353. * the columns in the order they should be
  2354. */
  2355. public void setColumnOrder(Column<T, ?>... columns) {
  2356. List<String> columnOrder = new ArrayList<>();
  2357. for (Column<T, ?> column : columns) {
  2358. if (columnSet.contains(column)) {
  2359. columnOrder.add(column.getInternalId());
  2360. } else {
  2361. throw new IllegalArgumentException(
  2362. "setColumnOrder should not be called "
  2363. + "with columns that are not in the grid.");
  2364. }
  2365. }
  2366. List<String> stateColumnOrder = getState().columnOrder;
  2367. if (stateColumnOrder.size() != columnOrder.size()) {
  2368. stateColumnOrder.removeAll(columnOrder);
  2369. columnOrder.addAll(stateColumnOrder);
  2370. }
  2371. getState().columnOrder = columnOrder;
  2372. fireColumnReorderEvent(false);
  2373. }
  2374. /**
  2375. * Returns the selection model for this grid.
  2376. *
  2377. * @return the selection model, not null
  2378. */
  2379. public GridSelectionModel<T> getSelectionModel() {
  2380. assert selectionModel != null : "No selection model set by "
  2381. + getClass().getName() + " constructor";
  2382. return selectionModel;
  2383. }
  2384. /**
  2385. * Use this grid as a single select in {@link Binder}.
  2386. * <p>
  2387. * Throws {@link IllegalStateException} if the grid is not using a
  2388. * {@link SingleSelectionModel}.
  2389. *
  2390. * @return the single select wrapper that can be used in binder
  2391. * @throws IllegalStateException
  2392. * if not using a single selection model
  2393. */
  2394. public SingleSelect<T> asSingleSelect() {
  2395. GridSelectionModel<T> model = getSelectionModel();
  2396. if (!(model instanceof SingleSelectionModel)) {
  2397. throw new IllegalStateException(
  2398. "Grid is not in single select mode, it needs to be explicitly set to such with setSelectionModel(SingleSelectionModel) before being able to use single selection features.");
  2399. }
  2400. return ((SingleSelectionModel<T>) model).asSingleSelect();
  2401. }
  2402. public Editor<T> getEditor() {
  2403. return editor;
  2404. }
  2405. /**
  2406. * User this grid as a multiselect in {@link Binder}.
  2407. * <p>
  2408. * Throws {@link IllegalStateException} if the grid is not using a
  2409. * {@link MultiSelectionModel}.
  2410. *
  2411. * @return the multiselect wrapper that can be used in binder
  2412. * @throws IllegalStateException
  2413. * if not using a multiselection model
  2414. */
  2415. public MultiSelect<T> asMultiSelect() {
  2416. GridSelectionModel<T> model = getSelectionModel();
  2417. if (!(model instanceof MultiSelectionModel)) {
  2418. throw new IllegalStateException(
  2419. "Grid is not in multiselect mode, it needs to be explicitly set to such with setSelectionModel(MultiSelectionModel) before being able to use multiselection features.");
  2420. }
  2421. return ((MultiSelectionModel<T>) model).asMultiSelect();
  2422. }
  2423. /**
  2424. * Sets the selection model for the grid.
  2425. * <p>
  2426. * This method is for setting a custom selection model, and is
  2427. * {@code protected} because {@link #setSelectionMode(SelectionMode)} should
  2428. * be used for easy switching between built-in selection models.
  2429. * <p>
  2430. * The default selection model is {@link SingleSelectionModelImpl}.
  2431. * <p>
  2432. * To use a custom selection model, you can e.g. extend the grid call this
  2433. * method with your custom selection model.
  2434. *
  2435. * @param model
  2436. * the selection model to use, not {@code null}
  2437. *
  2438. * @see #setSelectionMode(SelectionMode)
  2439. */
  2440. @SuppressWarnings("unchecked")
  2441. protected void setSelectionModel(GridSelectionModel<T> model) {
  2442. Objects.requireNonNull(model, "selection model cannot be null");
  2443. if (selectionModel != null) { // null when called from constructor
  2444. selectionModel.remove();
  2445. }
  2446. selectionModel = model;
  2447. if (selectionModel instanceof AbstractListingExtension) {
  2448. ((AbstractListingExtension<T>) selectionModel).extend(this);
  2449. } else {
  2450. addExtension(selectionModel);
  2451. }
  2452. }
  2453. /**
  2454. * Sets the grid's selection mode.
  2455. * <p>
  2456. * The built-in selection models are:
  2457. * <ul>
  2458. * <li>{@link SelectionMode#SINGLE} -> {@link SingleSelectionModelImpl},
  2459. * <b>the default model</b></li>
  2460. * <li>{@link SelectionMode#MULTI} -> {@link MultiSelectionModelImpl}, with
  2461. * checkboxes in the first column for selection</li>
  2462. * <li>{@link SelectionMode#NONE} -> {@link NoSelectionModel}, preventing
  2463. * selection</li>
  2464. * </ul>
  2465. * <p>
  2466. * To use your custom selection model, you can use
  2467. * {@link #setSelectionModel(GridSelectionModel)}, see existing selection
  2468. * model implementations for example.
  2469. *
  2470. * @param selectionMode
  2471. * the selection mode to switch to, not {@code null}
  2472. * @return the used selection model
  2473. *
  2474. * @see SelectionMode
  2475. * @see GridSelectionModel
  2476. * @see #setSelectionModel(GridSelectionModel)
  2477. */
  2478. public GridSelectionModel<T> setSelectionMode(SelectionMode selectionMode) {
  2479. Objects.requireNonNull(selectionMode, "Selection mode cannot be null.");
  2480. GridSelectionModel<T> model = selectionMode.createModel();
  2481. setSelectionModel(model);
  2482. return model;
  2483. }
  2484. /**
  2485. * Adds a selection listener to the current selection model.
  2486. * <p>
  2487. * <em>NOTE:</em> If selection mode is switched with
  2488. * {@link #setSelectionMode(SelectionMode)}, then this listener is not
  2489. * triggered anymore when selection changes!
  2490. * <p>
  2491. * This is a shorthand for
  2492. * {@code grid.getSelectionModel().addSelectionListener()}. To get more
  2493. * detailed selection events, use {@link #getSelectionModel()} and either
  2494. * {@link SingleSelectionModel#addSingleSelectionListener(SingleSelectionListener)}
  2495. * or
  2496. * {@link MultiSelectionModel#addMultiSelectionListener(MultiSelectionListener)}
  2497. * depending on the used selection mode.
  2498. *
  2499. * @param listener
  2500. * the listener to add
  2501. * @return a registration handle to remove the listener
  2502. * @throws UnsupportedOperationException
  2503. * if selection has been disabled with
  2504. * {@link SelectionMode.NONE}
  2505. */
  2506. public Registration addSelectionListener(SelectionListener<T> listener)
  2507. throws UnsupportedOperationException {
  2508. return getSelectionModel().addSelectionListener(listener);
  2509. }
  2510. /**
  2511. * Sort this Grid in ascending order by a specified column.
  2512. *
  2513. * @param column
  2514. * a column to sort against
  2515. *
  2516. */
  2517. public void sort(Column<T, ?> column) {
  2518. sort(column, SortDirection.ASCENDING);
  2519. }
  2520. /**
  2521. * Sort this Grid in user-specified {@link SortOrder} by a column.
  2522. *
  2523. * @param column
  2524. * a column to sort against
  2525. * @param direction
  2526. * a sort order value (ascending/descending)
  2527. *
  2528. */
  2529. public void sort(Column<T, ?> column, SortDirection direction) {
  2530. setSortOrder(
  2531. Collections.singletonList(new SortOrder<>(column, direction)));
  2532. }
  2533. /**
  2534. * Clear the current sort order, and re-sort the grid.
  2535. */
  2536. public void clearSortOrder() {
  2537. sortOrder.clear();
  2538. sort(false);
  2539. }
  2540. /**
  2541. * Sets the sort order to use.
  2542. *
  2543. * @param order
  2544. * a sort order list.
  2545. *
  2546. * @throws IllegalArgumentException
  2547. * if order is null
  2548. */
  2549. public void setSortOrder(List<SortOrder<Column<T, ?>>> order) {
  2550. setSortOrder(order, false);
  2551. }
  2552. /**
  2553. * Adds a sort order change listener that gets notified when the sort order
  2554. * changes.
  2555. *
  2556. * @param listener
  2557. * the sort order change listener to add
  2558. */
  2559. @Override
  2560. public Registration addSortListener(SortListener<Column<T, ?>> listener) {
  2561. return addListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
  2562. }
  2563. /**
  2564. * Get the current sort order list.
  2565. *
  2566. * @return a sort order list
  2567. */
  2568. public List<SortOrder<Column<T, ?>>> getSortOrder() {
  2569. return Collections.unmodifiableList(sortOrder);
  2570. }
  2571. @Override
  2572. protected GridState getState() {
  2573. return getState(true);
  2574. }
  2575. @Override
  2576. protected GridState getState(boolean markAsDirty) {
  2577. return (GridState) super.getState(markAsDirty);
  2578. }
  2579. /**
  2580. * Sets the column resize mode to use. The default mode is
  2581. * {@link ColumnResizeMode#ANIMATED}.
  2582. *
  2583. * @param mode
  2584. * a ColumnResizeMode value
  2585. */
  2586. public void setColumnResizeMode(ColumnResizeMode mode) {
  2587. getState().columnResizeMode = mode;
  2588. }
  2589. /**
  2590. * Returns the current column resize mode. The default mode is
  2591. * {@link ColumnResizeMode#ANIMATED}.
  2592. *
  2593. * @return a ColumnResizeMode value
  2594. */
  2595. public ColumnResizeMode getColumnResizeMode() {
  2596. return getState(false).columnResizeMode;
  2597. }
  2598. /**
  2599. * Creates a new Editor instance. Can be overridden to create a custom
  2600. * Editor. If the Editor is a {@link AbstractGridExtension}, it will be
  2601. * automatically added to {@link DataCommunicator}.
  2602. *
  2603. * @return editor
  2604. */
  2605. protected Editor<T> createEditor() {
  2606. return new EditorImpl<>();
  2607. }
  2608. private void addExtensionComponent(Component c) {
  2609. if (extensionComponents.add(c)) {
  2610. c.setParent(this);
  2611. markAsDirty();
  2612. }
  2613. }
  2614. private void removeExtensionComponent(Component c) {
  2615. if (extensionComponents.remove(c)) {
  2616. c.setParent(null);
  2617. markAsDirty();
  2618. }
  2619. }
  2620. private void fireColumnReorderEvent(boolean userOriginated) {
  2621. fireEvent(new ColumnReorderEvent(this, userOriginated));
  2622. }
  2623. private void fireColumnResizeEvent(Column<?, ?> column,
  2624. boolean userOriginated) {
  2625. fireEvent(new ColumnResizeEvent(this, column, userOriginated));
  2626. }
  2627. @Override
  2628. protected List<T> readItems(Element design, DesignContext context) {
  2629. return Collections.emptyList();
  2630. }
  2631. @Override
  2632. public DataProvider<T, ?> getDataProvider() {
  2633. return internalGetDataProvider();
  2634. }
  2635. @Override
  2636. public void setDataProvider(DataProvider<T, ?> dataProvider) {
  2637. internalSetDataProvider(dataProvider);
  2638. }
  2639. @Override
  2640. protected void doReadDesign(Element design, DesignContext context) {
  2641. Attributes attrs = design.attributes();
  2642. if (attrs.hasKey("selection-mode")) {
  2643. setSelectionMode(DesignAttributeHandler.readAttribute(
  2644. "selection-mode", attrs, SelectionMode.class));
  2645. }
  2646. Attributes attr = design.attributes();
  2647. if (attr.hasKey("selection-allowed")) {
  2648. setReadOnly(DesignAttributeHandler
  2649. .readAttribute("selection-allowed", attr, Boolean.class));
  2650. }
  2651. if (attrs.hasKey("rows")) {
  2652. setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs,
  2653. double.class));
  2654. }
  2655. readStructure(design, context);
  2656. // Read frozen columns after columns are read.
  2657. if (attrs.hasKey("frozen-columns")) {
  2658. setFrozenColumnCount(DesignAttributeHandler
  2659. .readAttribute("frozen-columns", attrs, int.class));
  2660. }
  2661. }
  2662. @Override
  2663. protected void doWriteDesign(Element design, DesignContext designContext) {
  2664. Attributes attr = design.attributes();
  2665. DesignAttributeHandler.writeAttribute("selection-allowed", attr,
  2666. isReadOnly(), false, Boolean.class, designContext);
  2667. Attributes attrs = design.attributes();
  2668. Grid<?> defaultInstance = designContext.getDefaultInstance(this);
  2669. DesignAttributeHandler.writeAttribute("frozen-columns", attrs,
  2670. getFrozenColumnCount(), defaultInstance.getFrozenColumnCount(),
  2671. int.class, designContext);
  2672. if (HeightMode.ROW.equals(getHeightMode())) {
  2673. DesignAttributeHandler.writeAttribute("rows", attrs,
  2674. getHeightByRows(), defaultInstance.getHeightByRows(),
  2675. double.class, designContext);
  2676. }
  2677. SelectionMode mode = getSelectionMode();
  2678. if (mode != null) {
  2679. DesignAttributeHandler.writeAttribute("selection-mode", attrs, mode,
  2680. SelectionMode.SINGLE, SelectionMode.class, designContext);
  2681. }
  2682. writeStructure(design, designContext);
  2683. }
  2684. @Override
  2685. protected T deserializeDeclarativeRepresentation(String item) {
  2686. if (item == null) {
  2687. return super.deserializeDeclarativeRepresentation(
  2688. new String(UUID.randomUUID().toString()));
  2689. }
  2690. return super.deserializeDeclarativeRepresentation(new String(item));
  2691. }
  2692. @Override
  2693. protected boolean isReadOnly() {
  2694. SelectionMode selectionMode = getSelectionMode();
  2695. if (SelectionMode.SINGLE.equals(selectionMode)) {
  2696. return asSingleSelect().isReadOnly();
  2697. } else if (SelectionMode.MULTI.equals(selectionMode)) {
  2698. return asMultiSelect().isReadOnly();
  2699. }
  2700. return false;
  2701. }
  2702. @Override
  2703. protected void setReadOnly(boolean readOnly) {
  2704. SelectionMode selectionMode = getSelectionMode();
  2705. if (SelectionMode.SINGLE.equals(selectionMode)) {
  2706. asSingleSelect().setReadOnly(readOnly);
  2707. } else if (SelectionMode.MULTI.equals(selectionMode)) {
  2708. asMultiSelect().setReadOnly(readOnly);
  2709. }
  2710. }
  2711. private void readStructure(Element design, DesignContext context) {
  2712. if (design.children().isEmpty()) {
  2713. return;
  2714. }
  2715. if (design.children().size() > 1
  2716. || !design.child(0).tagName().equals("table")) {
  2717. throw new DesignException(
  2718. "Grid needs to have a table element as its only child");
  2719. }
  2720. Element table = design.child(0);
  2721. Elements colgroups = table.getElementsByTag("colgroup");
  2722. if (colgroups.size() != 1) {
  2723. throw new DesignException(
  2724. "Table element in declarative Grid needs to have a"
  2725. + " colgroup defining the columns used in Grid");
  2726. }
  2727. List<DeclarativeValueProvider<T>> providers = new ArrayList<>();
  2728. for (Element col : colgroups.get(0).getElementsByTag("col")) {
  2729. String id = DesignAttributeHandler.readAttribute("column-id",
  2730. col.attributes(), null, String.class);
  2731. DeclarativeValueProvider<T> provider = new DeclarativeValueProvider<>();
  2732. Column<T, String> column = new Column<>("", provider,
  2733. new HtmlRenderer());
  2734. addColumn(getGeneratedIdentifier(), column);
  2735. if (id != null) {
  2736. column.setId(id);
  2737. }
  2738. providers.add(provider);
  2739. column.readDesign(col, context);
  2740. }
  2741. for (Element child : table.children()) {
  2742. if (child.tagName().equals("thead")) {
  2743. getHeader().readDesign(child, context);
  2744. } else if (child.tagName().equals("tbody")) {
  2745. readData(child, providers);
  2746. } else if (child.tagName().equals("tfoot")) {
  2747. getFooter().readDesign(child, context);
  2748. }
  2749. }
  2750. }
  2751. private void readData(Element body,
  2752. List<DeclarativeValueProvider<T>> providers) {
  2753. getSelectionModel().deselectAll();
  2754. List<T> items = new ArrayList<>();
  2755. for (Element row : body.children()) {
  2756. T item = deserializeDeclarativeRepresentation(row.attr("item"));
  2757. items.add(item);
  2758. if (row.hasAttr("selected")) {
  2759. getSelectionModel().select(item);
  2760. }
  2761. Elements cells = row.children();
  2762. int i = 0;
  2763. for (Element cell : cells) {
  2764. providers.get(i).addValue(item, cell.html());
  2765. i++;
  2766. }
  2767. }
  2768. setItems(items);
  2769. }
  2770. private void writeStructure(Element design, DesignContext designContext) {
  2771. if (getColumns().isEmpty()) {
  2772. return;
  2773. }
  2774. Element tableElement = design.appendElement("table");
  2775. Element colGroup = tableElement.appendElement("colgroup");
  2776. getColumns().forEach(column -> column
  2777. .writeDesign(colGroup.appendElement("col"), designContext));
  2778. // Always write thead. Reads correctly when there no header rows
  2779. getHeader().writeDesign(tableElement.appendElement("thead"),
  2780. designContext);
  2781. if (designContext.shouldWriteData(this)) {
  2782. Element bodyElement = tableElement.appendElement("tbody");
  2783. getDataProvider().fetch(new Query<>()).forEach(
  2784. item -> writeRow(bodyElement, item, designContext));
  2785. }
  2786. if (getFooter().getRowCount() > 0) {
  2787. getFooter().writeDesign(tableElement.appendElement("tfoot"),
  2788. designContext);
  2789. }
  2790. }
  2791. private void writeRow(Element container, T item, DesignContext context) {
  2792. Element tableRow = container.appendElement("tr");
  2793. tableRow.attr("item", serializeDeclarativeRepresentation(item));
  2794. if (getSelectionModel().isSelected(item)) {
  2795. tableRow.attr("selected", "");
  2796. }
  2797. for (Column<T, ?> column : getColumns()) {
  2798. Object value = column.valueProvider.apply(item);
  2799. tableRow.appendElement("td")
  2800. .append(Optional.ofNullable(value).map(Object::toString)
  2801. .map(DesignFormatter::encodeForTextNode)
  2802. .orElse(""));
  2803. }
  2804. }
  2805. private SelectionMode getSelectionMode() {
  2806. GridSelectionModel<T> selectionModel = getSelectionModel();
  2807. SelectionMode mode = null;
  2808. if (selectionModel.getClass().equals(SingleSelectionModelImpl.class)) {
  2809. mode = SelectionMode.SINGLE;
  2810. } else if (selectionModel.getClass()
  2811. .equals(MultiSelectionModelImpl.class)) {
  2812. mode = SelectionMode.MULTI;
  2813. } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
  2814. mode = SelectionMode.NONE;
  2815. }
  2816. return mode;
  2817. }
  2818. /**
  2819. * Sets a user-defined identifier for given column.
  2820. *
  2821. * @param column
  2822. * the column
  2823. * @param id
  2824. * the user-defined identifier
  2825. */
  2826. protected void setColumnId(String id, Column<T, ?> column) {
  2827. if (columnIds.containsKey(id)) {
  2828. throw new IllegalArgumentException("Duplicate ID for columns");
  2829. }
  2830. columnIds.put(id, column);
  2831. }
  2832. @Override
  2833. protected Collection<String> getCustomAttributes() {
  2834. Collection<String> result = super.getCustomAttributes();
  2835. // "rename" for frozen column count
  2836. result.add("frozen-column-count");
  2837. result.add("frozen-columns");
  2838. // "rename" for height-mode
  2839. result.add("height-by-rows");
  2840. result.add("rows");
  2841. // add a selection-mode attribute
  2842. result.add("selection-mode");
  2843. return result;
  2844. }
  2845. /**
  2846. * Returns a column identified by its internal id. This id should not be
  2847. * confused with the user-defined identifier.
  2848. *
  2849. * @param columnId
  2850. * the internal id of column
  2851. * @return column identified by internal id
  2852. */
  2853. protected Column<T, ?> getColumnByInternalId(String columnId) {
  2854. return columnKeys.get(columnId);
  2855. }
  2856. /**
  2857. * Returns the internal id for given column. This id should not be confused
  2858. * with the user-defined identifier.
  2859. *
  2860. * @param column
  2861. * the column
  2862. * @return internal id of given column
  2863. */
  2864. protected String getInternalIdForColumn(Column<T, ?> column) {
  2865. return column.getInternalId();
  2866. }
  2867. private void setSortOrder(List<SortOrder<Column<T, ?>>> order,
  2868. boolean userOriginated) {
  2869. Objects.requireNonNull(order, "Sort order list cannot be null");
  2870. sortOrder.clear();
  2871. if (order.isEmpty()) {
  2872. // Grid is not sorted anymore.
  2873. getDataCommunicator().setBackEndSorting(Collections.emptyList());
  2874. getDataCommunicator().setInMemorySorting(null);
  2875. fireEvent(new SortEvent<>(this, new ArrayList<>(sortOrder),
  2876. userOriginated));
  2877. return;
  2878. }
  2879. sortOrder.addAll(order);
  2880. sort(userOriginated);
  2881. }
  2882. private void sort(boolean userOriginated) {
  2883. // Set sort orders
  2884. // In-memory comparator
  2885. BinaryOperator<SerializableComparator<T>> operator = (comparator1,
  2886. comparator2) -> SerializableComparator
  2887. .asInstance((Comparator<T> & Serializable) comparator1
  2888. .thenComparing(comparator2));
  2889. SerializableComparator<T> comparator = sortOrder.stream().map(
  2890. order -> order.getSorted().getComparator(order.getDirection()))
  2891. .reduce((x, y) -> 0, operator);
  2892. getDataCommunicator().setInMemorySorting(comparator);
  2893. // Back-end sort properties
  2894. List<SortOrder<String>> sortProperties = new ArrayList<>();
  2895. sortOrder.stream().map(
  2896. order -> order.getSorted().getSortOrder(order.getDirection()))
  2897. .forEach(s -> s.forEach(sortProperties::add));
  2898. getDataCommunicator().setBackEndSorting(sortProperties);
  2899. // Close grid editor if it's open.
  2900. if (getEditor().isOpen()) {
  2901. getEditor().cancel();
  2902. }
  2903. fireEvent(new SortEvent<>(this, new ArrayList<>(sortOrder),
  2904. userOriginated));
  2905. }
  2906. }