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

IScrollTable.java 51KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888
  1. package com.itmill.toolkit.terminal.gwt.client.ui;
  2. import java.util.HashMap;
  3. import java.util.Iterator;
  4. import java.util.List;
  5. import java.util.Set;
  6. import java.util.Vector;
  7. import com.google.gwt.user.client.Command;
  8. import com.google.gwt.user.client.DOM;
  9. import com.google.gwt.user.client.DeferredCommand;
  10. import com.google.gwt.user.client.Element;
  11. import com.google.gwt.user.client.Event;
  12. import com.google.gwt.user.client.Timer;
  13. import com.google.gwt.user.client.Window;
  14. import com.google.gwt.user.client.ui.Composite;
  15. import com.google.gwt.user.client.ui.FlowPanel;
  16. import com.google.gwt.user.client.ui.Panel;
  17. import com.google.gwt.user.client.ui.RootPanel;
  18. import com.google.gwt.user.client.ui.ScrollListener;
  19. import com.google.gwt.user.client.ui.ScrollPanel;
  20. import com.google.gwt.user.client.ui.Widget;
  21. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  22. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  23. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  24. import com.itmill.toolkit.terminal.gwt.client.Util;
  25. import com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable.IScrollTableBody.IScrollTableRow;
  26. /**
  27. * Constructor for IScrollTable
  28. *
  29. * IScrollTable is a FlowPanel having two widgets in it: * TableHead component *
  30. * ScrollPanel
  31. *
  32. * TableHead contains table's header and widgets + logic for resizing,
  33. * reordering and hiding columns.
  34. *
  35. * ScrollPanel contains IScrollTableBody object which handles content. To save
  36. * some bandwidth and to improve clients responsiviness with loads of data, in
  37. * IScrollTableBody all rows are not necessarely rendered. There are "spacer" in
  38. * IScrollTableBody to use the exact same space as unrendered rows would use.
  39. * This way we can use seamlessly traditional scrollbars and scrolling to fetch
  40. * more rows instead of "paging".
  41. *
  42. * In IScrollTable we listen to scroll events. On horizontal scrolling we also
  43. * update TableHeads scroll position which has its scrollbars hidden. On
  44. * vertical scroll events we will check if we are reaching the end of area where
  45. * we have rows rendered and
  46. *
  47. * TODO implement unregistering for child componts in Cells
  48. */
  49. public class IScrollTable extends Composite implements Paintable, ITable,
  50. ScrollListener {
  51. public static final String CLASSNAME = "i-table";
  52. /**
  53. * multiple of pagelenght which component will cache when requesting more
  54. * rows
  55. */
  56. private static final double CACHE_RATE = 2;
  57. /**
  58. * fraction of pageLenght which can be scrolled without making new request
  59. */
  60. private static final double CACHE_REACT_RATE = 1.5;
  61. public static final char ALIGN_CENTER = 'c';
  62. public static final char ALIGN_LEFT = 'b';
  63. public static final char ALIGN_RIGHT = 'e';
  64. private int firstRowInViewPort = 0;
  65. private int pageLength = 15;
  66. private boolean rowHeaders = false;
  67. private String[] columnOrder;
  68. private ApplicationConnection client;
  69. private String paintableId;
  70. private boolean immediate;
  71. private int selectMode = ITable.SELECT_MODE_NONE;
  72. private Vector selectedRowKeys = new Vector();
  73. private boolean initializedAndAttached = false;
  74. private TableHead tHead = new TableHead();
  75. private ScrollPanel bodyContainer = new ScrollPanel();
  76. private int totalRows;
  77. private Set collapsedColumns;
  78. private RowRequestHandler rowRequestHandler;
  79. private IScrollTableBody tBody;
  80. private String width;
  81. private String height;
  82. private int firstvisible = 0;
  83. private boolean sortAscending;
  84. private String sortColumn;
  85. private boolean columnReordering;
  86. /**
  87. * This map contains captions and icon urls for actions like: * "33_c" ->
  88. * "Edit" * "33_i" -> "http://dom.com/edit.png"
  89. */
  90. private HashMap actionMap = new HashMap();
  91. private String[] visibleColOrder;
  92. private boolean initialContentReceived = false;
  93. private Element scrollPositionElement;
  94. public IScrollTable() {
  95. bodyContainer.addScrollListener(this);
  96. bodyContainer.setStyleName(CLASSNAME + "-body");
  97. FlowPanel panel = new FlowPanel();
  98. panel.setStyleName(CLASSNAME);
  99. panel.add(tHead);
  100. panel.add(bodyContainer);
  101. rowRequestHandler = new RowRequestHandler();
  102. initWidget(panel);
  103. }
  104. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  105. if (client.updateComponent(this, uidl, true))
  106. return;
  107. this.client = client;
  108. this.paintableId = uidl.getStringAttribute("id");
  109. this.immediate = uidl.getBooleanAttribute("immediate");
  110. this.totalRows = uidl.getIntAttribute("totalrows");
  111. this.pageLength = uidl.getIntAttribute("pagelength");
  112. if (pageLength == 0)
  113. pageLength = totalRows;
  114. this.firstvisible = uidl.hasVariable("firstvisible") ? uidl
  115. .getIntVariable("firstvisible") : 0;
  116. if (uidl.hasAttribute("rowheaders"))
  117. rowHeaders = true;
  118. if (uidl.hasAttribute("width")) {
  119. width = uidl.getStringAttribute("width");
  120. }
  121. if (uidl.hasAttribute("height"))
  122. height = uidl.getStringAttribute("height");
  123. if (uidl.hasVariable("sortascending")) {
  124. this.sortAscending = uidl.getBooleanVariable("sortascending");
  125. this.sortColumn = uidl.getStringVariable("sortcolumn");
  126. }
  127. if (uidl.hasVariable("selected")) {
  128. Set selectedKeys = uidl.getStringArrayVariableAsSet("selected");
  129. selectedRowKeys.clear();
  130. for (Iterator it = selectedKeys.iterator(); it.hasNext();)
  131. selectedRowKeys.add((String) it.next());
  132. }
  133. if (uidl.hasAttribute("selectmode")) {
  134. if (uidl.getStringAttribute("selectmode").equals("multi"))
  135. selectMode = ITable.SELECT_MODE_MULTI;
  136. else
  137. selectMode = ITable.SELECT_MODE_SINGLE;
  138. }
  139. if (uidl.hasVariable("columnorder")) {
  140. this.columnReordering = true;
  141. this.columnOrder = uidl.getStringArrayVariable("columnorder");
  142. }
  143. if (uidl.hasVariable("collapsedcolumns")) {
  144. tHead.setColumnCollapsingAllowed(true);
  145. this.collapsedColumns = uidl
  146. .getStringArrayVariableAsSet("collapsedcolumns");
  147. } else {
  148. tHead.setColumnCollapsingAllowed(false);
  149. }
  150. UIDL rowData = null;
  151. for (Iterator it = uidl.getChildIterator(); it.hasNext();) {
  152. UIDL c = (UIDL) it.next();
  153. if (c.getTag().equals("rows"))
  154. rowData = c;
  155. else if (c.getTag().equals("actions"))
  156. updateActionMap(c);
  157. else if (c.getTag().equals("visiblecolumns"))
  158. updateVisibleColumns(c);
  159. }
  160. updateHeader(uidl.getStringArrayAttribute("vcolorder"));
  161. if (initializedAndAttached) {
  162. updateBody(rowData, uidl.getIntAttribute("firstrow"), uidl
  163. .getIntAttribute("rows"));
  164. } else {
  165. getTBody().renderInitialRows(rowData,
  166. uidl.getIntAttribute("firstrow"),
  167. uidl.getIntAttribute("rows"), totalRows);
  168. bodyContainer.add(tBody);
  169. initialContentReceived = true;
  170. if (isAttached()) {
  171. sizeInit();
  172. }
  173. }
  174. hideScrollPositionAnnotation();
  175. }
  176. private void updateVisibleColumns(UIDL uidl) {
  177. Iterator it = uidl.getChildIterator();
  178. while (it.hasNext()) {
  179. UIDL col = (UIDL) it.next();
  180. tHead.updateCellFromUIDL(col);
  181. }
  182. }
  183. private IScrollTableBody getTBody() {
  184. if (tBody == null || totalRows != tBody.getTotalRows()) {
  185. if (tBody != null)
  186. tBody.removeFromParent();
  187. tBody = new IScrollTableBody();
  188. }
  189. return tBody;
  190. }
  191. private void updateActionMap(UIDL c) {
  192. Iterator it = c.getChildIterator();
  193. while (it.hasNext()) {
  194. UIDL action = (UIDL) it.next();
  195. String key = action.getStringAttribute("key");
  196. String caption = action.getStringAttribute("caption");
  197. actionMap.put(key + "_c", caption);
  198. if (action.hasAttribute("icon")) {
  199. // TODO need some uri handling ??
  200. actionMap.put(key + "_i", action.getStringAttribute("icon"));
  201. }
  202. }
  203. }
  204. public String getActionCaption(String actionKey) {
  205. return (String) actionMap.get(actionKey + "_c");
  206. }
  207. public String getActionIcon(String actionKey) {
  208. return (String) actionMap.get(actionKey + "_i");
  209. }
  210. private void updateHeader(String[] strings) {
  211. if (strings == null)
  212. return;
  213. int visibleCols = strings.length;
  214. int colIndex = 0;
  215. if (rowHeaders) {
  216. tHead.enableColumn("0", colIndex);
  217. visibleCols++;
  218. visibleColOrder = new String[visibleCols];
  219. visibleColOrder[colIndex] = "0";
  220. colIndex++;
  221. } else {
  222. visibleColOrder = new String[visibleCols];
  223. }
  224. for (int i = 0; i < strings.length; i++) {
  225. String cid = strings[i];
  226. visibleColOrder[colIndex] = cid;
  227. tHead.enableColumn(cid, colIndex);
  228. colIndex++;
  229. }
  230. }
  231. /**
  232. * @param uidl
  233. * which contains row data
  234. * @param firstRow
  235. * first row in data set
  236. * @param reqRows
  237. * amount of rows in data set
  238. */
  239. private void updateBody(UIDL uidl, int firstRow, int reqRows) {
  240. if (uidl == null || reqRows < 1)
  241. return;
  242. tBody.renderRows(uidl, firstRow, reqRows);
  243. int optimalFirstRow = (int) (firstRowInViewPort - pageLength
  244. * CACHE_RATE);
  245. while (tBody.getFirstRendered() < optimalFirstRow) {
  246. // client.console.log("removing row from start");
  247. tBody.unlinkRow(true);
  248. }
  249. int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength
  250. * CACHE_RATE);
  251. while (tBody.getLastRendered() > optimalLastRow) {
  252. // client.console.log("removing row from the end");
  253. tBody.unlinkRow(false);
  254. }
  255. }
  256. /**
  257. * Gives correct column index for given column key ("cid" in UIDL).
  258. *
  259. * @param colKey
  260. * @return column index of visible columns, -1 if column not visible
  261. */
  262. private int getColIndexByKey(String colKey) {
  263. // return 0 if asked for rowHeaders
  264. if ("0".equals(colKey))
  265. return 0;
  266. for (int i = 0; i < visibleColOrder.length; i++) {
  267. if (visibleColOrder[i].equals(colKey))
  268. return i;
  269. }
  270. return -1;
  271. }
  272. private boolean isCollapsedColumn(String colKey) {
  273. if (collapsedColumns == null)
  274. return false;
  275. if (collapsedColumns.contains(colKey))
  276. return true;
  277. return false;
  278. }
  279. private String getColKeyByIndex(int index) {
  280. return tHead.getHeaderCell(index).getColKey();
  281. }
  282. private void setColWidth(int colIndex, int w) {
  283. HeaderCell cell = tHead.getHeaderCell(colIndex);
  284. cell.setWidth(w);
  285. tBody.setColWidth(colIndex, w);
  286. String cid = cell.getColKey();
  287. ;
  288. }
  289. private int getColWidth(String colKey) {
  290. return tHead.getHeaderCell(colKey).getWidth();
  291. }
  292. private IScrollTableRow getRenderedRowByKey(String key) {
  293. Iterator it = tBody.iterator();
  294. IScrollTableRow r = null;
  295. while (it.hasNext()) {
  296. r = (IScrollTableRow) it.next();
  297. if (r.getKey().equals(key))
  298. return r;
  299. }
  300. return null;
  301. }
  302. private int getRenderedRowCount() {
  303. return tBody.getLastRendered() - tBody.getFirstRendered();
  304. }
  305. private void reOrderColumn(String columnKey, int newIndex) {
  306. int oldIndex = getColIndexByKey(columnKey);
  307. // Change header order
  308. tHead.moveCell(oldIndex, newIndex);
  309. // Change body order
  310. tBody.moveCol(oldIndex, newIndex);
  311. /*
  312. * Build new columnOrder and update it to server Note that columnOrder
  313. * also contains collapsed columns so we cannot directly build it from
  314. * cells vector Loop the old columnOrder and append in order to new
  315. * array unless on moved columnKey. On new index also put the moved key
  316. * i == index on columnOrder, j == index on newOrder
  317. */
  318. String oldKeyOnNewIndex = visibleColOrder[newIndex];
  319. if (rowHeaders)
  320. newIndex--; // columnOrder don't have rowHeader
  321. // add back hidden rows,
  322. for (int i = 0; i < columnOrder.length; i++) {
  323. if (columnOrder[i].equals(oldKeyOnNewIndex))
  324. break; // break loop at target
  325. if (isCollapsedColumn(columnOrder[i]))
  326. newIndex++;
  327. }
  328. // finally we can build the new columnOrder for server
  329. String[] newOrder = new String[columnOrder.length];
  330. for (int i = 0, j = 0; j < newOrder.length; i++) {
  331. if (j == newIndex) {
  332. newOrder[j] = columnKey;
  333. j++;
  334. }
  335. if (i == columnOrder.length)
  336. break;
  337. if (columnOrder[i].equals(columnKey))
  338. continue;
  339. newOrder[j] = columnOrder[i];
  340. j++;
  341. }
  342. columnOrder = newOrder;
  343. // also update visibleColumnOrder
  344. int i = rowHeaders ? 1 : 0;
  345. for (int j = 0; j < newOrder.length; j++) {
  346. String cid = newOrder[j];
  347. if (!isCollapsedColumn(cid))
  348. visibleColOrder[i++] = cid;
  349. }
  350. client.updateVariable(paintableId, "columnorder", columnOrder, false);
  351. }
  352. protected void onAttach() {
  353. super.onAttach();
  354. if (initialContentReceived) {
  355. sizeInit();
  356. }
  357. }
  358. protected void onDetach() {
  359. super.onDetach();
  360. // ensure that scrollPosElement will be detached
  361. if (scrollPositionElement != null) {
  362. Element parent = DOM.getParent(scrollPositionElement);
  363. if (parent != null)
  364. DOM.removeChild(parent, scrollPositionElement);
  365. }
  366. }
  367. /**
  368. * Run only once when component is attached and received its initial
  369. * content. This function : * Syncs headers and bodys "natural widths and
  370. * saves the values. * Sets proper width and height * Makes deferred request
  371. * to get some cache rows
  372. */
  373. private void sizeInit() {
  374. /*
  375. * We will use browsers table rendering algorithm to find proper column
  376. * widths. If content and header take less space than available, we will
  377. * divide extra space relatively to each column which has not width set.
  378. *
  379. * Overflow pixels are added to last column.
  380. *
  381. */
  382. Iterator headCells = tHead.iterator();
  383. int i = 0;
  384. int totalExplicitColumnsWidths = 0;
  385. int total = 0;
  386. int[] widths = new int[tHead.visibleCells.size()];
  387. // first loop: collect natural widths
  388. while (headCells.hasNext()) {
  389. HeaderCell hCell = (HeaderCell) headCells.next();
  390. int w;
  391. if (hCell.getWidth() > 0) {
  392. // server has defined column width explicitly
  393. w = hCell.getWidth();
  394. totalExplicitColumnsWidths += w;
  395. } else {
  396. int hw = DOM.getElementPropertyInt(hCell.getElement(),
  397. "offsetWidth");
  398. int cw = tBody.getColWidth(i);
  399. w = (hw > cw ? hw : cw) + IScrollTableBody.CELL_EXTRA_WIDTH;
  400. }
  401. widths[i] = w;
  402. total += w;
  403. i++;
  404. }
  405. tHead.disableBrowserIntelligence();
  406. if (height == null) {
  407. bodyContainer.setHeight((tBody.getRowHeight() * pageLength) + "px");
  408. } else {
  409. bodyContainer.setHeight(height);
  410. }
  411. if (width == null) {
  412. int w = total;
  413. w += getScrollbarWidth();
  414. bodyContainer.setWidth(w + "px");
  415. tHead.setWidth(w + "px");
  416. this.setWidth(w + "px");
  417. } else {
  418. if (width.indexOf("px") > 0) {
  419. bodyContainer.setWidth(width);
  420. tHead.setWidth(width);
  421. this.setWidth(width);
  422. } else if (width.indexOf("%") > 0) {
  423. this.setWidth(width);
  424. // contained blocks are relative to parents
  425. bodyContainer.setWidth("100%");
  426. tHead.setWidth("100%");
  427. }
  428. }
  429. int availW = tBody.getAvailableWidth();
  430. if (availW > total) {
  431. // natural size is smaller than available space
  432. int extraSpace = availW - total;
  433. int totalWidthR = total - totalExplicitColumnsWidths;
  434. if (totalWidthR > 0) {
  435. // now we will share this sum relatively to those without
  436. // explicit width
  437. headCells = tHead.iterator();
  438. i = 0;
  439. HeaderCell hCell;
  440. while (headCells.hasNext()) {
  441. hCell = (HeaderCell) headCells.next();
  442. if (hCell.getWidth() == -1) {
  443. int w = widths[i];
  444. int newSpace = extraSpace * w / totalWidthR;
  445. w += newSpace;
  446. widths[i] = w;
  447. }
  448. i++;
  449. }
  450. }
  451. } else {
  452. // bodys size will be more than available and scrollbar will appear
  453. }
  454. // last loop: set possibly modified values
  455. i = 0;
  456. headCells = tHead.iterator();
  457. while (headCells.hasNext()) {
  458. HeaderCell hCell = (HeaderCell) headCells.next();
  459. if (hCell.getWidth() == -1) {
  460. int w = widths[i];
  461. setColWidth(i, w);
  462. }
  463. i++;
  464. }
  465. if (firstvisible > 0) {
  466. bodyContainer
  467. .setScrollPosition(firstvisible * tBody.getRowHeight());
  468. firstRowInViewPort = firstvisible;
  469. }
  470. DeferredCommand.addCommand(new Command() {
  471. public void execute() {
  472. if (totalRows - 1 > tBody.getLastRendered()) {
  473. // fetch cache rows
  474. rowRequestHandler
  475. .setReqFirstRow(tBody.getLastRendered() + 1);
  476. rowRequestHandler
  477. .setReqRows((int) (pageLength * CACHE_RATE));
  478. rowRequestHandler.deferRowFetch();
  479. }
  480. }
  481. });
  482. initializedAndAttached = true;
  483. }
  484. private int getScrollbarWidth() {
  485. return bodyContainer.getOffsetWidth()
  486. - DOM.getElementPropertyInt(bodyContainer.getElement(),
  487. "clientWidth");
  488. }
  489. /**
  490. * This method has logick which rows needs to be requested from server when
  491. * user scrolls
  492. *
  493. */
  494. public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
  495. if (!initializedAndAttached)
  496. return;
  497. rowRequestHandler.cancel();
  498. // fix headers horizontal scrolling
  499. tHead.setHorizontalScrollPosition(scrollLeft);
  500. firstRowInViewPort = (int) Math.ceil(scrollTop
  501. / (double) tBody.getRowHeight());
  502. ApplicationConnection.getConsole().log(
  503. "At scrolltop: " + scrollTop + " At row " + firstRowInViewPort);
  504. int postLimit = (int) (firstRowInViewPort + pageLength + pageLength
  505. * CACHE_REACT_RATE);
  506. if (postLimit > totalRows - 1)
  507. postLimit = totalRows - 1;
  508. int preLimit = (int) (firstRowInViewPort - pageLength
  509. * CACHE_REACT_RATE);
  510. if (preLimit < 0)
  511. preLimit = 0;
  512. int lastRendered = tBody.getLastRendered();
  513. int firstRendered = tBody.getFirstRendered();
  514. if (postLimit <= lastRendered && preLimit >= firstRendered) {
  515. client.updateVariable(this.paintableId, "firstvisible",
  516. firstRowInViewPort, false);
  517. return; // scrolled withing "non-react area"
  518. }
  519. if (firstRowInViewPort - pageLength * CACHE_RATE > lastRendered
  520. || firstRowInViewPort + pageLength + pageLength * CACHE_RATE < firstRendered) {
  521. // need a totally new set
  522. ApplicationConnection.getConsole().log(
  523. "Table: need a totally new set");
  524. rowRequestHandler
  525. .setReqFirstRow((int) (firstRowInViewPort - pageLength
  526. * CACHE_RATE));
  527. rowRequestHandler
  528. .setReqRows((int) (2 * CACHE_RATE * pageLength + pageLength));
  529. rowRequestHandler.deferRowFetch();
  530. return;
  531. }
  532. if (preLimit < firstRendered) {
  533. // need some rows to the beginning of the rendered area
  534. ApplicationConnection
  535. .getConsole()
  536. .log(
  537. "Table: need some rows to the beginning of the rendered area");
  538. rowRequestHandler
  539. .setReqFirstRow((int) (firstRowInViewPort - pageLength
  540. * CACHE_RATE));
  541. rowRequestHandler.setReqRows(firstRendered
  542. - rowRequestHandler.getReqFirstRow());
  543. rowRequestHandler.deferRowFetch();
  544. return;
  545. }
  546. if (postLimit > lastRendered) {
  547. // need some rows to the end of the rendered area
  548. ApplicationConnection.getConsole().log(
  549. "need some rows to the end of the rendered area");
  550. rowRequestHandler.setReqFirstRow(lastRendered + 1);
  551. rowRequestHandler.setReqRows((int) ((firstRowInViewPort
  552. + pageLength + pageLength * CACHE_RATE) - lastRendered));
  553. rowRequestHandler.deferRowFetch();
  554. }
  555. }
  556. private void announceScrollPosition() {
  557. ApplicationConnection.getConsole().log("" + firstRowInViewPort);
  558. if (scrollPositionElement == null) {
  559. scrollPositionElement = DOM.createDiv();
  560. DOM.setElementProperty(scrollPositionElement, "className",
  561. "i-table-scrollposition");
  562. DOM
  563. .appendChild(RootPanel.get().getElement(),
  564. scrollPositionElement);
  565. }
  566. DOM.setStyleAttribute(scrollPositionElement, "left",
  567. (DOM.getAbsoluteLeft(getElement())
  568. + DOM
  569. .getElementPropertyInt(getElement(),
  570. "offsetWidth") / 2 - 75)
  571. + "px");
  572. DOM.setStyleAttribute(scrollPositionElement, "top", (DOM
  573. .getAbsoluteTop(getElement()))
  574. + "px");
  575. int last = (firstRowInViewPort + pageLength);
  576. if (last > totalRows)
  577. last = totalRows;
  578. DOM.setInnerHTML(scrollPositionElement, firstRowInViewPort + " - "
  579. + last + "...");
  580. DOM.setStyleAttribute(scrollPositionElement, "display", "block");
  581. }
  582. private void hideScrollPositionAnnotation() {
  583. if (scrollPositionElement != null)
  584. DOM.setStyleAttribute(scrollPositionElement, "display", "none");
  585. }
  586. private class RowRequestHandler extends Timer {
  587. private int reqFirstRow = 0;
  588. private int reqRows = 0;
  589. public void deferRowFetch() {
  590. if (reqRows > 0 && reqFirstRow < totalRows) {
  591. schedule(250);
  592. // tell scroll position to user if currently "visible" rows are
  593. // not rendered
  594. if ((firstRowInViewPort + pageLength > tBody.getLastRendered())
  595. || (firstRowInViewPort < tBody.getFirstRendered())) {
  596. announceScrollPosition();
  597. } else {
  598. hideScrollPositionAnnotation();
  599. }
  600. }
  601. }
  602. public void setReqFirstRow(int reqFirstRow) {
  603. if (reqFirstRow < 0)
  604. reqFirstRow = 0;
  605. else if (reqFirstRow >= totalRows)
  606. reqFirstRow = totalRows - 1;
  607. this.reqFirstRow = reqFirstRow;
  608. }
  609. public void setReqRows(int reqRows) {
  610. this.reqRows = reqRows;
  611. }
  612. public void run() {
  613. ApplicationConnection.getConsole().log(
  614. "Getting " + reqRows + " rows from " + reqFirstRow);
  615. client.updateVariable(paintableId, "firstvisible",
  616. firstRowInViewPort, false);
  617. client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
  618. false);
  619. client.updateVariable(paintableId, "reqrows", reqRows, true);
  620. }
  621. public int getReqFirstRow() {
  622. return reqFirstRow;
  623. }
  624. public int getReqRows() {
  625. return reqRows;
  626. }
  627. /**
  628. * Sends request to refresh content at this position.
  629. */
  630. public void refreshContent() {
  631. int first = (int) (firstRowInViewPort - pageLength * CACHE_RATE);
  632. int reqRows = (int) (2 * pageLength * CACHE_RATE + pageLength);
  633. if (first < 0) {
  634. reqRows = reqRows + first;
  635. first = 0;
  636. }
  637. this.setReqFirstRow(first);
  638. this.setReqRows(reqRows);
  639. run();
  640. }
  641. }
  642. public class HeaderCell extends Widget {
  643. private static final int DRAG_WIDGET_WIDTH = 2;
  644. private static final int MINIMUM_COL_WIDTH = 20;
  645. Element td = DOM.createTD();
  646. Element captionContainer = DOM.createDiv();
  647. Element colResizeWidget = DOM.createDiv();
  648. Element floatingCopyOfHeaderCell;
  649. private boolean sortable = false;
  650. private String cid;
  651. private boolean dragging;
  652. private int dragStartX;
  653. private int colIndex;
  654. private int originalWidth;
  655. private boolean isResizing;
  656. private int headerX;
  657. private boolean moved;
  658. private int closestSlot;
  659. private int width = -1;
  660. private char align = ALIGN_LEFT;
  661. private HeaderCell() {
  662. };
  663. public void setSortable(boolean b) {
  664. sortable = b;
  665. }
  666. public HeaderCell(String colId, String headerText) {
  667. this.cid = colId;
  668. DOM.setElementProperty(colResizeWidget, "className", CLASSNAME
  669. + "-resizer");
  670. DOM.setStyleAttribute(colResizeWidget, "width", DRAG_WIDGET_WIDTH
  671. + "px");
  672. DOM.sinkEvents(colResizeWidget, Event.MOUSEEVENTS);
  673. setText(headerText);
  674. DOM.appendChild(td, colResizeWidget);
  675. DOM.setElementProperty(captionContainer, "className", CLASSNAME
  676. + "-caption-container");
  677. DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
  678. DOM.appendChild(td, captionContainer);
  679. DOM.sinkEvents(td, Event.MOUSEEVENTS);
  680. setElement(td);
  681. }
  682. public void setWidth(int w) {
  683. this.width = w;
  684. DOM.setStyleAttribute(captionContainer, "width", (w
  685. - DRAG_WIDGET_WIDTH - 4)
  686. + "px");
  687. setWidth(w + "px");
  688. }
  689. public int getWidth() {
  690. return width;
  691. }
  692. public void setText(String headerText) {
  693. DOM.setInnerHTML(captionContainer, headerText);
  694. }
  695. public String getColKey() {
  696. return cid;
  697. }
  698. private void setSorted(boolean sorted) {
  699. if (sorted) {
  700. if (sortAscending)
  701. this.setStyleName("header-cell-asc");
  702. else
  703. this.setStyleName("header-cell-desc");
  704. } else {
  705. this.setStyleName("header-cell");
  706. }
  707. }
  708. /**
  709. * Handle column reordering.
  710. */
  711. public void onBrowserEvent(Event event) {
  712. if (isResizing
  713. || DOM.compare(DOM.eventGetTarget(event), colResizeWidget)) {
  714. onResizeEvent(event);
  715. } else {
  716. handleCaptionEvent(event);
  717. }
  718. super.onBrowserEvent(event);
  719. }
  720. private void createFloatingCopy() {
  721. floatingCopyOfHeaderCell = DOM.createDiv();
  722. DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
  723. floatingCopyOfHeaderCell = DOM
  724. .getChild(floatingCopyOfHeaderCell, 1);
  725. // TODO isolate non-standard css attribute (filter)
  726. // TODO move styles to css file
  727. DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
  728. CLASSNAME + "-header-drag");
  729. updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), DOM
  730. .getAbsoluteTop(td));
  731. DOM.appendChild(RootPanel.get().getElement(),
  732. floatingCopyOfHeaderCell);
  733. }
  734. private void updateFloatingCopysPosition(int x, int y) {
  735. x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
  736. "offsetWidth") / 2;
  737. DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
  738. if (y > 0)
  739. DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
  740. + "px");
  741. }
  742. private void hideFloatingCopy() {
  743. DOM.removeChild(RootPanel.get().getElement(),
  744. floatingCopyOfHeaderCell);
  745. floatingCopyOfHeaderCell = null;
  746. }
  747. protected void handleCaptionEvent(Event event) {
  748. switch (DOM.eventGetType(event)) {
  749. case Event.ONMOUSEDOWN:
  750. ApplicationConnection.getConsole().log(
  751. "HeaderCaption: mouse down");
  752. if (columnReordering) {
  753. dragging = true;
  754. moved = false;
  755. colIndex = getColIndexByKey(cid);
  756. DOM.setCapture(getElement());
  757. this.headerX = tHead.getAbsoluteLeft();
  758. ApplicationConnection
  759. .getConsole()
  760. .log(
  761. "HeaderCaption: Caption set to capture mouse events");
  762. DOM.eventPreventDefault(event); // prevent selecting text
  763. }
  764. break;
  765. case Event.ONMOUSEUP:
  766. ApplicationConnection.getConsole()
  767. .log("HeaderCaption: mouseUP");
  768. if (columnReordering) {
  769. dragging = false;
  770. DOM.releaseCapture(getElement());
  771. ApplicationConnection.getConsole().log(
  772. "HeaderCaption: Stopped column reordering");
  773. if (moved) {
  774. hideFloatingCopy();
  775. tHead.removeSlotFocus();
  776. if (closestSlot != colIndex
  777. && closestSlot != (colIndex + 1)) {
  778. if (closestSlot > colIndex)
  779. reOrderColumn(cid, closestSlot - 1);
  780. else
  781. reOrderColumn(cid, closestSlot);
  782. }
  783. }
  784. }
  785. if (!moved) {
  786. // mouse event was a click to header -> sort column
  787. if (sortable) {
  788. if (sortColumn.equals(cid)) {
  789. // just toggle order
  790. client.updateVariable(paintableId, "sortascending",
  791. !sortAscending, false);
  792. } else {
  793. // set table scrolled by this column
  794. client.updateVariable(paintableId, "sortcolumn",
  795. cid, false);
  796. }
  797. // get also cache columns at the same request
  798. bodyContainer.setScrollPosition(0);
  799. firstvisible = 0;
  800. rowRequestHandler.setReqFirstRow(0);
  801. rowRequestHandler.setReqRows((int) (2 * pageLength
  802. * CACHE_RATE + pageLength));
  803. rowRequestHandler.deferRowFetch();
  804. }
  805. break;
  806. }
  807. break;
  808. case Event.ONMOUSEMOVE:
  809. if (dragging) {
  810. ApplicationConnection.getConsole().log(
  811. "HeaderCaption: Dragging column, optimal index...");
  812. if (!moved) {
  813. createFloatingCopy();
  814. moved = true;
  815. }
  816. int x = DOM.eventGetClientX(event);
  817. int slotX = headerX;
  818. closestSlot = colIndex;
  819. int closestDistance = -1;
  820. int start = 0;
  821. if (rowHeaders) {
  822. start++;
  823. }
  824. int visibleCellCount = tHead.getVisibleCellCount();
  825. for (int i = start; i <= visibleCellCount; i++) {
  826. if (i > 0) {
  827. String colKey = getColKeyByIndex(i - 1);
  828. slotX += getColWidth(colKey);
  829. }
  830. int dist = Math.abs(x - slotX);
  831. if (closestDistance == -1 || dist < closestDistance) {
  832. closestDistance = dist;
  833. closestSlot = i;
  834. }
  835. }
  836. tHead.focusSlot(closestSlot);
  837. updateFloatingCopysPosition(x, -1);
  838. ApplicationConnection.getConsole().log("" + closestSlot);
  839. }
  840. break;
  841. default:
  842. break;
  843. }
  844. }
  845. private void onResizeEvent(Event event) {
  846. switch (DOM.eventGetType(event)) {
  847. case Event.ONMOUSEDOWN:
  848. isResizing = true;
  849. DOM.setCapture(getElement());
  850. dragStartX = DOM.eventGetClientX(event);
  851. colIndex = getColIndexByKey(cid);
  852. originalWidth = getWidth();
  853. DOM.eventPreventDefault(event);
  854. break;
  855. case Event.ONMOUSEUP:
  856. isResizing = false;
  857. DOM.releaseCapture(getElement());
  858. break;
  859. case Event.ONMOUSEMOVE:
  860. if (isResizing) {
  861. int deltaX = DOM.eventGetClientX(event) - dragStartX;
  862. if (deltaX == 0)
  863. return;
  864. int newWidth = originalWidth + deltaX;
  865. if (newWidth < MINIMUM_COL_WIDTH)
  866. newWidth = MINIMUM_COL_WIDTH;
  867. setColWidth(colIndex, newWidth);
  868. }
  869. break;
  870. default:
  871. break;
  872. }
  873. }
  874. public String getCaption() {
  875. return DOM.getInnerText(captionContainer);
  876. }
  877. public boolean isEnabled() {
  878. return getParent() != null;
  879. }
  880. public void setAlign(char c) {
  881. if (align != c) {
  882. switch (c) {
  883. case ALIGN_CENTER:
  884. DOM.setStyleAttribute(captionContainer, "textAlign",
  885. "center");
  886. break;
  887. case ALIGN_RIGHT:
  888. DOM.setStyleAttribute(captionContainer, "textAlign",
  889. "right");
  890. break;
  891. default:
  892. DOM.setStyleAttribute(captionContainer, "textAlign", "");
  893. break;
  894. }
  895. }
  896. align = c;
  897. }
  898. public char getAlign() {
  899. return align;
  900. }
  901. }
  902. /**
  903. * HeaderCell that is header cell for row headers.
  904. *
  905. * Reordering disabled and clicking on it resets sorting.
  906. */
  907. public class RowHeadersHeaderCell extends HeaderCell {
  908. RowHeadersHeaderCell() {
  909. super("0", "");
  910. }
  911. protected void handleCaptionEvent(Event event) {
  912. // NOP: RowHeaders cannot be reordered
  913. // TODO It'd be nice to reset sorting here
  914. }
  915. }
  916. public class TableHead extends Panel implements IActionOwner {
  917. private static final int WRAPPER_WIDTH = 9000;
  918. Vector visibleCells = new Vector();
  919. HashMap availableCells = new HashMap();
  920. Element div = DOM.createDiv();
  921. Element hTableWrapper = DOM.createDiv();
  922. Element hTableContainer = DOM.createDiv();
  923. Element table = DOM.createTable();
  924. Element headerTableBody = DOM.createTBody();
  925. Element tr = DOM.createTR();
  926. private Element columnSelector = DOM.createDiv();
  927. private int focusedSlot = -1;
  928. private boolean columnCollapsing = false;
  929. public TableHead() {
  930. DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
  931. DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
  932. + "-header");
  933. // TODO move styles to CSS
  934. DOM.setElementProperty(columnSelector, "className", CLASSNAME
  935. + "-column-selector");
  936. DOM.setStyleAttribute(columnSelector, "display", "none");
  937. DOM.appendChild(table, headerTableBody);
  938. DOM.appendChild(headerTableBody, tr);
  939. DOM.appendChild(hTableContainer, table);
  940. DOM.appendChild(hTableWrapper, hTableContainer);
  941. DOM.appendChild(div, columnSelector);
  942. DOM.appendChild(div, hTableWrapper);
  943. setElement(div);
  944. setStyleName(CLASSNAME + "-header-wrap");
  945. DOM.sinkEvents(columnSelector, Event.ONCLICK);
  946. availableCells.put("0", new RowHeadersHeaderCell());
  947. }
  948. public void updateCellFromUIDL(UIDL col) {
  949. String cid = col.getStringAttribute("cid");
  950. HeaderCell c = getHeaderCell(cid);
  951. if (c == null) {
  952. c = new HeaderCell(cid, col.getStringAttribute("caption"));
  953. availableCells.put(cid, c);
  954. } else {
  955. c.setText(col.getStringAttribute("caption"));
  956. }
  957. if (col.hasAttribute("sortable")) {
  958. c.setSortable(true);
  959. if (cid.equals(sortColumn))
  960. c.setSorted(true);
  961. else
  962. c.setSorted(false);
  963. }
  964. if (col.hasAttribute("align")) {
  965. c.setAlign(col.getStringAttribute("align").charAt(0));
  966. }
  967. if (col.hasAttribute("width")) {
  968. String width = col.getStringAttribute("width");
  969. c.setWidth(Integer.parseInt(width));
  970. }
  971. // TODO icon
  972. }
  973. public void enableColumn(String cid, int index) {
  974. HeaderCell c = getHeaderCell(cid);
  975. if (!c.isEnabled()) {
  976. setHeaderCell(index, c);
  977. }
  978. }
  979. public int getVisibleCellCount() {
  980. return visibleCells.size();
  981. }
  982. public void setHorizontalScrollPosition(int scrollLeft) {
  983. DOM.setElementPropertyInt(hTableWrapper, "scrollLeft", scrollLeft);
  984. }
  985. public void setWidth(int width) {
  986. DOM.setStyleAttribute(hTableWrapper, "width",
  987. (width - getColumnSelectorWidth()) + "px");
  988. super.setWidth(width + "px");
  989. }
  990. public void setWidth(String width) {
  991. if (width.indexOf("px") > 0) {
  992. int w = Integer.parseInt(width
  993. .substring(0, width.indexOf("px")));
  994. setWidth(w);
  995. } else {
  996. // this is an IE6 hack, would need a generator to isolate from
  997. // others
  998. if (Util.isIE6()) {
  999. DOM.setStyleAttribute(hTableWrapper, "width", (0) + "px");
  1000. super.setWidth(width);
  1001. int hTableWrappersWidth = this.getOffsetWidth()
  1002. - getColumnSelectorWidth();
  1003. DOM.setStyleAttribute(hTableWrapper, "width",
  1004. hTableWrappersWidth + "px");
  1005. } else {
  1006. super.setWidth(width);
  1007. }
  1008. }
  1009. }
  1010. private int getColumnSelectorWidth() {
  1011. int w = DOM.getElementPropertyInt(columnSelector, "offsetWidth") + 4; // some
  1012. // extra
  1013. // to
  1014. // survive
  1015. // with
  1016. // IE6
  1017. return w > 0 ? w : 15;
  1018. }
  1019. public void setColumnCollapsingAllowed(boolean cc) {
  1020. columnCollapsing = cc;
  1021. if (cc) {
  1022. DOM.setStyleAttribute(columnSelector, "display", "block");
  1023. } else {
  1024. DOM.setStyleAttribute(columnSelector, "display", "none");
  1025. }
  1026. }
  1027. public void disableBrowserIntelligence() {
  1028. DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
  1029. + "px");
  1030. }
  1031. public void setHeaderCell(int index, HeaderCell cell) {
  1032. if (index < visibleCells.size()) {
  1033. // insert to right slot
  1034. DOM.insertChild(tr, cell.getElement(), index);
  1035. adopt(cell);
  1036. visibleCells.insertElementAt(cell, index);
  1037. } else if (index == visibleCells.size()) {
  1038. // simply append
  1039. DOM.appendChild(tr, cell.getElement());
  1040. adopt(cell);
  1041. visibleCells.add(cell);
  1042. } else {
  1043. throw new RuntimeException(
  1044. "Header cells must be appended in order");
  1045. }
  1046. }
  1047. public HeaderCell getHeaderCell(int index) {
  1048. if (index < visibleCells.size())
  1049. return (HeaderCell) visibleCells.get(index);
  1050. else
  1051. return null;
  1052. }
  1053. /**
  1054. * Get's HeaderCell by it's column Key.
  1055. *
  1056. * Note that this returns HeaderCell even if it is currently collapsed.
  1057. *
  1058. * @param cid
  1059. * Column key of accessed HeaderCell
  1060. * @return HeaderCell
  1061. */
  1062. public HeaderCell getHeaderCell(String cid) {
  1063. return (HeaderCell) availableCells.get(cid);
  1064. }
  1065. public void moveCell(int oldIndex, int newIndex) {
  1066. HeaderCell hCell = getHeaderCell(oldIndex);
  1067. Element cell = hCell.getElement();
  1068. visibleCells.remove(oldIndex);
  1069. DOM.removeChild(tr, cell);
  1070. DOM.insertChild(tr, cell, newIndex);
  1071. visibleCells.insertElementAt(hCell, newIndex);
  1072. }
  1073. public Iterator iterator() {
  1074. return visibleCells.iterator();
  1075. }
  1076. public boolean remove(Widget w) {
  1077. if (visibleCells.contains(w)) {
  1078. visibleCells.remove(w);
  1079. orphan(w);
  1080. DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
  1081. return true;
  1082. }
  1083. return false;
  1084. }
  1085. public void removeCell(String colKey) {
  1086. HeaderCell c = getHeaderCell(colKey);
  1087. remove(c);
  1088. }
  1089. private void focusSlot(int index) {
  1090. removeSlotFocus();
  1091. if (index > 0)
  1092. DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
  1093. index - 1)), "className", CLASSNAME + "-resizer "
  1094. + CLASSNAME + "-focus-slot-right");
  1095. else
  1096. DOM.setElementProperty(DOM.getFirstChild(DOM
  1097. .getChild(tr, index)), "className", CLASSNAME
  1098. + "-resizer " + CLASSNAME + "-focus-slot-left");
  1099. focusedSlot = index;
  1100. }
  1101. private void removeSlotFocus() {
  1102. if (focusedSlot < 0)
  1103. return;
  1104. if (focusedSlot == 0)
  1105. DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
  1106. focusedSlot)), "className", CLASSNAME + "-resizer");
  1107. else if (focusedSlot > 0)
  1108. DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
  1109. focusedSlot - 1)), "className", CLASSNAME + "-resizer");
  1110. focusedSlot = -1;
  1111. }
  1112. public void onBrowserEvent(Event event) {
  1113. super.onBrowserEvent(event);
  1114. if (DOM.compare(DOM.eventGetTarget(event), columnSelector)) {
  1115. int left = DOM.getAbsoluteLeft(columnSelector);
  1116. int top = DOM.getAbsoluteTop(columnSelector)
  1117. + DOM.getElementPropertyInt(columnSelector,
  1118. "offsetHeight");
  1119. client.getContextMenu().showAt(this, left, top);
  1120. }
  1121. }
  1122. class VisibleColumnAction extends IAction {
  1123. String colKey;
  1124. private boolean collapsed;
  1125. public VisibleColumnAction(String colKey) {
  1126. super(IScrollTable.TableHead.this);
  1127. this.colKey = colKey;
  1128. caption = tHead.getHeaderCell(colKey).getCaption();
  1129. }
  1130. public void execute() {
  1131. client.getContextMenu().hide();
  1132. // toggle selected column
  1133. if (collapsedColumns.contains(colKey)) {
  1134. collapsedColumns.remove(colKey);
  1135. } else {
  1136. tHead.removeCell(colKey);
  1137. collapsedColumns.add(colKey);
  1138. }
  1139. // update variable to server
  1140. client.updateVariable(paintableId, "collapsedcolumns",
  1141. collapsedColumns.toArray(), false);
  1142. // let rowRequestHandler determine proper rows
  1143. rowRequestHandler.refreshContent();
  1144. }
  1145. public void setCollapsed(boolean b) {
  1146. collapsed = b;
  1147. }
  1148. /**
  1149. * Override default method to distinguish on/off columns
  1150. */
  1151. public String getHTML() {
  1152. StringBuffer buf = new StringBuffer();
  1153. if (collapsed)
  1154. buf.append("<span class=\"i-off\">");
  1155. buf.append(super.getHTML());
  1156. if (collapsed)
  1157. buf.append("</span>");
  1158. return buf.toString();
  1159. }
  1160. }
  1161. /*
  1162. * Returns columns as Action array for column select popup
  1163. */
  1164. public IAction[] getActions() {
  1165. Object[] cols;
  1166. if (IScrollTable.this.columnReordering) {
  1167. cols = columnOrder;
  1168. } else {
  1169. // if columnReordering is disabled, we need different way to get
  1170. // all available columns
  1171. cols = visibleColOrder;
  1172. cols = new Object[visibleColOrder.length
  1173. + collapsedColumns.size()];
  1174. int i;
  1175. for (i = 0; i < visibleColOrder.length; i++) {
  1176. cols[i] = visibleColOrder[i];
  1177. }
  1178. for (Iterator it = collapsedColumns.iterator(); it.hasNext();)
  1179. cols[i++] = it.next();
  1180. }
  1181. IAction[] actions = new IAction[cols.length];
  1182. for (int i = 0; i < cols.length; i++) {
  1183. String cid = (String) cols[i];
  1184. HeaderCell c = getHeaderCell(cid);
  1185. VisibleColumnAction a = new VisibleColumnAction(c.getColKey());
  1186. a.setCaption(c.getCaption());
  1187. if (!c.isEnabled())
  1188. a.setCollapsed(true);
  1189. actions[i] = a;
  1190. }
  1191. return actions;
  1192. }
  1193. public ApplicationConnection getClient() {
  1194. return client;
  1195. }
  1196. public String getPaintableId() {
  1197. return paintableId;
  1198. }
  1199. /**
  1200. * Returns column alignments for visible columns
  1201. */
  1202. public char[] getColumnAlignments() {
  1203. Iterator it = visibleCells.iterator();
  1204. char[] aligns = new char[visibleCells.size()];
  1205. int colIndex = 0;
  1206. while (it.hasNext()) {
  1207. aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
  1208. }
  1209. return aligns;
  1210. }
  1211. }
  1212. /**
  1213. * This Panel can only contain IScrollTableRow type of widgets. This
  1214. * "simulates" very large table, keeping spacers which take room of
  1215. * unrendered rows.
  1216. *
  1217. * @author mattitahvonen
  1218. *
  1219. */
  1220. public class IScrollTableBody extends Panel {
  1221. public static final int CELL_EXTRA_WIDTH = 20;
  1222. public static final int DEFAULT_ROW_HEIGHT = 25;
  1223. private int rowHeight = -1;
  1224. private List renderedRows = new Vector();
  1225. private boolean initDone = false;
  1226. private int totalRows;
  1227. Element preSpacer = DOM.createDiv();
  1228. Element postSpacer = DOM.createDiv();
  1229. Element container = DOM.createDiv();
  1230. Element tBody = DOM.createTBody();
  1231. Element table = DOM.createTable();
  1232. private int firstRendered;
  1233. private int lastRendered;
  1234. private char[] aligns;
  1235. IScrollTableBody() {
  1236. constructDOM();
  1237. setElement(container);
  1238. }
  1239. private void constructDOM() {
  1240. DOM.setElementProperty(table, "className", CLASSNAME + "-table");
  1241. DOM.setElementProperty(preSpacer, "className", CLASSNAME
  1242. + "-row-spacer");
  1243. DOM.setElementProperty(postSpacer, "className", CLASSNAME
  1244. + "-row-spacer");
  1245. DOM.appendChild(table, tBody);
  1246. DOM.appendChild(container, preSpacer);
  1247. DOM.appendChild(container, table);
  1248. DOM.appendChild(container, postSpacer);
  1249. }
  1250. public int getAvailableWidth() {
  1251. return DOM.getElementPropertyInt(preSpacer, "offsetWidth");
  1252. }
  1253. public void renderInitialRows(UIDL rowData, int firstIndex, int rows,
  1254. int totalRows) {
  1255. this.totalRows = totalRows;
  1256. this.firstRendered = firstIndex;
  1257. this.lastRendered = firstIndex + rows - 1;
  1258. Iterator it = rowData.getChildIterator();
  1259. aligns = tHead.getColumnAlignments();
  1260. while (it.hasNext()) {
  1261. IScrollTableRow row = new IScrollTableRow((UIDL) it.next(),
  1262. aligns);
  1263. addRow(row);
  1264. }
  1265. if (isAttached())
  1266. fixSpacers();
  1267. }
  1268. public void renderRows(UIDL rowData, int firstIndex, int rows) {
  1269. aligns = tHead.getColumnAlignments();
  1270. Iterator it = rowData.getChildIterator();
  1271. if (firstIndex == lastRendered + 1) {
  1272. while (it.hasNext()) {
  1273. IScrollTableRow row = createRow((UIDL) it.next());
  1274. addRow(row);
  1275. lastRendered++;
  1276. }
  1277. fixSpacers();
  1278. } else if (firstIndex + rows == firstRendered) {
  1279. IScrollTableRow[] rowArray = new IScrollTableRow[rows];
  1280. int i = rows;
  1281. while (it.hasNext()) {
  1282. i--;
  1283. rowArray[i] = createRow((UIDL) it.next());
  1284. }
  1285. for (i = 0; i < rows; i++) {
  1286. addRowBeforeFirstRendered(rowArray[i]);
  1287. firstRendered--;
  1288. }
  1289. // } else if (firstIndex > lastRendered || firstIndex + rows <
  1290. // firstRendered) {
  1291. } else if (true) {
  1292. // completely new set of rows
  1293. // create one row before truncating row
  1294. IScrollTableRow row = createRow((UIDL) it.next());
  1295. while (lastRendered + 1 > firstRendered)
  1296. unlinkRow(false);
  1297. firstRendered = firstIndex;
  1298. lastRendered = firstIndex - 1;
  1299. fixSpacers();
  1300. addRow(row);
  1301. lastRendered++;
  1302. while (it.hasNext()) {
  1303. addRow(createRow((UIDL) it.next()));
  1304. lastRendered++;
  1305. }
  1306. fixSpacers();
  1307. } else {
  1308. // sorted or column reordering changed
  1309. ApplicationConnection.getConsole().log(
  1310. "Bad update" + firstIndex + "/" + rows);
  1311. }
  1312. }
  1313. /**
  1314. * This mehtod is used to instantiate new rows for this table. It
  1315. * automatically sets correct widths to rows cells and assigns correct
  1316. * client reference for child widgets.
  1317. *
  1318. * This method can be called only after table has been initialized
  1319. *
  1320. * @param uidl
  1321. */
  1322. private IScrollTableRow createRow(UIDL uidl) {
  1323. IScrollTableRow row = new IScrollTableRow(uidl, aligns);
  1324. int cells = DOM.getChildCount(row.getElement());
  1325. for (int i = 0; i < cells; i++) {
  1326. Element cell = DOM.getChild(row.getElement(), i);
  1327. int w = IScrollTable.this.getColWidth(getColKeyByIndex(i));
  1328. DOM.setStyleAttribute(cell, "width", w + "px");
  1329. DOM.setStyleAttribute(DOM.getFirstChild(cell), "width", w
  1330. + "px");
  1331. }
  1332. return row;
  1333. }
  1334. private void addRowBeforeFirstRendered(IScrollTableRow row) {
  1335. IScrollTableRow first = null;
  1336. if (renderedRows.size() > 0)
  1337. first = (IScrollTableRow) renderedRows.get(0);
  1338. if (first != null && first.getStyleName().indexOf("i-odd") == -1)
  1339. row.addStyleName("i-odd");
  1340. DOM.insertChild(tBody, row.getElement(), 0);
  1341. adopt(row);
  1342. renderedRows.add(0, row);
  1343. }
  1344. private void addRow(IScrollTableRow row) {
  1345. IScrollTableRow last = null;
  1346. if (renderedRows.size() > 0)
  1347. last = (IScrollTableRow) renderedRows
  1348. .get(renderedRows.size() - 1);
  1349. if (last != null && last.getStyleName().indexOf("i-odd") == -1)
  1350. row.addStyleName("i-odd");
  1351. DOM.appendChild(tBody, row.getElement());
  1352. adopt(row);
  1353. renderedRows.add(row);
  1354. }
  1355. public Iterator iterator() {
  1356. return renderedRows.iterator();
  1357. }
  1358. public void unlinkRow(boolean fromBeginning) {
  1359. if (lastRendered - firstRendered < 0)
  1360. return;
  1361. int index;
  1362. if (fromBeginning) {
  1363. index = 0;
  1364. firstRendered++;
  1365. } else {
  1366. index = renderedRows.size() - 1;
  1367. lastRendered--;
  1368. }
  1369. IScrollTableRow toBeRemoved = (IScrollTableRow) renderedRows
  1370. .get(index);
  1371. DOM.removeChild(tBody, toBeRemoved.getElement());
  1372. this.orphan(toBeRemoved);
  1373. renderedRows.remove(index);
  1374. fixSpacers();
  1375. }
  1376. public boolean remove(Widget w) {
  1377. throw new UnsupportedOperationException();
  1378. }
  1379. protected void onAttach() {
  1380. super.onAttach();
  1381. fixSpacers();
  1382. // fix container blocks height to avoid "bouncing" when scrolling
  1383. DOM.setStyleAttribute(container, "height", totalRows
  1384. * getRowHeight() + "px");
  1385. }
  1386. private void fixSpacers() {
  1387. DOM.setStyleAttribute(preSpacer, "height", getRowHeight()
  1388. * firstRendered + "px");
  1389. DOM.setStyleAttribute(postSpacer, "height", getRowHeight()
  1390. * (totalRows - 1 - lastRendered) + "px");
  1391. }
  1392. public int getTotalRows() {
  1393. return totalRows;
  1394. }
  1395. public int getRowHeight() {
  1396. if (initDone)
  1397. return rowHeight;
  1398. else {
  1399. if (DOM.getChildCount(tBody) > 0) {
  1400. rowHeight = DOM
  1401. .getElementPropertyInt(tBody, "offsetHeight")
  1402. / DOM.getChildCount(tBody);
  1403. } else {
  1404. return DEFAULT_ROW_HEIGHT;
  1405. }
  1406. initDone = true;
  1407. return rowHeight;
  1408. }
  1409. }
  1410. public int getColWidth(int i) {
  1411. if (initDone) {
  1412. Element e = DOM.getChild(DOM.getChild(tBody, 0), i);
  1413. return DOM.getElementPropertyInt(e, "offsetWidth");
  1414. } else {
  1415. return 0;
  1416. }
  1417. }
  1418. public void setColWidth(int colIndex, int w) {
  1419. int rows = DOM.getChildCount(tBody);
  1420. for (int i = 0; i < rows; i++) {
  1421. Element cell = DOM.getChild(DOM.getChild(tBody, i), colIndex);
  1422. DOM.setStyleAttribute(cell, "width", w + "px");
  1423. DOM.setStyleAttribute(DOM.getFirstChild(cell), "width", w
  1424. + "px");
  1425. }
  1426. }
  1427. public int getLastRendered() {
  1428. return lastRendered;
  1429. }
  1430. public int getFirstRendered() {
  1431. return firstRendered;
  1432. }
  1433. public void moveCol(int oldIndex, int newIndex) {
  1434. // loop all rows and move given index to its new place
  1435. Iterator rows = iterator();
  1436. while (rows.hasNext()) {
  1437. IScrollTableRow row = (IScrollTableRow) rows.next();
  1438. Element td = DOM.getChild(row.getElement(), oldIndex);
  1439. DOM.removeChild(row.getElement(), td);
  1440. DOM.insertChild(row.getElement(), td, newIndex);
  1441. }
  1442. }
  1443. public class IScrollTableRow extends Panel implements IActionOwner {
  1444. Vector childWidgets = new Vector();
  1445. private boolean selected = false;
  1446. private int rowKey;
  1447. private String[] actionKeys = null;
  1448. private IScrollTableRow(int rowKey) {
  1449. this.rowKey = rowKey;
  1450. setElement(DOM.createElement("tr"));
  1451. DOM.sinkEvents(getElement(), Event.ONCLICK);
  1452. attachContextMenuEvent(getElement());
  1453. setStyleName(CLASSNAME + "-row");
  1454. }
  1455. protected void onDetach() {
  1456. Util.removeContextMenuEvent(getElement());
  1457. super.onDetach();
  1458. }
  1459. /**
  1460. * Attaches context menu event handler to given element. Attached
  1461. * handler fires showContextMenu function.
  1462. *
  1463. * @param el
  1464. * element where to attach contenxt menu event
  1465. */
  1466. private native void attachContextMenuEvent(Element el) /*-{
  1467. var row = this;
  1468. el.oncontextmenu = function(e) {
  1469. if(!e)
  1470. e = $wnd.event;
  1471. row.@com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable.IScrollTableBody.IScrollTableRow::showContextMenu(Lcom/google/gwt/user/client/Event;)(e);
  1472. return false;
  1473. };
  1474. }-*/;
  1475. public String getKey() {
  1476. return String.valueOf(rowKey);
  1477. }
  1478. public IScrollTableRow(UIDL uidl, char[] aligns) {
  1479. this(uidl.getIntAttribute("key"));
  1480. tHead.getColumnAlignments();
  1481. int col = 0;
  1482. // row header
  1483. if (rowHeaders) {
  1484. addCell(uidl.getStringAttribute("caption"), aligns[col++]);
  1485. }
  1486. if (uidl.hasAttribute("al"))
  1487. actionKeys = uidl.getStringArrayAttribute("al");
  1488. Iterator cells = uidl.getChildIterator();
  1489. while (cells.hasNext()) {
  1490. Object cell = cells.next();
  1491. if (cell instanceof String) {
  1492. addCell(cell.toString(), aligns[col++]);
  1493. } else {
  1494. Widget cellContent = client.getWidget((UIDL) cell);
  1495. ((Paintable) cellContent).updateFromUIDL((UIDL) cell,
  1496. client);
  1497. addCell(cellContent, aligns[col++]);
  1498. }
  1499. }
  1500. if (uidl.hasAttribute("selected") && !isSelected())
  1501. toggleSelection();
  1502. }
  1503. public void addCell(String text, char align) {
  1504. // String only content is optimized by not using Label widget
  1505. Element td = DOM.createTD();
  1506. Element container = DOM.createDiv();
  1507. DOM.setElementProperty(container, "className", CLASSNAME
  1508. + "-cell-content");
  1509. DOM.setInnerHTML(container, text);
  1510. if (align != ALIGN_LEFT) {
  1511. switch (align) {
  1512. case ALIGN_CENTER:
  1513. DOM.setStyleAttribute(container, "textAlign", "center");
  1514. break;
  1515. case ALIGN_RIGHT:
  1516. default:
  1517. DOM.setStyleAttribute(container, "textAlign", "right");
  1518. break;
  1519. }
  1520. }
  1521. DOM.appendChild(td, container);
  1522. DOM.appendChild(getElement(), td);
  1523. }
  1524. public void addCell(Widget w, char align) {
  1525. Element td = DOM.createTD();
  1526. Element container = DOM.createDiv();
  1527. DOM.setElementProperty(container, "className", CLASSNAME
  1528. + "-cell-content");
  1529. // TODO make widget cells respect align. text-align:center for
  1530. // IE, margin: auto for others
  1531. DOM.appendChild(td, container);
  1532. DOM.appendChild(getElement(), td);
  1533. DOM.appendChild(container, w.getElement());
  1534. adopt(w);
  1535. childWidgets.add(w);
  1536. }
  1537. public Iterator iterator() {
  1538. return childWidgets.iterator();
  1539. }
  1540. public boolean remove(Widget w) {
  1541. // TODO Auto-generated method stub
  1542. return false;
  1543. }
  1544. /*
  1545. * React on click that occur on content cells only
  1546. */
  1547. public void onBrowserEvent(Event event) {
  1548. String s = DOM.getElementProperty(DOM.eventGetTarget(event),
  1549. "className");
  1550. switch (DOM.eventGetType(event)) {
  1551. case Event.ONCLICK:
  1552. if ((CLASSNAME + "-cell-content").equals(s)) {
  1553. ApplicationConnection.getConsole().log("Row click");
  1554. if (selectMode > ITable.SELECT_MODE_NONE) {
  1555. toggleSelection();
  1556. client.updateVariable(paintableId, "selected",
  1557. selectedRowKeys.toArray(), immediate);
  1558. }
  1559. }
  1560. break;
  1561. default:
  1562. break;
  1563. }
  1564. super.onBrowserEvent(event);
  1565. }
  1566. public void showContextMenu(Event event) {
  1567. ApplicationConnection.getConsole().log("Context menu");
  1568. if (actionKeys != null) {
  1569. int left = DOM.eventGetClientX(event);
  1570. int top = DOM.eventGetClientY(event);
  1571. top += Window.getScrollTop();
  1572. left += Window.getScrollLeft();
  1573. client.getContextMenu().showAt(this, left, top);
  1574. }
  1575. }
  1576. public boolean isSelected() {
  1577. return selected;
  1578. }
  1579. private void toggleSelection() {
  1580. selected = !selected;
  1581. if (selected) {
  1582. if (selectMode == ITable.SELECT_MODE_SINGLE)
  1583. IScrollTable.this.deselectAll();
  1584. selectedRowKeys.add(String.valueOf(rowKey));
  1585. addStyleName("i-selected");
  1586. } else {
  1587. selectedRowKeys.remove(String.valueOf(rowKey));
  1588. removeStyleName("i-selected");
  1589. }
  1590. }
  1591. /*
  1592. * (non-Javadoc)
  1593. *
  1594. * @see com.itmill.toolkit.terminal.gwt.client.ui.IActionOwner#getActions()
  1595. */
  1596. public IAction[] getActions() {
  1597. if (actionKeys == null)
  1598. return new IAction[] {};
  1599. IAction[] actions = new IAction[actionKeys.length];
  1600. for (int i = 0; i < actions.length; i++) {
  1601. String actionKey = actionKeys[i];
  1602. ITreeAction a = new ITreeAction(this, String
  1603. .valueOf(rowKey), actionKey);
  1604. a.setCaption(getActionCaption(actionKey));
  1605. actions[i] = a;
  1606. }
  1607. return actions;
  1608. }
  1609. public ApplicationConnection getClient() {
  1610. return client;
  1611. }
  1612. public String getPaintableId() {
  1613. return paintableId;
  1614. }
  1615. }
  1616. }
  1617. public void deselectAll() {
  1618. Object[] keys = selectedRowKeys.toArray();
  1619. for (int i = 0; i < keys.length; i++) {
  1620. IScrollTableRow row = getRenderedRowByKey((String) keys[i]);
  1621. if (row != null && row.isSelected())
  1622. row.toggleSelection();
  1623. }
  1624. // still ensure all selects are removed from (not necessary rendered)
  1625. selectedRowKeys.clear();
  1626. }
  1627. }