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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686
  1. /*
  2. * Copyright 2000-2018 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.EnumSet;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Iterator;
  28. import java.util.LinkedHashMap;
  29. import java.util.LinkedHashSet;
  30. import java.util.LinkedList;
  31. import java.util.List;
  32. import java.util.Locale;
  33. import java.util.Map;
  34. import java.util.Map.Entry;
  35. import java.util.Set;
  36. import java.util.logging.Level;
  37. import java.util.logging.Logger;
  38. import org.jsoup.Jsoup;
  39. import org.jsoup.nodes.Attributes;
  40. import org.jsoup.nodes.Element;
  41. import org.jsoup.select.Elements;
  42. import com.vaadin.data.Container;
  43. import com.vaadin.data.Container.Indexed;
  44. import com.vaadin.data.Container.ItemSetChangeEvent;
  45. import com.vaadin.data.Container.ItemSetChangeListener;
  46. import com.vaadin.data.Container.ItemSetChangeNotifier;
  47. import com.vaadin.data.Container.PropertySetChangeEvent;
  48. import com.vaadin.data.Container.PropertySetChangeListener;
  49. import com.vaadin.data.Container.PropertySetChangeNotifier;
  50. import com.vaadin.data.Container.Sortable;
  51. import com.vaadin.data.Item;
  52. import com.vaadin.data.Property;
  53. import com.vaadin.data.Validator.InvalidValueException;
  54. import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory;
  55. import com.vaadin.data.fieldgroup.FieldGroup;
  56. import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
  57. import com.vaadin.data.fieldgroup.FieldGroupFieldFactory;
  58. import com.vaadin.data.sort.Sort;
  59. import com.vaadin.data.sort.SortOrder;
  60. import com.vaadin.data.util.IndexedContainer;
  61. import com.vaadin.data.util.converter.Converter;
  62. import com.vaadin.data.util.converter.ConverterUtil;
  63. import com.vaadin.event.ContextClickEvent;
  64. import com.vaadin.event.ItemClickEvent;
  65. import com.vaadin.event.ItemClickEvent.ItemClickListener;
  66. import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
  67. import com.vaadin.event.SelectionEvent;
  68. import com.vaadin.event.SelectionEvent.SelectionListener;
  69. import com.vaadin.event.SelectionEvent.SelectionNotifier;
  70. import com.vaadin.event.SortEvent;
  71. import com.vaadin.event.SortEvent.SortListener;
  72. import com.vaadin.event.SortEvent.SortNotifier;
  73. import com.vaadin.server.AbstractClientConnector;
  74. import com.vaadin.server.AbstractExtension;
  75. import com.vaadin.server.EncodeResult;
  76. import com.vaadin.server.ErrorMessage;
  77. import com.vaadin.server.Extension;
  78. import com.vaadin.server.JsonCodec;
  79. import com.vaadin.server.KeyMapper;
  80. import com.vaadin.server.VaadinSession;
  81. import com.vaadin.server.communication.data.DataGenerator;
  82. import com.vaadin.server.communication.data.RpcDataProviderExtension;
  83. import com.vaadin.shared.MouseEventDetails;
  84. import com.vaadin.shared.data.sort.SortDirection;
  85. import com.vaadin.shared.ui.grid.ColumnResizeMode;
  86. import com.vaadin.shared.ui.grid.EditorClientRpc;
  87. import com.vaadin.shared.ui.grid.EditorServerRpc;
  88. import com.vaadin.shared.ui.grid.GridClientRpc;
  89. import com.vaadin.shared.ui.grid.GridColumnState;
  90. import com.vaadin.shared.ui.grid.GridConstants;
  91. import com.vaadin.shared.ui.grid.GridConstants.Section;
  92. import com.vaadin.shared.ui.grid.GridServerRpc;
  93. import com.vaadin.shared.ui.grid.GridState;
  94. import com.vaadin.shared.ui.grid.GridStaticCellType;
  95. import com.vaadin.shared.ui.grid.GridStaticSectionState;
  96. import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
  97. import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
  98. import com.vaadin.shared.ui.grid.HeightMode;
  99. import com.vaadin.shared.ui.grid.ScrollDestination;
  100. import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
  101. import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
  102. import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
  103. import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
  104. import com.vaadin.shared.ui.label.ContentMode;
  105. import com.vaadin.shared.util.SharedUtil;
  106. import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed;
  107. import com.vaadin.ui.declarative.DesignAttributeHandler;
  108. import com.vaadin.ui.declarative.DesignContext;
  109. import com.vaadin.ui.declarative.DesignException;
  110. import com.vaadin.ui.declarative.DesignFormatter;
  111. import com.vaadin.ui.renderers.HtmlRenderer;
  112. import com.vaadin.ui.renderers.Renderer;
  113. import com.vaadin.ui.renderers.TextRenderer;
  114. import com.vaadin.util.ReflectTools;
  115. import elemental.json.Json;
  116. import elemental.json.JsonObject;
  117. import elemental.json.JsonValue;
  118. /**
  119. * A grid component for displaying tabular data.
  120. * <p>
  121. * Grid is always bound to a {@link Container.Indexed}, but is not a
  122. * {@code Container} of any kind in of itself. The contents of the given
  123. * Container is displayed with the help of {@link Renderer Renderers}.
  124. *
  125. * <h3 id="grid-headers-and-footers">Headers and Footers</h3>
  126. * <p>
  127. *
  128. *
  129. * <h3 id="grid-converters-and-renderers">Converters and Renderers</h3>
  130. * <p>
  131. * Each column has its own {@link Renderer} that displays data into something
  132. * that can be displayed in the browser. That data is first converted with a
  133. * {@link Converter} into something that the Renderer can process. This can also
  134. * be an implicit step - if a column has a simple data type, like a String, no
  135. * explicit assignment is needed.
  136. * <p>
  137. * Usually a renderer takes some kind of object, and converts it into a
  138. * HTML-formatted string.
  139. * <p>
  140. * <code><pre>
  141. * Grid grid = new Grid(myContainer);
  142. * Column column = grid.getColumn(STRING_DATE_PROPERTY);
  143. * column.setConverter(new StringToDateConverter());
  144. * column.setRenderer(new MyColorfulDateRenderer());
  145. * </pre></code>
  146. *
  147. * <h3 id="grid-lazyloading">Lazy Loading</h3>
  148. * <p>
  149. * The data is accessed as it is needed by Grid and not any sooner. In other
  150. * words, if the given Container is huge, but only the first few rows are
  151. * displayed to the user, only those (and a few more, for caching purposes) are
  152. * accessed.
  153. *
  154. * <h3 id="grid-selection-modes-and-models">Selection Modes and Models</h3>
  155. * <p>
  156. * Grid supports three selection <em>{@link SelectionMode modes}</em> (single,
  157. * multi, none), and comes bundled with one <em>{@link SelectionModel
  158. * model}</em> for each of the modes. The distinction between a selection mode
  159. * and selection model is as follows: a <em>mode</em> essentially says whether
  160. * you can have one, many or no rows selected. The model, however, has the
  161. * behavioral details of each. A single selection model may require that the
  162. * user deselects one row before selecting another one. A variant of a
  163. * multiselect might have a configurable maximum of rows that may be selected.
  164. * And so on.
  165. * <p>
  166. * <code><pre>
  167. * Grid grid = new Grid(myContainer);
  168. *
  169. * // uses the bundled SingleSelectionModel class
  170. * grid.setSelectionMode(SelectionMode.SINGLE);
  171. *
  172. * // changes the behavior to a custom selection model
  173. * grid.setSelectionModel(new MyTwoSelectionModel());
  174. * </pre></code>
  175. *
  176. * @since 7.4
  177. * @author Vaadin Ltd
  178. */
  179. public class Grid extends AbstractFocusable implements SelectionNotifier,
  180. SortNotifier, SelectiveRenderer, ItemClickNotifier {
  181. /**
  182. * An event listener for column visibility change events in the Grid.
  183. *
  184. * @since 7.5.0
  185. */
  186. public interface ColumnVisibilityChangeListener extends Serializable {
  187. /**
  188. * Called when a column has become hidden or unhidden.
  189. *
  190. * @param event
  191. */
  192. void columnVisibilityChanged(ColumnVisibilityChangeEvent event);
  193. }
  194. /**
  195. * An event that is fired when a column's visibility changes.
  196. *
  197. * @since 7.5.0
  198. */
  199. public static class ColumnVisibilityChangeEvent extends Component.Event {
  200. private final Column column;
  201. private final boolean userOriginated;
  202. private final boolean hidden;
  203. /**
  204. * Constructor for a column visibility change event.
  205. *
  206. * @param source
  207. * the grid from which this event originates
  208. * @param column
  209. * the column that changed its visibility
  210. * @param hidden
  211. * <code>true</code> if the column was hidden,
  212. * <code>false</code> if it became visible
  213. * @param isUserOriginated
  214. * <code>true</code> iff the event was triggered by an UI
  215. * interaction
  216. */
  217. public ColumnVisibilityChangeEvent(Grid source, Column column,
  218. boolean hidden, boolean isUserOriginated) {
  219. super(source);
  220. this.column = column;
  221. this.hidden = hidden;
  222. userOriginated = isUserOriginated;
  223. }
  224. /**
  225. * Gets the column that became hidden or visible.
  226. *
  227. * @return the column that became hidden or visible.
  228. * @see Column#isHidden()
  229. */
  230. public Column getColumn() {
  231. return column;
  232. }
  233. /**
  234. * Was the column set hidden or visible.
  235. *
  236. * @return <code>true</code> if the column was hidden <code>false</code>
  237. * if it was set visible
  238. */
  239. public boolean isHidden() {
  240. return hidden;
  241. }
  242. /**
  243. * Returns <code>true</code> if the column reorder was done by the user,
  244. * <code>false</code> if not and it was triggered by server side code.
  245. *
  246. * @return <code>true</code> if event is a result of user interaction
  247. */
  248. public boolean isUserOriginated() {
  249. return userOriginated;
  250. }
  251. }
  252. /**
  253. * A callback interface for generating details for a particular row in Grid.
  254. *
  255. * @since 7.5.0
  256. * @author Vaadin Ltd
  257. * @see DetailsGenerator#NULL
  258. */
  259. public interface DetailsGenerator extends Serializable {
  260. /** A details generator that provides no details */
  261. public DetailsGenerator NULL = new DetailsGenerator() {
  262. @Override
  263. public Component getDetails(RowReference rowReference) {
  264. return null;
  265. }
  266. };
  267. /**
  268. * This method is called for whenever a details row needs to be shown on
  269. * the client. Grid removes all of its references to details components
  270. * when they are no longer displayed on the client-side and will
  271. * re-request once needed again.
  272. * <p>
  273. * <em>Note:</em> If a component gets generated, it may not be manually
  274. * attached anywhere. The same details component can not be displayed
  275. * for multiple different rows.
  276. *
  277. * @param rowReference
  278. * the reference for the row for which to generate details
  279. * @return the details for the given row, or <code>null</code> to leave
  280. * the details empty.
  281. */
  282. Component getDetails(RowReference rowReference);
  283. }
  284. /**
  285. * A class that manages details components by calling
  286. * {@link DetailsGenerator} as needed. Details components are attached by
  287. * this class when the {@link RpcDataProviderExtension} is sending data to
  288. * the client. Details components are detached and forgotten when client
  289. * informs that it has dropped the corresponding item.
  290. *
  291. * @since 7.6.1
  292. */
  293. public final static class DetailComponentManager
  294. extends AbstractGridExtension implements DataGenerator {
  295. /**
  296. * The user-defined details generator.
  297. *
  298. * @see #setDetailsGenerator(DetailsGenerator)
  299. */
  300. private DetailsGenerator detailsGenerator;
  301. /**
  302. * This map represents all details that are currently visible on the
  303. * client. Details components get destroyed once they scroll out of
  304. * view.
  305. */
  306. private final Map<Object, Component> itemIdToDetailsComponent = new HashMap<Object, Component>();
  307. /**
  308. * Set of item ids that got <code>null</code> from DetailsGenerator when
  309. * {@link DetailsGenerator#getDetails(RowReference)} was called.
  310. */
  311. private final Set<Object> emptyDetails = new HashSet<Object>();
  312. /**
  313. * Set of item IDs for all open details rows. Contains even the ones
  314. * that are not currently visible on the client.
  315. */
  316. private final Set<Object> openDetails = new HashSet<Object>();
  317. public DetailComponentManager(Grid grid) {
  318. this(grid, DetailsGenerator.NULL);
  319. }
  320. public DetailComponentManager(Grid grid,
  321. DetailsGenerator detailsGenerator) {
  322. super(grid);
  323. setDetailsGenerator(detailsGenerator);
  324. }
  325. /**
  326. * Creates a details component with the help of the user-defined
  327. * {@link DetailsGenerator}.
  328. * <p>
  329. * This method attaches created components to the parent {@link Grid}.
  330. *
  331. * @param itemId
  332. * the item id for which to create the details component.
  333. * @throws IllegalStateException
  334. * if the current details generator provides a component
  335. * that was manually attached.
  336. */
  337. private void createDetails(Object itemId) throws IllegalStateException {
  338. assert itemId != null : "itemId was null";
  339. if (itemIdToDetailsComponent.containsKey(itemId)
  340. || emptyDetails.contains(itemId)) {
  341. // Don't overwrite existing components
  342. return;
  343. }
  344. RowReference rowReference = new RowReference(getParentGrid());
  345. rowReference.set(itemId);
  346. DetailsGenerator detailsGenerator = getParentGrid()
  347. .getDetailsGenerator();
  348. Component details = detailsGenerator.getDetails(rowReference);
  349. if (details != null) {
  350. if (details.getParent() != null) {
  351. String name = detailsGenerator.getClass().getName();
  352. throw new IllegalStateException(
  353. name + " generated a details component that already "
  354. + "was attached. (itemId: " + itemId
  355. + ", component: " + details + ")");
  356. }
  357. itemIdToDetailsComponent.put(itemId, details);
  358. addComponentToGrid(details);
  359. assert !emptyDetails.contains(itemId) : "Bookeeping thinks "
  360. + "itemId is empty even though we just created a "
  361. + "component for it (" + itemId + ")";
  362. } else {
  363. emptyDetails.add(itemId);
  364. }
  365. }
  366. /**
  367. * Destroys a details component correctly.
  368. * <p>
  369. * This method will detach the component from parent {@link Grid}.
  370. *
  371. * @param itemId
  372. * the item id for which to destroy the details component
  373. */
  374. private void destroyDetails(Object itemId) {
  375. emptyDetails.remove(itemId);
  376. Component removedComponent = itemIdToDetailsComponent
  377. .remove(itemId);
  378. if (removedComponent == null) {
  379. return;
  380. }
  381. removeComponentFromGrid(removedComponent);
  382. }
  383. /**
  384. * Recreates all visible details components.
  385. */
  386. public void refreshDetails() {
  387. Set<Object> visibleItemIds = new HashSet<Object>(
  388. itemIdToDetailsComponent.keySet());
  389. for (Object itemId : visibleItemIds) {
  390. destroyDetails(itemId);
  391. createDetails(itemId);
  392. refreshRow(itemId);
  393. }
  394. }
  395. /**
  396. * Sets details visiblity status of given item id.
  397. *
  398. * @param itemId
  399. * item id to set
  400. * @param visible
  401. * <code>true</code> if visible; <code>false</code> if not
  402. */
  403. public void setDetailsVisible(Object itemId, boolean visible) {
  404. if ((visible && openDetails.contains(itemId))
  405. || (!visible && !openDetails.contains(itemId))) {
  406. return;
  407. }
  408. if (visible) {
  409. openDetails.add(itemId);
  410. refreshRow(itemId);
  411. } else {
  412. openDetails.remove(itemId);
  413. destroyDetails(itemId);
  414. refreshRow(itemId);
  415. }
  416. }
  417. @Override
  418. public void generateData(Object itemId, Item item, JsonObject rowData) {
  419. // DetailComponentManager should not send anything if details
  420. // generator is the default null version.
  421. if (openDetails.contains(itemId)
  422. && !detailsGenerator.equals(DetailsGenerator.NULL)) {
  423. // Double check to be sure details component exists.
  424. createDetails(itemId);
  425. Component detailsComponent = itemIdToDetailsComponent
  426. .get(itemId);
  427. rowData.put(GridState.JSONKEY_DETAILS_VISIBLE,
  428. (detailsComponent != null
  429. ? detailsComponent.getConnectorId() : ""));
  430. }
  431. }
  432. @Override
  433. public void destroyData(Object itemId) {
  434. if (openDetails.contains(itemId)) {
  435. destroyDetails(itemId);
  436. }
  437. }
  438. /**
  439. * Sets a new details generator for row details.
  440. * <p>
  441. * The currently opened row details will be re-rendered.
  442. *
  443. * @param detailsGenerator
  444. * the details generator to set
  445. * @throws IllegalArgumentException
  446. * if detailsGenerator is <code>null</code>;
  447. */
  448. public void setDetailsGenerator(DetailsGenerator detailsGenerator)
  449. throws IllegalArgumentException {
  450. if (detailsGenerator == null) {
  451. throw new IllegalArgumentException(
  452. "Details generator may not be null");
  453. } else if (detailsGenerator == this.detailsGenerator) {
  454. return;
  455. }
  456. this.detailsGenerator = detailsGenerator;
  457. refreshDetails();
  458. }
  459. /**
  460. * Gets the current details generator for row details.
  461. *
  462. * @return the detailsGenerator the current details generator
  463. */
  464. public DetailsGenerator getDetailsGenerator() {
  465. return detailsGenerator;
  466. }
  467. /**
  468. * Checks whether details are visible for the given item.
  469. *
  470. * @param itemId
  471. * the id of the item for which to check details visibility
  472. * @return <code>true</code> iff the details are visible
  473. */
  474. public boolean isDetailsVisible(Object itemId) {
  475. return openDetails.contains(itemId);
  476. }
  477. }
  478. /**
  479. * Custom field group that allows finding property types before an item has
  480. * been bound.
  481. */
  482. private final class CustomFieldGroup extends FieldGroup {
  483. public CustomFieldGroup() {
  484. setFieldFactory(EditorFieldFactory.get());
  485. }
  486. @Override
  487. protected Class<?> getPropertyType(Object propertyId)
  488. throws BindException {
  489. if (getItemDataSource() == null) {
  490. return datasource.getType(propertyId);
  491. } else {
  492. return super.getPropertyType(propertyId);
  493. }
  494. }
  495. @Override
  496. protected <T extends Field> T build(String caption, Class<?> dataType,
  497. Class<T> fieldType) throws BindException {
  498. T field = super.build(caption, dataType, fieldType);
  499. if (field instanceof CheckBox) {
  500. field.setCaption(null);
  501. }
  502. return field;
  503. }
  504. @Override
  505. protected void bindFields() {
  506. List<Field<?>> fields = new ArrayList<Field<?>>(getFields());
  507. Item itemDataSource = getItemDataSource();
  508. if (itemDataSource == null) {
  509. unbindFields(fields);
  510. } else {
  511. bindFields(fields, itemDataSource);
  512. }
  513. }
  514. private void unbindFields(List<Field<?>> fields) {
  515. for (Field<?> field : fields) {
  516. clearField(field);
  517. unbind(field);
  518. field.setParent(null);
  519. }
  520. }
  521. private void bindFields(List<Field<?>> fields, Item itemDataSource) {
  522. for (Field<?> field : fields) {
  523. if (itemDataSource
  524. .getItemProperty(getPropertyId(field)) != null) {
  525. bind(field, getPropertyId(field));
  526. }
  527. }
  528. }
  529. }
  530. /**
  531. * Field factory used by default in the editor.
  532. *
  533. * Aims to fields of suitable type and with suitable size for use in the
  534. * editor row.
  535. */
  536. public static class EditorFieldFactory
  537. extends DefaultFieldGroupFieldFactory {
  538. private static final EditorFieldFactory INSTANCE = new EditorFieldFactory();
  539. protected EditorFieldFactory() {
  540. }
  541. /**
  542. * Returns the singleton instance
  543. *
  544. * @return the singleton instance
  545. */
  546. public static EditorFieldFactory get() {
  547. return INSTANCE;
  548. }
  549. @Override
  550. public <T extends Field> T createField(Class<?> type,
  551. Class<T> fieldType) {
  552. T f = super.createField(type, fieldType);
  553. if (f != null) {
  554. f.setWidth("100%");
  555. }
  556. return f;
  557. }
  558. @Override
  559. protected AbstractSelect createCompatibleSelect(
  560. Class<? extends AbstractSelect> fieldType) {
  561. if (anySelect(fieldType)) {
  562. return super.createCompatibleSelect(ComboBox.class);
  563. }
  564. return super.createCompatibleSelect(fieldType);
  565. }
  566. @Override
  567. protected void populateWithEnumData(AbstractSelect select,
  568. Class<? extends Enum> enumClass) {
  569. // Use enums directly and the EnumToStringConverter to be consistent
  570. // with what is shown in the Grid
  571. @SuppressWarnings("unchecked")
  572. EnumSet<?> enumSet = EnumSet.allOf(enumClass);
  573. for (Object r : enumSet) {
  574. select.addItem(r);
  575. }
  576. }
  577. }
  578. /**
  579. * Error handler for the editor
  580. */
  581. public interface EditorErrorHandler extends Serializable {
  582. /**
  583. * Called when an exception occurs while the editor row is being saved
  584. *
  585. * @param event
  586. * An event providing more information about the error
  587. */
  588. void commitError(CommitErrorEvent event);
  589. }
  590. /**
  591. * ContextClickEvent for the Grid Component.
  592. *
  593. * @since 7.6
  594. */
  595. public static class GridContextClickEvent extends ContextClickEvent {
  596. private final Object itemId;
  597. private final int rowIndex;
  598. private final Object propertyId;
  599. private final Section section;
  600. public GridContextClickEvent(Grid source,
  601. MouseEventDetails mouseEventDetails, Section section,
  602. int rowIndex, Object itemId, Object propertyId) {
  603. super(source, mouseEventDetails);
  604. this.itemId = itemId;
  605. this.propertyId = propertyId;
  606. this.section = section;
  607. this.rowIndex = rowIndex;
  608. }
  609. /**
  610. * Returns the item id of context clicked row.
  611. *
  612. * @return item id of clicked row; <code>null</code> if header or footer
  613. */
  614. public Object getItemId() {
  615. return itemId;
  616. }
  617. /**
  618. * Returns property id of clicked column.
  619. *
  620. * @return property id
  621. */
  622. public Object getPropertyId() {
  623. return propertyId;
  624. }
  625. /**
  626. * Return the clicked section of Grid.
  627. *
  628. * @return section of grid
  629. */
  630. public Section getSection() {
  631. return section;
  632. }
  633. /**
  634. * Returns the clicked row index relative to Grid section. In the body
  635. * of the Grid the index is the item index in the Container. Header and
  636. * Footer rows for index can be fetched with
  637. * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
  638. *
  639. * @return row index in section
  640. */
  641. public int getRowIndex() {
  642. return rowIndex;
  643. }
  644. @Override
  645. public Grid getComponent() {
  646. return (Grid) super.getComponent();
  647. }
  648. }
  649. /**
  650. * An event which is fired when saving the editor fails
  651. */
  652. public static class CommitErrorEvent extends Component.Event {
  653. private CommitException cause;
  654. private Set<Column> errorColumns = new HashSet<Column>();
  655. private String userErrorMessage;
  656. public CommitErrorEvent(Grid grid, CommitException cause) {
  657. super(grid);
  658. this.cause = cause;
  659. userErrorMessage = cause.getLocalizedMessage();
  660. }
  661. /**
  662. * Retrieves the cause of the failure
  663. *
  664. * @return the cause of the failure
  665. */
  666. public CommitException getCause() {
  667. return cause;
  668. }
  669. @Override
  670. public Grid getComponent() {
  671. return (Grid) super.getComponent();
  672. }
  673. /**
  674. * Checks if validation exceptions caused this error
  675. *
  676. * @return true if the problem was caused by a validation error
  677. */
  678. public boolean isValidationFailure() {
  679. return cause.getCause() instanceof InvalidValueException;
  680. }
  681. /**
  682. * Marks that an error indicator should be shown for the editor of a
  683. * column.
  684. *
  685. * @param column
  686. * the column to show an error for
  687. */
  688. public void addErrorColumn(Column column) {
  689. errorColumns.add(column);
  690. }
  691. /**
  692. * Gets all the columns that have been marked as erroneous.
  693. *
  694. * @return an umodifiable collection of erroneous columns
  695. */
  696. public Collection<Column> getErrorColumns() {
  697. return Collections.unmodifiableCollection(errorColumns);
  698. }
  699. /**
  700. * Gets the error message to show to the user.
  701. *
  702. * @return error message to show
  703. */
  704. public String getUserErrorMessage() {
  705. return userErrorMessage;
  706. }
  707. /**
  708. * Sets the error message to show to the user.
  709. *
  710. * @param userErrorMessage
  711. * the user error message to set
  712. */
  713. public void setUserErrorMessage(String userErrorMessage) {
  714. this.userErrorMessage = userErrorMessage;
  715. }
  716. }
  717. /**
  718. * An event listener for column reorder events in the Grid.
  719. *
  720. * @since 7.5.0
  721. */
  722. public interface ColumnReorderListener extends Serializable {
  723. /**
  724. * Called when the columns of the grid have been reordered.
  725. *
  726. * @param event
  727. * An event providing more information
  728. */
  729. void columnReorder(ColumnReorderEvent event);
  730. }
  731. /**
  732. * An event that is fired when the columns are reordered.
  733. *
  734. * @since 7.5.0
  735. */
  736. public static class ColumnReorderEvent extends Component.Event {
  737. private final boolean userOriginated;
  738. /**
  739. *
  740. * @param source
  741. * the grid where the event originated from
  742. * @param userOriginated
  743. * <code>true</code> if event is a result of user
  744. * interaction, <code>false</code> if from API call
  745. */
  746. public ColumnReorderEvent(Grid source, boolean userOriginated) {
  747. super(source);
  748. this.userOriginated = userOriginated;
  749. }
  750. /**
  751. * Returns <code>true</code> if the column reorder was done by the user,
  752. * <code>false</code> if not and it was triggered by server side code.
  753. *
  754. * @return <code>true</code> if event is a result of user interaction
  755. */
  756. public boolean isUserOriginated() {
  757. return userOriginated;
  758. }
  759. }
  760. /**
  761. * An event listener for column resize events in the Grid.
  762. *
  763. * @since 7.6
  764. */
  765. public interface ColumnResizeListener extends Serializable {
  766. /**
  767. * Called when the columns of the grid have been resized.
  768. *
  769. * @param event
  770. * An event providing more information
  771. */
  772. void columnResize(ColumnResizeEvent event);
  773. }
  774. /**
  775. * An event that is fired when a column is resized, either programmatically
  776. * or by the user.
  777. *
  778. * @since 7.6
  779. */
  780. public static class ColumnResizeEvent extends Component.Event {
  781. private final Column column;
  782. private final boolean userOriginated;
  783. /**
  784. *
  785. * @param source
  786. * the grid where the event originated from
  787. * @param userOriginated
  788. * <code>true</code> if event is a result of user
  789. * interaction, <code>false</code> if from API call
  790. */
  791. public ColumnResizeEvent(Grid source, Column column,
  792. boolean userOriginated) {
  793. super(source);
  794. this.column = column;
  795. this.userOriginated = userOriginated;
  796. }
  797. /**
  798. * Returns the column that was resized.
  799. *
  800. * @return the resized column.
  801. */
  802. public Column getColumn() {
  803. return column;
  804. }
  805. /**
  806. * Returns <code>true</code> if the column resize was done by the user,
  807. * <code>false</code> if not and it was triggered by server side code.
  808. *
  809. * @return <code>true</code> if event is a result of user interaction
  810. */
  811. public boolean isUserOriginated() {
  812. return userOriginated;
  813. }
  814. }
  815. /**
  816. * Interface for an editor event listener
  817. */
  818. public interface EditorListener extends Serializable {
  819. public static final Method EDITOR_OPEN_METHOD = ReflectTools.findMethod(
  820. EditorListener.class, "editorOpened", EditorOpenEvent.class);
  821. public static final Method EDITOR_MOVE_METHOD = ReflectTools.findMethod(
  822. EditorListener.class, "editorMoved", EditorMoveEvent.class);
  823. public static final Method EDITOR_CLOSE_METHOD = ReflectTools
  824. .findMethod(EditorListener.class, "editorClosed",
  825. EditorCloseEvent.class);
  826. /**
  827. * Called when an editor is opened
  828. *
  829. * @param e
  830. * an editor open event object
  831. */
  832. public void editorOpened(EditorOpenEvent e);
  833. /**
  834. * Called when an editor is reopened without closing it first
  835. *
  836. * @param e
  837. * an editor move event object
  838. */
  839. public void editorMoved(EditorMoveEvent e);
  840. /**
  841. * Called when an editor is closed
  842. *
  843. * @param e
  844. * an editor close event object
  845. */
  846. public void editorClosed(EditorCloseEvent e);
  847. }
  848. /**
  849. * Base class for editor related events
  850. */
  851. public static abstract class EditorEvent extends Component.Event {
  852. private Object itemID;
  853. protected EditorEvent(Grid source, Object itemID) {
  854. super(source);
  855. this.itemID = itemID;
  856. }
  857. /**
  858. * Get the item (row) for which this editor was opened
  859. */
  860. public Object getItem() {
  861. return itemID;
  862. }
  863. }
  864. /**
  865. * This event gets fired when an editor is opened
  866. */
  867. public static class EditorOpenEvent extends EditorEvent {
  868. public EditorOpenEvent(Grid source, Object itemID) {
  869. super(source, itemID);
  870. }
  871. }
  872. /**
  873. * This event gets fired when an editor is opened while another row is being
  874. * edited (i.e. editor focus moves elsewhere)
  875. */
  876. public static class EditorMoveEvent extends EditorEvent {
  877. public EditorMoveEvent(Grid source, Object itemID) {
  878. super(source, itemID);
  879. }
  880. }
  881. /**
  882. * This event gets fired when an editor is dismissed or closed by other
  883. * means.
  884. */
  885. public static class EditorCloseEvent extends EditorEvent {
  886. public EditorCloseEvent(Grid source, Object itemID) {
  887. super(source, itemID);
  888. }
  889. }
  890. /**
  891. * Default error handler for the editor
  892. *
  893. */
  894. public class DefaultEditorErrorHandler implements EditorErrorHandler {
  895. @Override
  896. public void commitError(CommitErrorEvent event) {
  897. Map<Field<?>, InvalidValueException> invalidFields = event
  898. .getCause().getInvalidFields();
  899. if (!invalidFields.isEmpty()) {
  900. Object firstErrorPropertyId = null;
  901. Field<?> firstErrorField = null;
  902. FieldGroup fieldGroup = event.getCause().getFieldGroup();
  903. for (Column column : getColumns()) {
  904. Object propertyId = column.getPropertyId();
  905. Field<?> field = fieldGroup.getField(propertyId);
  906. if (invalidFields.keySet().contains(field)) {
  907. event.addErrorColumn(column);
  908. if (firstErrorPropertyId == null) {
  909. firstErrorPropertyId = propertyId;
  910. firstErrorField = field;
  911. }
  912. }
  913. }
  914. /*
  915. * Validation error, show first failure as
  916. * "<Column header>: <message>"
  917. */
  918. String caption = getColumn(firstErrorPropertyId)
  919. .getHeaderCaption();
  920. String message = invalidFields.get(firstErrorField)
  921. .getLocalizedMessage();
  922. event.setUserErrorMessage(caption + ": " + message);
  923. } else {
  924. com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this).error(
  925. new ConnectorErrorEvent(Grid.this, event.getCause()));
  926. }
  927. }
  928. private Object getFirstPropertyId(FieldGroup fieldGroup,
  929. Set<Field<?>> keySet) {
  930. for (Column c : getColumns()) {
  931. Object propertyId = c.getPropertyId();
  932. Field<?> f = fieldGroup.getField(propertyId);
  933. if (keySet.contains(f)) {
  934. return propertyId;
  935. }
  936. }
  937. return null;
  938. }
  939. }
  940. /**
  941. * Selection modes representing built-in {@link SelectionModel
  942. * SelectionModels} that come bundled with {@link Grid}.
  943. * <p>
  944. * Passing one of these enums into
  945. * {@link Grid#setSelectionMode(SelectionMode)} is equivalent to calling
  946. * {@link Grid#setSelectionModel(SelectionModel)} with one of the built-in
  947. * implementations of {@link SelectionModel}.
  948. *
  949. * @see Grid#setSelectionMode(SelectionMode)
  950. * @see Grid#setSelectionModel(SelectionModel)
  951. */
  952. public enum SelectionMode {
  953. /** A SelectionMode that maps to {@link SingleSelectionModel} */
  954. SINGLE {
  955. @Override
  956. protected SelectionModel createModel() {
  957. return new SingleSelectionModel();
  958. }
  959. },
  960. /** A SelectionMode that maps to {@link MultiSelectionModel} */
  961. MULTI {
  962. @Override
  963. protected SelectionModel createModel() {
  964. return new MultiSelectionModel();
  965. }
  966. },
  967. /** A SelectionMode that maps to {@link NoSelectionModel} */
  968. NONE {
  969. @Override
  970. protected SelectionModel createModel() {
  971. return new NoSelectionModel();
  972. }
  973. };
  974. protected abstract SelectionModel createModel();
  975. }
  976. /**
  977. * The server-side interface that controls Grid's selection state.
  978. * SelectionModel should extend {@link AbstractGridExtension}.
  979. */
  980. public interface SelectionModel extends Serializable, Extension {
  981. /**
  982. * Interface implemented by selection models which support disabling
  983. * client side selection while still allowing programmatic selection on
  984. * the server.
  985. *
  986. * @since 7.7.7
  987. */
  988. public interface HasUserSelectionAllowed extends SelectionModel {
  989. /**
  990. * Checks if the user is allowed to change the selection.
  991. *
  992. * @return <code>true</code> if the user is allowed to change the
  993. * selection, <code>false</code> otherwise
  994. */
  995. public boolean isUserSelectionAllowed();
  996. /**
  997. * Sets whether the user is allowed to change the selection.
  998. *
  999. * @param userSelectionAllowed
  1000. * <code>true</code> if the user is allowed to change the
  1001. * selection, <code>false</code> otherwise
  1002. */
  1003. public void setUserSelectionAllowed(boolean userSelectionAllowed);
  1004. }
  1005. /**
  1006. * Checks whether an item is selected or not.
  1007. *
  1008. * @param itemId
  1009. * the item id to check for
  1010. * @return <code>true</code> iff the item is selected
  1011. */
  1012. boolean isSelected(Object itemId);
  1013. /**
  1014. * Returns a collection of all the currently selected itemIds.
  1015. *
  1016. * @return a collection of all the currently selected itemIds
  1017. */
  1018. Collection<Object> getSelectedRows();
  1019. /**
  1020. * Injects the current {@link Grid} instance into the SelectionModel.
  1021. * This method should usually call the extend method of
  1022. * {@link AbstractExtension}.
  1023. * <p>
  1024. * <em>Note:</em> This method should not be called manually.
  1025. *
  1026. * @param grid
  1027. * the Grid in which the SelectionModel currently is, or
  1028. * <code>null</code> when a selection model is being detached
  1029. * from a Grid.
  1030. */
  1031. void setGrid(Grid grid);
  1032. /**
  1033. * Resets the SelectiomModel to an initial state.
  1034. * <p>
  1035. * Most often this means that the selection state is cleared, but
  1036. * implementations are free to interpret the "initial state" as they
  1037. * wish. Some, for example, may want to keep the first selected item as
  1038. * selected.
  1039. */
  1040. void reset();
  1041. /**
  1042. * A SelectionModel that supports multiple selections to be made.
  1043. * <p>
  1044. * This interface has a contract of having the same behavior, no matter
  1045. * how the selection model is interacted with. In other words, if
  1046. * something is forbidden to do in e.g. the user interface, it must also
  1047. * be forbidden to do in the server-side and client-side APIs.
  1048. */
  1049. public interface Multi extends SelectionModel {
  1050. /**
  1051. * Marks items as selected.
  1052. * <p>
  1053. * This method does not clear any previous selection state, only
  1054. * adds to it.
  1055. *
  1056. * @param itemIds
  1057. * the itemId(s) to mark as selected
  1058. * @return <code>true</code> if the selection state changed.
  1059. * <code>false</code> if all the given itemIds already were
  1060. * selected
  1061. * @throws IllegalArgumentException
  1062. * if the <code>itemIds</code> varargs array is
  1063. * <code>null</code> or given itemIds don't exist in the
  1064. * container of Grid
  1065. * @see #deselect(Object...)
  1066. */
  1067. boolean select(Object... itemIds) throws IllegalArgumentException;
  1068. /**
  1069. * Marks items as selected.
  1070. * <p>
  1071. * This method does not clear any previous selection state, only
  1072. * adds to it.
  1073. *
  1074. * @param itemIds
  1075. * the itemIds to mark as selected
  1076. * @return <code>true</code> if the selection state changed.
  1077. * <code>false</code> if all the given itemIds already were
  1078. * selected
  1079. * @throws IllegalArgumentException
  1080. * if <code>itemIds</code> is <code>null</code> or given
  1081. * itemIds don't exist in the container of Grid
  1082. * @see #deselect(Collection)
  1083. */
  1084. boolean select(Collection<?> itemIds)
  1085. throws IllegalArgumentException;
  1086. /**
  1087. * Marks items as deselected.
  1088. *
  1089. * @param itemIds
  1090. * the itemId(s) to remove from being selected
  1091. * @return <code>true</code> if the selection state changed.
  1092. * <code>false</code> if none the given itemIds were
  1093. * selected previously
  1094. * @throws IllegalArgumentException
  1095. * if the <code>itemIds</code> varargs array is
  1096. * <code>null</code>
  1097. * @see #select(Object...)
  1098. */
  1099. boolean deselect(Object... itemIds) throws IllegalArgumentException;
  1100. /**
  1101. * Marks items as deselected.
  1102. *
  1103. * @param itemIds
  1104. * the itemId(s) to remove from being selected
  1105. * @return <code>true</code> if the selection state changed.
  1106. * <code>false</code> if none the given itemIds were
  1107. * selected previously
  1108. * @throws IllegalArgumentException
  1109. * if <code>itemIds</code> is <code>null</code>
  1110. * @see #select(Collection)
  1111. */
  1112. boolean deselect(Collection<?> itemIds)
  1113. throws IllegalArgumentException;
  1114. /**
  1115. * Marks all the items in the current Container as selected
  1116. *
  1117. * @return <code>true</code> iff some items were previously not
  1118. * selected
  1119. * @see #deselectAll()
  1120. */
  1121. boolean selectAll();
  1122. /**
  1123. * Marks all the items in the current Container as deselected
  1124. *
  1125. * @return <code>true</code> iff some items were previously selected
  1126. * @see #selectAll()
  1127. */
  1128. boolean deselectAll();
  1129. /**
  1130. * Marks items as selected while deselecting all items not in the
  1131. * given Collection.
  1132. *
  1133. * @param itemIds
  1134. * the itemIds to mark as selected
  1135. * @return <code>true</code> if the selection state changed.
  1136. * <code>false</code> if all the given itemIds already were
  1137. * selected
  1138. * @throws IllegalArgumentException
  1139. * if <code>itemIds</code> is <code>null</code> or given
  1140. * itemIds don't exist in the container of Grid
  1141. */
  1142. boolean setSelected(Collection<?> itemIds)
  1143. throws IllegalArgumentException;
  1144. /**
  1145. * Marks items as selected while deselecting all items not in the
  1146. * varargs array.
  1147. *
  1148. * @param itemIds
  1149. * the itemIds to mark as selected
  1150. * @return <code>true</code> if the selection state changed.
  1151. * <code>false</code> if all the given itemIds already were
  1152. * selected
  1153. * @throws IllegalArgumentException
  1154. * if the <code>itemIds</code> varargs array is
  1155. * <code>null</code> or given itemIds don't exist in the
  1156. * container of Grid
  1157. */
  1158. boolean setSelected(Object... itemIds)
  1159. throws IllegalArgumentException;
  1160. }
  1161. /**
  1162. * A SelectionModel that supports for only single rows to be selected at
  1163. * a time.
  1164. * <p>
  1165. * This interface has a contract of having the same behavior, no matter
  1166. * how the selection model is interacted with. In other words, if
  1167. * something is forbidden to do in e.g. the user interface, it must also
  1168. * be forbidden to do in the server-side and client-side APIs.
  1169. */
  1170. public interface Single extends SelectionModel {
  1171. /**
  1172. * Marks an item as selected.
  1173. *
  1174. * @param itemId
  1175. * the itemId to mark as selected; <code>null</code> for
  1176. * deselect
  1177. * @return <code>true</code> if the selection state changed.
  1178. * <code>false</code> if the itemId already was selected
  1179. * @throws IllegalStateException
  1180. * if the selection was illegal. One such reason might
  1181. * be that the given id was null, indicating a deselect,
  1182. * but implementation doesn't allow deselecting.
  1183. * re-selecting something
  1184. * @throws IllegalArgumentException
  1185. * if given itemId does not exist in the container of
  1186. * Grid
  1187. */
  1188. boolean select(Object itemId)
  1189. throws IllegalStateException, IllegalArgumentException;
  1190. /**
  1191. * Gets the item id of the currently selected item.
  1192. *
  1193. * @return the item id of the currently selected item, or
  1194. * <code>null</code> if nothing is selected
  1195. */
  1196. Object getSelectedRow();
  1197. /**
  1198. * Sets whether it's allowed to deselect the selected row through
  1199. * the UI. Deselection is allowed by default.
  1200. *
  1201. * @param deselectAllowed
  1202. * <code>true</code> if the selected row can be
  1203. * deselected without selecting another row instead;
  1204. * otherwise <code>false</code>.
  1205. */
  1206. public void setDeselectAllowed(boolean deselectAllowed);
  1207. /**
  1208. * Sets whether it's allowed to deselect the selected row through
  1209. * the UI.
  1210. *
  1211. * @return <code>true</code> if deselection is allowed; otherwise
  1212. * <code>false</code>
  1213. */
  1214. public boolean isDeselectAllowed();
  1215. }
  1216. /**
  1217. * A SelectionModel that does not allow for rows to be selected.
  1218. * <p>
  1219. * This interface has a contract of having the same behavior, no matter
  1220. * how the selection model is interacted with. In other words, if the
  1221. * developer is unable to select something programmatically, it is not
  1222. * allowed for the end-user to select anything, either.
  1223. */
  1224. public interface None extends SelectionModel {
  1225. /**
  1226. * {@inheritDoc}
  1227. *
  1228. * @return always <code>false</code>.
  1229. */
  1230. @Override
  1231. public boolean isSelected(Object itemId);
  1232. /**
  1233. * {@inheritDoc}
  1234. *
  1235. * @return always an empty collection.
  1236. */
  1237. @Override
  1238. public Collection<Object> getSelectedRows();
  1239. }
  1240. }
  1241. /**
  1242. * A base class for SelectionModels that contains some of the logic that is
  1243. * reusable.
  1244. */
  1245. public static abstract class AbstractSelectionModel extends
  1246. AbstractGridExtension implements SelectionModel, DataGenerator {
  1247. protected final LinkedHashSet<Object> selection = new LinkedHashSet<Object>();
  1248. @Override
  1249. public boolean isSelected(final Object itemId) {
  1250. return selection.contains(itemId);
  1251. }
  1252. @Override
  1253. public Collection<Object> getSelectedRows() {
  1254. return new ArrayList<Object>(selection);
  1255. }
  1256. @Override
  1257. public void setGrid(final Grid grid) {
  1258. if (grid != null) {
  1259. extend(grid);
  1260. }
  1261. }
  1262. /**
  1263. * Sanity check for existence of item id.
  1264. *
  1265. * @param itemId
  1266. * item id to be selected / deselected
  1267. *
  1268. * @throws IllegalArgumentException
  1269. * if item Id doesn't exist in the container of Grid
  1270. */
  1271. protected void checkItemIdExists(Object itemId)
  1272. throws IllegalArgumentException {
  1273. if (!getParentGrid().getContainerDataSource().containsId(itemId)) {
  1274. throw new IllegalArgumentException("Given item id (" + itemId
  1275. + ") does not exist in the container");
  1276. }
  1277. }
  1278. /**
  1279. * Sanity check for existence of item ids in given collection.
  1280. *
  1281. * @param itemIds
  1282. * item id collection to be selected / deselected
  1283. *
  1284. * @throws IllegalArgumentException
  1285. * if at least one item id doesn't exist in the container of
  1286. * Grid
  1287. */
  1288. protected void checkItemIdsExist(Collection<?> itemIds)
  1289. throws IllegalArgumentException {
  1290. for (Object itemId : itemIds) {
  1291. checkItemIdExists(itemId);
  1292. }
  1293. }
  1294. /**
  1295. * Fires a {@link SelectionEvent} to all the {@link SelectionListener
  1296. * SelectionListeners} currently added to the Grid in which this
  1297. * SelectionModel is.
  1298. * <p>
  1299. * Note that this is only a helper method, and routes the call all the
  1300. * way to Grid. A {@link SelectionModel} is not a
  1301. * {@link SelectionNotifier}
  1302. *
  1303. * @param oldSelection
  1304. * the complete {@link Collection} of the itemIds that were
  1305. * selected <em>before</em> this event happened
  1306. * @param newSelection
  1307. * the complete {@link Collection} of the itemIds that are
  1308. * selected <em>after</em> this event happened
  1309. */
  1310. protected void fireSelectionEvent(final Collection<Object> oldSelection,
  1311. final Collection<Object> newSelection) {
  1312. getParentGrid().fireSelectionEvent(oldSelection, newSelection);
  1313. }
  1314. @Override
  1315. public void generateData(Object itemId, Item item, JsonObject rowData) {
  1316. if (isSelected(itemId)) {
  1317. rowData.put(GridState.JSONKEY_SELECTED, true);
  1318. }
  1319. }
  1320. @Override
  1321. public void destroyData(Object itemId) {
  1322. // NO-OP
  1323. }
  1324. @Override
  1325. protected Object getItemId(String rowKey) {
  1326. return rowKey != null ? super.getItemId(rowKey) : null;
  1327. }
  1328. }
  1329. /**
  1330. * A default implementation of a {@link SelectionModel.Single}
  1331. */
  1332. public static class SingleSelectionModel extends AbstractSelectionModel
  1333. implements SelectionModel.Single, HasUserSelectionAllowed {
  1334. @Override
  1335. protected void extend(AbstractClientConnector target) {
  1336. super.extend(target);
  1337. registerRpc(new SingleSelectionModelServerRpc() {
  1338. @Override
  1339. public void select(String rowKey) {
  1340. if (!isUserSelectionAllowed()) {
  1341. throw new IllegalStateException(
  1342. "Client tried to select '" + rowKey
  1343. + "' although user selection is disallowed");
  1344. }
  1345. SingleSelectionModel.this.select(getItemId(rowKey), false);
  1346. }
  1347. });
  1348. }
  1349. @Override
  1350. public boolean select(final Object itemId) {
  1351. return select(itemId, true);
  1352. }
  1353. protected boolean select(final Object itemId, boolean refresh) {
  1354. if (itemId == null) {
  1355. return deselect(getSelectedRow());
  1356. }
  1357. checkItemIdExists(itemId);
  1358. final Object selectedRow = getSelectedRow();
  1359. final boolean modified = selection.add(itemId);
  1360. if (modified) {
  1361. final Collection<Object> deselected;
  1362. if (selectedRow != null) {
  1363. deselectInternal(selectedRow, false, true);
  1364. deselected = Collections.singleton(selectedRow);
  1365. } else {
  1366. deselected = Collections.emptySet();
  1367. }
  1368. fireSelectionEvent(deselected, selection);
  1369. }
  1370. if (refresh) {
  1371. refreshRow(itemId);
  1372. }
  1373. return modified;
  1374. }
  1375. private boolean deselect(final Object itemId) {
  1376. return deselectInternal(itemId, true, true);
  1377. }
  1378. private boolean deselectInternal(final Object itemId,
  1379. boolean fireEventIfNeeded, boolean refresh) {
  1380. final boolean modified = selection.remove(itemId);
  1381. if (modified) {
  1382. if (refresh) {
  1383. refreshRow(itemId);
  1384. }
  1385. if (fireEventIfNeeded) {
  1386. fireSelectionEvent(Collections.singleton(itemId),
  1387. Collections.emptySet());
  1388. }
  1389. }
  1390. return modified;
  1391. }
  1392. @Override
  1393. public Object getSelectedRow() {
  1394. if (selection.isEmpty()) {
  1395. return null;
  1396. } else {
  1397. return selection.iterator().next();
  1398. }
  1399. }
  1400. /**
  1401. * Resets the selection state.
  1402. * <p>
  1403. * If an item is selected, it will become deselected.
  1404. */
  1405. @Override
  1406. public void reset() {
  1407. deselect(getSelectedRow());
  1408. }
  1409. @Override
  1410. public void setDeselectAllowed(boolean deselectAllowed) {
  1411. getState().deselectAllowed = deselectAllowed;
  1412. }
  1413. @Override
  1414. public boolean isDeselectAllowed() {
  1415. return getState().deselectAllowed;
  1416. }
  1417. @Override
  1418. protected SingleSelectionModelState getState() {
  1419. return (SingleSelectionModelState) super.getState();
  1420. }
  1421. @Override
  1422. protected SingleSelectionModelState getState(boolean markAsDirty) {
  1423. return (SingleSelectionModelState) super.getState(markAsDirty);
  1424. }
  1425. @Override
  1426. public boolean isUserSelectionAllowed() {
  1427. return getState(false).userSelectionAllowed;
  1428. }
  1429. @Override
  1430. public void setUserSelectionAllowed(boolean userSelectionAllowed) {
  1431. getState().userSelectionAllowed = userSelectionAllowed;
  1432. }
  1433. }
  1434. /**
  1435. * A default implementation for a {@link SelectionModel.None}
  1436. */
  1437. public static class NoSelectionModel extends AbstractSelectionModel
  1438. implements SelectionModel.None {
  1439. @Override
  1440. public boolean isSelected(final Object itemId) {
  1441. return false;
  1442. }
  1443. @Override
  1444. public Collection<Object> getSelectedRows() {
  1445. return Collections.emptyList();
  1446. }
  1447. /**
  1448. * Semantically resets the selection model.
  1449. * <p>
  1450. * Effectively a no-op.
  1451. */
  1452. @Override
  1453. public void reset() {
  1454. // NOOP
  1455. }
  1456. }
  1457. /**
  1458. * A default implementation of a {@link SelectionModel.Multi}
  1459. */
  1460. public static class MultiSelectionModel extends AbstractSelectionModel
  1461. implements SelectionModel.Multi,
  1462. SelectionModel.HasUserSelectionAllowed {
  1463. /**
  1464. * The default selection size limit.
  1465. *
  1466. * @see #setSelectionLimit(int)
  1467. */
  1468. public static final int DEFAULT_MAX_SELECTIONS = 1000;
  1469. private int selectionLimit = DEFAULT_MAX_SELECTIONS;
  1470. @Override
  1471. protected void extend(AbstractClientConnector target) {
  1472. super.extend(target);
  1473. registerRpc(new MultiSelectionModelServerRpc() {
  1474. @Override
  1475. public void select(List<String> rowKeys) {
  1476. if (!isUserSelectionAllowed()) {
  1477. throw new IllegalStateException(
  1478. "Client tried to select '" + rowKeys
  1479. + "' although user selection is disallowed");
  1480. }
  1481. List<Object> items = new ArrayList<Object>();
  1482. for (String rowKey : rowKeys) {
  1483. items.add(getItemId(rowKey));
  1484. }
  1485. MultiSelectionModel.this.select(items, false);
  1486. }
  1487. @Override
  1488. public void deselect(List<String> rowKeys) {
  1489. if (!isUserSelectionAllowed()) {
  1490. throw new IllegalStateException(
  1491. "Client tried to deselect '" + rowKeys
  1492. + "' although user selection is disallowed");
  1493. }
  1494. List<Object> items = new ArrayList<Object>();
  1495. for (String rowKey : rowKeys) {
  1496. items.add(getItemId(rowKey));
  1497. }
  1498. MultiSelectionModel.this.deselect(items, false);
  1499. }
  1500. @Override
  1501. public void selectAll() {
  1502. if (!isUserSelectionAllowed()) {
  1503. throw new IllegalStateException(
  1504. "Client tried to select all although user selection is disallowed");
  1505. }
  1506. MultiSelectionModel.this.selectAll(false);
  1507. }
  1508. @Override
  1509. public void deselectAll() {
  1510. if (!isUserSelectionAllowed()) {
  1511. throw new IllegalStateException(
  1512. "Client tried to deselect all although user selection is disallowed");
  1513. }
  1514. MultiSelectionModel.this.deselectAll(false);
  1515. }
  1516. });
  1517. }
  1518. @Override
  1519. public boolean select(final Object... itemIds)
  1520. throws IllegalArgumentException {
  1521. if (itemIds != null) {
  1522. // select will fire the event
  1523. return select(Arrays.asList(itemIds));
  1524. } else {
  1525. throw new IllegalArgumentException(
  1526. "Vararg array of itemIds may not be null");
  1527. }
  1528. }
  1529. /**
  1530. * {@inheritDoc}
  1531. * <p>
  1532. * All items might not be selected if the limit set using
  1533. * {@link #setSelectionLimit(int)} is exceeded.
  1534. */
  1535. @Override
  1536. public boolean select(final Collection<?> itemIds)
  1537. throws IllegalArgumentException {
  1538. return select(itemIds, true);
  1539. }
  1540. protected boolean select(final Collection<?> itemIds, boolean refresh) {
  1541. if (itemIds == null) {
  1542. throw new IllegalArgumentException("itemIds may not be null");
  1543. }
  1544. // Sanity check
  1545. checkItemIdsExist(itemIds);
  1546. final boolean selectionWillChange = !selection.containsAll(itemIds)
  1547. && selection.size() < selectionLimit;
  1548. if (selectionWillChange) {
  1549. final HashSet<Object> oldSelection = new HashSet<Object>(
  1550. selection);
  1551. if (selection.size() + itemIds.size() >= selectionLimit) {
  1552. // Add one at a time if there's a risk of overflow
  1553. Iterator<?> iterator = itemIds.iterator();
  1554. while (iterator.hasNext()
  1555. && selection.size() < selectionLimit) {
  1556. selection.add(iterator.next());
  1557. }
  1558. } else {
  1559. selection.addAll(itemIds);
  1560. }
  1561. fireSelectionEvent(oldSelection, selection);
  1562. }
  1563. updateAllSelectedState();
  1564. if (refresh) {
  1565. for (Object itemId : itemIds) {
  1566. refreshRow(itemId);
  1567. }
  1568. }
  1569. return selectionWillChange;
  1570. }
  1571. /**
  1572. * Sets the maximum number of rows that can be selected at once. This is
  1573. * a mechanism to prevent exhausting server memory in situations where
  1574. * users select lots of rows. If the limit is reached, newly selected
  1575. * rows will not become recorded.
  1576. * <p>
  1577. * Old selections are not discarded if the current number of selected
  1578. * row exceeds the new limit.
  1579. * <p>
  1580. * The default limit is {@value #DEFAULT_MAX_SELECTIONS} rows.
  1581. *
  1582. * @param selectionLimit
  1583. * the non-negative selection limit to set
  1584. * @throws IllegalArgumentException
  1585. * if the limit is negative
  1586. */
  1587. public void setSelectionLimit(int selectionLimit) {
  1588. if (selectionLimit < 0) {
  1589. throw new IllegalArgumentException(
  1590. "The selection limit must be non-negative");
  1591. }
  1592. this.selectionLimit = selectionLimit;
  1593. }
  1594. /**
  1595. * Gets the selection limit.
  1596. *
  1597. * @see #setSelectionLimit(int)
  1598. *
  1599. * @return the selection limit
  1600. */
  1601. public int getSelectionLimit() {
  1602. return selectionLimit;
  1603. }
  1604. @Override
  1605. public boolean deselect(final Object... itemIds)
  1606. throws IllegalArgumentException {
  1607. if (itemIds != null) {
  1608. // deselect will fire the event
  1609. return deselect(Arrays.asList(itemIds));
  1610. } else {
  1611. throw new IllegalArgumentException(
  1612. "Vararg array of itemIds may not be null");
  1613. }
  1614. }
  1615. @Override
  1616. public boolean deselect(final Collection<?> itemIds)
  1617. throws IllegalArgumentException {
  1618. return deselect(itemIds, true);
  1619. }
  1620. protected boolean deselect(final Collection<?> itemIds,
  1621. boolean refresh) {
  1622. if (itemIds == null) {
  1623. throw new IllegalArgumentException("itemIds may not be null");
  1624. }
  1625. final boolean hasCommonElements = !Collections.disjoint(itemIds,
  1626. selection);
  1627. if (hasCommonElements) {
  1628. final HashSet<Object> oldSelection = new HashSet<Object>(
  1629. selection);
  1630. selection.removeAll(itemIds);
  1631. fireSelectionEvent(oldSelection, selection);
  1632. }
  1633. updateAllSelectedState();
  1634. if (refresh) {
  1635. for (Object itemId : itemIds) {
  1636. refreshRow(itemId);
  1637. }
  1638. }
  1639. return hasCommonElements;
  1640. }
  1641. @Override
  1642. public boolean selectAll() {
  1643. return selectAll(true);
  1644. }
  1645. protected boolean selectAll(boolean refresh) {
  1646. // select will fire the event
  1647. final Indexed container = getParentGrid().getContainerDataSource();
  1648. if (container != null) {
  1649. return select(container.getItemIds(), refresh);
  1650. } else if (selection.isEmpty()) {
  1651. return false;
  1652. } else {
  1653. /*
  1654. * this should never happen (no container but has a selection),
  1655. * but I guess the only theoretically correct course of
  1656. * action...
  1657. */
  1658. return deselectAll(false);
  1659. }
  1660. }
  1661. @Override
  1662. public boolean deselectAll() {
  1663. return deselectAll(true);
  1664. }
  1665. protected boolean deselectAll(boolean refresh) {
  1666. // deselect will fire the event
  1667. return deselect(getSelectedRows(), refresh);
  1668. }
  1669. /**
  1670. * {@inheritDoc}
  1671. * <p>
  1672. * The returned Collection is in <strong>order of selection</strong>
  1673. * &ndash; the item that was first selected will be first in the
  1674. * collection, and so on. Should an item have been selected twice
  1675. * without being deselected in between, it will have remained in its
  1676. * original position.
  1677. */
  1678. @Override
  1679. public Collection<Object> getSelectedRows() {
  1680. // overridden only for JavaDoc
  1681. return super.getSelectedRows();
  1682. }
  1683. /**
  1684. * Resets the selection model.
  1685. * <p>
  1686. * Equivalent to calling {@link #deselectAll()}
  1687. */
  1688. @Override
  1689. public void reset() {
  1690. deselectAll();
  1691. }
  1692. @Override
  1693. public boolean setSelected(Collection<?> itemIds)
  1694. throws IllegalArgumentException {
  1695. if (itemIds == null) {
  1696. throw new IllegalArgumentException("itemIds may not be null");
  1697. }
  1698. checkItemIdsExist(itemIds);
  1699. boolean changed = false;
  1700. Set<Object> selectedRows = new HashSet<Object>(itemIds);
  1701. final Collection<Object> oldSelection = getSelectedRows();
  1702. Set<Object> added = getDifference(selectedRows, selection);
  1703. if (!added.isEmpty()) {
  1704. changed = true;
  1705. selection.addAll(added);
  1706. for (Object id : added) {
  1707. refreshRow(id);
  1708. }
  1709. }
  1710. Set<Object> removed = getDifference(selection, selectedRows);
  1711. if (!removed.isEmpty()) {
  1712. changed = true;
  1713. selection.removeAll(removed);
  1714. for (Object id : removed) {
  1715. refreshRow(id);
  1716. }
  1717. }
  1718. if (changed) {
  1719. fireSelectionEvent(oldSelection, selection);
  1720. }
  1721. updateAllSelectedState();
  1722. return changed;
  1723. }
  1724. /**
  1725. * Compares two sets and returns a set containing all values that are
  1726. * present in the first, but not in the second.
  1727. *
  1728. * @param set1
  1729. * first item set
  1730. * @param set2
  1731. * second item set
  1732. * @return all values from set1 which are not present in set2
  1733. */
  1734. private static Set<Object> getDifference(Set<Object> set1,
  1735. Set<Object> set2) {
  1736. Set<Object> diff = new HashSet<Object>(set1);
  1737. diff.removeAll(set2);
  1738. return diff;
  1739. }
  1740. @Override
  1741. public boolean setSelected(Object... itemIds)
  1742. throws IllegalArgumentException {
  1743. if (itemIds != null) {
  1744. return setSelected(Arrays.asList(itemIds));
  1745. } else {
  1746. throw new IllegalArgumentException(
  1747. "Vararg array of itemIds may not be null");
  1748. }
  1749. }
  1750. private void updateAllSelectedState() {
  1751. int totalRowCount = getParentGrid().datasource.size();
  1752. int rows = Math.min(totalRowCount, selectionLimit);
  1753. if (totalRowCount == 0) {
  1754. getState().allSelected = false;
  1755. } else {
  1756. getState().allSelected = selection.size() >= rows;
  1757. }
  1758. }
  1759. @Override
  1760. protected MultiSelectionModelState getState() {
  1761. return (MultiSelectionModelState) super.getState();
  1762. }
  1763. @Override
  1764. protected MultiSelectionModelState getState(boolean markAsDirty) {
  1765. return (MultiSelectionModelState) super.getState(markAsDirty);
  1766. }
  1767. @Override
  1768. public boolean isUserSelectionAllowed() {
  1769. return getState(false).userSelectionAllowed;
  1770. }
  1771. @Override
  1772. public void setUserSelectionAllowed(boolean userSelectionAllowed) {
  1773. getState().userSelectionAllowed = userSelectionAllowed;
  1774. }
  1775. }
  1776. /**
  1777. * A data class which contains information which identifies a row in a
  1778. * {@link Grid}.
  1779. * <p>
  1780. * Since this class follows the <code>Flyweight</code>-pattern any instance
  1781. * of this object is subject to change without the user knowing it and so
  1782. * should not be stored anywhere outside of the method providing these
  1783. * instances.
  1784. */
  1785. public static class RowReference implements Serializable {
  1786. private final Grid grid;
  1787. private Object itemId;
  1788. /**
  1789. * Creates a new row reference for the given grid.
  1790. *
  1791. * @param grid
  1792. * the grid that the row belongs to
  1793. */
  1794. public RowReference(Grid grid) {
  1795. this.grid = grid;
  1796. }
  1797. /**
  1798. * Sets the identifying information for this row
  1799. *
  1800. * @param itemId
  1801. * the item id of the row
  1802. */
  1803. public void set(Object itemId) {
  1804. this.itemId = itemId;
  1805. }
  1806. /**
  1807. * Gets the grid that contains the referenced row.
  1808. *
  1809. * @return the grid that contains referenced row
  1810. */
  1811. public Grid getGrid() {
  1812. return grid;
  1813. }
  1814. /**
  1815. * Gets the item id of the row.
  1816. *
  1817. * @return the item id of the row
  1818. */
  1819. public Object getItemId() {
  1820. return itemId;
  1821. }
  1822. /**
  1823. * Gets the item for the row.
  1824. *
  1825. * @return the item for the row
  1826. */
  1827. public Item getItem() {
  1828. return grid.getContainerDataSource().getItem(itemId);
  1829. }
  1830. }
  1831. /**
  1832. * A data class which contains information which identifies a cell in a
  1833. * {@link Grid}.
  1834. * <p>
  1835. * Since this class follows the <code>Flyweight</code>-pattern any instance
  1836. * of this object is subject to change without the user knowing it and so
  1837. * should not be stored anywhere outside of the method providing these
  1838. * instances.
  1839. */
  1840. public static class CellReference implements Serializable {
  1841. private final RowReference rowReference;
  1842. private Object propertyId;
  1843. public CellReference(RowReference rowReference) {
  1844. this.rowReference = rowReference;
  1845. }
  1846. /**
  1847. * Sets the identifying information for this cell
  1848. *
  1849. * @param propertyId
  1850. * the property id of the column
  1851. */
  1852. public void set(Object propertyId) {
  1853. this.propertyId = propertyId;
  1854. }
  1855. /**
  1856. * Gets the grid that contains the referenced cell.
  1857. *
  1858. * @return the grid that contains referenced cell
  1859. */
  1860. public Grid getGrid() {
  1861. return rowReference.getGrid();
  1862. }
  1863. /**
  1864. * @return the property id of the column
  1865. */
  1866. public Object getPropertyId() {
  1867. return propertyId;
  1868. }
  1869. /**
  1870. * @return the property for the cell
  1871. */
  1872. public Property<?> getProperty() {
  1873. return getItem().getItemProperty(propertyId);
  1874. }
  1875. /**
  1876. * Gets the item id of the row of the cell.
  1877. *
  1878. * @return the item id of the row
  1879. */
  1880. public Object getItemId() {
  1881. return rowReference.getItemId();
  1882. }
  1883. /**
  1884. * Gets the item for the row of the cell.
  1885. *
  1886. * @return the item for the row
  1887. */
  1888. public Item getItem() {
  1889. return rowReference.getItem();
  1890. }
  1891. /**
  1892. * Gets the value of the cell.
  1893. *
  1894. * @return the value of the cell
  1895. */
  1896. public Object getValue() {
  1897. return getProperty().getValue();
  1898. }
  1899. }
  1900. /**
  1901. * A callback interface for generating custom style names for Grid rows.
  1902. *
  1903. * @see Grid#setRowStyleGenerator(RowStyleGenerator)
  1904. */
  1905. public interface RowStyleGenerator extends Serializable {
  1906. /**
  1907. * Called by Grid to generate a style name for a row.
  1908. *
  1909. * @param row
  1910. * the row to generate a style for
  1911. * @return the style name to add to this row, or {@code null} to not set
  1912. * any style
  1913. */
  1914. public String getStyle(RowReference row);
  1915. }
  1916. /**
  1917. * A callback interface for generating custom style names for Grid cells.
  1918. *
  1919. * @see Grid#setCellStyleGenerator(CellStyleGenerator)
  1920. */
  1921. public interface CellStyleGenerator extends Serializable {
  1922. /**
  1923. * Called by Grid to generate a style name for a column.
  1924. *
  1925. * @param cell
  1926. * the cell to generate a style for
  1927. * @return the style name to add to this cell, or {@code null} to not
  1928. * set any style
  1929. */
  1930. public String getStyle(CellReference cell);
  1931. }
  1932. /**
  1933. * A callback interface for generating optional descriptions (tooltips) for
  1934. * Grid rows. If a description is generated for a row, it is used for all
  1935. * the cells in the row for which a {@link CellDescriptionGenerator cell
  1936. * description} is not generated.
  1937. *
  1938. * @see Grid#setRowDescriptionGenerator
  1939. *
  1940. * @since 7.6
  1941. */
  1942. public interface RowDescriptionGenerator extends Serializable {
  1943. /**
  1944. * Called by Grid to generate a description (tooltip) for a row. The
  1945. * description may contain HTML which is rendered directly; if this is
  1946. * not desired the returned string must be escaped by the implementing
  1947. * method.
  1948. *
  1949. * @param row
  1950. * the row to generate a description for
  1951. * @return the row description or {@code null} for no description
  1952. */
  1953. public String getDescription(RowReference row);
  1954. }
  1955. /**
  1956. * A callback interface for generating optional descriptions (tooltips) for
  1957. * Grid cells. If a cell has both a {@link RowDescriptionGenerator row
  1958. * description} and a cell description, the latter has precedence.
  1959. *
  1960. * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator)
  1961. *
  1962. * @since 7.6
  1963. */
  1964. public interface CellDescriptionGenerator extends Serializable {
  1965. /**
  1966. * Called by Grid to generate a description (tooltip) for a cell. The
  1967. * description may contain HTML which is rendered directly; if this is
  1968. * not desired the returned string must be escaped by the implementing
  1969. * method.
  1970. *
  1971. * @param cell
  1972. * the cell to generate a description for
  1973. * @return the cell description or {@code null} for no description
  1974. */
  1975. public String getDescription(CellReference cell);
  1976. }
  1977. /**
  1978. * Class for generating all row and cell related data for the essential
  1979. * parts of Grid.
  1980. */
  1981. private class RowDataGenerator implements DataGenerator {
  1982. private void put(String key, String value, JsonObject object) {
  1983. if (value != null && !value.isEmpty()) {
  1984. object.put(key, value);
  1985. }
  1986. }
  1987. @Override
  1988. public void generateData(Object itemId, Item item, JsonObject rowData) {
  1989. RowReference row = new RowReference(Grid.this);
  1990. row.set(itemId);
  1991. if (rowStyleGenerator != null) {
  1992. String style = rowStyleGenerator.getStyle(row);
  1993. put(GridState.JSONKEY_ROWSTYLE, style, rowData);
  1994. }
  1995. if (rowDescriptionGenerator != null) {
  1996. String description = rowDescriptionGenerator
  1997. .getDescription(row);
  1998. put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData);
  1999. }
  2000. JsonObject cellStyles = Json.createObject();
  2001. JsonObject cellData = Json.createObject();
  2002. JsonObject cellDescriptions = Json.createObject();
  2003. CellReference cell = new CellReference(row);
  2004. for (Column column : getColumns()) {
  2005. cell.set(column.getPropertyId());
  2006. writeData(cell, cellData);
  2007. writeStyles(cell, cellStyles);
  2008. writeDescriptions(cell, cellDescriptions);
  2009. }
  2010. if (cellDescriptionGenerator != null
  2011. && cellDescriptions.keys().length > 0) {
  2012. rowData.put(GridState.JSONKEY_CELLDESCRIPTION,
  2013. cellDescriptions);
  2014. }
  2015. if (cellStyleGenerator != null && cellStyles.keys().length > 0) {
  2016. rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles);
  2017. }
  2018. rowData.put(GridState.JSONKEY_DATA, cellData);
  2019. }
  2020. private void writeStyles(CellReference cell, JsonObject styles) {
  2021. if (cellStyleGenerator != null) {
  2022. String style = cellStyleGenerator.getStyle(cell);
  2023. put(columnKeys.key(cell.getPropertyId()), style, styles);
  2024. }
  2025. }
  2026. private void writeDescriptions(CellReference cell,
  2027. JsonObject descriptions) {
  2028. if (cellDescriptionGenerator != null) {
  2029. String description = cellDescriptionGenerator
  2030. .getDescription(cell);
  2031. put(columnKeys.key(cell.getPropertyId()), description,
  2032. descriptions);
  2033. }
  2034. }
  2035. private void writeData(CellReference cell, JsonObject data) {
  2036. Column column = getColumn(cell.getPropertyId());
  2037. Converter<?, ?> converter = column.getConverter();
  2038. Renderer<?> renderer = column.getRenderer();
  2039. Item item = cell.getItem();
  2040. Property itemProperty = item.getItemProperty(cell.getPropertyId());
  2041. Object modelValue = itemProperty == null ? null
  2042. : itemProperty.getValue();
  2043. data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer
  2044. .encodeValue(modelValue, renderer, converter, getLocale()));
  2045. }
  2046. @Override
  2047. public void destroyData(Object itemId) {
  2048. // NO-OP
  2049. }
  2050. }
  2051. /**
  2052. * Abstract base class for Grid header and footer sections.
  2053. *
  2054. * @since 7.6
  2055. * @param <ROWTYPE>
  2056. * the type of the rows in the section
  2057. */
  2058. public abstract static class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>>
  2059. implements Serializable {
  2060. /**
  2061. * Abstract base class for Grid header and footer rows.
  2062. *
  2063. * @param <CELLTYPE>
  2064. * the type of the cells in the row
  2065. */
  2066. public abstract static class StaticRow<CELLTYPE extends StaticCell>
  2067. implements Serializable {
  2068. private RowState rowState = new RowState();
  2069. protected StaticSection<?> section;
  2070. private Map<Object, CELLTYPE> cells = new LinkedHashMap<Object, CELLTYPE>();
  2071. private Map<Set<CELLTYPE>, CELLTYPE> cellGroups = new HashMap<Set<CELLTYPE>, CELLTYPE>();
  2072. protected StaticRow(StaticSection<?> section) {
  2073. this.section = section;
  2074. }
  2075. protected void addCell(Object propertyId) {
  2076. CELLTYPE cell = createCell();
  2077. cell.setColumnId(
  2078. section.grid.getColumn(propertyId).getState().id);
  2079. cells.put(propertyId, cell);
  2080. rowState.cells.add(cell.getCellState());
  2081. }
  2082. protected void removeCell(Object propertyId) {
  2083. CELLTYPE cell = cells.remove(propertyId);
  2084. if (cell != null) {
  2085. Set<CELLTYPE> cellGroupForCell = getCellGroupForCell(cell);
  2086. if (cellGroupForCell != null) {
  2087. removeCellFromGroup(cell, cellGroupForCell);
  2088. } else {
  2089. cell.detach();
  2090. }
  2091. rowState.cells.remove(cell.getCellState());
  2092. }
  2093. }
  2094. private void removeCellFromGroup(CELLTYPE cell,
  2095. Set<CELLTYPE> cellGroup) {
  2096. String columnId = cell.getColumnId();
  2097. for (Set<String> group : rowState.cellGroups.keySet()) {
  2098. if (group.contains(columnId)) {
  2099. if (group.size() > 2) {
  2100. // Update map key correctly
  2101. CELLTYPE mergedCell = cellGroups.remove(cellGroup);
  2102. cellGroup.remove(cell);
  2103. cellGroups.put(cellGroup, mergedCell);
  2104. group.remove(columnId);
  2105. } else {
  2106. // Only one cell remaining in the group, disband it
  2107. // The contents of the group if removed
  2108. rowState.cellGroups.remove(group);
  2109. CELLTYPE mergedCell = cellGroups.remove(cellGroup);
  2110. mergedCell.detach();
  2111. }
  2112. return;
  2113. }
  2114. }
  2115. }
  2116. /**
  2117. * Creates and returns a new instance of the cell type.
  2118. *
  2119. * @return the created cell
  2120. */
  2121. protected abstract CELLTYPE createCell();
  2122. protected RowState getRowState() {
  2123. return rowState;
  2124. }
  2125. /**
  2126. * Returns the cell for the given property id on this row. If the
  2127. * column is merged returned cell is the cell for the whole group.
  2128. *
  2129. * @param propertyId
  2130. * the property id of the column
  2131. * @return the cell for the given property, merged cell for merged
  2132. * properties, null if not found
  2133. */
  2134. public CELLTYPE getCell(Object propertyId) {
  2135. CELLTYPE cell = cells.get(propertyId);
  2136. Set<CELLTYPE> cellGroup = getCellGroupForCell(cell);
  2137. if (cellGroup != null) {
  2138. cell = cellGroups.get(cellGroup);
  2139. }
  2140. return cell;
  2141. }
  2142. /**
  2143. * Merges columns cells in a row.
  2144. *
  2145. * @param propertyIds
  2146. * The property ids of columns to merge
  2147. * @return The remaining visible cell after the merge
  2148. */
  2149. public CELLTYPE join(Object... propertyIds) {
  2150. if (propertyIds.length < 2) {
  2151. throw new IllegalArgumentException(
  2152. "You need to merge at least 2 properties");
  2153. }
  2154. Set<CELLTYPE> cells = new HashSet<CELLTYPE>();
  2155. for (int i = 0; i < propertyIds.length; ++i) {
  2156. cells.add(getCell(propertyIds[i]));
  2157. }
  2158. return join(cells);
  2159. }
  2160. /**
  2161. * Merges columns cells in a row.
  2162. *
  2163. * @param cells
  2164. * The cells to merge. Must be from the same row.
  2165. * @return The remaining visible cell after the merge
  2166. */
  2167. public CELLTYPE join(CELLTYPE... cells) {
  2168. if (cells.length < 2) {
  2169. throw new IllegalArgumentException(
  2170. "You need to merge at least 2 cells");
  2171. }
  2172. return join(new HashSet<CELLTYPE>(Arrays.asList(cells)));
  2173. }
  2174. protected CELLTYPE join(Set<CELLTYPE> cells) {
  2175. for (CELLTYPE cell : cells) {
  2176. if (getCellGroupForCell(cell) != null) {
  2177. throw new IllegalArgumentException(
  2178. "Cell already merged");
  2179. } else if (!this.cells.containsValue(cell)) {
  2180. throw new IllegalArgumentException(
  2181. "Cell does not exist on this row");
  2182. }
  2183. }
  2184. // Create new cell data for the group
  2185. CELLTYPE newCell = createCell();
  2186. Set<String> columnGroup = new HashSet<String>();
  2187. for (CELLTYPE cell : cells) {
  2188. columnGroup.add(cell.getColumnId());
  2189. }
  2190. rowState.cellGroups.put(columnGroup, newCell.getCellState());
  2191. cellGroups.put(cells, newCell);
  2192. return newCell;
  2193. }
  2194. private Set<CELLTYPE> getCellGroupForCell(CELLTYPE cell) {
  2195. for (Set<CELLTYPE> group : cellGroups.keySet()) {
  2196. if (group.contains(cell)) {
  2197. return group;
  2198. }
  2199. }
  2200. return null;
  2201. }
  2202. /**
  2203. * Returns the custom style name for this row.
  2204. *
  2205. * @return the style name or null if no style name has been set
  2206. */
  2207. public String getStyleName() {
  2208. return getRowState().styleName;
  2209. }
  2210. /**
  2211. * Sets a custom style name for this row.
  2212. *
  2213. * @param styleName
  2214. * the style name to set or null to not use any style
  2215. * name
  2216. */
  2217. public void setStyleName(String styleName) {
  2218. getRowState().styleName = styleName;
  2219. }
  2220. /**
  2221. * Writes the declarative design to the given table row element.
  2222. *
  2223. * @since 7.5.0
  2224. * @param trElement
  2225. * Element to write design to
  2226. * @param designContext
  2227. * the design context
  2228. */
  2229. protected void writeDesign(Element trElement,
  2230. DesignContext designContext) {
  2231. Set<CELLTYPE> visited = new HashSet<CELLTYPE>();
  2232. for (Grid.Column column : section.grid.getColumns()) {
  2233. CELLTYPE cell = getCell(column.getPropertyId());
  2234. if (visited.contains(cell)) {
  2235. continue;
  2236. }
  2237. visited.add(cell);
  2238. Element cellElement = trElement
  2239. .appendElement(getCellTagName());
  2240. cell.writeDesign(cellElement, designContext);
  2241. for (Entry<Set<CELLTYPE>, CELLTYPE> entry : cellGroups
  2242. .entrySet()) {
  2243. if (entry.getValue() == cell) {
  2244. cellElement.attr("colspan",
  2245. "" + entry.getKey().size());
  2246. break;
  2247. }
  2248. }
  2249. }
  2250. }
  2251. /**
  2252. * Reads the declarative design from the given table row element.
  2253. *
  2254. * @since 7.5.0
  2255. * @param trElement
  2256. * Element to read design from
  2257. * @param designContext
  2258. * the design context
  2259. * @throws DesignException
  2260. * if the given table row contains unexpected children
  2261. */
  2262. protected void readDesign(Element trElement,
  2263. DesignContext designContext) throws DesignException {
  2264. Elements cellElements = trElement.children();
  2265. int totalColSpans = 0;
  2266. for (int i = 0; i < cellElements.size(); ++i) {
  2267. Element element = cellElements.get(i);
  2268. if (!element.tagName().equals(getCellTagName())) {
  2269. throw new DesignException(
  2270. "Unexpected element in tr while expecting "
  2271. + getCellTagName() + ": "
  2272. + element.tagName());
  2273. }
  2274. int columnIndex = i + totalColSpans;
  2275. int colspan = DesignAttributeHandler.readAttribute(
  2276. "colspan", element.attributes(), 1, int.class);
  2277. Set<CELLTYPE> cells = new HashSet<CELLTYPE>();
  2278. for (int c = 0; c < colspan; ++c) {
  2279. cells.add(getCell(section.grid.getColumns()
  2280. .get(columnIndex + c).getPropertyId()));
  2281. }
  2282. if (colspan > 1) {
  2283. totalColSpans += colspan - 1;
  2284. join(cells).readDesign(element, designContext);
  2285. } else {
  2286. cells.iterator().next().readDesign(element,
  2287. designContext);
  2288. }
  2289. }
  2290. }
  2291. abstract protected String getCellTagName();
  2292. void detach() {
  2293. for (CELLTYPE cell : cells.values()) {
  2294. cell.detach();
  2295. }
  2296. for (CELLTYPE cell : cellGroups.values()) {
  2297. cell.detach();
  2298. }
  2299. }
  2300. }
  2301. /**
  2302. * A header or footer cell. Has a simple textual caption.
  2303. */
  2304. abstract static class StaticCell implements Serializable {
  2305. private CellState cellState = new CellState();
  2306. private StaticRow<?> row;
  2307. protected StaticCell(StaticRow<?> row) {
  2308. this.row = row;
  2309. }
  2310. void setColumnId(String id) {
  2311. cellState.columnId = id;
  2312. }
  2313. String getColumnId() {
  2314. return cellState.columnId;
  2315. }
  2316. /**
  2317. * Gets the row where this cell is.
  2318. *
  2319. * @return row for this cell
  2320. */
  2321. public StaticRow<?> getRow() {
  2322. return row;
  2323. }
  2324. protected CellState getCellState() {
  2325. return cellState;
  2326. }
  2327. /**
  2328. * Sets the text displayed in this cell.
  2329. *
  2330. * @param text
  2331. * a plain text caption
  2332. */
  2333. public void setText(String text) {
  2334. removeComponentIfPresent();
  2335. cellState.text = text;
  2336. cellState.type = GridStaticCellType.TEXT;
  2337. row.section.markAsDirty();
  2338. }
  2339. /**
  2340. * Returns the text displayed in this cell.
  2341. *
  2342. * @return the plain text caption
  2343. */
  2344. public String getText() {
  2345. if (cellState.type != GridStaticCellType.TEXT) {
  2346. throw new IllegalStateException(
  2347. "Cannot fetch Text from a cell with type "
  2348. + cellState.type);
  2349. }
  2350. return cellState.text;
  2351. }
  2352. /**
  2353. * Returns the HTML content displayed in this cell.
  2354. *
  2355. * @return the html
  2356. *
  2357. */
  2358. public String getHtml() {
  2359. if (cellState.type != GridStaticCellType.HTML) {
  2360. throw new IllegalStateException(
  2361. "Cannot fetch HTML from a cell with type "
  2362. + cellState.type);
  2363. }
  2364. return cellState.html;
  2365. }
  2366. /**
  2367. * Sets the HTML content displayed in this cell.
  2368. *
  2369. * @param html
  2370. * the html to set
  2371. */
  2372. public void setHtml(String html) {
  2373. removeComponentIfPresent();
  2374. cellState.html = html;
  2375. cellState.type = GridStaticCellType.HTML;
  2376. row.section.markAsDirty();
  2377. }
  2378. /**
  2379. * Returns the component displayed in this cell.
  2380. *
  2381. * @return the component
  2382. */
  2383. public Component getComponent() {
  2384. if (cellState.type != GridStaticCellType.WIDGET) {
  2385. throw new IllegalStateException(
  2386. "Cannot fetch Component from a cell with type "
  2387. + cellState.type);
  2388. }
  2389. return (Component) cellState.connector;
  2390. }
  2391. /**
  2392. * Sets the component displayed in this cell.
  2393. *
  2394. * @param component
  2395. * the component to set
  2396. */
  2397. public void setComponent(Component component) {
  2398. removeComponentIfPresent();
  2399. component.setParent(row.section.grid);
  2400. cellState.connector = component;
  2401. cellState.type = GridStaticCellType.WIDGET;
  2402. row.section.markAsDirty();
  2403. }
  2404. /**
  2405. * Returns the type of content stored in this cell.
  2406. *
  2407. * @return cell content type
  2408. */
  2409. public GridStaticCellType getCellType() {
  2410. return cellState.type;
  2411. }
  2412. /**
  2413. * Returns the custom style name for this cell.
  2414. *
  2415. * @return the style name or null if no style name has been set
  2416. */
  2417. public String getStyleName() {
  2418. return cellState.styleName;
  2419. }
  2420. /**
  2421. * Sets a custom style name for this cell.
  2422. *
  2423. * @param styleName
  2424. * the style name to set or null to not use any style
  2425. * name
  2426. */
  2427. public void setStyleName(String styleName) {
  2428. cellState.styleName = styleName;
  2429. row.section.markAsDirty();
  2430. }
  2431. private void removeComponentIfPresent() {
  2432. Component component = (Component) cellState.connector;
  2433. if (component != null) {
  2434. component.setParent(null);
  2435. cellState.connector = null;
  2436. }
  2437. }
  2438. /**
  2439. * Writes the declarative design to the given table cell element.
  2440. *
  2441. * @since 7.5.0
  2442. * @param cellElement
  2443. * Element to write design to
  2444. * @param designContext
  2445. * the design context
  2446. */
  2447. protected void writeDesign(Element cellElement,
  2448. DesignContext designContext) {
  2449. switch (cellState.type) {
  2450. case TEXT:
  2451. cellElement.attr("plain-text", true);
  2452. cellElement.appendText(getText());
  2453. break;
  2454. case HTML:
  2455. cellElement.append(getHtml());
  2456. break;
  2457. case WIDGET:
  2458. cellElement.appendChild(
  2459. designContext.createElement(getComponent()));
  2460. break;
  2461. }
  2462. }
  2463. /**
  2464. * Reads the declarative design from the given table cell element.
  2465. *
  2466. * @since 7.5.0
  2467. * @param cellElement
  2468. * Element to read design from
  2469. * @param designContext
  2470. * the design context
  2471. */
  2472. protected void readDesign(Element cellElement,
  2473. DesignContext designContext) {
  2474. if (!cellElement.hasAttr("plain-text")) {
  2475. if (cellElement.children().size() > 0
  2476. && cellElement.child(0).tagName().contains("-")) {
  2477. setComponent(
  2478. designContext.readDesign(cellElement.child(0)));
  2479. } else {
  2480. setHtml(cellElement.html());
  2481. }
  2482. } else {
  2483. // text – need to unescape HTML entities
  2484. setText(DesignFormatter
  2485. .decodeFromTextNode(cellElement.html()));
  2486. }
  2487. }
  2488. void detach() {
  2489. removeComponentIfPresent();
  2490. }
  2491. }
  2492. protected Grid grid;
  2493. protected List<ROWTYPE> rows = new ArrayList<ROWTYPE>();
  2494. /**
  2495. * Sets the visibility of the whole section.
  2496. *
  2497. * @param visible
  2498. * true to show this section, false to hide
  2499. */
  2500. public void setVisible(boolean visible) {
  2501. if (getSectionState().visible != visible) {
  2502. getSectionState().visible = visible;
  2503. markAsDirty();
  2504. }
  2505. }
  2506. /**
  2507. * Returns the visibility of this section.
  2508. *
  2509. * @return true if visible, false otherwise.
  2510. */
  2511. public boolean isVisible() {
  2512. return getSectionState().visible;
  2513. }
  2514. /**
  2515. * Removes the row at the given position.
  2516. *
  2517. * @param rowIndex
  2518. * the position of the row
  2519. *
  2520. * @throws IllegalArgumentException
  2521. * if no row exists at given index
  2522. * @see #removeRow(StaticRow)
  2523. * @see #addRowAt(int)
  2524. * @see #appendRow()
  2525. * @see #prependRow()
  2526. */
  2527. public ROWTYPE removeRow(int rowIndex) {
  2528. if (rowIndex >= rows.size() || rowIndex < 0) {
  2529. throw new IllegalArgumentException(
  2530. "No row at given index " + rowIndex);
  2531. }
  2532. ROWTYPE row = rows.remove(rowIndex);
  2533. row.detach();
  2534. getSectionState().rows.remove(rowIndex);
  2535. markAsDirty();
  2536. return row;
  2537. }
  2538. /**
  2539. * Removes the given row from the section.
  2540. *
  2541. * @param row
  2542. * the row to be removed
  2543. *
  2544. * @throws IllegalArgumentException
  2545. * if the row does not exist in this section
  2546. * @see #removeRow(int)
  2547. * @see #addRowAt(int)
  2548. * @see #appendRow()
  2549. * @see #prependRow()
  2550. */
  2551. public void removeRow(ROWTYPE row) {
  2552. try {
  2553. removeRow(rows.indexOf(row));
  2554. } catch (IndexOutOfBoundsException e) {
  2555. throw new IllegalArgumentException(
  2556. "Section does not contain the given row");
  2557. }
  2558. }
  2559. /**
  2560. * Gets row at given index.
  2561. *
  2562. * @param rowIndex
  2563. * 0 based index for row. Counted from top to bottom
  2564. * @return row at given index
  2565. */
  2566. public ROWTYPE getRow(int rowIndex) {
  2567. if (rowIndex >= rows.size() || rowIndex < 0) {
  2568. throw new IllegalArgumentException(
  2569. "No row at given index " + rowIndex);
  2570. }
  2571. return rows.get(rowIndex);
  2572. }
  2573. /**
  2574. * Adds a new row at the top of this section.
  2575. *
  2576. * @return the new row
  2577. * @see #appendRow()
  2578. * @see #addRowAt(int)
  2579. * @see #removeRow(StaticRow)
  2580. * @see #removeRow(int)
  2581. */
  2582. public ROWTYPE prependRow() {
  2583. return addRowAt(0);
  2584. }
  2585. /**
  2586. * Adds a new row at the bottom of this section.
  2587. *
  2588. * @return the new row
  2589. * @see #prependRow()
  2590. * @see #addRowAt(int)
  2591. * @see #removeRow(StaticRow)
  2592. * @see #removeRow(int)
  2593. */
  2594. public ROWTYPE appendRow() {
  2595. return addRowAt(rows.size());
  2596. }
  2597. /**
  2598. * Inserts a new row at the given position.
  2599. *
  2600. * @param index
  2601. * the position at which to insert the row
  2602. * @return the new row
  2603. *
  2604. * @throws IndexOutOfBoundsException
  2605. * if the index is out of bounds
  2606. * @see #appendRow()
  2607. * @see #prependRow()
  2608. * @see #removeRow(StaticRow)
  2609. * @see #removeRow(int)
  2610. */
  2611. public ROWTYPE addRowAt(int index) {
  2612. if (index > rows.size() || index < 0) {
  2613. throw new IllegalArgumentException(
  2614. "Unable to add row at index " + index);
  2615. }
  2616. ROWTYPE row = createRow();
  2617. rows.add(index, row);
  2618. getSectionState().rows.add(index, row.getRowState());
  2619. for (Object id : grid.columns.keySet()) {
  2620. row.addCell(id);
  2621. }
  2622. markAsDirty();
  2623. return row;
  2624. }
  2625. /**
  2626. * Gets the amount of rows in this section.
  2627. *
  2628. * @return row count
  2629. */
  2630. public int getRowCount() {
  2631. return rows.size();
  2632. }
  2633. protected abstract GridStaticSectionState getSectionState();
  2634. protected abstract ROWTYPE createRow();
  2635. /**
  2636. * Informs the grid that state has changed and it should be redrawn.
  2637. */
  2638. protected void markAsDirty() {
  2639. grid.markAsDirty();
  2640. }
  2641. /**
  2642. * Removes a column for given property id from the section.
  2643. *
  2644. * @param propertyId
  2645. * property to be removed
  2646. */
  2647. protected void removeColumn(Object propertyId) {
  2648. for (ROWTYPE row : rows) {
  2649. row.removeCell(propertyId);
  2650. }
  2651. }
  2652. /**
  2653. * Adds a column for given property id to the section.
  2654. *
  2655. * @param propertyId
  2656. * property to be added
  2657. */
  2658. protected void addColumn(Object propertyId) {
  2659. for (ROWTYPE row : rows) {
  2660. row.addCell(propertyId);
  2661. }
  2662. }
  2663. /**
  2664. * Performs a sanity check that section is in correct state.
  2665. *
  2666. * @throws IllegalStateException
  2667. * if merged cells are not i n continuous range
  2668. */
  2669. protected void sanityCheck() throws IllegalStateException {
  2670. List<String> columnOrder = grid.getState().columnOrder;
  2671. for (ROWTYPE row : rows) {
  2672. for (Set<String> cellGroup : row.getRowState().cellGroups
  2673. .keySet()) {
  2674. if (!checkCellGroupAndOrder(columnOrder, cellGroup)) {
  2675. throw new IllegalStateException(
  2676. "Not all merged cells were in a continuous range.");
  2677. }
  2678. }
  2679. }
  2680. }
  2681. private boolean checkCellGroupAndOrder(List<String> columnOrder,
  2682. Set<String> cellGroup) {
  2683. if (!columnOrder.containsAll(cellGroup)) {
  2684. return false;
  2685. }
  2686. for (int i = 0; i < columnOrder.size(); ++i) {
  2687. if (!cellGroup.contains(columnOrder.get(i))) {
  2688. continue;
  2689. }
  2690. for (int j = 1; j < cellGroup.size(); ++j) {
  2691. if (!cellGroup.contains(columnOrder.get(i + j))) {
  2692. return false;
  2693. }
  2694. }
  2695. return true;
  2696. }
  2697. return false;
  2698. }
  2699. /**
  2700. * Writes the declarative design to the given table section element.
  2701. *
  2702. * @since 7.5.0
  2703. * @param tableSectionElement
  2704. * Element to write design to
  2705. * @param designContext
  2706. * the design context
  2707. */
  2708. protected void writeDesign(Element tableSectionElement,
  2709. DesignContext designContext) {
  2710. for (ROWTYPE row : rows) {
  2711. row.writeDesign(tableSectionElement.appendElement("tr"),
  2712. designContext);
  2713. }
  2714. }
  2715. /**
  2716. * Writes the declarative design from the given table section element.
  2717. *
  2718. * @since 7.5.0
  2719. * @param tableSectionElement
  2720. * Element to read design from
  2721. * @param designContext
  2722. * the design context
  2723. * @throws DesignException
  2724. * if the table section contains unexpected children
  2725. */
  2726. protected void readDesign(Element tableSectionElement,
  2727. DesignContext designContext) throws DesignException {
  2728. while (rows.size() > 0) {
  2729. removeRow(0);
  2730. }
  2731. for (Element row : tableSectionElement.children()) {
  2732. if (!row.tagName().equals("tr")) {
  2733. throw new DesignException("Unexpected element in "
  2734. + tableSectionElement.tagName() + ": "
  2735. + row.tagName());
  2736. }
  2737. appendRow().readDesign(row, designContext);
  2738. }
  2739. }
  2740. }
  2741. /**
  2742. * Represents the header section of a Grid.
  2743. */
  2744. protected static class Header extends StaticSection<HeaderRow> {
  2745. private HeaderRow defaultRow = null;
  2746. private final GridStaticSectionState headerState = new GridStaticSectionState();
  2747. protected Header(Grid grid) {
  2748. this.grid = grid;
  2749. grid.getState(true).header = headerState;
  2750. HeaderRow row = createRow();
  2751. rows.add(row);
  2752. setDefaultRow(row);
  2753. getSectionState().rows.add(row.getRowState());
  2754. }
  2755. /**
  2756. * Sets the default row of this header. The default row is a special
  2757. * header row providing a user interface for sorting columns.
  2758. *
  2759. * @param row
  2760. * the new default row, or null for no default row
  2761. *
  2762. * @throws IllegalArgumentException
  2763. * this header does not contain the row
  2764. */
  2765. public void setDefaultRow(HeaderRow row) {
  2766. if (row == defaultRow) {
  2767. return;
  2768. }
  2769. if (row != null && !rows.contains(row)) {
  2770. throw new IllegalArgumentException(
  2771. "Cannot set a default row that does not exist in the section");
  2772. }
  2773. if (defaultRow != null) {
  2774. defaultRow.setDefaultRow(false);
  2775. }
  2776. if (row != null) {
  2777. row.setDefaultRow(true);
  2778. }
  2779. defaultRow = row;
  2780. markAsDirty();
  2781. }
  2782. /**
  2783. * Returns the current default row of this header. The default row is a
  2784. * special header row providing a user interface for sorting columns.
  2785. *
  2786. * @return the default row or null if no default row set
  2787. */
  2788. public HeaderRow getDefaultRow() {
  2789. return defaultRow;
  2790. }
  2791. @Override
  2792. protected GridStaticSectionState getSectionState() {
  2793. return headerState;
  2794. }
  2795. @Override
  2796. protected HeaderRow createRow() {
  2797. return new HeaderRow(this);
  2798. }
  2799. @Override
  2800. public HeaderRow removeRow(int rowIndex) {
  2801. HeaderRow row = super.removeRow(rowIndex);
  2802. if (row == defaultRow) {
  2803. // Default Header Row was just removed.
  2804. setDefaultRow(null);
  2805. }
  2806. return row;
  2807. }
  2808. @Override
  2809. protected void sanityCheck() throws IllegalStateException {
  2810. super.sanityCheck();
  2811. boolean hasDefaultRow = false;
  2812. for (HeaderRow row : rows) {
  2813. if (row.getRowState().defaultRow) {
  2814. if (!hasDefaultRow) {
  2815. hasDefaultRow = true;
  2816. } else {
  2817. throw new IllegalStateException(
  2818. "Multiple default rows in header");
  2819. }
  2820. }
  2821. }
  2822. }
  2823. @Override
  2824. protected void readDesign(Element tableSectionElement,
  2825. DesignContext designContext) {
  2826. super.readDesign(tableSectionElement, designContext);
  2827. if (defaultRow == null && !rows.isEmpty()) {
  2828. grid.setDefaultHeaderRow(rows.get(0));
  2829. }
  2830. }
  2831. }
  2832. /**
  2833. * Represents a header row in Grid.
  2834. */
  2835. public static class HeaderRow extends StaticSection.StaticRow<HeaderCell> {
  2836. protected HeaderRow(StaticSection<?> section) {
  2837. super(section);
  2838. }
  2839. private void setDefaultRow(boolean value) {
  2840. getRowState().defaultRow = value;
  2841. }
  2842. private boolean isDefaultRow() {
  2843. return getRowState().defaultRow;
  2844. }
  2845. @Override
  2846. protected HeaderCell createCell() {
  2847. return new HeaderCell(this);
  2848. }
  2849. @Override
  2850. protected String getCellTagName() {
  2851. return "th";
  2852. }
  2853. @Override
  2854. protected void writeDesign(Element trElement,
  2855. DesignContext designContext) {
  2856. super.writeDesign(trElement, designContext);
  2857. if (section.grid.getDefaultHeaderRow() == this) {
  2858. DesignAttributeHandler.writeAttribute("default",
  2859. trElement.attributes(), true, null, boolean.class);
  2860. }
  2861. }
  2862. @Override
  2863. protected void readDesign(Element trElement,
  2864. DesignContext designContext) {
  2865. super.readDesign(trElement, designContext);
  2866. boolean defaultRow = DesignAttributeHandler.readAttribute("default",
  2867. trElement.attributes(), false, boolean.class);
  2868. if (defaultRow) {
  2869. section.grid.setDefaultHeaderRow(this);
  2870. }
  2871. }
  2872. }
  2873. /**
  2874. * Represents a header cell in Grid. Can be a merged cell for multiple
  2875. * columns.
  2876. */
  2877. public static class HeaderCell extends StaticSection.StaticCell {
  2878. protected HeaderCell(HeaderRow row) {
  2879. super(row);
  2880. }
  2881. }
  2882. /**
  2883. * Represents the footer section of a Grid. By default Footer is not
  2884. * visible.
  2885. */
  2886. protected static class Footer extends StaticSection<FooterRow> {
  2887. private final GridStaticSectionState footerState = new GridStaticSectionState();
  2888. protected Footer(Grid grid) {
  2889. this.grid = grid;
  2890. grid.getState(true).footer = footerState;
  2891. }
  2892. @Override
  2893. protected GridStaticSectionState getSectionState() {
  2894. return footerState;
  2895. }
  2896. @Override
  2897. protected FooterRow createRow() {
  2898. return new FooterRow(this);
  2899. }
  2900. @Override
  2901. protected void sanityCheck() throws IllegalStateException {
  2902. super.sanityCheck();
  2903. }
  2904. }
  2905. /**
  2906. * Represents a footer row in Grid.
  2907. */
  2908. public static class FooterRow extends StaticSection.StaticRow<FooterCell> {
  2909. protected FooterRow(StaticSection<?> section) {
  2910. super(section);
  2911. }
  2912. @Override
  2913. protected FooterCell createCell() {
  2914. return new FooterCell(this);
  2915. }
  2916. @Override
  2917. protected String getCellTagName() {
  2918. return "td";
  2919. }
  2920. }
  2921. /**
  2922. * Represents a footer cell in Grid.
  2923. */
  2924. public static class FooterCell extends StaticSection.StaticCell {
  2925. protected FooterCell(FooterRow row) {
  2926. super(row);
  2927. }
  2928. }
  2929. /**
  2930. * A column in the grid. Can be obtained by calling
  2931. * {@link Grid#getColumn(Object propertyId)}.
  2932. */
  2933. public static class Column implements Serializable {
  2934. /**
  2935. * The state of the column shared to the client
  2936. */
  2937. private final GridColumnState state;
  2938. /**
  2939. * The grid this column is associated with
  2940. */
  2941. private final Grid grid;
  2942. /**
  2943. * Backing property for column
  2944. */
  2945. private final Object propertyId;
  2946. private Converter<?, Object> converter;
  2947. /**
  2948. * A check for allowing the
  2949. * {@link #Column(Grid, GridColumnState, Object) constructor} to call
  2950. * {@link #setConverter(Converter)} with a <code>null</code>, even if
  2951. * model and renderer aren't compatible.
  2952. */
  2953. private boolean isFirstConverterAssignment = true;
  2954. /**
  2955. * Internally used constructor.
  2956. *
  2957. * @param grid
  2958. * The grid this column belongs to. Should not be null.
  2959. * @param state
  2960. * the shared state of this column
  2961. * @param propertyId
  2962. * the backing property id for this column
  2963. */
  2964. Column(Grid grid, GridColumnState state, Object propertyId) {
  2965. this.grid = grid;
  2966. this.state = state;
  2967. this.propertyId = propertyId;
  2968. internalSetRenderer(new TextRenderer());
  2969. }
  2970. /**
  2971. * Returns the serializable state of this column that is sent to the
  2972. * client side connector.
  2973. *
  2974. * @return the internal state of the column
  2975. */
  2976. GridColumnState getState() {
  2977. return state;
  2978. }
  2979. /**
  2980. * Returns the property id for the backing property of this Column
  2981. *
  2982. * @return property id
  2983. */
  2984. public Object getPropertyId() {
  2985. return propertyId;
  2986. }
  2987. /**
  2988. * Returns the caption of the header. By default the header caption is
  2989. * the property id of the column.
  2990. *
  2991. * @return the text in the default row of header.
  2992. *
  2993. * @throws IllegalStateException
  2994. * if the column no longer is attached to the grid
  2995. */
  2996. public String getHeaderCaption() throws IllegalStateException {
  2997. checkColumnIsAttached();
  2998. return state.headerCaption;
  2999. }
  3000. /**
  3001. * Sets the caption of the header. This caption is also used as the
  3002. * hiding toggle caption, unless it is explicitly set via
  3003. * {@link #setHidingToggleCaption(String)}.
  3004. *
  3005. * @param caption
  3006. * the text to show in the caption
  3007. * @return the column itself
  3008. *
  3009. * @throws IllegalStateException
  3010. * if the column is no longer attached to any grid
  3011. */
  3012. public Column setHeaderCaption(String caption)
  3013. throws IllegalStateException {
  3014. checkColumnIsAttached();
  3015. if (caption == null) {
  3016. caption = ""; // Render null as empty
  3017. }
  3018. caption = Jsoup.parse(caption).text();
  3019. state.headerCaption = caption;
  3020. HeaderRow row = grid.getHeader().getDefaultRow();
  3021. if (row != null) {
  3022. row.getCell(grid.getPropertyIdByColumnId(state.id))
  3023. .setText(caption);
  3024. }
  3025. return this;
  3026. }
  3027. /**
  3028. * Gets the caption of the hiding toggle for this column.
  3029. *
  3030. * @since 7.5.0
  3031. * @see #setHidingToggleCaption(String)
  3032. * @return the caption for the hiding toggle for this column
  3033. * @throws IllegalStateException
  3034. * if the column is no longer attached to any grid
  3035. */
  3036. public String getHidingToggleCaption() throws IllegalStateException {
  3037. checkColumnIsAttached();
  3038. return state.hidingToggleCaption;
  3039. }
  3040. /**
  3041. * Sets the caption of the hiding toggle for this column. Shown in the
  3042. * toggle for this column in the grid's sidebar when the column is
  3043. * {@link #isHidable() hidable}.
  3044. * <p>
  3045. * The default value is <code>null</code>, and in that case the column's
  3046. * {@link #getHeaderCaption() header caption} is used.
  3047. * <p>
  3048. * <em>NOTE:</em> setting this to empty string might cause the hiding
  3049. * toggle to not render correctly.
  3050. *
  3051. * @since 7.5.0
  3052. * @param hidingToggleCaption
  3053. * the text to show in the column hiding toggle
  3054. * @return the column itself
  3055. * @throws IllegalStateException
  3056. * if the column is no longer attached to any grid
  3057. */
  3058. public Column setHidingToggleCaption(String hidingToggleCaption)
  3059. throws IllegalStateException {
  3060. checkColumnIsAttached();
  3061. state.hidingToggleCaption = hidingToggleCaption;
  3062. grid.markAsDirty();
  3063. return this;
  3064. }
  3065. /**
  3066. * Returns the width (in pixels). By default a column is 100px wide.
  3067. *
  3068. * @return the width in pixels of the column
  3069. * @throws IllegalStateException
  3070. * if the column is no longer attached to any grid
  3071. */
  3072. public double getWidth() throws IllegalStateException {
  3073. checkColumnIsAttached();
  3074. return state.width;
  3075. }
  3076. /**
  3077. * Sets the width (in pixels).
  3078. * <p>
  3079. * This overrides any configuration set by any of
  3080. * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
  3081. * {@link #setMaximumWidth(double)}.
  3082. *
  3083. * @param pixelWidth
  3084. * the new pixel width of the column
  3085. * @return the column itself
  3086. *
  3087. * @throws IllegalStateException
  3088. * if the column is no longer attached to any grid
  3089. * @throws IllegalArgumentException
  3090. * thrown if pixel width is less than zero
  3091. */
  3092. public Column setWidth(double pixelWidth)
  3093. throws IllegalStateException, IllegalArgumentException {
  3094. checkColumnIsAttached();
  3095. if (pixelWidth < 0) {
  3096. throw new IllegalArgumentException(
  3097. "Pixel width should be greated than 0 (in " + toString()
  3098. + ")");
  3099. }
  3100. if (state.width != pixelWidth) {
  3101. state.width = pixelWidth;
  3102. grid.markAsDirty();
  3103. grid.fireColumnResizeEvent(this, false);
  3104. }
  3105. return this;
  3106. }
  3107. /**
  3108. * Returns whether this column has an undefined width.
  3109. *
  3110. * @since 7.6
  3111. * @return whether the width is undefined
  3112. * @throws IllegalStateException
  3113. * if the column is no longer attached to any grid
  3114. */
  3115. public boolean isWidthUndefined() {
  3116. checkColumnIsAttached();
  3117. return state.width < 0;
  3118. }
  3119. /**
  3120. * Marks the column width as undefined. An undefined width means the
  3121. * grid is free to resize the column based on the cell contents and
  3122. * available space in the grid.
  3123. *
  3124. * @return the column itself
  3125. */
  3126. public Column setWidthUndefined() {
  3127. checkColumnIsAttached();
  3128. if (!isWidthUndefined()) {
  3129. state.width = -1;
  3130. grid.markAsDirty();
  3131. grid.fireColumnResizeEvent(this, false);
  3132. }
  3133. return this;
  3134. }
  3135. /**
  3136. * Checks if column is attached and throws an
  3137. * {@link IllegalStateException} if it is not
  3138. *
  3139. * @throws IllegalStateException
  3140. * if the column is no longer attached to any grid
  3141. */
  3142. protected void checkColumnIsAttached() throws IllegalStateException {
  3143. if (grid.getColumnByColumnId(state.id) == null) {
  3144. throw new IllegalStateException("Column no longer exists.");
  3145. }
  3146. }
  3147. /**
  3148. * Sets this column as the last frozen column in its grid.
  3149. *
  3150. * @return the column itself
  3151. *
  3152. * @throws IllegalArgumentException
  3153. * if the column is no longer attached to any grid
  3154. * @see Grid#setFrozenColumnCount(int)
  3155. */
  3156. public Column setLastFrozenColumn() {
  3157. checkColumnIsAttached();
  3158. grid.setFrozenColumnCount(
  3159. grid.getState(false).columnOrder.indexOf(getState().id)
  3160. + 1);
  3161. return this;
  3162. }
  3163. /**
  3164. * Sets the renderer for this column.
  3165. * <p>
  3166. * If a suitable converter isn't defined explicitly, the session
  3167. * converter factory is used to find a compatible converter.
  3168. *
  3169. * @param renderer
  3170. * the renderer to use
  3171. * @return the column itself
  3172. *
  3173. * @throws IllegalArgumentException
  3174. * if no compatible converter could be found
  3175. *
  3176. * @see VaadinSession#getConverterFactory()
  3177. * @see ConverterUtil#getConverter(Class, Class, VaadinSession)
  3178. * @see #setConverter(Converter)
  3179. */
  3180. public Column setRenderer(Renderer<?> renderer) {
  3181. if (!internalSetRenderer(renderer)) {
  3182. throw new IllegalArgumentException(
  3183. "Could not find a converter for converting from the model type "
  3184. + getModelType()
  3185. + " to the renderer presentation type "
  3186. + renderer.getPresentationType() + " (in "
  3187. + toString() + ")");
  3188. }
  3189. return this;
  3190. }
  3191. /**
  3192. * Sets the renderer for this column and the converter used to convert
  3193. * from the property value type to the renderer presentation type.
  3194. *
  3195. * @param renderer
  3196. * the renderer to use, cannot be null
  3197. * @param converter
  3198. * the converter to use
  3199. * @return the column itself
  3200. *
  3201. * @throws IllegalArgumentException
  3202. * if the renderer is already associated with a grid column
  3203. */
  3204. public <T> Column setRenderer(Renderer<T> renderer,
  3205. Converter<? extends T, ?> converter) {
  3206. if (renderer.getParent() != null) {
  3207. throw new IllegalArgumentException(
  3208. "Cannot set a renderer that is already connected to a grid column (in "
  3209. + toString() + ")");
  3210. }
  3211. if (getRenderer() != null) {
  3212. grid.removeExtension(getRenderer());
  3213. }
  3214. grid.addRenderer(renderer);
  3215. state.rendererConnector = renderer;
  3216. setConverter(converter);
  3217. return this;
  3218. }
  3219. /**
  3220. * Sets the converter used to convert from the property value type to
  3221. * the renderer presentation type.
  3222. *
  3223. * @param converter
  3224. * the converter to use, or {@code null} to not use any
  3225. * converters
  3226. * @return the column itself
  3227. *
  3228. * @throws IllegalArgumentException
  3229. * if the types are not compatible
  3230. */
  3231. public Column setConverter(Converter<?, ?> converter)
  3232. throws IllegalArgumentException {
  3233. Class<?> modelType = getModelType();
  3234. if (converter != null) {
  3235. if (!converter.getModelType().isAssignableFrom(modelType)) {
  3236. throw new IllegalArgumentException(
  3237. "The converter model type "
  3238. + converter.getModelType()
  3239. + " is not compatible with the property type "
  3240. + modelType + " (in " + toString() + ")");
  3241. } else if (!getRenderer().getPresentationType()
  3242. .isAssignableFrom(converter.getPresentationType())) {
  3243. throw new IllegalArgumentException(
  3244. "The converter presentation type "
  3245. + converter.getPresentationType()
  3246. + " is not compatible with the renderer presentation type "
  3247. + getRenderer().getPresentationType()
  3248. + " (in " + toString() + ")");
  3249. }
  3250. }
  3251. else {
  3252. /*
  3253. * Since the converter is null (i.e. will be removed), we need
  3254. * to know that the renderer and model are compatible. If not,
  3255. * we can't allow for this to happen.
  3256. *
  3257. * The constructor is allowed to call this method with null
  3258. * without any compatibility checks, therefore we have a special
  3259. * case for it.
  3260. */
  3261. Class<?> rendererPresentationType = getRenderer()
  3262. .getPresentationType();
  3263. if (!isFirstConverterAssignment && !rendererPresentationType
  3264. .isAssignableFrom(modelType)) {
  3265. throw new IllegalArgumentException(
  3266. "Cannot remove converter, "
  3267. + "as renderer's presentation type "
  3268. + rendererPresentationType.getName()
  3269. + " and column's " + "model "
  3270. + modelType.getName() + " type aren't "
  3271. + "directly compatible with each other (in "
  3272. + toString() + ")");
  3273. }
  3274. }
  3275. isFirstConverterAssignment = false;
  3276. @SuppressWarnings("unchecked")
  3277. Converter<?, Object> castConverter = (Converter<?, Object>) converter;
  3278. this.converter = castConverter;
  3279. return this;
  3280. }
  3281. /**
  3282. * Returns the renderer instance used by this column.
  3283. *
  3284. * @return the renderer
  3285. */
  3286. public Renderer<?> getRenderer() {
  3287. return (Renderer<?>) getState().rendererConnector;
  3288. }
  3289. /**
  3290. * Returns the converter instance used by this column.
  3291. *
  3292. * @return the converter
  3293. */
  3294. public Converter<?, ?> getConverter() {
  3295. return converter;
  3296. }
  3297. private <T> boolean internalSetRenderer(Renderer<T> renderer) {
  3298. Converter<? extends T, ?> converter;
  3299. if (isCompatibleWithProperty(renderer, getConverter())) {
  3300. // Use the existing converter (possibly none) if types
  3301. // compatible
  3302. converter = (Converter<? extends T, ?>) getConverter();
  3303. } else {
  3304. converter = ConverterUtil.getConverter(
  3305. renderer.getPresentationType(), getModelType(),
  3306. getSession());
  3307. }
  3308. setRenderer(renderer, converter);
  3309. return isCompatibleWithProperty(renderer, converter);
  3310. }
  3311. private VaadinSession getSession() {
  3312. UI ui = grid.getUI();
  3313. return ui != null ? ui.getSession() : null;
  3314. }
  3315. private boolean isCompatibleWithProperty(Renderer<?> renderer,
  3316. Converter<?, ?> converter) {
  3317. Class<?> type;
  3318. if (converter == null) {
  3319. type = getModelType();
  3320. } else {
  3321. type = converter.getPresentationType();
  3322. }
  3323. return renderer.getPresentationType().isAssignableFrom(type);
  3324. }
  3325. private Class<?> getModelType() {
  3326. return grid.getContainerDataSource()
  3327. .getType(grid.getPropertyIdByColumnId(state.id));
  3328. }
  3329. /**
  3330. * Sets whether this column is sortable by the user. The grid can be
  3331. * sorted by a sortable column by clicking or tapping the column's
  3332. * default header. Programmatic sorting using the Grid#sort methods is
  3333. * not affected by this setting.
  3334. *
  3335. * @param sortable
  3336. * {@code true} if the user should be able to sort the
  3337. * column, {@code false} otherwise
  3338. * @return the column itself
  3339. *
  3340. * @throws IllegalStateException
  3341. * if the data source of the Grid does not implement
  3342. * {@link Sortable}
  3343. * @throws IllegalStateException
  3344. * if the data source does not support sorting by the
  3345. * property associated with this column
  3346. */
  3347. public Column setSortable(boolean sortable) {
  3348. checkColumnIsAttached();
  3349. if (sortable) {
  3350. if (!(grid.datasource instanceof Sortable)) {
  3351. throw new IllegalStateException("Can't set column "
  3352. + toString()
  3353. + " sortable. The Container of Grid does not implement Sortable");
  3354. } else if (!((Sortable) grid.datasource)
  3355. .getSortableContainerPropertyIds()
  3356. .contains(propertyId)) {
  3357. throw new IllegalStateException(
  3358. "Can't set column " + toString()
  3359. + " sortable. Container doesn't support sorting by property "
  3360. + propertyId);
  3361. }
  3362. }
  3363. state.sortable = sortable;
  3364. grid.markAsDirty();
  3365. return this;
  3366. }
  3367. /**
  3368. * Returns whether the user can sort the grid by this column.
  3369. * <p>
  3370. * <em>Note:</em> it is possible to sort by this column programmatically
  3371. * using the Grid#sort methods regardless of the returned value.
  3372. *
  3373. * @return {@code true} if the column is sortable by the user,
  3374. * {@code false} otherwise
  3375. */
  3376. public boolean isSortable() {
  3377. return state.sortable;
  3378. }
  3379. @Override
  3380. public String toString() {
  3381. return getClass().getSimpleName() + "[propertyId:"
  3382. + grid.getPropertyIdByColumnId(state.id) + "]";
  3383. }
  3384. /**
  3385. * Sets the ratio with which the column expands.
  3386. * <p>
  3387. * By default, all columns expand equally (treated as if all of them had
  3388. * an expand ratio of 1). Once at least one column gets a defined expand
  3389. * ratio, the implicit expand ratio is removed, and only the defined
  3390. * expand ratios are taken into account.
  3391. * <p>
  3392. * If a column has a defined width ({@link #setWidth(double)}), it
  3393. * overrides this method's effects.
  3394. * <p>
  3395. * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
  3396. * and 2, respectively. The column with a <strong>ratio of 0 is exactly
  3397. * as wide as its contents requires</strong>. The column with a ratio of
  3398. * 1 is as wide as it needs, <strong>plus a third of any excess
  3399. * space</strong>, because we have 3 parts total, and this column
  3400. * reserves only one of those. The column with a ratio of 2, is as wide
  3401. * as it needs to be, <strong>plus two thirds</strong> of the excess
  3402. * width.
  3403. *
  3404. * @param expandRatio
  3405. * the expand ratio of this column. {@code 0} to not have it
  3406. * expand at all. A negative number to clear the expand
  3407. * value.
  3408. * @throws IllegalStateException
  3409. * if the column is no longer attached to any grid
  3410. * @see #setWidth(double)
  3411. */
  3412. public Column setExpandRatio(int expandRatio)
  3413. throws IllegalStateException {
  3414. checkColumnIsAttached();
  3415. getState().expandRatio = expandRatio;
  3416. grid.markAsDirty();
  3417. return this;
  3418. }
  3419. /**
  3420. * Returns the column's expand ratio.
  3421. *
  3422. * @return the column's expand ratio
  3423. * @see #setExpandRatio(int)
  3424. */
  3425. public int getExpandRatio() {
  3426. return getState().expandRatio;
  3427. }
  3428. /**
  3429. * Clears the expand ratio for this column.
  3430. * <p>
  3431. * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
  3432. *
  3433. * @throws IllegalStateException
  3434. * if the column is no longer attached to any grid
  3435. */
  3436. public Column clearExpandRatio() throws IllegalStateException {
  3437. return setExpandRatio(-1);
  3438. }
  3439. /**
  3440. * Sets the minimum width for this column.
  3441. * <p>
  3442. * This defines the minimum guaranteed pixel width of the column
  3443. * <em>when it is set to expand</em>.
  3444. *
  3445. * @throws IllegalStateException
  3446. * if the column is no longer attached to any grid
  3447. * @see #setExpandRatio(int)
  3448. */
  3449. public Column setMinimumWidth(double pixels)
  3450. throws IllegalStateException {
  3451. checkColumnIsAttached();
  3452. final double maxwidth = getMaximumWidth();
  3453. if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
  3454. throw new IllegalArgumentException("New minimum width ("
  3455. + pixels + ") was greater than maximum width ("
  3456. + maxwidth + ")");
  3457. }
  3458. getState().minWidth = pixels;
  3459. grid.markAsDirty();
  3460. return this;
  3461. }
  3462. /**
  3463. * Return the minimum width for this column.
  3464. *
  3465. * @return the minimum width for this column
  3466. * @see #setMinimumWidth(double)
  3467. */
  3468. public double getMinimumWidth() {
  3469. return getState().minWidth;
  3470. }
  3471. /**
  3472. * Sets the maximum width for this column.
  3473. * <p>
  3474. * This defines the maximum allowed pixel width of the column <em>when
  3475. * it is set to expand</em>.
  3476. *
  3477. * @param pixels
  3478. * the maximum width
  3479. * @throws IllegalStateException
  3480. * if the column is no longer attached to any grid
  3481. * @see #setExpandRatio(int)
  3482. */
  3483. public Column setMaximumWidth(double pixels) {
  3484. checkColumnIsAttached();
  3485. final double minwidth = getMinimumWidth();
  3486. if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
  3487. throw new IllegalArgumentException("New maximum width ("
  3488. + pixels + ") was less than minimum width (" + minwidth
  3489. + ")");
  3490. }
  3491. getState().maxWidth = pixels;
  3492. grid.markAsDirty();
  3493. return this;
  3494. }
  3495. /**
  3496. * Returns the maximum width for this column.
  3497. *
  3498. * @return the maximum width for this column
  3499. * @see #setMaximumWidth(double)
  3500. */
  3501. public double getMaximumWidth() {
  3502. return getState().maxWidth;
  3503. }
  3504. /**
  3505. * Sets whether the properties corresponding to this column should be
  3506. * editable when the item editor is active. By default columns are
  3507. * editable.
  3508. * <p>
  3509. * Values in non-editable columns are currently not displayed when the
  3510. * editor is active, but this will probably change in the future. They
  3511. * are not automatically assigned an editor field and, if one is
  3512. * manually assigned, it is not used. Columns that cannot (or should
  3513. * not) be edited even in principle should be set non-editable.
  3514. *
  3515. * @param editable
  3516. * {@code true} if this column should be editable,
  3517. * {@code false} otherwise
  3518. * @return this column
  3519. *
  3520. * @throws IllegalStateException
  3521. * if the editor is currently active
  3522. *
  3523. * @see Grid#editItem(Object)
  3524. * @see Grid#isEditorActive()
  3525. */
  3526. public Column setEditable(boolean editable) {
  3527. checkColumnIsAttached();
  3528. if (grid.isEditorActive()) {
  3529. throw new IllegalStateException(
  3530. "Cannot change column editable status while the editor is active");
  3531. }
  3532. getState().editable = editable;
  3533. grid.markAsDirty();
  3534. return this;
  3535. }
  3536. /**
  3537. * Returns whether the properties corresponding to this column should be
  3538. * editable when the item editor is active.
  3539. *
  3540. * @return {@code true} if this column is editable, {@code false}
  3541. * otherwise
  3542. *
  3543. * @see Grid#editItem(Object)
  3544. * @see #setEditable(boolean)
  3545. */
  3546. public boolean isEditable() {
  3547. return getState().editable;
  3548. }
  3549. /**
  3550. * Sets the field component used to edit the properties in this column
  3551. * when the item editor is active. If an item has not been set, then the
  3552. * binding is postponed until the item is set using
  3553. * {@link #editItem(Object)}.
  3554. * <p>
  3555. * Setting the field to <code>null</code> clears any previously set
  3556. * field, causing a new field to be created the next time the item
  3557. * editor is opened.
  3558. *
  3559. * @param editor
  3560. * the editor field
  3561. * @return this column
  3562. */
  3563. public Column setEditorField(Field<?> editor) {
  3564. grid.setEditorField(getPropertyId(), editor);
  3565. return this;
  3566. }
  3567. /**
  3568. * Returns the editor field used to edit the properties in this column
  3569. * when the item editor is active. Returns null if the column is not
  3570. * {@link Column#isEditable() editable}.
  3571. * <p>
  3572. * When {@link #editItem(Object) editItem} is called, fields are
  3573. * automatically created and bound for any unbound properties.
  3574. * <p>
  3575. * Getting a field before the editor has been opened depends on special
  3576. * support from the {@link FieldGroup} in use. Using this method with a
  3577. * user-provided <code>FieldGroup</code> might cause
  3578. * {@link FieldGroup.BindException BindException} to be thrown.
  3579. *
  3580. * @return the bound field; or <code>null</code> if the respective
  3581. * column is not editable
  3582. *
  3583. * @throws IllegalArgumentException
  3584. * if there is no column for the provided property id
  3585. * @throws FieldGroup.BindException
  3586. * if no field has been configured and there is a problem
  3587. * building or binding
  3588. */
  3589. public Field<?> getEditorField() {
  3590. return grid.getEditorField(getPropertyId());
  3591. }
  3592. /**
  3593. * Hides or shows the column. By default columns are visible before
  3594. * explicitly hiding them.
  3595. *
  3596. * @since 7.5.0
  3597. * @param hidden
  3598. * <code>true</code> to hide the column, <code>false</code>
  3599. * to show
  3600. * @return this column
  3601. */
  3602. public Column setHidden(boolean hidden) {
  3603. if (hidden != getState().hidden) {
  3604. getState().hidden = hidden;
  3605. grid.markAsDirty();
  3606. grid.fireColumnVisibilityChangeEvent(this, hidden, false);
  3607. }
  3608. return this;
  3609. }
  3610. /**
  3611. * Returns whether this column is hidden. Default is {@code false}.
  3612. *
  3613. * @since 7.5.0
  3614. * @return <code>true</code> if the column is currently hidden,
  3615. * <code>false</code> otherwise
  3616. */
  3617. public boolean isHidden() {
  3618. return getState().hidden;
  3619. }
  3620. /**
  3621. * Sets whether this column can be hidden by the user. Hidable columns
  3622. * can be hidden and shown via the sidebar menu.
  3623. *
  3624. * @since 7.5.0
  3625. * @param hidable
  3626. * <code>true</code> iff the column may be hidable by the
  3627. * user via UI interaction
  3628. * @return this column
  3629. */
  3630. public Column setHidable(boolean hidable) {
  3631. if (hidable != getState().hidable) {
  3632. getState().hidable = hidable;
  3633. grid.markAsDirty();
  3634. }
  3635. return this;
  3636. }
  3637. /**
  3638. * Returns whether this column can be hidden by the user. Default is
  3639. * {@code false}.
  3640. * <p>
  3641. * <em>Note:</em> the column can be programmatically hidden using
  3642. * {@link #setHidden(boolean)} regardless of the returned value.
  3643. *
  3644. * @since 7.5.0
  3645. * @return <code>true</code> if the user can hide the column,
  3646. * <code>false</code> if not
  3647. */
  3648. public boolean isHidable() {
  3649. return getState().hidable;
  3650. }
  3651. /**
  3652. * Sets whether this column can be resized by the user.
  3653. *
  3654. * @since 7.6
  3655. * @param resizable
  3656. * {@code true} if this column should be resizable,
  3657. * {@code false} otherwise
  3658. */
  3659. public Column setResizable(boolean resizable) {
  3660. if (resizable != getState().resizable) {
  3661. getState().resizable = resizable;
  3662. grid.markAsDirty();
  3663. }
  3664. return this;
  3665. }
  3666. /**
  3667. * Returns whether this column can be resized by the user. Default is
  3668. * {@code true}.
  3669. * <p>
  3670. * <em>Note:</em> the column can be programmatically resized using
  3671. * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
  3672. * of the returned value.
  3673. *
  3674. * @since 7.6
  3675. * @return {@code true} if this column is resizable, {@code false}
  3676. * otherwise
  3677. */
  3678. public boolean isResizable() {
  3679. return getState().resizable;
  3680. }
  3681. /**
  3682. * Writes the design attributes for this column into given element.
  3683. *
  3684. * @since 7.5.0
  3685. *
  3686. * @param design
  3687. * Element to write attributes into
  3688. *
  3689. * @param designContext
  3690. * the design context
  3691. */
  3692. protected void writeDesign(Element design,
  3693. DesignContext designContext) {
  3694. Attributes attributes = design.attributes();
  3695. GridColumnState def = new GridColumnState();
  3696. DesignAttributeHandler.writeAttribute("property-id", attributes,
  3697. getPropertyId(), null, Object.class);
  3698. // Sortable is a special attribute that depends on the container.
  3699. DesignAttributeHandler.writeAttribute("sortable", attributes,
  3700. isSortable(), null, boolean.class);
  3701. DesignAttributeHandler.writeAttribute("editable", attributes,
  3702. isEditable(), def.editable, boolean.class);
  3703. DesignAttributeHandler.writeAttribute("resizable", attributes,
  3704. isResizable(), def.resizable, boolean.class);
  3705. DesignAttributeHandler.writeAttribute("hidable", attributes,
  3706. isHidable(), def.hidable, boolean.class);
  3707. DesignAttributeHandler.writeAttribute("hidden", attributes,
  3708. isHidden(), def.hidden, boolean.class);
  3709. DesignAttributeHandler.writeAttribute("hiding-toggle-caption",
  3710. attributes, getHidingToggleCaption(), null, String.class);
  3711. DesignAttributeHandler.writeAttribute("width", attributes,
  3712. getWidth(), def.width, Double.class);
  3713. DesignAttributeHandler.writeAttribute("min-width", attributes,
  3714. getMinimumWidth(), def.minWidth, Double.class);
  3715. DesignAttributeHandler.writeAttribute("max-width", attributes,
  3716. getMaximumWidth(), def.maxWidth, Double.class);
  3717. DesignAttributeHandler.writeAttribute("expand", attributes,
  3718. getExpandRatio(), def.expandRatio, Integer.class);
  3719. }
  3720. /**
  3721. * Reads the design attributes for this column from given element.
  3722. *
  3723. * @since 7.5.0
  3724. * @param design
  3725. * Element to read attributes from
  3726. * @param designContext
  3727. * the design context
  3728. */
  3729. protected void readDesign(Element design, DesignContext designContext) {
  3730. Attributes attributes = design.attributes();
  3731. if (design.hasAttr("sortable")) {
  3732. setSortable(DesignAttributeHandler.readAttribute("sortable",
  3733. attributes, boolean.class));
  3734. }
  3735. if (design.hasAttr("editable")) {
  3736. setEditable(DesignAttributeHandler.readAttribute("editable",
  3737. attributes, boolean.class));
  3738. }
  3739. if (design.hasAttr("resizable")) {
  3740. setResizable(DesignAttributeHandler.readAttribute("resizable",
  3741. attributes, boolean.class));
  3742. }
  3743. if (design.hasAttr("hidable")) {
  3744. setHidable(DesignAttributeHandler.readAttribute("hidable",
  3745. attributes, boolean.class));
  3746. }
  3747. if (design.hasAttr("hidden")) {
  3748. setHidden(DesignAttributeHandler.readAttribute("hidden",
  3749. attributes, boolean.class));
  3750. }
  3751. if (design.hasAttr("hiding-toggle-caption")) {
  3752. setHidingToggleCaption(DesignAttributeHandler.readAttribute(
  3753. "hiding-toggle-caption", attributes, String.class));
  3754. }
  3755. // Read size info where necessary.
  3756. if (design.hasAttr("width")) {
  3757. setWidth(DesignAttributeHandler.readAttribute("width",
  3758. attributes, Double.class));
  3759. }
  3760. if (design.hasAttr("min-width")) {
  3761. setMinimumWidth(DesignAttributeHandler
  3762. .readAttribute("min-width", attributes, Double.class));
  3763. }
  3764. if (design.hasAttr("max-width")) {
  3765. setMaximumWidth(DesignAttributeHandler
  3766. .readAttribute("max-width", attributes, Double.class));
  3767. }
  3768. if (design.hasAttr("expand")) {
  3769. if (design.attr("expand").isEmpty()) {
  3770. setExpandRatio(1);
  3771. } else {
  3772. setExpandRatio(DesignAttributeHandler.readAttribute(
  3773. "expand", attributes, Integer.class));
  3774. }
  3775. }
  3776. }
  3777. }
  3778. /**
  3779. * An abstract base class for server-side {@link Renderer Grid renderers}.
  3780. * This class currently extends the AbstractExtension superclass, but this
  3781. * fact should be regarded as an implementation detail and subject to change
  3782. * in a future major or minor Vaadin revision.
  3783. *
  3784. * @param <T>
  3785. * the type this renderer knows how to present
  3786. */
  3787. public static abstract class AbstractRenderer<T>
  3788. extends AbstractGridExtension implements Renderer<T> {
  3789. private final Class<T> presentationType;
  3790. private final String nullRepresentation;
  3791. protected AbstractRenderer(Class<T> presentationType,
  3792. String nullRepresentation) {
  3793. this.presentationType = presentationType;
  3794. this.nullRepresentation = nullRepresentation;
  3795. }
  3796. protected AbstractRenderer(Class<T> presentationType) {
  3797. this(presentationType, null);
  3798. }
  3799. /**
  3800. * This method is inherited from AbstractExtension but should never be
  3801. * called directly with an AbstractRenderer.
  3802. */
  3803. @Deprecated
  3804. @Override
  3805. protected Class<Grid> getSupportedParentType() {
  3806. return Grid.class;
  3807. }
  3808. /**
  3809. * This method is inherited from AbstractExtension but should never be
  3810. * called directly with an AbstractRenderer.
  3811. */
  3812. @Deprecated
  3813. @Override
  3814. protected void extend(AbstractClientConnector target) {
  3815. super.extend(target);
  3816. }
  3817. @Override
  3818. public Class<T> getPresentationType() {
  3819. return presentationType;
  3820. }
  3821. @Override
  3822. public JsonValue encode(T value) {
  3823. if (value == null) {
  3824. return encode(getNullRepresentation(), String.class);
  3825. } else {
  3826. return encode(value, getPresentationType());
  3827. }
  3828. }
  3829. /**
  3830. * Null representation for the renderer
  3831. *
  3832. * @return a textual representation of {@code null}
  3833. */
  3834. protected String getNullRepresentation() {
  3835. return nullRepresentation;
  3836. }
  3837. /**
  3838. * Encodes the given value to JSON.
  3839. * <p>
  3840. * This is a helper method that can be invoked by an
  3841. * {@link #encode(Object) encode(T)} override if serializing a value of
  3842. * type other than {@link #getPresentationType() the presentation type}
  3843. * is desired. For instance, a {@code Renderer<Date>} could first turn a
  3844. * date value into a formatted string and return
  3845. * {@code encode(dateString, String.class)}.
  3846. *
  3847. * @param value
  3848. * the value to be encoded
  3849. * @param type
  3850. * the type of the value
  3851. * @return a JSON representation of the given value
  3852. */
  3853. protected <U> JsonValue encode(U value, Class<U> type) {
  3854. return JsonCodec
  3855. .encode(value, null, type, getUI().getConnectorTracker())
  3856. .getEncodedValue();
  3857. }
  3858. /**
  3859. * Converts and encodes the given data model property value using the
  3860. * given converter and renderer. This method is public only for testing
  3861. * purposes.
  3862. *
  3863. * @since 7.6
  3864. * @param renderer
  3865. * the renderer to use
  3866. * @param converter
  3867. * the converter to use
  3868. * @param modelValue
  3869. * the value to convert and encode
  3870. * @param locale
  3871. * the locale to use in conversion
  3872. * @return an encoded value ready to be sent to the client
  3873. */
  3874. public static <T> JsonValue encodeValue(Object modelValue,
  3875. Renderer<T> renderer, Converter<?, ?> converter,
  3876. Locale locale) {
  3877. Class<T> presentationType = renderer.getPresentationType();
  3878. T presentationValue;
  3879. if (converter == null) {
  3880. try {
  3881. presentationValue = presentationType.cast(modelValue);
  3882. } catch (ClassCastException e) {
  3883. if (presentationType == String.class) {
  3884. // If there is no converter, just fallback to using
  3885. // toString(). modelValue can't be null as
  3886. // Class.cast(null) will always succeed
  3887. presentationValue = (T) modelValue.toString();
  3888. } else {
  3889. throw new Converter.ConversionException(
  3890. "Unable to convert value of type "
  3891. + modelValue.getClass().getName()
  3892. + " to presentation type "
  3893. + presentationType.getName()
  3894. + ". No converter is set and the types are not compatible.");
  3895. }
  3896. }
  3897. } else {
  3898. assert presentationType
  3899. .isAssignableFrom(converter.getPresentationType());
  3900. @SuppressWarnings("unchecked")
  3901. Converter<T, Object> safeConverter = (Converter<T, Object>) converter;
  3902. presentationValue = safeConverter.convertToPresentation(
  3903. modelValue, safeConverter.getPresentationType(),
  3904. locale);
  3905. }
  3906. JsonValue encodedValue;
  3907. try {
  3908. encodedValue = renderer.encode(presentationValue);
  3909. } catch (Exception e) {
  3910. getLogger().log(Level.SEVERE, "Unable to encode data", e);
  3911. encodedValue = renderer.encode(null);
  3912. }
  3913. return encodedValue;
  3914. }
  3915. private static Logger getLogger() {
  3916. return Logger.getLogger(AbstractRenderer.class.getName());
  3917. }
  3918. }
  3919. /**
  3920. * An abstract base class for server-side Grid extensions.
  3921. * <p>
  3922. * Note: If the extension is an instance of {@link DataGenerator} it will
  3923. * automatically register itself to {@link RpcDataProviderExtension} of
  3924. * extended Grid. On remove this registration is automatically removed.
  3925. *
  3926. * @since 7.5
  3927. */
  3928. public static abstract class AbstractGridExtension
  3929. extends AbstractExtension {
  3930. /**
  3931. * Constructs a new Grid extension.
  3932. */
  3933. public AbstractGridExtension() {
  3934. super();
  3935. }
  3936. /**
  3937. * Constructs a new Grid extension and extends given Grid.
  3938. *
  3939. * @param grid
  3940. * a grid instance
  3941. */
  3942. public AbstractGridExtension(Grid grid) {
  3943. super();
  3944. extend(grid);
  3945. }
  3946. @Override
  3947. protected void extend(AbstractClientConnector target) {
  3948. super.extend(target);
  3949. if (this instanceof DataGenerator) {
  3950. getParentGrid().datasourceExtension
  3951. .addDataGenerator((DataGenerator) this);
  3952. }
  3953. }
  3954. @Override
  3955. public void remove() {
  3956. if (this instanceof DataGenerator) {
  3957. getParentGrid().datasourceExtension
  3958. .removeDataGenerator((DataGenerator) this);
  3959. }
  3960. super.remove();
  3961. }
  3962. /**
  3963. * Gets the item id for a row key.
  3964. * <p>
  3965. * A key is used to identify a particular row on both a server and a
  3966. * client. This method can be used to get the item id for the row key
  3967. * that the client has sent.
  3968. *
  3969. * @param rowKey
  3970. * the row key for which to retrieve an item id
  3971. * @return the item id corresponding to {@code key}
  3972. */
  3973. protected Object getItemId(String rowKey) {
  3974. return getParentGrid().getKeyMapper().get(rowKey);
  3975. }
  3976. /**
  3977. * Gets the column for a column id.
  3978. * <p>
  3979. * An id is used to identify a particular column on both a server and a
  3980. * client. This method can be used to get the column for the column id
  3981. * that the client has sent.
  3982. *
  3983. * @param columnId
  3984. * the column id for which to retrieve a column
  3985. * @return the column corresponding to {@code columnId}
  3986. */
  3987. protected Column getColumn(String columnId) {
  3988. return getParentGrid().getColumnByColumnId(columnId);
  3989. }
  3990. /**
  3991. * Gets the parent Grid of the renderer.
  3992. *
  3993. * @return parent grid
  3994. * @throws IllegalStateException
  3995. * if parent is not Grid
  3996. */
  3997. protected Grid getParentGrid() {
  3998. if (getParent() instanceof Grid) {
  3999. Grid grid = (Grid) getParent();
  4000. return grid;
  4001. } else if (getParent() == null) {
  4002. throw new IllegalStateException(
  4003. "Renderer is not attached to any parent");
  4004. } else {
  4005. throw new IllegalStateException(
  4006. "Renderers can be used only with Grid. Extended "
  4007. + getParent().getClass().getSimpleName()
  4008. + " instead");
  4009. }
  4010. }
  4011. /**
  4012. * Resends the row data for given item id to the client.
  4013. *
  4014. * @since 7.6
  4015. * @param itemId
  4016. * row to refresh
  4017. */
  4018. protected void refreshRow(Object itemId) {
  4019. getParentGrid().datasourceExtension.updateRowData(itemId);
  4020. }
  4021. /**
  4022. * Informs the parent Grid that this Extension wants to add a child
  4023. * component to it.
  4024. *
  4025. * @since 7.6
  4026. * @param c
  4027. * component
  4028. */
  4029. protected void addComponentToGrid(Component c) {
  4030. getParentGrid().addComponent(c);
  4031. }
  4032. /**
  4033. * Informs the parent Grid that this Extension wants to remove a child
  4034. * component from it.
  4035. *
  4036. * @since 7.6
  4037. * @param c
  4038. * component
  4039. */
  4040. protected void removeComponentFromGrid(Component c) {
  4041. getParentGrid().removeComponent(c);
  4042. }
  4043. }
  4044. /**
  4045. * The data source attached to the grid
  4046. */
  4047. private Container.Indexed datasource;
  4048. /**
  4049. * Property id to column instance mapping
  4050. */
  4051. private final Map<Object, Column> columns = new HashMap<Object, Column>();
  4052. /**
  4053. * Key generator for column server-to-client communication
  4054. */
  4055. private final KeyMapper<Object> columnKeys = new KeyMapper<Object>();
  4056. /**
  4057. * The current sort order
  4058. */
  4059. private final List<SortOrder> sortOrder = new ArrayList<SortOrder>();
  4060. /**
  4061. * Property listener for listening to changes in data source properties.
  4062. */
  4063. private final PropertySetChangeListener propertyListener = new PropertySetChangeListener() {
  4064. @Override
  4065. public void containerPropertySetChange(PropertySetChangeEvent event) {
  4066. Collection<?> properties = new HashSet<Object>(
  4067. event.getContainer().getContainerPropertyIds());
  4068. // Find columns that need to be removed.
  4069. List<Column> removedColumns = new LinkedList<Column>();
  4070. for (Object propertyId : columns.keySet()) {
  4071. if (!properties.contains(propertyId)) {
  4072. removedColumns.add(getColumn(propertyId));
  4073. }
  4074. }
  4075. // Actually remove columns.
  4076. for (Column column : removedColumns) {
  4077. Object propertyId = column.getPropertyId();
  4078. internalRemoveColumn(propertyId);
  4079. columnKeys.remove(propertyId);
  4080. }
  4081. datasourceExtension.columnsRemoved(removedColumns);
  4082. // Add new columns
  4083. List<Column> addedColumns = new LinkedList<Column>();
  4084. for (Object propertyId : properties) {
  4085. if (!columns.containsKey(propertyId)) {
  4086. addedColumns.add(appendColumn(propertyId));
  4087. }
  4088. }
  4089. datasourceExtension.columnsAdded(addedColumns);
  4090. if (getFrozenColumnCount() > columns.size()) {
  4091. setFrozenColumnCount(columns.size());
  4092. }
  4093. // Unset sortable for non-sortable columns.
  4094. if (datasource instanceof Sortable) {
  4095. Collection<?> sortables = ((Sortable) datasource)
  4096. .getSortableContainerPropertyIds();
  4097. for (Object propertyId : columns.keySet()) {
  4098. Column column = columns.get(propertyId);
  4099. if (!sortables.contains(propertyId)
  4100. && column.isSortable()) {
  4101. column.setSortable(false);
  4102. }
  4103. }
  4104. }
  4105. }
  4106. };
  4107. private final ItemSetChangeListener editorClosingItemSetListener = new ItemSetChangeListener() {
  4108. @Override
  4109. public void containerItemSetChange(ItemSetChangeEvent event) {
  4110. cancelEditor();
  4111. }
  4112. };
  4113. private RpcDataProviderExtension datasourceExtension;
  4114. /**
  4115. * The selection model that is currently in use. Never <code>null</code>
  4116. * after the constructor has been run.
  4117. */
  4118. private SelectionModel selectionModel;
  4119. /**
  4120. * Used to know whether selection change events originate from the server or
  4121. * the client so the selection change handler knows whether the changes
  4122. * should be sent to the client.
  4123. */
  4124. private boolean applyingSelectionFromClient;
  4125. private final Header header = new Header(this);
  4126. private final Footer footer = new Footer(this);
  4127. private Object editedItemId = null;
  4128. private boolean editorActive = false;
  4129. /**
  4130. * True while the editor is storing the field values, i.e. commiting the
  4131. * field group.
  4132. */
  4133. private boolean editorSaving = false;
  4134. private FieldGroup editorFieldGroup = new CustomFieldGroup();
  4135. /**
  4136. * Poperty ID to Field mapping that stores editor fields set by
  4137. * {@link #setEditorField(Object, Field)}.
  4138. */
  4139. private Map<Object, Field<?>> editorFields = new HashMap<Object, Field<?>>();
  4140. private CellStyleGenerator cellStyleGenerator;
  4141. private RowStyleGenerator rowStyleGenerator;
  4142. private CellDescriptionGenerator cellDescriptionGenerator;
  4143. private RowDescriptionGenerator rowDescriptionGenerator;
  4144. /**
  4145. * <code>true</code> if Grid is using the internal IndexedContainer created
  4146. * in Grid() constructor, or <code>false</code> if the user has set their
  4147. * own Container.
  4148. *
  4149. * @see #setContainerDataSource(Indexed)
  4150. * @see #Grid()
  4151. */
  4152. private boolean defaultContainer = true;
  4153. private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler();
  4154. private DetailComponentManager detailComponentManager = null;
  4155. private Set<Component> extensionComponents = new HashSet<Component>();
  4156. private static final Method SELECTION_CHANGE_METHOD = ReflectTools
  4157. .findMethod(SelectionListener.class, "select",
  4158. SelectionEvent.class);
  4159. private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools
  4160. .findMethod(SortListener.class, "sort", SortEvent.class);
  4161. private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod(
  4162. ColumnReorderListener.class, "columnReorder",
  4163. ColumnReorderEvent.class);
  4164. private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(
  4165. ColumnResizeListener.class, "columnResize",
  4166. ColumnResizeEvent.class);
  4167. private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools
  4168. .findMethod(ColumnVisibilityChangeListener.class,
  4169. "columnVisibilityChanged",
  4170. ColumnVisibilityChangeEvent.class);
  4171. /**
  4172. * Creates a new Grid with a new {@link IndexedContainer} as the data
  4173. * source.
  4174. */
  4175. public Grid() {
  4176. this(null, null);
  4177. }
  4178. /**
  4179. * Creates a new Grid using the given data source.
  4180. *
  4181. * @param dataSource
  4182. * the indexed container to use as a data source
  4183. */
  4184. public Grid(final Container.Indexed dataSource) {
  4185. this(null, dataSource);
  4186. }
  4187. /**
  4188. * Creates a new Grid with the given caption and a new
  4189. * {@link IndexedContainer} data source.
  4190. *
  4191. * @param caption
  4192. * the caption of the grid
  4193. */
  4194. public Grid(String caption) {
  4195. this(caption, null);
  4196. }
  4197. /**
  4198. * Creates a new Grid with the given caption and data source. If the data
  4199. * source is null, a new {@link IndexedContainer} will be used.
  4200. *
  4201. * @param caption
  4202. * the caption of the grid
  4203. * @param dataSource
  4204. * the indexed container to use as a data source
  4205. */
  4206. public Grid(String caption, Container.Indexed dataSource) {
  4207. if (dataSource == null) {
  4208. internalSetContainerDataSource(new IndexedContainer());
  4209. } else {
  4210. setContainerDataSource(dataSource);
  4211. }
  4212. setCaption(caption);
  4213. initGrid();
  4214. }
  4215. /**
  4216. * Grid initial setup
  4217. */
  4218. private void initGrid() {
  4219. setSelectionMode(getDefaultSelectionMode());
  4220. registerRpc(new GridServerRpc() {
  4221. @Override
  4222. public void sort(String[] columnIds, SortDirection[] directions,
  4223. boolean userOriginated) {
  4224. assert columnIds.length == directions.length;
  4225. List<SortOrder> order = new ArrayList<SortOrder>(
  4226. columnIds.length);
  4227. for (int i = 0; i < columnIds.length; i++) {
  4228. Object propertyId = getPropertyIdByColumnId(columnIds[i]);
  4229. order.add(new SortOrder(propertyId, directions[i]));
  4230. }
  4231. setSortOrder(order, userOriginated);
  4232. if (!order.equals(getSortOrder())) {
  4233. /*
  4234. * Actual sort order is not what the client expects. Make
  4235. * sure the client gets a state change event by clearing the
  4236. * diffstate and marking as dirty
  4237. */
  4238. ConnectorTracker connectorTracker = getUI()
  4239. .getConnectorTracker();
  4240. JsonObject diffState = connectorTracker
  4241. .getDiffState(Grid.this);
  4242. diffState.remove("sortColumns");
  4243. diffState.remove("sortDirs");
  4244. markAsDirty();
  4245. }
  4246. }
  4247. @Override
  4248. public void itemClick(String rowKey, String columnId,
  4249. MouseEventDetails details) {
  4250. Object itemId = getKeyMapper().get(rowKey);
  4251. Item item = datasource.getItem(itemId);
  4252. Object propertyId = getPropertyIdByColumnId(columnId);
  4253. fireEvent(new ItemClickEvent(Grid.this, item, itemId,
  4254. propertyId, details));
  4255. }
  4256. @Override
  4257. public void columnsReordered(List<String> newColumnOrder,
  4258. List<String> oldColumnOrder) {
  4259. final String diffStateKey = "columnOrder";
  4260. ConnectorTracker connectorTracker = getUI()
  4261. .getConnectorTracker();
  4262. JsonObject diffState = connectorTracker.getDiffState(Grid.this);
  4263. // discard the change if the columns have been reordered from
  4264. // the server side, as the server side is always right
  4265. if (getState(false).columnOrder.equals(oldColumnOrder)) {
  4266. // Don't mark as dirty since client has the state already
  4267. getState(false).columnOrder = newColumnOrder;
  4268. // write changes to diffState so that possible reverting the
  4269. // column order is sent to client
  4270. assert diffState
  4271. .hasKey(diffStateKey) : "Field name has changed";
  4272. Type type = null;
  4273. try {
  4274. type = (getState(false).getClass()
  4275. .getField(diffStateKey).getGenericType());
  4276. } catch (NoSuchFieldException e) {
  4277. e.printStackTrace();
  4278. } catch (SecurityException e) {
  4279. e.printStackTrace();
  4280. }
  4281. EncodeResult encodeResult = JsonCodec.encode(
  4282. getState(false).columnOrder, diffState, type,
  4283. connectorTracker);
  4284. diffState.put(diffStateKey, encodeResult.getEncodedValue());
  4285. fireColumnReorderEvent(true);
  4286. } else {
  4287. // make sure the client is reverted to the order that the
  4288. // server thinks it is
  4289. diffState.remove(diffStateKey);
  4290. markAsDirty();
  4291. }
  4292. }
  4293. @Override
  4294. public void columnVisibilityChanged(String id, boolean hidden,
  4295. boolean userOriginated) {
  4296. final Column column = getColumnByColumnId(id);
  4297. final GridColumnState columnState = column.getState();
  4298. if (columnState.hidden != hidden) {
  4299. columnState.hidden = hidden;
  4300. final String diffStateKey = "columns";
  4301. ConnectorTracker connectorTracker = getUI()
  4302. .getConnectorTracker();
  4303. JsonObject diffState = connectorTracker
  4304. .getDiffState(Grid.this);
  4305. assert diffState
  4306. .hasKey(diffStateKey) : "Field name has changed";
  4307. Type type = null;
  4308. try {
  4309. type = (getState(false).getClass()
  4310. .getField(diffStateKey).getGenericType());
  4311. } catch (NoSuchFieldException e) {
  4312. e.printStackTrace();
  4313. } catch (SecurityException e) {
  4314. e.printStackTrace();
  4315. }
  4316. EncodeResult encodeResult = JsonCodec.encode(
  4317. getState(false).columns, diffState, type,
  4318. connectorTracker);
  4319. diffState.put(diffStateKey, encodeResult.getEncodedValue());
  4320. fireColumnVisibilityChangeEvent(column, hidden,
  4321. userOriginated);
  4322. }
  4323. }
  4324. @Override
  4325. public void contextClick(int rowIndex, String rowKey,
  4326. String columnId, Section section,
  4327. MouseEventDetails details) {
  4328. Object itemId = null;
  4329. if (rowKey != null) {
  4330. itemId = getKeyMapper().get(rowKey);
  4331. }
  4332. fireEvent(new GridContextClickEvent(Grid.this, details, section,
  4333. rowIndex, itemId, getPropertyIdByColumnId(columnId)));
  4334. }
  4335. @Override
  4336. public void columnResized(String id, double pixels) {
  4337. final Column column = getColumnByColumnId(id);
  4338. if (column != null && column.isResizable()) {
  4339. column.getState().width = pixels;
  4340. fireColumnResizeEvent(column, true);
  4341. }
  4342. }
  4343. });
  4344. registerRpc(new EditorServerRpc() {
  4345. @Override
  4346. public void bind(int rowIndex) {
  4347. try {
  4348. Object id = getContainerDataSource().getIdByIndex(rowIndex);
  4349. final boolean opening = editedItemId == null;
  4350. final boolean moving = !opening && !editedItemId.equals(id);
  4351. final boolean allowMove = !isEditorBuffered()
  4352. && getEditorFieldGroup().isValid();
  4353. if (opening || !moving || allowMove) {
  4354. doBind(id);
  4355. } else {
  4356. failBind(null);
  4357. }
  4358. } catch (Exception e) {
  4359. failBind(e);
  4360. }
  4361. }
  4362. private void doBind(Object id) {
  4363. editedItemId = id;
  4364. doEditItem();
  4365. getEditorRpc().confirmBind(true);
  4366. }
  4367. private void failBind(Exception e) {
  4368. if (e != null) {
  4369. handleError(e);
  4370. }
  4371. getEditorRpc().confirmBind(false);
  4372. }
  4373. @Override
  4374. public void cancel(int rowIndex) {
  4375. try {
  4376. // For future proofing even though cannot currently fail
  4377. doCancelEditor();
  4378. } catch (Exception e) {
  4379. handleError(e);
  4380. }
  4381. }
  4382. @Override
  4383. public void save(int rowIndex) {
  4384. List<String> errorColumnIds = null;
  4385. String errorMessage = null;
  4386. boolean success = false;
  4387. try {
  4388. saveEditor();
  4389. success = true;
  4390. } catch (CommitException e) {
  4391. try {
  4392. CommitErrorEvent event = new CommitErrorEvent(Grid.this,
  4393. e);
  4394. getEditorErrorHandler().commitError(event);
  4395. errorMessage = event.getUserErrorMessage();
  4396. errorColumnIds = new ArrayList<String>();
  4397. for (Column column : event.getErrorColumns()) {
  4398. errorColumnIds.add(column.state.id);
  4399. }
  4400. } catch (Exception ee) {
  4401. // A badly written error handler can throw an exception,
  4402. // which would lock up the Grid
  4403. handleError(ee);
  4404. }
  4405. } catch (Exception e) {
  4406. handleError(e);
  4407. }
  4408. getEditorRpc().confirmSave(success, errorMessage,
  4409. errorColumnIds);
  4410. }
  4411. private void handleError(Exception e) {
  4412. com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this)
  4413. .error(new ConnectorErrorEvent(Grid.this, e));
  4414. }
  4415. });
  4416. }
  4417. @Override
  4418. public void beforeClientResponse(boolean initial) {
  4419. try {
  4420. header.sanityCheck();
  4421. footer.sanityCheck();
  4422. } catch (Exception e) {
  4423. e.printStackTrace();
  4424. setComponentError(new ErrorMessage() {
  4425. @Override
  4426. public ErrorLevel getErrorLevel() {
  4427. return ErrorLevel.CRITICAL;
  4428. }
  4429. @Override
  4430. public String getFormattedHtmlMessage() {
  4431. return "Incorrectly merged cells";
  4432. }
  4433. });
  4434. }
  4435. super.beforeClientResponse(initial);
  4436. }
  4437. /**
  4438. * Sets the grid data source.
  4439. * <p>
  4440. *
  4441. * <strong>Note</strong> Grid columns are based on properties and try to
  4442. * detect a correct converter for the data type. The columns are not
  4443. * reinitialized automatically if the container is changed, and if the same
  4444. * properties are present after container change, the columns are reused.
  4445. * Properties with same names, but different data types will lead to
  4446. * unpredictable behaviour.
  4447. *
  4448. * @param container
  4449. * The container data source. Cannot be null.
  4450. * @throws IllegalArgumentException
  4451. * if the data source is null
  4452. */
  4453. public void setContainerDataSource(Container.Indexed container) {
  4454. defaultContainer = false;
  4455. internalSetContainerDataSource(container);
  4456. }
  4457. private void internalSetContainerDataSource(Container.Indexed container) {
  4458. if (container == null) {
  4459. throw new IllegalArgumentException(
  4460. "Cannot set the datasource to null");
  4461. }
  4462. if (datasource == container) {
  4463. return;
  4464. }
  4465. // Remove old listeners
  4466. if (datasource instanceof PropertySetChangeNotifier) {
  4467. ((PropertySetChangeNotifier) datasource)
  4468. .removePropertySetChangeListener(propertyListener);
  4469. }
  4470. if (datasourceExtension != null) {
  4471. removeExtension(datasourceExtension);
  4472. }
  4473. // Remove old DetailComponentManager
  4474. if (detailComponentManager != null) {
  4475. detailComponentManager.remove();
  4476. }
  4477. resetEditor();
  4478. datasource = container;
  4479. //
  4480. // Adjust sort order
  4481. //
  4482. if (container instanceof Container.Sortable) {
  4483. // If the container is sortable, go through the current sort order
  4484. // and match each item to the sortable properties of the new
  4485. // container. If the new container does not support an item in the
  4486. // current sort order, that item is removed from the current sort
  4487. // order list.
  4488. Collection<?> sortableProps = ((Container.Sortable) getContainerDataSource())
  4489. .getSortableContainerPropertyIds();
  4490. Iterator<SortOrder> i = sortOrder.iterator();
  4491. while (i.hasNext()) {
  4492. if (!sortableProps.contains(i.next().getPropertyId())) {
  4493. i.remove();
  4494. }
  4495. }
  4496. sort(false);
  4497. } else {
  4498. // Clear sorting order. Don't sort.
  4499. sortOrder.clear();
  4500. }
  4501. datasourceExtension = new RpcDataProviderExtension(container);
  4502. datasourceExtension.extend(this);
  4503. datasourceExtension.addDataGenerator(new RowDataGenerator());
  4504. for (Extension e : getExtensions()) {
  4505. if (e instanceof DataGenerator) {
  4506. datasourceExtension.addDataGenerator((DataGenerator) e);
  4507. }
  4508. }
  4509. if (detailComponentManager != null) {
  4510. detailComponentManager = new DetailComponentManager(this,
  4511. detailComponentManager.getDetailsGenerator());
  4512. } else {
  4513. detailComponentManager = new DetailComponentManager(this);
  4514. }
  4515. /*
  4516. * selectionModel == null when the invocation comes from the
  4517. * constructor.
  4518. */
  4519. if (selectionModel != null) {
  4520. selectionModel.reset();
  4521. }
  4522. // Listen to changes in properties and remove columns if needed
  4523. if (datasource instanceof PropertySetChangeNotifier) {
  4524. ((PropertySetChangeNotifier) datasource)
  4525. .addPropertySetChangeListener(propertyListener);
  4526. }
  4527. /*
  4528. * activeRowHandler will be updated by the client-side request that
  4529. * occurs on container change - no need to actively re-insert any
  4530. * ValueChangeListeners at this point.
  4531. */
  4532. setFrozenColumnCount(0);
  4533. if (columns.isEmpty()) {
  4534. // Add columns
  4535. for (Object propertyId : datasource.getContainerPropertyIds()) {
  4536. Column column = appendColumn(propertyId);
  4537. // Initial sorting is defined by container
  4538. if (datasource instanceof Sortable) {
  4539. column.setSortable(((Sortable) datasource)
  4540. .getSortableContainerPropertyIds()
  4541. .contains(propertyId));
  4542. } else {
  4543. column.setSortable(false);
  4544. }
  4545. }
  4546. } else {
  4547. Collection<?> properties = datasource.getContainerPropertyIds();
  4548. for (Object property : columns.keySet()) {
  4549. if (!properties.contains(property)) {
  4550. throw new IllegalStateException(
  4551. "Found at least one column in Grid that does not exist in the given container: "
  4552. + property + " with the header \""
  4553. + getColumn(property).getHeaderCaption()
  4554. + "\". "
  4555. + "Call removeAllColumns() before setContainerDataSource() if you want to reconfigure the columns based on the new container.");
  4556. }
  4557. if (!(datasource instanceof Sortable)
  4558. || !((Sortable) datasource)
  4559. .getSortableContainerPropertyIds()
  4560. .contains(property)) {
  4561. columns.get(property).setSortable(false);
  4562. }
  4563. }
  4564. }
  4565. }
  4566. /**
  4567. * Returns the grid data source.
  4568. *
  4569. * @return the container data source of the grid
  4570. */
  4571. public Container.Indexed getContainerDataSource() {
  4572. return datasource;
  4573. }
  4574. /**
  4575. * Returns a column based on the property id
  4576. *
  4577. * @param propertyId
  4578. * the property id of the column
  4579. * @return the column or <code>null</code> if not found
  4580. */
  4581. public Column getColumn(Object propertyId) {
  4582. return columns.get(propertyId);
  4583. }
  4584. /**
  4585. * Returns a copy of currently configures columns in their current visual
  4586. * order in this Grid.
  4587. *
  4588. * @return unmodifiable copy of current columns in visual order
  4589. */
  4590. public List<Column> getColumns() {
  4591. List<Column> columns = new ArrayList<Grid.Column>();
  4592. for (String columnId : getState(false).columnOrder) {
  4593. columns.add(getColumnByColumnId(columnId));
  4594. }
  4595. return Collections.unmodifiableList(columns);
  4596. }
  4597. /**
  4598. * Adds a new Column to Grid. Also adds the property to container with data
  4599. * type String, if property for column does not exist in it. Default value
  4600. * for the new property is an empty String.
  4601. * <p>
  4602. * Note that adding a new property is only done for the default container
  4603. * that Grid sets up with the default constructor.
  4604. *
  4605. * @param propertyId
  4606. * the property id of the new column
  4607. * @return the new column
  4608. *
  4609. * @throws IllegalStateException
  4610. * if column for given property already exists in this grid
  4611. */
  4612. public Column addColumn(Object propertyId) throws IllegalStateException {
  4613. if (datasource.getContainerPropertyIds().contains(propertyId)
  4614. && !columns.containsKey(propertyId)) {
  4615. appendColumn(propertyId);
  4616. } else if (defaultContainer) {
  4617. addColumnProperty(propertyId, String.class, "");
  4618. } else {
  4619. if (columns.containsKey(propertyId)) {
  4620. throw new IllegalStateException(
  4621. "A column for property id '" + propertyId.toString()
  4622. + "' already exists in this grid");
  4623. } else {
  4624. throw new IllegalStateException(
  4625. "Property id '" + propertyId.toString()
  4626. + "' does not exist in the container");
  4627. }
  4628. }
  4629. // Inform the data provider of this new column.
  4630. Column column = getColumn(propertyId);
  4631. List<Column> addedColumns = new ArrayList<Column>();
  4632. addedColumns.add(column);
  4633. datasourceExtension.columnsAdded(addedColumns);
  4634. return column;
  4635. }
  4636. /**
  4637. * Adds a new Column to Grid. This function makes sure that the property
  4638. * with the given id and data type exists in the container. If property does
  4639. * not exists, it will be created.
  4640. * <p>
  4641. * Default value for the new property is 0 if type is Integer, Double and
  4642. * Float. If type is String, default value is an empty string. For all other
  4643. * types the default value is null.
  4644. * <p>
  4645. * Note that adding a new property is only done for the default container
  4646. * that Grid sets up with the default constructor.
  4647. *
  4648. * @param propertyId
  4649. * the property id of the new column
  4650. * @param type
  4651. * the data type for the new property
  4652. * @return the new column
  4653. *
  4654. * @throws IllegalStateException
  4655. * if column for given property already exists in this grid or
  4656. * property already exists in the container with wrong type
  4657. */
  4658. public Column addColumn(Object propertyId, Class<?> type) {
  4659. addColumnProperty(propertyId, type, null);
  4660. return getColumn(propertyId);
  4661. }
  4662. protected void addColumnProperty(Object propertyId, Class<?> type,
  4663. Object defaultValue) throws IllegalStateException {
  4664. if (!defaultContainer) {
  4665. throw new IllegalStateException(
  4666. "Container for this Grid is not a default container from Grid() constructor");
  4667. }
  4668. if (!columns.containsKey(propertyId)) {
  4669. if (!datasource.getContainerPropertyIds().contains(propertyId)) {
  4670. datasource.addContainerProperty(propertyId, type, defaultValue);
  4671. } else {
  4672. Property<?> containerProperty = datasource.getContainerProperty(
  4673. datasource.firstItemId(), propertyId);
  4674. if (containerProperty.getType() == type) {
  4675. appendColumn(propertyId);
  4676. } else {
  4677. throw new IllegalStateException(
  4678. "DataSource already has the given property "
  4679. + propertyId + " with a different type");
  4680. }
  4681. }
  4682. } else {
  4683. throw new IllegalStateException(
  4684. "Grid already has a column for property " + propertyId);
  4685. }
  4686. }
  4687. /**
  4688. * Removes all columns from this Grid.
  4689. */
  4690. public void removeAllColumns() {
  4691. List<Column> removed = new ArrayList<Column>(columns.values());
  4692. Set<Object> properties = new HashSet<Object>(columns.keySet());
  4693. for (Object propertyId : properties) {
  4694. removeColumn(propertyId);
  4695. }
  4696. datasourceExtension.columnsRemoved(removed);
  4697. }
  4698. /**
  4699. * Used internally by the {@link Grid} to get a {@link Column} by
  4700. * referencing its generated state id. Also used by {@link Column} to verify
  4701. * if it has been detached from the {@link Grid}.
  4702. *
  4703. * @param columnId
  4704. * the client id generated for the column when the column is
  4705. * added to the grid
  4706. * @return the column with the id or <code>null</code> if not found
  4707. */
  4708. Column getColumnByColumnId(String columnId) {
  4709. Object propertyId = getPropertyIdByColumnId(columnId);
  4710. return getColumn(propertyId);
  4711. }
  4712. /**
  4713. * Used internally by the {@link Grid} to get a property id by referencing
  4714. * the columns generated state id.
  4715. *
  4716. * @param columnId
  4717. * The state id of the column
  4718. * @return The column instance or null if not found
  4719. */
  4720. Object getPropertyIdByColumnId(String columnId) {
  4721. return columnKeys.get(columnId);
  4722. }
  4723. /**
  4724. * Returns whether column reordering is allowed. Default value is
  4725. * <code>false</code>.
  4726. *
  4727. * @since 7.5.0
  4728. * @return true if reordering is allowed
  4729. */
  4730. public boolean isColumnReorderingAllowed() {
  4731. return getState(false).columnReorderingAllowed;
  4732. }
  4733. /**
  4734. * Sets whether or not column reordering is allowed. Default value is
  4735. * <code>false</code>.
  4736. *
  4737. * @since 7.5.0
  4738. * @param columnReorderingAllowed
  4739. * specifies whether column reordering is allowed
  4740. */
  4741. public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
  4742. if (isColumnReorderingAllowed() != columnReorderingAllowed) {
  4743. getState().columnReorderingAllowed = columnReorderingAllowed;
  4744. }
  4745. }
  4746. @Override
  4747. protected GridState getState() {
  4748. return (GridState) super.getState();
  4749. }
  4750. @Override
  4751. protected GridState getState(boolean markAsDirty) {
  4752. return (GridState) super.getState(markAsDirty);
  4753. }
  4754. /**
  4755. * Sets the column resize mode to use. The default mode is
  4756. * {@link ColumnResizeMode#ANIMATED}.
  4757. *
  4758. * @param mode
  4759. * a ColumnResizeMode value
  4760. *
  4761. * @since 7.7.5
  4762. */
  4763. public void setColumnResizeMode(ColumnResizeMode mode) {
  4764. getState().columnResizeMode = mode;
  4765. }
  4766. /**
  4767. * Returns the current column resize mode. The default mode is
  4768. * {@link ColumnResizeMode#ANIMATED}.
  4769. *
  4770. * @return a ColumnResizeMode value
  4771. *
  4772. * @since 7.7.5
  4773. */
  4774. public ColumnResizeMode getColumnResizeMode() {
  4775. return getState(false).columnResizeMode;
  4776. }
  4777. /**
  4778. * Creates a new column based on a property id and appends it as the last
  4779. * column.
  4780. *
  4781. * @param datasourcePropertyId
  4782. * The property id of a property in the datasource
  4783. */
  4784. private Column appendColumn(Object datasourcePropertyId) {
  4785. if (datasourcePropertyId == null) {
  4786. throw new IllegalArgumentException("Property id cannot be null");
  4787. }
  4788. assert datasource.getContainerPropertyIds().contains(
  4789. datasourcePropertyId) : "Datasource should contain the property id";
  4790. GridColumnState columnState = new GridColumnState();
  4791. columnState.id = columnKeys.key(datasourcePropertyId);
  4792. Column column = new Column(this, columnState, datasourcePropertyId);
  4793. columns.put(datasourcePropertyId, column);
  4794. getState().columns.add(columnState);
  4795. getState().columnOrder.add(columnState.id);
  4796. header.addColumn(datasourcePropertyId);
  4797. footer.addColumn(datasourcePropertyId);
  4798. String humanFriendlyPropertyId = SharedUtil.propertyIdToHumanFriendly(
  4799. String.valueOf(datasourcePropertyId));
  4800. column.setHeaderCaption(humanFriendlyPropertyId);
  4801. if (datasource instanceof Sortable
  4802. && ((Sortable) datasource).getSortableContainerPropertyIds()
  4803. .contains(datasourcePropertyId)) {
  4804. column.setSortable(true);
  4805. }
  4806. return column;
  4807. }
  4808. /**
  4809. * Removes a column from Grid based on a property id.
  4810. *
  4811. * @param propertyId
  4812. * The property id of column to be removed
  4813. *
  4814. * @throws IllegalArgumentException
  4815. * if there is no column for given property id in this grid
  4816. */
  4817. public void removeColumn(Object propertyId)
  4818. throws IllegalArgumentException {
  4819. if (!columns.keySet().contains(propertyId)) {
  4820. throw new IllegalArgumentException(
  4821. "There is no column for given property id " + propertyId);
  4822. }
  4823. List<Column> removed = new ArrayList<Column>();
  4824. removed.add(getColumn(propertyId));
  4825. internalRemoveColumn(propertyId);
  4826. datasourceExtension.columnsRemoved(removed);
  4827. }
  4828. private void internalRemoveColumn(Object propertyId) {
  4829. setEditorField(propertyId, null);
  4830. header.removeColumn(propertyId);
  4831. footer.removeColumn(propertyId);
  4832. Column column = columns.remove(propertyId);
  4833. getState().columnOrder.remove(columnKeys.key(propertyId));
  4834. getState().columns.remove(column.getState());
  4835. removeExtension(column.getRenderer());
  4836. }
  4837. /**
  4838. * Sets the columns and their order for the grid. Current columns whose
  4839. * property id is not in propertyIds are removed. Similarly, a column is
  4840. * added for any property id in propertyIds that has no corresponding column
  4841. * in this Grid.
  4842. *
  4843. * @since 7.5.0
  4844. *
  4845. * @param propertyIds
  4846. * properties in the desired column order
  4847. */
  4848. public void setColumns(Object... propertyIds) {
  4849. if (SharedUtil.containsDuplicates(propertyIds)) {
  4850. throw new IllegalArgumentException(
  4851. "The propertyIds array contains duplicates: "
  4852. + SharedUtil.getDuplicates(propertyIds));
  4853. }
  4854. Set<?> removePids = new HashSet<Object>(columns.keySet());
  4855. removePids.removeAll(Arrays.asList(propertyIds));
  4856. for (Object removePid : removePids) {
  4857. removeColumn(removePid);
  4858. }
  4859. Set<?> addPids = new HashSet<Object>(Arrays.asList(propertyIds));
  4860. addPids.removeAll(columns.keySet());
  4861. for (Object propertyId : addPids) {
  4862. addColumn(propertyId);
  4863. }
  4864. setColumnOrder(propertyIds);
  4865. }
  4866. /**
  4867. * Sets a new column order for the grid. All columns which are not ordered
  4868. * here will remain in the order they were before as the last columns of
  4869. * grid.
  4870. *
  4871. * @param propertyIds
  4872. * properties in the order columns should be
  4873. */
  4874. public void setColumnOrder(Object... propertyIds) {
  4875. if (SharedUtil.containsDuplicates(propertyIds)) {
  4876. throw new IllegalArgumentException(
  4877. "The propertyIds array contains duplicates: "
  4878. + SharedUtil.getDuplicates(propertyIds));
  4879. }
  4880. List<String> columnOrder = new ArrayList<String>();
  4881. for (Object propertyId : propertyIds) {
  4882. if (columns.containsKey(propertyId)) {
  4883. columnOrder.add(columnKeys.key(propertyId));
  4884. } else {
  4885. throw new IllegalArgumentException(
  4886. "Grid does not contain column for property "
  4887. + String.valueOf(propertyId));
  4888. }
  4889. }
  4890. List<String> stateColumnOrder = getState().columnOrder;
  4891. if (stateColumnOrder.size() != columnOrder.size()) {
  4892. stateColumnOrder.removeAll(columnOrder);
  4893. columnOrder.addAll(stateColumnOrder);
  4894. }
  4895. getState().columnOrder = columnOrder;
  4896. fireColumnReorderEvent(false);
  4897. }
  4898. /**
  4899. * Sets the number of frozen columns in this grid. Setting the count to 0
  4900. * means that no data columns will be frozen, but the built-in selection
  4901. * checkbox column will still be frozen if it's in use. Setting the count to
  4902. * -1 will also disable the selection column.
  4903. * <p>
  4904. * The default value is 0.
  4905. *
  4906. * @param numberOfColumns
  4907. * the number of columns that should be frozen
  4908. *
  4909. * @throws IllegalArgumentException
  4910. * if the column count is < 0 or > the number of visible columns
  4911. */
  4912. public void setFrozenColumnCount(int numberOfColumns) {
  4913. if (numberOfColumns < -1 || numberOfColumns > columns.size()) {
  4914. throw new IllegalArgumentException(
  4915. "count must be between -1 and the current number of columns ("
  4916. + columns.size() + "): " + numberOfColumns);
  4917. }
  4918. getState().frozenColumnCount = numberOfColumns;
  4919. }
  4920. /**
  4921. * Gets the number of frozen columns in this grid. 0 means that no data
  4922. * columns will be frozen, but the built-in selection checkbox column will
  4923. * still be frozen if it's in use. -1 means that not even the selection
  4924. * column is frozen.
  4925. * <p>
  4926. * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
  4927. * columns} in the count.
  4928. *
  4929. * @see #setFrozenColumnCount(int)
  4930. *
  4931. * @return the number of frozen columns
  4932. */
  4933. public int getFrozenColumnCount() {
  4934. return getState(false).frozenColumnCount;
  4935. }
  4936. /**
  4937. * Scrolls to a certain item, using {@link ScrollDestination#ANY}.
  4938. * <p>
  4939. * If the item has visible details, its size will also be taken into
  4940. * account.
  4941. *
  4942. * @param itemId
  4943. * id of item to scroll to.
  4944. * @throws IllegalArgumentException
  4945. * if the provided id is not recognized by the data source.
  4946. */
  4947. public void scrollTo(Object itemId) throws IllegalArgumentException {
  4948. scrollTo(itemId, ScrollDestination.ANY);
  4949. }
  4950. /**
  4951. * Scrolls to a certain item, using user-specified scroll destination.
  4952. * <p>
  4953. * If the item has visible details, its size will also be taken into
  4954. * account.
  4955. *
  4956. * @param itemId
  4957. * id of item to scroll to.
  4958. * @param destination
  4959. * value specifying desired position of scrolled-to row.
  4960. * @throws IllegalArgumentException
  4961. * if the provided id is not recognized by the data source.
  4962. */
  4963. public void scrollTo(Object itemId, ScrollDestination destination)
  4964. throws IllegalArgumentException {
  4965. int row = datasource.indexOfId(itemId);
  4966. if (row == -1) {
  4967. throw new IllegalArgumentException(
  4968. "Item with specified ID does not exist in data source");
  4969. }
  4970. GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
  4971. clientRPC.scrollToRow(row, destination);
  4972. }
  4973. /**
  4974. * Scrolls to the beginning of the first data row.
  4975. */
  4976. public void scrollToStart() {
  4977. GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
  4978. clientRPC.scrollToStart();
  4979. }
  4980. /**
  4981. * Scrolls to the end of the last data row.
  4982. */
  4983. public void scrollToEnd() {
  4984. GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
  4985. clientRPC.scrollToEnd();
  4986. }
  4987. /**
  4988. * Sets the number of rows that should be visible in Grid's body, while
  4989. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  4990. * <p>
  4991. * If Grid is currently not in {@link HeightMode#ROW}, the given value is
  4992. * remembered, and applied once the mode is applied.
  4993. *
  4994. * @param rows
  4995. * The height in terms of number of rows displayed in Grid's
  4996. * body. If Grid doesn't contain enough rows, white space is
  4997. * displayed instead. If <code>null</code> is given, then Grid's
  4998. * height is undefined
  4999. * @throws IllegalArgumentException
  5000. * if {@code rows} is zero or less
  5001. * @throws IllegalArgumentException
  5002. * if {@code rows} is {@link Double#isInfinite(double) infinite}
  5003. * @throws IllegalArgumentException
  5004. * if {@code rows} is {@link Double#isNaN(double) NaN}
  5005. */
  5006. public void setHeightByRows(double rows) {
  5007. if (rows <= 0.0d) {
  5008. throw new IllegalArgumentException(
  5009. "More than zero rows must be shown.");
  5010. } else if (Double.isInfinite(rows)) {
  5011. throw new IllegalArgumentException(
  5012. "Grid doesn't support infinite heights");
  5013. } else if (Double.isNaN(rows)) {
  5014. throw new IllegalArgumentException("NaN is not a valid row count");
  5015. }
  5016. getState().heightByRows = rows;
  5017. }
  5018. /**
  5019. * Gets the amount of rows in Grid's body that are shown, while
  5020. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  5021. *
  5022. * @return the amount of rows that are being shown in Grid's body
  5023. * @see #setHeightByRows(double)
  5024. */
  5025. public double getHeightByRows() {
  5026. return getState(false).heightByRows;
  5027. }
  5028. /**
  5029. * {@inheritDoc}
  5030. * <p>
  5031. * <em>Note:</em> This method will change the widget's size in the browser
  5032. * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
  5033. *
  5034. * @see #setHeightMode(HeightMode)
  5035. */
  5036. @Override
  5037. public void setHeight(float height, Unit unit) {
  5038. super.setHeight(height, unit);
  5039. }
  5040. /**
  5041. * Defines the mode in which the Grid widget's height is calculated.
  5042. * <p>
  5043. * If {@link HeightMode#CSS} is given, Grid will respect the values given
  5044. * via a {@code setHeight}-method, and behave as a traditional Component.
  5045. * <p>
  5046. * If {@link HeightMode#ROW} is given, Grid will make sure that the body
  5047. * will display as many rows as {@link #getHeightByRows()} defines.
  5048. * <em>Note:</em> If headers/footers are inserted or removed, the widget
  5049. * will resize itself to still display the required amount of rows in its
  5050. * body. It also takes the horizontal scrollbar into account.
  5051. *
  5052. * @param heightMode
  5053. * the mode in to which Grid should be set
  5054. */
  5055. public void setHeightMode(HeightMode heightMode) {
  5056. /*
  5057. * This method is a workaround for the fact that Vaadin re-applies
  5058. * widget dimensions (height/width) on each state change event. The
  5059. * original design was to have setHeight and setHeightByRow be equals,
  5060. * and whichever was called the latest was considered in effect.
  5061. *
  5062. * But, because of Vaadin always calling setHeight on the widget, this
  5063. * approach doesn't work.
  5064. */
  5065. getState().heightMode = heightMode;
  5066. }
  5067. /**
  5068. * Returns the current {@link HeightMode} the Grid is in.
  5069. * <p>
  5070. * Defaults to {@link HeightMode#CSS}.
  5071. *
  5072. * @return the current HeightMode
  5073. */
  5074. public HeightMode getHeightMode() {
  5075. return getState(false).heightMode;
  5076. }
  5077. /* Selection related methods: */
  5078. /**
  5079. * Takes a new {@link SelectionModel} into use.
  5080. * <p>
  5081. * The SelectionModel that is previously in use will have all its items
  5082. * deselected. If any items were selected, this will fire a
  5083. * {@link SelectionEvent}.
  5084. * <p>
  5085. * If the given SelectionModel is already in use, this method does nothing.
  5086. *
  5087. * @param selectionModel
  5088. * the new SelectionModel to use
  5089. * @throws IllegalArgumentException
  5090. * if {@code selectionModel} is <code>null</code>
  5091. */
  5092. public void setSelectionModel(SelectionModel selectionModel)
  5093. throws IllegalArgumentException {
  5094. if (selectionModel == null) {
  5095. throw new IllegalArgumentException(
  5096. "Selection model may not be null");
  5097. }
  5098. if (this.selectionModel != selectionModel) {
  5099. Collection<Object> oldSelection;
  5100. // this.selectionModel is null on init
  5101. if (this.selectionModel != null) {
  5102. oldSelection = this.selectionModel.getSelectedRows();
  5103. this.selectionModel.remove();
  5104. } else {
  5105. oldSelection = Collections.emptyList();
  5106. }
  5107. this.selectionModel = selectionModel;
  5108. selectionModel.setGrid(this);
  5109. Collection<Object> newSelection = this.selectionModel
  5110. .getSelectedRows();
  5111. if (!SharedUtil.equals(oldSelection, newSelection)) {
  5112. fireSelectionEvent(oldSelection, newSelection);
  5113. }
  5114. // selection is included in the row data, so the client needs to be
  5115. // updated
  5116. datasourceExtension.refreshCache();
  5117. }
  5118. }
  5119. /**
  5120. * Returns the currently used {@link SelectionModel}.
  5121. *
  5122. * @return the currently used SelectionModel
  5123. */
  5124. public SelectionModel getSelectionModel() {
  5125. return selectionModel;
  5126. }
  5127. /**
  5128. * Sets the Grid's selection mode.
  5129. * <p>
  5130. * Grid supports three selection modes: multiselect, single select and no
  5131. * selection, and this is a convenience method for choosing between one of
  5132. * them.
  5133. * <p>
  5134. * Technically, this method is a shortcut that can be used instead of
  5135. * calling {@code setSelectionModel} with a specific SelectionModel
  5136. * instance. Grid comes with three built-in SelectionModel classes, and the
  5137. * {@link SelectionMode} enum represents each of them.
  5138. * <p>
  5139. * Essentially, the two following method calls are equivalent:
  5140. * <p>
  5141. * <code><pre>
  5142. * grid.setSelectionMode(SelectionMode.MULTI);
  5143. * grid.setSelectionModel(new MultiSelectionMode());
  5144. * </pre></code>
  5145. *
  5146. *
  5147. * @param selectionMode
  5148. * the selection mode to switch to
  5149. * @return The {@link SelectionModel} instance that was taken into use
  5150. * @throws IllegalArgumentException
  5151. * if {@code selectionMode} is <code>null</code>
  5152. * @see SelectionModel
  5153. */
  5154. public SelectionModel setSelectionMode(final SelectionMode selectionMode)
  5155. throws IllegalArgumentException {
  5156. if (selectionMode == null) {
  5157. throw new IllegalArgumentException(
  5158. "selection mode may not be null");
  5159. }
  5160. final SelectionModel newSelectionModel = selectionMode.createModel();
  5161. setSelectionModel(newSelectionModel);
  5162. return newSelectionModel;
  5163. }
  5164. /**
  5165. * Checks whether an item is selected or not.
  5166. *
  5167. * @param itemId
  5168. * the item id to check for
  5169. * @return <code>true</code> iff the item is selected
  5170. */
  5171. // keep this javadoc in sync with SelectionModel.isSelected
  5172. public boolean isSelected(Object itemId) {
  5173. return selectionModel.isSelected(itemId);
  5174. }
  5175. /**
  5176. * Returns a collection of all the currently selected itemIds.
  5177. * <p>
  5178. * This method is a shorthand that delegates to the
  5179. * {@link #getSelectionModel() selection model}.
  5180. *
  5181. * @return a collection of all the currently selected itemIds
  5182. */
  5183. // keep this javadoc in sync with SelectionModel.getSelectedRows
  5184. public Collection<Object> getSelectedRows() {
  5185. return getSelectionModel().getSelectedRows();
  5186. }
  5187. /**
  5188. * Gets the item id of the currently selected item.
  5189. * <p>
  5190. * This method is a shorthand that delegates to the
  5191. * {@link #getSelectionModel() selection model}. Only
  5192. * {@link SelectionModel.Single} is supported.
  5193. *
  5194. * @return the item id of the currently selected item, or <code>null</code>
  5195. * if nothing is selected
  5196. * @throws IllegalStateException
  5197. * if the selection model does not implement
  5198. * {@code SelectionModel.Single}
  5199. */
  5200. // keep this javadoc in sync with SelectionModel.Single.getSelectedRow
  5201. public Object getSelectedRow() throws IllegalStateException {
  5202. if (selectionModel instanceof SelectionModel.Single) {
  5203. return ((SelectionModel.Single) selectionModel).getSelectedRow();
  5204. } else if (selectionModel instanceof SelectionModel.Multi) {
  5205. throw new IllegalStateException("Cannot get unique selected row: "
  5206. + "Grid is in multiselect mode "
  5207. + "(the current selection model is "
  5208. + selectionModel.getClass().getName() + ").");
  5209. } else if (selectionModel instanceof SelectionModel.None) {
  5210. throw new IllegalStateException(
  5211. "Cannot get selected row: " + "Grid selection is disabled "
  5212. + "(the current selection model is "
  5213. + selectionModel.getClass().getName() + ").");
  5214. } else {
  5215. throw new IllegalStateException("Cannot get selected row: "
  5216. + "Grid selection model does not implement "
  5217. + SelectionModel.Single.class.getName() + " or "
  5218. + SelectionModel.Multi.class.getName()
  5219. + "(the current model is "
  5220. + selectionModel.getClass().getName() + ").");
  5221. }
  5222. }
  5223. /**
  5224. * Marks an item as selected.
  5225. * <p>
  5226. * This method is a shorthand that delegates to the
  5227. * {@link #getSelectionModel() selection model}. Only
  5228. * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
  5229. * supported.
  5230. *
  5231. * @param itemId
  5232. * the itemId to mark as selected
  5233. * @return <code>true</code> if the selection state changed,
  5234. * <code>false</code> if the itemId already was selected
  5235. * @throws IllegalArgumentException
  5236. * if the {@code itemId} doesn't exist in the currently active
  5237. * Container
  5238. * @throws IllegalStateException
  5239. * if the selection was illegal. One such reason might be that
  5240. * the implementation already had an item selected, and that
  5241. * needs to be explicitly deselected before re-selecting
  5242. * something.
  5243. * @throws IllegalStateException
  5244. * if the selection model does not implement
  5245. * {@code SelectionModel.Single} or {@code SelectionModel.Multi}
  5246. */
  5247. // keep this javadoc in sync with SelectionModel.Single.select
  5248. public boolean select(Object itemId)
  5249. throws IllegalArgumentException, IllegalStateException {
  5250. if (selectionModel instanceof SelectionModel.Single) {
  5251. return ((SelectionModel.Single) selectionModel).select(itemId);
  5252. } else if (selectionModel instanceof SelectionModel.Multi) {
  5253. return ((SelectionModel.Multi) selectionModel).select(itemId);
  5254. } else if (selectionModel instanceof SelectionModel.None) {
  5255. throw new IllegalStateException("Cannot select row '" + itemId
  5256. + "': Grid selection is disabled "
  5257. + "(the current selection model is "
  5258. + selectionModel.getClass().getName() + ").");
  5259. } else {
  5260. throw new IllegalStateException("Cannot select row '" + itemId
  5261. + "': Grid selection model does not implement "
  5262. + SelectionModel.Single.class.getName() + " or "
  5263. + SelectionModel.Multi.class.getName()
  5264. + "(the current model is "
  5265. + selectionModel.getClass().getName() + ").");
  5266. }
  5267. }
  5268. /**
  5269. * Marks an item as unselected.
  5270. * <p>
  5271. * This method is a shorthand that delegates to the
  5272. * {@link #getSelectionModel() selection model}. Only
  5273. * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
  5274. * supported.
  5275. *
  5276. * @param itemId
  5277. * the itemId to remove from being selected
  5278. * @return <code>true</code> if the selection state changed,
  5279. * <code>false</code> if the itemId was already selected
  5280. * @throws IllegalArgumentException
  5281. * if the {@code itemId} doesn't exist in the currently active
  5282. * Container
  5283. * @throws IllegalStateException
  5284. * if the deselection was illegal. One such reason might be that
  5285. * the implementation requires one or more items to be selected
  5286. * at all times.
  5287. * @throws IllegalStateException
  5288. * if the selection model does not implement
  5289. * {@code SelectionModel.Single} or {code SelectionModel.Multi}
  5290. */
  5291. // keep this javadoc in sync with SelectionModel.Single.deselect
  5292. public boolean deselect(Object itemId) throws IllegalStateException {
  5293. if (selectionModel instanceof SelectionModel.Single) {
  5294. if (isSelected(itemId)) {
  5295. return ((SelectionModel.Single) selectionModel).select(null);
  5296. }
  5297. return false;
  5298. } else if (selectionModel instanceof SelectionModel.Multi) {
  5299. return ((SelectionModel.Multi) selectionModel).deselect(itemId);
  5300. } else if (selectionModel instanceof SelectionModel.None) {
  5301. throw new IllegalStateException("Cannot deselect row '" + itemId
  5302. + "': Grid selection is disabled "
  5303. + "(the current selection model is "
  5304. + selectionModel.getClass().getName() + ").");
  5305. } else {
  5306. throw new IllegalStateException("Cannot deselect row '" + itemId
  5307. + "': Grid selection model does not implement "
  5308. + SelectionModel.Single.class.getName() + " or "
  5309. + SelectionModel.Multi.class.getName()
  5310. + "(the current model is "
  5311. + selectionModel.getClass().getName() + ").");
  5312. }
  5313. }
  5314. /**
  5315. * Marks all items as unselected.
  5316. * <p>
  5317. * This method is a shorthand that delegates to the
  5318. * {@link #getSelectionModel() selection model}. Only
  5319. * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
  5320. * supported.
  5321. *
  5322. * @return <code>true</code> if the selection state changed,
  5323. * <code>false</code> if the itemId was already selected
  5324. * @throws IllegalStateException
  5325. * if the deselection was illegal. One such reason might be that
  5326. * the implementation requires one or more items to be selected
  5327. * at all times.
  5328. * @throws IllegalStateException
  5329. * if the selection model does not implement
  5330. * {@code SelectionModel.Single} or {code SelectionModel.Multi}
  5331. */
  5332. public boolean deselectAll() throws IllegalStateException {
  5333. if (selectionModel instanceof SelectionModel.Single) {
  5334. if (getSelectedRow() != null) {
  5335. return deselect(getSelectedRow());
  5336. }
  5337. return false;
  5338. } else if (selectionModel instanceof SelectionModel.Multi) {
  5339. return ((SelectionModel.Multi) selectionModel).deselectAll();
  5340. } else if (selectionModel instanceof SelectionModel.None) {
  5341. throw new IllegalStateException(
  5342. "Cannot deselect all rows" + ": Grid selection is disabled "
  5343. + "(the current selection model is "
  5344. + selectionModel.getClass().getName() + ").");
  5345. } else {
  5346. throw new IllegalStateException("Cannot deselect all rows:"
  5347. + " Grid selection model does not implement "
  5348. + SelectionModel.Single.class.getName() + " or "
  5349. + SelectionModel.Multi.class.getName()
  5350. + "(the current model is "
  5351. + selectionModel.getClass().getName() + ").");
  5352. }
  5353. }
  5354. /**
  5355. * Fires a selection change event.
  5356. * <p>
  5357. * <strong>Note:</strong> This is not a method that should be called by
  5358. * application logic. This method is publicly accessible only so that
  5359. * {@link SelectionModel SelectionModels} would be able to inform Grid of
  5360. * these events.
  5361. *
  5362. * @param newSelection
  5363. * the selection that was added by this event
  5364. * @param oldSelection
  5365. * the selection that was removed by this event
  5366. */
  5367. public void fireSelectionEvent(Collection<Object> oldSelection,
  5368. Collection<Object> newSelection) {
  5369. fireEvent(new SelectionEvent(this, oldSelection, newSelection));
  5370. }
  5371. @Override
  5372. public void addSelectionListener(SelectionListener listener) {
  5373. addListener(SelectionEvent.class, listener, SELECTION_CHANGE_METHOD);
  5374. }
  5375. @Override
  5376. public void removeSelectionListener(SelectionListener listener) {
  5377. removeListener(SelectionEvent.class, listener, SELECTION_CHANGE_METHOD);
  5378. }
  5379. private void fireColumnReorderEvent(boolean userOriginated) {
  5380. fireEvent(new ColumnReorderEvent(this, userOriginated));
  5381. }
  5382. /**
  5383. * Registers a new column reorder listener.
  5384. *
  5385. * @since 7.5.0
  5386. * @param listener
  5387. * the listener to register
  5388. */
  5389. public void addColumnReorderListener(ColumnReorderListener listener) {
  5390. addListener(ColumnReorderEvent.class, listener, COLUMN_REORDER_METHOD);
  5391. }
  5392. /**
  5393. * Removes a previously registered column reorder listener.
  5394. *
  5395. * @since 7.5.0
  5396. * @param listener
  5397. * the listener to remove
  5398. */
  5399. public void removeColumnReorderListener(ColumnReorderListener listener) {
  5400. removeListener(ColumnReorderEvent.class, listener,
  5401. COLUMN_REORDER_METHOD);
  5402. }
  5403. private void fireColumnResizeEvent(Column column, boolean userOriginated) {
  5404. fireEvent(new ColumnResizeEvent(this, column, userOriginated));
  5405. }
  5406. /**
  5407. * Registers a new column resize listener.
  5408. *
  5409. * @param listener
  5410. * the listener to register
  5411. */
  5412. public void addColumnResizeListener(ColumnResizeListener listener) {
  5413. addListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
  5414. }
  5415. /**
  5416. * Removes a previously registered column resize listener.
  5417. *
  5418. * @param listener
  5419. * the listener to remove
  5420. */
  5421. public void removeColumnResizeListener(ColumnResizeListener listener) {
  5422. removeListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
  5423. }
  5424. /**
  5425. * Gets the {@link KeyMapper } being used by the data source.
  5426. *
  5427. * @return the key mapper being used by the data source
  5428. */
  5429. KeyMapper<Object> getKeyMapper() {
  5430. return datasourceExtension.getKeyMapper();
  5431. }
  5432. /**
  5433. * Adds a renderer to this grid's connector hierarchy.
  5434. *
  5435. * @param renderer
  5436. * the renderer to add
  5437. */
  5438. void addRenderer(Renderer<?> renderer) {
  5439. addExtension(renderer);
  5440. }
  5441. /**
  5442. * Sets the current sort order using the fluid Sort API. Read the
  5443. * documentation for {@link Sort} for more information.
  5444. * <p>
  5445. * <em>Note:</em> Sorting by a property that has no column in Grid will hide
  5446. * all possible sorting indicators.
  5447. *
  5448. * @param s
  5449. * a sort instance
  5450. *
  5451. * @throws IllegalStateException
  5452. * if container is not sortable (does not implement
  5453. * Container.Sortable)
  5454. * @throws IllegalArgumentException
  5455. * if trying to sort by non-existing property
  5456. */
  5457. public void sort(Sort s) {
  5458. setSortOrder(s.build());
  5459. }
  5460. /**
  5461. * Sort this Grid in ascending order by a specified property.
  5462. * <p>
  5463. * <em>Note:</em> Sorting by a property that has no column in Grid will hide
  5464. * all possible sorting indicators.
  5465. *
  5466. * @param propertyId
  5467. * a property ID
  5468. *
  5469. * @throws IllegalStateException
  5470. * if container is not sortable (does not implement
  5471. * Container.Sortable)
  5472. * @throws IllegalArgumentException
  5473. * if trying to sort by non-existing property
  5474. */
  5475. public void sort(Object propertyId) {
  5476. sort(propertyId, SortDirection.ASCENDING);
  5477. }
  5478. /**
  5479. * Sort this Grid in user-specified {@link SortOrder} by a property.
  5480. * <p>
  5481. * <em>Note:</em> Sorting by a property that has no column in Grid will hide
  5482. * all possible sorting indicators.
  5483. *
  5484. * @param propertyId
  5485. * a property ID
  5486. * @param direction
  5487. * a sort order value (ascending/descending)
  5488. *
  5489. * @throws IllegalStateException
  5490. * if container is not sortable (does not implement
  5491. * Container.Sortable)
  5492. * @throws IllegalArgumentException
  5493. * if trying to sort by non-existing property
  5494. */
  5495. public void sort(Object propertyId, SortDirection direction) {
  5496. sort(Sort.by(propertyId, direction));
  5497. }
  5498. /**
  5499. * Clear the current sort order, and re-sort the grid.
  5500. */
  5501. public void clearSortOrder() {
  5502. sortOrder.clear();
  5503. sort(false);
  5504. }
  5505. /**
  5506. * Sets the sort order to use.
  5507. * <p>
  5508. * <em>Note:</em> Sorting by a property that has no column in Grid will hide
  5509. * all possible sorting indicators.
  5510. *
  5511. * @param order
  5512. * a sort order list.
  5513. *
  5514. * @throws IllegalStateException
  5515. * if container is not sortable (does not implement
  5516. * Container.Sortable)
  5517. * @throws IllegalArgumentException
  5518. * if order is null or trying to sort by non-existing property
  5519. */
  5520. public void setSortOrder(List<SortOrder> order) {
  5521. setSortOrder(order, false);
  5522. }
  5523. private void setSortOrder(List<SortOrder> order, boolean userOriginated)
  5524. throws IllegalStateException, IllegalArgumentException {
  5525. if (!(getContainerDataSource() instanceof Container.Sortable)) {
  5526. throw new IllegalStateException(
  5527. "Attached container is not sortable (does not implement Container.Sortable)");
  5528. }
  5529. if (order == null) {
  5530. throw new IllegalArgumentException("Order list may not be null!");
  5531. }
  5532. sortOrder.clear();
  5533. Collection<?> sortableProps = ((Container.Sortable) getContainerDataSource())
  5534. .getSortableContainerPropertyIds();
  5535. for (SortOrder o : order) {
  5536. if (!sortableProps.contains(o.getPropertyId())) {
  5537. throw new IllegalArgumentException("Property "
  5538. + o.getPropertyId()
  5539. + " does not exist or is not sortable in the current container");
  5540. }
  5541. }
  5542. sortOrder.addAll(order);
  5543. sort(userOriginated);
  5544. }
  5545. /**
  5546. * Get the current sort order list.
  5547. *
  5548. * @return a sort order list
  5549. */
  5550. public List<SortOrder> getSortOrder() {
  5551. return Collections.unmodifiableList(sortOrder);
  5552. }
  5553. /**
  5554. * Apply sorting to data source.
  5555. */
  5556. private void sort(boolean userOriginated) {
  5557. Container c = getContainerDataSource();
  5558. if (c instanceof Container.Sortable) {
  5559. Container.Sortable cs = (Container.Sortable) c;
  5560. final int items = sortOrder.size();
  5561. Object[] propertyIds = new Object[items];
  5562. boolean[] directions = new boolean[items];
  5563. SortDirection[] stateDirs = new SortDirection[items];
  5564. for (int i = 0; i < items; ++i) {
  5565. SortOrder order = sortOrder.get(i);
  5566. stateDirs[i] = order.getDirection();
  5567. propertyIds[i] = order.getPropertyId();
  5568. switch (order.getDirection()) {
  5569. case ASCENDING:
  5570. directions[i] = true;
  5571. break;
  5572. case DESCENDING:
  5573. directions[i] = false;
  5574. break;
  5575. default:
  5576. throw new IllegalArgumentException("getDirection() of "
  5577. + order + " returned an unexpected value");
  5578. }
  5579. }
  5580. cs.sort(propertyIds, directions);
  5581. if (columns.keySet().containsAll(Arrays.asList(propertyIds))) {
  5582. String[] columnKeys = new String[items];
  5583. for (int i = 0; i < items; ++i) {
  5584. columnKeys[i] = this.columnKeys.key(propertyIds[i]);
  5585. }
  5586. getState().sortColumns = columnKeys;
  5587. getState(false).sortDirs = stateDirs;
  5588. } else {
  5589. // Not all sorted properties are in Grid. Remove any indicators.
  5590. getState().sortColumns = new String[] {};
  5591. getState(false).sortDirs = new SortDirection[] {};
  5592. }
  5593. fireEvent(new SortEvent(this, new ArrayList<SortOrder>(sortOrder),
  5594. userOriginated));
  5595. } else {
  5596. throw new IllegalStateException(
  5597. "Container is not sortable (does not implement Container.Sortable)");
  5598. }
  5599. }
  5600. /**
  5601. * Adds a sort order change listener that gets notified when the sort order
  5602. * changes.
  5603. *
  5604. * @param listener
  5605. * the sort order change listener to add
  5606. */
  5607. @Override
  5608. public void addSortListener(SortListener listener) {
  5609. addListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
  5610. }
  5611. /**
  5612. * Removes a sort order change listener previously added using
  5613. * {@link #addSortListener(SortListener)}.
  5614. *
  5615. * @param listener
  5616. * the sort order change listener to remove
  5617. */
  5618. @Override
  5619. public void removeSortListener(SortListener listener) {
  5620. removeListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
  5621. }
  5622. /* Grid Headers */
  5623. /**
  5624. * Returns the header section of this grid. The default header contains a
  5625. * single row displaying the column captions.
  5626. *
  5627. * @return the header
  5628. */
  5629. protected Header getHeader() {
  5630. return header;
  5631. }
  5632. /**
  5633. * Gets the header row at given index.
  5634. *
  5635. * @param rowIndex
  5636. * 0 based index for row. Counted from top to bottom
  5637. * @return header row at given index
  5638. * @throws IllegalArgumentException
  5639. * if no row exists at given index
  5640. */
  5641. public HeaderRow getHeaderRow(int rowIndex) {
  5642. return header.getRow(rowIndex);
  5643. }
  5644. /**
  5645. * Inserts a new row at the given position to the header section. Shifts the
  5646. * row currently at that position and any subsequent rows down (adds one to
  5647. * their indices).
  5648. *
  5649. * @param index
  5650. * the position at which to insert the row
  5651. * @return the new row
  5652. *
  5653. * @throws IllegalArgumentException
  5654. * if the index is less than 0 or greater than row count
  5655. * @see #appendHeaderRow()
  5656. * @see #prependHeaderRow()
  5657. * @see #removeHeaderRow(HeaderRow)
  5658. * @see #removeHeaderRow(int)
  5659. */
  5660. public HeaderRow addHeaderRowAt(int index) {
  5661. return header.addRowAt(index);
  5662. }
  5663. /**
  5664. * Adds a new row at the bottom of the header section.
  5665. *
  5666. * @return the new row
  5667. * @see #prependHeaderRow()
  5668. * @see #addHeaderRowAt(int)
  5669. * @see #removeHeaderRow(HeaderRow)
  5670. * @see #removeHeaderRow(int)
  5671. */
  5672. public HeaderRow appendHeaderRow() {
  5673. return header.appendRow();
  5674. }
  5675. /**
  5676. * Returns the current default row of the header section. The default row is
  5677. * a special header row providing a user interface for sorting columns.
  5678. * Setting a header text for column updates cells in the default header.
  5679. *
  5680. * @return the default row or null if no default row set
  5681. */
  5682. public HeaderRow getDefaultHeaderRow() {
  5683. return header.getDefaultRow();
  5684. }
  5685. /**
  5686. * Gets the row count for the header section.
  5687. *
  5688. * @return row count
  5689. */
  5690. public int getHeaderRowCount() {
  5691. return header.getRowCount();
  5692. }
  5693. /**
  5694. * Adds a new row at the top of the header section.
  5695. *
  5696. * @return the new row
  5697. * @see #appendHeaderRow()
  5698. * @see #addHeaderRowAt(int)
  5699. * @see #removeHeaderRow(HeaderRow)
  5700. * @see #removeHeaderRow(int)
  5701. */
  5702. public HeaderRow prependHeaderRow() {
  5703. return header.prependRow();
  5704. }
  5705. /**
  5706. * Removes the given row from the header section.
  5707. *
  5708. * @param row
  5709. * the row to be removed
  5710. *
  5711. * @throws IllegalArgumentException
  5712. * if the row does not exist in this section
  5713. * @see #removeHeaderRow(int)
  5714. * @see #addHeaderRowAt(int)
  5715. * @see #appendHeaderRow()
  5716. * @see #prependHeaderRow()
  5717. */
  5718. public void removeHeaderRow(HeaderRow row) {
  5719. header.removeRow(row);
  5720. }
  5721. /**
  5722. * Removes the row at the given position from the header section.
  5723. *
  5724. * @param rowIndex
  5725. * the position of the row
  5726. *
  5727. * @throws IllegalArgumentException
  5728. * if no row exists at given index
  5729. * @see #removeHeaderRow(HeaderRow)
  5730. * @see #addHeaderRowAt(int)
  5731. * @see #appendHeaderRow()
  5732. * @see #prependHeaderRow()
  5733. */
  5734. public void removeHeaderRow(int rowIndex) {
  5735. header.removeRow(rowIndex);
  5736. }
  5737. /**
  5738. * Sets the default row of the header. The default row is a special header
  5739. * row providing a user interface for sorting columns.
  5740. *
  5741. * @param row
  5742. * the new default row, or null for no default row
  5743. *
  5744. * @throws IllegalArgumentException
  5745. * header does not contain the row
  5746. */
  5747. public void setDefaultHeaderRow(HeaderRow row) {
  5748. header.setDefaultRow(row);
  5749. }
  5750. /**
  5751. * Sets the visibility of the header section.
  5752. *
  5753. * @param visible
  5754. * true to show header section, false to hide
  5755. */
  5756. public void setHeaderVisible(boolean visible) {
  5757. header.setVisible(visible);
  5758. }
  5759. /**
  5760. * Returns the visibility of the header section.
  5761. *
  5762. * @return true if visible, false otherwise.
  5763. */
  5764. public boolean isHeaderVisible() {
  5765. return header.isVisible();
  5766. }
  5767. /* Grid Footers */
  5768. /**
  5769. * Returns the footer section of this grid. The default header contains a
  5770. * single row displaying the column captions.
  5771. *
  5772. * @return the footer
  5773. */
  5774. protected Footer getFooter() {
  5775. return footer;
  5776. }
  5777. /**
  5778. * Gets the footer row at given index.
  5779. *
  5780. * @param rowIndex
  5781. * 0 based index for row. Counted from top to bottom
  5782. * @return footer row at given index
  5783. * @throws IllegalArgumentException
  5784. * if no row exists at given index
  5785. */
  5786. public FooterRow getFooterRow(int rowIndex) {
  5787. return footer.getRow(rowIndex);
  5788. }
  5789. /**
  5790. * Inserts a new row at the given position to the footer section. Shifts the
  5791. * row currently at that position and any subsequent rows down (adds one to
  5792. * their indices).
  5793. *
  5794. * @param index
  5795. * the position at which to insert the row
  5796. * @return the new row
  5797. *
  5798. * @throws IllegalArgumentException
  5799. * if the index is less than 0 or greater than row count
  5800. * @see #appendFooterRow()
  5801. * @see #prependFooterRow()
  5802. * @see #removeFooterRow(FooterRow)
  5803. * @see #removeFooterRow(int)
  5804. */
  5805. public FooterRow addFooterRowAt(int index) {
  5806. return footer.addRowAt(index);
  5807. }
  5808. /**
  5809. * Adds a new row at the bottom of the footer section.
  5810. *
  5811. * @return the new row
  5812. * @see #prependFooterRow()
  5813. * @see #addFooterRowAt(int)
  5814. * @see #removeFooterRow(FooterRow)
  5815. * @see #removeFooterRow(int)
  5816. */
  5817. public FooterRow appendFooterRow() {
  5818. return footer.appendRow();
  5819. }
  5820. /**
  5821. * Gets the row count for the footer.
  5822. *
  5823. * @return row count
  5824. */
  5825. public int getFooterRowCount() {
  5826. return footer.getRowCount();
  5827. }
  5828. /**
  5829. * Adds a new row at the top of the footer section.
  5830. *
  5831. * @return the new row
  5832. * @see #appendFooterRow()
  5833. * @see #addFooterRowAt(int)
  5834. * @see #removeFooterRow(FooterRow)
  5835. * @see #removeFooterRow(int)
  5836. */
  5837. public FooterRow prependFooterRow() {
  5838. return footer.prependRow();
  5839. }
  5840. /**
  5841. * Removes the given row from the footer section.
  5842. *
  5843. * @param row
  5844. * the row to be removed
  5845. *
  5846. * @throws IllegalArgumentException
  5847. * if the row does not exist in this section
  5848. * @see #removeFooterRow(int)
  5849. * @see #addFooterRowAt(int)
  5850. * @see #appendFooterRow()
  5851. * @see #prependFooterRow()
  5852. */
  5853. public void removeFooterRow(FooterRow row) {
  5854. footer.removeRow(row);
  5855. }
  5856. /**
  5857. * Removes the row at the given position from the footer section.
  5858. *
  5859. * @param rowIndex
  5860. * the position of the row
  5861. *
  5862. * @throws IllegalArgumentException
  5863. * if no row exists at given index
  5864. * @see #removeFooterRow(FooterRow)
  5865. * @see #addFooterRowAt(int)
  5866. * @see #appendFooterRow()
  5867. * @see #prependFooterRow()
  5868. */
  5869. public void removeFooterRow(int rowIndex) {
  5870. footer.removeRow(rowIndex);
  5871. }
  5872. /**
  5873. * Sets the visibility of the footer section.
  5874. *
  5875. * @param visible
  5876. * true to show footer section, false to hide
  5877. */
  5878. public void setFooterVisible(boolean visible) {
  5879. footer.setVisible(visible);
  5880. }
  5881. /**
  5882. * Returns the visibility of the footer section.
  5883. *
  5884. * @return true if visible, false otherwise.
  5885. */
  5886. public boolean isFooterVisible() {
  5887. return footer.isVisible();
  5888. }
  5889. private void addComponent(Component c) {
  5890. extensionComponents.add(c);
  5891. c.setParent(this);
  5892. markAsDirty();
  5893. }
  5894. private void removeComponent(Component c) {
  5895. extensionComponents.remove(c);
  5896. c.setParent(null);
  5897. markAsDirty();
  5898. }
  5899. @Override
  5900. public Iterator<Component> iterator() {
  5901. // This is a hash set to avoid adding header/footer components inside
  5902. // merged cells multiple times
  5903. LinkedHashSet<Component> componentList = new LinkedHashSet<Component>();
  5904. Header header = getHeader();
  5905. for (int i = 0; i < header.getRowCount(); ++i) {
  5906. HeaderRow row = header.getRow(i);
  5907. for (Object propId : columns.keySet()) {
  5908. HeaderCell cell = row.getCell(propId);
  5909. if (cell.getCellState().type == GridStaticCellType.WIDGET) {
  5910. componentList.add(cell.getComponent());
  5911. }
  5912. }
  5913. }
  5914. Footer footer = getFooter();
  5915. for (int i = 0; i < footer.getRowCount(); ++i) {
  5916. FooterRow row = footer.getRow(i);
  5917. for (Object propId : columns.keySet()) {
  5918. FooterCell cell = row.getCell(propId);
  5919. if (cell.getCellState().type == GridStaticCellType.WIDGET) {
  5920. componentList.add(cell.getComponent());
  5921. }
  5922. }
  5923. }
  5924. componentList.addAll(getEditorFields());
  5925. componentList.addAll(extensionComponents);
  5926. return componentList.iterator();
  5927. }
  5928. @Override
  5929. public boolean isRendered(Component childComponent) {
  5930. if (getEditorFields().contains(childComponent)) {
  5931. // Only render editor fields if the editor is open
  5932. return isEditorActive();
  5933. } else {
  5934. // TODO Header and footer components should also only be rendered if
  5935. // the header/footer is visible
  5936. return true;
  5937. }
  5938. }
  5939. EditorClientRpc getEditorRpc() {
  5940. return getRpcProxy(EditorClientRpc.class);
  5941. }
  5942. /**
  5943. * Sets the {@code CellDescriptionGenerator} instance for generating
  5944. * optional descriptions (tooltips) for individual Grid cells. If a
  5945. * {@link RowDescriptionGenerator} is also set, the row description it
  5946. * generates is displayed for cells for which {@code generator} returns
  5947. * <code>null</code>.
  5948. * <p>
  5949. * The descriptions are rendered in the browser as HTML and the developer is
  5950. * responsible for ensuring no harmful HTML is used.
  5951. *
  5952. * @param generator
  5953. * the description generator to use or <code>null</code> to
  5954. * remove a previously set generator if any
  5955. *
  5956. * @see #setCellDescriptionGenerator(CellDescriptionGenerator, ContentMode)
  5957. * @see #setRowDescriptionGenerator(RowDescriptionGenerator)
  5958. *
  5959. * @since 7.6
  5960. */
  5961. public void setCellDescriptionGenerator(CellDescriptionGenerator generator) {
  5962. /*
  5963. * When porting this to the v7 version in Framework 8, the default
  5964. * should be changed to PREFORMATTED to preserve the more secure
  5965. * default that has accidentally been used there.
  5966. */
  5967. setCellDescriptionGenerator(generator, ContentMode.HTML);
  5968. }
  5969. /**
  5970. * Sets the {@code CellDescriptionGenerator} instance and content mode for
  5971. * generating optional descriptions (tooltips) for individual Grid cells. If
  5972. * a {@link RowDescriptionGenerator} is also set, the row description it
  5973. * generates is displayed for cells for which {@code generator} returns
  5974. * <code>null</code>.
  5975. *
  5976. * @param generator
  5977. * the description generator to use or <code>null</code> to
  5978. * remove a previously set generator if any
  5979. * @param contentMode
  5980. * the content mode for cell tooltips, not <code>null</code>
  5981. * @see #setRowDescriptionGenerator(RowDescriptionGenerator)
  5982. *
  5983. * @since 7.7.14
  5984. */
  5985. public void setCellDescriptionGenerator(CellDescriptionGenerator generator,
  5986. ContentMode contentMode) {
  5987. if (contentMode == null) {
  5988. throw new IllegalArgumentException("Content mode cannot be null");
  5989. }
  5990. cellDescriptionGenerator = generator;
  5991. getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null);
  5992. getState().cellTooltipContentMode = contentMode;
  5993. datasourceExtension.refreshCache();
  5994. }
  5995. /**
  5996. * Returns the {@code CellDescriptionGenerator} instance used to generate
  5997. * descriptions (tooltips) for Grid cells.
  5998. *
  5999. * @return the description generator or {@code null} if no generator is set
  6000. *
  6001. * @since 7.6
  6002. */
  6003. public CellDescriptionGenerator getCellDescriptionGenerator() {
  6004. return cellDescriptionGenerator;
  6005. }
  6006. /**
  6007. * Gets the content mode used for cell descriptions.
  6008. *
  6009. * @return the content mode used for cell descriptions, not
  6010. * <code>null</code>
  6011. * @see #setCellDescriptionGenerator(CellDescriptionGenerator, ContentMode)
  6012. */
  6013. public ContentMode getCellDescriptionContentMode() {
  6014. return getState(false).cellTooltipContentMode;
  6015. }
  6016. /**
  6017. * Sets the {@code RowDescriptionGenerator} instance for generating optional
  6018. * descriptions (tooltips) for Grid rows. If a
  6019. * {@link CellDescriptionGenerator} is also set, the row description
  6020. * generated by {@code generator} is used for cells for which the cell
  6021. * description generator returns <code>null</code>.
  6022. * <p>
  6023. * The descriptions are rendered in the browser as HTML and the developer is
  6024. * responsible for ensuring no harmful HTML is used.
  6025. *
  6026. * @param generator
  6027. * the description generator to use or <code>null</code> to
  6028. * remove a previously set generator if any
  6029. *
  6030. * @see #setRowDescriptionGenerator(RowDescriptionGenerator, ContentMode)
  6031. * @see #setCellDescriptionGenerator(CellDescriptionGenerator)
  6032. *
  6033. * @since 7.6
  6034. */
  6035. public void setRowDescriptionGenerator(RowDescriptionGenerator generator) {
  6036. /*
  6037. * When porting this to the v7 version in Framework 8, the default
  6038. * should be changed to PREFORMATTED to preserve the more secure
  6039. * default that has accidentally been used there.
  6040. */
  6041. setRowDescriptionGenerator(generator, ContentMode.HTML);
  6042. }
  6043. /**
  6044. * Sets the {@code RowDescriptionGenerator} instance for generating optional
  6045. * descriptions (tooltips) for Grid rows. If a
  6046. * {@link CellDescriptionGenerator} is also set, the row description
  6047. * generated by {@code generator} is used for cells for which the cell
  6048. * description generator returns <code>null</code>.
  6049. *
  6050. * @param generator
  6051. * the description generator to use or <code>null</code> to
  6052. * remove a previously set generator if any
  6053. * @param contentMode
  6054. * the content mode for row tooltips, not <code>null</code>
  6055. *
  6056. * @see #setCellDescriptionGenerator(CellDescriptionGenerator)
  6057. *
  6058. * @since 7.7.14
  6059. */
  6060. public void setRowDescriptionGenerator(RowDescriptionGenerator generator,
  6061. ContentMode contentMode) {
  6062. if (contentMode == null) {
  6063. throw new IllegalArgumentException("Content mode cannot be null");
  6064. }
  6065. rowDescriptionGenerator = generator;
  6066. getState().hasDescriptions = (generator != null
  6067. || cellDescriptionGenerator != null);
  6068. getState().rowTooltipContentMode = contentMode;
  6069. datasourceExtension.refreshCache();
  6070. }
  6071. /**
  6072. * Gets the content mode used for row descriptions.
  6073. *
  6074. * @return the content mode used for row descriptions, not <code>null</code>
  6075. * @see #setRowDescriptionGenerator(RowDescriptionGenerator, ContentMode)
  6076. */
  6077. public ContentMode getRowDescriptionContentMode() {
  6078. return getState(false).rowTooltipContentMode;
  6079. }
  6080. /**
  6081. * Returns the {@code RowDescriptionGenerator} instance used to generate
  6082. * descriptions (tooltips) for Grid rows
  6083. *
  6084. * @return the description generator or {@code} null if no generator is set
  6085. *
  6086. * @since 7.6
  6087. */
  6088. public RowDescriptionGenerator getRowDescriptionGenerator() {
  6089. return rowDescriptionGenerator;
  6090. }
  6091. /**
  6092. * Sets the style generator that is used for generating styles for cells
  6093. *
  6094. * @param cellStyleGenerator
  6095. * the cell style generator to set, or <code>null</code> to
  6096. * remove a previously set generator
  6097. */
  6098. public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
  6099. this.cellStyleGenerator = cellStyleGenerator;
  6100. datasourceExtension.refreshCache();
  6101. }
  6102. /**
  6103. * Gets the style generator that is used for generating styles for cells
  6104. *
  6105. * @return the cell style generator, or <code>null</code> if no generator is
  6106. * set
  6107. */
  6108. public CellStyleGenerator getCellStyleGenerator() {
  6109. return cellStyleGenerator;
  6110. }
  6111. /**
  6112. * Sets the style generator that is used for generating styles for rows
  6113. *
  6114. * @param rowStyleGenerator
  6115. * the row style generator to set, or <code>null</code> to remove
  6116. * a previously set generator
  6117. */
  6118. public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) {
  6119. this.rowStyleGenerator = rowStyleGenerator;
  6120. datasourceExtension.refreshCache();
  6121. }
  6122. /**
  6123. * Gets the style generator that is used for generating styles for rows
  6124. *
  6125. * @return the row style generator, or <code>null</code> if no generator is
  6126. * set
  6127. */
  6128. public RowStyleGenerator getRowStyleGenerator() {
  6129. return rowStyleGenerator;
  6130. }
  6131. /**
  6132. * Adds a row to the underlying container. The order of the parameters
  6133. * should match the current visible column order.
  6134. * <p>
  6135. * Please note that it's generally only safe to use this method during
  6136. * initialization. After Grid has been initialized and the visible column
  6137. * order might have been changed, it's better to instead add items directly
  6138. * to the underlying container and use {@link Item#getItemProperty(Object)}
  6139. * to make sure each value is assigned to the intended property.
  6140. *
  6141. * @param values
  6142. * the cell values of the new row, in the same order as the
  6143. * visible column order, not <code>null</code>.
  6144. * @return the item id of the new row
  6145. * @throws IllegalArgumentException
  6146. * if values is null
  6147. * @throws IllegalArgumentException
  6148. * if its length does not match the number of visible columns
  6149. * @throws IllegalArgumentException
  6150. * if a parameter value is not an instance of the corresponding
  6151. * property type
  6152. * @throws UnsupportedOperationException
  6153. * if the container does not support adding new items
  6154. */
  6155. public Object addRow(Object... values) {
  6156. if (values == null) {
  6157. throw new IllegalArgumentException("Values cannot be null");
  6158. }
  6159. Indexed dataSource = getContainerDataSource();
  6160. List<String> columnOrder = getState(false).columnOrder;
  6161. if (values.length != columnOrder.size()) {
  6162. throw new IllegalArgumentException(
  6163. "There are " + columnOrder.size() + " visible columns, but "
  6164. + values.length + " cell values were provided.");
  6165. }
  6166. // First verify all parameter types
  6167. for (int i = 0; i < columnOrder.size(); i++) {
  6168. Object propertyId = getPropertyIdByColumnId(columnOrder.get(i));
  6169. Class<?> propertyType = dataSource.getType(propertyId);
  6170. if (values[i] != null && !propertyType.isInstance(values[i])) {
  6171. throw new IllegalArgumentException("Parameter " + i + "("
  6172. + values[i] + ") is not an instance of "
  6173. + propertyType.getCanonicalName());
  6174. }
  6175. }
  6176. Object itemId = dataSource.addItem();
  6177. try {
  6178. Item item = dataSource.getItem(itemId);
  6179. for (int i = 0; i < columnOrder.size(); i++) {
  6180. Object propertyId = getPropertyIdByColumnId(columnOrder.get(i));
  6181. Property<Object> property = item.getItemProperty(propertyId);
  6182. property.setValue(values[i]);
  6183. }
  6184. } catch (RuntimeException e) {
  6185. try {
  6186. dataSource.removeItem(itemId);
  6187. } catch (Exception e2) {
  6188. getLogger().log(Level.SEVERE,
  6189. "Error recovering from exception in addRow", e);
  6190. }
  6191. throw e;
  6192. }
  6193. return itemId;
  6194. }
  6195. /**
  6196. * Refreshes, i.e. causes the client side to re-render the rows with the
  6197. * given item ids.
  6198. * <p>
  6199. * Calling this for a row which is not currently rendered on the client side
  6200. * has no effect.
  6201. *
  6202. * @param itemIds
  6203. * the item id(s) of the row to refresh.
  6204. */
  6205. public void refreshRows(Object... itemIds) {
  6206. for (Object itemId : itemIds) {
  6207. datasourceExtension.updateRowData(itemId);
  6208. }
  6209. }
  6210. /**
  6211. * Refreshes, i.e. causes the client side to re-render all rows.
  6212. *
  6213. * @since 7.7.7
  6214. */
  6215. public void refreshAllRows() {
  6216. datasourceExtension.refreshCache();
  6217. }
  6218. private static Logger getLogger() {
  6219. return Logger.getLogger(Grid.class.getName());
  6220. }
  6221. /**
  6222. * Sets whether or not the item editor UI is enabled for this grid. When the
  6223. * editor is enabled, the user can open it by double-clicking a row or
  6224. * hitting enter when a row is focused. The editor can also be opened
  6225. * programmatically using the {@link #editItem(Object)} method.
  6226. *
  6227. * @param isEnabled
  6228. * <code>true</code> to enable the feature, <code>false</code>
  6229. * otherwise
  6230. * @throws IllegalStateException
  6231. * if an item is currently being edited
  6232. *
  6233. * @see #getEditedItemId()
  6234. */
  6235. public void setEditorEnabled(boolean isEnabled)
  6236. throws IllegalStateException {
  6237. if (isEditorActive()) {
  6238. throw new IllegalStateException(
  6239. "Cannot disable the editor while an item ("
  6240. + getEditedItemId() + ") is being edited");
  6241. }
  6242. if (isEditorEnabled() != isEnabled) {
  6243. getState().editorEnabled = isEnabled;
  6244. }
  6245. }
  6246. /**
  6247. * Checks whether the item editor UI is enabled for this grid.
  6248. *
  6249. * @return <code>true</code> iff the editor is enabled for this grid
  6250. *
  6251. * @see #setEditorEnabled(boolean)
  6252. * @see #getEditedItemId()
  6253. */
  6254. public boolean isEditorEnabled() {
  6255. return getState(false).editorEnabled;
  6256. }
  6257. /**
  6258. * Gets the id of the item that is currently being edited.
  6259. *
  6260. * @return the id of the item that is currently being edited, or
  6261. * <code>null</code> if no item is being edited at the moment
  6262. */
  6263. public Object getEditedItemId() {
  6264. return editedItemId;
  6265. }
  6266. /**
  6267. * Gets the field group that is backing the item editor of this grid.
  6268. *
  6269. * @return the backing field group
  6270. */
  6271. public FieldGroup getEditorFieldGroup() {
  6272. return editorFieldGroup;
  6273. }
  6274. /**
  6275. * Sets the field group that is backing the item editor of this grid.
  6276. *
  6277. * @param fieldGroup
  6278. * the backing field group
  6279. *
  6280. * @throws IllegalStateException
  6281. * if the editor is currently active
  6282. */
  6283. public void setEditorFieldGroup(FieldGroup fieldGroup) {
  6284. if (isEditorActive()) {
  6285. throw new IllegalStateException(
  6286. "Cannot change field group while an item ("
  6287. + getEditedItemId() + ") is being edited");
  6288. }
  6289. editorFieldGroup = fieldGroup;
  6290. }
  6291. /**
  6292. * Returns whether an item is currently being edited in the editor.
  6293. *
  6294. * @return true iff the editor is open
  6295. */
  6296. public boolean isEditorActive() {
  6297. return editorActive;
  6298. }
  6299. private void checkColumnExists(Object propertyId) {
  6300. if (getColumn(propertyId) == null) {
  6301. throw new IllegalArgumentException(
  6302. "There is no column with the property id " + propertyId);
  6303. }
  6304. }
  6305. private Field<?> getEditorField(Object propertyId) {
  6306. checkColumnExists(propertyId);
  6307. if (!getColumn(propertyId).isEditable()) {
  6308. return null;
  6309. }
  6310. Field<?> editor = editorFieldGroup.getField(propertyId);
  6311. // If field group has no field for this property, see if we have it
  6312. // stored
  6313. if (editor == null) {
  6314. editor = editorFields.get(propertyId);
  6315. if (editor != null) {
  6316. editorFieldGroup.bind(editor, propertyId);
  6317. }
  6318. }
  6319. // Otherwise try to build one
  6320. try {
  6321. if (editor == null) {
  6322. editor = editorFieldGroup.buildAndBind(propertyId);
  6323. }
  6324. } finally {
  6325. if (editor == null) {
  6326. editor = editorFieldGroup.getField(propertyId);
  6327. }
  6328. if (editor != null && editor.getParent() != Grid.this) {
  6329. assert editor.getParent() == null;
  6330. editor.setParent(this);
  6331. }
  6332. }
  6333. return editor;
  6334. }
  6335. /**
  6336. * Opens the editor interface for the provided item. Scrolls the Grid to
  6337. * bring the item to view if it is not already visible.
  6338. *
  6339. * Note that any cell content rendered by a WidgetRenderer will not be
  6340. * visible in the editor row.
  6341. *
  6342. * @param itemId
  6343. * the id of the item to edit
  6344. * @throws IllegalStateException
  6345. * if the editor is not enabled or already editing an item in
  6346. * buffered mode
  6347. * @throws IllegalArgumentException
  6348. * if the {@code itemId} is not in the backing container
  6349. * @see #setEditorEnabled(boolean)
  6350. */
  6351. public void editItem(Object itemId)
  6352. throws IllegalStateException, IllegalArgumentException {
  6353. if (!isEditorEnabled()) {
  6354. throw new IllegalStateException("Item editor is not enabled");
  6355. } else if (isEditorBuffered() && editedItemId != null) {
  6356. throw new IllegalStateException("Editing item " + itemId
  6357. + " failed. Item editor is already editing item "
  6358. + editedItemId);
  6359. } else if (!getContainerDataSource().containsId(itemId)) {
  6360. throw new IllegalArgumentException("Item with id " + itemId
  6361. + " not found in current container");
  6362. }
  6363. editedItemId = itemId;
  6364. getEditorRpc().bind(getContainerDataSource().indexOfId(itemId));
  6365. }
  6366. protected void doEditItem() {
  6367. Item item = getContainerDataSource().getItem(editedItemId);
  6368. editorFieldGroup.setItemDataSource(item);
  6369. for (Column column : getColumns()) {
  6370. column.getState().editorConnector = item
  6371. .getItemProperty(column.getPropertyId()) == null ? null
  6372. : getEditorField(column.getPropertyId());
  6373. }
  6374. editorActive = true;
  6375. // Must ensure that all fields, recursively, are sent to the client
  6376. // This is needed because the fields are hidden using isRendered
  6377. for (Field<?> f : getEditorFields()) {
  6378. f.markAsDirtyRecursive();
  6379. }
  6380. if (datasource instanceof ItemSetChangeNotifier) {
  6381. ((ItemSetChangeNotifier) datasource)
  6382. .addItemSetChangeListener(editorClosingItemSetListener);
  6383. }
  6384. }
  6385. private void setEditorField(Object propertyId, Field<?> field) {
  6386. checkColumnExists(propertyId);
  6387. Field<?> oldField = editorFieldGroup.getField(propertyId);
  6388. if (oldField != null) {
  6389. editorFieldGroup.unbind(oldField);
  6390. oldField.setParent(null);
  6391. }
  6392. if (field != null) {
  6393. field.setParent(this);
  6394. editorFieldGroup.bind(field, propertyId);
  6395. }
  6396. // Store field for this property for future reference
  6397. editorFields.put(propertyId, field);
  6398. }
  6399. /**
  6400. * Saves all changes done to the bound fields.
  6401. * <p>
  6402. * <em>Note:</em> This is a pass-through call to the backing field group.
  6403. *
  6404. * @throws CommitException
  6405. * If the commit was aborted
  6406. *
  6407. * @see FieldGroup#commit()
  6408. */
  6409. public void saveEditor() throws CommitException {
  6410. try {
  6411. editorSaving = true;
  6412. editorFieldGroup.commit();
  6413. } finally {
  6414. editorSaving = false;
  6415. }
  6416. }
  6417. /**
  6418. * Cancels the currently active edit if any. Hides the editor and discards
  6419. * possible unsaved changes in the editor fields.
  6420. */
  6421. public void cancelEditor() {
  6422. if (editorSaving) {
  6423. // If the editor is already saving the values, it's too late to
  6424. // cancel it. This prevents item set changes from propagating during
  6425. // save, causing discard to be run during commit.
  6426. return;
  6427. }
  6428. if (isEditorActive()) {
  6429. getEditorRpc()
  6430. .cancel(getContainerDataSource().indexOfId(editedItemId));
  6431. doCancelEditor();
  6432. }
  6433. }
  6434. protected void doCancelEditor() {
  6435. editedItemId = null;
  6436. editorActive = false;
  6437. editorFieldGroup.discard();
  6438. editorFieldGroup.setItemDataSource(null);
  6439. if (datasource instanceof ItemSetChangeNotifier) {
  6440. ((ItemSetChangeNotifier) datasource)
  6441. .removeItemSetChangeListener(editorClosingItemSetListener);
  6442. }
  6443. // Mark Grid as dirty so the client side gets to know that the editors
  6444. // are no longer attached
  6445. markAsDirty();
  6446. }
  6447. void resetEditor() {
  6448. if (isEditorActive()) {
  6449. /*
  6450. * Simply force cancel the editing; throwing here would just make
  6451. * Grid.setContainerDataSource semantics more complicated.
  6452. */
  6453. cancelEditor();
  6454. }
  6455. for (Field<?> editor : getEditorFields()) {
  6456. editor.setParent(null);
  6457. }
  6458. editedItemId = null;
  6459. editorActive = false;
  6460. editorFieldGroup = new CustomFieldGroup();
  6461. }
  6462. /**
  6463. * Gets a collection of all fields bound to the item editor of this grid.
  6464. * <p>
  6465. * When {@link #editItem(Object) editItem} is called, fields are
  6466. * automatically created and bound to any unbound properties.
  6467. *
  6468. * @return a collection of all the fields bound to the item editor
  6469. */
  6470. Collection<Field<?>> getEditorFields() {
  6471. Collection<Field<?>> fields = editorFieldGroup.getFields();
  6472. assert allAttached(fields);
  6473. return fields;
  6474. }
  6475. private boolean allAttached(Collection<? extends Component> components) {
  6476. for (Component component : components) {
  6477. if (component.getParent() != this) {
  6478. return false;
  6479. }
  6480. }
  6481. return true;
  6482. }
  6483. /**
  6484. * Sets the field factory for the {@link FieldGroup}. The field factory is
  6485. * only used when {@link FieldGroup} creates a new field.
  6486. * <p>
  6487. * <em>Note:</em> This is a pass-through call to the backing field group.
  6488. *
  6489. * @param fieldFactory
  6490. * The field factory to use
  6491. */
  6492. public void setEditorFieldFactory(FieldGroupFieldFactory fieldFactory) {
  6493. editorFieldGroup.setFieldFactory(fieldFactory);
  6494. }
  6495. /**
  6496. * Sets the error handler for the editor.
  6497. *
  6498. * The error handler is called whenever there is an exception in the editor.
  6499. *
  6500. * @param editorErrorHandler
  6501. * The editor error handler to use
  6502. * @throws IllegalArgumentException
  6503. * if the error handler is null
  6504. */
  6505. public void setEditorErrorHandler(EditorErrorHandler editorErrorHandler)
  6506. throws IllegalArgumentException {
  6507. if (editorErrorHandler == null) {
  6508. throw new IllegalArgumentException(
  6509. "The error handler cannot be null");
  6510. }
  6511. this.editorErrorHandler = editorErrorHandler;
  6512. }
  6513. /**
  6514. * Gets the error handler used for the editor
  6515. *
  6516. * @see #setErrorHandler(com.vaadin.server.ErrorHandler)
  6517. * @return the editor error handler, never null
  6518. */
  6519. public EditorErrorHandler getEditorErrorHandler() {
  6520. return editorErrorHandler;
  6521. }
  6522. /**
  6523. * Gets the field factory for the {@link FieldGroup}. The field factory is
  6524. * only used when {@link FieldGroup} creates a new field.
  6525. * <p>
  6526. * <em>Note:</em> This is a pass-through call to the backing field group.
  6527. *
  6528. * @return The field factory in use
  6529. */
  6530. public FieldGroupFieldFactory getEditorFieldFactory() {
  6531. return editorFieldGroup.getFieldFactory();
  6532. }
  6533. /**
  6534. * Sets the caption on the save button in the Grid editor.
  6535. *
  6536. * @param saveCaption
  6537. * the caption to set
  6538. * @throws IllegalArgumentException
  6539. * if {@code saveCaption} is {@code null}
  6540. */
  6541. public void setEditorSaveCaption(String saveCaption)
  6542. throws IllegalArgumentException {
  6543. if (saveCaption == null) {
  6544. throw new IllegalArgumentException("Save caption cannot be null");
  6545. }
  6546. getState().editorSaveCaption = saveCaption;
  6547. }
  6548. /**
  6549. * Gets the current caption of the save button in the Grid editor.
  6550. *
  6551. * @return the current caption of the save button
  6552. */
  6553. public String getEditorSaveCaption() {
  6554. return getState(false).editorSaveCaption;
  6555. }
  6556. /**
  6557. * Sets the caption on the cancel button in the Grid editor.
  6558. *
  6559. * @param cancelCaption
  6560. * the caption to set
  6561. * @throws IllegalArgumentException
  6562. * if {@code cancelCaption} is {@code null}
  6563. */
  6564. public void setEditorCancelCaption(String cancelCaption)
  6565. throws IllegalArgumentException {
  6566. if (cancelCaption == null) {
  6567. throw new IllegalArgumentException("Cancel caption cannot be null");
  6568. }
  6569. getState().editorCancelCaption = cancelCaption;
  6570. }
  6571. /**
  6572. * Gets the current caption of the cancel button in the Grid editor.
  6573. *
  6574. * @return the current caption of the cancel button
  6575. */
  6576. public String getEditorCancelCaption() {
  6577. return getState(false).editorCancelCaption;
  6578. }
  6579. /**
  6580. * Sets the buffered editor mode. The default mode is buffered (
  6581. * <code>true</code>).
  6582. *
  6583. * @since 7.6
  6584. * @param editorBuffered
  6585. * <code>true</code> to enable buffered editor,
  6586. * <code>false</code> to disable it
  6587. * @throws IllegalStateException
  6588. * If editor is active while attempting to change the buffered
  6589. * mode.
  6590. */
  6591. public void setEditorBuffered(boolean editorBuffered)
  6592. throws IllegalStateException {
  6593. if (isEditorActive()) {
  6594. throw new IllegalStateException(
  6595. "Can't change editor unbuffered mode while editor is active.");
  6596. }
  6597. getState().editorBuffered = editorBuffered;
  6598. editorFieldGroup.setBuffered(editorBuffered);
  6599. }
  6600. /**
  6601. * Gets the buffered editor mode.
  6602. *
  6603. * @since 7.6
  6604. * @return <code>true</code> if buffered editor is enabled,
  6605. * <code>false</code> otherwise
  6606. */
  6607. public boolean isEditorBuffered() {
  6608. return getState(false).editorBuffered;
  6609. }
  6610. @Override
  6611. public void addItemClickListener(ItemClickListener listener) {
  6612. addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
  6613. listener, ItemClickEvent.ITEM_CLICK_METHOD);
  6614. }
  6615. @Override
  6616. @Deprecated
  6617. public void addListener(ItemClickListener listener) {
  6618. addItemClickListener(listener);
  6619. }
  6620. @Override
  6621. public void removeItemClickListener(ItemClickListener listener) {
  6622. removeListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
  6623. listener);
  6624. }
  6625. @Override
  6626. @Deprecated
  6627. public void removeListener(ItemClickListener listener) {
  6628. removeItemClickListener(listener);
  6629. }
  6630. /**
  6631. * Requests that the column widths should be recalculated.
  6632. * <p>
  6633. * In most cases Grid will know when column widths need to be recalculated
  6634. * but this method can be used to force recalculation in situations when
  6635. * grid does not recalculate automatically.
  6636. *
  6637. * @since 7.4.1
  6638. */
  6639. public void recalculateColumnWidths() {
  6640. getRpcProxy(GridClientRpc.class).recalculateColumnWidths();
  6641. }
  6642. /**
  6643. * Registers a new column visibility change listener
  6644. *
  6645. * @since 7.5.0
  6646. * @param listener
  6647. * the listener to register
  6648. */
  6649. public void addColumnVisibilityChangeListener(
  6650. ColumnVisibilityChangeListener listener) {
  6651. addListener(ColumnVisibilityChangeEvent.class, listener,
  6652. COLUMN_VISIBILITY_METHOD);
  6653. }
  6654. /**
  6655. * Removes a previously registered column visibility change listener
  6656. *
  6657. * @since 7.5.0
  6658. * @param listener
  6659. * the listener to remove
  6660. */
  6661. public void removeColumnVisibilityChangeListener(
  6662. ColumnVisibilityChangeListener listener) {
  6663. removeListener(ColumnVisibilityChangeEvent.class, listener,
  6664. COLUMN_VISIBILITY_METHOD);
  6665. }
  6666. private void fireColumnVisibilityChangeEvent(Column column, boolean hidden,
  6667. boolean isUserOriginated) {
  6668. fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden,
  6669. isUserOriginated));
  6670. }
  6671. /**
  6672. * Sets a new details generator for row details.
  6673. * <p>
  6674. * The currently opened row details will be re-rendered.
  6675. *
  6676. * @since 7.5.0
  6677. * @param detailsGenerator
  6678. * the details generator to set
  6679. * @throws IllegalArgumentException
  6680. * if detailsGenerator is <code>null</code>;
  6681. */
  6682. public void setDetailsGenerator(DetailsGenerator detailsGenerator)
  6683. throws IllegalArgumentException {
  6684. detailComponentManager.setDetailsGenerator(detailsGenerator);
  6685. }
  6686. /**
  6687. * Gets the current details generator for row details.
  6688. *
  6689. * @since 7.5.0
  6690. * @return the detailsGenerator the current details generator
  6691. */
  6692. public DetailsGenerator getDetailsGenerator() {
  6693. return detailComponentManager.getDetailsGenerator();
  6694. }
  6695. /**
  6696. * Shows or hides the details for a specific item.
  6697. *
  6698. * @since 7.5.0
  6699. * @param itemId
  6700. * the id of the item for which to set details visibility
  6701. * @param visible
  6702. * <code>true</code> to show the details, or <code>false</code>
  6703. * to hide them
  6704. */
  6705. public void setDetailsVisible(Object itemId, boolean visible) {
  6706. detailComponentManager.setDetailsVisible(itemId, visible);
  6707. }
  6708. /**
  6709. * Checks whether details are visible for the given item.
  6710. *
  6711. * @since 7.5.0
  6712. * @param itemId
  6713. * the id of the item for which to check details visibility
  6714. * @return <code>true</code> iff the details are visible
  6715. */
  6716. public boolean isDetailsVisible(Object itemId) {
  6717. return detailComponentManager.isDetailsVisible(itemId);
  6718. }
  6719. private static SelectionMode getDefaultSelectionMode() {
  6720. return SelectionMode.SINGLE;
  6721. }
  6722. @Override
  6723. public void readDesign(Element design, DesignContext context) {
  6724. super.readDesign(design, context);
  6725. Attributes attrs = design.attributes();
  6726. if (attrs.hasKey("editable")) {
  6727. setEditorEnabled(DesignAttributeHandler.readAttribute("editable",
  6728. attrs, boolean.class));
  6729. }
  6730. if (attrs.hasKey("rows")) {
  6731. setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs,
  6732. double.class));
  6733. setHeightMode(HeightMode.ROW);
  6734. }
  6735. if (attrs.hasKey("selection-mode")) {
  6736. setSelectionMode(DesignAttributeHandler.readAttribute(
  6737. "selection-mode", attrs, SelectionMode.class));
  6738. }
  6739. if (design.children().size() > 0) {
  6740. if (design.children().size() > 1
  6741. || !design.child(0).tagName().equals("table")) {
  6742. throw new DesignException(
  6743. "Grid needs to have a table element as its only child");
  6744. }
  6745. Element table = design.child(0);
  6746. Elements colgroups = table.getElementsByTag("colgroup");
  6747. if (colgroups.size() != 1) {
  6748. throw new DesignException(
  6749. "Table element in declarative Grid needs to have a"
  6750. + " colgroup defining the columns used in Grid");
  6751. }
  6752. int i = 0;
  6753. for (Element col : colgroups.get(0).getElementsByTag("col")) {
  6754. String propertyId = DesignAttributeHandler.readAttribute(
  6755. "property-id", col.attributes(), "property-" + i,
  6756. String.class);
  6757. addColumn(propertyId, String.class).readDesign(col, context);
  6758. ++i;
  6759. }
  6760. for (Element child : table.children()) {
  6761. if (child.tagName().equals("thead")) {
  6762. header.readDesign(child, context);
  6763. } else if (child.tagName().equals("tbody")) {
  6764. for (Element row : child.children()) {
  6765. Elements cells = row.children();
  6766. Object[] data = new String[cells.size()];
  6767. for (int c = 0; c < cells.size(); ++c) {
  6768. data[c] = cells.get(c).html();
  6769. }
  6770. addRow(data);
  6771. }
  6772. // Since inline data is used, set HTML renderer for columns
  6773. for (Column c : getColumns()) {
  6774. c.setRenderer(new HtmlRenderer());
  6775. }
  6776. } else if (child.tagName().equals("tfoot")) {
  6777. footer.readDesign(child, context);
  6778. }
  6779. }
  6780. }
  6781. // Read frozen columns after columns are read.
  6782. if (attrs.hasKey("frozen-columns")) {
  6783. setFrozenColumnCount(DesignAttributeHandler
  6784. .readAttribute("frozen-columns", attrs, int.class));
  6785. }
  6786. }
  6787. @Override
  6788. public void writeDesign(Element design, DesignContext context) {
  6789. super.writeDesign(design, context);
  6790. Attributes attrs = design.attributes();
  6791. Grid def = context.getDefaultInstance(this);
  6792. DesignAttributeHandler.writeAttribute("editable", attrs,
  6793. isEditorEnabled(), def.isEditorEnabled(), boolean.class);
  6794. DesignAttributeHandler.writeAttribute("frozen-columns", attrs,
  6795. getFrozenColumnCount(), def.getFrozenColumnCount(), int.class);
  6796. if (getHeightMode() == HeightMode.ROW) {
  6797. DesignAttributeHandler.writeAttribute("rows", attrs,
  6798. getHeightByRows(), def.getHeightByRows(), double.class);
  6799. }
  6800. SelectionMode selectionMode = null;
  6801. if (selectionModel.getClass().equals(SingleSelectionModel.class)) {
  6802. selectionMode = SelectionMode.SINGLE;
  6803. } else if (selectionModel.getClass()
  6804. .equals(MultiSelectionModel.class)) {
  6805. selectionMode = SelectionMode.MULTI;
  6806. } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
  6807. selectionMode = SelectionMode.NONE;
  6808. }
  6809. assert selectionMode != null : "Unexpected selection model "
  6810. + selectionModel.getClass().getName();
  6811. DesignAttributeHandler.writeAttribute("selection-mode", attrs,
  6812. selectionMode, getDefaultSelectionMode(), SelectionMode.class);
  6813. if (columns.isEmpty()) {
  6814. // Empty grid. Structure not needed.
  6815. return;
  6816. }
  6817. // Do structure.
  6818. Element tableElement = design.appendElement("table");
  6819. Element colGroup = tableElement.appendElement("colgroup");
  6820. List<Column> columnOrder = getColumns();
  6821. for (int i = 0; i < columnOrder.size(); ++i) {
  6822. Column column = columnOrder.get(i);
  6823. Element colElement = colGroup.appendElement("col");
  6824. column.writeDesign(colElement, context);
  6825. }
  6826. // Always write thead. Reads correctly when there no header rows
  6827. header.writeDesign(tableElement.appendElement("thead"), context);
  6828. if (context.shouldWriteData(this)) {
  6829. Element bodyElement = tableElement.appendElement("tbody");
  6830. for (Object itemId : datasource.getItemIds()) {
  6831. Element tableRow = bodyElement.appendElement("tr");
  6832. for (Column c : getColumns()) {
  6833. Object value = datasource.getItem(itemId)
  6834. .getItemProperty(c.getPropertyId()).getValue();
  6835. tableRow.appendElement("td")
  6836. .append((value != null ? DesignFormatter
  6837. .encodeForTextNode(value.toString()) : ""));
  6838. }
  6839. }
  6840. }
  6841. if (footer.getRowCount() > 0) {
  6842. footer.writeDesign(tableElement.appendElement("tfoot"), context);
  6843. }
  6844. }
  6845. @Override
  6846. protected Collection<String> getCustomAttributes() {
  6847. Collection<String> result = super.getCustomAttributes();
  6848. result.add("editor-enabled");
  6849. result.add("editable");
  6850. result.add("frozen-column-count");
  6851. result.add("frozen-columns");
  6852. result.add("height-by-rows");
  6853. result.add("rows");
  6854. result.add("selection-mode");
  6855. result.add("header-visible");
  6856. result.add("footer-visible");
  6857. result.add("editor-error-handler");
  6858. result.add("height-mode");
  6859. return result;
  6860. }
  6861. }