/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.v7.client.renderers;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.vaadin.client.WidgetUtil;
import com.vaadin.v7.client.widget.escalator.Cell;
import com.vaadin.v7.client.widget.escalator.RowContainer;
import com.vaadin.v7.client.widget.grid.CellReference;
import com.vaadin.v7.client.widget.grid.EventCellReference;
import com.vaadin.v7.client.widgets.Escalator;
import com.vaadin.v7.client.widgets.Grid;
import com.vaadin.v7.shared.ui.grid.GridConstants.Section;
/**
* An abstract superclass for renderers that render clickable widgets. Click
* handlers can be added to a renderer to listen to click events emitted by all
* widgets rendered by the renderer.
*
* @param <T>
* the presentation (column) type
* @param <W>
* the widget type
*
* @since 7.4
* @author Vaadin Ltd
*/
public abstract class ClickableRenderer<T, W extends Widget>
extends WidgetRenderer<T, W> implements ClickHandler {
/**
* A handler for {@link RendererClickEvent renderer click events}.
*
* @param <R>
* the row type of the containing Grid
*
* @see ButtonRenderer#addClickHandler(RendererClickHandler)
*/
public interface RendererClickHandler<R> extends EventHandler {
/**
* Called when a rendered button is clicked.
*
* @param event
* the event representing the click
*/
void onClick(RendererClickEvent<R> event);
}
/**
* An event fired when a widget rendered by a ClickableWidgetRenderer
* subclass is clicked.
*
* @param <R>
* the row type of the containing Grid
*/
@SuppressWarnings("rawtypes")
public static class RendererClickEvent<R>
extends MouseEvent<RendererClickHandler> {
@SuppressWarnings("unchecked")
static final Type<RendererClickHandler> TYPE = new Type<RendererClickHandler>(
BrowserEvents.CLICK, new RendererClickEvent());
private CellReference<R> cell;
private R row;
private RendererClickEvent() {
}
/**
* Returns the cell of the clicked button.
*
* @return the cell
*/
public CellReference<R> getCell() {
return cell;
}
/**
* Returns the data object corresponding to the row of the clicked
* button.
*
* @return the row data object
*/
public R getRow() {
return row;
}
@Override
public Type<RendererClickHandler> getAssociatedType() {
return TYPE;
}
@Override
@SuppressWarnings("unchecked")
protected void dispatch(RendererClickHandler handler) {
EventTarget target = getNativeEvent().getEventTarget();
if (!Element.is(target)) {
return;
}
Element e = Element.as(target);
Grid<R> grid = (Grid<R>) findClosestParentGrid(e);
cell = findCell(grid, e);
row = cell.getRow();
handler.onClick(this);
}
/**
* Returns the cell the given element belongs to.
*
* @param grid
* the grid instance that is queried
* @param e
* a cell element or the descendant of one
* @return the cell or null if the element is not a grid cell or a
* descendant of one
*/
private static <T> CellReference<T> findCell(Grid<T> grid, Element e) {
RowContainer container = getEscalator(grid).findRowContainer(e);
if (container == null) {
return null;
}
Cell cell = container.getCell(e);
EventCellReference<T> cellReference = new EventCellReference<T>(
grid);
// FIXME: Section is currently always body. Might be useful for the
// future to have an actual check.
cellReference.set(cell, Section.BODY);
return cellReference;
}
private native static Escalator getEscalator(Grid<?> grid)
/*-{
return grid.@com.vaadin.v7.client.widgets.Grid::escalator;
}-*/;
/**
* Returns the Grid instance containing the given element, if any.
* <p>
* <strong>Note:</strong> This method may not work reliably if the grid
* in question is wrapped in a {@link Composite} <em>unless</em> the
* element is inside another widget that is a child of the wrapped grid;
* please refer to the note in
* {@link WidgetUtil#findWidget(Element, Class) Util.findWidget} for
* details.
*
* @param e
* the element whose parent grid to find
* @return the parent grid or null if none found.
*/
private static Grid<?> findClosestParentGrid(Element e) {
Widget w = WidgetUtil.findWidget(e, null);
while (w != null && !(w instanceof Grid)) {
w = w.getParent();
}
return (Grid<?>) w;
}
}
private HandlerManager handlerManager;
/**
* {@inheritDoc}
* <p>
* <em>Implementation note:</em> It is the implementing method's
* responsibility to add {@code this} as a click handler of the returned
* widget, or a widget nested therein, in order to make click events
* propagate properly to handlers registered via
* {@link #addClickHandler(RendererClickHandler) addClickHandler}.
*/
@Override
public abstract W createWidget();
/**
* Adds a click handler to this button renderer. The handler is invoked
* every time one of the widgets rendered by this renderer is clicked.
* <p>
* Note that the row type of the click handler must match the row type of
* the containing Grid.
*
* @param handler
* the click handler to be added
*/
public HandlerRegistration addClickHandler(
RendererClickHandler<?> handler) {
if (handlerManager == null) {
handlerManager = new HandlerManager(this);
}
return handlerManager.addHandler(RendererClickEvent.TYPE, handler);
}
@Override
public void onClick(ClickEvent event) {
/*
* The handler manager is lazily instantiated so it's null iff
* addClickHandler is never called.
*/
if (handlerManager != null) {
DomEvent.fireNativeEvent(event.getNativeEvent(), handlerManager);
}
}
}