001: /*
002: * MyGWT Widget Library
003: * Copyright(c) 2007, MyGWT.
004: * licensing@mygwt.net
005: *
006: * http://mygwt.net/license
007: */
008: package net.mygwt.ui.client.widget.table;
009:
010: import java.util.ArrayList;
011: import java.util.HashMap;
012: import java.util.List;
013: import java.util.Map;
014:
015: import net.mygwt.ui.client.Events;
016: import net.mygwt.ui.client.MyDOM;
017: import net.mygwt.ui.client.Style;
018: import net.mygwt.ui.client.event.BaseEvent;
019: import net.mygwt.ui.client.event.Listener;
020: import net.mygwt.ui.client.util.DelayedTask;
021: import net.mygwt.ui.client.util.StyleTemplate;
022: import net.mygwt.ui.client.widget.Component;
023: import net.mygwt.ui.client.widget.menu.Menu;
024:
025: import com.google.gwt.user.client.DOM;
026: import com.google.gwt.user.client.Element;
027: import com.google.gwt.user.client.Event;
028: import com.google.gwt.user.client.ui.Widget;
029: import com.google.gwt.user.client.ui.WidgetHelper;
030:
031: /**
032: * The table is used to display two-dimensional table of cells.
033: *
034: * <dl>
035: * <dt><b>Styles:</b></dt>
036: * <dd>SINGLE, MULTI, HORIZONTAL</dd>
037: *
038: * <dt><b>Events:</b></dt>
039: *
040: * <dd><b>BeforeAdd</b> : (widget, item add)<br>
041: * <div>Fires before a item is added or inserted. Listeners can set the
042: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
043: * <ul>
044: * <li>widget : this</li>
045: * <li>item : the item being added</li>
046: * <li>index : the index at which the item will be added</li>
047: * </ul>
048: * </dd>
049: *
050: * <dd><b>BeforeRemove</b> : (widget, item)<br>
051: * <div>Fires before a item is removed. Listeners can set the <code>doit</code>
052: * field to <code>false</code> to cancel the action.</div>
053: * <ul>
054: * <li>widget : this</li>
055: * <li>item : the item being removed</li>
056: * </ul>
057: * </dd>
058: *
059: * <dd><b>Add</b> : (widget, item)<br>
060: * <div>Fires after a item has been added or inserted.</div>
061: * <ul>
062: * <li>widget : this</li>
063: * <li>item : the item that was added</li>
064: * <li>index : the index at which the item will be added</li>
065: * </ul>
066: * </dd>
067: *
068: * <dd><b>Remove</b> : (widget this, item)<br>
069: * <div>Fires after a item has been removed.</div>
070: * <ul>
071: * <li>widget : this</li>
072: * <li>item : the item being removed</li>
073: * </ul>
074: * </dd>
075: *
076: * <dd><b>SelectionChange</b> : (widget this)<br>
077: * <div>Fired after the selection has changed</div>
078: * <ul>
079: * <li>widget : this</li>
080: * </ul>
081: * </dd>
082: *
083: * <dd><b>ContextMenu</b> : (widget)<br>
084: * <div>Fires before the tables context menu is shown. Listeners can set the
085: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
086: * <ul>
087: * <li>widget : this</li>
088: * </ul>
089: * </dd>
090: *
091: * <dd><b>CellClick</b> : (widget, rowIndex, index, event)<br>
092: * <div>Fired after a cell is clicked</div>
093: * <ul>
094: * <li>widget : this</li>
095: * <li>rowIndex : row index</li>
096: * <li>index : cell index</li>
097: * <li>event : the dom event</li>
098: * </ul>
099: * </dd>
100: *
101: * <dd><b>CellDoubleClick</b> : (widget, rowIndex, index, event)<br>
102: * <div>Fired after a cell is double clicked</div>
103: * <ul>
104: * <li>widget : this</li>
105: * <li>rowIndex : row index</li>
106: * <li>index : cell index</li>
107: * <li>event : the dom event</li>
108: * </ul>
109: * </dd>
110: *
111: * <dd><b>ColumnClick</b> : (widget, index column index)<br>
112: * <div>Fired after a column is clicked</div>
113: * <ul>
114: * <li>widget : this</li>
115: * <li>index : the column index</li>
116: * </ul>
117: * </dd>
118: *
119: * <dd><b>RowClick</b> : (widget, rowIndex, index cell index, event)<br>
120: * <div>Fired after a row is clicked</div>
121: * <ul>
122: * <li>widget : this</li>
123: * <li>rowIndex : the row index</li>
124: * <li>index : the cell index</li>
125: * <li>event : the dom event</li>
126: * </ul>
127: * </dd>
128: *
129: * <dd><b>RowDoubleClick</b> : (widget, rowIndex, index cell index, event)<br>
130: * <div>Fired after a row is double clicked</div>
131: * <ul>
132: * <li>widget : this</li>
133: * <li>rowIndex : the row index</li>
134: * <li>index : the cell index</li>
135: * <li>event : the dom event</li>
136: * </ul>
137: * </dd>
138: *
139: * <dd><b>SortChange</b> : (widget, index, size)<br>
140: * <div>Fires before the table is sorted. Listeners can set the
141: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
142: * <ul>
143: * <li>widget : this</li>
144: * <li>index : the column index</li>
145: * <li>size : the sort direction</li>
146: * </ul>
147: * </dd>
148: *
149: * <p>
150: * Note: Only one of the styles SINGLE and MULTI may be specified.
151: * </p>
152: *
153: * @see TableColumn
154: * @see TableColumnModel
155: */
156: public class Table extends Component implements ITable {
157:
158: /**
159: * The table's column model.
160: */
161: protected TableColumnModel cm;
162:
163: /**
164: * The selection model.
165: */
166: protected RowSelectionModel sm;
167:
168: /**
169: * The table header.
170: */
171: protected TableHeader header;
172:
173: boolean verticalLines;
174: StyleTemplate styleTemplate = null;
175: private List items;
176: private Map nodes = new HashMap();
177: private TableView view;
178: private boolean disableColumnContextMenu;
179: private boolean highlight;
180:
181: private int lastLeft;
182:
183: private DelayedTask scrollTask = new DelayedTask(new Listener() {
184: public void handleEvent(BaseEvent be) {
185: header.updateSplitBars();
186: }
187: });
188:
189: /**
190: * Creates a new single select table. A column model must be set before the
191: * table is rendered.
192: */
193: public Table() {
194: super (Style.SINGLE | Style.FOCUSABLE);
195: init();
196: }
197:
198: /**
199: * Creates a new table.
200: *
201: * @param style the style information
202: * @param cm the column model
203: */
204: public Table(int style, TableColumnModel cm) {
205: super (style | Style.FOCUSABLE);
206: this .cm = cm;
207: cm.table = this ;
208: init();
209: }
210:
211: /**
212: * Adds a item to the table.
213: *
214: * @param item the item to be added
215: */
216: public void add(TableItem item) {
217: insert(item, getItemCount());
218: }
219:
220: /**
221: * Deselects the item at the given index.
222: *
223: * @param index the item to deselect
224: */
225: public void deselect(int index) {
226: sm.deselect(index);
227: }
228:
229: /**
230: * Deselects all items.
231: */
232: public void deselectAll() {
233: sm.deselectAll();
234: }
235:
236: /**
237: * Returns the item using the specified target.
238: *
239: * @param element the element or child element
240: * @return the item
241: */
242: public TableItem findItem(Element element) {
243: int size = getItemCount();
244: for (int i = 0; i < size; i++) {
245: TableItem item = getItem(i);
246: if (DOM.isOrHasChild(item.getElement(), element)) {
247: return item;
248: }
249: }
250: return null;
251: }
252:
253: /**
254: * Returns the column at the specified index.
255: *
256: * @param index the column index
257: * @return the column
258: */
259: public TableColumn getColumn(int index) {
260: return cm.getColumn(index);
261: }
262:
263: /**
264: * Returns the column with the given id.
265: *
266: * @param id the column id
267: * @return the column
268: */
269: public TableColumn getColumn(String id) {
270: return cm.getColumn(id);
271: }
272:
273: /**
274: * Returns the column context menu enabed state.
275: *
276: * @return <code>true</code> if enabled, <code>false</code> otherwise.
277: */
278: public boolean getColumnContextMenu() {
279: return !disableColumnContextMenu;
280: }
281:
282: /**
283: * Returns the number of columns contained in the table.
284: *
285: * @return the number of columns
286: */
287: public int getColumnCount() {
288: return cm.getColumnCount();
289: }
290:
291: /**
292: * Returns the table's column model.
293: *
294: * @return the column model
295: */
296: public TableColumnModel getColumnModel() {
297: return cm;
298: }
299:
300: public Menu getContextMenu() {
301: return super .getContextMenu();
302: }
303:
304: /**
305: * Returns the item at the given index.
306: *
307: * @param index the index of the item to return
308: * @return the item at the given index or <code>null</code>
309: */
310: public TableItem getItem(int index) {
311: if (index < 0 || index >= getItemCount())
312: return null;
313: return (TableItem) items.get(index);
314: }
315:
316: /**
317: * Returns the number of items contained in the table.
318: *
319: * @return the number of items
320: */
321: public int getItemCount() {
322: return items.size();
323: }
324:
325: /**
326: * Returns the table's items.
327: *
328: * @return the table items
329: */
330: public List getItems() {
331: return items;
332: }
333:
334: /**
335: * Returns the selected item. If the list is multi-select, returns the first
336: * selected item.
337: *
338: * @return the item or <code>null</code> if no selections
339: */
340: public TableItem getSelectedItem() {
341: return sm.getSelection().size() == 0 ? null : (TableItem) sm
342: .getSelection().get(0);
343: }
344:
345: /**
346: * Returns an array of <code>TableItems</code> that are currently selected.
347: *
348: * @return a list of selected items
349: */
350: public TableItem[] getSelection() {
351: return (TableItem[]) sm.getSelection()
352: .toArray(new TableItem[0]);
353: }
354:
355: /**
356: * Returns the table's selection model.
357: *
358: * @return the selection model
359: */
360: public RowSelectionModel getSelectionModel() {
361: return sm;
362: }
363:
364: /**
365: * Returns the table's style information.
366: *
367: * @return the style information
368: */
369: public int getStyle() {
370: return style;
371: }
372:
373: /**
374: * Returns the table's header.
375: *
376: * @return the table header
377: */
378: public TableHeader getTableHeader() {
379: if (header == null) {
380: header = new TableHeader(this );
381: }
382: return header;
383: }
384:
385: /**
386: * Returns <code>true</code> if vertical lines are enabled.
387: *
388: * @return the vertical line state
389: */
390: public boolean getVerticalLines() {
391: return verticalLines;
392: }
393:
394: /**
395: * Returns the table's view.
396: *
397: * @return the view
398: */
399: public TableView getView() {
400: if (view == null) {
401: view = new TableView();
402: }
403: return view;
404: }
405:
406: /**
407: * Returns the index of the item or -1 if not found.
408: *
409: * @param item the search item
410: * @return the index of the item or -1 if not found
411: */
412: public int indexOf(TableItem item) {
413: return items.indexOf(item);
414: }
415:
416: /**
417: * Inserts a item into the table.
418: *
419: * @param item the item to insert
420: * @param index the insert location
421: */
422: public void insert(TableItem item, int index) {
423: if (fireEvent(Events.BeforeAdd, this , item, index)) {
424: items.add(index, item);
425: register(item);
426: if (rendered) {
427: view.renderItem(item, index);
428: }
429: fireEvent(Events.Add, this , item, index);
430: }
431: }
432:
433: public boolean isHighlight() {
434: return highlight;
435: }
436:
437: public void onBaseEvent(BaseEvent be) {
438: TableItem item = findItem(be.getTarget());
439: if (item != null) {
440: item.onBaseEvent(be);
441: }
442: }
443:
444: public void onBrowserEvent(Event event) {
445: super .onBrowserEvent(event);
446: int type = DOM.eventGetType(event);
447:
448: if (type == Event.ONSCROLL) {
449: int left = MyDOM.getScrollLeft(view.getScrollElement());
450: if (left == lastLeft) {
451: return;
452: }
453: lastLeft = left;
454: MyDOM.setLeft(header.getElement(), -left);
455: scrollTask.delay(400);
456: }
457: }
458:
459: /**
460: * Recalculates the ui based on the table's current size.
461: */
462: public void recalculate() {
463: if (isRendered()) {
464: header.resizeColumns(false, true);
465: }
466: }
467:
468: /**
469: * Removes the item from the table.
470: *
471: * @param item the item to be removed
472: */
473: public void remove(TableItem item) {
474: if (fireEvent(Events.BeforeRemove, this , item)) {
475: sm.remove(item);
476: items.remove(item);
477: unregister(item);
478: if (rendered) {
479: view.removeItem(item);
480: }
481: fireEvent(Events.Remove, this , item);
482: }
483: }
484:
485: /**
486: * Removes all the item's.
487: */
488: public void removeAll() {
489: int count = getItemCount();
490: for (int i = 0; i < count; i++) {
491: remove(getItem(0));
492: }
493: }
494:
495: /**
496: * Scrolls the item into view.
497: *
498: * @param item the item
499: */
500: public void scrollIntoView(TableItem item) {
501: MyDOM.scrollIntoView(item.getElement(),
502: view.getScrollElement(), false);
503: }
504:
505: /**
506: * Selects the item at the given index.
507: *
508: * @param index the row to select
509: */
510: public void select(int index) {
511: sm.select(index);
512: }
513:
514: /**
515: * Selects the the item.
516: *
517: * @param item the item to be selected
518: */
519: public void select(TableItem item) {
520: select(indexOf(item));
521: }
522:
523: /**
524: * Sets whether the column context menu is enabled. Initial value is
525: * <code>true</code>.
526: *
527: * @param enabled the enabled state
528: */
529: public void setColumnContextMenu(boolean enabled) {
530: this .disableColumnContextMenu = !enabled;
531: }
532:
533: public void setColumnModel(TableColumnModel cm) {
534: this .cm = cm;
535: cm.table = this ;
536: }
537:
538: public void setContextMenu(Menu menu) {
539: super .setContextMenu(menu);
540: }
541:
542: public void setHighlight(boolean highlight) {
543: this .highlight = highlight;
544: }
545:
546: /**
547: * Sets the table's selection model.
548: *
549: * @param sm the selection model
550: */
551: public void setSelectionModel(RowSelectionModel sm) {
552: if (sm != null) {
553: this .sm.unbind(this );
554: this .sm = null;
555: }
556: this .sm = sm;
557: this .sm.init(this );
558: }
559:
560: /**
561: * Sets the table's style. Has no effect if called after the table has been
562: * rendered. See the class documentation for a list of valid style.
563: *
564: * @param style the style
565: */
566: public void setStyle(int style) {
567: if (!isRendered()) {
568: this .style = style;
569: }
570: }
571:
572: /**
573: * Sets the table's header. Should only be called when providing a custom
574: * table header. Has no effect if called after the table has been rendered.
575: *
576: * @param header the table header
577: */
578: public void setTableHeader(TableHeader header) {
579: if (!isRendered()) {
580: this .header = header;
581: }
582: }
583:
584: /**
585: * Sets whether cells should have have a horizontal border. Default value is
586: * <code>false</code>.
587: *
588: * @param show <code>true</code> to display horizontal borders
589: */
590: public void setVerticalLines(boolean show) {
591: this .verticalLines = show;
592: }
593:
594: /**
595: * Sets the table's view. Provides a way to provide specialized views. table
596: * views.
597: *
598: * @param view the view
599: */
600: public void setView(TableView view) {
601: this .view = view;
602: }
603:
604: /**
605: * Sorts the table using the specified column index.
606: *
607: * @param index the column index
608: * @param direction the direction to sort (NONE, ASC, DESC)
609: */
610: public void sort(int index, int direction) {
611: if (isRendered()) {
612: BaseEvent be = new BaseEvent();
613: be.index = index;
614: be.size = direction;
615: if (fireEvent(Events.SortChange, be)) {
616: getTableHeader().sort(index, direction);
617: getView().sort(index, direction);
618: }
619: }
620: }
621:
622: protected void doAttachChildren() {
623: WidgetHelper.doAttach(header);
624: int count = getItemCount();
625: for (int i = 0; i < count; i++) {
626: TableItem item = getItem(i);
627: if (item.hasWidgets) {
628: int ct = item.getValues().length;
629: for (int j = 0; j < ct; j++) {
630: Object obj = item.getValue(j);
631: if (obj != null && obj instanceof Widget) {
632: WidgetHelper
633: .doAttach((Widget) item.getValue(j));
634: }
635: }
636: }
637: }
638: }
639:
640: protected void doDetachChildren() {
641: WidgetHelper.doDetach(header);
642: int count = getItemCount();
643: for (int i = 0; i < count; i++) {
644: TableItem item = getItem(i);
645: if (item.hasWidgets) {
646: int ct = item.getValues().length;
647: for (int j = 0; j < ct; j++) {
648: Object obj = item.getValue(j);
649: if (obj != null && obj instanceof Widget) {
650: WidgetHelper
651: .doDetach((Widget) item.getValue(j));
652: }
653: }
654: }
655: }
656: }
657:
658: protected String getRenderedValue(int column, Object value) {
659: TableColumn col = cm.getColumn(column);
660: if (col.getRenderer() != null) {
661: return col.getRenderer().render(col.getID(), value);
662: } else {
663: if (value != null) {
664: return value.toString();
665: }
666: return null;
667: }
668: }
669:
670: protected void init() {
671: baseStyle = "my-tbl";
672: items = new ArrayList();
673: initSelectionModel();
674: disableTextSelection(true);
675: }
676:
677: protected void initSelectionModel() {
678: if ((style & Style.MULTI) != 0) {
679: sm = new RowSelectionModel(Style.MULTI);
680: } else {
681: sm = new RowSelectionModel(Style.SINGLE);
682: }
683: sm.init(this );
684: }
685:
686: protected void onRender() {
687: setElement(DOM.createDiv());
688: setStyleName("my-tbl");
689:
690: header = getTableHeader();
691: header.init(this );
692:
693: DOM.appendChild(getElement(), header.getElement());
694:
695: if (styleTemplate == null) {
696: Element style = DOM.createElement("style");
697: DOM
698: .setElementProperty(style, "id", getId()
699: + "-cols-style");
700: DOM.appendChild(MyDOM.getHead(), style);
701: styleTemplate = new StyleTemplate(style);
702: }
703:
704: for (int i = 0, n = cm.getColumnCount(); i < n; i++) {
705: TableColumn c = cm.getColumn(i);
706: int w = cm.getWidthInPixels(c.index);
707: styleTemplate.set("." + getId() + "-col-" + i, "width:" + w
708: + "px !important;");
709: }
710: styleTemplate.apply();
711:
712: view = getView();
713: view.init(this );
714: view.render();
715:
716: sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
717: | Event.KEYEVENTS | Event.ONSCROLL);
718:
719: view.renderItems();
720: }
721:
722: protected void onResize(int width, int height) {
723: header.resizeColumns(false, true);
724: }
725:
726: protected void onRightClick(BaseEvent be) {
727: TableItem item = findItem(be.getTarget());
728: if (item != null) {
729: item.onClick(be);
730: }
731: super .onRightClick(be);
732: }
733:
734: protected void onShowContextMenu(int x, int y) {
735: super .onShowContextMenu(x, y);
736: getView().clearHoverStyles();
737: }
738:
739: private void register(TableItem item) {
740: nodes.put(item.getId(), item);
741: }
742:
743: private void unregister(TableItem item) {
744: nodes.remove(item.getId());
745: }
746:
747: }
|