aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatti Tahvonen <matti.tahvonen@itmill.com>2008-08-29 10:08:29 +0000
committerMatti Tahvonen <matti.tahvonen@itmill.com>2008-08-29 10:08:29 +0000
commit40cbef33e1c69f1c912d6a21968bcc8455476289 (patch)
tree714c3f9acdf85863c97b7d6821f618993e474463
parente9d29d5cf3cb2f5ab3606692afc07be27f1810f4 (diff)
downloadvaadin-framework-40cbef33e1c69f1c912d6a21968bcc8455476289.tar.gz
vaadin-framework-40cbef33e1c69f1c912d6a21968bcc8455476289.zip
#2009, ItemClickEvents
svn changeset:5298/svn branch:trunk
-rw-r--r--src/com/itmill/toolkit/event/ItemClickEvent.java158
-rw-r--r--src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java92
-rw-r--r--src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java6
-rw-r--r--src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java2
-rw-r--r--src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java54
-rw-r--r--src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java24
-rw-r--r--src/com/itmill/toolkit/tests/tickets/Ticket2009.java125
-rw-r--r--src/com/itmill/toolkit/ui/Table.java248
-rw-r--r--src/com/itmill/toolkit/ui/Tree.java42
9 files changed, 639 insertions, 112 deletions
diff --git a/src/com/itmill/toolkit/event/ItemClickEvent.java b/src/com/itmill/toolkit/event/ItemClickEvent.java
new file mode 100644
index 0000000000..782aa990ca
--- /dev/null
+++ b/src/com/itmill/toolkit/event/ItemClickEvent.java
@@ -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
index 0000000000..8d91863090
--- /dev/null
+++ b/src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java
@@ -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;
+ }
+
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java
index 71258c192e..b1e9f5d919 100644
--- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java
+++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java
@@ -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!
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java
index 37fb6feca8..3493617722 100644
--- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java
+++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java
@@ -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
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java
index 25209a1431..6dab67ccd6 100644
--- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java
+++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java
@@ -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);
}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java
index 4615d3990f..08f3930799 100644
--- a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java
+++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java
@@ -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
index 0000000000..f9eebb02b7
--- /dev/null
+++ b/src/com/itmill/toolkit/tests/tickets/Ticket2009.java
@@ -0,0 +1,125 @@
+package com.itmill.toolkit.tests.tickets;
+
+import com.itmill.toolkit.data.Container;
+import com.itmill.toolkit.event.ItemClickEvent;
+import com.itmill.toolkit.tests.TestForTablesInitialColumnWidthLogicRendering;
+import com.itmill.toolkit.ui.Button;
+import com.itmill.toolkit.ui.Label;
+import com.itmill.toolkit.ui.OrderedLayout;
+import com.itmill.toolkit.ui.Panel;
+import com.itmill.toolkit.ui.Table;
+import com.itmill.toolkit.ui.TextField;
+import com.itmill.toolkit.ui.Tree;
+import com.itmill.toolkit.ui.Window;
+import com.itmill.toolkit.ui.Button.ClickEvent;
+
+public class Ticket2009 extends com.itmill.toolkit.Application {
+
+ TextField f = new TextField();
+
+ public void init() {
+ final Window main = new Window(getClass().getName().substring(
+ getClass().getName().lastIndexOf(".") + 1));
+ setMainWindow(main);
+
+ OrderedLayout ol = new OrderedLayout(
+ OrderedLayout.ORIENTATION_HORIZONTAL);
+ main.setLayout(ol);
+ ol.setSizeFull();
+
+ Panel p = new Panel("Tree test");
+ p.setSizeFull();
+
+ Tree t = new Tree();
+
+ t.addItem("Foo");
+ t.addItem("Bar");
+
+ final OrderedLayout events = new OrderedLayout();
+
+ t.addListener(new ItemClickEvent.ItemClickListener() {
+ public void itemClick(ItemClickEvent event) {
+ events.addComponent(new Label(new Label("Click:"
+ + (event.isDoubleClick() ? "double" : "single")
+ + " button:" + event.getButton() + " propertyId:"
+ + event.getPropertyId() + " itemID:"
+ + event.getItemId() + " item:" + event.getItem())));
+
+ }
+ });
+
+ main.addComponent(p);
+ p.addComponent(t);
+ p.addComponent(events);
+
+ Panel p2 = new Panel("Table test (try dbl click also)");
+ p2.setSizeFull();
+
+ final OrderedLayout events2 = new OrderedLayout();
+ Table table = TestForTablesInitialColumnWidthLogicRendering
+ .getTestTable(5, 100);
+ table.setRowHeaderMode(Table.ROW_HEADER_MODE_ID);
+ table.addListener(new ItemClickEvent.ItemClickListener() {
+ public void itemClick(ItemClickEvent event) {
+ events2.addComponent(new Label("Click:"
+ + (event.isDoubleClick() ? "double" : "single")
+ + " button:" + event.getButton() + " propertyId:"
+ + event.getPropertyId() + " itemID:"
+ + event.getItemId() + " item:" + event.getItem()));
+ if (event.isDoubleClick()) {
+ new PropertyEditor(event);
+ }
+
+ }
+ });
+ p2.addComponent(table);
+ p2.addComponent(events2);
+
+ main.addComponent(p2);
+
+ }
+
+ class PropertyEditor extends Window {
+
+ private static final int W = 300;
+ private static final int H = 150;
+
+ private Container c;
+ private Object itemid;
+ private Object propertyid;
+
+ TextField editor = new TextField();
+ Button done = new Button("Done");
+
+ PropertyEditor(ItemClickEvent event) {
+ c = (Container) event.getSource();
+
+ propertyid = event.getPropertyId();
+ itemid = event.getItemId();
+
+ setCaption("Editing " + itemid + " : " + propertyid);
+
+ editor.setPropertyDataSource(c.getContainerProperty(itemid,
+ propertyid));
+ addComponent(editor);
+ addComponent(done);
+
+ setWidth(W);
+ setHeight(H);
+
+ setPositionX(event.getClientX() - W / 2);
+ setPositionY(event.getClientY() - H / 2);
+
+ getMainWindow().addWindow(this);
+
+ done.addListener(new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ getMainWindow().removeWindow(PropertyEditor.this);
+ }
+ });
+
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/com/itmill/toolkit/ui/Table.java b/src/com/itmill/toolkit/ui/Table.java
index a8b660554f..1a5fb10a67 100644
--- a/src/com/itmill/toolkit/ui/Table.java
+++ b/src/com/itmill/toolkit/ui/Table.java
@@ -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();
+ }
+ }
}
diff --git a/src/com/itmill/toolkit/ui/Tree.java b/src/com/itmill/toolkit/ui/Tree.java
index 67383ef51f..c6d07ebe65 100644
--- a/src/com/itmill/toolkit/ui/Tree.java
+++ b/src/com/itmill/toolkit/ui/Tree.java
@@ -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();
+ }
+ }
+
}