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

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