summaryrefslogtreecommitdiffstats
path: root/src/com/vaadin
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/vaadin')
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java852
-rw-r--r--src/com/vaadin/ui/Component.java19
-rw-r--r--src/com/vaadin/ui/Table.java488
3 files changed, 1328 insertions, 31 deletions
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
index 72efa90ab4..fbd762027b 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
@@ -12,6 +12,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.NodeList;
@@ -84,10 +85,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
public static final String CLASSNAME = "v-table";
public static final String ITEM_CLICK_EVENT_ID = "itemClick";
+ public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick";
+ public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick";
private static final double CACHE_RATE_DEFAULT = 2;
/**
+ * The default multi select mode where simple left clicks only selects one
+ * item, CTRL+left click selects multiple items and SHIFT-left click selects
+ * a range of items.
+ */
+ private static final int MULTISELECT_MODE_DEFAULT = 0;
+
+ /**
* multiple of pagelength which component will cache when requesting more
* rows
*/
@@ -117,6 +127,46 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private final HashSet<String> selectedRowKeys = new HashSet<String>();
+ /**
+ * Represents a select range of rows
+ */
+ private class SelectionRange {
+ /**
+ * The starting key of the range
+ */
+ private int startRowKey;
+
+ /**
+ * The ending key of the range
+ */
+ private int endRowKey;
+
+ /**
+ * Constuctor.
+ *
+ * @param startRowKey
+ * The range start. Must be less than endRowKey
+ * @param endRowKey
+ * The range end. Must be bigger than startRowKey
+ */
+ public SelectionRange(int startRowKey, int endRowKey) {
+ this.startRowKey = startRowKey;
+ this.endRowKey = endRowKey;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return startRowKey + "-" + endRowKey;
+ }
+ };
+
+ private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
+
private boolean initializedAndAttached = false;
/**
@@ -126,6 +176,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private final TableHead tHead = new TableHead();
+ private final TableFooter tFoot = new TableFooter();
+
private final ScrollPanel bodyContainer = new ScrollPanel();
private int totalRows;
@@ -149,6 +201,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private Element scrollPositionElement;
private boolean enabled;
private boolean showColHeaders;
+ private boolean showColFooters;
/** flag to indicate that table body has changed */
private boolean isNewBody = true;
@@ -166,6 +219,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private boolean rendering = false;
private int dragmode;
+ private int multiselectmode;
+ private int lastSelectedRowKey = -1;
+
public VScrollTable() {
bodyContainer.addScrollHandler(this);
bodyContainer.setStyleName(CLASSNAME + "-body");
@@ -173,14 +229,23 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
setStyleName(CLASSNAME);
add(tHead);
add(bodyContainer);
+ add(tFoot);
rowRequestHandler = new RowRequestHandler();
-
}
@SuppressWarnings("unchecked")
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
rendering = true;
+
+ /*
+ * We need to do this before updateComponent since updateComponent calls
+ * this.setHeight() which will calculate a new body height depending on
+ * the space available.
+ */
+ showColFooters = uidl.getBooleanAttribute("colfooters");
+ tFoot.setVisible(showColFooters);
+
if (client.updateComponent(this, uidl, true)) {
rendering = false;
return;
@@ -199,6 +264,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (scrollBody != null) {
if (totalRows == 0) {
tHead.clear();
+ tFoot.clear();
}
initializedAndAttached = false;
initialContentReceived = false;
@@ -210,12 +276,16 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
dragmode = uidl.hasAttribute("dragmode") ? uidl
.getIntAttribute("dragmode") : 0;
+ multiselectmode = uidl.hasAttribute("multiselectmode") ? uidl
+ .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT;
+
setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
: CACHE_RATE_DEFAULT);
recalcWidths = uidl.hasAttribute("recalcWidths");
if (recalcWidths) {
tHead.clear();
+ tFoot.clear();
}
if (uidl.hasAttribute("pagelength")) {
@@ -245,6 +315,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
final Set<String> selectedKeys = uidl
.getStringArrayVariableAsSet("selected");
selectedRowKeys.clear();
+ selectedRowRanges.clear();
for (String string : selectedKeys) {
selectedRowKeys.add(string);
}
@@ -285,6 +356,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
updateActionMap(c);
} else if (c.getTag().equals("visiblecolumns")) {
tHead.updateCellsFromUIDL(c);
+ tFoot.updateCellsFromUIDL(c);
} else if (c.getTag().equals("-ac")) {
ac = c;
}
@@ -302,6 +374,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
updateHeader(uidl.getStringArrayAttribute("vcolorder"));
+ updateFooter(uidl.getStringArrayAttribute("vcolorder"));
+
if (!recalcWidths && initializedAndAttached) {
updateBody(rowData, uidl.getIntAttribute("firstrow"), uidl
.getIntAttribute("rows"));
@@ -422,6 +496,38 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
/**
+ * Updates footers.
+ * <p>
+ * Update headers whould be called before this method is called!
+ * </p>
+ *
+ * @param strings
+ */
+ private void updateFooter(String[] strings) {
+ if (strings == null) {
+ return;
+ }
+
+ // Add dummy column if row headers are present
+ int colIndex = 0;
+ if (showRowHeaders) {
+ tFoot.enableColumn("0", colIndex);
+ colIndex++;
+ } else {
+ tFoot.removeCell("0");
+ }
+
+ int i;
+ for (i = 0; i < strings.length; i++) {
+ final String cid = strings[i];
+ tFoot.enableColumn(cid, colIndex);
+ colIndex++;
+ }
+
+ tFoot.setVisible(showColFooters);
+ }
+
+ /**
* @param uidl
* which contains row data
* @param firstRow
@@ -497,9 +603,18 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
- final HeaderCell cell = tHead.getHeaderCell(colIndex);
- cell.setWidth(w, isDefinedWidth);
+ // Set header column width
+ final HeaderCell hcell = tHead.getHeaderCell(colIndex);
+ hcell.setWidth(w, isDefinedWidth);
+
+ // Set body column width
scrollBody.setColWidth(colIndex, w);
+
+ // Set footer column width
+ final FooterCell fcell = tFoot.getFooterCell(colIndex);
+ if (fcell != null) {
+ fcell.setWidth(w, isDefinedWidth);
+ }
}
private int getColWidth(String colKey) {
@@ -528,6 +643,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// Change body order
scrollBody.moveCol(oldIndex, newIndex);
+ // Change footer order
+ tFoot.moveCell(oldIndex, newIndex);
+
/*
* Build new columnOrder and update it to server Note that columnOrder
* also contains collapsed columns so we cannot directly build it from
@@ -621,6 +739,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
final int[] widths = new int[tHead.visibleCells.size()];
tHead.enableBrowserIntelligence();
+ tFoot.enableBrowserIntelligence();
+
// first loop: collect natural widths
while (headCells.hasNext()) {
final HeaderCell hCell = (HeaderCell) headCells.next();
@@ -646,6 +766,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
tHead.disableBrowserIntelligence();
+ tFoot.disableBrowserIntelligence();
boolean willHaveScrollbarz = willHaveScrollbars();
@@ -1129,7 +1250,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
@Override
public void onBrowserEvent(Event event) {
if (enabled && event != null) {
- if (isResizing || event.getTarget() == colResizeWidget) {
+ if (isResizing
+ || event.getEventTarget().cast() == colResizeWidget) {
onResizeEvent(event);
} else {
handleCaptionEvent(event);
@@ -1166,6 +1288,24 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
floatingCopyOfHeaderCell = null;
}
+ /**
+ * Fires a header click event after the user has clicked a column header cell
+ *
+ * @param event
+ * The click event
+ */
+ private void fireHeaderClickedEvent(Event event) {
+ if (client.hasEventListeners(VScrollTable.this,
+ HEADER_CLICK_EVENT_ID)) {
+ MouseEventDetails details = new MouseEventDetails(event);
+ client.updateVariable(paintableId, "headerClickEvent", details
+ .toString(), false);
+ client
+ .updateVariable(paintableId, "headerClickCID", cid,
+ immediate);
+ }
+ }
+
protected void handleCaptionEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONMOUSEDOWN:
@@ -1216,8 +1356,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
* cache_rate + pageLength));
rowRequestHandler.deferRowFetch();
}
+ fireHeaderClickedEvent(event);
break;
}
+ fireHeaderClickedEvent(event);
break;
case Event.ONMOUSEMOVE:
if (dragging) {
@@ -1502,8 +1644,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
headerChangedDuringUpdate = true;
}
}
-
}
+
// check for orphaned header cells
for (Iterator<String> cit = availableCells.keySet().iterator(); cit
.hasNext();) {
@@ -1513,7 +1655,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
cit.remove();
}
}
-
}
public void enableColumn(String cid, int index) {
@@ -1661,7 +1802,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
@Override
public void onBrowserEvent(Event event) {
if (enabled) {
- if (event.getTarget() == columnSelector) {
+ if (event.getEventTarget().cast() == columnSelector) {
final int left = DOM.getAbsoluteLeft(columnSelector);
final int top = DOM.getAbsoluteTop(columnSelector)
+ DOM.getElementPropertyInt(columnSelector,
@@ -1795,6 +1936,536 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
/**
+ * A cell in the footer
+ */
+ public class FooterCell extends Widget {
+ private Element td = DOM.createTD();
+ private Element captionContainer = DOM.createDiv();
+ private char align = ALIGN_LEFT;
+ private int width = -1;
+ private float expandRatio = 0;
+ private String cid;
+
+ public FooterCell(String colId, String headerText) {
+ cid = colId;
+
+ setText(headerText);
+
+ DOM.setElementProperty(captionContainer, "className", CLASSNAME
+ + "-footer-container");
+
+ // ensure no clipping initially (problem on column additions)
+ DOM.setStyleAttribute(captionContainer, "overflow", "visible");
+
+ DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
+
+ DOM.appendChild(td, captionContainer);
+
+ DOM.sinkEvents(td, Event.MOUSEEVENTS);
+
+ setElement(td);
+ }
+
+ /**
+ * Sets the text of the footer
+ *
+ * @param footerText
+ * The text in the footer
+ */
+ public void setText(String footerText) {
+ DOM.setInnerHTML(captionContainer, footerText);
+ }
+
+ /**
+ * Set alignment of the text in the cell
+ *
+ * @param c
+ * The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
+ * ALIGN_RIGHT
+ */
+ public void setAlign(char c) {
+ if (align != c) {
+ switch (c) {
+ case ALIGN_CENTER:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "center");
+ break;
+ case ALIGN_RIGHT:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "right");
+ break;
+ default:
+ DOM.setStyleAttribute(captionContainer, "textAlign", "");
+ break;
+ }
+ }
+ align = c;
+ }
+
+ /**
+ * Get the alignment of the text int the cell
+ *
+ * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
+ */
+ public char getAlign() {
+ return align;
+ }
+
+ /**
+ * Sets the width of the cell
+ *
+ * @param w
+ * The width of the cell
+ * @param ensureDefinedWidth
+ * Ensures the the given width is not recalculated
+ */
+ public void setWidth(int w, boolean ensureDefinedWidth) {
+
+ if (ensureDefinedWidth) {
+ // on column resize expand ratio becomes zero
+ expandRatio = 0;
+ }
+ if (width == w) {
+ return;
+ }
+ width = w;
+ if (width <= 0) {
+ // go to default mode, clip content if necessary
+ DOM.setStyleAttribute(captionContainer, "overflow", "");
+ }
+ if (w == -1) {
+ DOM.setStyleAttribute(captionContainer, "width", "");
+ setWidth("");
+ } else {
+
+ /*
+ * Reduce width with one pixel for the right border since the
+ * footers does not have any spacers between them.
+ *
+ * IE6 will calculate the footer width wrong by 2 pixels due to
+ * borders used so add it to border widths.
+ */
+ int borderWidths = 1;
+ if (BrowserInfo.get().isIE6()) {
+ borderWidths += 2;
+ }
+
+ // Set the container width (check for negative value)
+ if (w - borderWidths >= 0) {
+ captionContainer.getStyle().setPropertyPx("width",
+ w - borderWidths);
+ } else {
+ captionContainer.getStyle().setPropertyPx("width", 0);
+ }
+
+ /*
+ * if we already have tBody, set the header width properly, if
+ * not defer it. IE will fail with complex float in table header
+ * unless TD width is not explicitly set.
+ */
+ if (scrollBody != null) {
+ /*
+ * Reduce with one since footer does not have any spacers,
+ * instead a 1 pixel border.
+ */
+ int tdWidth = width + scrollBody.getCellExtraWidth()
+ - borderWidths;
+ setWidth(tdWidth + "px");
+ } else {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+
+ int borderWidths = 1;
+ if (BrowserInfo.get().isIE6()) {
+ borderWidths += 2;
+ }
+
+ int tdWidth = width
+ + scrollBody.getCellExtraWidth()
+ - borderWidths;
+ setWidth(tdWidth + "px");
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Sets the width to undefined
+ */
+ public void setUndefinedWidth() {
+ setWidth(-1, false);
+ }
+
+ /**
+ * Sets the expand ratio of the cell
+ *
+ * @param floatAttribute
+ * The expand ratio
+ */
+ public void setExpandRatio(float floatAttribute) {
+ expandRatio = floatAttribute;
+ }
+
+ /**
+ * Returns the expand ration of the cell
+ *
+ * @return The expand ratio
+ */
+ public float getExpandRatio() {
+ return expandRatio;
+ }
+
+ /**
+ * Is the cell enabled?
+ *
+ * @return True if enabled else False
+ */
+ public boolean isEnabled() {
+ return getParent() != null;
+ }
+
+ /**
+ * Handle column clicking
+ */
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (enabled && event != null) {
+ handleCaptionEvent(event);
+ }
+ }
+
+ /**
+ * Handles a event on the captions
+ *
+ * @param event
+ * The event to handle
+ */
+ protected void handleCaptionEvent(Event event) {
+ if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+ fireFooterClickedEvent(event);
+ }
+ }
+
+ /**
+ * Fires a footer click event after the user has clicked a column footer
+ * cell
+ *
+ * @param event
+ * The click event
+ */
+ private void fireFooterClickedEvent(Event event) {
+ if (client.hasEventListeners(VScrollTable.this,
+ FOOTER_CLICK_EVENT_ID)) {
+ MouseEventDetails details = new MouseEventDetails(event);
+ client.updateVariable(paintableId, "footerClickEvent", details
+ .toString(), false);
+ client.updateVariable(paintableId, "footerClickCID", cid,
+ immediate);
+ }
+ }
+
+ /**
+ * Returns the column key of the column
+ *
+ * @return The column key
+ */
+ public String getColKey() {
+ return cid;
+ }
+ }
+
+ /**
+ * HeaderCell that is header cell for row headers.
+ *
+ * Reordering disabled and clicking on it resets sorting.
+ */
+ public class RowHeadersFooterCell extends FooterCell {
+
+ RowHeadersFooterCell() {
+ super("0", "");
+ }
+
+ @Override
+ protected void handleCaptionEvent(Event event) {
+ // NOP: RowHeaders cannot be reordered
+ // TODO It'd be nice to reset sorting here
+ }
+ }
+
+ /**
+ * The footer of the table which can be seen in the bottom of the Table.
+ */
+ public class TableFooter extends Panel {
+
+ private static final int WRAPPER_WIDTH = 9000;
+
+ ArrayList<Widget> visibleCells = new ArrayList<Widget>();
+ HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
+
+ Element div = DOM.createDiv();
+ Element hTableWrapper = DOM.createDiv();
+ Element hTableContainer = DOM.createDiv();
+ Element table = DOM.createTable();
+ Element headerTableBody = DOM.createTBody();
+ Element tr = DOM.createTR();
+
+ public TableFooter() {
+
+ DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+ DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
+ + "-footer");
+
+ DOM.appendChild(table, headerTableBody);
+ DOM.appendChild(headerTableBody, tr);
+ DOM.appendChild(hTableContainer, table);
+ DOM.appendChild(hTableWrapper, hTableContainer);
+ DOM.appendChild(div, hTableWrapper);
+ setElement(div);
+
+ setStyleName(CLASSNAME + "-footer-wrap");
+
+ availableCells.put("0", new RowHeadersFooterCell());
+ }
+
+ @Override
+ public void clear() {
+ for (String cid : availableCells.keySet()) {
+ removeCell(cid);
+ }
+ availableCells.clear();
+ availableCells.put("0", new RowHeadersFooterCell());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
+ * .ui.Widget)
+ */
+ @Override
+ public boolean remove(Widget w) {
+ if (visibleCells.contains(w)) {
+ visibleCells.remove(w);
+ orphan(w);
+ DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
+ */
+ public Iterator<Widget> iterator() {
+ return visibleCells.iterator();
+ }
+
+ /**
+ * Gets a footer cell which represents the given columnId
+ *
+ * @param cid
+ * The columnId
+ *
+ * @return The cell
+ */
+ public FooterCell getFooterCell(String cid) {
+ return availableCells.get(cid);
+ }
+
+ /**
+ * Gets a footer cell by using a column index
+ *
+ * @param index
+ * The index of the column
+ * @return The Cell
+ */
+ public FooterCell getFooterCell(int index) {
+ if (index < visibleCells.size()) {
+ return (FooterCell) visibleCells.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Updates the cells contents when updateUIDL request is received
+ *
+ * @param uidl
+ * The UIDL
+ */
+ public void updateCellsFromUIDL(UIDL uidl) {
+ Iterator<?> columnIterator = uidl.getChildIterator();
+ HashSet<String> updated = new HashSet<String>();
+ updated.add("0");
+ while (columnIterator.hasNext()) {
+ final UIDL col = (UIDL) columnIterator.next();
+ final String cid = col.getStringAttribute("cid");
+ updated.add(cid);
+
+ String caption = col.getStringAttribute("fcaption");
+ FooterCell c = getFooterCell(cid);
+ if (c == null) {
+ c = new FooterCell(cid, caption);
+ availableCells.put(cid, c);
+ if (initializedAndAttached) {
+ // we will need a column width recalculation
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ } else {
+ c.setText(caption);
+ }
+
+ if (col.hasAttribute("align")) {
+ c.setAlign(col.getStringAttribute("align").charAt(0));
+ }
+ if (col.hasAttribute("width")) {
+ final String width = col.getStringAttribute("width");
+ c.setWidth(Integer.parseInt(width), true);
+ } else if (recalcWidths) {
+ c.setUndefinedWidth();
+ }
+ if (col.hasAttribute("er")) {
+ c.setExpandRatio(col.getFloatAttribute("er"));
+ }
+ if (col.hasAttribute("collapsed")) {
+ // ensure header is properly removed from parent (case when
+ // collapsing happens via servers side api)
+ if (c.isAttached()) {
+ c.removeFromParent();
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ // check for orphaned header cells
+ for (Iterator<String> cit = availableCells.keySet().iterator(); cit
+ .hasNext();) {
+ String cid = cit.next();
+ if (!updated.contains(cid)) {
+ removeCell(cid);
+ cit.remove();
+ }
+ }
+ }
+
+ /**
+ * Set a footer cell for a specified column index
+ *
+ * @param index
+ * The index
+ * @param cell
+ * The footer cell
+ */
+ public void setFooterCell(int index, FooterCell cell) {
+ if (cell.isEnabled()) {
+ // we're moving the cell
+ DOM.removeChild(tr, cell.getElement());
+ orphan(cell);
+ }
+ if (index < visibleCells.size()) {
+ // insert to right slot
+ DOM.insertChild(tr, cell.getElement(), index);
+ adopt(cell);
+ visibleCells.add(index, cell);
+ } else if (index == visibleCells.size()) {
+ // simply append
+ DOM.appendChild(tr, cell.getElement());
+ adopt(cell);
+ visibleCells.add(cell);
+ } else {
+ throw new RuntimeException(
+ "Header cells must be appended in order");
+ }
+ }
+
+ /**
+ * Remove a cell by using the columnId
+ *
+ * @param colKey
+ * The columnId to remove
+ */
+ public void removeCell(String colKey) {
+ final FooterCell c = getFooterCell(colKey);
+ remove(c);
+ }
+
+ /**
+ * Enable a column (Sets the footer cell)
+ *
+ * @param cid
+ * The columnId
+ * @param index
+ * The index of the column
+ */
+ public void enableColumn(String cid, int index) {
+ final FooterCell c = getFooterCell(cid);
+ if (!c.isEnabled() || getFooterCell(index) != c) {
+ setFooterCell(index, c);
+ if (initializedAndAttached) {
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ /**
+ * Disable browser measurement of the table width
+ */
+ public void disableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
+ + "px");
+ }
+
+ /**
+ * Enable browser measurement of the table width
+ */
+ public void enableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", "");
+ }
+
+ /**
+ * Set the horizontal position in the cell in the footer. This is done
+ * when a horizontal scrollbar is present.
+ *
+ * @param scrollLeft
+ * The value of the leftScroll
+ */
+ public void setHorizontalScrollPosition(int scrollLeft) {
+ if (BrowserInfo.get().isIE6()) {
+ hTableWrapper.getStyle().setProperty("position", "relative");
+ hTableWrapper.getStyle().setPropertyPx("left", -scrollLeft);
+ } else {
+ hTableWrapper.setScrollLeft(scrollLeft);
+ }
+ }
+
+ /**
+ * Swap cells when the column are dragged
+ *
+ * @param oldIndex
+ * The old index of the cell
+ * @param newIndex
+ * The new index of the cell
+ */
+ public void moveCell(int oldIndex, int newIndex) {
+ final FooterCell hCell = getFooterCell(oldIndex);
+ final Element cell = hCell.getElement();
+
+ visibleCells.remove(oldIndex);
+ DOM.removeChild(tr, cell);
+
+ DOM.insertChild(tr, cell, newIndex);
+ visibleCells.add(newIndex, hCell);
+ }
+ }
+
+ /**
* This Panel can only contain VScrollTableRow type of widgets. This
* "simulates" very large table, keeping spacers which take room of
* unrendered rows.
@@ -2353,7 +3024,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
}
if (uidl.hasAttribute("selected") && !isSelected()) {
- toggleSelection();
+ toggleSelection(true);
}
}
@@ -2484,6 +3155,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
}
+ /**
+ * Add this to the element mouse down event by using
+ * element.setPropertyJSO
+ * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it
+ * then again when the mouse is depressed in the mouse up event.
+ *
+ * @return Returns the JSO preventing text selection
+ */
+ private native JavaScriptObject applyDisableTextSelectionIEHack()
+ /*-{
+ return function(){ return false; };
+ }-*/;
+
/*
* React on click that occur on content cells only
*/
@@ -2501,11 +3185,61 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
handleClickEvent(event, targetTdOrTr);
if (event.getButton() == Event.BUTTON_LEFT
&& selectMode > Table.SELECT_MODE_NONE) {
- toggleSelection();
+
+ // Ctrl+Shift click
+ if ((event.getCtrlKey() || event.getMetaKey())
+ && event.getShiftKey()
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ toggleShiftSelection(false);
+
+ // Ctrl click
+ } else if ((event.getCtrlKey() || event
+ .getMetaKey())
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ toggleSelection(true);
+
+ // Shift click
+ } else if (event.getShiftKey()
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ toggleShiftSelection(true);
+
+ // click
+ } else {
+ if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ deselectAll();
+ }
+ toggleSelection(multiselectmode == MULTISELECT_MODE_DEFAULT);
+ }
+
+ // Remove IE text selection hack
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast())
+ .setPropertyJSO(
+ "onselectstart", null);
+ }
+
// Note: changing the immediateness of this
// might
// require changes to "clickEvent" immediateness
// also.
+ if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ Set<String> ranges = new HashSet<String>();
+ for (SelectionRange range : selectedRowRanges) {
+ ranges.add(range.toString());
+ }
+ client
+ .updateVariable(
+ paintableId,
+ "selectedRanges",
+ ranges
+ .toArray(new String[selectedRowRanges
+ .size()]),
+ false);
+ }
+
client
.updateVariable(
paintableId,
@@ -2567,6 +3301,23 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
event.preventDefault();
event.stopPropagation();
+ } else if (event.getCtrlKey()
+ || event.getShiftKey()
+ || event.getMetaKey()
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ // Prevent default text selection in Firefox
+ event.preventDefault();
+
+ // Prevent default text selection in IE
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast())
+ .setPropertyJSO(
+ "onselectstart",
+ applyDisableTextSelectionIEHack());
+ }
+
+ event.stopPropagation();
}
break;
case Event.ONMOUSEOUT:
@@ -2642,19 +3393,27 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
left += Window.getScrollLeft();
client.getContextMenu().showAt(this, left, top);
}
- event.cancelBubble(true);
+ event.stopPropagation();
event.preventDefault();
}
+ /**
+ * Has the row been selected?
+ *
+ * @return Returns true if selected, else false
+ */
public boolean isSelected() {
return selected;
}
- private void toggleSelection() {
+ /**
+ * Toggle the selection of the row
+ */
+ public void toggleSelection(boolean ctrlSelect) {
selected = !selected;
if (selected) {
- if (selectMode == Table.SELECT_MODE_SINGLE) {
- deselectAll();
+ if (ctrlSelect) {
+ lastSelectedRowKey = rowKey;
}
selectedRowKeys.add(String.valueOf(rowKey));
addStyleName("v-selected");
@@ -2664,6 +3423,59 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
}
+ /**
+ * Is called when a user clicks an item when holding SHIFT key down.
+ * This will select a new range from the last cell clicked
+ *
+ * @param deselectPrevious
+ * Should the previous selected range be deselected
+ */
+ private void toggleShiftSelection(boolean deselectPrevious) {
+
+ /*
+ * Ensures that we are in multiselect mode and that we have a
+ * previous selection which was not a deselection
+ */
+ if (selectedRowKeys.isEmpty() || lastSelectedRowKey < 0) {
+ // No previous selection found
+ deselectAll();
+ toggleSelection(true);
+ return;
+ }
+
+ // Set the selectable range
+ int startKey = lastSelectedRowKey;
+ int endKey = rowKey;
+ if (endKey < startKey) {
+ // Swap keys if in the wrong order
+ startKey ^= endKey;
+ endKey ^= startKey;
+ startKey ^= endKey;
+ }
+
+ // Deselect previous items if so desired
+ if (deselectPrevious) {
+ deselectAll();
+ }
+
+ // Select the range (not including this row)
+ for (int r = startKey; r <= endKey; r++) {
+ if (r != rowKey) {
+ VScrollTableRow row = getRenderedRowByKey(String
+ .valueOf(r));
+ if (row != null && !row.isSelected()) {
+ row.toggleSelection(false);
+ }
+ }
+ }
+
+ // Toggle clicked rows selection
+ toggleSelection(false);
+
+ // Add range
+ selectedRowRanges.add(new SelectionRange(startKey, endKey));
+ }
+
/*
* (non-Javadoc)
*
@@ -2763,17 +3575,20 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
}
+ /**
+ * Deselects all items
+ */
public void deselectAll() {
final Object[] keys = selectedRowKeys.toArray();
for (int i = 0; i < keys.length; i++) {
final VScrollTableRow row = getRenderedRowByKey((String) keys[i]);
if (row != null && row.isSelected()) {
- row.toggleSelection();
+ row.toggleSelection(false);
}
}
// still ensure all selects are removed from (not necessary rendered)
selectedRowKeys.clear();
-
+ selectedRowRanges.clear();
}
/**
@@ -2943,6 +3758,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private void setContentWidth(int pixels) {
tHead.setWidth(pixels + "px");
bodyContainer.setWidth(pixels + "px");
+ tFoot.setWidth(pixels + "px");
}
private int borderWidth = -1;
@@ -2967,7 +3783,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
*/
private void setContainerHeight() {
if (height != null && !"".equals(height)) {
- int contentH = getOffsetHeight() - tHead.getOffsetHeight();
+ int contentH = getOffsetHeight() - tHead.getOffsetHeight()
+ - tFoot.getOffsetHeight();
contentH -= getContentAreaBorderHeight();
if (contentH < 0) {
contentH = 0;
@@ -3095,6 +3912,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// fix headers horizontal scrolling
tHead.setHorizontalScrollPosition(scrollLeft);
+ // fix footers horizontal scrolling
+ tFoot.setHorizontalScrollPosition(scrollLeft);
+
firstRowInViewPort = (int) Math.ceil(scrollTop
/ scrollBody.getRowHeight());
if (firstRowInViewPort > totalRows - pageLength) {
diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java
index 251a415e83..cfc648d9b0 100644
--- a/src/com/vaadin/ui/Component.java
+++ b/src/com/vaadin/ui/Component.java
@@ -441,7 +441,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* <pre>
* RichTextArea area = new RichTextArea();
* area.setCaption(&quot;You can edit stuff here&quot;);
- * area.setValue(&quot;&lt;h1&gt;Helpful Heading&lt;/h1&gt;&quot; + &quot;&lt;p&gt;All this is for you to edit.&lt;/p&gt;&quot;);
+ * area.setValue(&quot;&lt;h1&gt;Helpful Heading&lt;/h1&gt;&quot;
+ * + &quot;&lt;p&gt;All this is for you to edit.&lt;/p&gt;&quot;);
* </pre>
*
* <p>
@@ -573,7 +574,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* Gets the application object to which the component is attached.
*
* <p>
- * The method will return {@code null} if the component has not yet been
+ * The method will return {@code null} if the component is not currently
* attached to an application. This is often a problem in constructors of
* regular components and in the initializers of custom composite
* components. A standard workaround is to move the problematic
@@ -716,7 +717,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* as an empty collection.
*/
public void childRequestedRepaint(
- Collection<RepaintRequestListener> alreadyNotified);
+ Collection<RepaintRequestListener> alreadyNotified);
/* Component event framework */
@@ -819,11 +820,13 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
*
* public void componentEvent(Event event) {
* // Act according to the source of the event
- * if (event.getSource() == ok &amp;&amp; event.getClass() == Button.ClickEvent.class)
+ * if (event.getSource() == ok
+ * &amp;&amp; event.getClass() == Button.ClickEvent.class)
* getWindow().showNotification(&quot;Click!&quot;);
*
* // Display source component and event class names
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName() + &quot;: &quot; + event.getClass().getName());
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
* }
* }
*
@@ -851,7 +854,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* getWindow().showNotification(&quot;Click!&quot;);
*
* // Display source component and event class names
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName() + &quot;: &quot; + event.getClass().getName());
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
* }
* </pre>
*
@@ -898,7 +902,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* if (event.getSource() == ok)
* getWindow().showNotification(&quot;Click!&quot;);
*
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName() + &quot;: &quot; + event.getClass().getName());
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
* }
* }
*
diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java
index 610d638ab4..234173c2f1 100644
--- a/src/com/vaadin/ui/Table.java
+++ b/src/com/vaadin/ui/Table.java
@@ -5,6 +5,7 @@
package com.vaadin.ui;
import java.io.Serializable;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -93,6 +94,22 @@ public class Table extends AbstractSelect implements Action.Container,
MULTIROW
}
+ /**
+ * Multi select modes that controls how multi select behaves.
+ */
+ public enum MultiSelectMode {
+ /**
+ * Simple left clicks only selects one item, CTRL+left click selects
+ * multiple items and SHIFT-left click selects a range of items.
+ */
+ DEFAULT,
+ /**
+ * Uses the old method of selection. CTRL- and SHIFT-clicks are disabled and
+ * clicking on the items selects/deselects them.
+ */
+ SIMPLE
+ }
+
private static final int CELL_KEY = 0;
private static final int CELL_HEADER = 1;
@@ -225,6 +242,11 @@ public class Table extends AbstractSelect implements Action.Container,
private final HashMap<Object, String> columnHeaders = new HashMap<Object, String>();
/**
+ * Holds footers for visible columns (by propertyId).
+ */
+ private final HashMap<Object, String> columnFooters = new HashMap<Object, String>();
+
+ /**
* Holds icons for visible columns (by propertyId).
*/
private final HashMap<Object, Resource> columnIcons = new HashMap<Object, Resource>();
@@ -271,6 +293,11 @@ public class Table extends AbstractSelect implements Action.Container,
private int columnHeaderMode = COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID;
/**
+ * Should the Table footer be visible?
+ */
+ private boolean columnFootersVisible = false;
+
+ /**
* True iff the row captions are hidden.
*/
private boolean rowCaptionsAreHidden = true;
@@ -368,6 +395,12 @@ public class Table extends AbstractSelect implements Action.Container,
private DropHandler dropHandler;
+ private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+ private HeaderClickHandler headerClickHandler;
+
+ private FooterClickHandler footerClickHandler;
+
/* Table constructors */
/**
@@ -1830,7 +1863,77 @@ public class Table extends AbstractSelect implements Action.Container,
resetPageBuffer();
enableContentRefreshing(true);
+ }
+
+ /**
+ * Gets items ids from a range of key values
+ *
+ * @param startRowKey
+ * The start key
+ * @param endRowKey
+ * The end key
+ * @return
+ */
+ private Set<Object> getItemIdsInRange(int startRowKey, int endRowKey) {
+ HashSet<Object> ids = new HashSet<Object>();
+
+ Object startItemId = itemIdMapper.get(String.valueOf(startRowKey));
+ ids.add(startItemId);
+
+ Object endItemId = itemIdMapper.get(String.valueOf(endRowKey));
+ ids.add(endItemId);
+ Object currentItemId = startItemId;
+
+ Container.Ordered ordered = (Container.Ordered) items;
+ while (currentItemId != endItemId) {
+ currentItemId = ordered.nextItemId(currentItemId);
+ if (currentItemId != null) {
+ ids.add(currentItemId);
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * Handles selection if selection is a multiselection
+ *
+ * @param variables
+ * The variables
+ */
+ private void handleSelectedItems(Map<String, Object> variables) {
+ final String[] ka = (String[]) variables.get("selected");
+ final String[] ranges = (String[]) variables.get("selectedRanges");
+
+ // Converts the key-array to id-set
+ final LinkedList s = new LinkedList();
+ for (int i = 0; i < ka.length; i++) {
+ final Object id = itemIdMapper.get(ka[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ requestRepaint();
+ } else if (id != null && containsId(id)) {
+ s.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && s.size() < 1) {
+ // empty selection not allowed, keep old value
+ requestRepaint();
+ return;
+ }
+
+ // Add range items
+ for (String range : ranges) {
+ String[] limits = range.split("-");
+ int start = Integer.valueOf(limits[0]);
+ int end = Integer.valueOf(limits[1]);
+ s.addAll(getItemIdsInRange(start, end));
+ }
+
+ setValue(s, true);
}
/* Component basics */
@@ -1857,6 +1960,18 @@ public class Table extends AbstractSelect implements Action.Container,
variables.remove("selected");
}
+ /*
+ * The AbstractSelect cannot handle the multiselection properly, instead
+ * we handle it ourself
+ */
+ else if (isSelectable() && isMultiSelect()
+ && variables.containsKey("selected")
+ && multiSelectMode == MultiSelectMode.DEFAULT) {
+ handleSelectedItems(variables);
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
super.changeVariables(source, variables);
// Client might update the pagelength if Table height is fixed
@@ -2000,6 +2115,8 @@ public class Table extends AbstractSelect implements Action.Container,
* @param variables
*/
private void handleClickEvent(Map<String, Object> variables) {
+
+ // Item click event
if (variables.containsKey("clickEvent")) {
String key = (String) variables.get("clickedKey");
Object itemId = itemIdMapper.get(key);
@@ -2017,6 +2134,33 @@ public class Table extends AbstractSelect implements Action.Container,
evt));
}
}
+
+ // Header click event
+ else if (variables.containsKey("headerClickEvent")) {
+
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("headerClickEvent"));
+
+ Object cid = variables.get("headerClickCID");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+ fireEvent(new HeaderClickEvent(this, propertyId, details));
+ }
+
+ // Footer click event
+ else if (variables.containsKey("footerClickEvent")) {
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("footerClickEvent"));
+
+ Object cid = variables.get("footerClickCID");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+ fireEvent(new FooterClickEvent(this, propertyId, details));
+ }
}
/**
@@ -2065,6 +2209,10 @@ public class Table extends AbstractSelect implements Action.Container,
target.addAttribute("dragmode", dragMode.ordinal());
}
+ if (multiSelectMode != MultiSelectMode.DEFAULT) {
+ target.addAttribute("multiselectmode", multiSelectMode.ordinal());
+ }
+
// Initialize temps
final Object[] colids = getVisibleColumns();
final int cols = colids.length;
@@ -2098,15 +2246,10 @@ public class Table extends AbstractSelect implements Action.Container,
// selection support
LinkedList<String> selectedKeys = new LinkedList<String>();
- if (isMultiSelect()) {
- // only paint selections that are currently visible in the client
+ if (isMultiSelect()) {
HashSet sel = new HashSet((Set) getValue());
- Collection vids = getVisibleItemIds();
- for (Iterator it = vids.iterator(); it.hasNext();) {
- Object id = it.next();
- if (sel.contains(id)) {
- selectedKeys.add(itemIdMapper.key(id));
- }
+ for (Object id : sel) {
+ selectedKeys.add(itemIdMapper.key(id));
}
} else {
Object value = getValue();
@@ -2146,6 +2289,9 @@ public class Table extends AbstractSelect implements Action.Container,
if (rowheads) {
target.addAttribute("rowheaders", true);
}
+ if (columnFootersVisible) {
+ target.addAttribute("colfooters", true);
+ }
// Visible column order
final Collection sortables = getSortableContainerPropertyIds();
@@ -2380,6 +2526,8 @@ public class Table extends AbstractSelect implements Action.Container,
target.addAttribute("cid", columnIdMap.key(columnId));
final String head = getColumnHeader(columnId);
target.addAttribute("caption", (head != null ? head : ""));
+ final String foot = getColumnFooter(columnId);
+ target.addAttribute("fcaption", (foot != null ? foot : ""));
if (isColumnCollapsed(columnId)) {
target.addAttribute("collapsed", true);
}
@@ -2641,6 +2789,7 @@ public class Table extends AbstractSelect implements Action.Container,
columnAlignments.remove(propertyId);
columnIcons.remove(propertyId);
columnHeaders.remove(propertyId);
+ columnFooters.remove(propertyId);
return super.removeContainerProperty(propertyId);
}
@@ -3433,6 +3582,27 @@ public class Table extends AbstractSelect implements Action.Container,
}
/**
+ * Sets the behavior of how the multi-select mode should behave when the
+ * table is both selectable and in multi-select mode.
+ *
+ * @param mode
+ * The select mode of the table
+ */
+ public void setMultiSelectMode(MultiSelectMode mode) {
+ multiSelectMode = mode;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the select mode in which multi-select is used.
+ *
+ * @return The multi select mode
+ */
+ public MultiSelectMode getMultiSelectMode() {
+ return multiSelectMode;
+ }
+
+ /**
* Lazy loading accept criterion for Table. Accepted target rows are loaded
* from server once per drag and drop operation. Developer must override one
* method that decides on which rows the currently dragged data can be
@@ -3525,4 +3695,306 @@ public class Table extends AbstractSelect implements Action.Container,
}
+ /**
+ * Click event fired when clicking on the Table headers. The event includes
+ * a reference the the Table the event originated from, the property id of
+ * the column which header was pressed and details about the mouse event
+ * itself.
+ */
+ public static class HeaderClickEvent extends Component.Event {
+ public static final Method HEADER_CLICK_METHOD;
+
+ static {
+ try {
+ // Set the header click method
+ HEADER_CLICK_METHOD = HeaderClickHandler.class
+ .getDeclaredMethod("handleHeaderClick",
+ new Class[] { HeaderClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ // The property id of the column which header was pressed
+ private Object columnPropertyId;
+
+ // The mouse details
+ private MouseEventDetails details;
+
+ public HeaderClickEvent(Component source, Object propertyId,
+ MouseEventDetails details) {
+ super(source);
+ this.details = details;
+ columnPropertyId = propertyId;
+ }
+
+ /**
+ * Gets the property id of the column which header was pressed
+ *
+ * @return The column propety id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+
+ /**
+ * Returns the details of the mouse event like the mouse coordinates,
+ * button pressed etc.
+ *
+ * @return The mouse details
+ */
+ public MouseEventDetails getEventDetails() {
+ return details;
+ }
+ }
+
+ /**
+ * Click event fired when clicking on the Table footers. The event includes
+ * a reference the the Table the event originated from, the property id of
+ * the column which header was pressed and details about the mouse event
+ * itself.
+ */
+ public static class FooterClickEvent extends Component.Event {
+ public static final Method FOOTER_CLICK_METHOD;
+
+ static {
+ try {
+ // Set the header click method
+ FOOTER_CLICK_METHOD = FooterClickHandler.class
+ .getDeclaredMethod("handleFooterClick",
+ new Class[] { FooterClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ // The property id of the column which header was pressed
+ private Object columnPropertyId;
+
+ // The mouse details
+ private MouseEventDetails details;
+
+ /**
+ * Constructor
+ * @param source
+ * The source of the component
+ * @param propertyId
+ * The propertyId of the column
+ * @param details
+ * The mouse details of the click
+ */
+ public FooterClickEvent(Component source, Object propertyId,
+ MouseEventDetails details) {
+ super(source);
+ columnPropertyId = propertyId;
+ this.details = details;
+ }
+
+ /**
+ * Gets the property id of the column which header was pressed
+ *
+ * @return The column propety id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+
+ /**
+ * Returns the details of the mouse event like the mouse coordinates,
+ * button pressed etc.
+ *
+ * @return The mouse details
+ */
+ public MouseEventDetails getEventDetails() {
+ return details;
+ }
+ }
+
+ /**
+ * Interface for the handler listening to column header mouse click events.
+ * The handleHeaderClick method is called when the user presses a header
+ * column cell.
+ */
+ public interface HeaderClickHandler {
+
+ /**
+ * Called when a user clicks a header column cell
+ *
+ * @param event
+ * The event which contains information about the column and
+ * the mouse click event
+ */
+ public void handleHeaderClick(HeaderClickEvent event);
+ }
+
+ /**
+ * Interface for the handler listening to column footer mouse click events.
+ * The handleHeaderClick method is called when the user presses a footer
+ * column cell.
+ */
+ public interface FooterClickHandler {
+
+ /**
+ * Called when a user clicks a footer column cell
+ *
+ * @param event
+ * The event which contains information about the column and
+ * the mouse click event
+ */
+ public void handleFooterClick(FooterClickEvent event);
+ }
+
+ /**
+ * Sets the header click handler which handles the click events when the
+ * user clicks on a column header cell in the Table.
+ * <p>
+ * The handler will receive events which contains information about which
+ * column was clicked and some details about the mouse event.
+ * </p>
+ *
+ * @param handler
+ * The handler which should handle the header click events.
+ */
+ public void setHeaderClickHandler(HeaderClickHandler handler) {
+ if (headerClickHandler != handler) {
+ if (handler == null && headerClickHandler != null) {
+ // Remove header click handler
+ removeListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler);
+
+ headerClickHandler = handler;
+ } else if (headerClickHandler != null) {
+ // Replace header click handler
+ removeListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler);
+
+ headerClickHandler = handler;
+
+ addListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler,
+ HeaderClickEvent.HEADER_CLICK_METHOD);
+ } else if (handler != null) {
+ // Set a new header click handler
+ headerClickHandler = handler;
+ addListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler,
+ HeaderClickEvent.HEADER_CLICK_METHOD);
+ }
+ }
+ }
+
+ /**
+ * Sets the footer click handler which handles the click events when the
+ * user clicks on a column footer cell in the Table.
+ * <p>
+ * The handler will recieve events which contains information about which
+ * column was clicked and some details about the mouse event.
+ * </p>
+ *
+ * @param handler
+ * The handler which should handle the footer click events
+ */
+ public void setFooterClickHandler(FooterClickHandler handler) {
+ if (footerClickHandler != handler) {
+ if (handler == null && footerClickHandler != null) {
+ // Remove header click handler
+ removeListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler);
+ footerClickHandler = handler;
+ } else if (footerClickHandler != null) {
+ // Replace footer click handler
+ removeListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler);
+ footerClickHandler = handler;
+ addListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler,
+ FooterClickEvent.FOOTER_CLICK_METHOD);
+ } else if (handler != null) {
+ // Set a new footer click handler
+ footerClickHandler = handler;
+ addListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler,
+ FooterClickEvent.FOOTER_CLICK_METHOD);
+ }
+ }
+ }
+
+ /**
+ * Returns the header click handler which receives click events from the
+ * columns header cells when they are clicked on.
+ *
+ * @return
+ */
+ public HeaderClickHandler getHeaderClickHandler() {
+ return headerClickHandler;
+ }
+
+ /**
+ * Returns the footer click handler which recieves click events from the
+ * columns footer cells when they are clicked on.
+ *
+ * @return
+ */
+ public FooterClickHandler getFooterClickHandler() {
+ return footerClickHandler;
+ }
+
+ /**
+ * Gets the footer caption beneath the rows
+ *
+ * @param propertyId
+ * The propertyId of the column *
+ * @return The caption of the footer or NULL if not set
+ */
+ public String getColumnFooter(Object propertyId) {
+ return columnFooters.get(propertyId);
+ }
+
+ /**
+ * Sets the column footer caption. The column footer caption is the text
+ * displayed beneath the column if footers have been set visible.
+ *
+ * @param propertyId
+ * The properyId of the column
+ *
+ * @param footer
+ * The caption of the footer
+ */
+ public void setColumnFooter(Object propertyId, String footer) {
+ if (footer == null) {
+ columnFooters.remove(propertyId);
+ return;
+ }
+ columnFooters.put(propertyId, footer);
+
+ requestRepaint();
+ }
+
+ /**
+ * Sets the footer visible in the bottom of the table.
+ * <p>
+ * The footer can be used to add column related data like sums to the bottom
+ * of the Table using setColumnFooter(Object propertyId, String footer).
+ * </p>
+ *
+ * @param visible
+ * Should the footer be visible
+ */
+ public void setFooterVisible(boolean visible){
+ columnFootersVisible = visible;
+
+ // Assures the visual refresh
+ refreshRenderedCells();
+ }
+
+ /**
+ * Is the footer currently visible?
+ *
+ * @return Returns true if visible else false
+ */
+ public boolean isFooterVisible() {
+ return columnFootersVisible;
+ }
}