]> source.dussan.org Git - vaadin-framework.git/commitdiff
#2009, ItemClickEvents
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 29 Aug 2008 10:08:29 +0000 (10:08 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 29 Aug 2008 10:08:29 +0000 (10:08 +0000)
svn changeset:5298/svn branch:trunk

src/com/itmill/toolkit/event/ItemClickEvent.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java
src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java
src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java
src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java
src/com/itmill/toolkit/tests/tickets/Ticket2009.java [new file with mode: 0644]
src/com/itmill/toolkit/ui/Table.java
src/com/itmill/toolkit/ui/Tree.java

diff --git a/src/com/itmill/toolkit/event/ItemClickEvent.java b/src/com/itmill/toolkit/event/ItemClickEvent.java
new file mode 100644 (file)
index 0000000..782aa99
--- /dev/null
@@ -0,0 +1,158 @@
+package com.itmill.toolkit.event;
+
+import java.lang.reflect.Method;
+
+import com.itmill.toolkit.data.Container;
+import com.itmill.toolkit.data.Item;
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails;
+import com.itmill.toolkit.ui.Component;
+import com.itmill.toolkit.ui.Component.Event;
+
+/**
+ * 
+ * Click event fired by a {@link Component} implementing
+ * {@link com.itmill.toolkit.data.Container} interface. ItemClickEvents happens
+ * on an {@link Item} rendered somehow on terminal. Event may also contain a
+ * specific {@link Property} on which the click event happened.
+ * 
+ * ClickEvents are rather terminal dependent events. Correct values in event
+ * details cannot be guaranteed.
+ * 
+ * EXPERIMENTAL FEATURE, user input is welcome
+ * 
+ * @since 5.3
+ * 
+ * TODO extract generic super class/interfaces if we implement some other click
+ * events.
+ */
+public class ItemClickEvent extends Event {
+    public static final int BUTTON_LEFT = MouseEventDetails.BUTTON_LEFT;
+    public static final int BUTTON_MIDDLE = MouseEventDetails.BUTTON_MIDDLE;
+    public static final int BUTTON_RIGHT = MouseEventDetails.BUTTON_RIGHT;
+
+    private MouseEventDetails details;
+    private Item item;
+    private Object itemId;
+    private Object propertyId;
+
+    public ItemClickEvent(Component source, Item item, Object itemId,
+            Object propertyId, MouseEventDetails details) {
+        super(source);
+        this.details = details;
+        this.item = item;
+        this.itemId = itemId;
+        this.propertyId = propertyId;
+    }
+
+    /**
+     * Gets the item on which the click event occurred.
+     * 
+     * @return item which was clicked
+     */
+    public Item getItem() {
+        return item;
+    }
+
+    /**
+     * Gets a possible identifier in source for clicked Item
+     * 
+     * @return
+     */
+    public Object getItemId() {
+        return itemId;
+    }
+
+    /**
+     * Returns property on which click event occurred. Returns null if source
+     * cannot be resolved at property leve. For example if clicked a cell in
+     * table, the "column id" is returned.
+     * 
+     * @return a property id of clicked property or null if click didn't occur
+     *         on any distinct property.
+     */
+    public Object getPropertyId() {
+        return propertyId;
+    }
+
+    public int getButton() {
+        return details.getButton();
+    }
+
+    public int getClientX() {
+        return details.getClientX();
+    }
+
+    public int getClientY() {
+        return details.getClientY();
+    }
+
+    public boolean isDoubleClick() {
+        return details.isDoubleClick();
+    }
+
+    public boolean isAltKey() {
+        return details.isAltKey();
+    }
+
+    public boolean isCtrlKey() {
+        return details.isCtrlKey();
+    }
+
+    public boolean isMetaKey() {
+        return details.isMetaKey();
+    }
+
+    public boolean isShiftKey() {
+        return details.isShiftKey();
+    }
+
+    /**
+     * Serial generated by eclipse
+     */
+    private static final long serialVersionUID = 3576399524236787971L;
+
+    public static final Method ITEM_CLICK_METHOD;
+
+    static {
+        try {
+            ITEM_CLICK_METHOD = ItemClickListener.class.getDeclaredMethod(
+                    "itemClick", new Class[] { ItemClickEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException();
+        }
+    }
+
+    public interface ItemClickListener {
+        public void itemClick(ItemClickEvent event);
+    }
+
+    /**
+     * Components implementing
+     * 
+     * @link {@link Container} interface may support emitting
+     *       {@link ItemClickEvent}s.
+     */
+    public interface ItemClickSource {
+        /**
+         * Register listener to handle ItemClickEvents.
+         * 
+         * Note! Click listeners are rather terminal dependent features.
+         * 
+         * This feature is EXPERIMENTAL
+         * 
+         * @param listener
+         *                ItemClickListener to be registered
+         */
+        public void addListener(ItemClickListener listener);
+
+        /**
+         * Removes ItemClickListener.
+         * 
+         * @param listener
+         */
+        public void removeListener(ItemClickListener listener);
+    }
+
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java b/src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java
new file mode 100644 (file)
index 0000000..8d91863
--- /dev/null
@@ -0,0 +1,92 @@
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+
+/**
+ * Helper class to store and transfer mouse event details.
+ */
+public class MouseEventDetails {
+    public static final int BUTTON_LEFT = Event.BUTTON_LEFT;
+    public static final int BUTTON_MIDDLE = Event.BUTTON_MIDDLE;
+    public static final int BUTTON_RIGHT = Event.BUTTON_RIGHT;
+
+    private static final char DELIM = ',';
+
+    private int button;
+    private int clientX;
+    private int clientY;
+    private boolean altKey;
+    private boolean ctrlKey;
+    private boolean metaKey;
+    private boolean shiftKey;
+    private int type;
+
+    public int getButton() {
+        return button;
+    }
+
+    public int getClientX() {
+        return clientX;
+    }
+
+    public int getClientY() {
+        return clientY;
+    }
+
+    public boolean isAltKey() {
+        return altKey;
+    }
+
+    public boolean isCtrlKey() {
+        return ctrlKey;
+    }
+
+    public boolean isMetaKey() {
+        return metaKey;
+    }
+
+    public boolean isShiftKey() {
+        return shiftKey;
+    }
+
+    public MouseEventDetails(Event evt) {
+        button = DOM.eventGetButton(evt);
+        clientX = DOM.eventGetClientX(evt);
+        clientY = DOM.eventGetClientY(evt);
+        altKey = DOM.eventGetAltKey(evt);
+        ctrlKey = DOM.eventGetCtrlKey(evt);
+        metaKey = DOM.eventGetMetaKey(evt);
+        shiftKey = DOM.eventGetShiftKey(evt);
+        type = DOM.eventGetType(evt);
+    }
+
+    private MouseEventDetails() {
+    }
+
+    public String toString() {
+        return "" + button + DELIM + clientX + DELIM + clientY + DELIM + altKey
+                + DELIM + ctrlKey + DELIM + metaKey + DELIM + shiftKey + DELIM
+                + type;
+    }
+
+    public static MouseEventDetails deSerialize(String serializedString) {
+        MouseEventDetails instance = new MouseEventDetails();
+        String[] fields = serializedString.split(",");
+
+        instance.button = Integer.parseInt(fields[0]);
+        instance.clientX = Integer.parseInt(fields[1]);
+        instance.clientY = Integer.parseInt(fields[2]);
+        instance.altKey = Boolean.valueOf(fields[3]).booleanValue();
+        instance.ctrlKey = Boolean.valueOf(fields[4]).booleanValue();
+        instance.metaKey = Boolean.valueOf(fields[5]).booleanValue();
+        instance.shiftKey = Boolean.valueOf(fields[6]).booleanValue();
+        instance.type = Integer.parseInt(fields[7]);
+        return instance;
+    }
+
+    public boolean isDoubleClick() {
+        return type == Event.ONDBLCLICK;
+    }
+
+}
index 71258c192e5ceea4aa7927cf17afbe3efb4f511f..b1e9f5d9192c82979221c95b7b0a065b7ebef748 100644 (file)
@@ -117,8 +117,8 @@ public class IOrderedLayout extends Panel implements Container,
      * <p>
      * There are two modes - vertical and horizontal.
      * <ul>
-     * <li>Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap
-     * child ))).</li>
+     * <li>Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap (
+     * child ))).</li>
      * <li>Horizontal mode uses structure: table ( tbody ( tr-childcontainer (
      * td-wrap ( child ) td-wrap ( child) )) )</li>
      * </ul>
@@ -574,7 +574,7 @@ public class IOrderedLayout extends Panel implements Container,
      * without letting root element to affect the calculation.
      * 
      * @param offset
-     *            offsetWidth or offsetHeight
+     *                offsetWidth or offsetHeight
      */
     private int rootOffsetMeasure(String offset) {
         // TODO This method must be optimized!
index 37fb6feca8d793358f986179e6f4d660a5233b6d..349361772283c01956d0831a14297f49bcab93cc 100644 (file)
@@ -302,8 +302,6 @@ public class IPanel extends SimplePanel implements Paintable,
 
             // Restore content to flow
             if (hasChildren) {
-                ApplicationConnection.getConsole().log(
-                        "positioning:" + origPositioning);
                 DOM.setStyleAttribute(contentEl, "position", origPositioning);
             }
             // restore scroll position
index 25209a143199527fe1e0fd54be6cd7a418c8f50e..6dab67ccd64dcf975acb1f32fdeb80caf621488f 100644 (file)
@@ -27,6 +27,7 @@ import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
 import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails;
 import com.itmill.toolkit.terminal.gwt.client.Paintable;
 import com.itmill.toolkit.terminal.gwt.client.UIDL;
 import com.itmill.toolkit.terminal.gwt.client.Util;
@@ -128,6 +129,7 @@ public class IScrollTable extends Composite implements Table, ScrollListener,
      * for container element. Then this value is used as a fallback.
      */
     private int oldAvailPixels;
+    private boolean emitClickEvents;
 
     public IScrollTable() {
 
@@ -154,6 +156,7 @@ public class IScrollTable extends Composite implements Table, ScrollListener,
         this.client = client;
         paintableId = uidl.getStringAttribute("id");
         immediate = uidl.getBooleanAttribute("immediate");
+        emitClickEvents = uidl.getBooleanAttribute("listenClicks");
         final int newTotalRows = uidl.getIntAttribute("totalrows");
         if (newTotalRows != totalRows) {
             if (tBody != null) {
@@ -1942,7 +1945,7 @@ public class IScrollTable extends Composite implements Table, ScrollListener,
             private IScrollTableRow(int rowKey) {
                 this.rowKey = rowKey;
                 setElement(DOM.createElement("tr"));
-                DOM.sinkEvents(getElement(), Event.ONCLICK);
+                DOM.sinkEvents(getElement(), Event.ONCLICK | Event.ONDBLCLICK);
                 attachContextMenuEvent(getElement());
             }
 
@@ -2084,26 +2087,55 @@ public class IScrollTable extends Composite implements Table, ScrollListener,
                 return false;
             }
 
+            private void handleClickEvent(Event event) {
+                if (emitClickEvents) {
+                    boolean dbl = DOM.eventGetType(event) == Event.ONDBLCLICK;
+                    final Element tdOrTr = DOM.getParent(DOM
+                            .eventGetTarget(event));
+                    client.updateVariable(paintableId, "clickedKey", ""
+                            + rowKey, false);
+                    if (DOM.compare(getElement(), DOM.getParent(tdOrTr))) {
+                        int childIndex = DOM
+                                .getChildIndex(getElement(), tdOrTr);
+                        String colKey = null;
+                        colKey = tHead.getHeaderCell(childIndex).getColKey();
+                        client.updateVariable(paintableId, "clickedColKey",
+                                colKey, false);
+                    }
+                    MouseEventDetails details = new MouseEventDetails(event);
+                    client
+                            .updateVariable(
+                                    paintableId,
+                                    "clickEvent",
+                                    details.toString(),
+                                    !(!dbl
+                                            && selectMode > Table.SELECT_MODE_NONE && immediate));
+                }
+            }
+
             /*
              * React on click that occur on content cells only
              */
             public void onBrowserEvent(Event event) {
-                switch (DOM.eventGetType(event)) {
-                case Event.ONCLICK:
-                    final Element tdOrTr = DOM.getParent(DOM
-                            .eventGetTarget(event));
-                    if (DOM.compare(getElement(), tdOrTr)
-                            || DOM.compare(getElement(), DOM.getParent(tdOrTr))) {
+                final Element tdOrTr = DOM.getParent(DOM.eventGetTarget(event));
+                if (DOM.compare(getElement(), tdOrTr)
+                        || DOM.compare(getElement(), DOM.getParent(tdOrTr))) {
+                    switch (DOM.eventGetType(event)) {
+                    case Event.ONCLICK:
+                        handleClickEvent(event);
                         if (selectMode > Table.SELECT_MODE_NONE) {
                             toggleSelection();
                             client.updateVariable(paintableId, "selected",
                                     selectedRowKeys.toArray(), immediate);
                         }
-                    }
-                    break;
+                        break;
+                    case Event.ONDBLCLICK:
+                        handleClickEvent(event);
+                        break;
 
-                default:
-                    break;
+                    default:
+                        break;
+                    }
                 }
                 super.onBrowserEvent(event);
             }
index 4615d3990fe08c3e34a979bae4f714f733dab291..08f39307994f4b196577f69493008d586b55fcc5 100644 (file)
@@ -16,6 +16,7 @@ import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.SimplePanel;
 import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails;
 import com.itmill.toolkit.terminal.gwt.client.Paintable;
 import com.itmill.toolkit.terminal.gwt.client.UIDL;
 import com.itmill.toolkit.terminal.gwt.client.Util;
@@ -49,6 +50,8 @@ public class ITree extends FlowPanel implements Paintable {
 
     private boolean readonly;
 
+    private boolean emitClickEvents;
+
     public ITree() {
         super();
         setStyleName(CLASSNAME);
@@ -97,6 +100,7 @@ public class ITree extends FlowPanel implements Paintable {
 
         disabled = uidl.getBooleanAttribute("disabled");
         readonly = uidl.getBooleanAttribute("readonly");
+        emitClickEvents = uidl.getBooleanAttribute("listenClicks");
 
         isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect");
 
@@ -189,7 +193,7 @@ public class ITree extends FlowPanel implements Paintable {
 
         public TreeNode() {
             constructDom();
-            sinkEvents(Event.ONCLICK);
+            sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.ONMOUSEUP);
         }
 
         public void onBrowserEvent(Event event) {
@@ -197,14 +201,19 @@ public class ITree extends FlowPanel implements Paintable {
             if (disabled) {
                 return;
             }
-            if (DOM.eventGetType(event) == Event.ONCLICK) {
-                final Element target = DOM.eventGetTarget(event);
+            final int type = DOM.eventGetType(event);
+            final Element target = DOM.eventGetTarget(event);
+            if (emitClickEvents && DOM.compare(target, nodeCaptionSpan)
+                    && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) {
+                fireClick(event);
+            }
+            if (type == Event.ONCLICK) {
                 if (DOM.compare(getElement(), target)
                         || DOM.compare(ie6compatnode, target)) {
                     // state change
                     toggleState();
                 } else if (!readonly && DOM.compare(target, nodeCaptionSpan)) {
-                    // caption click = selection change
+                    // caption click = selection change && possible click event
                     toggleSelection();
                 }
                 DOM.eventCancelBubble(event, true);
@@ -215,6 +224,13 @@ public class ITree extends FlowPanel implements Paintable {
 
         }
 
+        private void fireClick(Event evt) {
+            MouseEventDetails details = new MouseEventDetails(evt);
+            client.updateVariable(paintableId, "clickedKey", key, false);
+            client.updateVariable(paintableId, "clickEvent",
+                    details.toString(), !(selectable && immediate));
+        }
+
         private void toggleSelection() {
             if (selectable) {
                 ITree.this.setSelected(this, !isSelected());
diff --git a/src/com/itmill/toolkit/tests/tickets/Ticket2009.java b/src/com/itmill/toolkit/tests/tickets/Ticket2009.java
new file mode 100644 (file)
index 0000000..f9eebb0
--- /dev/null
@@ -0,0 +1,125 @@
+package com.itmill.toolkit.tests.tickets;\r
+\r
+import com.itmill.toolkit.data.Container;\r
+import com.itmill.toolkit.event.ItemClickEvent;\r
+import com.itmill.toolkit.tests.TestForTablesInitialColumnWidthLogicRendering;\r
+import com.itmill.toolkit.ui.Button;\r
+import com.itmill.toolkit.ui.Label;\r
+import com.itmill.toolkit.ui.OrderedLayout;\r
+import com.itmill.toolkit.ui.Panel;\r
+import com.itmill.toolkit.ui.Table;\r
+import com.itmill.toolkit.ui.TextField;\r
+import com.itmill.toolkit.ui.Tree;\r
+import com.itmill.toolkit.ui.Window;\r
+import com.itmill.toolkit.ui.Button.ClickEvent;\r
+\r
+public class Ticket2009 extends com.itmill.toolkit.Application {\r
+\r
+    TextField f = new TextField();\r
+\r
+    public void init() {\r
+        final Window main = new Window(getClass().getName().substring(\r
+                getClass().getName().lastIndexOf(".") + 1));\r
+        setMainWindow(main);\r
+\r
+        OrderedLayout ol = new OrderedLayout(\r
+                OrderedLayout.ORIENTATION_HORIZONTAL);\r
+        main.setLayout(ol);\r
+        ol.setSizeFull();\r
+\r
+        Panel p = new Panel("Tree test");\r
+        p.setSizeFull();\r
+\r
+        Tree t = new Tree();\r
+\r
+        t.addItem("Foo");\r
+        t.addItem("Bar");\r
+\r
+        final OrderedLayout events = new OrderedLayout();\r
+\r
+        t.addListener(new ItemClickEvent.ItemClickListener() {\r
+            public void itemClick(ItemClickEvent event) {\r
+                events.addComponent(new Label(new Label("Click:"\r
+                        + (event.isDoubleClick() ? "double" : "single")\r
+                        + " button:" + event.getButton() + " propertyId:"\r
+                        + event.getPropertyId() + " itemID:"\r
+                        + event.getItemId() + " item:" + event.getItem())));\r
+\r
+            }\r
+        });\r
+\r
+        main.addComponent(p);\r
+        p.addComponent(t);\r
+        p.addComponent(events);\r
+\r
+        Panel p2 = new Panel("Table test (try dbl click also)");\r
+        p2.setSizeFull();\r
+\r
+        final OrderedLayout events2 = new OrderedLayout();\r
+        Table table = TestForTablesInitialColumnWidthLogicRendering\r
+                .getTestTable(5, 100);\r
+        table.setRowHeaderMode(Table.ROW_HEADER_MODE_ID);\r
+        table.addListener(new ItemClickEvent.ItemClickListener() {\r
+            public void itemClick(ItemClickEvent event) {\r
+                events2.addComponent(new Label("Click:"\r
+                        + (event.isDoubleClick() ? "double" : "single")\r
+                        + " button:" + event.getButton() + " propertyId:"\r
+                        + event.getPropertyId() + " itemID:"\r
+                        + event.getItemId() + " item:" + event.getItem()));\r
+                if (event.isDoubleClick()) {\r
+                    new PropertyEditor(event);\r
+                }\r
+\r
+            }\r
+        });\r
+        p2.addComponent(table);\r
+        p2.addComponent(events2);\r
+\r
+        main.addComponent(p2);\r
+\r
+    }\r
+\r
+    class PropertyEditor extends Window {\r
+\r
+        private static final int W = 300;\r
+        private static final int H = 150;\r
+\r
+        private Container c;\r
+        private Object itemid;\r
+        private Object propertyid;\r
+\r
+        TextField editor = new TextField();\r
+        Button done = new Button("Done");\r
+\r
+        PropertyEditor(ItemClickEvent event) {\r
+            c = (Container) event.getSource();\r
+\r
+            propertyid = event.getPropertyId();\r
+            itemid = event.getItemId();\r
+\r
+            setCaption("Editing " + itemid + " : " + propertyid);\r
+\r
+            editor.setPropertyDataSource(c.getContainerProperty(itemid,\r
+                    propertyid));\r
+            addComponent(editor);\r
+            addComponent(done);\r
+\r
+            setWidth(W);\r
+            setHeight(H);\r
+\r
+            setPositionX(event.getClientX() - W / 2);\r
+            setPositionY(event.getClientY() - H / 2);\r
+\r
+            getMainWindow().addWindow(this);\r
+\r
+            done.addListener(new Button.ClickListener() {\r
+                public void buttonClick(ClickEvent event) {\r
+                    getMainWindow().removeWindow(PropertyEditor.this);\r
+                }\r
+            });\r
+\r
+        }\r
+\r
+    }\r
+\r
+}
\ No newline at end of file
index a8b660554f31cfe813158bf7a7b1c78acd8b1187..1a5fb10a67b47c7b8baefc326f1589881f4f4cbd 100644 (file)
@@ -21,10 +21,14 @@ import com.itmill.toolkit.data.Property;
 import com.itmill.toolkit.data.util.ContainerOrderedWrapper;
 import com.itmill.toolkit.data.util.IndexedContainer;
 import com.itmill.toolkit.event.Action;
+import com.itmill.toolkit.event.ItemClickEvent;
+import com.itmill.toolkit.event.ItemClickEvent.ItemClickListener;
+import com.itmill.toolkit.event.ItemClickEvent.ItemClickSource;
 import com.itmill.toolkit.terminal.KeyMapper;
 import com.itmill.toolkit.terminal.PaintException;
 import com.itmill.toolkit.terminal.PaintTarget;
 import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails;
 
 /**
  * <p>
@@ -44,7 +48,7 @@ import com.itmill.toolkit.terminal.Resource;
  * @since 3.0
  */
 public class Table extends AbstractSelect implements Action.Container,
-        Container.Ordered, Container.Sortable {
+        Container.Ordered, Container.Sortable, ItemClickSource {
 
     private static final int CELL_KEY = 0;
 
@@ -306,6 +310,8 @@ public class Table extends AbstractSelect implements Action.Container,
      */
     private CellStyleGenerator cellStyleGenerator = null;
 
+    private int clickListenerCount;
+
     /* Table constructors */
 
     /**
@@ -364,7 +370,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param visibleColumns
-     *            the Array of shown property id:s.
+     *                the Array of shown property id:s.
      */
     public void setVisibleColumns(Object[] visibleColumns) {
 
@@ -461,8 +467,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param columnHeaders
-     *            the Array of column headers that match the
-     *            <code>getVisibleColumns</code> method.
+     *                the Array of column headers that match the
+     *                <code>getVisibleColumns</code> method.
      */
     public void setColumnHeaders(String[] columnHeaders) {
 
@@ -521,8 +527,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param columnIcons
-     *            the Array of icons that match the
-     *            <code>getVisibleColumns</code>.
+     *                the Array of icons that match the
+     *                <code>getVisibleColumns</code>.
      */
     public void setColumnIcons(Resource[] columnIcons) {
 
@@ -548,8 +554,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * 
      * <p>
      * The items in the array must match the properties identified by
-     * <code>getVisibleColumns()</code>. The possible values for the alignments
-     * include:
+     * <code>getVisibleColumns()</code>. The possible values for the
+     * alignments include:
      * <ul>
      * <li><code>ALIGN_LEFT</code>: Left alignment</li>
      * <li><code>ALIGN_CENTER</code>: Centered</li>
@@ -579,8 +585,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * 
      * <p>
      * The items in the array must match the properties identified by
-     * <code>getVisibleColumns()</code>. The possible values for the alignments
-     * include:
+     * <code>getVisibleColumns()</code>. The possible values for the
+     * alignments include:
      * <ul>
      * <li><code>ALIGN_LEFT</code>: Left alignment</li>
      * <li><code>ALIGN_CENTER</code>: Centered</li>
@@ -590,7 +596,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param columnAlignments
-     *            the Column alignments array.
+     *                the Column alignments array.
      */
     public void setColumnAlignments(String[] columnAlignments) {
 
@@ -629,9 +635,9 @@ public class Table extends AbstractSelect implements Action.Container,
      * will make decision of width.
      * 
      * @param columnId
-     *            colunmns property id
+     *                colunmns property id
      * @param width
-     *            width to be reserved for colunmns content
+     *                width to be reserved for colunmns content
      * @since 4.0.3
      */
     public void setColumnWidth(Object columnId, int width) {
@@ -673,7 +679,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param pageLength
-     *            the Length of one page.
+     *                the Length of one page.
      */
     public void setPageLength(int pageLength) {
         if (pageLength >= 0 && this.pageLength != pageLength) {
@@ -715,7 +721,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Setter for property currentPageFirstItemId.
      * 
      * @param currentPageFirstItemId
-     *            the New value of property currentPageFirstItemId.
+     *                the New value of property currentPageFirstItemId.
      */
     public void setCurrentPageFirstItemId(Object currentPageFirstItemId) {
 
@@ -753,7 +759,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Gets the icon Resource for the specified column.
      * 
      * @param propertyId
-     *            the propertyId indentifying the column.
+     *                the propertyId indentifying the column.
      * @return the icon for the specified column; null if the column has no icon
      *         set, or if the column is not visible.
      */
@@ -768,9 +774,9 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param propertyId
-     *            the propertyId identifying the column.
+     *                the propertyId identifying the column.
      * @param icon
-     *            the icon Resource to set.
+     *                the icon Resource to set.
      */
     public void setColumnIcon(Object propertyId, Resource icon) {
 
@@ -789,7 +795,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Gets the header for the specified column.
      * 
      * @param propertyId
-     *            the propertyId indentifying the column.
+     *                the propertyId indentifying the column.
      * @return the header for the specifed column if it has one.
      */
     public String getColumnHeader(Object propertyId) {
@@ -810,9 +816,9 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sets the column header for the specified column;
      * 
      * @param propertyId
-     *            the propertyId indentifying the column.
+     *                the propertyId indentifying the column.
      * @param header
-     *            the header to set.
+     *                the header to set.
      */
     public void setColumnHeader(Object propertyId, String header) {
 
@@ -830,7 +836,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Gets the specified column's alignment.
      * 
      * @param propertyId
-     *            the propertyID identifying the column.
+     *                the propertyID identifying the column.
      * @return the specified column's alignment if it as one; null otherwise.
      */
     public String getColumnAlignment(Object propertyId) {
@@ -847,9 +853,9 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param propertyId
-     *            the propertyID identifying the column.
+     *                the propertyID identifying the column.
      * @param alignment
-     *            the desired alignment.
+     *                the desired alignment.
      */
     public void setColumnAlignment(Object propertyId, String alignment) {
 
@@ -876,7 +882,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Checks if the specified column is collapsed.
      * 
      * @param propertyId
-     *            the propertyID identifying the column.
+     *                the propertyID identifying the column.
      * @return true if the column is collapsed; false otherwise;
      */
     public boolean isColumnCollapsed(Object propertyId) {
@@ -889,9 +895,9 @@ public class Table extends AbstractSelect implements Action.Container,
      * 
      * 
      * @param propertyId
-     *            the propertyID identifying the column.
+     *                the propertyID identifying the column.
      * @param collapsed
-     *            the desired collapsedness.
+     *                the desired collapsedness.
      * @throws IllegalAccessException
      */
     public void setColumnCollapsed(Object propertyId, boolean collapsed)
@@ -924,7 +930,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sets whether column collapsing is allowed or not.
      * 
      * @param collapsingAllowed
-     *            specifies whether column collapsing is allowed.
+     *                specifies whether column collapsing is allowed.
      */
     public void setColumnCollapsingAllowed(boolean collapsingAllowed) {
         columnCollapsingAllowed = collapsingAllowed;
@@ -950,7 +956,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sets whether column reordering is allowed or not.
      * 
      * @param reorderingAllowed
-     *            specifies whether column reordering is allowed.
+     *                specifies whether column reordering is allowed.
      */
     public void setColumnReorderingAllowed(boolean reorderingAllowed) {
         columnReorderingAllowed = reorderingAllowed;
@@ -1080,7 +1086,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Setter for property currentPageFirstItem.
      * 
      * @param newIndex
-     *            the New value of property currentPageFirstItem.
+     *                the New value of property currentPageFirstItem.
      */
     public void setCurrentPageFirstItemIndex(int newIndex) {
         setCurrentPageFirstItemIndex(newIndex, true);
@@ -1103,7 +1109,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * @deprecated functionality is not needed in ajax rendering model
      * 
      * @param pageBuffering
-     *            the New value of property pageBuffering.
+     *                the New value of property pageBuffering.
      */
     public void setPageBufferingEnabled(boolean pageBuffering) {
 
@@ -1130,7 +1136,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param selectable
-     *            the New value of property selectable.
+     *                the New value of property selectable.
      */
     public void setSelectable(boolean selectable) {
         if (this.selectable != selectable) {
@@ -1152,7 +1158,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Setter for property columnHeaderMode.
      * 
      * @param columnHeaderMode
-     *            the New value of property columnHeaderMode.
+     *                the New value of property columnHeaderMode.
      */
     public void setColumnHeaderMode(int columnHeaderMode) {
         if (columnHeaderMode >= COLUMN_HEADER_MODE_HIDDEN
@@ -1392,20 +1398,20 @@ public class Table extends AbstractSelect implements Action.Container,
      * <code>toString()</code> is used as row caption.
      * <li><code>ROW_HEADER_MODE_PROPERTY</code>: Property set with
      * <code>setItemCaptionPropertyId()</code> is used as row header.
-     * <li><code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code>: Items Id-objects
-     * <code>toString()</code> is used as row header. If caption is explicitly
-     * specified, it overrides the id-caption.
+     * <li><code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code>: Items
+     * Id-objects <code>toString()</code> is used as row header. If caption is
+     * explicitly specified, it overrides the id-caption.
      * <li><code>ROW_HEADER_MODE_EXPLICIT</code>: The row headers must be
      * explicitly specified.</li>
-     * <li><code>ROW_HEADER_MODE_INDEX</code>: The index of the item is used as
-     * row caption. The index mode can only be used with the containers
+     * <li><code>ROW_HEADER_MODE_INDEX</code>: The index of the item is used
+     * as row caption. The index mode can only be used with the containers
      * implementing <code>Container.Indexed</code> interface.</li>
      * </ul>
      * The default value is <code>ROW_HEADER_MODE_HIDDEN</code>
      * </p>
      * 
      * @param mode
-     *            the One of the modes listed above.
+     *                the One of the modes listed above.
      */
     public void setRowHeaderMode(int mode) {
         if (ROW_HEADER_MODE_HIDDEN == mode) {
@@ -1435,13 +1441,13 @@ public class Table extends AbstractSelect implements Action.Container,
      * columns) with given values.
      * 
      * @param cells
-     *            the Object array that is used for filling the visible cells
-     *            new row. The types must be settable to visible column property
-     *            types.
+     *                the Object array that is used for filling the visible
+     *                cells new row. The types must be settable to visible
+     *                column property types.
      * @param itemId
-     *            the Id the new row. If null, a new id is automatically
-     *            assigned. If given, the table cant already have a item with
-     *            given id.
+     *                the Id the new row. If null, a new id is automatically
+     *                assigned. If given, the table cant already have a item
+     *                with given id.
      * @return Returns item id for the new row. Returns null if operation fails.
      */
     public Object addItem(Object[] cells, Object itemId)
@@ -1704,7 +1710,38 @@ public class Table extends AbstractSelect implements Action.Container,
             }
         }
 
-        enableContentRefreshing(clientNeedsContentRefresh);
+        // handle clicks before content refresh if content is refreshed anyway
+        if (clientNeedsContentRefresh) {
+            handleClickEvent(variables);
+            enableContentRefreshing(clientNeedsContentRefresh);
+        } else {
+            enableContentRefreshing(clientNeedsContentRefresh);
+            handleClickEvent(variables);
+        }
+    }
+
+    /**
+     * Handles click event
+     * 
+     * @param variables
+     */
+    private void handleClickEvent(Map variables) {
+        if (clickListenerCount > 0) {
+            if (variables.containsKey("clickEvent")) {
+                String key = (String) variables.get("clickedKey");
+                Object itemId = itemIdMapper.get(key);
+                Object propertyId = null;
+                String colkey = (String) variables.get("clickedColKey");
+                // click is not necessary on a property
+                if (colkey != null) {
+                    propertyId = columnIdMap.get(colkey);
+                }
+                MouseEventDetails evt = MouseEventDetails
+                        .deSerialize((String) variables.get("clickEvent"));
+                fireEvent(new ItemClickEvent(this, getItem(itemId), itemId,
+                        propertyId, evt));
+            }
+        }
     }
 
     /**
@@ -1724,7 +1761,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Go to mode where content content refreshing has effect.
      * 
      * @param refreshContent
-     *            true if content refresh needs to be done
+     *                true if content refresh needs to be done
      */
     protected void enableContentRefreshing(boolean refreshContent) {
         isContentRefreshesEnabled = true;
@@ -1736,9 +1773,8 @@ public class Table extends AbstractSelect implements Action.Container,
     /*
      * (non-Javadoc)
      * 
-     * @see
-     * com.itmill.toolkit.ui.AbstractSelect#paintContent(com.itmill.toolkit.
-     * terminal.PaintTarget)
+     * @see com.itmill.toolkit.ui.AbstractSelect#paintContent(com.itmill.toolkit.
+     *      terminal.PaintTarget)
      */
     public void paintContent(PaintTarget target) throws PaintException {
 
@@ -1800,6 +1836,11 @@ public class Table extends AbstractSelect implements Action.Container,
         } else {
             target.addAttribute("selectmode", "none");
         }
+
+        if (clickListenerCount > 0) {
+            target.addAttribute("listenClicks", true);
+        }
+
         target.addAttribute("cols", cols);
         target.addAttribute("rows", rows);
 
@@ -2106,11 +2147,11 @@ public class Table extends AbstractSelect implements Action.Container,
      * the value representation.
      * 
      * @param rowId
-     *            the Id of the row (same as item Id).
+     *                the Id of the row (same as item Id).
      * @param colId
-     *            the Id of the column.
+     *                the Id of the column.
      * @param property
-     *            the Property to be presented.
+     *                the Property to be presented.
      * @return Object Either formatted value or Component for field.
      * @see #setFieldFactory(FieldFactory)
      */
@@ -2133,11 +2174,11 @@ public class Table extends AbstractSelect implements Action.Container,
      * and return a empty string for null properties.
      * 
      * @param rowId
-     *            the Id of the row (same as item Id).
+     *                the Id of the row (same as item Id).
      * @param colId
-     *            the Id of the column.
+     *                the Id of the column.
      * @param property
-     *            the Property to be formatted.
+     *                the Property to be formatted.
      * @return the String representation of property and its value.
      * @since 3.1
      */
@@ -2305,11 +2346,11 @@ public class Table extends AbstractSelect implements Action.Container,
      * Adds a new property to the table and show it as a visible column.
      * 
      * @param propertyId
-     *            the Id of the proprty.
+     *                the Id of the proprty.
      * @param type
-     *            the class of the property.
+     *                the class of the property.
      * @param defaultValue
-     *            the default value given for all existing items.
+     *                the default value given for all existing items.
      * @see com.itmill.toolkit.data.Container#addContainerProperty(Object,
      *      Class, Object)
      */
@@ -2339,21 +2380,21 @@ public class Table extends AbstractSelect implements Action.Container,
      * Adds a new property to the table and show it as a visible column.
      * 
      * @param propertyId
-     *            the Id of the proprty
+     *                the Id of the proprty
      * @param type
-     *            the class of the property
+     *                the class of the property
      * @param defaultValue
-     *            the default value given for all existing items
+     *                the default value given for all existing items
      * @param columnHeader
-     *            the Explicit header of the column. If explicit header is not
-     *            needed, this should be set null.
+     *                the Explicit header of the column. If explicit header is
+     *                not needed, this should be set null.
      * @param columnIcon
-     *            the Icon of the column. If icon is not needed, this should be
-     *            set null.
+     *                the Icon of the column. If icon is not needed, this should
+     *                be set null.
      * @param columnAlignment
-     *            the Alignment of the column. Null implies align left.
+     *                the Alignment of the column. Null implies align left.
      * @throws UnsupportedOperationException
-     *             if the operation is not supported.
+     *                 if the operation is not supported.
      * @see com.itmill.toolkit.data.Container#addContainerProperty(Object,
      *      Class, Object)
      */
@@ -2388,9 +2429,9 @@ public class Table extends AbstractSelect implements Action.Container,
      * </p>
      * 
      * @param id
-     *            the id of the column to be added
+     *                the id of the column to be added
      * @param generatedColumn
-     *            the {@link ColumnGenerator} to use for this column
+     *                the {@link ColumnGenerator} to use for this column
      */
     public void addGeneratedColumn(Object id, ColumnGenerator generatedColumn) {
         if (generatedColumn == null) {
@@ -2412,7 +2453,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Removes a generated column previously added with addGeneratedColumn.
      * 
      * @param id
-     *            id of the generated column to remove
+     *                id of the generated column to remove
      * @return true if the column could be removed (existed in the Table)
      */
     public boolean removeGeneratedColumn(Object id) {
@@ -2487,7 +2528,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Adding new items is not supported.
      * 
      * @throws UnsupportedOperationException
-     *             if set to true.
+     *                 if set to true.
      * @see com.itmill.toolkit.ui.Select#setNewItemsAllowed(boolean)
      */
     public void setNewItemsAllowed(boolean allowNewOptions)
@@ -2501,7 +2542,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Focusing to this component is not supported.
      * 
      * @throws UnsupportedOperationException
-     *             if invoked.
+     *                 if invoked.
      * @see com.itmill.toolkit.ui.AbstractField#focus()
      */
     public void focus() throws UnsupportedOperationException {
@@ -2617,7 +2658,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * BaseFieldFactory is used.
      * 
      * @param fieldFactory
-     *            the field factory to set.
+     *                the field factory to set.
      * @see #isEditable
      * @see BaseFieldFactory
      * 
@@ -2660,7 +2701,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * property to true.
      * 
      * @param editable
-     *            true if table should be editable by user.
+     *                true if table should be editable by user.
      * @see Field
      * @see FieldFactory
      * 
@@ -2677,8 +2718,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sorts the table.
      * 
      * @throws UnsupportedOperationException
-     *             if the container data source does not implement
-     *             Container.Sortable
+     *                 if the container data source does not implement
+     *                 Container.Sortable
      * @see com.itmill.toolkit.data.Container.Sortable#sort(java.lang.Object[],
      *      boolean[])
      * 
@@ -2703,8 +2744,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sorts the table by currently selected sorting column.
      * 
      * @throws UnsupportedOperationException
-     *             if the container data source does not implement
-     *             Container.Sortable
+     *                 if the container data source does not implement
+     *                 Container.Sortable
      */
     public void sort() {
         if (getSortContainerPropertyId() == null) {
@@ -2741,7 +2782,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sets the currently sorted column property id.
      * 
      * @param propertyId
-     *            the Container property id of the currently sorted column.
+     *                the Container property id of the currently sorted column.
      */
     public void setSortContainerPropertyId(Object propertyId) {
         setSortContainerPropertyId(propertyId, true);
@@ -2771,7 +2812,8 @@ public class Table extends AbstractSelect implements Action.Container,
     /**
      * Is the table currently sorted in ascending order.
      * 
-     * @return <code>true</code> if ascending, <code>false</code> if descending.
+     * @return <code>true</code> if ascending, <code>false</code> if
+     *         descending.
      */
     public boolean isSortAscending() {
         return sortAscending;
@@ -2781,8 +2823,8 @@ public class Table extends AbstractSelect implements Action.Container,
      * Sets the table in ascending order.
      * 
      * @param ascending
-     *            <code>true</code> if ascending, <code>false</code> if
-     *            descending.
+     *                <code>true</code> if ascending, <code>false</code> if
+     *                descending.
      */
     public void setSortAscending(boolean ascending) {
         setSortAscending(ascending, true);
@@ -2825,7 +2867,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * columns are given even in the case where datasource would support this.
      * 
      * @param sortDisabled
-     *            True iff sorting is disabled.
+     *                True iff sorting is disabled.
      */
     public void setSortDisabled(boolean sortDisabled) {
         if (this.sortDisabled != sortDisabled) {
@@ -2870,12 +2912,13 @@ public class Table extends AbstractSelect implements Action.Container,
          * generated.
          * 
          * @param source
-         *            the source Table
+         *                the source Table
          * @param itemId
-         *            the itemId (aka rowId) for the of the cell to be generated
+         *                the itemId (aka rowId) for the of the cell to be
+         *                generated
          * @param columnId
-         *            the id for the generated column (as specified in
-         *            addGeneratedColumn)
+         *                the id for the generated column (as specified in
+         *                addGeneratedColumn)
          * @return
          */
         public abstract Component generateCell(Table source, Object itemId,
@@ -2886,7 +2929,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Set cell style generator for Table.
      * 
      * @param cellStyleGenerator
-     *            New cell style generator or null to remove generator.
+     *                New cell style generator or null to remove generator.
      */
     public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
         this.cellStyleGenerator = cellStyleGenerator;
@@ -2914,13 +2957,36 @@ public class Table extends AbstractSelect implements Action.Container,
          * Called by Table when a cell (and row) is painted.
          * 
          * @param itemId
-         *            The itemId of the painted cell
+         *                The itemId of the painted cell
          * @param propertyId
-         *            The propertyId of the cell, null when getting row style
+         *                The propertyId of the cell, null when getting row
+         *                style
          * @return The style name to add to this cell or row. (the CSS class
          *         name will be i-table-cell-content-[style name], or
          *         i-table-row-[style name] for rows)
          */
         public abstract String getStyle(Object itemId, Object propertyId);
     }
+
+    public void addListener(ItemClickListener listener) {
+        addListener(ItemClickEvent.class, listener,
+                ItemClickEvent.ITEM_CLICK_METHOD);
+        clickListenerCount++;
+        // repaint needed only if click listening became necessary
+        if (clickListenerCount == 1) {
+            requestRepaint();
+        }
+
+    }
+
+    public void removeListener(ItemClickListener listener) {
+        removeListener(ItemClickEvent.class, listener,
+                ItemClickEvent.ITEM_CLICK_METHOD);
+        clickListenerCount++;
+        // repaint needed only if click listening is not needed in client
+        // anymore
+        if (clickListenerCount == 0) {
+            requestRepaint();
+        }
+    }
 }
index 67383ef51f4ec658927420f94579b6bccd433eb5..c6d07ebe65cddfd2e12085d36dc5b77664498195 100644 (file)
@@ -21,10 +21,14 @@ import com.itmill.toolkit.data.Container;
 import com.itmill.toolkit.data.util.ContainerHierarchicalWrapper;
 import com.itmill.toolkit.data.util.IndexedContainer;
 import com.itmill.toolkit.event.Action;
+import com.itmill.toolkit.event.ItemClickEvent;
+import com.itmill.toolkit.event.ItemClickEvent.ItemClickListener;
+import com.itmill.toolkit.event.ItemClickEvent.ItemClickSource;
 import com.itmill.toolkit.terminal.KeyMapper;
 import com.itmill.toolkit.terminal.PaintException;
 import com.itmill.toolkit.terminal.PaintTarget;
 import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails;
 
 /**
  * MenuTree component. MenuTree can be used to select an item (or multiple
@@ -36,7 +40,7 @@ import com.itmill.toolkit.terminal.Resource;
  * @since 3.0
  */
 public class Tree extends AbstractSelect implements Container.Hierarchical,
-        Action.Container {
+        Action.Container, ItemClickSource {
 
     /* Static members */
 
@@ -327,6 +331,15 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
      */
     public void changeVariables(Object source, Map variables) {
 
+        if (clickListenerCount > 0 && variables.containsKey("clickedKey")) {
+            String key = (String) variables.get("clickedKey");
+
+            Object id = itemIdMapper.get(key);
+            MouseEventDetails details = MouseEventDetails.deSerialize((String) variables
+                    .get("clickEvent"));
+            fireEvent(new ItemClickEvent(this, getItem(id), id, null, details));
+        }
+
         if (!isSelectable() && variables.containsKey("selected")) {
             // Not-selectable is a special case, AbstractSelect does not support
             // TODO could be optimized.
@@ -418,6 +431,10 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
                 target.addAttribute("nullselect", true);
             }
 
+            if (clickListenerCount > 0) {
+                target.addAttribute("listenClicks", true);
+            }
+
         }
 
         // Initialize variables
@@ -996,4 +1013,27 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
         }
     }
 
+    private int clickListenerCount = 0;
+
+    public void addListener(ItemClickListener listener) {
+        addListener(ItemClickEvent.class, listener,
+                ItemClickEvent.ITEM_CLICK_METHOD);
+        clickListenerCount++;
+        // repaint needed only if click listening became necessary
+        if (clickListenerCount == 1) {
+            requestRepaint();
+        }
+    }
+
+    public void removeListener(ItemClickListener listener) {
+        removeListener(ItemClickEvent.class, listener,
+                ItemClickEvent.ITEM_CLICK_METHOD);
+        clickListenerCount++;
+        // repaint needed only if click listening is not needed in client
+        // anymore
+        if (clickListenerCount == 0) {
+            requestRepaint();
+        }
+    }
+
 }