您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

VScrollTable.java 103KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Iterator;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.Set;
  12. import com.google.gwt.dom.client.Document;
  13. import com.google.gwt.dom.client.NodeList;
  14. import com.google.gwt.dom.client.TableCellElement;
  15. import com.google.gwt.dom.client.TableRowElement;
  16. import com.google.gwt.dom.client.TableSectionElement;
  17. import com.google.gwt.user.client.Command;
  18. import com.google.gwt.user.client.DOM;
  19. import com.google.gwt.user.client.DeferredCommand;
  20. import com.google.gwt.user.client.Element;
  21. import com.google.gwt.user.client.Event;
  22. import com.google.gwt.user.client.Timer;
  23. import com.google.gwt.user.client.Window;
  24. import com.google.gwt.user.client.ui.FlowPanel;
  25. import com.google.gwt.user.client.ui.Panel;
  26. import com.google.gwt.user.client.ui.RootPanel;
  27. import com.google.gwt.user.client.ui.ScrollListener;
  28. import com.google.gwt.user.client.ui.ScrollPanel;
  29. import com.google.gwt.user.client.ui.Widget;
  30. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  31. import com.vaadin.terminal.gwt.client.BrowserInfo;
  32. import com.vaadin.terminal.gwt.client.Container;
  33. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  34. import com.vaadin.terminal.gwt.client.Paintable;
  35. import com.vaadin.terminal.gwt.client.RenderSpace;
  36. import com.vaadin.terminal.gwt.client.UIDL;
  37. import com.vaadin.terminal.gwt.client.Util;
  38. import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
  39. /**
  40. * VScrollTable
  41. *
  42. * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
  43. * ScrollPanel
  44. *
  45. * TableHead contains table's header and widgets + logic for resizing,
  46. * reordering and hiding columns.
  47. *
  48. * ScrollPanel contains VScrollTableBody object which handles content. To save
  49. * some bandwidth and to improve clients responsiveness with loads of data, in
  50. * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
  51. * VScrollTableBody to use the exact same space as non-rendered rows would use.
  52. * This way we can use seamlessly traditional scrollbars and scrolling to fetch
  53. * more rows instead of "paging".
  54. *
  55. * In VScrollTable we listen to scroll events. On horizontal scrolling we also
  56. * update TableHeads scroll position which has its scrollbars hidden. On
  57. * vertical scroll events we will check if we are reaching the end of area where
  58. * we have rows rendered and
  59. *
  60. * TODO implement unregistering for child components in Cells
  61. */
  62. public class VScrollTable extends FlowPanel implements Table, ScrollListener {
  63. public static final String CLASSNAME = "v-table";
  64. /**
  65. * multiple of pagelength which component will cache when requesting more
  66. * rows
  67. */
  68. private static final double CACHE_RATE = 2;
  69. /**
  70. * fraction of pageLenght which can be scrolled without making new request
  71. */
  72. private static final double CACHE_REACT_RATE = 1.5;
  73. public static final char ALIGN_CENTER = 'c';
  74. public static final char ALIGN_LEFT = 'b';
  75. public static final char ALIGN_RIGHT = 'e';
  76. private int firstRowInViewPort = 0;
  77. private int pageLength = 15;
  78. private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
  79. private boolean showRowHeaders = false;
  80. private String[] columnOrder;
  81. private ApplicationConnection client;
  82. private String paintableId;
  83. private boolean immediate;
  84. private int selectMode = Table.SELECT_MODE_NONE;
  85. private final HashSet<String> selectedRowKeys = new HashSet<String>();
  86. private boolean initializedAndAttached = false;
  87. /**
  88. * Flag to indicate if a column width recalculation is needed due update.
  89. */
  90. private boolean headerChangedDuringUpdate = false;
  91. private final TableHead tHead = new TableHead();
  92. private final ScrollPanel bodyContainer = new ScrollPanel();
  93. private int totalRows;
  94. private Set<String> collapsedColumns;
  95. private final RowRequestHandler rowRequestHandler;
  96. private VScrollTableBody tBody;
  97. private int firstvisible = 0;
  98. private boolean sortAscending;
  99. private String sortColumn;
  100. private boolean columnReordering;
  101. /**
  102. * This map contains captions and icon urls for actions like: * "33_c" ->
  103. * "Edit" * "33_i" -> "http://dom.com/edit.png"
  104. */
  105. private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
  106. private String[] visibleColOrder;
  107. private boolean initialContentReceived = false;
  108. private Element scrollPositionElement;
  109. private boolean enabled;
  110. private boolean showColHeaders;
  111. /** flag to indicate that table body has changed */
  112. private boolean isNewBody = true;
  113. private boolean emitClickEvents;
  114. /*
  115. * Read from the "recalcWidths" -attribute. When it is true, the table will
  116. * recalculate the widths for columns - desirable in some cases. For #1983,
  117. * marked experimental.
  118. */
  119. boolean recalcWidths = false;
  120. private final ArrayList<Panel> lazyUnregistryBag = new ArrayList<Panel>();
  121. private String height;
  122. private String width = "";
  123. private boolean rendering = false;
  124. public VScrollTable() {
  125. bodyContainer.addScrollListener(this);
  126. bodyContainer.setStyleName(CLASSNAME + "-body");
  127. setStyleName(CLASSNAME);
  128. add(tHead);
  129. add(bodyContainer);
  130. rowRequestHandler = new RowRequestHandler();
  131. }
  132. @SuppressWarnings("unchecked")
  133. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  134. rendering = true;
  135. if (client.updateComponent(this, uidl, true)) {
  136. rendering = false;
  137. return;
  138. }
  139. // we may have pending cache row fetch, cancel it. See #2136
  140. rowRequestHandler.cancel();
  141. enabled = !uidl.hasAttribute("disabled");
  142. this.client = client;
  143. paintableId = uidl.getStringAttribute("id");
  144. immediate = uidl.getBooleanAttribute("immediate");
  145. emitClickEvents = uidl.getBooleanAttribute("listenClicks");
  146. final int newTotalRows = uidl.getIntAttribute("totalrows");
  147. if (newTotalRows != totalRows) {
  148. if (tBody != null) {
  149. if (totalRows == 0) {
  150. tHead.clear();
  151. }
  152. initializedAndAttached = false;
  153. initialContentReceived = false;
  154. isNewBody = true;
  155. }
  156. totalRows = newTotalRows;
  157. }
  158. recalcWidths = uidl.hasAttribute("recalcWidths");
  159. pageLength = uidl.getIntAttribute("pagelength");
  160. if (pageLength == 0) {
  161. pageLength = totalRows;
  162. }
  163. firstvisible = uidl.hasVariable("firstvisible") ? uidl
  164. .getIntVariable("firstvisible") : 0;
  165. if (firstvisible != lastRequestedFirstvisible && tBody != null) {
  166. // received 'surprising' firstvisible from server: scroll there
  167. firstRowInViewPort = firstvisible;
  168. bodyContainer
  169. .setScrollPosition(firstvisible * tBody.getRowHeight());
  170. }
  171. showRowHeaders = uidl.getBooleanAttribute("rowheaders");
  172. showColHeaders = uidl.getBooleanAttribute("colheaders");
  173. if (uidl.hasVariable("sortascending")) {
  174. sortAscending = uidl.getBooleanVariable("sortascending");
  175. sortColumn = uidl.getStringVariable("sortcolumn");
  176. }
  177. if (uidl.hasVariable("selected")) {
  178. final Set<String> selectedKeys = uidl
  179. .getStringArrayVariableAsSet("selected");
  180. selectedRowKeys.clear();
  181. for (String string : selectedKeys) {
  182. selectedRowKeys.add(string);
  183. }
  184. }
  185. if (uidl.hasAttribute("selectmode")) {
  186. if (uidl.getBooleanAttribute("readonly")) {
  187. selectMode = Table.SELECT_MODE_NONE;
  188. } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
  189. selectMode = Table.SELECT_MODE_MULTI;
  190. } else if (uidl.getStringAttribute("selectmode").equals("single")) {
  191. selectMode = Table.SELECT_MODE_SINGLE;
  192. } else {
  193. selectMode = Table.SELECT_MODE_NONE;
  194. }
  195. }
  196. if (uidl.hasVariable("columnorder")) {
  197. columnReordering = true;
  198. columnOrder = uidl.getStringArrayVariable("columnorder");
  199. }
  200. if (uidl.hasVariable("collapsedcolumns")) {
  201. tHead.setColumnCollapsingAllowed(true);
  202. collapsedColumns = uidl
  203. .getStringArrayVariableAsSet("collapsedcolumns");
  204. } else {
  205. tHead.setColumnCollapsingAllowed(false);
  206. }
  207. UIDL rowData = null;
  208. for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
  209. final UIDL c = (UIDL) it.next();
  210. if (c.getTag().equals("rows")) {
  211. rowData = c;
  212. } else if (c.getTag().equals("actions")) {
  213. updateActionMap(c);
  214. } else if (c.getTag().equals("visiblecolumns")) {
  215. tHead.updateCellsFromUIDL(c);
  216. }
  217. }
  218. updateHeader(uidl.getStringArrayAttribute("vcolorder"));
  219. if (!recalcWidths && initializedAndAttached) {
  220. updateBody(rowData, uidl.getIntAttribute("firstrow"), uidl
  221. .getIntAttribute("rows"));
  222. if (headerChangedDuringUpdate) {
  223. lazyAdjustColumnWidths.schedule(1);
  224. }
  225. } else {
  226. if (tBody != null) {
  227. tBody.removeFromParent();
  228. lazyUnregistryBag.add(tBody);
  229. }
  230. tBody = new VScrollTableBody();
  231. tBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
  232. uidl.getIntAttribute("rows"));
  233. bodyContainer.add(tBody);
  234. initialContentReceived = true;
  235. if (isAttached()) {
  236. sizeInit();
  237. }
  238. }
  239. if (selectMode == Table.SELECT_MODE_NONE) {
  240. tBody.addStyleName(CLASSNAME + "-body-noselection");
  241. } else {
  242. tBody.removeStyleName(CLASSNAME + "-body-noselection");
  243. }
  244. hideScrollPositionAnnotation();
  245. purgeUnregistryBag();
  246. rendering = false;
  247. headerChangedDuringUpdate = false;
  248. }
  249. /**
  250. * Unregisters Paintables in "trashed" HasWidgets (IScrollTableBodys or
  251. * IScrollTableRows). This is done lazily as Table must survive from
  252. * "subtreecaching" logic.
  253. */
  254. private void purgeUnregistryBag() {
  255. for (Iterator<Panel> iterator = lazyUnregistryBag.iterator(); iterator
  256. .hasNext();) {
  257. client.unregisterChildPaintables(iterator.next());
  258. }
  259. lazyUnregistryBag.clear();
  260. }
  261. private void updateActionMap(UIDL c) {
  262. final Iterator<?> it = c.getChildIterator();
  263. while (it.hasNext()) {
  264. final UIDL action = (UIDL) it.next();
  265. final String key = action.getStringAttribute("key");
  266. final String caption = action.getStringAttribute("caption");
  267. actionMap.put(key + "_c", caption);
  268. if (action.hasAttribute("icon")) {
  269. // TODO need some uri handling ??
  270. actionMap.put(key + "_i", client.translateVaadinUri(action
  271. .getStringAttribute("icon")));
  272. }
  273. }
  274. }
  275. public String getActionCaption(String actionKey) {
  276. return actionMap.get(actionKey + "_c");
  277. }
  278. public String getActionIcon(String actionKey) {
  279. return actionMap.get(actionKey + "_i");
  280. }
  281. private void updateHeader(String[] strings) {
  282. if (strings == null) {
  283. return;
  284. }
  285. int visibleCols = strings.length;
  286. int colIndex = 0;
  287. if (showRowHeaders) {
  288. tHead.enableColumn("0", colIndex);
  289. visibleCols++;
  290. visibleColOrder = new String[visibleCols];
  291. visibleColOrder[colIndex] = "0";
  292. colIndex++;
  293. } else {
  294. visibleColOrder = new String[visibleCols];
  295. tHead.removeCell("0");
  296. }
  297. int i;
  298. for (i = 0; i < strings.length; i++) {
  299. final String cid = strings[i];
  300. visibleColOrder[colIndex] = cid;
  301. tHead.enableColumn(cid, colIndex);
  302. colIndex++;
  303. }
  304. tHead.setVisible(showColHeaders);
  305. }
  306. /**
  307. * @param uidl
  308. * which contains row data
  309. * @param firstRow
  310. * first row in data set
  311. * @param reqRows
  312. * amount of rows in data set
  313. */
  314. private void updateBody(UIDL uidl, int firstRow, int reqRows) {
  315. if (uidl == null || reqRows < 1) {
  316. // container is empty, remove possibly existing rows
  317. if (firstRow < 0) {
  318. while (tBody.getLastRendered() > tBody.firstRendered) {
  319. tBody.unlinkRow(false);
  320. }
  321. tBody.unlinkRow(false);
  322. }
  323. return;
  324. }
  325. tBody.renderRows(uidl, firstRow, reqRows);
  326. final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
  327. * CACHE_RATE);
  328. boolean cont = true;
  329. while (cont && tBody.getLastRendered() > optimalFirstRow
  330. && tBody.getFirstRendered() < optimalFirstRow) {
  331. // client.console.log("removing row from start");
  332. cont = tBody.unlinkRow(true);
  333. }
  334. final int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength
  335. * CACHE_RATE);
  336. cont = true;
  337. while (cont && tBody.getLastRendered() > optimalLastRow) {
  338. // client.console.log("removing row from the end");
  339. cont = tBody.unlinkRow(false);
  340. }
  341. tBody.fixSpacers();
  342. }
  343. /**
  344. * Gives correct column index for given column key ("cid" in UIDL).
  345. *
  346. * @param colKey
  347. * @return column index of visible columns, -1 if column not visible
  348. */
  349. private int getColIndexByKey(String colKey) {
  350. // return 0 if asked for rowHeaders
  351. if ("0".equals(colKey)) {
  352. return 0;
  353. }
  354. for (int i = 0; i < visibleColOrder.length; i++) {
  355. if (visibleColOrder[i].equals(colKey)) {
  356. return i;
  357. }
  358. }
  359. return -1;
  360. }
  361. private boolean isCollapsedColumn(String colKey) {
  362. if (collapsedColumns == null) {
  363. return false;
  364. }
  365. if (collapsedColumns.contains(colKey)) {
  366. return true;
  367. }
  368. return false;
  369. }
  370. private String getColKeyByIndex(int index) {
  371. return tHead.getHeaderCell(index).getColKey();
  372. }
  373. private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
  374. final HeaderCell cell = tHead.getHeaderCell(colIndex);
  375. cell.setWidth(w, isDefinedWidth);
  376. tBody.setColWidth(colIndex, w);
  377. }
  378. private int getColWidth(String colKey) {
  379. return tHead.getHeaderCell(colKey).getWidth();
  380. }
  381. private VScrollTableRow getRenderedRowByKey(String key) {
  382. final Iterator<Widget> it = tBody.iterator();
  383. VScrollTableRow r = null;
  384. while (it.hasNext()) {
  385. r = (VScrollTableRow) it.next();
  386. if (r.getKey().equals(key)) {
  387. return r;
  388. }
  389. }
  390. return null;
  391. }
  392. private void reOrderColumn(String columnKey, int newIndex) {
  393. final int oldIndex = getColIndexByKey(columnKey);
  394. // Change header order
  395. tHead.moveCell(oldIndex, newIndex);
  396. // Change body order
  397. tBody.moveCol(oldIndex, newIndex);
  398. /*
  399. * Build new columnOrder and update it to server Note that columnOrder
  400. * also contains collapsed columns so we cannot directly build it from
  401. * cells vector Loop the old columnOrder and append in order to new
  402. * array unless on moved columnKey. On new index also put the moved key
  403. * i == index on columnOrder, j == index on newOrder
  404. */
  405. final String oldKeyOnNewIndex = visibleColOrder[newIndex];
  406. if (showRowHeaders) {
  407. newIndex--; // columnOrder don't have rowHeader
  408. }
  409. // add back hidden rows,
  410. for (int i = 0; i < columnOrder.length; i++) {
  411. if (columnOrder[i].equals(oldKeyOnNewIndex)) {
  412. break; // break loop at target
  413. }
  414. if (isCollapsedColumn(columnOrder[i])) {
  415. newIndex++;
  416. }
  417. }
  418. // finally we can build the new columnOrder for server
  419. final String[] newOrder = new String[columnOrder.length];
  420. for (int i = 0, j = 0; j < newOrder.length; i++) {
  421. if (j == newIndex) {
  422. newOrder[j] = columnKey;
  423. j++;
  424. }
  425. if (i == columnOrder.length) {
  426. break;
  427. }
  428. if (columnOrder[i].equals(columnKey)) {
  429. continue;
  430. }
  431. newOrder[j] = columnOrder[i];
  432. j++;
  433. }
  434. columnOrder = newOrder;
  435. // also update visibleColumnOrder
  436. int i = showRowHeaders ? 1 : 0;
  437. for (int j = 0; j < newOrder.length; j++) {
  438. final String cid = newOrder[j];
  439. if (!isCollapsedColumn(cid)) {
  440. visibleColOrder[i++] = cid;
  441. }
  442. }
  443. client.updateVariable(paintableId, "columnorder", columnOrder, false);
  444. }
  445. @Override
  446. protected void onAttach() {
  447. super.onAttach();
  448. if (initialContentReceived) {
  449. sizeInit();
  450. }
  451. }
  452. @Override
  453. protected void onDetach() {
  454. rowRequestHandler.cancel();
  455. super.onDetach();
  456. // ensure that scrollPosElement will be detached
  457. if (scrollPositionElement != null) {
  458. final Element parent = DOM.getParent(scrollPositionElement);
  459. if (parent != null) {
  460. DOM.removeChild(parent, scrollPositionElement);
  461. }
  462. }
  463. }
  464. /**
  465. * Run only once when component is attached and received its initial
  466. * content. This function : * Syncs headers and bodys "natural widths and
  467. * saves the values. * Sets proper width and height * Makes deferred request
  468. * to get some cache rows
  469. */
  470. private void sizeInit() {
  471. /*
  472. * We will use browsers table rendering algorithm to find proper column
  473. * widths. If content and header take less space than available, we will
  474. * divide extra space relatively to each column which has not width set.
  475. *
  476. * Overflow pixels are added to last column.
  477. */
  478. Iterator<Widget> headCells = tHead.iterator();
  479. int i = 0;
  480. int totalExplicitColumnsWidths = 0;
  481. int total = 0;
  482. float expandRatioDivider = 0;
  483. final int[] widths = new int[tHead.visibleCells.size()];
  484. tHead.enableBrowserIntelligence();
  485. // first loop: collect natural widths
  486. while (headCells.hasNext()) {
  487. final HeaderCell hCell = (HeaderCell) headCells.next();
  488. int w = hCell.getWidth();
  489. if (hCell.isDefinedWidth()) {
  490. // server has defined column width explicitly
  491. totalExplicitColumnsWidths += w;
  492. } else {
  493. if (hCell.getExpandRatio() > 0) {
  494. expandRatioDivider += hCell.getExpandRatio();
  495. w = 0;
  496. } else {
  497. // get and store greater of header width and column width,
  498. // and
  499. // store it as a minimumn natural col width
  500. w = hCell.getNaturalColumnWidth(i);
  501. }
  502. hCell.setNaturalMinimumColumnWidth(w);
  503. }
  504. widths[i] = w;
  505. total += w;
  506. i++;
  507. }
  508. tHead.disableBrowserIntelligence();
  509. boolean willHaveScrollbarz = willHaveScrollbars();
  510. // fix "natural" width if width not set
  511. if (width == null || "".equals(width)) {
  512. int w = total;
  513. w += tBody.getCellExtraWidth() * visibleColOrder.length;
  514. if (willHaveScrollbarz) {
  515. w += Util.getNativeScrollbarSize();
  516. }
  517. setContentWidth(w);
  518. }
  519. int availW = tBody.getAvailableWidth();
  520. if (BrowserInfo.get().isIE()) {
  521. // Hey IE, are you really sure about this?
  522. availW = tBody.getAvailableWidth();
  523. }
  524. availW -= tBody.getCellExtraWidth() * visibleColOrder.length;
  525. if (willHaveScrollbarz) {
  526. availW -= Util.getNativeScrollbarSize();
  527. }
  528. boolean needsReLayout = false;
  529. if (availW > total) {
  530. // natural size is smaller than available space
  531. final int extraSpace = availW - total;
  532. final int totalWidthR = total - totalExplicitColumnsWidths;
  533. if (totalWidthR > 0) {
  534. needsReLayout = true;
  535. if (expandRatioDivider > 0) {
  536. // visible columns have some active expand ratios, excess
  537. // space is divided according to them
  538. headCells = tHead.iterator();
  539. i = 0;
  540. while (headCells.hasNext()) {
  541. HeaderCell hCell = (HeaderCell) headCells.next();
  542. if (hCell.getExpandRatio() > 0) {
  543. int w = widths[i];
  544. final int newSpace = (int) (extraSpace * (hCell
  545. .getExpandRatio() / expandRatioDivider));
  546. w += newSpace;
  547. widths[i] = w;
  548. }
  549. i++;
  550. }
  551. } else {
  552. // now we will share this sum relatively to those without
  553. // explicit width
  554. headCells = tHead.iterator();
  555. i = 0;
  556. while (headCells.hasNext()) {
  557. HeaderCell hCell = (HeaderCell) headCells.next();
  558. if (!hCell.isDefinedWidth()) {
  559. int w = widths[i];
  560. final int newSpace = extraSpace * w / totalWidthR;
  561. w += newSpace;
  562. widths[i] = w;
  563. }
  564. i++;
  565. }
  566. }
  567. }
  568. } else {
  569. // bodys size will be more than available and scrollbar will appear
  570. }
  571. // last loop: set possibly modified values or reset if new tBody
  572. i = 0;
  573. headCells = tHead.iterator();
  574. while (headCells.hasNext()) {
  575. final HeaderCell hCell = (HeaderCell) headCells.next();
  576. if (isNewBody || hCell.getWidth() == -1) {
  577. final int w = widths[i];
  578. setColWidth(i, w, false);
  579. }
  580. i++;
  581. }
  582. if (needsReLayout) {
  583. tBody.reLayoutComponents();
  584. }
  585. /*
  586. * Fix "natural" height if height is not set. This must be after width
  587. * fixing so the components' widths have been adjusted.
  588. */
  589. if (height == null || "".equals(height)) {
  590. /*
  591. * We must force an update of the row height as this point as it
  592. * might have been (incorrectly) calculated earlier
  593. */
  594. if (pageLength == totalRows) {
  595. /*
  596. * A hack to support variable height rows when paging is off.
  597. * Generally this is not supported by scrolltable. We want to
  598. * show all rows so the bodyHeight should be equal to the table
  599. * height.
  600. */
  601. int bodyHeight = tBody.getOffsetHeight();
  602. bodyContainer.setHeight(bodyHeight + "px");
  603. Util.runWebkitOverflowAutoFix(bodyContainer.getElement());
  604. } else {
  605. int bodyHeight = (tBody.getRowHeight(true) * pageLength);
  606. bodyContainer.setHeight(bodyHeight + "px");
  607. }
  608. }
  609. isNewBody = false;
  610. if (firstvisible > 0) {
  611. // Deferred due some Firefox oddities. IE & Safari could survive
  612. // without
  613. DeferredCommand.addCommand(new Command() {
  614. public void execute() {
  615. bodyContainer.setScrollPosition(firstvisible
  616. * tBody.getRowHeight());
  617. firstRowInViewPort = firstvisible;
  618. }
  619. });
  620. }
  621. if (enabled) {
  622. // Do we need cache rows
  623. if (tBody.getLastRendered() + 1 < firstRowInViewPort + pageLength
  624. + CACHE_REACT_RATE * pageLength) {
  625. if (totalRows - 1 > tBody.getLastRendered()) {
  626. // fetch cache rows
  627. rowRequestHandler
  628. .setReqFirstRow(tBody.getLastRendered() + 1);
  629. rowRequestHandler
  630. .setReqRows((int) (pageLength * CACHE_RATE));
  631. rowRequestHandler.deferRowFetch(1);
  632. }
  633. }
  634. }
  635. initializedAndAttached = true;
  636. }
  637. private boolean willHaveScrollbars() {
  638. if (!(height != null && !height.equals(""))) {
  639. if (pageLength < totalRows) {
  640. return true;
  641. }
  642. } else {
  643. int fakeheight = tBody.getRowHeight() * totalRows;
  644. int availableHeight = bodyContainer.getElement().getPropertyInt(
  645. "clientHeight");
  646. if (fakeheight > availableHeight) {
  647. return true;
  648. }
  649. }
  650. return false;
  651. }
  652. /**
  653. * This method has logic which rows needs to be requested from server when
  654. * user scrolls
  655. */
  656. public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
  657. if (!initializedAndAttached) {
  658. return;
  659. }
  660. if (!enabled) {
  661. bodyContainer.setScrollPosition(firstRowInViewPort
  662. * tBody.getRowHeight());
  663. return;
  664. }
  665. rowRequestHandler.cancel();
  666. // fix headers horizontal scrolling
  667. tHead.setHorizontalScrollPosition(scrollLeft);
  668. firstRowInViewPort = (int) Math.ceil(scrollTop
  669. / (double) tBody.getRowHeight());
  670. int postLimit = (int) (firstRowInViewPort + pageLength + pageLength
  671. * CACHE_REACT_RATE);
  672. if (postLimit > totalRows - 1) {
  673. postLimit = totalRows - 1;
  674. }
  675. int preLimit = (int) (firstRowInViewPort - pageLength
  676. * CACHE_REACT_RATE);
  677. if (preLimit < 0) {
  678. preLimit = 0;
  679. }
  680. final int lastRendered = tBody.getLastRendered();
  681. final int firstRendered = tBody.getFirstRendered();
  682. if (postLimit <= lastRendered && preLimit >= firstRendered) {
  683. // remember which firstvisible we requested, in case the server has
  684. // a differing opinion
  685. lastRequestedFirstvisible = firstRowInViewPort;
  686. client.updateVariable(paintableId, "firstvisible",
  687. firstRowInViewPort, false);
  688. return; // scrolled withing "non-react area"
  689. }
  690. if (firstRowInViewPort - pageLength * CACHE_RATE > lastRendered
  691. || firstRowInViewPort + pageLength + pageLength * CACHE_RATE < firstRendered) {
  692. // need a totally new set
  693. rowRequestHandler
  694. .setReqFirstRow((int) (firstRowInViewPort - pageLength
  695. * CACHE_RATE));
  696. int last = firstRowInViewPort + (int) CACHE_RATE * pageLength
  697. + pageLength;
  698. if (last > totalRows) {
  699. last = totalRows - 1;
  700. }
  701. rowRequestHandler.setReqRows(last
  702. - rowRequestHandler.getReqFirstRow() + 1);
  703. rowRequestHandler.deferRowFetch();
  704. return;
  705. }
  706. if (preLimit < firstRendered) {
  707. // need some rows to the beginning of the rendered area
  708. rowRequestHandler
  709. .setReqFirstRow((int) (firstRowInViewPort - pageLength
  710. * CACHE_RATE));
  711. rowRequestHandler.setReqRows(firstRendered
  712. - rowRequestHandler.getReqFirstRow());
  713. rowRequestHandler.deferRowFetch();
  714. return;
  715. }
  716. if (postLimit > lastRendered) {
  717. // need some rows to the end of the rendered area
  718. rowRequestHandler.setReqFirstRow(lastRendered + 1);
  719. rowRequestHandler.setReqRows((int) ((firstRowInViewPort
  720. + pageLength + pageLength * CACHE_RATE) - lastRendered));
  721. rowRequestHandler.deferRowFetch();
  722. }
  723. }
  724. private void announceScrollPosition() {
  725. if (scrollPositionElement == null) {
  726. scrollPositionElement = DOM.createDiv();
  727. DOM.setElementProperty(scrollPositionElement, "className",
  728. CLASSNAME + "-scrollposition");
  729. DOM
  730. .setStyleAttribute(scrollPositionElement, "position",
  731. "absolute");
  732. DOM.appendChild(getElement(), scrollPositionElement);
  733. }
  734. DOM.setStyleAttribute(scrollPositionElement, "marginLeft", (DOM
  735. .getElementPropertyInt(getElement(), "offsetWidth") / 2 - 80)
  736. + "px");
  737. DOM.setStyleAttribute(scrollPositionElement, "marginTop", -(DOM
  738. .getElementPropertyInt(bodyContainer.getElement(),
  739. "offsetHeight"))
  740. + "px");
  741. // indexes go from 1-totalRows, as rowheaders in index-mode indicate
  742. int last = (firstRowInViewPort + (bodyContainer.getOffsetHeight() / tBody
  743. .getRowHeight()));
  744. if (last > totalRows) {
  745. last = totalRows;
  746. }
  747. DOM.setInnerHTML(scrollPositionElement, "<span>"
  748. + (firstRowInViewPort + 1) + " &ndash; " + last + "..."
  749. + "</span>");
  750. DOM.setStyleAttribute(scrollPositionElement, "display", "block");
  751. }
  752. private void hideScrollPositionAnnotation() {
  753. if (scrollPositionElement != null) {
  754. DOM.setStyleAttribute(scrollPositionElement, "display", "none");
  755. }
  756. }
  757. private class RowRequestHandler extends Timer {
  758. private int reqFirstRow = 0;
  759. private int reqRows = 0;
  760. public void deferRowFetch() {
  761. deferRowFetch(250);
  762. }
  763. public void deferRowFetch(int msec) {
  764. if (reqRows > 0 && reqFirstRow < totalRows) {
  765. schedule(msec);
  766. // tell scroll position to user if currently "visible" rows are
  767. // not rendered
  768. if ((firstRowInViewPort + pageLength > tBody.getLastRendered())
  769. || (firstRowInViewPort < tBody.getFirstRendered())) {
  770. announceScrollPosition();
  771. } else {
  772. hideScrollPositionAnnotation();
  773. }
  774. }
  775. }
  776. public void setReqFirstRow(int reqFirstRow) {
  777. if (reqFirstRow < 0) {
  778. reqFirstRow = 0;
  779. } else if (reqFirstRow >= totalRows) {
  780. reqFirstRow = totalRows - 1;
  781. }
  782. this.reqFirstRow = reqFirstRow;
  783. }
  784. public void setReqRows(int reqRows) {
  785. this.reqRows = reqRows;
  786. }
  787. @Override
  788. public void run() {
  789. if (client.hasActiveRequest()) {
  790. // if client connection is busy, don't bother loading it more
  791. schedule(250);
  792. } else {
  793. int firstToBeRendered = tBody.firstRendered;
  794. if (reqFirstRow < firstToBeRendered) {
  795. firstToBeRendered = reqFirstRow;
  796. } else if (firstRowInViewPort - (int) (CACHE_RATE * pageLength) > firstToBeRendered) {
  797. firstToBeRendered = firstRowInViewPort
  798. - (int) (CACHE_RATE * pageLength);
  799. if (firstToBeRendered < 0) {
  800. firstToBeRendered = 0;
  801. }
  802. }
  803. int lastToBeRendered = tBody.lastRendered;
  804. if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
  805. lastToBeRendered = reqFirstRow + reqRows - 1;
  806. } else if (firstRowInViewPort + pageLength + pageLength
  807. * CACHE_RATE < lastToBeRendered) {
  808. lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * CACHE_RATE));
  809. if (lastToBeRendered >= totalRows) {
  810. lastToBeRendered = totalRows - 1;
  811. }
  812. // due Safari 3.1 bug (see #2607), verify reqrows, original
  813. // problem unknown, but this should catch the issue
  814. if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
  815. reqRows = lastToBeRendered - reqFirstRow;
  816. }
  817. }
  818. client.updateVariable(paintableId, "firstToBeRendered",
  819. firstToBeRendered, false);
  820. client.updateVariable(paintableId, "lastToBeRendered",
  821. lastToBeRendered, false);
  822. // remember which firstvisible we requested, in case the server
  823. // has
  824. // a differing opinion
  825. lastRequestedFirstvisible = firstRowInViewPort;
  826. client.updateVariable(paintableId, "firstvisible",
  827. firstRowInViewPort, false);
  828. client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
  829. false);
  830. client.updateVariable(paintableId, "reqrows", reqRows, true);
  831. }
  832. }
  833. public int getReqFirstRow() {
  834. return reqFirstRow;
  835. }
  836. public int getReqRows() {
  837. return reqRows;
  838. }
  839. /**
  840. * Sends request to refresh content at this position.
  841. */
  842. public void refreshContent() {
  843. int first = (int) (firstRowInViewPort - pageLength * CACHE_RATE);
  844. int reqRows = (int) (2 * pageLength * CACHE_RATE + pageLength);
  845. if (first < 0) {
  846. reqRows = reqRows + first;
  847. first = 0;
  848. }
  849. setReqFirstRow(first);
  850. setReqRows(reqRows);
  851. run();
  852. }
  853. }
  854. public class HeaderCell extends Widget {
  855. Element td = DOM.createTD();
  856. Element captionContainer = DOM.createDiv();
  857. Element colResizeWidget = DOM.createDiv();
  858. Element floatingCopyOfHeaderCell;
  859. private boolean sortable = false;
  860. private final String cid;
  861. private boolean dragging;
  862. private int dragStartX;
  863. private int colIndex;
  864. private int originalWidth;
  865. private boolean isResizing;
  866. private int headerX;
  867. private boolean moved;
  868. private int closestSlot;
  869. private int width = -1;
  870. private int naturalWidth = -1;
  871. private char align = ALIGN_LEFT;
  872. boolean definedWidth = false;
  873. private float expandRatio = 0;
  874. public void setSortable(boolean b) {
  875. sortable = b;
  876. }
  877. public void setNaturalMinimumColumnWidth(int w) {
  878. naturalWidth = w;
  879. }
  880. public HeaderCell(String colId, String headerText) {
  881. cid = colId;
  882. DOM.setElementProperty(colResizeWidget, "className", CLASSNAME
  883. + "-resizer");
  884. DOM.sinkEvents(colResizeWidget, Event.MOUSEEVENTS);
  885. setText(headerText);
  886. DOM.appendChild(td, colResizeWidget);
  887. DOM.setElementProperty(captionContainer, "className", CLASSNAME
  888. + "-caption-container");
  889. // ensure no clipping initially (problem on column additions)
  890. DOM.setStyleAttribute(captionContainer, "overflow", "visible");
  891. DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
  892. DOM.appendChild(td, captionContainer);
  893. DOM.sinkEvents(td, Event.MOUSEEVENTS);
  894. setElement(td);
  895. }
  896. public void setWidth(int w, boolean ensureDefinedWidth) {
  897. if (ensureDefinedWidth) {
  898. definedWidth = true;
  899. // on column resize expand ratio becomes zero
  900. expandRatio = 0;
  901. }
  902. if (width == w) {
  903. return;
  904. }
  905. if (width == -1) {
  906. // go to default mode, clip content if necessary
  907. DOM.setStyleAttribute(captionContainer, "overflow", "");
  908. }
  909. width = w;
  910. if (w == -1) {
  911. DOM.setStyleAttribute(captionContainer, "width", "");
  912. setWidth("");
  913. } else {
  914. captionContainer.getStyle().setPropertyPx("width", w);
  915. /*
  916. * if we already have tBody, set the header width properly, if
  917. * not defer it. IE will fail with complex float in table header
  918. * unless TD width is not explicitly set.
  919. */
  920. if (tBody != null) {
  921. int tdWidth = width + tBody.getCellExtraWidth();
  922. setWidth(tdWidth + "px");
  923. } else {
  924. DeferredCommand.addCommand(new Command() {
  925. public void execute() {
  926. int tdWidth = width + tBody.getCellExtraWidth();
  927. setWidth(tdWidth + "px");
  928. }
  929. });
  930. }
  931. }
  932. }
  933. public void setUndefinedWidth() {
  934. definedWidth = false;
  935. setWidth(-1, false);
  936. }
  937. /**
  938. * Detects if width is fixed by developer on server side or resized to
  939. * current width by user.
  940. *
  941. * @return true if defined, false if "natural" width
  942. */
  943. public boolean isDefinedWidth() {
  944. return definedWidth;
  945. }
  946. public int getWidth() {
  947. return width;
  948. }
  949. public void setText(String headerText) {
  950. DOM.setInnerHTML(captionContainer, headerText);
  951. }
  952. public String getColKey() {
  953. return cid;
  954. }
  955. private void setSorted(boolean sorted) {
  956. if (sorted) {
  957. if (sortAscending) {
  958. this.setStyleName(CLASSNAME + "-header-cell-asc");
  959. } else {
  960. this.setStyleName(CLASSNAME + "-header-cell-desc");
  961. }
  962. } else {
  963. this.setStyleName(CLASSNAME + "-header-cell");
  964. }
  965. }
  966. /**
  967. * Handle column reordering.
  968. */
  969. @Override
  970. public void onBrowserEvent(Event event) {
  971. if (enabled && event != null) {
  972. if (isResizing || event.getTarget() == colResizeWidget) {
  973. onResizeEvent(event);
  974. } else {
  975. handleCaptionEvent(event);
  976. }
  977. }
  978. }
  979. private void createFloatingCopy() {
  980. floatingCopyOfHeaderCell = DOM.createDiv();
  981. DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
  982. floatingCopyOfHeaderCell = DOM
  983. .getChild(floatingCopyOfHeaderCell, 1);
  984. DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
  985. CLASSNAME + "-header-drag");
  986. updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), DOM
  987. .getAbsoluteTop(td));
  988. DOM.appendChild(RootPanel.get().getElement(),
  989. floatingCopyOfHeaderCell);
  990. }
  991. private void updateFloatingCopysPosition(int x, int y) {
  992. x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
  993. "offsetWidth") / 2;
  994. DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
  995. if (y > 0) {
  996. DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
  997. + "px");
  998. }
  999. }
  1000. private void hideFloatingCopy() {
  1001. DOM.removeChild(RootPanel.get().getElement(),
  1002. floatingCopyOfHeaderCell);
  1003. floatingCopyOfHeaderCell = null;
  1004. }
  1005. protected void handleCaptionEvent(Event event) {
  1006. switch (DOM.eventGetType(event)) {
  1007. case Event.ONMOUSEDOWN:
  1008. if (columnReordering) {
  1009. dragging = true;
  1010. moved = false;
  1011. colIndex = getColIndexByKey(cid);
  1012. DOM.setCapture(getElement());
  1013. headerX = tHead.getAbsoluteLeft();
  1014. DOM.eventPreventDefault(event); // prevent selecting text
  1015. }
  1016. break;
  1017. case Event.ONMOUSEUP:
  1018. if (columnReordering) {
  1019. dragging = false;
  1020. DOM.releaseCapture(getElement());
  1021. if (moved) {
  1022. hideFloatingCopy();
  1023. tHead.removeSlotFocus();
  1024. if (closestSlot != colIndex
  1025. && closestSlot != (colIndex + 1)) {
  1026. if (closestSlot > colIndex) {
  1027. reOrderColumn(cid, closestSlot - 1);
  1028. } else {
  1029. reOrderColumn(cid, closestSlot);
  1030. }
  1031. }
  1032. }
  1033. }
  1034. if (!moved) {
  1035. // mouse event was a click to header -> sort column
  1036. if (sortable) {
  1037. if (sortColumn.equals(cid)) {
  1038. // just toggle order
  1039. client.updateVariable(paintableId, "sortascending",
  1040. !sortAscending, false);
  1041. } else {
  1042. // set table scrolled by this column
  1043. client.updateVariable(paintableId, "sortcolumn",
  1044. cid, false);
  1045. }
  1046. // get also cache columns at the same request
  1047. bodyContainer.setScrollPosition(0);
  1048. firstvisible = 0;
  1049. rowRequestHandler.setReqFirstRow(0);
  1050. rowRequestHandler.setReqRows((int) (2 * pageLength
  1051. * CACHE_RATE + pageLength));
  1052. rowRequestHandler.deferRowFetch();
  1053. }
  1054. break;
  1055. }
  1056. break;
  1057. case Event.ONMOUSEMOVE:
  1058. if (dragging) {
  1059. if (!moved) {
  1060. createFloatingCopy();
  1061. moved = true;
  1062. }
  1063. final int x = DOM.eventGetClientX(event)
  1064. + DOM.getElementPropertyInt(tHead.hTableWrapper,
  1065. "scrollLeft");
  1066. int slotX = headerX;
  1067. closestSlot = colIndex;
  1068. int closestDistance = -1;
  1069. int start = 0;
  1070. if (showRowHeaders) {
  1071. start++;
  1072. }
  1073. final int visibleCellCount = tHead.getVisibleCellCount();
  1074. for (int i = start; i <= visibleCellCount; i++) {
  1075. if (i > 0) {
  1076. final String colKey = getColKeyByIndex(i - 1);
  1077. slotX += getColWidth(colKey);
  1078. }
  1079. final int dist = Math.abs(x - slotX);
  1080. if (closestDistance == -1 || dist < closestDistance) {
  1081. closestDistance = dist;
  1082. closestSlot = i;
  1083. }
  1084. }
  1085. tHead.focusSlot(closestSlot);
  1086. updateFloatingCopysPosition(DOM.eventGetClientX(event), -1);
  1087. }
  1088. break;
  1089. default:
  1090. break;
  1091. }
  1092. }
  1093. private void onResizeEvent(Event event) {
  1094. switch (DOM.eventGetType(event)) {
  1095. case Event.ONMOUSEDOWN:
  1096. isResizing = true;
  1097. DOM.setCapture(getElement());
  1098. dragStartX = DOM.eventGetClientX(event);
  1099. colIndex = getColIndexByKey(cid);
  1100. originalWidth = getWidth();
  1101. DOM.eventPreventDefault(event);
  1102. break;
  1103. case Event.ONMOUSEUP:
  1104. isResizing = false;
  1105. DOM.releaseCapture(getElement());
  1106. // readjust undefined width columns
  1107. lazyAdjustColumnWidths.cancel();
  1108. lazyAdjustColumnWidths.schedule(1);
  1109. break;
  1110. case Event.ONMOUSEMOVE:
  1111. if (isResizing) {
  1112. final int deltaX = DOM.eventGetClientX(event) - dragStartX;
  1113. if (deltaX == 0) {
  1114. return;
  1115. }
  1116. int newWidth = originalWidth + deltaX;
  1117. if (newWidth < tBody.getCellExtraWidth()) {
  1118. newWidth = tBody.getCellExtraWidth();
  1119. }
  1120. setColWidth(colIndex, newWidth, true);
  1121. }
  1122. break;
  1123. default:
  1124. break;
  1125. }
  1126. }
  1127. public String getCaption() {
  1128. return DOM.getInnerText(captionContainer);
  1129. }
  1130. public boolean isEnabled() {
  1131. return getParent() != null;
  1132. }
  1133. public void setAlign(char c) {
  1134. if (align != c) {
  1135. switch (c) {
  1136. case ALIGN_CENTER:
  1137. DOM.setStyleAttribute(captionContainer, "textAlign",
  1138. "center");
  1139. break;
  1140. case ALIGN_RIGHT:
  1141. DOM.setStyleAttribute(captionContainer, "textAlign",
  1142. "right");
  1143. break;
  1144. default:
  1145. DOM.setStyleAttribute(captionContainer, "textAlign", "");
  1146. break;
  1147. }
  1148. }
  1149. align = c;
  1150. }
  1151. public char getAlign() {
  1152. return align;
  1153. }
  1154. /**
  1155. * Detects the natural minimum width for the column of this header cell.
  1156. * If column is resized by user or the width is defined by server the
  1157. * actual width is returned. Else the natural min width is returned.
  1158. *
  1159. * @param columnIndex
  1160. * column index hint, if -1 (unknown) it will be detected
  1161. *
  1162. * @return
  1163. */
  1164. public int getNaturalColumnWidth(int columnIndex) {
  1165. if (isDefinedWidth()) {
  1166. return width;
  1167. } else {
  1168. if (naturalWidth < 0) {
  1169. // This is recently revealed column. Try to detect a proper
  1170. // value (greater of header and data
  1171. // cols)
  1172. final int hw = ((Element) getElement().getLastChild())
  1173. .getOffsetWidth()
  1174. + tBody.getCellExtraWidth();
  1175. if (columnIndex < 0) {
  1176. columnIndex = 0;
  1177. for (Iterator<Widget> it = tHead.iterator(); it
  1178. .hasNext(); columnIndex++) {
  1179. if (it.next() == this) {
  1180. break;
  1181. }
  1182. }
  1183. }
  1184. final int cw = tBody.getColWidth(columnIndex);
  1185. naturalWidth = (hw > cw ? hw : cw);
  1186. }
  1187. return naturalWidth;
  1188. }
  1189. }
  1190. public void setExpandRatio(float floatAttribute) {
  1191. expandRatio = floatAttribute;
  1192. }
  1193. public float getExpandRatio() {
  1194. return expandRatio;
  1195. }
  1196. }
  1197. /**
  1198. * HeaderCell that is header cell for row headers.
  1199. *
  1200. * Reordering disabled and clicking on it resets sorting.
  1201. */
  1202. public class RowHeadersHeaderCell extends HeaderCell {
  1203. RowHeadersHeaderCell() {
  1204. super("0", "");
  1205. }
  1206. @Override
  1207. protected void handleCaptionEvent(Event event) {
  1208. // NOP: RowHeaders cannot be reordered
  1209. // TODO It'd be nice to reset sorting here
  1210. }
  1211. }
  1212. public class TableHead extends Panel implements ActionOwner {
  1213. private static final int WRAPPER_WIDTH = 9000;
  1214. ArrayList<Widget> visibleCells = new ArrayList<Widget>();
  1215. HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
  1216. Element div = DOM.createDiv();
  1217. Element hTableWrapper = DOM.createDiv();
  1218. Element hTableContainer = DOM.createDiv();
  1219. Element table = DOM.createTable();
  1220. Element headerTableBody = DOM.createTBody();
  1221. Element tr = DOM.createTR();
  1222. private final Element columnSelector = DOM.createDiv();
  1223. private int focusedSlot = -1;
  1224. public TableHead() {
  1225. if (BrowserInfo.get().isIE()) {
  1226. table.setPropertyInt("cellSpacing", 0);
  1227. }
  1228. DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
  1229. DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
  1230. + "-header");
  1231. // TODO move styles to CSS
  1232. DOM.setElementProperty(columnSelector, "className", CLASSNAME
  1233. + "-column-selector");
  1234. DOM.setStyleAttribute(columnSelector, "display", "none");
  1235. DOM.appendChild(table, headerTableBody);
  1236. DOM.appendChild(headerTableBody, tr);
  1237. DOM.appendChild(hTableContainer, table);
  1238. DOM.appendChild(hTableWrapper, hTableContainer);
  1239. DOM.appendChild(div, hTableWrapper);
  1240. DOM.appendChild(div, columnSelector);
  1241. setElement(div);
  1242. setStyleName(CLASSNAME + "-header-wrap");
  1243. DOM.sinkEvents(columnSelector, Event.ONCLICK);
  1244. availableCells.put("0", new RowHeadersHeaderCell());
  1245. }
  1246. @Override
  1247. public void clear() {
  1248. for (String cid : availableCells.keySet()) {
  1249. removeCell(cid);
  1250. }
  1251. availableCells.clear();
  1252. availableCells.put("0", new RowHeadersHeaderCell());
  1253. }
  1254. public void updateCellsFromUIDL(UIDL uidl) {
  1255. Iterator<?> it = uidl.getChildIterator();
  1256. HashSet<String> updated = new HashSet<String>();
  1257. updated.add("0");
  1258. while (it.hasNext()) {
  1259. final UIDL col = (UIDL) it.next();
  1260. final String cid = col.getStringAttribute("cid");
  1261. updated.add(cid);
  1262. String caption = buildCaptionHtmlSnippet(col);
  1263. HeaderCell c = getHeaderCell(cid);
  1264. if (c == null) {
  1265. c = new HeaderCell(cid, caption);
  1266. availableCells.put(cid, c);
  1267. if (initializedAndAttached) {
  1268. // we will need a column width recalculation
  1269. initializedAndAttached = false;
  1270. initialContentReceived = false;
  1271. isNewBody = true;
  1272. }
  1273. } else {
  1274. c.setText(caption);
  1275. }
  1276. if (col.hasAttribute("sortable")) {
  1277. c.setSortable(true);
  1278. if (cid.equals(sortColumn)) {
  1279. c.setSorted(true);
  1280. } else {
  1281. c.setSorted(false);
  1282. }
  1283. } else {
  1284. c.setSortable(false);
  1285. }
  1286. if (col.hasAttribute("align")) {
  1287. c.setAlign(col.getStringAttribute("align").charAt(0));
  1288. }
  1289. if (col.hasAttribute("width")) {
  1290. final String width = col.getStringAttribute("width");
  1291. c.setWidth(Integer.parseInt(width), true);
  1292. } else if (recalcWidths) {
  1293. c.setUndefinedWidth();
  1294. }
  1295. if (col.hasAttribute("er")) {
  1296. c.setExpandRatio(col.getFloatAttribute("er"));
  1297. }
  1298. }
  1299. // check for orphaned header cells
  1300. for (Iterator<String> cit = availableCells.keySet().iterator(); cit
  1301. .hasNext();) {
  1302. String cid = cit.next();
  1303. if (!updated.contains(cid)) {
  1304. removeCell(cid);
  1305. cit.remove();
  1306. }
  1307. }
  1308. }
  1309. public void enableColumn(String cid, int index) {
  1310. final HeaderCell c = getHeaderCell(cid);
  1311. if (!c.isEnabled() || getHeaderCell(index) != c) {
  1312. setHeaderCell(index, c);
  1313. if (initializedAndAttached) {
  1314. headerChangedDuringUpdate = true;
  1315. }
  1316. }
  1317. }
  1318. public int getVisibleCellCount() {
  1319. return visibleCells.size();
  1320. }
  1321. public void setHorizontalScrollPosition(int scrollLeft) {
  1322. if (BrowserInfo.get().isIE6()) {
  1323. hTableWrapper.getStyle().setProperty("position", "relative");
  1324. hTableWrapper.getStyle().setPropertyPx("left", -scrollLeft);
  1325. } else {
  1326. hTableWrapper.setScrollLeft(scrollLeft);
  1327. }
  1328. }
  1329. public void setColumnCollapsingAllowed(boolean cc) {
  1330. if (cc) {
  1331. DOM.setStyleAttribute(columnSelector, "display", "block");
  1332. } else {
  1333. DOM.setStyleAttribute(columnSelector, "display", "none");
  1334. }
  1335. }
  1336. public void disableBrowserIntelligence() {
  1337. DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
  1338. + "px");
  1339. }
  1340. public void enableBrowserIntelligence() {
  1341. DOM.setStyleAttribute(hTableContainer, "width", "");
  1342. }
  1343. public void setHeaderCell(int index, HeaderCell cell) {
  1344. if (cell.isEnabled()) {
  1345. // we're moving the cell
  1346. DOM.removeChild(tr, cell.getElement());
  1347. orphan(cell);
  1348. }
  1349. if (index < visibleCells.size()) {
  1350. // insert to right slot
  1351. DOM.insertChild(tr, cell.getElement(), index);
  1352. adopt(cell);
  1353. visibleCells.add(index, cell);
  1354. } else if (index == visibleCells.size()) {
  1355. // simply append
  1356. DOM.appendChild(tr, cell.getElement());
  1357. adopt(cell);
  1358. visibleCells.add(cell);
  1359. } else {
  1360. throw new RuntimeException(
  1361. "Header cells must be appended in order");
  1362. }
  1363. }
  1364. public HeaderCell getHeaderCell(int index) {
  1365. if (index < visibleCells.size()) {
  1366. return (HeaderCell) visibleCells.get(index);
  1367. } else {
  1368. return null;
  1369. }
  1370. }
  1371. /**
  1372. * Get's HeaderCell by it's column Key.
  1373. *
  1374. * Note that this returns HeaderCell even if it is currently collapsed.
  1375. *
  1376. * @param cid
  1377. * Column key of accessed HeaderCell
  1378. * @return HeaderCell
  1379. */
  1380. public HeaderCell getHeaderCell(String cid) {
  1381. return availableCells.get(cid);
  1382. }
  1383. public void moveCell(int oldIndex, int newIndex) {
  1384. final HeaderCell hCell = getHeaderCell(oldIndex);
  1385. final Element cell = hCell.getElement();
  1386. visibleCells.remove(oldIndex);
  1387. DOM.removeChild(tr, cell);
  1388. DOM.insertChild(tr, cell, newIndex);
  1389. visibleCells.add(newIndex, hCell);
  1390. }
  1391. public Iterator<Widget> iterator() {
  1392. return visibleCells.iterator();
  1393. }
  1394. @Override
  1395. public boolean remove(Widget w) {
  1396. if (visibleCells.contains(w)) {
  1397. visibleCells.remove(w);
  1398. orphan(w);
  1399. DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
  1400. return true;
  1401. }
  1402. return false;
  1403. }
  1404. public void removeCell(String colKey) {
  1405. final HeaderCell c = getHeaderCell(colKey);
  1406. remove(c);
  1407. }
  1408. private void focusSlot(int index) {
  1409. removeSlotFocus();
  1410. if (index > 0) {
  1411. DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
  1412. index - 1)), "className", CLASSNAME + "-resizer "
  1413. + CLASSNAME + "-focus-slot-right");
  1414. } else {
  1415. DOM.setElementProperty(DOM.getFirstChild(DOM
  1416. .getChild(tr, index)), "className", CLASSNAME
  1417. + "-resizer " + CLASSNAME + "-focus-slot-left");
  1418. }
  1419. focusedSlot = index;
  1420. }
  1421. private void removeSlotFocus() {
  1422. if (focusedSlot < 0) {
  1423. return;
  1424. }
  1425. if (focusedSlot == 0) {
  1426. DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
  1427. focusedSlot)), "className", CLASSNAME + "-resizer");
  1428. } else if (focusedSlot > 0) {
  1429. DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
  1430. focusedSlot - 1)), "className", CLASSNAME + "-resizer");
  1431. }
  1432. focusedSlot = -1;
  1433. }
  1434. @Override
  1435. public void onBrowserEvent(Event event) {
  1436. if (enabled) {
  1437. if (event.getTarget() == columnSelector) {
  1438. final int left = DOM.getAbsoluteLeft(columnSelector);
  1439. final int top = DOM.getAbsoluteTop(columnSelector)
  1440. + DOM.getElementPropertyInt(columnSelector,
  1441. "offsetHeight");
  1442. client.getContextMenu().showAt(this, left, top);
  1443. }
  1444. }
  1445. }
  1446. class VisibleColumnAction extends Action {
  1447. String colKey;
  1448. private boolean collapsed;
  1449. public VisibleColumnAction(String colKey) {
  1450. super(VScrollTable.TableHead.this);
  1451. this.colKey = colKey;
  1452. caption = tHead.getHeaderCell(colKey).getCaption();
  1453. }
  1454. @Override
  1455. public void execute() {
  1456. client.getContextMenu().hide();
  1457. // toggle selected column
  1458. if (collapsedColumns.contains(colKey)) {
  1459. collapsedColumns.remove(colKey);
  1460. } else {
  1461. tHead.removeCell(colKey);
  1462. collapsedColumns.add(colKey);
  1463. lazyAdjustColumnWidths.schedule(1);
  1464. }
  1465. // update variable to server
  1466. client.updateVariable(paintableId, "collapsedcolumns",
  1467. collapsedColumns.toArray(), false);
  1468. // let rowRequestHandler determine proper rows
  1469. rowRequestHandler.refreshContent();
  1470. }
  1471. public void setCollapsed(boolean b) {
  1472. collapsed = b;
  1473. }
  1474. /**
  1475. * Override default method to distinguish on/off columns
  1476. */
  1477. @Override
  1478. public String getHTML() {
  1479. final StringBuffer buf = new StringBuffer();
  1480. if (collapsed) {
  1481. buf.append("<span class=\"v-off\">");
  1482. } else {
  1483. buf.append("<span class=\"v-on\">");
  1484. }
  1485. buf.append(super.getHTML());
  1486. buf.append("</span>");
  1487. return buf.toString();
  1488. }
  1489. }
  1490. /*
  1491. * Returns columns as Action array for column select popup
  1492. */
  1493. public Action[] getActions() {
  1494. Object[] cols;
  1495. if (columnReordering) {
  1496. cols = columnOrder;
  1497. } else {
  1498. // if columnReordering is disabled, we need different way to get
  1499. // all available columns
  1500. cols = visibleColOrder;
  1501. cols = new Object[visibleColOrder.length
  1502. + collapsedColumns.size()];
  1503. int i;
  1504. for (i = 0; i < visibleColOrder.length; i++) {
  1505. cols[i] = visibleColOrder[i];
  1506. }
  1507. for (final Iterator<String> it = collapsedColumns.iterator(); it
  1508. .hasNext();) {
  1509. cols[i++] = it.next();
  1510. }
  1511. }
  1512. final Action[] actions = new Action[cols.length];
  1513. for (int i = 0; i < cols.length; i++) {
  1514. final String cid = (String) cols[i];
  1515. final HeaderCell c = getHeaderCell(cid);
  1516. final VisibleColumnAction a = new VisibleColumnAction(c
  1517. .getColKey());
  1518. a.setCaption(c.getCaption());
  1519. if (!c.isEnabled()) {
  1520. a.setCollapsed(true);
  1521. }
  1522. actions[i] = a;
  1523. }
  1524. return actions;
  1525. }
  1526. public ApplicationConnection getClient() {
  1527. return client;
  1528. }
  1529. public String getPaintableId() {
  1530. return paintableId;
  1531. }
  1532. /**
  1533. * Returns column alignments for visible columns
  1534. */
  1535. public char[] getColumnAlignments() {
  1536. final Iterator<Widget> it = visibleCells.iterator();
  1537. final char[] aligns = new char[visibleCells.size()];
  1538. int colIndex = 0;
  1539. while (it.hasNext()) {
  1540. aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
  1541. }
  1542. return aligns;
  1543. }
  1544. }
  1545. /**
  1546. * This Panel can only contain VScrollTableRow type of widgets. This
  1547. * "simulates" very large table, keeping spacers which take room of
  1548. * unrendered rows.
  1549. *
  1550. */
  1551. public class VScrollTableBody extends Panel {
  1552. public static final int DEFAULT_ROW_HEIGHT = 24;
  1553. private int rowHeight = -1;
  1554. private final List<Widget> renderedRows = new ArrayList<Widget>();
  1555. /**
  1556. * Due some optimizations row height measuring is deferred and initial
  1557. * set of rows is rendered detached. Flag set on when table body has
  1558. * been attached in dom and rowheight has been measured.
  1559. */
  1560. private boolean tBodyMeasurementsDone = false;
  1561. Element preSpacer = DOM.createDiv();
  1562. Element postSpacer = DOM.createDiv();
  1563. Element container = DOM.createDiv();
  1564. TableSectionElement tBodyElement = Document.get().createTBodyElement();
  1565. Element table = DOM.createTable();
  1566. private int firstRendered;
  1567. private int lastRendered;
  1568. private char[] aligns;
  1569. VScrollTableBody() {
  1570. constructDOM();
  1571. setElement(container);
  1572. }
  1573. private void constructDOM() {
  1574. DOM.setElementProperty(table, "className", CLASSNAME + "-table");
  1575. if (BrowserInfo.get().isIE()) {
  1576. table.setPropertyInt("cellSpacing", 0);
  1577. }
  1578. DOM.setElementProperty(preSpacer, "className", CLASSNAME
  1579. + "-row-spacer");
  1580. DOM.setElementProperty(postSpacer, "className", CLASSNAME
  1581. + "-row-spacer");
  1582. table.appendChild(tBodyElement);
  1583. DOM.appendChild(container, preSpacer);
  1584. DOM.appendChild(container, table);
  1585. DOM.appendChild(container, postSpacer);
  1586. }
  1587. public int getAvailableWidth() {
  1588. int availW = bodyContainer.getOffsetWidth() - getBorderWidth();
  1589. return availW;
  1590. }
  1591. public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
  1592. firstRendered = firstIndex;
  1593. lastRendered = firstIndex + rows - 1;
  1594. final Iterator<?> it = rowData.getChildIterator();
  1595. aligns = tHead.getColumnAlignments();
  1596. while (it.hasNext()) {
  1597. final VScrollTableRow row = new VScrollTableRow((UIDL) it
  1598. .next(), aligns);
  1599. addRow(row);
  1600. }
  1601. if (isAttached()) {
  1602. fixSpacers();
  1603. }
  1604. }
  1605. public void renderRows(UIDL rowData, int firstIndex, int rows) {
  1606. // FIXME REVIEW
  1607. aligns = tHead.getColumnAlignments();
  1608. final Iterator<?> it = rowData.getChildIterator();
  1609. if (firstIndex == lastRendered + 1) {
  1610. while (it.hasNext()) {
  1611. final VScrollTableRow row = createRow((UIDL) it.next());
  1612. addRow(row);
  1613. lastRendered++;
  1614. }
  1615. fixSpacers();
  1616. } else if (firstIndex + rows == firstRendered) {
  1617. final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
  1618. int i = rows;
  1619. while (it.hasNext()) {
  1620. i--;
  1621. rowArray[i] = createRow((UIDL) it.next());
  1622. }
  1623. for (i = 0; i < rows; i++) {
  1624. addRowBeforeFirstRendered(rowArray[i]);
  1625. firstRendered--;
  1626. }
  1627. } else {
  1628. // completely new set of rows
  1629. while (lastRendered + 1 > firstRendered) {
  1630. unlinkRow(false);
  1631. }
  1632. final VScrollTableRow row = createRow((UIDL) it.next());
  1633. firstRendered = firstIndex;
  1634. lastRendered = firstIndex - 1;
  1635. addRow(row);
  1636. lastRendered++;
  1637. setContainerHeight();
  1638. fixSpacers();
  1639. while (it.hasNext()) {
  1640. addRow(createRow((UIDL) it.next()));
  1641. lastRendered++;
  1642. }
  1643. fixSpacers();
  1644. }
  1645. // this may be a new set of rows due content change,
  1646. // ensure we have proper cache rows
  1647. int reactFirstRow = (int) (firstRowInViewPort - pageLength
  1648. * CACHE_REACT_RATE);
  1649. int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
  1650. * CACHE_REACT_RATE);
  1651. if (reactFirstRow < 0) {
  1652. reactFirstRow = 0;
  1653. }
  1654. if (reactLastRow > totalRows) {
  1655. reactLastRow = totalRows - 1;
  1656. }
  1657. if (lastRendered < reactLastRow) {
  1658. // get some cache rows below visible area
  1659. rowRequestHandler.setReqFirstRow(lastRendered + 1);
  1660. rowRequestHandler.setReqRows(reactLastRow - lastRendered - 1);
  1661. rowRequestHandler.deferRowFetch(1);
  1662. } else if (tBody.getFirstRendered() > reactFirstRow) {
  1663. /*
  1664. * Branch for fetching cache above visible area.
  1665. *
  1666. * If cache needed for both before and after visible area, this
  1667. * will be rendered after-cache is reveived and rendered. So in
  1668. * some rare situations table may take two cache visits to
  1669. * server.
  1670. */
  1671. rowRequestHandler.setReqFirstRow(reactFirstRow);
  1672. rowRequestHandler.setReqRows(firstRendered - reactFirstRow);
  1673. rowRequestHandler.deferRowFetch(1);
  1674. }
  1675. }
  1676. /**
  1677. * This method is used to instantiate new rows for this table. It
  1678. * automatically sets correct widths to rows cells and assigns correct
  1679. * client reference for child widgets.
  1680. *
  1681. * This method can be called only after table has been initialized
  1682. *
  1683. * @param uidl
  1684. */
  1685. private VScrollTableRow createRow(UIDL uidl) {
  1686. final VScrollTableRow row = new VScrollTableRow(uidl, aligns);
  1687. final int cells = DOM.getChildCount(row.getElement());
  1688. for (int i = 0; i < cells; i++) {
  1689. final Element cell = DOM.getChild(row.getElement(), i);
  1690. int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
  1691. if (w < 0) {
  1692. w = 0;
  1693. }
  1694. cell.getFirstChildElement().getStyle()
  1695. .setPropertyPx("width", w);
  1696. cell.getStyle().setPropertyPx("width", w);
  1697. }
  1698. return row;
  1699. }
  1700. private void addRowBeforeFirstRendered(VScrollTableRow row) {
  1701. VScrollTableRow first = null;
  1702. if (renderedRows.size() > 0) {
  1703. first = (VScrollTableRow) renderedRows.get(0);
  1704. }
  1705. if (first != null && first.getStyleName().indexOf("-odd") == -1) {
  1706. row.addStyleName(CLASSNAME + "-row-odd");
  1707. } else {
  1708. row.addStyleName(CLASSNAME + "-row");
  1709. }
  1710. if (row.isSelected()) {
  1711. row.addStyleName("v-selected");
  1712. }
  1713. tBodyElement.insertBefore(row.getElement(), tBodyElement
  1714. .getFirstChild());
  1715. adopt(row);
  1716. renderedRows.add(0, row);
  1717. }
  1718. private void addRow(VScrollTableRow row) {
  1719. VScrollTableRow last = null;
  1720. if (renderedRows.size() > 0) {
  1721. last = (VScrollTableRow) renderedRows
  1722. .get(renderedRows.size() - 1);
  1723. }
  1724. if (last != null && last.getStyleName().indexOf("-odd") == -1) {
  1725. row.addStyleName(CLASSNAME + "-row-odd");
  1726. } else {
  1727. row.addStyleName(CLASSNAME + "-row");
  1728. }
  1729. if (row.isSelected()) {
  1730. row.addStyleName("v-selected");
  1731. }
  1732. tBodyElement.appendChild(row.getElement());
  1733. adopt(row);
  1734. renderedRows.add(row);
  1735. }
  1736. public Iterator<Widget> iterator() {
  1737. return renderedRows.iterator();
  1738. }
  1739. /**
  1740. * @return false if couldn't remove row
  1741. */
  1742. public boolean unlinkRow(boolean fromBeginning) {
  1743. if (lastRendered - firstRendered < 0) {
  1744. return false;
  1745. }
  1746. int index;
  1747. if (fromBeginning) {
  1748. index = 0;
  1749. firstRendered++;
  1750. } else {
  1751. index = renderedRows.size() - 1;
  1752. lastRendered--;
  1753. }
  1754. if (index >= 0) {
  1755. final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
  1756. .get(index);
  1757. lazyUnregistryBag.add(toBeRemoved);
  1758. tBodyElement.removeChild(toBeRemoved.getElement());
  1759. orphan(toBeRemoved);
  1760. renderedRows.remove(index);
  1761. fixSpacers();
  1762. return true;
  1763. } else {
  1764. return false;
  1765. }
  1766. }
  1767. @Override
  1768. public boolean remove(Widget w) {
  1769. throw new UnsupportedOperationException();
  1770. }
  1771. @Override
  1772. protected void onAttach() {
  1773. super.onAttach();
  1774. setContainerHeight();
  1775. }
  1776. /**
  1777. * Fix container blocks height according to totalRows to avoid
  1778. * "bouncing" when scrolling
  1779. */
  1780. private void setContainerHeight() {
  1781. fixSpacers();
  1782. DOM.setStyleAttribute(container, "height", totalRows
  1783. * getRowHeight() + "px");
  1784. }
  1785. private void fixSpacers() {
  1786. int prepx = getRowHeight() * firstRendered;
  1787. if (prepx < 0) {
  1788. prepx = 0;
  1789. }
  1790. DOM.setStyleAttribute(preSpacer, "height", prepx + "px");
  1791. int postpx = getRowHeight() * (totalRows - 1 - lastRendered);
  1792. if (postpx < 0) {
  1793. postpx = 0;
  1794. }
  1795. DOM.setStyleAttribute(postSpacer, "height", postpx + "px");
  1796. }
  1797. public int getRowHeight() {
  1798. return getRowHeight(false);
  1799. }
  1800. public int getRowHeight(boolean forceUpdate) {
  1801. if (tBodyMeasurementsDone && !forceUpdate) {
  1802. return rowHeight;
  1803. } else {
  1804. if (tBodyElement.getRows().getLength() > 0) {
  1805. rowHeight = getTableHeight()
  1806. / tBodyElement.getRows().getLength();
  1807. } else {
  1808. if (isAttached()) {
  1809. // measure row height by adding a dummy row
  1810. VScrollTableRow scrollTableRow = new VScrollTableRow();
  1811. tBodyElement.appendChild(scrollTableRow.getElement());
  1812. getRowHeight(forceUpdate);
  1813. tBodyElement.removeChild(scrollTableRow.getElement());
  1814. } else {
  1815. // TODO investigate if this can never happen anymore
  1816. return DEFAULT_ROW_HEIGHT;
  1817. }
  1818. }
  1819. tBodyMeasurementsDone = true;
  1820. updatePageLength();
  1821. return rowHeight;
  1822. }
  1823. }
  1824. public int getTableHeight() {
  1825. return table.getOffsetHeight();
  1826. }
  1827. /**
  1828. * Returns the width available for column content.
  1829. *
  1830. * @param columnIndex
  1831. * @return
  1832. */
  1833. public int getColWidth(int columnIndex) {
  1834. if (tBodyMeasurementsDone) {
  1835. NodeList<TableRowElement> rows = tBodyElement.getRows();
  1836. if (rows.getLength() == 0) {
  1837. // no rows yet rendered
  1838. return 0;
  1839. } else {
  1840. com.google.gwt.dom.client.Element wrapperdiv = rows
  1841. .getItem(0).getCells().getItem(columnIndex)
  1842. .getFirstChildElement();
  1843. return wrapperdiv.getOffsetWidth();
  1844. }
  1845. } else {
  1846. return 0;
  1847. }
  1848. }
  1849. /**
  1850. * Sets the content width of a column.
  1851. *
  1852. * Due IE limitation, we must set the width to a wrapper elements inside
  1853. * table cells (with overflow hidden, which does not work on td
  1854. * elements).
  1855. *
  1856. * To get this work properly crossplatform, we will also set the width
  1857. * of td.
  1858. *
  1859. * @param colIndex
  1860. * @param w
  1861. */
  1862. public void setColWidth(int colIndex, int w) {
  1863. NodeList<TableRowElement> rows2 = tBodyElement.getRows();
  1864. final int rows = rows2.getLength();
  1865. for (int i = 0; i < rows; i++) {
  1866. TableRowElement row = rows2.getItem(i);
  1867. TableCellElement cell = row.getCells().getItem(colIndex);
  1868. cell.getFirstChildElement().getStyle()
  1869. .setPropertyPx("width", w);
  1870. cell.getStyle().setPropertyPx("width", w);
  1871. }
  1872. }
  1873. private int cellExtraWidth = -1;
  1874. /**
  1875. * Method to return the space used for cell paddings + border.
  1876. */
  1877. private int getCellExtraWidth() {
  1878. if (cellExtraWidth < 0) {
  1879. detectExtrawidth();
  1880. }
  1881. return cellExtraWidth;
  1882. }
  1883. private void detectExtrawidth() {
  1884. NodeList<TableRowElement> rows = tBodyElement.getRows();
  1885. if (rows.getLength() == 0) {
  1886. /* need to temporary add empty row and detect */
  1887. VScrollTableRow scrollTableRow = new VScrollTableRow();
  1888. tBodyElement.appendChild(scrollTableRow.getElement());
  1889. detectExtrawidth();
  1890. tBodyElement.removeChild(scrollTableRow.getElement());
  1891. } else {
  1892. boolean noCells = false;
  1893. TableRowElement item = rows.getItem(0);
  1894. TableCellElement firstTD = item.getCells().getItem(0);
  1895. if (firstTD == null) {
  1896. // content is currently empty, we need to add a fake cell
  1897. // for measuring
  1898. noCells = true;
  1899. VScrollTableRow next = (VScrollTableRow) iterator().next();
  1900. next.addCell("", ALIGN_LEFT, "", true);
  1901. firstTD = item.getCells().getItem(0);
  1902. }
  1903. com.google.gwt.dom.client.Element wrapper = firstTD
  1904. .getFirstChildElement();
  1905. cellExtraWidth = firstTD.getOffsetWidth()
  1906. - wrapper.getOffsetWidth();
  1907. if (noCells) {
  1908. firstTD.getParentElement().removeChild(firstTD);
  1909. }
  1910. }
  1911. }
  1912. private void reLayoutComponents() {
  1913. for (Widget w : this) {
  1914. VScrollTableRow r = (VScrollTableRow) w;
  1915. for (Widget widget : r) {
  1916. client.handleComponentRelativeSize(widget);
  1917. }
  1918. }
  1919. }
  1920. public int getLastRendered() {
  1921. return lastRendered;
  1922. }
  1923. public int getFirstRendered() {
  1924. return firstRendered;
  1925. }
  1926. public void moveCol(int oldIndex, int newIndex) {
  1927. // loop all rows and move given index to its new place
  1928. final Iterator<?> rows = iterator();
  1929. while (rows.hasNext()) {
  1930. final VScrollTableRow row = (VScrollTableRow) rows.next();
  1931. final Element td = DOM.getChild(row.getElement(), oldIndex);
  1932. DOM.removeChild(row.getElement(), td);
  1933. DOM.insertChild(row.getElement(), td, newIndex);
  1934. }
  1935. }
  1936. public class VScrollTableRow extends Panel implements ActionOwner,
  1937. Container {
  1938. ArrayList<Widget> childWidgets = new ArrayList<Widget>();
  1939. private boolean selected = false;
  1940. private final int rowKey;
  1941. private List<UIDL> pendingComponentPaints;
  1942. private String[] actionKeys = null;
  1943. private final TableRowElement rowElement;
  1944. private VScrollTableRow(int rowKey) {
  1945. this.rowKey = rowKey;
  1946. rowElement = Document.get().createTRElement();
  1947. setElement(rowElement);
  1948. DOM.sinkEvents(getElement(), Event.ONMOUSEUP | Event.ONDBLCLICK
  1949. | Event.ONCONTEXTMENU);
  1950. }
  1951. private void paintComponent(Paintable p, UIDL uidl) {
  1952. if (isAttached()) {
  1953. p.updateFromUIDL(uidl, client);
  1954. } else {
  1955. if (pendingComponentPaints == null) {
  1956. pendingComponentPaints = new LinkedList<UIDL>();
  1957. }
  1958. pendingComponentPaints.add(uidl);
  1959. }
  1960. }
  1961. @Override
  1962. protected void onAttach() {
  1963. super.onAttach();
  1964. if (pendingComponentPaints != null) {
  1965. for (UIDL uidl : pendingComponentPaints) {
  1966. Paintable paintable = client.getPaintable(uidl);
  1967. paintable.updateFromUIDL(uidl, client);
  1968. }
  1969. }
  1970. }
  1971. public String getKey() {
  1972. return String.valueOf(rowKey);
  1973. }
  1974. public VScrollTableRow(UIDL uidl, char[] aligns) {
  1975. this(uidl.getIntAttribute("key"));
  1976. String rowStyle = uidl.getStringAttribute("rowstyle");
  1977. if (rowStyle != null) {
  1978. addStyleName(CLASSNAME + "-row-" + rowStyle);
  1979. }
  1980. tHead.getColumnAlignments();
  1981. int col = 0;
  1982. int visibleColumnIndex = -1;
  1983. // row header
  1984. if (showRowHeaders) {
  1985. addCell(buildCaptionHtmlSnippet(uidl), aligns[col++], "",
  1986. true);
  1987. }
  1988. if (uidl.hasAttribute("al")) {
  1989. actionKeys = uidl.getStringArrayAttribute("al");
  1990. }
  1991. final Iterator<?> cells = uidl.getChildIterator();
  1992. while (cells.hasNext()) {
  1993. final Object cell = cells.next();
  1994. visibleColumnIndex++;
  1995. String columnId = visibleColOrder[visibleColumnIndex];
  1996. String style = "";
  1997. if (uidl.hasAttribute("style-" + columnId)) {
  1998. style = uidl.getStringAttribute("style-" + columnId);
  1999. }
  2000. if (cell instanceof String) {
  2001. addCell(cell.toString(), aligns[col++], style, false);
  2002. } else {
  2003. final Paintable cellContent = client
  2004. .getPaintable((UIDL) cell);
  2005. addCell((Widget) cellContent, aligns[col++], style);
  2006. paintComponent(cellContent, (UIDL) cell);
  2007. }
  2008. }
  2009. if (uidl.hasAttribute("selected") && !isSelected()) {
  2010. toggleSelection();
  2011. }
  2012. }
  2013. /**
  2014. * Add a dummy row, used for measurements if Table is empty.
  2015. */
  2016. public VScrollTableRow() {
  2017. this(0);
  2018. addStyleName(CLASSNAME + "-row");
  2019. addCell("_", 'b', "", true);
  2020. }
  2021. public void addCell(String text, char align, String style,
  2022. boolean textIsHTML) {
  2023. // String only content is optimized by not using Label widget
  2024. final Element td = DOM.createTD();
  2025. final Element container = DOM.createDiv();
  2026. String className = CLASSNAME + "-cell-content";
  2027. if (style != null && !style.equals("")) {
  2028. className += " " + CLASSNAME + "-cell-content-" + style;
  2029. }
  2030. td.setClassName(className);
  2031. container.setClassName(CLASSNAME + "-cell-wrapper");
  2032. if (textIsHTML) {
  2033. container.setInnerHTML(text);
  2034. } else {
  2035. container.setInnerText(text);
  2036. }
  2037. if (align != ALIGN_LEFT) {
  2038. switch (align) {
  2039. case ALIGN_CENTER:
  2040. container.getStyle().setProperty("textAlign", "center");
  2041. break;
  2042. case ALIGN_RIGHT:
  2043. default:
  2044. container.getStyle().setProperty("textAlign", "right");
  2045. break;
  2046. }
  2047. }
  2048. td.appendChild(container);
  2049. getElement().appendChild(td);
  2050. }
  2051. public void addCell(Widget w, char align, String style) {
  2052. final Element td = DOM.createTD();
  2053. final Element container = DOM.createDiv();
  2054. String className = CLASSNAME + "-cell-content";
  2055. if (style != null && !style.equals("")) {
  2056. className += " " + CLASSNAME + "-cell-content-" + style;
  2057. }
  2058. td.setClassName(className);
  2059. container.setClassName(CLASSNAME + "-cell-wrapper");
  2060. // TODO most components work with this, but not all (e.g.
  2061. // Select)
  2062. // Old comment: make widget cells respect align.
  2063. // text-align:center for IE, margin: auto for others
  2064. if (align != ALIGN_LEFT) {
  2065. switch (align) {
  2066. case ALIGN_CENTER:
  2067. container.getStyle().setProperty("textAlign", "center");
  2068. break;
  2069. case ALIGN_RIGHT:
  2070. default:
  2071. container.getStyle().setProperty("textAlign", "right");
  2072. break;
  2073. }
  2074. }
  2075. td.appendChild(container);
  2076. getElement().appendChild(td);
  2077. // ensure widget not attached to another element (possible tBody
  2078. // change)
  2079. w.removeFromParent();
  2080. container.appendChild(w.getElement());
  2081. adopt(w);
  2082. childWidgets.add(w);
  2083. }
  2084. public Iterator<Widget> iterator() {
  2085. return childWidgets.iterator();
  2086. }
  2087. @Override
  2088. public boolean remove(Widget w) {
  2089. if (childWidgets.contains(w)) {
  2090. orphan(w);
  2091. DOM.removeChild(DOM.getParent(w.getElement()), w
  2092. .getElement());
  2093. childWidgets.remove(w);
  2094. return true;
  2095. } else {
  2096. return false;
  2097. }
  2098. }
  2099. private void handleClickEvent(Event event, Element targetTdOrTr) {
  2100. if (emitClickEvents) {
  2101. boolean doubleClick = (DOM.eventGetType(event) == Event.ONDBLCLICK);
  2102. /* This row was clicked */
  2103. client.updateVariable(paintableId, "clickedKey", ""
  2104. + rowKey, false);
  2105. if (getElement() == targetTdOrTr.getParentElement()) {
  2106. /* A specific column was clicked */
  2107. int childIndex = DOM.getChildIndex(getElement(),
  2108. targetTdOrTr);
  2109. String colKey = null;
  2110. colKey = tHead.getHeaderCell(childIndex).getColKey();
  2111. client.updateVariable(paintableId, "clickedColKey",
  2112. colKey, false);
  2113. }
  2114. MouseEventDetails details = new MouseEventDetails(event);
  2115. // Note: the 'immediate' logic would need to be more
  2116. // involved (see #2104), but iscrolltable always sends
  2117. // select event, even though nullselectionallowed wont let
  2118. // the change trough. Will need to be updated if that is
  2119. // changed.
  2120. client
  2121. .updateVariable(
  2122. paintableId,
  2123. "clickEvent",
  2124. details.toString(),
  2125. !(event.getButton() == Event.BUTTON_LEFT
  2126. && !doubleClick
  2127. && selectMode > Table.SELECT_MODE_NONE && immediate));
  2128. }
  2129. }
  2130. /*
  2131. * React on click that occur on content cells only
  2132. */
  2133. @Override
  2134. public void onBrowserEvent(Event event) {
  2135. if (enabled) {
  2136. Element targetTdOrTr = getEventTargetTdOrTr(event);
  2137. if (targetTdOrTr != null) {
  2138. switch (DOM.eventGetType(event)) {
  2139. case Event.ONDBLCLICK:
  2140. handleClickEvent(event, targetTdOrTr);
  2141. break;
  2142. case Event.ONMOUSEUP:
  2143. handleClickEvent(event, targetTdOrTr);
  2144. if (event.getButton() == Event.BUTTON_LEFT
  2145. && selectMode > Table.SELECT_MODE_NONE) {
  2146. toggleSelection();
  2147. // Note: changing the immediateness of this
  2148. // might
  2149. // require changes to "clickEvent" immediateness
  2150. // also.
  2151. client.updateVariable(paintableId, "selected",
  2152. selectedRowKeys.toArray(), immediate);
  2153. }
  2154. break;
  2155. case Event.ONCONTEXTMENU:
  2156. showContextMenu(event);
  2157. break;
  2158. default:
  2159. break;
  2160. }
  2161. }
  2162. }
  2163. super.onBrowserEvent(event);
  2164. }
  2165. /**
  2166. * Finds the TD that the event interacts with. Returns null if the
  2167. * target of the event should not be handled. If the event target is
  2168. * the row directly this method returns the TR element instead of
  2169. * the TD.
  2170. *
  2171. * @param event
  2172. * @return TD or TR element that the event targets (the actual event
  2173. * target is this element or a child of it)
  2174. */
  2175. private Element getEventTargetTdOrTr(Event event) {
  2176. Element targetTdOrTr = null;
  2177. final Element eventTarget = DOM.eventGetTarget(event);
  2178. final Element eventTargetParent = DOM.getParent(eventTarget);
  2179. final Element eventTargetGrandParent = DOM
  2180. .getParent(eventTargetParent);
  2181. final Element thisTrElement = getElement();
  2182. if (eventTarget == thisTrElement) {
  2183. // This was a click on the TR element
  2184. targetTdOrTr = eventTarget;
  2185. // rowTarget = true;
  2186. } else if (thisTrElement == eventTargetParent) {
  2187. // Target parent is the TR, so the actual target is the TD
  2188. targetTdOrTr = eventTarget;
  2189. } else if (thisTrElement == eventTargetGrandParent) {
  2190. // Target grand parent is the TR, so the parent is the TD
  2191. targetTdOrTr = eventTargetParent;
  2192. } else {
  2193. /*
  2194. * This is a workaround to make Labels and Embedded in a
  2195. * Table clickable (see #2688). It is really not a fix as it
  2196. * does not work for a custom component (not extending
  2197. * VLabel/VEmbedded) or for read only textfields etc.
  2198. */
  2199. Element tdElement = eventTargetParent;
  2200. while (DOM.getParent(tdElement) != thisTrElement) {
  2201. tdElement = DOM.getParent(tdElement);
  2202. }
  2203. Element componentElement = tdElement.getFirstChildElement()
  2204. .getFirstChildElement().cast();
  2205. Widget widget = (Widget) client
  2206. .getPaintable(componentElement);
  2207. if (widget instanceof VLabel || widget instanceof VEmbedded) {
  2208. targetTdOrTr = tdElement;
  2209. }
  2210. }
  2211. return targetTdOrTr;
  2212. }
  2213. public void showContextMenu(Event event) {
  2214. if (enabled && actionKeys != null) {
  2215. int left = event.getClientX();
  2216. int top = event.getClientY();
  2217. top += Window.getScrollTop();
  2218. left += Window.getScrollLeft();
  2219. client.getContextMenu().showAt(this, left, top);
  2220. }
  2221. event.cancelBubble(true);
  2222. event.preventDefault();
  2223. }
  2224. public boolean isSelected() {
  2225. return selected;
  2226. }
  2227. private void toggleSelection() {
  2228. selected = !selected;
  2229. if (selected) {
  2230. if (selectMode == Table.SELECT_MODE_SINGLE) {
  2231. deselectAll();
  2232. }
  2233. selectedRowKeys.add(String.valueOf(rowKey));
  2234. addStyleName("v-selected");
  2235. } else {
  2236. selectedRowKeys.remove(String.valueOf(rowKey));
  2237. removeStyleName("v-selected");
  2238. }
  2239. }
  2240. /*
  2241. * (non-Javadoc)
  2242. *
  2243. * @see com.vaadin.terminal.gwt.client.ui.IActionOwner#getActions ()
  2244. */
  2245. public Action[] getActions() {
  2246. if (actionKeys == null) {
  2247. return new Action[] {};
  2248. }
  2249. final Action[] actions = new Action[actionKeys.length];
  2250. for (int i = 0; i < actions.length; i++) {
  2251. final String actionKey = actionKeys[i];
  2252. final TreeAction a = new TreeAction(this, String
  2253. .valueOf(rowKey), actionKey);
  2254. a.setCaption(getActionCaption(actionKey));
  2255. a.setIconUrl(getActionIcon(actionKey));
  2256. actions[i] = a;
  2257. }
  2258. return actions;
  2259. }
  2260. public ApplicationConnection getClient() {
  2261. return client;
  2262. }
  2263. public String getPaintableId() {
  2264. return paintableId;
  2265. }
  2266. public RenderSpace getAllocatedSpace(Widget child) {
  2267. int w = 0;
  2268. int i = getColIndexOf(child);
  2269. HeaderCell headerCell = tHead.getHeaderCell(i);
  2270. if (headerCell != null) {
  2271. if (initializedAndAttached) {
  2272. w = headerCell.getWidth();
  2273. } else {
  2274. // header offset width is not absolutely correct value,
  2275. // but a best guess (expecting similar content in all
  2276. // columns ->
  2277. // if one component is relative width so are others)
  2278. w = headerCell.getOffsetWidth() - getCellExtraWidth();
  2279. }
  2280. }
  2281. return new RenderSpace(w, getRowHeight());
  2282. }
  2283. private int getColIndexOf(Widget child) {
  2284. com.google.gwt.dom.client.Element widgetCell = child
  2285. .getElement().getParentElement().getParentElement();
  2286. NodeList<TableCellElement> cells = rowElement.getCells();
  2287. for (int i = 0; i < cells.getLength(); i++) {
  2288. if (cells.getItem(i) == widgetCell) {
  2289. return i;
  2290. }
  2291. }
  2292. return -1;
  2293. }
  2294. public boolean hasChildComponent(Widget component) {
  2295. return childWidgets.contains(component);
  2296. }
  2297. public void replaceChildComponent(Widget oldComponent,
  2298. Widget newComponent) {
  2299. com.google.gwt.dom.client.Element parentElement = oldComponent
  2300. .getElement().getParentElement();
  2301. int index = childWidgets.indexOf(oldComponent);
  2302. oldComponent.removeFromParent();
  2303. parentElement.appendChild(newComponent.getElement());
  2304. childWidgets.add(index, newComponent);
  2305. adopt(newComponent);
  2306. }
  2307. public boolean requestLayout(Set<Paintable> children) {
  2308. // row size should never change and system wouldn't event
  2309. // survive as this is a kind of fake paitable
  2310. return true;
  2311. }
  2312. public void updateCaption(Paintable component, UIDL uidl) {
  2313. // NOP, not rendered
  2314. }
  2315. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  2316. // Should never be called,
  2317. // Component container interface faked here to get layouts
  2318. // render properly
  2319. }
  2320. }
  2321. }
  2322. public void deselectAll() {
  2323. final Object[] keys = selectedRowKeys.toArray();
  2324. for (int i = 0; i < keys.length; i++) {
  2325. final VScrollTableRow row = getRenderedRowByKey((String) keys[i]);
  2326. if (row != null && row.isSelected()) {
  2327. row.toggleSelection();
  2328. }
  2329. }
  2330. // still ensure all selects are removed from (not necessary rendered)
  2331. selectedRowKeys.clear();
  2332. }
  2333. /**
  2334. * Determines the pagelength when the table height is fixed.
  2335. */
  2336. public void updatePageLength() {
  2337. if (tBody == null) {
  2338. return;
  2339. }
  2340. if (height == null || height.equals("")) {
  2341. return;
  2342. }
  2343. int rowHeight = tBody.getRowHeight();
  2344. int bodyH = bodyContainer.getOffsetHeight();
  2345. int rowsAtOnce = bodyH / rowHeight;
  2346. boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
  2347. if (anotherPartlyVisible) {
  2348. rowsAtOnce++;
  2349. }
  2350. pageLength = rowsAtOnce;
  2351. client.updateVariable(paintableId, "pagelength", pageLength, false);
  2352. }
  2353. @Override
  2354. public void setWidth(String width) {
  2355. if (this.width.equals(width)) {
  2356. return;
  2357. }
  2358. this.width = width;
  2359. if (width != null && !"".equals(width)) {
  2360. super.setWidth(width);
  2361. int innerPixels = getOffsetWidth() - getBorderWidth();
  2362. if (innerPixels < 0) {
  2363. innerPixels = 0;
  2364. }
  2365. setContentWidth(innerPixels);
  2366. if (!rendering) {
  2367. // readjust undefined width columns
  2368. lazyAdjustColumnWidths.cancel();
  2369. lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
  2370. }
  2371. } else {
  2372. super.setWidth("");
  2373. }
  2374. }
  2375. private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
  2376. private final Timer lazyAdjustColumnWidths = new Timer() {
  2377. /**
  2378. * Check for column widths, and available width, to see if we can fix
  2379. * column widths "optimally". Doing this lazily to avoid expensive
  2380. * calculation when resizing is not yet finished.
  2381. */
  2382. @Override
  2383. public void run() {
  2384. Iterator<Widget> headCells = tHead.iterator();
  2385. int usedMinimumWidth = 0;
  2386. int totalExplicitColumnsWidths = 0;
  2387. float expandRatioDivider = 0;
  2388. int colIndex = 0;
  2389. while (headCells.hasNext()) {
  2390. final HeaderCell hCell = (HeaderCell) headCells.next();
  2391. if (hCell.isDefinedWidth()) {
  2392. totalExplicitColumnsWidths += hCell.getWidth();
  2393. usedMinimumWidth += hCell.getWidth();
  2394. } else {
  2395. usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex);
  2396. expandRatioDivider += hCell.getExpandRatio();
  2397. }
  2398. colIndex++;
  2399. }
  2400. int availW = tBody.getAvailableWidth();
  2401. // Hey IE, are you really sure about this?
  2402. availW = tBody.getAvailableWidth();
  2403. availW -= tBody.getCellExtraWidth() * visibleColOrder.length;
  2404. if (willHaveScrollbars()) {
  2405. availW -= Util.getNativeScrollbarSize();
  2406. }
  2407. int extraSpace = availW - usedMinimumWidth;
  2408. if (extraSpace < 0) {
  2409. extraSpace = 0;
  2410. }
  2411. int totalUndefinedNaturaWidths = usedMinimumWidth
  2412. - totalExplicitColumnsWidths;
  2413. // we have some space that can be divided optimally
  2414. HeaderCell hCell;
  2415. colIndex = 0;
  2416. headCells = tHead.iterator();
  2417. while (headCells.hasNext()) {
  2418. hCell = (HeaderCell) headCells.next();
  2419. if (!hCell.isDefinedWidth()) {
  2420. int w = hCell.getNaturalColumnWidth(colIndex);
  2421. int newSpace;
  2422. if (expandRatioDivider > 0) {
  2423. // divide excess space by expand ratios
  2424. newSpace = (int) (w + extraSpace
  2425. * hCell.getExpandRatio() / expandRatioDivider);
  2426. } else {
  2427. if (totalUndefinedNaturaWidths != 0) {
  2428. // divide relatively to natural column widths
  2429. newSpace = w + extraSpace * w
  2430. / totalUndefinedNaturaWidths;
  2431. } else {
  2432. newSpace = w;
  2433. }
  2434. }
  2435. setColWidth(colIndex, newSpace, false);
  2436. }
  2437. colIndex++;
  2438. }
  2439. Util.runWebkitOverflowAutoFix(bodyContainer.getElement());
  2440. tBody.reLayoutComponents();
  2441. }
  2442. };
  2443. /**
  2444. * helper to set pixel size of head and body part
  2445. *
  2446. * @param pixels
  2447. */
  2448. private void setContentWidth(int pixels) {
  2449. tHead.setWidth(pixels + "px");
  2450. bodyContainer.setWidth(pixels + "px");
  2451. }
  2452. private int borderWidth = -1;
  2453. /**
  2454. * @return border left + border right
  2455. */
  2456. private int getBorderWidth() {
  2457. if (borderWidth < 0) {
  2458. borderWidth = Util.measureHorizontalPaddingAndBorder(bodyContainer
  2459. .getElement(), 2);
  2460. if (borderWidth < 0) {
  2461. borderWidth = 0;
  2462. }
  2463. }
  2464. return borderWidth;
  2465. }
  2466. /**
  2467. * Ensures scrollable area is properly sized.
  2468. */
  2469. private void setContainerHeight() {
  2470. if (height != null && !"".equals(height)) {
  2471. int contentH = getOffsetHeight() - tHead.getOffsetHeight();
  2472. contentH -= getContentAreaBorderHeight();
  2473. if (contentH < 0) {
  2474. contentH = 0;
  2475. }
  2476. bodyContainer.setHeight(contentH + "px");
  2477. }
  2478. }
  2479. private int contentAreaBorderHeight = -1;
  2480. /**
  2481. * @return border top + border bottom of the scrollable area of table
  2482. */
  2483. private int getContentAreaBorderHeight() {
  2484. if (contentAreaBorderHeight < 0) {
  2485. DOM.setStyleAttribute(bodyContainer.getElement(), "overflow",
  2486. "hidden");
  2487. contentAreaBorderHeight = bodyContainer.getOffsetHeight()
  2488. - bodyContainer.getElement().getPropertyInt("clientHeight");
  2489. DOM.setStyleAttribute(bodyContainer.getElement(), "overflow",
  2490. "auto");
  2491. }
  2492. return contentAreaBorderHeight;
  2493. }
  2494. @Override
  2495. public void setHeight(String height) {
  2496. this.height = height;
  2497. super.setHeight(height);
  2498. setContainerHeight();
  2499. updatePageLength();
  2500. }
  2501. /*
  2502. * Overridden due Table might not survive of visibility change (scroll pos
  2503. * lost). Example ITabPanel just set contained components invisible and back
  2504. * when changing tabs.
  2505. */
  2506. @Override
  2507. public void setVisible(boolean visible) {
  2508. if (isVisible() != visible) {
  2509. super.setVisible(visible);
  2510. if (initializedAndAttached) {
  2511. if (visible) {
  2512. DeferredCommand.addCommand(new Command() {
  2513. public void execute() {
  2514. bodyContainer.setScrollPosition(firstRowInViewPort
  2515. * tBody.getRowHeight());
  2516. }
  2517. });
  2518. }
  2519. }
  2520. }
  2521. }
  2522. /**
  2523. * Helper function to build html snippet for column or row headers
  2524. *
  2525. * @param uidl
  2526. * possibly with values caption and icon
  2527. * @return html snippet containing possibly an icon + caption text
  2528. */
  2529. private String buildCaptionHtmlSnippet(UIDL uidl) {
  2530. String s = uidl.getStringAttribute("caption");
  2531. if (uidl.hasAttribute("icon")) {
  2532. s = "<img src=\""
  2533. + client
  2534. .translateVaadinUri(uidl.getStringAttribute("icon"))
  2535. + "\" alt=\"icon\" class=\"v-icon\">" + s;
  2536. }
  2537. return s;
  2538. }
  2539. }