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 139KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073
  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.BeanPropertySet;
  43. import com.vaadin.data.Binder;
  44. import com.vaadin.data.Binder.Binding;
  45. import com.vaadin.data.HasDataProvider;
  46. import com.vaadin.data.HasValue;
  47. import com.vaadin.data.PropertyDefinition;
  48. import com.vaadin.data.PropertySet;
  49. import com.vaadin.data.ValueProvider;
  50. import com.vaadin.data.provider.CallbackDataProvider;
  51. import com.vaadin.data.provider.DataCommunicator;
  52. import com.vaadin.data.provider.DataProvider;
  53. import com.vaadin.data.provider.GridSortOrder;
  54. import com.vaadin.data.provider.GridSortOrderBuilder;
  55. import com.vaadin.data.provider.Query;
  56. import com.vaadin.data.provider.QuerySortOrder;
  57. import com.vaadin.event.ConnectorEvent;
  58. import com.vaadin.event.ContextClickEvent;
  59. import com.vaadin.event.SortEvent;
  60. import com.vaadin.event.SortEvent.SortListener;
  61. import com.vaadin.event.SortEvent.SortNotifier;
  62. import com.vaadin.event.selection.MultiSelectionListener;
  63. import com.vaadin.event.selection.SelectionListener;
  64. import com.vaadin.event.selection.SingleSelectionListener;
  65. import com.vaadin.server.EncodeResult;
  66. import com.vaadin.server.Extension;
  67. import com.vaadin.server.JsonCodec;
  68. import com.vaadin.server.SerializableComparator;
  69. import com.vaadin.server.SerializableFunction;
  70. import com.vaadin.server.SerializableSupplier;
  71. import com.vaadin.server.Setter;
  72. import com.vaadin.server.VaadinServiceClassLoaderUtil;
  73. import com.vaadin.shared.Connector;
  74. import com.vaadin.shared.MouseEventDetails;
  75. import com.vaadin.shared.Registration;
  76. import com.vaadin.shared.data.DataCommunicatorConstants;
  77. import com.vaadin.shared.data.sort.SortDirection;
  78. import com.vaadin.shared.ui.grid.AbstractGridExtensionState;
  79. import com.vaadin.shared.ui.grid.ColumnResizeMode;
  80. import com.vaadin.shared.ui.grid.ColumnState;
  81. import com.vaadin.shared.ui.grid.DetailsManagerState;
  82. import com.vaadin.shared.ui.grid.GridClientRpc;
  83. import com.vaadin.shared.ui.grid.GridConstants;
  84. import com.vaadin.shared.ui.grid.GridConstants.Section;
  85. import com.vaadin.shared.ui.grid.GridServerRpc;
  86. import com.vaadin.shared.ui.grid.GridState;
  87. import com.vaadin.shared.ui.grid.HeightMode;
  88. import com.vaadin.shared.ui.grid.ScrollDestination;
  89. import com.vaadin.shared.ui.grid.SectionState;
  90. import com.vaadin.ui.components.grid.ColumnReorderListener;
  91. import com.vaadin.ui.components.grid.ColumnResizeListener;
  92. import com.vaadin.ui.components.grid.ColumnVisibilityChangeListener;
  93. import com.vaadin.ui.components.grid.DescriptionGenerator;
  94. import com.vaadin.ui.components.grid.DetailsGenerator;
  95. import com.vaadin.ui.components.grid.Editor;
  96. import com.vaadin.ui.components.grid.EditorImpl;
  97. import com.vaadin.ui.components.grid.Footer;
  98. import com.vaadin.ui.components.grid.FooterRow;
  99. import com.vaadin.ui.components.grid.GridSelectionModel;
  100. import com.vaadin.ui.components.grid.Header;
  101. import com.vaadin.ui.components.grid.Header.Row;
  102. import com.vaadin.ui.components.grid.HeaderRow;
  103. import com.vaadin.ui.components.grid.ItemClickListener;
  104. import com.vaadin.ui.components.grid.MultiSelectionModel;
  105. import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
  106. import com.vaadin.ui.components.grid.NoSelectionModel;
  107. import com.vaadin.ui.components.grid.SingleSelectionModel;
  108. import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
  109. import com.vaadin.ui.components.grid.SortOrderProvider;
  110. import com.vaadin.ui.declarative.DesignAttributeHandler;
  111. import com.vaadin.ui.declarative.DesignContext;
  112. import com.vaadin.ui.declarative.DesignException;
  113. import com.vaadin.ui.declarative.DesignFormatter;
  114. import com.vaadin.ui.renderers.AbstractRenderer;
  115. import com.vaadin.ui.renderers.ComponentRenderer;
  116. import com.vaadin.ui.renderers.HtmlRenderer;
  117. import com.vaadin.ui.renderers.Renderer;
  118. import com.vaadin.ui.renderers.TextRenderer;
  119. import com.vaadin.util.ReflectTools;
  120. import elemental.json.Json;
  121. import elemental.json.JsonObject;
  122. import elemental.json.JsonValue;
  123. /**
  124. * A grid component for displaying tabular data.
  125. *
  126. * @author Vaadin Ltd
  127. * @since 8.0
  128. *
  129. * @param <T>
  130. * the grid bean type
  131. */
  132. public class Grid<T> extends AbstractListing<T> implements HasComponents,
  133. HasDataProvider<T>, SortNotifier<GridSortOrder<T>> {
  134. private static final String DECLARATIVE_DATA_ITEM_TYPE = "data-item-type";
  135. /**
  136. * A callback method for fetching items. The callback is provided with a
  137. * list of sort orders, offset index and limit.
  138. *
  139. * @param <T>
  140. * the grid bean type
  141. */
  142. @FunctionalInterface
  143. public interface FetchItemsCallback<T> extends Serializable {
  144. /**
  145. * Returns a stream of items ordered by given sort orders, limiting the
  146. * results with given offset and limit.
  147. * <p>
  148. * This method is called after the size of the data set is asked from a
  149. * related size callback. The offset and limit are promised to be within
  150. * the size of the data set.
  151. *
  152. * @param sortOrder
  153. * a list of sort orders
  154. * @param offset
  155. * the first index to fetch
  156. * @param limit
  157. * the fetched item count
  158. * @return stream of items
  159. */
  160. public Stream<T> fetchItems(List<QuerySortOrder> sortOrder, int offset,
  161. int limit);
  162. }
  163. @Deprecated
  164. private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod(
  165. ColumnReorderListener.class, "columnReorder",
  166. ColumnReorderEvent.class);
  167. private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools
  168. .findMethod(SortListener.class, "sort", SortEvent.class);
  169. @Deprecated
  170. private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(
  171. ColumnResizeListener.class, "columnResize",
  172. ColumnResizeEvent.class);
  173. @Deprecated
  174. private static final Method ITEM_CLICK_METHOD = ReflectTools
  175. .findMethod(ItemClickListener.class, "itemClick", ItemClick.class);
  176. @Deprecated
  177. private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools
  178. .findMethod(ColumnVisibilityChangeListener.class,
  179. "columnVisibilityChanged",
  180. ColumnVisibilityChangeEvent.class);
  181. /**
  182. * Selection mode representing the built-in selection models in grid.
  183. * <p>
  184. * These enums can be used in {@link Grid#setSelectionMode(SelectionMode)}
  185. * to easily switch between the build-in selection models.
  186. *
  187. * @see Grid#setSelectionMode(SelectionMode)
  188. * @see Grid#setSelectionModel(GridSelectionModel)
  189. */
  190. public enum SelectionMode {
  191. /**
  192. * Single selection mode that maps to build-in
  193. * {@link SingleSelectionModel}.
  194. *
  195. * @see SingleSelectionModelImpl
  196. */
  197. SINGLE {
  198. @Override
  199. protected <T> GridSelectionModel<T> createModel() {
  200. return new SingleSelectionModelImpl<>();
  201. }
  202. },
  203. /**
  204. * Multiselection mode that maps to build-in {@link MultiSelectionModel}
  205. * .
  206. *
  207. * @see MultiSelectionModelImpl
  208. */
  209. MULTI {
  210. @Override
  211. protected <T> GridSelectionModel<T> createModel() {
  212. return new MultiSelectionModelImpl<>();
  213. }
  214. },
  215. /**
  216. * Selection model that doesn't allow selection.
  217. *
  218. * @see NoSelectionModel
  219. */
  220. NONE {
  221. @Override
  222. protected <T> GridSelectionModel<T> createModel() {
  223. return new NoSelectionModel<>();
  224. }
  225. };
  226. /**
  227. * Creates the selection model to use with this enum.
  228. *
  229. * @param <T>
  230. * the type of items in the grid
  231. * @return the selection model
  232. */
  233. protected abstract <T> GridSelectionModel<T> createModel();
  234. }
  235. /**
  236. * An event that is fired when the columns are reordered.
  237. */
  238. public static class ColumnReorderEvent extends Component.Event {
  239. private final boolean userOriginated;
  240. /**
  241. *
  242. * @param source
  243. * the grid where the event originated from
  244. * @param userOriginated
  245. * <code>true</code> if event is a result of user
  246. * interaction, <code>false</code> if from API call
  247. */
  248. public ColumnReorderEvent(Grid source, boolean userOriginated) {
  249. super(source);
  250. this.userOriginated = userOriginated;
  251. }
  252. /**
  253. * Returns <code>true</code> if the column reorder was done by the user,
  254. * <code>false</code> if not and it was triggered by server side code.
  255. *
  256. * @return <code>true</code> if event is a result of user interaction
  257. */
  258. public boolean isUserOriginated() {
  259. return userOriginated;
  260. }
  261. }
  262. /**
  263. * An event that is fired when a column is resized, either programmatically
  264. * or by the user.
  265. */
  266. public static class ColumnResizeEvent extends Component.Event {
  267. private final Column<?, ?> column;
  268. private final boolean userOriginated;
  269. /**
  270. *
  271. * @param source
  272. * the grid where the event originated from
  273. * @param userOriginated
  274. * <code>true</code> if event is a result of user
  275. * interaction, <code>false</code> if from API call
  276. */
  277. public ColumnResizeEvent(Grid<?> source, Column<?, ?> column,
  278. boolean userOriginated) {
  279. super(source);
  280. this.column = column;
  281. this.userOriginated = userOriginated;
  282. }
  283. /**
  284. * Returns the column that was resized.
  285. *
  286. * @return the resized column.
  287. */
  288. public Column<?, ?> getColumn() {
  289. return column;
  290. }
  291. /**
  292. * Returns <code>true</code> if the column resize was done by the user,
  293. * <code>false</code> if not and it was triggered by server side code.
  294. *
  295. * @return <code>true</code> if event is a result of user interaction
  296. */
  297. public boolean isUserOriginated() {
  298. return userOriginated;
  299. }
  300. }
  301. /**
  302. * An event fired when an item in the Grid has been clicked.
  303. *
  304. * @param <T>
  305. * the grid bean type
  306. */
  307. public static class ItemClick<T> extends ConnectorEvent {
  308. private final T item;
  309. private final Column<T, ?> column;
  310. private final MouseEventDetails mouseEventDetails;
  311. /**
  312. * Creates a new {@code ItemClick} event containing the given item and
  313. * Column originating from the given Grid.
  314. *
  315. */
  316. public ItemClick(Grid<T> source, Column<T, ?> column, T item,
  317. MouseEventDetails mouseEventDetails) {
  318. super(source);
  319. this.column = column;
  320. this.item = item;
  321. this.mouseEventDetails = mouseEventDetails;
  322. }
  323. /**
  324. * Returns the clicked item.
  325. *
  326. * @return the clicked item
  327. */
  328. public T getItem() {
  329. return item;
  330. }
  331. /**
  332. * Returns the clicked column.
  333. *
  334. * @return the clicked column
  335. */
  336. public Column<T, ?> getColumn() {
  337. return column;
  338. }
  339. /**
  340. * Returns the source Grid.
  341. *
  342. * @return the grid
  343. */
  344. @Override
  345. public Grid<T> getSource() {
  346. return (Grid<T>) super.getSource();
  347. }
  348. /**
  349. * Returns the mouse event details.
  350. *
  351. * @return the mouse event details
  352. */
  353. public MouseEventDetails getMouseEventDetails() {
  354. return mouseEventDetails;
  355. }
  356. }
  357. /**
  358. * ContextClickEvent for the Grid Component.
  359. *
  360. * @param <T>
  361. * the grid bean type
  362. */
  363. public static class GridContextClickEvent<T> extends ContextClickEvent {
  364. private final T item;
  365. private final int rowIndex;
  366. private final Column<T, ?> column;
  367. private final Section section;
  368. /**
  369. * Creates a new context click event.
  370. *
  371. * @param source
  372. * the grid where the context click occurred
  373. * @param mouseEventDetails
  374. * details about mouse position
  375. * @param section
  376. * the section of the grid which was clicked
  377. * @param rowIndex
  378. * the index of the row which was clicked
  379. * @param item
  380. * the item which was clicked
  381. * @param column
  382. * the column which was clicked
  383. */
  384. public GridContextClickEvent(Grid<T> source,
  385. MouseEventDetails mouseEventDetails, Section section,
  386. int rowIndex, T item, Column<T, ?> column) {
  387. super(source, mouseEventDetails);
  388. this.item = item;
  389. this.section = section;
  390. this.column = column;
  391. this.rowIndex = rowIndex;
  392. }
  393. /**
  394. * Returns the item of context clicked row.
  395. *
  396. * @return item of clicked row; <code>null</code> if header or footer
  397. */
  398. public T getItem() {
  399. return item;
  400. }
  401. /**
  402. * Returns the clicked column.
  403. *
  404. * @return the clicked column
  405. */
  406. public Column<T, ?> getColumn() {
  407. return column;
  408. }
  409. /**
  410. * Return the clicked section of Grid.
  411. *
  412. * @return section of grid
  413. */
  414. public Section getSection() {
  415. return section;
  416. }
  417. /**
  418. * Returns the clicked row index.
  419. * <p>
  420. * Header and Footer rows for index can be fetched with
  421. * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
  422. *
  423. * @return row index in section
  424. */
  425. public int getRowIndex() {
  426. return rowIndex;
  427. }
  428. @Override
  429. public Grid<T> getComponent() {
  430. return (Grid<T>) super.getComponent();
  431. }
  432. }
  433. /**
  434. * An event that is fired when a column's visibility changes.
  435. *
  436. * @since 7.5.0
  437. */
  438. public static class ColumnVisibilityChangeEvent extends Component.Event {
  439. private final Column<?, ?> column;
  440. private final boolean userOriginated;
  441. private final boolean hidden;
  442. /**
  443. * Constructor for a column visibility change event.
  444. *
  445. * @param source
  446. * the grid from which this event originates
  447. * @param column
  448. * the column that changed its visibility
  449. * @param hidden
  450. * <code>true</code> if the column was hidden,
  451. * <code>false</code> if it became visible
  452. * @param isUserOriginated
  453. * <code>true</code> iff the event was triggered by an UI
  454. * interaction
  455. */
  456. public ColumnVisibilityChangeEvent(Grid<?> source, Column<?, ?> column,
  457. boolean hidden, boolean isUserOriginated) {
  458. super(source);
  459. this.column = column;
  460. this.hidden = hidden;
  461. userOriginated = isUserOriginated;
  462. }
  463. /**
  464. * Gets the column that became hidden or visible.
  465. *
  466. * @return the column that became hidden or visible.
  467. * @see Column#isHidden()
  468. */
  469. public Column<?, ?> getColumn() {
  470. return column;
  471. }
  472. /**
  473. * Was the column set hidden or visible.
  474. *
  475. * @return <code>true</code> if the column was hidden <code>false</code>
  476. * if it was set visible
  477. */
  478. public boolean isHidden() {
  479. return hidden;
  480. }
  481. /**
  482. * Returns <code>true</code> if the column reorder was done by the user,
  483. * <code>false</code> if not and it was triggered by server side code.
  484. *
  485. * @return <code>true</code> if event is a result of user interaction
  486. */
  487. public boolean isUserOriginated() {
  488. return userOriginated;
  489. }
  490. }
  491. /**
  492. * A helper base class for creating extensions for the Grid component.
  493. *
  494. * @param <T>
  495. */
  496. public abstract static class AbstractGridExtension<T>
  497. extends AbstractListingExtension<T> {
  498. @Override
  499. public void extend(AbstractListing<T> grid) {
  500. if (!(grid instanceof Grid)) {
  501. throw new IllegalArgumentException(
  502. getClass().getSimpleName() + " can only extend Grid");
  503. }
  504. super.extend(grid);
  505. }
  506. /**
  507. * Adds given component to the connector hierarchy of Grid.
  508. *
  509. * @param c
  510. * the component to add
  511. */
  512. protected void addComponentToGrid(Component c) {
  513. getParent().addExtensionComponent(c);
  514. }
  515. /**
  516. * Removes given component from the connector hierarchy of Grid.
  517. *
  518. * @param c
  519. * the component to remove
  520. */
  521. protected void removeComponentFromGrid(Component c) {
  522. getParent().removeExtensionComponent(c);
  523. }
  524. @Override
  525. public Grid<T> getParent() {
  526. return (Grid<T>) super.getParent();
  527. }
  528. @Override
  529. protected AbstractGridExtensionState getState() {
  530. return (AbstractGridExtensionState) super.getState();
  531. }
  532. @Override
  533. protected AbstractGridExtensionState getState(boolean markAsDirty) {
  534. return (AbstractGridExtensionState) super.getState(markAsDirty);
  535. }
  536. protected String getInternalIdForColumn(Column<T, ?> column) {
  537. return getParent().getInternalIdForColumn(column);
  538. }
  539. }
  540. private final class GridServerRpcImpl implements GridServerRpc {
  541. @Override
  542. public void sort(String[] columnInternalIds, SortDirection[] directions,
  543. boolean isUserOriginated) {
  544. assert columnInternalIds.length == directions.length : "Column and sort direction counts don't match.";
  545. List<GridSortOrder<T>> list = new ArrayList<>(directions.length);
  546. for (int i = 0; i < columnInternalIds.length; ++i) {
  547. Column<T, ?> column = columnKeys.get(columnInternalIds[i]);
  548. list.add(new GridSortOrder<>(column, directions[i]));
  549. }
  550. setSortOrder(list, isUserOriginated);
  551. }
  552. @Override
  553. public void itemClick(String rowKey, String columnInternalId,
  554. MouseEventDetails details) {
  555. Column<T, ?> column = getColumnByInternalId(columnInternalId);
  556. T item = getDataCommunicator().getKeyMapper().get(rowKey);
  557. fireEvent(new ItemClick<>(Grid.this, column, item, details));
  558. }
  559. @Override
  560. public void contextClick(int rowIndex, String rowKey,
  561. String columnInternalId, Section section,
  562. MouseEventDetails details) {
  563. T item = null;
  564. if (rowKey != null) {
  565. item = getDataCommunicator().getKeyMapper().get(rowKey);
  566. }
  567. fireEvent(new GridContextClickEvent<>(Grid.this, details, section,
  568. rowIndex, item, getColumnByInternalId(columnInternalId)));
  569. }
  570. @Override
  571. public void columnsReordered(List<String> newColumnOrder,
  572. List<String> oldColumnOrder) {
  573. final String diffStateKey = "columnOrder";
  574. ConnectorTracker connectorTracker = getUI().getConnectorTracker();
  575. JsonObject diffState = connectorTracker.getDiffState(Grid.this);
  576. // discard the change if the columns have been reordered from
  577. // the server side, as the server side is always right
  578. if (getState(false).columnOrder.equals(oldColumnOrder)) {
  579. // Don't mark as dirty since client has the state already
  580. getState(false).columnOrder = newColumnOrder;
  581. // write changes to diffState so that possible reverting the
  582. // column order is sent to client
  583. assert diffState
  584. .hasKey(diffStateKey) : "Field name has changed";
  585. Type type = null;
  586. try {
  587. type = getState(false).getClass().getField(diffStateKey)
  588. .getGenericType();
  589. } catch (NoSuchFieldException | SecurityException e) {
  590. e.printStackTrace();
  591. }
  592. EncodeResult encodeResult = JsonCodec.encode(
  593. getState(false).columnOrder, diffState, type,
  594. connectorTracker);
  595. diffState.put(diffStateKey, encodeResult.getEncodedValue());
  596. fireColumnReorderEvent(true);
  597. } else {
  598. // make sure the client is reverted to the order that the
  599. // server thinks it is
  600. diffState.remove(diffStateKey);
  601. markAsDirty();
  602. }
  603. }
  604. @Override
  605. public void columnVisibilityChanged(String internalId, boolean hidden) {
  606. Column<T, ?> column = getColumnByInternalId(internalId);
  607. ColumnState columnState = column.getState(false);
  608. if (columnState.hidden != hidden) {
  609. columnState.hidden = hidden;
  610. fireColumnVisibilityChangeEvent(column, hidden, true);
  611. }
  612. }
  613. @Override
  614. public void columnResized(String internalId, double pixels) {
  615. final Column<T, ?> column = getColumnByInternalId(internalId);
  616. if (column != null && column.isResizable()) {
  617. column.getState().width = pixels;
  618. fireColumnResizeEvent(column, true);
  619. }
  620. }
  621. }
  622. /**
  623. * Class for managing visible details rows.
  624. *
  625. * @param <T>
  626. * the grid bean type
  627. */
  628. public static class DetailsManager<T> extends AbstractGridExtension<T> {
  629. private final Set<T> visibleDetails = new HashSet<>();
  630. private final Map<T, Component> components = new HashMap<>();
  631. private DetailsGenerator<T> generator;
  632. /**
  633. * Sets the details component generator.
  634. *
  635. * @param generator
  636. * the generator for details components
  637. */
  638. public void setDetailsGenerator(DetailsGenerator<T> generator) {
  639. if (this.generator != generator) {
  640. removeAllComponents();
  641. }
  642. this.generator = generator;
  643. visibleDetails.forEach(this::refresh);
  644. }
  645. @Override
  646. public void remove() {
  647. removeAllComponents();
  648. super.remove();
  649. }
  650. private void removeAllComponents() {
  651. // Clean up old components
  652. components.values().forEach(this::removeComponentFromGrid);
  653. components.clear();
  654. }
  655. @Override
  656. public void generateData(T data, JsonObject jsonObject) {
  657. if (generator == null || !visibleDetails.contains(data)) {
  658. return;
  659. }
  660. if (!components.containsKey(data)) {
  661. Component detailsComponent = generator.apply(data);
  662. Objects.requireNonNull(detailsComponent,
  663. "Details generator can't create null components");
  664. if (detailsComponent.getParent() != null) {
  665. throw new IllegalStateException(
  666. "Details component was already attached");
  667. }
  668. addComponentToGrid(detailsComponent);
  669. components.put(data, detailsComponent);
  670. }
  671. jsonObject.put(GridState.JSONKEY_DETAILS_VISIBLE,
  672. components.get(data).getConnectorId());
  673. }
  674. @Override
  675. public void destroyData(T data) {
  676. // No clean up needed. Components are removed when hiding details
  677. // and/or changing details generator
  678. }
  679. /**
  680. * Sets the visibility of details component for given item.
  681. *
  682. * @param data
  683. * the item to show details for
  684. * @param visible
  685. * {@code true} if details component should be visible;
  686. * {@code false} if it should be hidden
  687. */
  688. public void setDetailsVisible(T data, boolean visible) {
  689. boolean refresh = false;
  690. if (!visible) {
  691. refresh = visibleDetails.remove(data);
  692. if (components.containsKey(data)) {
  693. removeComponentFromGrid(components.remove(data));
  694. }
  695. } else {
  696. refresh = visibleDetails.add(data);
  697. }
  698. if (refresh) {
  699. refresh(data);
  700. }
  701. }
  702. /**
  703. * Returns the visibility of details component for given item.
  704. *
  705. * @param data
  706. * the item to show details for
  707. *
  708. * @return {@code true} if details component should be visible;
  709. * {@code false} if it should be hidden
  710. */
  711. public boolean isDetailsVisible(T data) {
  712. return visibleDetails.contains(data);
  713. }
  714. @Override
  715. public Grid<T> getParent() {
  716. return super.getParent();
  717. }
  718. @Override
  719. protected DetailsManagerState getState() {
  720. return (DetailsManagerState) super.getState();
  721. }
  722. @Override
  723. protected DetailsManagerState getState(boolean markAsDirty) {
  724. return (DetailsManagerState) super.getState(markAsDirty);
  725. }
  726. }
  727. /**
  728. * This extension manages the configuration and data communication for a
  729. * Column inside of a Grid component.
  730. *
  731. * @param <T>
  732. * the grid bean type
  733. * @param <V>
  734. * the column value type
  735. */
  736. public static class Column<T, V> extends AbstractGridExtension<T> {
  737. private final SerializableFunction<T, ? extends V> valueProvider;
  738. private SortOrderProvider sortOrderProvider = direction -> {
  739. String id = getId();
  740. if (id == null) {
  741. return Stream.empty();
  742. } else {
  743. return Stream.of(new QuerySortOrder(id, direction));
  744. }
  745. };
  746. private SerializableComparator<T> comparator;
  747. private StyleGenerator<T> styleGenerator = item -> null;
  748. private DescriptionGenerator<T> descriptionGenerator;
  749. private Binding<T, ?> editorBinding;
  750. private Map<T, Component> activeComponents = new HashMap<>();
  751. private String userId;
  752. /**
  753. * Constructs a new Column configuration with given renderer and value
  754. * provider.
  755. *
  756. * @param valueProvider
  757. * the function to get values from items, not
  758. * <code>null</code>
  759. * @param renderer
  760. * the type of value, not <code>null</code>
  761. */
  762. protected Column(ValueProvider<T, V> valueProvider,
  763. Renderer<? super V> renderer) {
  764. Objects.requireNonNull(valueProvider,
  765. "Value provider can't be null");
  766. Objects.requireNonNull(renderer, "Renderer can't be null");
  767. ColumnState state = getState();
  768. this.valueProvider = valueProvider;
  769. state.renderer = renderer;
  770. state.caption = "";
  771. // Add the renderer as a child extension of this extension, thus
  772. // ensuring the renderer will be unregistered when this column is
  773. // removed
  774. addExtension(renderer);
  775. Class<? super V> valueType = renderer.getPresentationType();
  776. if (Comparable.class.isAssignableFrom(valueType)) {
  777. comparator = (a, b) -> compareComparables(
  778. valueProvider.apply(a), valueProvider.apply(b));
  779. } else if (Number.class.isAssignableFrom(valueType)) {
  780. /*
  781. * Value type will be Number whenever using NumberRenderer.
  782. * Provide explicit comparison support in this case even though
  783. * Number itself isn't Comparable.
  784. */
  785. comparator = (a, b) -> compareNumbers(
  786. (Number) valueProvider.apply(a),
  787. (Number) valueProvider.apply(b));
  788. } else {
  789. comparator = (a, b) -> compareMaybeComparables(
  790. valueProvider.apply(a), valueProvider.apply(b));
  791. }
  792. }
  793. private static int compareMaybeComparables(Object a, Object b) {
  794. if (hasCommonComparableBaseType(a, b)) {
  795. return compareComparables(a, b);
  796. } else {
  797. return compareComparables(Objects.toString(a, ""),
  798. Objects.toString(b, ""));
  799. }
  800. }
  801. private static boolean hasCommonComparableBaseType(Object a, Object b) {
  802. if (a instanceof Comparable<?> && b instanceof Comparable<?>) {
  803. Class<?> aClass = a.getClass();
  804. Class<?> bClass = b.getClass();
  805. if (aClass == bClass) {
  806. return true;
  807. }
  808. Class<?> baseType = ReflectTools.findCommonBaseType(aClass,
  809. bClass);
  810. if (Comparable.class.isAssignableFrom(baseType)) {
  811. return true;
  812. }
  813. }
  814. if ((a == null && b instanceof Comparable<?>)
  815. || (b == null && a instanceof Comparable<?>)) {
  816. return true;
  817. }
  818. return false;
  819. }
  820. @SuppressWarnings("unchecked")
  821. private static int compareComparables(Object a, Object b) {
  822. return ((Comparator) Comparator
  823. .nullsLast(Comparator.naturalOrder())).compare(a, b);
  824. }
  825. @SuppressWarnings("unchecked")
  826. private static int compareNumbers(Number a, Number b) {
  827. Number valueA = a != null ? a : Double.POSITIVE_INFINITY;
  828. Number valueB = b != null ? b : Double.POSITIVE_INFINITY;
  829. // Most Number implementations are Comparable
  830. if (valueA instanceof Comparable
  831. && valueA.getClass().isInstance(valueB)) {
  832. return ((Comparable<Number>) valueA).compareTo(valueB);
  833. } else if (valueA.equals(valueB)) {
  834. return 0;
  835. } else {
  836. // Fall back to comparing based on potentially truncated values
  837. int compare = Long.compare(valueA.longValue(),
  838. valueB.longValue());
  839. if (compare == 0) {
  840. // This might still produce 0 even though the values are not
  841. // equals, but there's nothing more we can do about that
  842. compare = Double.compare(valueA.doubleValue(),
  843. valueB.doubleValue());
  844. }
  845. return compare;
  846. }
  847. }
  848. @Override
  849. public void generateData(T data, JsonObject jsonObject) {
  850. ColumnState state = getState(false);
  851. String communicationId = getConnectorId();
  852. assert communicationId != null : "No communication ID set for column "
  853. + state.caption;
  854. @SuppressWarnings("unchecked")
  855. Renderer<V> renderer = (Renderer<V>) state.renderer;
  856. JsonObject obj = getDataObject(jsonObject,
  857. DataCommunicatorConstants.DATA);
  858. V providerValue = valueProvider.apply(data);
  859. // Make Grid track components.
  860. if (renderer instanceof ComponentRenderer
  861. && providerValue instanceof Component) {
  862. addComponent(data, (Component) providerValue);
  863. }
  864. JsonValue rendererValue = renderer.encode(providerValue);
  865. obj.put(communicationId, rendererValue);
  866. String style = styleGenerator.apply(data);
  867. if (style != null && !style.isEmpty()) {
  868. JsonObject styleObj = getDataObject(jsonObject,
  869. GridState.JSONKEY_CELLSTYLES);
  870. styleObj.put(communicationId, style);
  871. }
  872. if (descriptionGenerator != null) {
  873. String description = descriptionGenerator.apply(data);
  874. if (description != null && !description.isEmpty()) {
  875. JsonObject descriptionObj = getDataObject(jsonObject,
  876. GridState.JSONKEY_CELLDESCRIPTION);
  877. descriptionObj.put(communicationId, description);
  878. }
  879. }
  880. }
  881. private void addComponent(T data, Component component) {
  882. if (activeComponents.containsKey(data)) {
  883. if (activeComponents.get(data).equals(component)) {
  884. // Reusing old component
  885. return;
  886. }
  887. removeComponent(data);
  888. }
  889. activeComponents.put(data, component);
  890. addComponentToGrid(component);
  891. }
  892. @Override
  893. public void destroyData(T item) {
  894. removeComponent(item);
  895. }
  896. private void removeComponent(T item) {
  897. Component component = activeComponents.remove(item);
  898. if (component != null) {
  899. removeComponentFromGrid(component);
  900. }
  901. }
  902. @Override
  903. public void destroyAllData() {
  904. // Make a defensive copy of keys, as the map gets cleared when
  905. // removing components.
  906. new HashSet<>(activeComponents.keySet())
  907. .forEach(this::removeComponent);
  908. }
  909. /**
  910. * Gets a data object with the given key from the given JsonObject. If
  911. * there is no object with the key, this method creates a new
  912. * JsonObject.
  913. *
  914. * @param jsonObject
  915. * the json object
  916. * @param key
  917. * the key where the desired data object is stored
  918. * @return data object for the given key
  919. */
  920. private JsonObject getDataObject(JsonObject jsonObject, String key) {
  921. if (!jsonObject.hasKey(key)) {
  922. jsonObject.put(key, Json.createObject());
  923. }
  924. return jsonObject.getObject(key);
  925. }
  926. @Override
  927. protected ColumnState getState() {
  928. return getState(true);
  929. }
  930. @Override
  931. protected ColumnState getState(boolean markAsDirty) {
  932. return (ColumnState) super.getState(markAsDirty);
  933. }
  934. /**
  935. * This method extends the given Grid with this Column.
  936. *
  937. * @param grid
  938. * the grid to extend
  939. */
  940. private void extend(Grid<T> grid) {
  941. super.extend(grid);
  942. }
  943. /**
  944. * Returns the identifier used with this Column in communication.
  945. *
  946. * @return the identifier string
  947. */
  948. private String getInternalId() {
  949. return getState(false).internalId;
  950. }
  951. /**
  952. * Sets the identifier to use with this Column in communication.
  953. *
  954. * @param id
  955. * the identifier string
  956. */
  957. private void setInternalId(String id) {
  958. Objects.requireNonNull(id, "Communication ID can't be null");
  959. getState().internalId = id;
  960. }
  961. /**
  962. * Returns the user-defined identifier for this column.
  963. *
  964. * @return the identifier string
  965. */
  966. public String getId() {
  967. return userId;
  968. }
  969. /**
  970. * Sets the user-defined identifier to map this column. The identifier
  971. * can be used for example in {@link Grid#getColumn(String)}.
  972. * <p>
  973. * The id is also used as the {@link #setSortProperty(String...) backend
  974. * sort property} for this column if no sort property or sort order
  975. * provider has been set for this column.
  976. *
  977. * @see #setSortProperty(String...)
  978. * @see #setSortOrderProvider(SortOrderProvider)
  979. *
  980. * @param id
  981. * the identifier string
  982. * @return this column
  983. */
  984. public Column<T, V> setId(String id) {
  985. Objects.requireNonNull(id, "Column identifier cannot be null");
  986. if (this.userId != null) {
  987. throw new IllegalStateException(
  988. "Column identifier cannot be changed");
  989. }
  990. this.userId = id;
  991. getGrid().setColumnId(id, this);
  992. return this;
  993. }
  994. /**
  995. * Gets the function used to produce the value for data in this column
  996. * based on the row item.
  997. *
  998. * @return the value provider function
  999. */
  1000. public SerializableFunction<T, ? extends V> getValueProvider() {
  1001. return valueProvider;
  1002. }
  1003. /**
  1004. * Sets whether the user can sort this column or not.
  1005. *
  1006. * @param sortable
  1007. * {@code true} if the column can be sorted by the user;
  1008. * {@code false} if not
  1009. * @return this column
  1010. */
  1011. public Column<T, V> setSortable(boolean sortable) {
  1012. getState().sortable = sortable;
  1013. return this;
  1014. }
  1015. /**
  1016. * Gets whether the user can sort this column or not.
  1017. *
  1018. * @return {@code true} if the column can be sorted by the user;
  1019. * {@code false} if not
  1020. */
  1021. public boolean isSortable() {
  1022. return getState(false).sortable;
  1023. }
  1024. /**
  1025. * Sets the header caption for this column.
  1026. *
  1027. * @param caption
  1028. * the header caption, not null
  1029. *
  1030. * @return this column
  1031. */
  1032. public Column<T, V> setCaption(String caption) {
  1033. Objects.requireNonNull(caption, "Header caption can't be null");
  1034. if (caption.equals(getState(false).caption)) {
  1035. return this;
  1036. }
  1037. getState().caption = caption;
  1038. HeaderRow row = getGrid().getDefaultHeaderRow();
  1039. if (row != null) {
  1040. row.getCell(this).setText(caption);
  1041. }
  1042. return this;
  1043. }
  1044. /**
  1045. * Gets the header caption for this column.
  1046. *
  1047. * @return header caption
  1048. */
  1049. public String getCaption() {
  1050. return getState(false).caption;
  1051. }
  1052. /**
  1053. * Sets a comparator to use with in-memory sorting with this column.
  1054. * Sorting with a back-end is done using
  1055. * {@link Column#setSortProperty(String...)}.
  1056. *
  1057. * @param comparator
  1058. * the comparator to use when sorting data in this column
  1059. * @return this column
  1060. */
  1061. public Column<T, V> setComparator(
  1062. SerializableComparator<T> comparator) {
  1063. Objects.requireNonNull(comparator, "Comparator can't be null");
  1064. this.comparator = comparator;
  1065. return this;
  1066. }
  1067. /**
  1068. * Gets the comparator to use with in-memory sorting for this column
  1069. * when sorting in the given direction.
  1070. *
  1071. * @param sortDirection
  1072. * the direction this column is sorted by
  1073. * @return comparator for this column
  1074. */
  1075. public SerializableComparator<T> getComparator(
  1076. SortDirection sortDirection) {
  1077. Objects.requireNonNull(comparator,
  1078. "No comparator defined for sorted column.");
  1079. boolean reverse = sortDirection != SortDirection.ASCENDING;
  1080. return reverse ? (t1, t2) -> comparator.reversed().compare(t1, t2)
  1081. : comparator;
  1082. }
  1083. /**
  1084. * Sets strings describing back end properties to be used when sorting
  1085. * this column.
  1086. * <p>
  1087. * By default, the {@link #setId(String) column id} will be used as the
  1088. * sort property.
  1089. *
  1090. * @param properties
  1091. * the array of strings describing backend properties
  1092. * @return this column
  1093. */
  1094. public Column<T, V> setSortProperty(String... properties) {
  1095. Objects.requireNonNull(properties, "Sort properties can't be null");
  1096. sortOrderProvider = dir -> Arrays.stream(properties)
  1097. .map(s -> new QuerySortOrder(s, dir));
  1098. return this;
  1099. }
  1100. /**
  1101. * Sets the sort orders when sorting this column. The sort order
  1102. * provider is a function which provides {@link QuerySortOrder} objects
  1103. * to describe how to sort by this column.
  1104. * <p>
  1105. * By default, the {@link #setId(String) column id} will be used as the
  1106. * sort property.
  1107. *
  1108. * @param provider
  1109. * the function to use when generating sort orders with the
  1110. * given direction
  1111. * @return this column
  1112. */
  1113. public Column<T, V> setSortOrderProvider(SortOrderProvider provider) {
  1114. Objects.requireNonNull(provider,
  1115. "Sort order provider can't be null");
  1116. sortOrderProvider = provider;
  1117. return this;
  1118. }
  1119. /**
  1120. * Gets the sort orders to use with back-end sorting for this column
  1121. * when sorting in the given direction.
  1122. *
  1123. * @see #setSortProperty(String...)
  1124. * @see #setId(String)
  1125. * @see #setSortOrderProvider(SortOrderProvider)
  1126. *
  1127. * @param direction
  1128. * the sorting direction
  1129. * @return stream of sort orders
  1130. */
  1131. public Stream<QuerySortOrder> getSortOrder(SortDirection direction) {
  1132. return sortOrderProvider.apply(direction);
  1133. }
  1134. /**
  1135. * Sets the style generator that is used for generating class names for
  1136. * cells in this column. Returning null from the generator results in no
  1137. * custom style name being set.
  1138. *
  1139. * @param cellStyleGenerator
  1140. * the cell style generator to set, not null
  1141. * @return this column
  1142. * @throws NullPointerException
  1143. * if {@code cellStyleGenerator} is {@code null}
  1144. */
  1145. public Column<T, V> setStyleGenerator(
  1146. StyleGenerator<T> cellStyleGenerator) {
  1147. Objects.requireNonNull(cellStyleGenerator,
  1148. "Cell style generator must not be null");
  1149. this.styleGenerator = cellStyleGenerator;
  1150. getGrid().getDataCommunicator().reset();
  1151. return this;
  1152. }
  1153. /**
  1154. * Gets the style generator that is used for generating styles for
  1155. * cells.
  1156. *
  1157. * @return the cell style generator
  1158. */
  1159. public StyleGenerator<T> getStyleGenerator() {
  1160. return styleGenerator;
  1161. }
  1162. /**
  1163. * Sets the description generator that is used for generating
  1164. * descriptions for cells in this column.
  1165. *
  1166. * @param cellDescriptionGenerator
  1167. * the cell description generator to set, or
  1168. * <code>null</code> to remove a previously set generator
  1169. * @return this column
  1170. */
  1171. public Column<T, V> setDescriptionGenerator(
  1172. DescriptionGenerator<T> cellDescriptionGenerator) {
  1173. this.descriptionGenerator = cellDescriptionGenerator;
  1174. getGrid().getDataCommunicator().reset();
  1175. return this;
  1176. }
  1177. /**
  1178. * Gets the description generator that is used for generating
  1179. * descriptions for cells.
  1180. *
  1181. * @return the cell description generator, or <code>null</code> if no
  1182. * generator is set
  1183. */
  1184. public DescriptionGenerator<T> getDescriptionGenerator() {
  1185. return descriptionGenerator;
  1186. }
  1187. /**
  1188. * Sets the ratio with which the column expands.
  1189. * <p>
  1190. * By default, all columns expand equally (treated as if all of them had
  1191. * an expand ratio of 1). Once at least one column gets a defined expand
  1192. * ratio, the implicit expand ratio is removed, and only the defined
  1193. * expand ratios are taken into account.
  1194. * <p>
  1195. * If a column has a defined width ({@link #setWidth(double)}), it
  1196. * overrides this method's effects.
  1197. * <p>
  1198. * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
  1199. * and 2, respectively. The column with a <strong>ratio of 0 is exactly
  1200. * as wide as its contents requires</strong>. The column with a ratio of
  1201. * 1 is as wide as it needs, <strong>plus a third of any excess
  1202. * space</strong>, because we have 3 parts total, and this column
  1203. * reserves only one of those. The column with a ratio of 2, is as wide
  1204. * as it needs to be, <strong>plus two thirds</strong> of the excess
  1205. * width.
  1206. *
  1207. * @param expandRatio
  1208. * the expand ratio of this column. {@code 0} to not have it
  1209. * expand at all. A negative number to clear the expand
  1210. * value.
  1211. * @throws IllegalStateException
  1212. * if the column is no longer attached to any grid
  1213. * @see #setWidth(double)
  1214. */
  1215. public Column<T, V> setExpandRatio(int expandRatio)
  1216. throws IllegalStateException {
  1217. checkColumnIsAttached();
  1218. if (expandRatio != getExpandRatio()) {
  1219. getState().expandRatio = expandRatio;
  1220. getGrid().markAsDirty();
  1221. }
  1222. return this;
  1223. }
  1224. /**
  1225. * Returns the column's expand ratio.
  1226. *
  1227. * @return the column's expand ratio
  1228. * @see #setExpandRatio(int)
  1229. */
  1230. public int getExpandRatio() {
  1231. return getState(false).expandRatio;
  1232. }
  1233. /**
  1234. * Clears the expand ratio for this column.
  1235. * <p>
  1236. * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
  1237. *
  1238. * @throws IllegalStateException
  1239. * if the column is no longer attached to any grid
  1240. */
  1241. public Column<T, V> clearExpandRatio() throws IllegalStateException {
  1242. return setExpandRatio(-1);
  1243. }
  1244. /**
  1245. * Returns the width (in pixels). By default a column is 100px wide.
  1246. *
  1247. * @return the width in pixels of the column
  1248. * @throws IllegalStateException
  1249. * if the column is no longer attached to any grid
  1250. */
  1251. public double getWidth() throws IllegalStateException {
  1252. checkColumnIsAttached();
  1253. return getState(false).width;
  1254. }
  1255. /**
  1256. * Sets the width (in pixels).
  1257. * <p>
  1258. * This overrides any configuration set by any of
  1259. * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
  1260. * {@link #setMaximumWidth(double)}.
  1261. *
  1262. * @param pixelWidth
  1263. * the new pixel width of the column
  1264. * @return the column itself
  1265. *
  1266. * @throws IllegalStateException
  1267. * if the column is no longer attached to any grid
  1268. * @throws IllegalArgumentException
  1269. * thrown if pixel width is less than zero
  1270. */
  1271. public Column<T, V> setWidth(double pixelWidth)
  1272. throws IllegalStateException, IllegalArgumentException {
  1273. checkColumnIsAttached();
  1274. if (pixelWidth < 0) {
  1275. throw new IllegalArgumentException(
  1276. "Pixel width should be greated than 0 (in " + toString()
  1277. + ")");
  1278. }
  1279. if (pixelWidth != getWidth()) {
  1280. getState().width = pixelWidth;
  1281. getGrid().markAsDirty();
  1282. getGrid().fireColumnResizeEvent(this, false);
  1283. }
  1284. return this;
  1285. }
  1286. /**
  1287. * Returns whether this column has an undefined width.
  1288. *
  1289. * @since 7.6
  1290. * @return whether the width is undefined
  1291. * @throws IllegalStateException
  1292. * if the column is no longer attached to any grid
  1293. */
  1294. public boolean isWidthUndefined() {
  1295. checkColumnIsAttached();
  1296. return getState(false).width < 0;
  1297. }
  1298. /**
  1299. * Marks the column width as undefined. An undefined width means the
  1300. * grid is free to resize the column based on the cell contents and
  1301. * available space in the grid.
  1302. *
  1303. * @return the column itself
  1304. */
  1305. public Column<T, V> setWidthUndefined() {
  1306. checkColumnIsAttached();
  1307. if (!isWidthUndefined()) {
  1308. getState().width = -1;
  1309. getGrid().markAsDirty();
  1310. getGrid().fireColumnResizeEvent(this, false);
  1311. }
  1312. return this;
  1313. }
  1314. /**
  1315. * Sets the minimum width for this column.
  1316. * <p>
  1317. * This defines the minimum guaranteed pixel width of the column
  1318. * <em>when it is set to expand</em>.
  1319. *
  1320. * @throws IllegalStateException
  1321. * if the column is no longer attached to any grid
  1322. * @see #setExpandRatio(int)
  1323. */
  1324. public Column<T, V> setMinimumWidth(double pixels)
  1325. throws IllegalStateException {
  1326. checkColumnIsAttached();
  1327. final double maxwidth = getMaximumWidth();
  1328. if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
  1329. throw new IllegalArgumentException("New minimum width ("
  1330. + pixels + ") was greater than maximum width ("
  1331. + maxwidth + ")");
  1332. }
  1333. getState().minWidth = pixels;
  1334. getGrid().markAsDirty();
  1335. return this;
  1336. }
  1337. /**
  1338. * Return the minimum width for this column.
  1339. *
  1340. * @return the minimum width for this column
  1341. * @see #setMinimumWidth(double)
  1342. */
  1343. public double getMinimumWidth() {
  1344. return getState(false).minWidth;
  1345. }
  1346. /**
  1347. * Sets the maximum width for this column.
  1348. * <p>
  1349. * This defines the maximum allowed pixel width of the column <em>when
  1350. * it is set to expand</em>.
  1351. *
  1352. * @param pixels
  1353. * the maximum width
  1354. * @throws IllegalStateException
  1355. * if the column is no longer attached to any grid
  1356. * @see #setExpandRatio(int)
  1357. */
  1358. public Column<T, V> setMaximumWidth(double pixels) {
  1359. checkColumnIsAttached();
  1360. final double minwidth = getMinimumWidth();
  1361. if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
  1362. throw new IllegalArgumentException("New maximum width ("
  1363. + pixels + ") was less than minimum width (" + minwidth
  1364. + ")");
  1365. }
  1366. getState().maxWidth = pixels;
  1367. getGrid().markAsDirty();
  1368. return this;
  1369. }
  1370. /**
  1371. * Returns the maximum width for this column.
  1372. *
  1373. * @return the maximum width for this column
  1374. * @see #setMaximumWidth(double)
  1375. */
  1376. public double getMaximumWidth() {
  1377. return getState(false).maxWidth;
  1378. }
  1379. /**
  1380. * Sets whether this column can be resized by the user.
  1381. *
  1382. * @since 7.6
  1383. * @param resizable
  1384. * {@code true} if this column should be resizable,
  1385. * {@code false} otherwise
  1386. * @throws IllegalStateException
  1387. * if the column is no longer attached to any grid
  1388. */
  1389. public Column<T, V> setResizable(boolean resizable) {
  1390. checkColumnIsAttached();
  1391. if (resizable != isResizable()) {
  1392. getState().resizable = resizable;
  1393. getGrid().markAsDirty();
  1394. }
  1395. return this;
  1396. }
  1397. /**
  1398. * Gets the caption of the hiding toggle for this column.
  1399. *
  1400. * @since 7.5.0
  1401. * @see #setHidingToggleCaption(String)
  1402. * @return the caption for the hiding toggle for this column
  1403. */
  1404. public String getHidingToggleCaption() {
  1405. return getState(false).hidingToggleCaption;
  1406. }
  1407. /**
  1408. * Sets the caption of the hiding toggle for this column. Shown in the
  1409. * toggle for this column in the grid's sidebar when the column is
  1410. * {@link #isHidable() hidable}.
  1411. * <p>
  1412. * The default value is <code>null</code>, and in that case the column's
  1413. * {@link #getCaption() header caption} is used.
  1414. * <p>
  1415. * <em>NOTE:</em> setting this to empty string might cause the hiding
  1416. * toggle to not render correctly.
  1417. *
  1418. * @since 7.5.0
  1419. * @param hidingToggleCaption
  1420. * the text to show in the column hiding toggle
  1421. * @return the column itself
  1422. */
  1423. public Column<T, V> setHidingToggleCaption(String hidingToggleCaption) {
  1424. if (hidingToggleCaption != getHidingToggleCaption()) {
  1425. getState().hidingToggleCaption = hidingToggleCaption;
  1426. }
  1427. return this;
  1428. }
  1429. /**
  1430. * Hides or shows the column. By default columns are visible before
  1431. * explicitly hiding them.
  1432. *
  1433. * @since 7.5.0
  1434. * @param hidden
  1435. * <code>true</code> to hide the column, <code>false</code>
  1436. * to show
  1437. * @return this column
  1438. * @throws IllegalStateException
  1439. * if the column is no longer attached to any grid
  1440. */
  1441. public Column<T, V> setHidden(boolean hidden) {
  1442. checkColumnIsAttached();
  1443. if (hidden != isHidden()) {
  1444. getState().hidden = hidden;
  1445. getGrid().fireColumnVisibilityChangeEvent(this, hidden, false);
  1446. }
  1447. return this;
  1448. }
  1449. /**
  1450. * Returns whether this column is hidden. Default is {@code false}.
  1451. *
  1452. * @since 7.5.0
  1453. * @return <code>true</code> if the column is currently hidden,
  1454. * <code>false</code> otherwise
  1455. */
  1456. public boolean isHidden() {
  1457. return getState(false).hidden;
  1458. }
  1459. /**
  1460. * Sets whether this column can be hidden by the user. Hidable columns
  1461. * can be hidden and shown via the sidebar menu.
  1462. *
  1463. * @since 7.5.0
  1464. * @param hidable
  1465. * <code>true</code> iff the column may be hidable by the
  1466. * user via UI interaction
  1467. * @return this column
  1468. */
  1469. public Column<T, V> setHidable(boolean hidable) {
  1470. if (hidable != isHidable()) {
  1471. getState().hidable = hidable;
  1472. }
  1473. return this;
  1474. }
  1475. /**
  1476. * Returns whether this column can be hidden by the user. Default is
  1477. * {@code false}.
  1478. * <p>
  1479. * <em>Note:</em> the column can be programmatically hidden using
  1480. * {@link #setHidden(boolean)} regardless of the returned value.
  1481. *
  1482. * @since 7.5.0
  1483. * @return <code>true</code> if the user can hide the column,
  1484. * <code>false</code> if not
  1485. */
  1486. public boolean isHidable() {
  1487. return getState(false).hidable;
  1488. }
  1489. /**
  1490. * Returns whether this column can be resized by the user. Default is
  1491. * {@code true}.
  1492. * <p>
  1493. * <em>Note:</em> the column can be programmatically resized using
  1494. * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
  1495. * of the returned value.
  1496. *
  1497. * @since 7.6
  1498. * @return {@code true} if this column is resizable, {@code false}
  1499. * otherwise
  1500. */
  1501. public boolean isResizable() {
  1502. return getState(false).resizable;
  1503. }
  1504. /**
  1505. * Sets whether this Column has a component displayed in Editor or not.
  1506. * A column can only be editable if an editor component or binding has
  1507. * been set.
  1508. *
  1509. * @param editable
  1510. * {@code true} if column is editable; {@code false} if not
  1511. * @return this column
  1512. *
  1513. * @see #setEditorComponent(HasValue, Setter)
  1514. * @see #setEditorBinding(Binding)
  1515. */
  1516. public Column<T, V> setEditable(boolean editable) {
  1517. Objects.requireNonNull(editorBinding,
  1518. "Column has no editor binding or component defined");
  1519. getState().editable = editable;
  1520. return this;
  1521. }
  1522. /**
  1523. * Gets whether this Column has a component displayed in Editor or not.
  1524. *
  1525. * @return {@code true} if the column displays an editor component;
  1526. * {@code false} if not
  1527. */
  1528. public boolean isEditable() {
  1529. return getState(false).editable;
  1530. }
  1531. /**
  1532. * Sets an editor binding for this column. The {@link Binding} is used
  1533. * when a row is in editor mode to define how to populate an editor
  1534. * component based on the edited row and how to update an item based on
  1535. * the value in the editor component.
  1536. * <p>
  1537. * To create a binding to use with a column, define a binding for the
  1538. * editor binder (<code>grid.getEditor().getBinder()</code>) using e.g.
  1539. * {@link Binder#forField(HasValue)}. You can also use
  1540. * {@link #setEditorComponent(HasValue, Setter)} if no validator or
  1541. * converter is needed for the binding.
  1542. * <p>
  1543. * The {@link HasValue} that the binding is defined to use must be a
  1544. * {@link Component}.
  1545. *
  1546. * @param binding
  1547. * the binding to use for this column
  1548. * @return this column
  1549. *
  1550. * @see #setEditorComponent(HasValue, Setter)
  1551. * @see Binding
  1552. * @see Grid#getEditor()
  1553. * @see Editor#getBinder()
  1554. */
  1555. public Column<T, V> setEditorBinding(Binding<T, ?> binding) {
  1556. Objects.requireNonNull(binding, "null is not a valid editor field");
  1557. if (!(binding.getField() instanceof Component)) {
  1558. throw new IllegalArgumentException(
  1559. "Binding target must be a component.");
  1560. }
  1561. this.editorBinding = binding;
  1562. return setEditable(true);
  1563. }
  1564. /**
  1565. * Gets the binder binding that is currently used for this column.
  1566. *
  1567. * @return the used binder binding, or <code>null</code> if no binding
  1568. * is configured
  1569. *
  1570. * @see #setEditorBinding(Binding)
  1571. */
  1572. public Binding<T, ?> getEditorBinding() {
  1573. return editorBinding;
  1574. }
  1575. /**
  1576. * Sets a component and setter to use for editing values of this column
  1577. * in the editor row. This is a shorthand for use in simple cases where
  1578. * no validator or converter is needed. Use
  1579. * {@link #setEditorBinding(Binding)} to support more complex cases.
  1580. * <p>
  1581. * <strong>Note:</strong> The same component cannot be used for multiple
  1582. * columns.
  1583. *
  1584. * @param editorComponent
  1585. * the editor component
  1586. * @param setter
  1587. * a setter that stores the component value in the row item
  1588. * @return this column
  1589. *
  1590. * @see #setEditorBinding(Binding)
  1591. * @see Grid#getEditor()
  1592. * @see Binder#bind(HasValue, ValueProvider, Setter)
  1593. */
  1594. public <C extends HasValue<V> & Component> Column<T, V> setEditorComponent(
  1595. C editorComponent, Setter<T, V> setter) {
  1596. Objects.requireNonNull(editorComponent,
  1597. "Editor component cannot be null");
  1598. Objects.requireNonNull(setter, "Setter cannot be null");
  1599. Binding<T, V> binding = getGrid().getEditor().getBinder()
  1600. .bind(editorComponent, valueProvider::apply, setter);
  1601. return setEditorBinding(binding);
  1602. }
  1603. /**
  1604. * Sets a component to use for editing values of this columns in the
  1605. * editor row. This method can only be used if the column has an
  1606. * {@link #setId(String) id} and the {@link Grid} has been created using
  1607. * {@link Grid#Grid(Class)} or some other way that allows finding
  1608. * properties based on property names.
  1609. * <p>
  1610. * This is a shorthand for use in simple cases where no validator or
  1611. * converter is needed. Use {@link #setEditorBinding(Binding)} to
  1612. * support more complex cases.
  1613. * <p>
  1614. * <strong>Note:</strong> The same component cannot be used for multiple
  1615. * columns.
  1616. *
  1617. * @param editorComponent
  1618. * the editor component
  1619. * @return this column
  1620. *
  1621. * @see #setEditorBinding(Binding)
  1622. * @see Grid#getEditor()
  1623. * @see Binder#bind(HasValue, String)
  1624. * @see Grid#Grid(Class)
  1625. */
  1626. public <F, C extends HasValue<F> & Component> Column<T, V> setEditorComponent(
  1627. C editorComponent) {
  1628. Objects.requireNonNull(editorComponent,
  1629. "Editor component cannot be null");
  1630. String propertyName = getId();
  1631. if (propertyName == null) {
  1632. throw new IllegalStateException(
  1633. "setEditorComponent without a setter can only be used if the column has an id. "
  1634. + "Use another setEditorComponent(Component, Setter) or setEditorBinding(Binding) instead.");
  1635. }
  1636. Binding<T, F> binding = getGrid().getEditor().getBinder()
  1637. .bind(editorComponent, propertyName);
  1638. return setEditorBinding(binding);
  1639. }
  1640. /**
  1641. * Sets the Renderer for this Column. Setting the renderer will cause
  1642. * all currently available row data to be recreated and sent to the
  1643. * client.
  1644. *
  1645. * @param renderer
  1646. * the new renderer
  1647. * @return this column
  1648. */
  1649. public Column<T, V> setRenderer(Renderer<? super V> renderer) {
  1650. Objects.requireNonNull(renderer, "Renderer can't be null");
  1651. // Remove old renderer
  1652. Connector oldRenderer = getState().renderer;
  1653. if (oldRenderer != null && oldRenderer instanceof Extension) {
  1654. removeExtension((Extension) oldRenderer);
  1655. }
  1656. // Set new renderer
  1657. getState().renderer = renderer;
  1658. addExtension(renderer);
  1659. // Trigger redraw
  1660. getParent().getDataCommunicator().reset();
  1661. return this;
  1662. }
  1663. /**
  1664. * Gets the grid that this column belongs to.
  1665. *
  1666. * @return the grid that this column belongs to, or <code>null</code> if
  1667. * this column has not yet been associated with any grid
  1668. */
  1669. protected Grid<T> getGrid() {
  1670. return getParent();
  1671. }
  1672. /**
  1673. * Checks if column is attached and throws an
  1674. * {@link IllegalStateException} if it is not.
  1675. *
  1676. * @throws IllegalStateException
  1677. * if the column is no longer attached to any grid
  1678. */
  1679. protected void checkColumnIsAttached() throws IllegalStateException {
  1680. if (getGrid() == null) {
  1681. throw new IllegalStateException(
  1682. "Column is no longer attached to a grid.");
  1683. }
  1684. }
  1685. /**
  1686. * Writes the design attributes for this column into given element.
  1687. *
  1688. * @since 7.5.0
  1689. *
  1690. * @param element
  1691. * Element to write attributes into
  1692. *
  1693. * @param designContext
  1694. * the design context
  1695. */
  1696. protected void writeDesign(Element element,
  1697. DesignContext designContext) {
  1698. Attributes attributes = element.attributes();
  1699. ColumnState defaultState = new ColumnState();
  1700. if (getId() == null) {
  1701. setId("column" + getGrid().getColumns().indexOf(this));
  1702. }
  1703. DesignAttributeHandler.writeAttribute("column-id", attributes,
  1704. getId(), null, String.class, designContext);
  1705. // Sortable is a special attribute that depends on the data
  1706. // provider.
  1707. DesignAttributeHandler.writeAttribute("sortable", attributes,
  1708. isSortable(), null, boolean.class, designContext);
  1709. DesignAttributeHandler.writeAttribute("editable", attributes,
  1710. isEditable(), defaultState.editable, boolean.class,
  1711. designContext);
  1712. DesignAttributeHandler.writeAttribute("resizable", attributes,
  1713. isResizable(), defaultState.resizable, boolean.class,
  1714. designContext);
  1715. DesignAttributeHandler.writeAttribute("hidable", attributes,
  1716. isHidable(), defaultState.hidable, boolean.class,
  1717. designContext);
  1718. DesignAttributeHandler.writeAttribute("hidden", attributes,
  1719. isHidden(), defaultState.hidden, boolean.class,
  1720. designContext);
  1721. DesignAttributeHandler.writeAttribute("hiding-toggle-caption",
  1722. attributes, getHidingToggleCaption(),
  1723. defaultState.hidingToggleCaption, String.class,
  1724. designContext);
  1725. DesignAttributeHandler.writeAttribute("width", attributes,
  1726. getWidth(), defaultState.width, Double.class,
  1727. designContext);
  1728. DesignAttributeHandler.writeAttribute("min-width", attributes,
  1729. getMinimumWidth(), defaultState.minWidth, Double.class,
  1730. designContext);
  1731. DesignAttributeHandler.writeAttribute("max-width", attributes,
  1732. getMaximumWidth(), defaultState.maxWidth, Double.class,
  1733. designContext);
  1734. DesignAttributeHandler.writeAttribute("expand", attributes,
  1735. getExpandRatio(), defaultState.expandRatio, Integer.class,
  1736. designContext);
  1737. }
  1738. /**
  1739. * Reads the design attributes for this column from given element.
  1740. *
  1741. * @since 7.5.0
  1742. * @param design
  1743. * Element to read attributes from
  1744. * @param designContext
  1745. * the design context
  1746. */
  1747. @SuppressWarnings("unchecked")
  1748. protected void readDesign(Element design, DesignContext designContext) {
  1749. Attributes attributes = design.attributes();
  1750. if (design.hasAttr("sortable")) {
  1751. setSortable(DesignAttributeHandler.readAttribute("sortable",
  1752. attributes, boolean.class));
  1753. } else {
  1754. setSortable(false);
  1755. }
  1756. if (design.hasAttr("editable")) {
  1757. /*
  1758. * This is a fake editor just to have something (otherwise
  1759. * "setEditable" throws an exception.
  1760. *
  1761. * Let's use TextField here because we support only Strings as
  1762. * inline data type. It will work incorrectly for other types
  1763. * but we don't support them anyway.
  1764. */
  1765. setEditorComponent((HasValue<V> & Component) new TextField(),
  1766. (item, value) -> {
  1767. // Ignore user value since we don't know the setter
  1768. });
  1769. setEditable(DesignAttributeHandler.readAttribute("editable",
  1770. attributes, boolean.class));
  1771. }
  1772. if (design.hasAttr("resizable")) {
  1773. setResizable(DesignAttributeHandler.readAttribute("resizable",
  1774. attributes, boolean.class));
  1775. }
  1776. if (design.hasAttr("hidable")) {
  1777. setHidable(DesignAttributeHandler.readAttribute("hidable",
  1778. attributes, boolean.class));
  1779. }
  1780. if (design.hasAttr("hidden")) {
  1781. setHidden(DesignAttributeHandler.readAttribute("hidden",
  1782. attributes, boolean.class));
  1783. }
  1784. if (design.hasAttr("hiding-toggle-caption")) {
  1785. setHidingToggleCaption(DesignAttributeHandler.readAttribute(
  1786. "hiding-toggle-caption", attributes, String.class));
  1787. }
  1788. // Read size info where necessary.
  1789. if (design.hasAttr("width")) {
  1790. setWidth(DesignAttributeHandler.readAttribute("width",
  1791. attributes, Double.class));
  1792. }
  1793. if (design.hasAttr("min-width")) {
  1794. setMinimumWidth(DesignAttributeHandler
  1795. .readAttribute("min-width", attributes, Double.class));
  1796. }
  1797. if (design.hasAttr("max-width")) {
  1798. setMaximumWidth(DesignAttributeHandler
  1799. .readAttribute("max-width", attributes, Double.class));
  1800. }
  1801. if (design.hasAttr("expand")) {
  1802. if (design.attr("expand").isEmpty()) {
  1803. setExpandRatio(1);
  1804. } else {
  1805. setExpandRatio(DesignAttributeHandler.readAttribute(
  1806. "expand", attributes, Integer.class));
  1807. }
  1808. }
  1809. }
  1810. }
  1811. private class HeaderImpl extends Header {
  1812. @Override
  1813. protected Grid<T> getGrid() {
  1814. return Grid.this;
  1815. }
  1816. @Override
  1817. protected SectionState getState(boolean markAsDirty) {
  1818. return Grid.this.getState(markAsDirty).header;
  1819. }
  1820. @Override
  1821. protected Column<?, ?> getColumnByInternalId(String internalId) {
  1822. return getGrid().getColumnByInternalId(internalId);
  1823. }
  1824. @Override
  1825. @SuppressWarnings("unchecked")
  1826. protected String getInternalIdForColumn(Column<?, ?> column) {
  1827. return getGrid().getInternalIdForColumn((Column<T, ?>) column);
  1828. }
  1829. };
  1830. private class FooterImpl extends Footer {
  1831. @Override
  1832. protected Grid<T> getGrid() {
  1833. return Grid.this;
  1834. }
  1835. @Override
  1836. protected SectionState getState(boolean markAsDirty) {
  1837. return Grid.this.getState(markAsDirty).footer;
  1838. }
  1839. @Override
  1840. protected Column<?, ?> getColumnByInternalId(String internalId) {
  1841. return getGrid().getColumnByInternalId(internalId);
  1842. }
  1843. @Override
  1844. @SuppressWarnings("unchecked")
  1845. protected String getInternalIdForColumn(Column<?, ?> column) {
  1846. return getGrid().getInternalIdForColumn((Column<T, ?>) column);
  1847. }
  1848. };
  1849. private final Set<Column<T, ?>> columnSet = new LinkedHashSet<>();
  1850. private final Map<String, Column<T, ?>> columnKeys = new HashMap<>();
  1851. private final Map<String, Column<T, ?>> columnIds = new HashMap<>();
  1852. private final List<GridSortOrder<T>> sortOrder = new ArrayList<>();
  1853. private final DetailsManager<T> detailsManager;
  1854. private final Set<Component> extensionComponents = new HashSet<>();
  1855. private StyleGenerator<T> styleGenerator = item -> null;
  1856. private DescriptionGenerator<T> descriptionGenerator;
  1857. private final Header header = new HeaderImpl();
  1858. private final Footer footer = new FooterImpl();
  1859. private int counter = 0;
  1860. private GridSelectionModel<T> selectionModel;
  1861. private Editor<T> editor;
  1862. private PropertySet<T> propertySet;
  1863. private Class<T> beanType = null;
  1864. /**
  1865. * Creates a new grid without support for creating columns based on property
  1866. * names. Use an alternative constructor, such as {@link Grid#Grid(Class)},
  1867. * to create a grid that automatically sets up columns based on the type of
  1868. * presented data.
  1869. *
  1870. * @see #Grid(Class)
  1871. * @see #withPropertySet(PropertySet)
  1872. */
  1873. public Grid() {
  1874. this(new DataCommunicator<>());
  1875. }
  1876. /**
  1877. * Creates a new grid that uses reflection based on the provided bean type
  1878. * to automatically set up an initial set of columns. All columns will be
  1879. * configured using the same {@link Object#toString()} renderer that is used
  1880. * by {@link #addColumn(ValueProvider)}.
  1881. *
  1882. * @param beanType
  1883. * the bean type to use, not <code>null</code>
  1884. * @see #Grid()
  1885. * @see #withPropertySet(PropertySet)
  1886. */
  1887. public Grid(Class<T> beanType) {
  1888. this(BeanPropertySet.get(beanType));
  1889. this.beanType = beanType;
  1890. }
  1891. /**
  1892. * Creates a new grid with the given data communicator and without support
  1893. * for creating columns based on property names.
  1894. *
  1895. * @param dataCommunicator
  1896. * the custom data communicator to set
  1897. * @see #Grid()
  1898. * @see #Grid(PropertySet, DataCommunicator)
  1899. */
  1900. protected Grid(DataCommunicator<T> dataCommunicator) {
  1901. this(new PropertySet<T>() {
  1902. @Override
  1903. public Stream<PropertyDefinition<T, ?>> getProperties() {
  1904. // No columns configured by default
  1905. return Stream.empty();
  1906. }
  1907. @Override
  1908. public Optional<PropertyDefinition<T, ?>> getProperty(String name) {
  1909. throw new IllegalStateException(
  1910. "A Grid created without a bean type class literal or a custom property set"
  1911. + " doesn't support finding properties by name.");
  1912. }
  1913. }, dataCommunicator);
  1914. }
  1915. /**
  1916. * Creates a grid using a custom {@link PropertySet} implementation for
  1917. * configuring the initial columns and resolving property names for
  1918. * {@link #addColumn(String)} and
  1919. * {@link Column#setEditorComponent(HasValue)}.
  1920. *
  1921. * @see #withPropertySet(PropertySet)
  1922. *
  1923. * @param propertySet
  1924. * the property set implementation to use, not <code>null</code>.
  1925. */
  1926. protected Grid(PropertySet<T> propertySet) {
  1927. this(propertySet, new DataCommunicator<>());
  1928. }
  1929. /**
  1930. * Creates a grid using a custom {@link PropertySet} implementation and
  1931. * custom data communicator.
  1932. * <p>
  1933. * Property set is used for configuring the initial columns and resolving
  1934. * property names for {@link #addColumn(String)} and
  1935. * {@link Column#setEditorComponent(HasValue)}.
  1936. *
  1937. * @see #withPropertySet(PropertySet)
  1938. *
  1939. * @param propertySet
  1940. * the property set implementation to use, not <code>null</code>.
  1941. * @param dataCommunicator
  1942. * the data communicator to use, not<code>null</code>
  1943. */
  1944. protected Grid(PropertySet<T> propertySet,
  1945. DataCommunicator<T> dataCommunicator) {
  1946. super(dataCommunicator);
  1947. registerRpc(new GridServerRpcImpl());
  1948. setDefaultHeaderRow(appendHeaderRow());
  1949. setSelectionModel(new SingleSelectionModelImpl<>());
  1950. detailsManager = new DetailsManager<>();
  1951. addExtension(detailsManager);
  1952. addDataGenerator(detailsManager);
  1953. addDataGenerator((item, json) -> {
  1954. String styleName = styleGenerator.apply(item);
  1955. if (styleName != null && !styleName.isEmpty()) {
  1956. json.put(GridState.JSONKEY_ROWSTYLE, styleName);
  1957. }
  1958. if (descriptionGenerator != null) {
  1959. String description = descriptionGenerator.apply(item);
  1960. if (description != null && !description.isEmpty()) {
  1961. json.put(GridState.JSONKEY_ROWDESCRIPTION, description);
  1962. }
  1963. }
  1964. });
  1965. setPropertySet(propertySet);
  1966. // Automatically add columns for all available properties
  1967. propertySet.getProperties().map(PropertyDefinition::getName)
  1968. .forEach(this::addColumn);
  1969. }
  1970. /**
  1971. * Sets the property set to use for this grid. Does not create or update
  1972. * columns in any way but will delete and re-create the editor.
  1973. * <p>
  1974. * This is only meant to be called from constructors and readDesign, at a
  1975. * stage where it does not matter if you throw away the editor.
  1976. *
  1977. * @param propertySet
  1978. * the property set to use
  1979. */
  1980. protected void setPropertySet(PropertySet<T> propertySet) {
  1981. Objects.requireNonNull(propertySet, "propertySet cannot be null");
  1982. this.propertySet = propertySet;
  1983. if (editor instanceof Extension) {
  1984. removeExtension((Extension) editor);
  1985. }
  1986. editor = createEditor();
  1987. if (editor instanceof Extension) {
  1988. addExtension((Extension) editor);
  1989. }
  1990. }
  1991. /**
  1992. * Creates a grid using a custom {@link PropertySet} implementation for
  1993. * creating a default set of columns and for resolving property names with
  1994. * {@link #addColumn(String)} and
  1995. * {@link Column#setEditorComponent(HasValue)}.
  1996. * <p>
  1997. * This functionality is provided as static method instead of as a public
  1998. * constructor in order to make it possible to use a custom property set
  1999. * without creating a subclass while still leaving the public constructors
  2000. * focused on the common use cases.
  2001. *
  2002. * @see Grid#Grid()
  2003. * @see Grid#Grid(Class)
  2004. *
  2005. * @param propertySet
  2006. * the property set implementation to use, not <code>null</code>.
  2007. * @return a new grid using the provided property set, not <code>null</code>
  2008. */
  2009. public static <BEAN> Grid<BEAN> withPropertySet(
  2010. PropertySet<BEAN> propertySet) {
  2011. return new Grid<>(propertySet);
  2012. }
  2013. /**
  2014. * Creates a new {@code Grid} using the given caption
  2015. *
  2016. * @param caption
  2017. * the caption of the grid
  2018. */
  2019. public Grid(String caption) {
  2020. this();
  2021. setCaption(caption);
  2022. }
  2023. /**
  2024. * Creates a new {@code Grid} using the given caption and
  2025. * {@code DataProvider}
  2026. *
  2027. * @param caption
  2028. * the caption of the grid
  2029. * @param dataProvider
  2030. * the data provider, not {@code null}
  2031. */
  2032. public Grid(String caption, DataProvider<T, ?> dataProvider) {
  2033. this(caption);
  2034. setDataProvider(dataProvider);
  2035. }
  2036. /**
  2037. * Creates a new {@code Grid} using the given {@code DataProvider}
  2038. *
  2039. * @param dataProvider
  2040. * the data provider, not {@code null}
  2041. */
  2042. public Grid(DataProvider<T, ?> dataProvider) {
  2043. this();
  2044. setDataProvider(dataProvider);
  2045. }
  2046. /**
  2047. * Creates a new {@code Grid} using the given caption and collection of
  2048. * items
  2049. *
  2050. * @param caption
  2051. * the caption of the grid
  2052. * @param items
  2053. * the data items to use, not {@çode null}
  2054. */
  2055. public Grid(String caption, Collection<T> items) {
  2056. this(caption, DataProvider.ofCollection(items));
  2057. }
  2058. /**
  2059. * Gets the bean type used by this grid.
  2060. * <p>
  2061. * The bean type is used to automatically set up a column added using a
  2062. * property name.
  2063. *
  2064. * @return the used bean type or <code>null</code> if no bean type has been
  2065. * defined
  2066. */
  2067. public Class<T> getBeanType() {
  2068. return beanType;
  2069. }
  2070. public <V> void fireColumnVisibilityChangeEvent(Column<T, V> column,
  2071. boolean hidden, boolean userOriginated) {
  2072. fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden,
  2073. userOriginated));
  2074. }
  2075. /**
  2076. * Adds a new column with the given property name. The column will use a
  2077. * {@link TextRenderer}. The value is converted to a String using
  2078. * {@link Object#toString()}. The property name will be used as the
  2079. * {@link Column#getId() column id} and the {@link Column#getCaption()
  2080. * column caption} will be set based on the property definition.
  2081. * <p>
  2082. * This method can only be used for a <code>Grid</code> created using
  2083. * {@link Grid#Grid(Class)} or {@link #withPropertySet(PropertySet)}.
  2084. *
  2085. * @param propertyName
  2086. * the property name of the new column, not <code>null</code>
  2087. * @return the newly added column, not <code>null</code>
  2088. */
  2089. public Column<T, ?> addColumn(String propertyName) {
  2090. return addColumn(propertyName, new TextRenderer());
  2091. }
  2092. /**
  2093. * Adds a new column with the given property name and renderer. The property
  2094. * name will be used as the {@link Column#getId() column id} and the
  2095. * {@link Column#getCaption() column caption} will be set based on the
  2096. * property definition.
  2097. * <p>
  2098. * This method can only be used for a <code>Grid</code> created using
  2099. * {@link Grid#Grid(Class)} or {@link #withPropertySet(PropertySet)}.
  2100. *
  2101. * @param propertyName
  2102. * the property name of the new column, not <code>null</code>
  2103. * @param renderer
  2104. * the renderer to use, not <code>null</code>
  2105. * @return the newly added column, not <code>null</code>
  2106. */
  2107. public Column<T, ?> addColumn(String propertyName,
  2108. AbstractRenderer<? super T, ?> renderer) {
  2109. Objects.requireNonNull(propertyName, "Property name cannot be null");
  2110. Objects.requireNonNull(renderer, "Renderer cannot be null");
  2111. if (getColumn(propertyName) != null) {
  2112. throw new IllegalStateException(
  2113. "There is already a column for " + propertyName);
  2114. }
  2115. PropertyDefinition<T, ?> definition = propertySet
  2116. .getProperty(propertyName)
  2117. .orElseThrow(() -> new IllegalArgumentException(
  2118. "Could not resolve property name " + propertyName
  2119. + " from " + propertySet));
  2120. if (!renderer.getPresentationType()
  2121. .isAssignableFrom(definition.getType())) {
  2122. throw new IllegalArgumentException(renderer.toString()
  2123. + " cannot be used with a property of type "
  2124. + definition.getType().getName());
  2125. }
  2126. @SuppressWarnings({ "unchecked", "rawtypes" })
  2127. Column<T, ?> column = addColumn(definition.getGetter(),
  2128. (AbstractRenderer) renderer).setId(definition.getName())
  2129. .setCaption(definition.getCaption());
  2130. return column;
  2131. }
  2132. /**
  2133. * Adds a new text column to this {@link Grid} with a value provider. The
  2134. * column will use a {@link TextRenderer}. The value is converted to a
  2135. * String using {@link Object#toString()}. In-memory sorting will use the
  2136. * natural ordering of elements if they are mutually comparable and
  2137. * otherwise fall back to comparing the string representations of the
  2138. * values.
  2139. *
  2140. * @param valueProvider
  2141. * the value provider
  2142. *
  2143. * @return the new column
  2144. */
  2145. public <V> Column<T, V> addColumn(ValueProvider<T, V> valueProvider) {
  2146. return addColumn(valueProvider, new TextRenderer());
  2147. }
  2148. /**
  2149. * Adds a new column to this {@link Grid} with typed renderer and value
  2150. * provider.
  2151. *
  2152. * @param valueProvider
  2153. * the value provider
  2154. * @param renderer
  2155. * the column value class
  2156. * @param <V>
  2157. * the column value type
  2158. *
  2159. * @return the new column
  2160. *
  2161. * @see AbstractRenderer
  2162. */
  2163. public <V> Column<T, V> addColumn(ValueProvider<T, V> valueProvider,
  2164. AbstractRenderer<? super T, ? super V> renderer) {
  2165. String generatedIdentifier = getGeneratedIdentifier();
  2166. Column<T, V> column = createColumn(valueProvider, renderer);
  2167. addColumn(generatedIdentifier, column);
  2168. return column;
  2169. }
  2170. /**
  2171. * Creates a column instance from a value provider and a renderer.
  2172. *
  2173. * @param valueProvider
  2174. * the value provider
  2175. * @param renderer
  2176. * the renderer
  2177. * @return a new column instance
  2178. */
  2179. protected <V> Column<T, V> createColumn(ValueProvider<T, V> valueProvider,
  2180. AbstractRenderer<? super T, ? super V> renderer) {
  2181. return new Column<>(valueProvider, renderer);
  2182. }
  2183. private void addColumn(String identifier, Column<T, ?> column) {
  2184. if (getColumns().contains(column)) {
  2185. return;
  2186. }
  2187. column.extend(this);
  2188. columnSet.add(column);
  2189. columnKeys.put(identifier, column);
  2190. column.setInternalId(identifier);
  2191. addDataGenerator(column);
  2192. getState().columnOrder.add(identifier);
  2193. getHeader().addColumn(identifier);
  2194. getFooter().addColumn(identifier);
  2195. if (getDefaultHeaderRow() != null) {
  2196. getDefaultHeaderRow().getCell(column).setText(column.getCaption());
  2197. }
  2198. }
  2199. /**
  2200. * Removes the given column from this {@link Grid}.
  2201. *
  2202. * @param column
  2203. * the column to remove
  2204. */
  2205. public void removeColumn(Column<T, ?> column) {
  2206. if (columnSet.remove(column)) {
  2207. String columnId = column.getInternalId();
  2208. int displayIndex = getState(false).columnOrder.indexOf(columnId);
  2209. assert displayIndex != -1 : "Tried to remove a column which is not included in columnOrder. This should not be possible as all columns should be in columnOrder.";
  2210. columnKeys.remove(columnId);
  2211. columnIds.remove(column.getId());
  2212. column.remove();
  2213. getHeader().removeColumn(columnId);
  2214. getFooter().removeColumn(columnId);
  2215. getState(true).columnOrder.remove(columnId);
  2216. if (displayIndex < getFrozenColumnCount()) {
  2217. setFrozenColumnCount(getFrozenColumnCount() - 1);
  2218. }
  2219. }
  2220. }
  2221. /**
  2222. * Removes the column with the given column id.
  2223. *
  2224. * @see #removeColumn(Column)
  2225. * @see Column#setId(String)
  2226. *
  2227. * @param columnId
  2228. * the id of the column to remove, not <code>null</code>
  2229. */
  2230. public void removeColumn(String columnId) {
  2231. removeColumn(getColumnOrThrow(columnId));
  2232. }
  2233. /**
  2234. * Removes all columns from this Grid.
  2235. *
  2236. * @since 8.0.2
  2237. */
  2238. public void removeAllColumns() {
  2239. for (Column<T, ?> column : getColumns()) {
  2240. removeColumn(column);
  2241. }
  2242. }
  2243. /**
  2244. * Sets the details component generator.
  2245. *
  2246. * @param generator
  2247. * the generator for details components
  2248. */
  2249. public void setDetailsGenerator(DetailsGenerator<T> generator) {
  2250. this.detailsManager.setDetailsGenerator(generator);
  2251. }
  2252. /**
  2253. * Sets the visibility of details component for given item.
  2254. *
  2255. * @param data
  2256. * the item to show details for
  2257. * @param visible
  2258. * {@code true} if details component should be visible;
  2259. * {@code false} if it should be hidden
  2260. */
  2261. public void setDetailsVisible(T data, boolean visible) {
  2262. detailsManager.setDetailsVisible(data, visible);
  2263. }
  2264. /**
  2265. * Returns the visibility of details component for given item.
  2266. *
  2267. * @param data
  2268. * the item to show details for
  2269. *
  2270. * @return {@code true} if details component should be visible;
  2271. * {@code false} if it should be hidden
  2272. */
  2273. public boolean isDetailsVisible(T data) {
  2274. return detailsManager.isDetailsVisible(data);
  2275. }
  2276. /**
  2277. * Gets an unmodifiable collection of all columns currently in this
  2278. * {@link Grid}.
  2279. *
  2280. * @return unmodifiable collection of columns
  2281. */
  2282. public List<Column<T, ?>> getColumns() {
  2283. return Collections.unmodifiableList(getState(false).columnOrder.stream()
  2284. .map(columnKeys::get).collect(Collectors.toList()));
  2285. }
  2286. /**
  2287. * Gets a {@link Column} of this grid by its identifying string.
  2288. *
  2289. * @see Column#setId(String)
  2290. *
  2291. * @param columnId
  2292. * the identifier of the column to get
  2293. * @return the column corresponding to the given column identifier, or
  2294. * <code>null</code> if there is no such column
  2295. */
  2296. public Column<T, ?> getColumn(String columnId) {
  2297. return columnIds.get(columnId);
  2298. }
  2299. private Column<T, ?> getColumnOrThrow(String columnId) {
  2300. Objects.requireNonNull(columnId, "Column id cannot be null");
  2301. Column<T, ?> column = getColumn(columnId);
  2302. if (column == null) {
  2303. throw new IllegalStateException(
  2304. "There is no column with the id " + columnId);
  2305. }
  2306. return column;
  2307. }
  2308. /**
  2309. * {@inheritDoc}
  2310. * <p>
  2311. * Note that the order of the returned components it not specified.
  2312. */
  2313. @Override
  2314. public Iterator<Component> iterator() {
  2315. Set<Component> componentSet = new LinkedHashSet<>(extensionComponents);
  2316. Header header = getHeader();
  2317. for (int i = 0; i < header.getRowCount(); ++i) {
  2318. HeaderRow row = header.getRow(i);
  2319. componentSet.addAll(row.getComponents());
  2320. }
  2321. Footer footer = getFooter();
  2322. for (int i = 0; i < footer.getRowCount(); ++i) {
  2323. FooterRow row = footer.getRow(i);
  2324. componentSet.addAll(row.getComponents());
  2325. }
  2326. return Collections.unmodifiableSet(componentSet).iterator();
  2327. }
  2328. /**
  2329. * Sets the number of frozen columns in this grid. Setting the count to 0
  2330. * means that no data columns will be frozen, but the built-in selection
  2331. * checkbox column will still be frozen if it's in use. Setting the count to
  2332. * -1 will also disable the selection column.
  2333. * <p>
  2334. * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
  2335. * columns} in the count.
  2336. * <p>
  2337. * The default value is 0.
  2338. *
  2339. * @param numberOfColumns
  2340. * the number of columns that should be frozen
  2341. *
  2342. * @throws IllegalArgumentException
  2343. * if the column count is less than -1 or greater than the
  2344. * number of visible columns
  2345. */
  2346. public void setFrozenColumnCount(int numberOfColumns) {
  2347. if (numberOfColumns < -1 || numberOfColumns > columnSet.size()) {
  2348. throw new IllegalArgumentException(
  2349. "count must be between -1 and the current number of columns ("
  2350. + columnSet.size() + "): " + numberOfColumns);
  2351. }
  2352. getState().frozenColumnCount = numberOfColumns;
  2353. }
  2354. /**
  2355. * Gets the number of frozen columns in this grid. 0 means that no data
  2356. * columns will be frozen, but the built-in selection checkbox column will
  2357. * still be frozen if it's in use. -1 means that not even the selection
  2358. * column is frozen.
  2359. * <p>
  2360. * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
  2361. * columns} in the count.
  2362. *
  2363. * @see #setFrozenColumnCount(int)
  2364. *
  2365. * @return the number of frozen columns
  2366. */
  2367. public int getFrozenColumnCount() {
  2368. return getState(false).frozenColumnCount;
  2369. }
  2370. /**
  2371. * Sets the number of rows that should be visible in Grid's body. This
  2372. * method will set the height mode to be {@link HeightMode#ROW}.
  2373. *
  2374. * @param rows
  2375. * The height in terms of number of rows displayed in Grid's
  2376. * body. If Grid doesn't contain enough rows, white space is
  2377. * displayed instead. If <code>null</code> is given, then Grid's
  2378. * height is undefined
  2379. * @throws IllegalArgumentException
  2380. * if {@code rows} is zero or less
  2381. * @throws IllegalArgumentException
  2382. * if {@code rows} is {@link Double#isInfinite(double) infinite}
  2383. * @throws IllegalArgumentException
  2384. * if {@code rows} is {@link Double#isNaN(double) NaN}
  2385. */
  2386. public void setHeightByRows(double rows) {
  2387. if (rows <= 0.0d) {
  2388. throw new IllegalArgumentException(
  2389. "More than zero rows must be shown.");
  2390. } else if (Double.isInfinite(rows)) {
  2391. throw new IllegalArgumentException(
  2392. "Grid doesn't support infinite heights");
  2393. } else if (Double.isNaN(rows)) {
  2394. throw new IllegalArgumentException("NaN is not a valid row count");
  2395. }
  2396. getState().heightMode = HeightMode.ROW;
  2397. getState().heightByRows = rows;
  2398. }
  2399. /**
  2400. * Gets the amount of rows in Grid's body that are shown, while
  2401. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  2402. *
  2403. * @return the amount of rows that are being shown in Grid's body
  2404. * @see #setHeightByRows(double)
  2405. */
  2406. public double getHeightByRows() {
  2407. return getState(false).heightByRows;
  2408. }
  2409. /**
  2410. * {@inheritDoc}
  2411. * <p>
  2412. * <em>Note:</em> This method will set the height mode to be
  2413. * {@link HeightMode#CSS}.
  2414. *
  2415. * @see #setHeightMode(HeightMode)
  2416. */
  2417. @Override
  2418. public void setHeight(float height, Unit unit) {
  2419. getState().heightMode = HeightMode.CSS;
  2420. super.setHeight(height, unit);
  2421. }
  2422. /**
  2423. * Defines the mode in which the Grid widget's height is calculated.
  2424. * <p>
  2425. * If {@link HeightMode#CSS} is given, Grid will respect the values given
  2426. * via a {@code setHeight}-method, and behave as a traditional Component.
  2427. * <p>
  2428. * If {@link HeightMode#ROW} is given, Grid will make sure that the body
  2429. * will display as many rows as {@link #getHeightByRows()} defines.
  2430. * <em>Note:</em> If headers/footers are inserted or removed, the widget
  2431. * will resize itself to still display the required amount of rows in its
  2432. * body. It also takes the horizontal scrollbar into account.
  2433. *
  2434. * @param heightMode
  2435. * the mode in to which Grid should be set
  2436. */
  2437. public void setHeightMode(HeightMode heightMode) {
  2438. /*
  2439. * This method is a workaround for the fact that Vaadin re-applies
  2440. * widget dimensions (height/width) on each state change event. The
  2441. * original design was to have setHeight and setHeightByRow be equals,
  2442. * and whichever was called the latest was considered in effect.
  2443. *
  2444. * But, because of Vaadin always calling setHeight on the widget, this
  2445. * approach doesn't work.
  2446. */
  2447. getState().heightMode = heightMode;
  2448. }
  2449. /**
  2450. * Returns the current {@link HeightMode} the Grid is in.
  2451. * <p>
  2452. * Defaults to {@link HeightMode#CSS}.
  2453. *
  2454. * @return the current HeightMode
  2455. */
  2456. public HeightMode getHeightMode() {
  2457. return getState(false).heightMode;
  2458. }
  2459. /**
  2460. * Sets the style generator that is used for generating class names for rows
  2461. * in this grid. Returning null from the generator results in no custom
  2462. * style name being set.
  2463. *
  2464. * @see StyleGenerator
  2465. *
  2466. * @param styleGenerator
  2467. * the row style generator to set, not null
  2468. * @throws NullPointerException
  2469. * if {@code styleGenerator} is {@code null}
  2470. */
  2471. public void setStyleGenerator(StyleGenerator<T> styleGenerator) {
  2472. Objects.requireNonNull(styleGenerator,
  2473. "Style generator must not be null");
  2474. this.styleGenerator = styleGenerator;
  2475. getDataCommunicator().reset();
  2476. }
  2477. /**
  2478. * Gets the style generator that is used for generating class names for
  2479. * rows.
  2480. *
  2481. * @see StyleGenerator
  2482. *
  2483. * @return the row style generator
  2484. */
  2485. public StyleGenerator<T> getStyleGenerator() {
  2486. return styleGenerator;
  2487. }
  2488. /**
  2489. * Sets the description generator that is used for generating descriptions
  2490. * for rows.
  2491. *
  2492. * @param descriptionGenerator
  2493. * the row description generator to set, or <code>null</code> to
  2494. * remove a previously set generator
  2495. */
  2496. public void setDescriptionGenerator(
  2497. DescriptionGenerator<T> descriptionGenerator) {
  2498. this.descriptionGenerator = descriptionGenerator;
  2499. getDataCommunicator().reset();
  2500. }
  2501. /**
  2502. * Gets the description generator that is used for generating descriptions
  2503. * for rows.
  2504. *
  2505. * @return the row description generator, or <code>null</code> if no
  2506. * generator is set
  2507. */
  2508. public DescriptionGenerator<T> getDescriptionGenerator() {
  2509. return descriptionGenerator;
  2510. }
  2511. //
  2512. // HEADER AND FOOTER
  2513. //
  2514. /**
  2515. * Returns the header row at the given index.
  2516. *
  2517. * @param index
  2518. * the index of the row, where the topmost row has index zero
  2519. * @return the header row at the index
  2520. * @throws IndexOutOfBoundsException
  2521. * if {@code rowIndex < 0 || rowIndex >= getHeaderRowCount()}
  2522. */
  2523. public HeaderRow getHeaderRow(int index) {
  2524. return getHeader().getRow(index);
  2525. }
  2526. /**
  2527. * Gets the number of rows in the header section.
  2528. *
  2529. * @return the number of header rows
  2530. */
  2531. public int getHeaderRowCount() {
  2532. return header.getRowCount();
  2533. }
  2534. /**
  2535. * Inserts a new row at the given position to the header section. Shifts the
  2536. * row currently at that position and any subsequent rows down (adds one to
  2537. * their indices). Inserting at {@link #getHeaderRowCount()} appends the row
  2538. * at the bottom of the header.
  2539. *
  2540. * @param index
  2541. * the index at which to insert the row, where the topmost row
  2542. * has index zero
  2543. * @return the inserted header row
  2544. *
  2545. * @throws IndexOutOfBoundsException
  2546. * if {@code rowIndex < 0 || rowIndex > getHeaderRowCount()}
  2547. *
  2548. * @see #appendHeaderRow()
  2549. * @see #prependHeaderRow()
  2550. * @see #removeHeaderRow(HeaderRow)
  2551. * @see #removeHeaderRow(int)
  2552. */
  2553. public HeaderRow addHeaderRowAt(int index) {
  2554. return getHeader().addRowAt(index);
  2555. }
  2556. /**
  2557. * Adds a new row at the bottom of the header section.
  2558. *
  2559. * @return the appended header row
  2560. *
  2561. * @see #prependHeaderRow()
  2562. * @see #addHeaderRowAt(int)
  2563. * @see #removeHeaderRow(HeaderRow)
  2564. * @see #removeHeaderRow(int)
  2565. */
  2566. public HeaderRow appendHeaderRow() {
  2567. return addHeaderRowAt(getHeaderRowCount());
  2568. }
  2569. /**
  2570. * Adds a new row at the top of the header section.
  2571. *
  2572. * @return the prepended header row
  2573. *
  2574. * @see #appendHeaderRow()
  2575. * @see #addHeaderRowAt(int)
  2576. * @see #removeHeaderRow(HeaderRow)
  2577. * @see #removeHeaderRow(int)
  2578. */
  2579. public HeaderRow prependHeaderRow() {
  2580. return addHeaderRowAt(0);
  2581. }
  2582. /**
  2583. * Removes the given row from the header section. Removing a default row
  2584. * sets the Grid to have no default row.
  2585. *
  2586. * @param row
  2587. * the header row to be removed, not null
  2588. *
  2589. * @throws IllegalArgumentException
  2590. * if the header does not contain the row
  2591. *
  2592. * @see #removeHeaderRow(int)
  2593. * @see #addHeaderRowAt(int)
  2594. * @see #appendHeaderRow()
  2595. * @see #prependHeaderRow()
  2596. */
  2597. public void removeHeaderRow(HeaderRow row) {
  2598. getHeader().removeRow(row);
  2599. }
  2600. /**
  2601. * Removes the row at the given position from the header section.
  2602. *
  2603. * @param index
  2604. * the index of the row to remove, where the topmost row has
  2605. * index zero
  2606. *
  2607. * @throws IndexOutOfBoundsException
  2608. * if {@code index < 0 || index >= getHeaderRowCount()}
  2609. *
  2610. * @see #removeHeaderRow(HeaderRow)
  2611. * @see #addHeaderRowAt(int)
  2612. * @see #appendHeaderRow()
  2613. * @see #prependHeaderRow()
  2614. */
  2615. public void removeHeaderRow(int index) {
  2616. getHeader().removeRow(index);
  2617. }
  2618. /**
  2619. * Returns the current default row of the header.
  2620. *
  2621. * @return the default row or null if no default row set
  2622. *
  2623. * @see #setDefaultHeaderRow(HeaderRow)
  2624. */
  2625. public HeaderRow getDefaultHeaderRow() {
  2626. return header.getDefaultRow();
  2627. }
  2628. /**
  2629. * Sets the default row of the header. The default row is a special header
  2630. * row that displays column captions and sort indicators. By default Grid
  2631. * has a single row which is also the default row. When a header row is set
  2632. * as the default row, any existing cell content is replaced by the column
  2633. * captions.
  2634. *
  2635. * @param row
  2636. * the new default row, or null for no default row
  2637. *
  2638. * @throws IllegalArgumentException
  2639. * if the header does not contain the row
  2640. */
  2641. public void setDefaultHeaderRow(HeaderRow row) {
  2642. header.setDefaultRow((Row) row);
  2643. }
  2644. /**
  2645. * Returns the header section of this grid. The default header contains a
  2646. * single row, set as the {@linkplain #setDefaultHeaderRow(HeaderRow)
  2647. * default row}.
  2648. *
  2649. * @return the header section
  2650. */
  2651. protected Header getHeader() {
  2652. return header;
  2653. }
  2654. /**
  2655. * Returns the footer row at the given index.
  2656. *
  2657. * @param index
  2658. * the index of the row, where the topmost row has index zero
  2659. * @return the footer row at the index
  2660. * @throws IndexOutOfBoundsException
  2661. * if {@code rowIndex < 0 || rowIndex >= getFooterRowCount()}
  2662. */
  2663. public FooterRow getFooterRow(int index) {
  2664. return getFooter().getRow(index);
  2665. }
  2666. /**
  2667. * Gets the number of rows in the footer section.
  2668. *
  2669. * @return the number of footer rows
  2670. */
  2671. public int getFooterRowCount() {
  2672. return getFooter().getRowCount();
  2673. }
  2674. /**
  2675. * Inserts a new row at the given position to the footer section. Shifts the
  2676. * row currently at that position and any subsequent rows down (adds one to
  2677. * their indices). Inserting at {@link #getFooterRowCount()} appends the row
  2678. * at the bottom of the footer.
  2679. *
  2680. * @param index
  2681. * the index at which to insert the row, where the topmost row
  2682. * has index zero
  2683. * @return the inserted footer row
  2684. *
  2685. * @throws IndexOutOfBoundsException
  2686. * if {@code rowIndex < 0 || rowIndex > getFooterRowCount()}
  2687. *
  2688. * @see #appendFooterRow()
  2689. * @see #prependFooterRow()
  2690. * @see #removeFooterRow(FooterRow)
  2691. * @see #removeFooterRow(int)
  2692. */
  2693. public FooterRow addFooterRowAt(int index) {
  2694. return getFooter().addRowAt(index);
  2695. }
  2696. /**
  2697. * Adds a new row at the bottom of the footer section.
  2698. *
  2699. * @return the appended footer row
  2700. *
  2701. * @see #prependFooterRow()
  2702. * @see #addFooterRowAt(int)
  2703. * @see #removeFooterRow(FooterRow)
  2704. * @see #removeFooterRow(int)
  2705. */
  2706. public FooterRow appendFooterRow() {
  2707. return addFooterRowAt(getFooterRowCount());
  2708. }
  2709. /**
  2710. * Adds a new row at the top of the footer section.
  2711. *
  2712. * @return the prepended footer row
  2713. *
  2714. * @see #appendFooterRow()
  2715. * @see #addFooterRowAt(int)
  2716. * @see #removeFooterRow(FooterRow)
  2717. * @see #removeFooterRow(int)
  2718. */
  2719. public FooterRow prependFooterRow() {
  2720. return addFooterRowAt(0);
  2721. }
  2722. /**
  2723. * Removes the given row from the footer section. Removing a default row
  2724. * sets the Grid to have no default row.
  2725. *
  2726. * @param row
  2727. * the footer row to be removed, not null
  2728. *
  2729. * @throws IllegalArgumentException
  2730. * if the footer does not contain the row
  2731. *
  2732. * @see #removeFooterRow(int)
  2733. * @see #addFooterRowAt(int)
  2734. * @see #appendFooterRow()
  2735. * @see #prependFooterRow()
  2736. */
  2737. public void removeFooterRow(FooterRow row) {
  2738. getFooter().removeRow(row);
  2739. }
  2740. /**
  2741. * Removes the row at the given position from the footer section.
  2742. *
  2743. * @param index
  2744. * the index of the row to remove, where the topmost row has
  2745. * index zero
  2746. *
  2747. * @throws IndexOutOfBoundsException
  2748. * if {@code index < 0 || index >= getFooterRowCount()}
  2749. *
  2750. * @see #removeFooterRow(FooterRow)
  2751. * @see #addFooterRowAt(int)
  2752. * @see #appendFooterRow()
  2753. * @see #prependFooterRow()
  2754. */
  2755. public void removeFooterRow(int index) {
  2756. getFooter().removeRow(index);
  2757. }
  2758. /**
  2759. * Returns the footer section of this grid.
  2760. *
  2761. * @return the footer section
  2762. */
  2763. protected Footer getFooter() {
  2764. return footer;
  2765. }
  2766. /**
  2767. * Registers a new column reorder listener.
  2768. *
  2769. * @param listener
  2770. * the listener to register, not null
  2771. * @return a registration for the listener
  2772. */
  2773. public Registration addColumnReorderListener(
  2774. ColumnReorderListener listener) {
  2775. return addListener(ColumnReorderEvent.class, listener,
  2776. COLUMN_REORDER_METHOD);
  2777. }
  2778. /**
  2779. * Registers a new column resize listener.
  2780. *
  2781. * @param listener
  2782. * the listener to register, not null
  2783. * @return a registration for the listener
  2784. */
  2785. public Registration addColumnResizeListener(ColumnResizeListener listener) {
  2786. return addListener(ColumnResizeEvent.class, listener,
  2787. COLUMN_RESIZE_METHOD);
  2788. }
  2789. /**
  2790. * Adds an item click listener. The listener is called when an item of this
  2791. * {@code Grid} is clicked.
  2792. *
  2793. * @param listener
  2794. * the item click listener, not null
  2795. * @return a registration for the listener
  2796. */
  2797. public Registration addItemClickListener(
  2798. ItemClickListener<? super T> listener) {
  2799. return addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClick.class,
  2800. listener, ITEM_CLICK_METHOD);
  2801. }
  2802. /**
  2803. * Registers a new column visibility change listener.
  2804. *
  2805. * @param listener
  2806. * the listener to register, not null
  2807. * @return a registration for the listener
  2808. */
  2809. public Registration addColumnVisibilityChangeListener(
  2810. ColumnVisibilityChangeListener listener) {
  2811. return addListener(ColumnVisibilityChangeEvent.class, listener,
  2812. COLUMN_VISIBILITY_METHOD);
  2813. }
  2814. /**
  2815. * Returns whether column reordering is allowed. Default value is
  2816. * <code>false</code>.
  2817. *
  2818. * @return true if reordering is allowed
  2819. */
  2820. public boolean isColumnReorderingAllowed() {
  2821. return getState(false).columnReorderingAllowed;
  2822. }
  2823. /**
  2824. * Sets whether or not column reordering is allowed. Default value is
  2825. * <code>false</code>.
  2826. *
  2827. * @param columnReorderingAllowed
  2828. * specifies whether column reordering is allowed
  2829. */
  2830. public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
  2831. if (isColumnReorderingAllowed() != columnReorderingAllowed) {
  2832. getState().columnReorderingAllowed = columnReorderingAllowed;
  2833. }
  2834. }
  2835. /**
  2836. * Sets the columns and their order based on their column ids. Columns
  2837. * currently in this grid that are not present in the list of column ids are
  2838. * removed. This includes any column that has no id. Similarly, any new
  2839. * column in columns will be added to this grid. New columns can only be
  2840. * added for a <code>Grid</code> created using {@link Grid#Grid(Class)} or
  2841. * {@link #withPropertySet(PropertySet)}.
  2842. *
  2843. *
  2844. * @param columnIds
  2845. * the column ids to set
  2846. *
  2847. * @see Column#setId(String)
  2848. */
  2849. public void setColumns(String... columnIds) {
  2850. // Must extract to an explicitly typed variable because otherwise javac
  2851. // cannot determine which overload of setColumnOrder to use
  2852. Column<T, ?>[] newColumnOrder = Stream.of(columnIds)
  2853. .map((Function<String, Column<T, ?>>) id -> {
  2854. Column<T, ?> column = getColumn(id);
  2855. if (column == null) {
  2856. column = addColumn(id);
  2857. }
  2858. return column;
  2859. }).toArray(Column[]::new);
  2860. setColumnOrder(newColumnOrder);
  2861. // The columns to remove are now at the end of the column list
  2862. getColumns().stream().skip(columnIds.length)
  2863. .forEach(this::removeColumn);
  2864. }
  2865. private String getIdentifier(Column<T, ?> column) {
  2866. return columnKeys.entrySet().stream()
  2867. .filter(entry -> entry.getValue().equals(column))
  2868. .map(entry -> entry.getKey()).findFirst()
  2869. .orElse(getGeneratedIdentifier());
  2870. }
  2871. private String getGeneratedIdentifier() {
  2872. String columnId = "" + counter;
  2873. counter++;
  2874. return columnId;
  2875. }
  2876. /**
  2877. * Sets a new column order for the grid. All columns which are not ordered
  2878. * here will remain in the order they were before as the last columns of
  2879. * grid.
  2880. *
  2881. * @param columns
  2882. * the columns in the order they should be
  2883. */
  2884. public void setColumnOrder(Column<T, ?>... columns) {
  2885. setColumnOrder(Stream.of(columns));
  2886. }
  2887. private void setColumnOrder(Stream<Column<T, ?>> columns) {
  2888. List<String> columnOrder = new ArrayList<>();
  2889. columns.forEach(column -> {
  2890. if (columnSet.contains(column)) {
  2891. columnOrder.add(column.getInternalId());
  2892. } else {
  2893. throw new IllegalStateException(
  2894. "setColumnOrder should not be called "
  2895. + "with columns that are not in the grid.");
  2896. }
  2897. });
  2898. List<String> stateColumnOrder = getState().columnOrder;
  2899. if (stateColumnOrder.size() != columnOrder.size()) {
  2900. stateColumnOrder.removeAll(columnOrder);
  2901. columnOrder.addAll(stateColumnOrder);
  2902. }
  2903. getState().columnOrder = columnOrder;
  2904. fireColumnReorderEvent(false);
  2905. }
  2906. /**
  2907. * Sets a new column order for the grid based on their column ids. All
  2908. * columns which are not ordered here will remain in the order they were
  2909. * before as the last columns of grid.
  2910. *
  2911. * @param columnIds
  2912. * the column ids in the order they should be
  2913. *
  2914. * @see Column#setId(String)
  2915. */
  2916. public void setColumnOrder(String... columnIds) {
  2917. setColumnOrder(Stream.of(columnIds).map(this::getColumnOrThrow));
  2918. }
  2919. /**
  2920. * Returns the selection model for this grid.
  2921. *
  2922. * @return the selection model, not null
  2923. */
  2924. public GridSelectionModel<T> getSelectionModel() {
  2925. assert selectionModel != null : "No selection model set by "
  2926. + getClass().getName() + " constructor";
  2927. return selectionModel;
  2928. }
  2929. /**
  2930. * Use this grid as a single select in {@link Binder}.
  2931. * <p>
  2932. * Throws {@link IllegalStateException} if the grid is not using a
  2933. * {@link SingleSelectionModel}.
  2934. *
  2935. * @return the single select wrapper that can be used in binder
  2936. * @throws IllegalStateException
  2937. * if not using a single selection model
  2938. */
  2939. public SingleSelect<T> asSingleSelect() {
  2940. GridSelectionModel<T> model = getSelectionModel();
  2941. if (!(model instanceof SingleSelectionModel)) {
  2942. throw new IllegalStateException(
  2943. "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.");
  2944. }
  2945. return ((SingleSelectionModel<T>) model).asSingleSelect();
  2946. }
  2947. public Editor<T> getEditor() {
  2948. return editor;
  2949. }
  2950. /**
  2951. * User this grid as a multiselect in {@link Binder}.
  2952. * <p>
  2953. * Throws {@link IllegalStateException} if the grid is not using a
  2954. * {@link MultiSelectionModel}.
  2955. *
  2956. * @return the multiselect wrapper that can be used in binder
  2957. * @throws IllegalStateException
  2958. * if not using a multiselection model
  2959. */
  2960. public MultiSelect<T> asMultiSelect() {
  2961. GridSelectionModel<T> model = getSelectionModel();
  2962. if (!(model instanceof MultiSelectionModel)) {
  2963. throw new IllegalStateException(
  2964. "Grid is not in multiselect mode, it needs to be explicitly set to such with setSelectionModel(MultiSelectionModel) before being able to use multiselection features.");
  2965. }
  2966. return ((MultiSelectionModel<T>) model).asMultiSelect();
  2967. }
  2968. /**
  2969. * Sets the selection model for the grid.
  2970. * <p>
  2971. * This method is for setting a custom selection model, and is
  2972. * {@code protected} because {@link #setSelectionMode(SelectionMode)} should
  2973. * be used for easy switching between built-in selection models.
  2974. * <p>
  2975. * The default selection model is {@link SingleSelectionModelImpl}.
  2976. * <p>
  2977. * To use a custom selection model, you can e.g. extend the grid call this
  2978. * method with your custom selection model.
  2979. *
  2980. * @param model
  2981. * the selection model to use, not {@code null}
  2982. *
  2983. * @see #setSelectionMode(SelectionMode)
  2984. */
  2985. @SuppressWarnings("unchecked")
  2986. protected void setSelectionModel(GridSelectionModel<T> model) {
  2987. Objects.requireNonNull(model, "selection model cannot be null");
  2988. if (selectionModel != null) { // null when called from constructor
  2989. selectionModel.remove();
  2990. }
  2991. selectionModel = model;
  2992. if (selectionModel instanceof AbstractListingExtension) {
  2993. ((AbstractListingExtension<T>) selectionModel).extend(this);
  2994. } else {
  2995. addExtension(selectionModel);
  2996. }
  2997. }
  2998. /**
  2999. * Sets the grid's selection mode.
  3000. * <p>
  3001. * The built-in selection models are:
  3002. * <ul>
  3003. * <li>{@link SelectionMode#SINGLE} -> {@link SingleSelectionModelImpl},
  3004. * <b>the default model</b></li>
  3005. * <li>{@link SelectionMode#MULTI} -> {@link MultiSelectionModelImpl}, with
  3006. * checkboxes in the first column for selection</li>
  3007. * <li>{@link SelectionMode#NONE} -> {@link NoSelectionModel}, preventing
  3008. * selection</li>
  3009. * </ul>
  3010. * <p>
  3011. * To use your custom selection model, you can use
  3012. * {@link #setSelectionModel(GridSelectionModel)}, see existing selection
  3013. * model implementations for example.
  3014. *
  3015. * @param selectionMode
  3016. * the selection mode to switch to, not {@code null}
  3017. * @return the used selection model
  3018. *
  3019. * @see SelectionMode
  3020. * @see GridSelectionModel
  3021. * @see #setSelectionModel(GridSelectionModel)
  3022. */
  3023. public GridSelectionModel<T> setSelectionMode(SelectionMode selectionMode) {
  3024. Objects.requireNonNull(selectionMode, "Selection mode cannot be null.");
  3025. GridSelectionModel<T> model = selectionMode.createModel();
  3026. setSelectionModel(model);
  3027. return model;
  3028. }
  3029. /**
  3030. * This method is a shorthand that delegates to the currently set selection
  3031. * model.
  3032. *
  3033. * @see #getSelectionModel()
  3034. * @see GridSelectionModel
  3035. */
  3036. public Set<T> getSelectedItems() {
  3037. return getSelectionModel().getSelectedItems();
  3038. }
  3039. /**
  3040. * This method is a shorthand that delegates to the currently set selection
  3041. * model.
  3042. *
  3043. * @see #getSelectionModel()
  3044. * @see GridSelectionModel
  3045. */
  3046. public void select(T item) {
  3047. getSelectionModel().select(item);
  3048. }
  3049. /**
  3050. * This method is a shorthand that delegates to the currently set selection
  3051. * model.
  3052. *
  3053. * @see #getSelectionModel()
  3054. * @see GridSelectionModel
  3055. */
  3056. public void deselect(T item) {
  3057. getSelectionModel().deselect(item);
  3058. }
  3059. /**
  3060. * This method is a shorthand that delegates to the currently set selection
  3061. * model.
  3062. *
  3063. * @see #getSelectionModel()
  3064. * @see GridSelectionModel
  3065. */
  3066. public void deselectAll() {
  3067. getSelectionModel().deselectAll();
  3068. }
  3069. /**
  3070. * Adds a selection listener to the current selection model.
  3071. * <p>
  3072. * <em>NOTE:</em> If selection mode is switched with
  3073. * {@link #setSelectionMode(SelectionMode)}, then this listener is not
  3074. * triggered anymore when selection changes!
  3075. * <p>
  3076. * This is a shorthand for
  3077. * {@code grid.getSelectionModel().addSelectionListener()}. To get more
  3078. * detailed selection events, use {@link #getSelectionModel()} and either
  3079. * {@link SingleSelectionModel#addSingleSelectionListener(SingleSelectionListener)}
  3080. * or
  3081. * {@link MultiSelectionModel#addMultiSelectionListener(MultiSelectionListener)}
  3082. * depending on the used selection mode.
  3083. *
  3084. * @param listener
  3085. * the listener to add
  3086. * @return a registration handle to remove the listener
  3087. * @throws UnsupportedOperationException
  3088. * if selection has been disabled with
  3089. * {@link SelectionMode#NONE}
  3090. */
  3091. public Registration addSelectionListener(SelectionListener<T> listener)
  3092. throws UnsupportedOperationException {
  3093. return getSelectionModel().addSelectionListener(listener);
  3094. }
  3095. /**
  3096. * Sort this Grid in ascending order by a specified column.
  3097. *
  3098. * @param column
  3099. * a column to sort against
  3100. *
  3101. */
  3102. public void sort(Column<T, ?> column) {
  3103. sort(column, SortDirection.ASCENDING);
  3104. }
  3105. /**
  3106. * Sort this Grid in user-specified direction by a column.
  3107. *
  3108. * @param column
  3109. * a column to sort against
  3110. * @param direction
  3111. * a sort order value (ascending/descending)
  3112. *
  3113. */
  3114. public void sort(Column<T, ?> column, SortDirection direction) {
  3115. setSortOrder(Collections
  3116. .singletonList(new GridSortOrder<>(column, direction)));
  3117. }
  3118. /**
  3119. * Sort this Grid in ascending order by a specified column defined by id.
  3120. *
  3121. * @param columnId
  3122. * the id of the column to sort against
  3123. *
  3124. * @see Column#setId(String)
  3125. */
  3126. public void sort(String columnId) {
  3127. sort(columnId, SortDirection.ASCENDING);
  3128. }
  3129. /**
  3130. * Sort this Grid in a user-specified direction by a column defined by id.
  3131. *
  3132. * @param columnId
  3133. * the id of the column to sort against
  3134. * @param direction
  3135. * a sort order value (ascending/descending)
  3136. *
  3137. * @see Column#setId(String)
  3138. */
  3139. public void sort(String columnId, SortDirection direction) {
  3140. sort(getColumnOrThrow(columnId), direction);
  3141. }
  3142. /**
  3143. * Clear the current sort order, and re-sort the grid.
  3144. */
  3145. public void clearSortOrder() {
  3146. sortOrder.clear();
  3147. sort(false);
  3148. }
  3149. /**
  3150. * Sets the sort order to use.
  3151. *
  3152. * @param order
  3153. * a sort order list.
  3154. *
  3155. * @throws IllegalArgumentException
  3156. * if order is null
  3157. */
  3158. public void setSortOrder(List<GridSortOrder<T>> order) {
  3159. setSortOrder(order, false);
  3160. }
  3161. /**
  3162. * Sets the sort order to use, given a {@link GridSortOrderBuilder}.
  3163. * Shorthand for {@code setSortOrder(builder.build())}.
  3164. *
  3165. * @see GridSortOrderBuilder
  3166. *
  3167. * @param builder
  3168. * the sort builder to retrieve the sort order from
  3169. * @throws NullPointerException
  3170. * if builder is null
  3171. */
  3172. public void setSortOrder(GridSortOrderBuilder<T> builder) {
  3173. Objects.requireNonNull(builder, "Sort builder cannot be null");
  3174. setSortOrder(builder.build());
  3175. }
  3176. /**
  3177. * Adds a sort order change listener that gets notified when the sort order
  3178. * changes.
  3179. *
  3180. * @param listener
  3181. * the sort order change listener to add
  3182. */
  3183. @Override
  3184. public Registration addSortListener(
  3185. SortListener<GridSortOrder<T>> listener) {
  3186. return addListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
  3187. }
  3188. /**
  3189. * Get the current sort order list.
  3190. *
  3191. * @return a sort order list
  3192. */
  3193. public List<GridSortOrder<T>> getSortOrder() {
  3194. return Collections.unmodifiableList(sortOrder);
  3195. }
  3196. /**
  3197. * Scrolls to a certain item, using {@link ScrollDestination#ANY}.
  3198. * <p>
  3199. * If the item has visible details, its size will also be taken into
  3200. * account.
  3201. *
  3202. * @param row
  3203. * id of item to scroll to.
  3204. * @throws IllegalArgumentException
  3205. * if the provided id is not recognized by the data source.
  3206. */
  3207. public void scrollTo(int row) throws IllegalArgumentException {
  3208. scrollTo(row, ScrollDestination.ANY);
  3209. }
  3210. /**
  3211. * Scrolls to a certain item, using user-specified scroll destination.
  3212. * <p>
  3213. * If the row has visible details, its size will also be taken into account.
  3214. *
  3215. * @param row
  3216. * id of item to scroll to.
  3217. * @param destination
  3218. * value specifying desired position of scrolled-to row, not
  3219. * {@code null}
  3220. * @throws IllegalArgumentException
  3221. * if the provided row is outside the item range
  3222. */
  3223. public void scrollTo(int row, ScrollDestination destination) {
  3224. Objects.requireNonNull(destination,
  3225. "ScrollDestination can not be null");
  3226. if (row > getDataProvider().size(new Query())) {
  3227. throw new IllegalArgumentException("Row outside dataProvider size");
  3228. }
  3229. getRpcProxy(GridClientRpc.class).scrollToRow(row, destination);
  3230. }
  3231. /**
  3232. * Scrolls to the beginning of the first data row.
  3233. */
  3234. public void scrollToStart() {
  3235. getRpcProxy(GridClientRpc.class).scrollToStart();
  3236. }
  3237. /**
  3238. * Scrolls to the end of the last data row.
  3239. */
  3240. public void scrollToEnd() {
  3241. getRpcProxy(GridClientRpc.class).scrollToEnd();
  3242. }
  3243. @Override
  3244. protected GridState getState() {
  3245. return getState(true);
  3246. }
  3247. @Override
  3248. protected GridState getState(boolean markAsDirty) {
  3249. return (GridState) super.getState(markAsDirty);
  3250. }
  3251. /**
  3252. * Sets the column resize mode to use. The default mode is
  3253. * {@link ColumnResizeMode#ANIMATED}.
  3254. *
  3255. * @param mode
  3256. * a ColumnResizeMode value
  3257. * @since 7.7.5
  3258. */
  3259. public void setColumnResizeMode(ColumnResizeMode mode) {
  3260. getState().columnResizeMode = mode;
  3261. }
  3262. /**
  3263. * Returns the current column resize mode. The default mode is
  3264. * {@link ColumnResizeMode#ANIMATED}.
  3265. *
  3266. * @return a ColumnResizeMode value
  3267. * @since 7.7.5
  3268. */
  3269. public ColumnResizeMode getColumnResizeMode() {
  3270. return getState(false).columnResizeMode;
  3271. }
  3272. /**
  3273. * Creates a new Editor instance. Can be overridden to create a custom
  3274. * Editor. If the Editor is a {@link AbstractGridExtension}, it will be
  3275. * automatically added to {@link DataCommunicator}.
  3276. *
  3277. * @return editor
  3278. */
  3279. protected Editor<T> createEditor() {
  3280. return new EditorImpl<>(propertySet);
  3281. }
  3282. private void addExtensionComponent(Component c) {
  3283. if (extensionComponents.add(c)) {
  3284. c.setParent(this);
  3285. markAsDirty();
  3286. }
  3287. }
  3288. private void removeExtensionComponent(Component c) {
  3289. if (extensionComponents.remove(c)) {
  3290. c.setParent(null);
  3291. markAsDirty();
  3292. }
  3293. }
  3294. private void fireColumnReorderEvent(boolean userOriginated) {
  3295. fireEvent(new ColumnReorderEvent(this, userOriginated));
  3296. }
  3297. private void fireColumnResizeEvent(Column<?, ?> column,
  3298. boolean userOriginated) {
  3299. fireEvent(new ColumnResizeEvent(this, column, userOriginated));
  3300. }
  3301. @Override
  3302. protected void readItems(Element design, DesignContext context) {
  3303. // Grid handles reading of items in Grid#readData
  3304. }
  3305. @Override
  3306. public DataProvider<T, ?> getDataProvider() {
  3307. return internalGetDataProvider();
  3308. }
  3309. @Override
  3310. public void setDataProvider(DataProvider<T, ?> dataProvider) {
  3311. internalSetDataProvider(dataProvider);
  3312. }
  3313. /**
  3314. * Sets a CallbackDataProvider using the given fetch items callback and a
  3315. * size callback.
  3316. * <p>
  3317. * This method is a shorthand for making a {@link CallbackDataProvider} that
  3318. * handles a partial {@link Query} object.
  3319. *
  3320. * @param fetchItems
  3321. * a callback for fetching items
  3322. * @param sizeCallback
  3323. * a callback for getting the count of items
  3324. *
  3325. * @see CallbackDataProvider
  3326. * @see #setDataProvider(DataProvider)
  3327. */
  3328. public void setDataProvider(FetchItemsCallback<T> fetchItems,
  3329. SerializableSupplier<Integer> sizeCallback) {
  3330. internalSetDataProvider(
  3331. new CallbackDataProvider<>(
  3332. q -> fetchItems.fetchItems(q.getSortOrders(),
  3333. q.getOffset(), q.getLimit()),
  3334. q -> sizeCallback.get()));
  3335. }
  3336. @Override
  3337. protected void doReadDesign(Element design, DesignContext context) {
  3338. Attributes attrs = design.attributes();
  3339. if (design.hasAttr(DECLARATIVE_DATA_ITEM_TYPE)) {
  3340. String itemType = design.attr(DECLARATIVE_DATA_ITEM_TYPE);
  3341. setBeanType(itemType);
  3342. }
  3343. if (attrs.hasKey("selection-mode")) {
  3344. setSelectionMode(DesignAttributeHandler.readAttribute(
  3345. "selection-mode", attrs, SelectionMode.class));
  3346. }
  3347. Attributes attr = design.attributes();
  3348. if (attr.hasKey("selection-allowed")) {
  3349. setReadOnly(DesignAttributeHandler
  3350. .readAttribute("selection-allowed", attr, Boolean.class));
  3351. }
  3352. if (attrs.hasKey("rows")) {
  3353. setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs,
  3354. double.class));
  3355. }
  3356. readStructure(design, context);
  3357. // Read frozen columns after columns are read.
  3358. if (attrs.hasKey("frozen-columns")) {
  3359. setFrozenColumnCount(DesignAttributeHandler
  3360. .readAttribute("frozen-columns", attrs, int.class));
  3361. }
  3362. }
  3363. /**
  3364. * Sets the bean type to use for property mapping.
  3365. * <p>
  3366. * This method is responsible also for setting or updating the property set
  3367. * so that it matches the given bean type.
  3368. * <p>
  3369. * Protected mostly for Designer needs, typically should not be overridden
  3370. * or even called.
  3371. *
  3372. * @param beanTypeClassName
  3373. * the fully qualified class name of the bean type
  3374. */
  3375. @SuppressWarnings("unchecked")
  3376. protected void setBeanType(String beanTypeClassName) {
  3377. setBeanType((Class<T>) resolveClass(beanTypeClassName));
  3378. }
  3379. /**
  3380. * Sets the bean type to use for property mapping.
  3381. * <p>
  3382. * This method is responsible also for setting or updating the property set
  3383. * so that it matches the given bean type.
  3384. * <p>
  3385. * Protected mostly for Designer needs, typically should not be overridden
  3386. * or even called.
  3387. *
  3388. * @param beanType
  3389. * the bean type class
  3390. */
  3391. protected void setBeanType(Class<T> beanType) {
  3392. this.beanType = beanType;
  3393. setPropertySet(BeanPropertySet.get(beanType));
  3394. }
  3395. private Class<?> resolveClass(String qualifiedClassName) {
  3396. try {
  3397. Class<?> resolvedClass = Class.forName(qualifiedClassName, true,
  3398. VaadinServiceClassLoaderUtil.findDefaultClassLoader());
  3399. return resolvedClass;
  3400. } catch (ClassNotFoundException | SecurityException e) {
  3401. throw new IllegalArgumentException(
  3402. "Unable to find class " + qualifiedClassName, e);
  3403. }
  3404. }
  3405. @Override
  3406. protected void doWriteDesign(Element design, DesignContext designContext) {
  3407. Attributes attr = design.attributes();
  3408. if (this.beanType != null) {
  3409. design.attr(DECLARATIVE_DATA_ITEM_TYPE,
  3410. this.beanType.getCanonicalName());
  3411. }
  3412. DesignAttributeHandler.writeAttribute("selection-allowed", attr,
  3413. isReadOnly(), false, Boolean.class, designContext);
  3414. Attributes attrs = design.attributes();
  3415. Grid<?> defaultInstance = designContext.getDefaultInstance(this);
  3416. DesignAttributeHandler.writeAttribute("frozen-columns", attrs,
  3417. getFrozenColumnCount(), defaultInstance.getFrozenColumnCount(),
  3418. int.class, designContext);
  3419. if (HeightMode.ROW.equals(getHeightMode())) {
  3420. DesignAttributeHandler.writeAttribute("rows", attrs,
  3421. getHeightByRows(), defaultInstance.getHeightByRows(),
  3422. double.class, designContext);
  3423. }
  3424. SelectionMode mode = getSelectionMode();
  3425. if (mode != null) {
  3426. DesignAttributeHandler.writeAttribute("selection-mode", attrs, mode,
  3427. SelectionMode.SINGLE, SelectionMode.class, designContext);
  3428. }
  3429. writeStructure(design, designContext);
  3430. }
  3431. @Override
  3432. protected T deserializeDeclarativeRepresentation(String item) {
  3433. if (item == null) {
  3434. return super.deserializeDeclarativeRepresentation(
  3435. new String(UUID.randomUUID().toString()));
  3436. }
  3437. return super.deserializeDeclarativeRepresentation(new String(item));
  3438. }
  3439. @Override
  3440. protected boolean isReadOnly() {
  3441. SelectionMode selectionMode = getSelectionMode();
  3442. if (SelectionMode.SINGLE.equals(selectionMode)) {
  3443. return asSingleSelect().isReadOnly();
  3444. } else if (SelectionMode.MULTI.equals(selectionMode)) {
  3445. return asMultiSelect().isReadOnly();
  3446. }
  3447. return false;
  3448. }
  3449. @Override
  3450. protected void setReadOnly(boolean readOnly) {
  3451. SelectionMode selectionMode = getSelectionMode();
  3452. if (SelectionMode.SINGLE.equals(selectionMode)) {
  3453. asSingleSelect().setReadOnly(readOnly);
  3454. } else if (SelectionMode.MULTI.equals(selectionMode)) {
  3455. asMultiSelect().setReadOnly(readOnly);
  3456. }
  3457. }
  3458. private void readStructure(Element design, DesignContext context) {
  3459. if (design.children().isEmpty()) {
  3460. return;
  3461. }
  3462. if (design.children().size() > 1
  3463. || !design.child(0).tagName().equals("table")) {
  3464. throw new DesignException(
  3465. "Grid needs to have a table element as its only child");
  3466. }
  3467. Element table = design.child(0);
  3468. Elements colgroups = table.getElementsByTag("colgroup");
  3469. if (colgroups.size() != 1) {
  3470. throw new DesignException(
  3471. "Table element in declarative Grid needs to have a"
  3472. + " colgroup defining the columns used in Grid");
  3473. }
  3474. List<DeclarativeValueProvider<T>> providers = new ArrayList<>();
  3475. for (Element col : colgroups.get(0).getElementsByTag("col")) {
  3476. String id = DesignAttributeHandler.readAttribute("column-id",
  3477. col.attributes(), null, String.class);
  3478. // If there is a property with a matching name available,
  3479. // map to that
  3480. Optional<PropertyDefinition<T, ?>> property = propertySet
  3481. .getProperties().filter(p -> p.getName().equals(id))
  3482. .findFirst();
  3483. Column<T, ?> column;
  3484. if (property.isPresent()) {
  3485. column = addColumn(id);
  3486. } else {
  3487. DeclarativeValueProvider<T> provider = new DeclarativeValueProvider<>();
  3488. column = new Column<>(provider, new HtmlRenderer());
  3489. addColumn(getGeneratedIdentifier(), column);
  3490. if (id != null) {
  3491. column.setId(id);
  3492. }
  3493. providers.add(provider);
  3494. }
  3495. column.readDesign(col, context);
  3496. }
  3497. for (Element child : table.children()) {
  3498. if (child.tagName().equals("thead")) {
  3499. getHeader().readDesign(child, context);
  3500. } else if (child.tagName().equals("tbody")) {
  3501. readData(child, providers);
  3502. } else if (child.tagName().equals("tfoot")) {
  3503. getFooter().readDesign(child, context);
  3504. }
  3505. }
  3506. }
  3507. protected void readData(Element body,
  3508. List<DeclarativeValueProvider<T>> providers) {
  3509. getSelectionModel().deselectAll();
  3510. List<T> items = new ArrayList<>();
  3511. List<T> selectedItems = new ArrayList<>();
  3512. for (Element row : body.children()) {
  3513. T item = deserializeDeclarativeRepresentation(row.attr("item"));
  3514. items.add(item);
  3515. if (row.hasAttr("selected")) {
  3516. selectedItems.add(item);
  3517. }
  3518. Elements cells = row.children();
  3519. int i = 0;
  3520. for (Element cell : cells) {
  3521. providers.get(i).addValue(item, cell.html());
  3522. i++;
  3523. }
  3524. }
  3525. setItems(items);
  3526. selectedItems.forEach(getSelectionModel()::select);
  3527. }
  3528. private void writeStructure(Element design, DesignContext designContext) {
  3529. if (getColumns().isEmpty()) {
  3530. return;
  3531. }
  3532. Element tableElement = design.appendElement("table");
  3533. Element colGroup = tableElement.appendElement("colgroup");
  3534. getColumns().forEach(column -> column
  3535. .writeDesign(colGroup.appendElement("col"), designContext));
  3536. // Always write thead. Reads correctly when there no header rows
  3537. getHeader().writeDesign(tableElement.appendElement("thead"),
  3538. designContext);
  3539. if (designContext.shouldWriteData(this)) {
  3540. Element bodyElement = tableElement.appendElement("tbody");
  3541. writeData(bodyElement, designContext);
  3542. }
  3543. if (getFooter().getRowCount() > 0) {
  3544. getFooter().writeDesign(tableElement.appendElement("tfoot"),
  3545. designContext);
  3546. }
  3547. }
  3548. protected void writeData(Element body, DesignContext designContext) {
  3549. getDataProvider().fetch(new Query<>())
  3550. .forEach(item -> writeRow(body, item, designContext));
  3551. }
  3552. private void writeRow(Element container, T item, DesignContext context) {
  3553. Element tableRow = container.appendElement("tr");
  3554. tableRow.attr("item", serializeDeclarativeRepresentation(item));
  3555. if (getSelectionModel().isSelected(item)) {
  3556. tableRow.attr("selected", "");
  3557. }
  3558. for (Column<T, ?> column : getColumns()) {
  3559. Object value = column.valueProvider.apply(item);
  3560. tableRow.appendElement("td")
  3561. .append(Optional.ofNullable(value).map(Object::toString)
  3562. .map(DesignFormatter::encodeForTextNode)
  3563. .orElse(""));
  3564. }
  3565. }
  3566. private SelectionMode getSelectionMode() {
  3567. GridSelectionModel<T> selectionModel = getSelectionModel();
  3568. SelectionMode mode = null;
  3569. if (selectionModel.getClass().equals(SingleSelectionModelImpl.class)) {
  3570. mode = SelectionMode.SINGLE;
  3571. } else if (selectionModel.getClass()
  3572. .equals(MultiSelectionModelImpl.class)) {
  3573. mode = SelectionMode.MULTI;
  3574. } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
  3575. mode = SelectionMode.NONE;
  3576. }
  3577. return mode;
  3578. }
  3579. /**
  3580. * Sets a user-defined identifier for given column.
  3581. *
  3582. * @see Column#setId(String)
  3583. *
  3584. * @param column
  3585. * the column
  3586. * @param id
  3587. * the user-defined identifier
  3588. */
  3589. protected void setColumnId(String id, Column<T, ?> column) {
  3590. if (columnIds.containsKey(id)) {
  3591. throw new IllegalArgumentException("Duplicate ID for columns");
  3592. }
  3593. columnIds.put(id, column);
  3594. }
  3595. @Override
  3596. protected Collection<String> getCustomAttributes() {
  3597. Collection<String> result = super.getCustomAttributes();
  3598. // "rename" for frozen column count
  3599. result.add("frozen-column-count");
  3600. result.add("frozen-columns");
  3601. // "rename" for height-mode
  3602. result.add("height-by-rows");
  3603. result.add("rows");
  3604. // add a selection-mode attribute
  3605. result.add("selection-mode");
  3606. return result;
  3607. }
  3608. /**
  3609. * Returns a column identified by its internal id. This id should not be
  3610. * confused with the user-defined identifier.
  3611. *
  3612. * @param columnId
  3613. * the internal id of column
  3614. * @return column identified by internal id
  3615. */
  3616. protected Column<T, ?> getColumnByInternalId(String columnId) {
  3617. return columnKeys.get(columnId);
  3618. }
  3619. /**
  3620. * Returns the internal id for given column. This id should not be confused
  3621. * with the user-defined identifier.
  3622. *
  3623. * @param column
  3624. * the column
  3625. * @return internal id of given column
  3626. */
  3627. protected String getInternalIdForColumn(Column<T, ?> column) {
  3628. return column.getInternalId();
  3629. }
  3630. private void setSortOrder(List<GridSortOrder<T>> order,
  3631. boolean userOriginated) {
  3632. Objects.requireNonNull(order, "Sort order list cannot be null");
  3633. sortOrder.clear();
  3634. if (order.isEmpty()) {
  3635. // Grid is not sorted anymore.
  3636. getDataCommunicator().setBackEndSorting(Collections.emptyList());
  3637. getDataCommunicator().setInMemorySorting(null);
  3638. fireEvent(new SortEvent<>(this, new ArrayList<>(sortOrder),
  3639. userOriginated));
  3640. return;
  3641. }
  3642. sortOrder.addAll(order);
  3643. sort(userOriginated);
  3644. }
  3645. private void sort(boolean userOriginated) {
  3646. // Set sort orders
  3647. // In-memory comparator
  3648. getDataCommunicator().setInMemorySorting(createSortingComparator());
  3649. // Back-end sort properties
  3650. List<QuerySortOrder> sortProperties = new ArrayList<>();
  3651. sortOrder.stream().map(
  3652. order -> order.getSorted().getSortOrder(order.getDirection()))
  3653. .forEach(s -> s.forEach(sortProperties::add));
  3654. getDataCommunicator().setBackEndSorting(sortProperties);
  3655. // Close grid editor if it's open.
  3656. if (getEditor().isOpen()) {
  3657. getEditor().cancel();
  3658. }
  3659. fireEvent(new SortEvent<>(this, new ArrayList<>(sortOrder),
  3660. userOriginated));
  3661. }
  3662. /**
  3663. * Creates a comparator for grid to sort rows.
  3664. *
  3665. * @return the comparator based on column sorting information.
  3666. */
  3667. protected SerializableComparator<T> createSortingComparator() {
  3668. BinaryOperator<SerializableComparator<T>> operator = (comparator1,
  3669. comparator2) -> {
  3670. /*
  3671. * thenComparing is defined to return a serializable comparator as
  3672. * long as both original comparators are also serializable
  3673. */
  3674. return comparator1.thenComparing(comparator2)::compare;
  3675. };
  3676. return sortOrder.stream().map(
  3677. order -> order.getSorted().getComparator(order.getDirection()))
  3678. .reduce((x, y) -> 0, operator);
  3679. }
  3680. }