Skip to content

Instantly share code, notes, and snippets.

@branflake2267
Created August 7, 2018 00:21
Show Gist options
  • Save branflake2267/ec514749cf4322c0b6b4a12b390c8c97 to your computer and use it in GitHub Desktop.
Save branflake2267/ec514749cf4322c0b6b4a12b390c8c97 to your computer and use it in GitHub Desktop.
GXT 4.0.3 workaround for GridInlineEditing and Grid DND (Draggable). EXTGWT-5679
package com.sencha.gxt.widget.core.client.grid.editing;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Timer;
import com.sencha.gxt.core.client.GXT;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.gestures.DoubleTapGestureRecognizer;
import com.sencha.gxt.core.client.gestures.DoubleTapGestureRecognizer.DoubleTapGestureEvent;
import com.sencha.gxt.core.client.gestures.DoubleTapGestureRecognizer.DoubleTapGestureEvent.DoubleTapGestureEventHandler;
import com.sencha.gxt.core.client.gestures.TapGestureRecognizer.TapGestureEvent;
import com.sencha.gxt.core.client.gestures.TapGestureRecognizer.TapGestureEvent.TapGestureHandler;
import com.sencha.gxt.core.client.gestures.TouchData;
import com.sencha.gxt.core.client.util.KeyNav;
import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration;
import com.sencha.gxt.data.shared.Converter;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent.BeforeCollapseItemHandler;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent.HasBeforeCollapseItemHandlers;
import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent;
import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent.BeforeExpandItemHandler;
import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent.HasBeforeExpandItemHandlers;
import com.sencha.gxt.widget.core.client.event.BeforeStartEditEvent;
import com.sencha.gxt.widget.core.client.event.BeforeStartEditEvent.BeforeStartEditHandler;
import com.sencha.gxt.widget.core.client.event.CancelEditEvent;
import com.sencha.gxt.widget.core.client.event.CancelEditEvent.CancelEditHandler;
import com.sencha.gxt.widget.core.client.event.CollapseItemEvent.HasCollapseItemHandlers;
import com.sencha.gxt.widget.core.client.event.ColumnWidthChangeEvent;
import com.sencha.gxt.widget.core.client.event.ColumnWidthChangeEvent.ColumnWidthChangeHandler;
import com.sencha.gxt.widget.core.client.event.CompleteEditEvent;
import com.sencha.gxt.widget.core.client.event.CompleteEditEvent.CompleteEditHandler;
import com.sencha.gxt.widget.core.client.event.ExpandItemEvent.HasExpandItemHandlers;
import com.sencha.gxt.widget.core.client.event.HeaderMouseDownEvent;
import com.sencha.gxt.widget.core.client.event.HeaderMouseDownEvent.HeaderMouseDownHandler;
import com.sencha.gxt.widget.core.client.event.ReconfigureEvent;
import com.sencha.gxt.widget.core.client.event.ReconfigureEvent.ReconfigureHandler;
import com.sencha.gxt.widget.core.client.event.StartEditEvent;
import com.sencha.gxt.widget.core.client.event.StartEditEvent.StartEditHandler;
import com.sencha.gxt.widget.core.client.form.IsField;
import com.sencha.gxt.widget.core.client.form.ValueBaseField;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.Grid.Callback;
import com.sencha.gxt.widget.core.client.grid.Grid.GridCell;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.GridView.SelectableTarget;
import com.sencha.gxt.widget.core.client.grid.GroupingView;
import com.sencha.gxt.widget.core.client.tips.ToolTip;
public abstract class AbstractGridEditing<M> implements GridEditing<M> {
protected class AbstractGridEditingKeyNav extends KeyNav {
@Override
public void onEnter(NativeEvent evt) {
AbstractGridEditing.this.onEnter(evt);
}
@Override
public void onEsc(NativeEvent evt) {
AbstractGridEditing.this.onEsc(evt);
}
}
protected class Handler implements AttachEvent.Handler, ScrollHandler, ClickHandler, DoubleClickHandler,
MouseDownHandler, MouseUpHandler, TapGestureHandler, DoubleTapGestureEventHandler, BeforeExpandItemHandler<M>,
BeforeCollapseItemHandler<M>, HeaderMouseDownHandler, ReconfigureHandler, ColumnWidthChangeHandler {
@Override
public void onAttachOrDetach(AttachEvent event) {
AbstractGridEditing.this.onAttachOrDetach(event);
}
@Override
public void onBeforeCollapse(BeforeCollapseItemEvent<M> event) {
completeEditing();
}
@Override
public void onBeforeExpand(BeforeExpandItemEvent<M> event) {
completeEditing();
}
@Override
public void onClick(ClickEvent event) {
AbstractGridEditing.this.onClick(event);
}
@Override
public void onColumnWidthChange(ColumnWidthChangeEvent event) {
completeEditing();
}
@Override
public void onDoubleClick(DoubleClickEvent event) {
AbstractGridEditing.this.onDoubleClick(event);
}
@Override
public void onTapGesture(TapGestureEvent event) {
AbstractGridEditing.this.onTapGesture(event);
}
@Override
public void onDoubleTapGesture(DoubleTapGestureEvent event) {
AbstractGridEditing.this.onDoubleTapGesture(event);
}
@Override
public void onHeaderMouseDown(HeaderMouseDownEvent event) {
handleHeaderMouseDown(event);
}
@Override
public void onMouseDown(MouseDownEvent event) {
AbstractGridEditing.this.onMouseDown(event);
}
@Override
public void onMouseUp(MouseUpEvent event) {
AbstractGridEditing.this.onMouseUp(event);
}
@Override
public void onReconfigure(ReconfigureEvent event) {
AbstractGridEditing.this.onReconfigure(event);
}
@Override
public void onScroll(ScrollEvent event) {
AbstractGridEditing.this.onScroll(event);
}
}
protected GridCell previousActiveCell;
protected GridCell activeCell;
protected ColumnModel<M> columnModel;
protected Map<ColumnConfig<M, ?>, Converter<?, ?>> converterMap = new HashMap<ColumnConfig<M, ?>, Converter<?, ?>>();
protected Grid<M> editableGrid;
protected Map<ColumnConfig<M, ?>, IsField<?>> editorMap = new HashMap<ColumnConfig<M, ?>, IsField<?>>();
protected GroupingHandlerRegistration groupRegistration;
protected Handler handler;
protected KeyNav keyNav;
protected boolean bound;
protected boolean lastValid;
protected Timer monitorTimer;
protected ToolTip tooltip;
protected ClicksToEdit clicksToEdit = ClicksToEdit.ONE;
protected HandlerManager handlerManager;
protected boolean errorSummary = true;
protected int monitorPoll = 200;
protected boolean monitorValid = true;
/**
* Used for onTab
*/
protected Callback callback = new Callback() {
@Override
public boolean isSelectable(GridCell cell) {
if (editableGrid != null) {
ColumnModel<M> cm = editableGrid.getColumnModel();
return !cm.isHidden(cell.getCol()) && editorMap.containsKey(cm.getColumn(cell.getCol()));
}
return false;
}
};
protected SelectableTarget selectableTarget = new SelectableTarget() {
@Override
public boolean selectableTarget(Element target, Cell<?> cell, int colIndex) {
// Like onTab, determine if the column has an editor and if it does it can be selected.
if (editableGrid != null) {
ColumnModel<M> cm = editableGrid.getColumnModel();
return !cm.isHidden(colIndex) && editorMap.containsKey(cm.getColumn(colIndex));
}
// if there is no editor use the default
if (cell != null) {
return cell.handlesSelection();
}
return true;
}
};
@Override
public HandlerRegistration addBeforeStartEditHandler(BeforeStartEditHandler<M> handler) {
return ensureHandlers().addHandler(BeforeStartEditEvent.getType(), handler);
}
@Override
public HandlerRegistration addCancelEditHandler(CancelEditHandler<M> handler) {
return ensureHandlers().addHandler(CancelEditEvent.getType(), handler);
}
@Override
public HandlerRegistration addCompleteEditHandler(CompleteEditHandler<M> handler) {
return ensureHandlers().addHandler(CompleteEditEvent.getType(), handler);
}
@Override
public <N, O> void addEditor(ColumnConfig<M, N> columnConfig, Converter<N, O> converter, IsField<O> field) {
assert columnConfig != null && field != null : "You have to define a columnConfig and a field.";
if (converter != null) {
converterMap.put(columnConfig, converter);
} else {
converterMap.remove(columnConfig);
}
editorMap.put(columnConfig, field);
}
@Override
public <N> void addEditor(ColumnConfig<M, N> columnConfig, IsField<N> field) {
addEditor(columnConfig, null, field);
}
@Override
public HandlerRegistration addStartEditHandler(StartEditHandler<M> handler) {
return ensureHandlers().addHandler(StartEditEvent.getType(), handler);
}
@Override
public abstract void cancelEditing();
/**
* Clears the editors.
*/
public void clearEditors() {
editorMap.clear();
converterMap.clear();
}
@Override
public abstract void completeEditing();
@Override
public void fireEvent(GwtEvent<?> event) {
if (handlerManager != null) {
handlerManager.fireEvent(event);
}
}
/**
* Returns the active cell.
*
* @return the active cell or null if no active edit
*/
public GridCell getActiveCell() {
return activeCell;
}
/**
* Returns the clicks to edit.
*
* @return the clicks to edit
*/
public ClicksToEdit getClicksToEdit() {
return clicksToEdit;
}
@SuppressWarnings("unchecked")
@Override
public <N, O> Converter<N, O> getConverter(ColumnConfig<M, N> columnConfig) {
return (Converter<N, O>) converterMap.get(columnConfig);
}
@Override
public Grid<M> getEditableGrid() {
return editableGrid;
}
@Override
@SuppressWarnings("unchecked")
public <O> IsField<O> getEditor(ColumnConfig<M, ?> columnConfig) {
return (IsField<O>) editorMap.get(columnConfig);
}
/**
* Returns the cell editor.
*
* @param target event target that was focused on
* @return the editor
* @since 4.0.4
*/
public <O> IsField<O> getEditor(XElement target) {
if (target == null) {
return null;
}
GridCell cell = findCell(target);
if (cell == null) {
return null;
}
ColumnConfig<M, ?> columnConfig = columnModel.getColumn(cell.getCol());
return getEditor(columnConfig);
}
/**
* Returns the interval that the editor is validated.
*
* @return the interval in ms
*/
public int getMonitorPoll() {
return monitorPoll;
}
@Override
public boolean isEditing() {
return activeCell != null;
}
/**
* Returns true if a tooltip with an error summary is shown.
*
* @return true if a tooltip with an error summary is shown
*/
public boolean isErrorSummary() {
return errorSummary;
}
/**
* Returns true if valid monitoring is enabled.
*
* @return the monitor valid state
*/
public boolean isMonitorValid() {
return monitorValid;
}
@Override
public void removeEditor(ColumnConfig<M, ?> columnConfig) {
editorMap.remove(columnConfig);
converterMap.remove(columnConfig);
}
/**
* Sets the number of clicks to edit (defaults to ONE).
*
* @param clicksToEdit the clicks to edit
*/
public void setClicksToEdit(ClicksToEdit clicksToEdit) {
this.clicksToEdit = clicksToEdit;
}
@Override
public void setEditableGrid(Grid<M> editableGrid) {
cancelEditing();
if (groupRegistration != null) {
groupRegistration.removeHandler();
groupRegistration = null;
}
this.editableGrid = editableGrid;
this.columnModel = editableGrid == null ? null : editableGrid.getColumnModel();
if (keyNav != null && editableGrid == null) {
keyNav.bind(null);
} else {
ensureInternalKeyNav().bind(editableGrid);
}
if (editableGrid != null) {
// Override custom cell.handleSelection() if there is an editor for the cell
editableGrid.getView().setSelectableTarget(selectableTarget);
GroupingHandlerRegistration reg = new GroupingHandlerRegistration();
reg.add(editableGrid.addDomHandler(ensureInternHandler(), ClickEvent.getType()));
reg.add(editableGrid.addDomHandler(ensureInternHandler(), MouseDownEvent.getType()));
reg.add(editableGrid.addDomHandler(ensureInternHandler(), MouseUpEvent.getType()));
reg.add(editableGrid.addDomHandler(ensureInternHandler(), DoubleClickEvent.getType()));
reg.add(editableGrid.addDomHandler(ensureInternHandler(), ScrollEvent.getType()));
reg.add(editableGrid.addHandler(ensureInternHandler(), HeaderMouseDownEvent.getType()));
reg.add(editableGrid.addHandler(ensureInternHandler(), ReconfigureEvent.getType()));
if (!GXT.isMSEdge() && !GXT.isIE()) {
reg.add(editableGrid.addHandler(ensureInternHandler(), TapGestureEvent.getType()));
reg.add(editableGrid.addHandler(ensureInternHandler(), DoubleTapGestureEvent.getType()));
}
reg.add(editableGrid.getColumnModel().addColumnWidthChangeHandler(ensureInternHandler()));
if (editableGrid instanceof HasExpandItemHandlers) {
@SuppressWarnings({ "rawtypes", "unchecked" })
HasBeforeExpandItemHandlers<M> hasHandlers = (HasBeforeExpandItemHandlers) editableGrid;
reg.add(hasHandlers.addBeforeExpandHandler(ensureInternHandler()));
}
if (editableGrid instanceof HasCollapseItemHandlers) {
@SuppressWarnings({ "rawtypes", "unchecked" })
HasBeforeCollapseItemHandlers<M> hasHandlers = (HasBeforeCollapseItemHandlers) editableGrid;
reg.add(hasHandlers.addBeforeCollapseHandler(ensureInternHandler()));
}
groupRegistration = reg;
if (GXT.isTouch()) {
boolean containsDoubleTapGestureRecognizer = false;
for (int i = 0; i < editableGrid.getGestureRecognizerCount(); i++) {
if (editableGrid.getGestureRecognizer(i) instanceof DoubleTapGestureRecognizer) {
containsDoubleTapGestureRecognizer = true;
break;
}
}
if (!containsDoubleTapGestureRecognizer) {
editableGrid.addGestureRecognizer(new DoubleTapGestureRecognizer() {
@Override
protected void onTap(TouchData touchData) {
// do nothing here - we want to rely on the long press or tap GR to fire the single tap
}
@Override
protected void handlePreventDefault(NativeEvent event) {
// don't prevent default here
}
});
}
}
}
}
/**
* True to show a tooltip with an error summary (defaults to true)
*
* @param errorSummary true to show an error summary.
*/
public void setErrorSummary(boolean errorSummary) {
this.errorSummary = errorSummary;
}
/**
* Sets the polling interval that the row editor validation is run (defaults to 200).
*
* @param monitorPoll the polling interval in ms in that validation is done
*/
public void setMonitorPoll(int monitorPoll) {
this.monitorPoll = monitorPoll;
}
/**
* True to monitor the valid status of this editor (defaults to true).
*
* @param monitorValid true to monitor this row editor
*/
public void setMonitorValid(boolean monitorValid) {
this.monitorValid = monitorValid;
}
@Override
public abstract void startEditing(GridCell cell);
protected void bindHandler() {
boolean valid = isValid();
if (!valid) {
lastValid = false;
if (isErrorSummary()) {
showTooltip(getErrorHtml());
}
} else if (valid && !lastValid) {
hideTooltip();
lastValid = true;
}
}
protected HandlerManager ensureHandlers() {
if (handlerManager == null) {
handlerManager = new HandlerManager(this);
}
return handlerManager;
}
protected KeyNav ensureInternalKeyNav() {
if (keyNav == null) {
keyNav = new AbstractGridEditingKeyNav();
}
return keyNav;
}
protected Handler ensureInternHandler() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
protected GridCell findCell(Element target) {
if (editableGrid != null) {
if (isSelectectableTarget(target) && editableGrid.getView().getBody().isOrHasChild(target)) {
int row = editableGrid.getView().findRowIndex(target);
int col = editableGrid.getView().findCellIndex(target, null);
if (row != -1 && col != -1) {
return new GridCell(row, col);
}
}
}
return null;
}
/**
* Override the ability to turn on and off selection of a cell. See cell.handlesSelection().
*/
protected boolean isSelectectableTarget(Element target) {
// The cell must be able to handle selection to edit it.
return editableGrid.getView().isSelectableTarget(target);
}
protected void focusCell(int row, int col) {
// this could could be executing after the editor has been removed or hidden
// which can throw an exception in IE
if (getEditableGrid().isAttached()) {
try {
getEditableGrid().getView().focusCell(row, col, true);
} catch (Exception e) {
}
}
}
protected void focusGrid() {
getEditableGrid().focus();
}
protected abstract SafeHtml getErrorHtml();
protected void getErrorMessage(IsField<?> field, SafeHtmlBuilder sb, SafeHtml title) {
boolean result = true;
if (field instanceof ValueBaseField) {
ValueBaseField<?> vfield = (ValueBaseField<?>) field;
result = vfield.isCurrentValid(true);
}
if (!result || !field.isValid(true)) {
sb.appendHtmlConstant("<li><b>");
sb.append(title);
sb.appendHtmlConstant("</b>: ");
sb.appendEscaped(field.getErrors().get(0).getMessage());
sb.appendHtmlConstant("</li>");
}
}
protected void handleDoubleEdit(final NativeEvent event) {
startEditing(event);
}
protected <N, O> void handleHeaderMouseDown(HeaderMouseDownEvent event) {
completeEditing();
}
protected void handleSingleEdit(final NativeEvent event) {
final GridCell cell = findCell(event.getEventTarget().<Element>cast());
if (cell == null) {
return;
}
// When starting an edit on the same row of an active edit the active edit value
// is lost as the active cell does not complete the edit
// this only happens with TreeGrid, not Grid which could be looked into
if (activeCell != null && activeCell.getRow() == cell.getRow()) {
completeEditing();
}
// Edit is starting and stopping immediately when leaving another active edit that completes
if (GXT.isiOS()) {
startEditing(cell);
} else {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
startEditing(cell);
}
});
}
}
protected void hideTooltip() {
if (tooltip != null) {
tooltip.hide();
tooltip.disable();
}
}
protected abstract boolean isValid();
protected void onAttachOrDetach(AttachEvent event) {
if (!event.isAttached()) {
cancelEditing();
}
}
/**
* @param event The tap event.
* @since 4.0.3
*/
public void onTapGesture(TapGestureEvent event) {
// If this is a grouping header, ignore this
GridView<M> view = getEditableGrid().getView();
if (view != null && view instanceof GroupingView) {
XElement target = event.getTouchData().getLastNativeEvent().getEventTarget().cast();
GroupingView<M> groupingView = (GroupingView<M>) view;
XElement head = groupingView.getGroupingAppearance().findHead(target);
if (head != null) {
// Ignore this if the click is on the grouping header head.
return;
}
}
if (clicksToEdit == ClicksToEdit.ONE) {
// Get the target element from the touch data
XElement element = event.getTouchData().getLastNativeEvent().getEventTarget().<XElement>cast();
// Retrieve the cell based on the target element
final GridCell cell = findCell(element);
if (cell == null) {
return;
}
startEditing(cell);
event.getTouchData().getLastNativeEvent().preventDefault();
}
}
/**
* @param event The double tap event.
* @since 4.0.3
*/
public void onDoubleTapGesture(DoubleTapGestureEvent event) {
// If this is a grouping header, ignore this
GridView<M> view = getEditableGrid().getView();
if (view != null && view instanceof GroupingView) {
XElement target = event.getTouchData().getLastNativeEvent().getEventTarget().cast();
GroupingView<M> groupingView = (GroupingView<M>) view;
XElement head = groupingView.getGroupingAppearance().findHead(target);
if (head != null) {
// Ignore this if the click is on the grouping header head.
return;
}
}
if (clicksToEdit == ClicksToEdit.TWO) {
// Get the target element from the touch data
XElement element = event.getTouchData().getLastNativeEvent().getEventTarget().<XElement>cast();
// Retrieve the cell based on the target element
final GridCell cell = findCell(element);
if (cell == null) {
return;
}
startEditing(cell);
event.getTouchData().getLastNativeEvent().preventDefault();
}
}
protected void onClick(final ClickEvent event) {
if (clicksToEdit == ClicksToEdit.ONE) {
handleSingleEdit(event.getNativeEvent());
}
}
protected void onDoubleClick(DoubleClickEvent event) {
if (clicksToEdit == ClicksToEdit.TWO) {
handleDoubleEdit(event.getNativeEvent());
}
}
protected void onEnter(NativeEvent evt) {
evt.preventDefault();
GridCell gc = activeCell;
if (gc == null) {
XElement target = evt.getEventTarget().cast();
gc = findCell(target);
}
if (isEditing()) {
completeEditing();
} else {
startEditing(gc);
}
if (gc != null) {
focusCell(gc.getRow(), gc.getCol());
focusGrid();
}
}
protected void onEsc(NativeEvent evt) {
GridCell gc = activeCell;
cancelEditing();
if (gc != null) {
focusCell(gc.getRow(), gc.getCol());
focusGrid();
}
}
protected void onMouseDown(MouseDownEvent event) {
}
protected void onMouseUp(MouseUpEvent event) {
}
@SuppressWarnings("unchecked")
protected void onReconfigure(ReconfigureEvent event) {
setEditableGrid((Grid<M>) event.getSource());
}
protected void onScroll(ScrollEvent event) {
cancelEditing();
}
protected abstract void showTooltip(SafeHtml content);
protected void startEditing(Element target) {
GridCell cell = findCell(target);
if (cell != null) {
int row = cell.getRow();
int col = cell.getCol();
if (row != -1 && col != -1) {
startEditing(new GridCell(row, col));
}
}
}
protected void startMonitoring() {
if (!bound && monitorValid) {
bound = true;
if (monitorTimer == null) {
monitorTimer = new Timer() {
@Override
public void run() {
bindHandler();
}
};
}
monitorTimer.scheduleRepeating(monitorPoll);
}
}
protected void stopMonitoring() {
bound = false;
if (monitorTimer != null) {
monitorTimer.cancel();
}
}
protected void startEditing(NativeEvent evt) {
if (Element.is(evt.getEventTarget())) {
startEditing(Element.as(evt.getEventTarget()));
}
}
}
package com.sencha.gxt.fx.client;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.Style;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.gestures.DragGestureRecognizer;
import com.sencha.gxt.core.client.gestures.TouchData;
import com.sencha.gxt.core.client.gestures.TouchEventToGestureAdapter;
import com.sencha.gxt.core.client.resources.CommonStyles;
import com.sencha.gxt.core.client.resources.CommonStyles.Styles;
import com.sencha.gxt.core.client.util.BaseEventPreview;
import com.sencha.gxt.core.client.util.Point;
import com.sencha.gxt.core.client.util.Rectangle;
import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration;
import com.sencha.gxt.fx.client.DragCancelEvent.DragCancelHandler;
import com.sencha.gxt.fx.client.DragCancelEvent.HasDragCancelHandlers;
import com.sencha.gxt.fx.client.DragEndEvent.DragEndHandler;
import com.sencha.gxt.fx.client.DragEndEvent.HasDragEndHandlers;
import com.sencha.gxt.fx.client.DragHandler.HasDragHandlers;
import com.sencha.gxt.fx.client.DragMoveEvent.DragMoveHandler;
import com.sencha.gxt.fx.client.DragMoveEvent.HasDragMoveHandlers;
import com.sencha.gxt.fx.client.DragStartEvent.DragStartHandler;
import com.sencha.gxt.fx.client.DragStartEvent.HasDragStartHandlers;
/**
* <p>Adds drag behavior to any widget. Drag operations can be initiated from the
* widget itself, or another widget, such as the header in a dialog.</p>
* <p>It is possible to specify event targets that will be ignored. If the target
* element has the {@link Styles#nodrag()} style (as returned by
* {@link CommonStyles#get()}) it will not trigger a drag operation.</p>
*/
public class Draggable implements HasDragStartHandlers, HasDragEndHandlers, HasDragMoveHandlers, HasDragCancelHandlers,
HasDragHandlers {
public interface DraggableAppearance {
void addUnselectableStyle(Element element);
Element createProxy();
void removeUnselectableStyle(Element element);
void setProxyStyle(String proxyClass);
}
protected int conX, conY, conWidth, conHeight;
protected int dragStartX, dragStartY;
protected int lastX, lastY;
protected XElement proxyEl;
protected Rectangle startBounds;
protected DragGestureRecognizer dragGestureRecognizer;
protected final DraggableAppearance appearance;
protected int clientWidth, clientHeight;
protected boolean constrainClient = true;
protected boolean constrainHorizontal;
protected boolean constrainVertical;
protected Widget container;
protected boolean dragging;
protected Widget dragWidget;
protected XElement dragWidgetElement;
protected boolean enabled = true;
protected Widget handleWidget;
protected GroupingHandlerRegistration handlerRegistrations;
protected boolean moveAfterProxyDrag = true;
protected BaseEventPreview eventPreview;
protected boolean sizeProxyToSource = true;
protected int startDragDistance = 2;
protected Element startElement;
// config
protected boolean updateZIndex = true;
protected boolean useProxy = true;
protected int xLeft = Style.DEFAULT, xRight = Style.DEFAULT;
protected int xTop = Style.DEFAULT, xBottom = Style.DEFAULT;
protected SimpleEventBus eventBus;
/**
* Creates a new draggable instance.
*
* @param dragWidget the widget to be dragged
*/
public Draggable(Widget dragWidget) {
this(dragWidget, dragWidget, GWT.<DraggableAppearance> create(DraggableAppearance.class));
}
/**
* Creates a new draggable instance.
*
* @param dragWidget the widget to be dragged
* @param appearance the appearance with which to render the component
*/
public Draggable(Widget dragWidget, DraggableAppearance appearance) {
this(dragWidget, dragWidget, appearance);
}
/**
* Create a new draggable instance.
*
* @param dragWidget the widget to be dragged
* @param handleWidget the widget drags will be initiated from
*/
public Draggable(final Widget dragWidget, final Widget handleWidget) {
this(dragWidget, handleWidget, GWT.<DraggableAppearance>create(DraggableAppearance.class));
}
/**
* Create a new draggable instance.
*
* @param dragWidget the widget to be dragged
* @param handleWidget the widget drags will be initiated from
* @param appearance the appearance with which to render the component
*/
public Draggable(final Widget dragWidget, final Widget handleWidget, DraggableAppearance appearance) {
this.dragWidget = dragWidget;
this.handleWidget = handleWidget;
this.appearance = appearance;
handleWidget.getElement().getStyle().setProperty("touchAction", "none");
handleWidget.getElement().getStyle().setProperty("msTouchAction", "none");
dragWidgetElement = dragWidget.getElement().cast();
// Determines where the drop target is for mouse
// Mouse over provides drop over element target
eventPreview = new BaseEventPreview() {
@Override
public boolean onPreview(NativePreviewEvent event) {
Event e = event.getNativeEvent().cast();
e.preventDefault();
switch (event.getTypeInt()) {
case Event.ONKEYDOWN:
if (dragging && e.getKeyCode() == KeyCodes.KEY_ESCAPE) {
cancelDrag();
}
break;
case Event.ONMOUSEMOVE:
onMouseMove(e);
break;
case Event.ONMOUSEUP:
stopDrag(e);
break;
}
return true;
}
};
eventPreview.setAutoHide(false);
MouseDownHandler mouseDownHandler = new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent event) {
Draggable.this.onMouseDown(event);
}
};
// Register mouse and touch listeners
handlerRegistrations = new GroupingHandlerRegistration();
handlerRegistrations.add(handleWidget.addDomHandler(mouseDownHandler, MouseDownEvent.getType()));
dragGestureRecognizer = getDragGestureRecognizer();
}
/**
* Crates the drag gesture recognizer and passes along the touch events to Draggable.
*
* @return the grag gesture recognizer
*/
protected DragGestureRecognizer getDragGestureRecognizer() {
if (dragGestureRecognizer == null) {
dragGestureRecognizer = new DragGestureRecognizer() {
@Override
protected boolean onStart(TouchData startedTouch) {
super.onStart(startedTouch);
return onTouchStart(startedTouch);
}
@Override
protected void onMove(List<TouchData> touches) {
super.onMove(touches);
onTouchMove(touches);
}
@Override
protected void onCancel(List<TouchData> touches) {
super.onCancel(touches);
onTouchEnd(touches);
}
@Override
protected void onEnd(List<TouchData> touches) {
super.onEnd(touches);
onTouchEnd(touches);
}
};
TouchEventToGestureAdapter touchEventsGestureAdapter = new TouchEventToGestureAdapter(handleWidget, dragGestureRecognizer);
handlerRegistrations.add(touchEventsGestureAdapter.getHandlerRegistration());
}
return dragGestureRecognizer;
}
protected void onTouchEnd(List<TouchData> touches) {
stopDrag(touches.get(0).getLastNativeEvent().<Event>cast());
}
protected void onTouchMove(List<TouchData> touches) {
TouchData touch = touches.get(0);
Point pos = touch.getLastPosition();
handleMove(pos.getX(), pos.getY(), touch.getLastNativeEvent().<Event>cast());
}
protected boolean onTouchStart(TouchData startedTouch) {
if (!enabled) {
return false;
}
Element target = startedTouch.getStartElement().asElement();
if (hasClassName(target, CommonStyles.get().nodrag())) {
return false;
}
if (!handleWidget.getElement().isOrHasChild(target)) {
return false;
}
Point position = startedTouch.getStartPosition();
Element start = startedTouch.getStartElement() != null ? startedTouch.getStartElement().asElement() : null;
handleStart(position.getX(), position.getY(), startedTouch.getLastNativeEvent().<Event>cast(), start);
return true;
}
@Override
public HandlerRegistration addDragCancelHandler(DragCancelHandler handler) {
return ensureHandlers().addHandler(DragCancelEvent.getType(), handler);
}
@Override
public HandlerRegistration addDragEndHandler(DragEndHandler handler) {
return ensureHandlers().addHandler(DragEndEvent.getType(), handler);
}
@Override
public HandlerRegistration addDragHandler(DragHandler handler) {
GroupingHandlerRegistration reg = new GroupingHandlerRegistration();
reg.add(ensureHandlers().addHandler(DragStartEvent.getType(), handler));
reg.add(ensureHandlers().addHandler(DragEndEvent.getType(), handler));
reg.add(ensureHandlers().addHandler(DragMoveEvent.getType(), handler));
reg.add(ensureHandlers().addHandler(DragCancelEvent.getType(), handler));
return reg;
}
@Override
public HandlerRegistration addDragMoveHandler(DragMoveHandler handler) {
return ensureHandlers().addHandler(DragMoveEvent.getType(), handler);
}
@Override
public HandlerRegistration addDragStartHandler(DragStartHandler handler) {
return ensureHandlers().addHandler(DragStartEvent.getType(), handler);
}
/**
* Cancels the drag if running.
*/
public void cancelDrag() {
eventPreview.remove();
if (dragging) {
dragging = false;
if (isUseProxy()) {
proxyEl.disableTextSelection(false);
proxyEl.getStyle().setVisibility(Visibility.HIDDEN);
proxyEl.removeFromParent();
} else {
dragWidgetElement.setXY(startBounds.getX(), startBounds.getY());
}
ensureHandlers().fireEventFromSource(new DragCancelEvent(dragWidget, startElement), this);
afterDrag();
}
startElement = null;
}
/**
* Returns the drag container.
*
* @return the drag container
*/
public Widget getContainer() {
return container;
}
/**
* Specifies a container to which the drag widget is constrained.
*
* @param container the container
*/
public void setContainer(Widget container) {
this.container = container;
}
/**
* Returns the drag handleWidget.
*
* @return the drag handleWidget
*/
public Widget getDragHandle() {
return handleWidget;
}
/**
* Returns the widget being dragged.
*
* @return the drag widget
*/
public Widget getDragWidget() {
return dragWidget;
}
/**
* Returns the number of pixels the cursor must move before dragging begins.
*
* @return the distance in pixels
*/
public int getStartDragDistance() {
return startDragDistance;
}
/**
* Specifies how far the cursor must move after mousedown to start dragging
* (defaults to 2).
*
* @param startDragDistance the start distance in pixels
*/
public void setStartDragDistance(int startDragDistance) {
this.startDragDistance = startDragDistance;
}
/**
* Returns true if drag is constrained to the viewport.
*
* @return the constrain client state
*/
public boolean isConstrainClient() {
return constrainClient;
}
/**
* True to set constrain movement to the viewport (defaults to true).
*
* @param constrainClient true to constrain to viewport
*/
public void setConstrainClient(boolean constrainClient) {
this.constrainClient = constrainClient;
}
/**
* Returns true if horizontal movement is constrained.
*
* @return the horizontal constrain state
*/
public boolean isConstrainHorizontal() {
return constrainHorizontal;
}
/**
* True to stop horizontal movement (defaults to false).
*
* @param constrainHorizontal true to stop horizontal movement
*/
public void setConstrainHorizontal(boolean constrainHorizontal) {
this.constrainHorizontal = constrainHorizontal;
}
/**
* Returns true if vertical movement is constrained.
*
* @return true if vertical movement is constrained
*/
public boolean isConstrainVertical() {
return constrainVertical;
}
/**
* True to stop vertical movement (defaults to false).
*
* @param constrainVertical true to stop vertical movement
*/
public void setConstrainVertical(boolean constrainVertical) {
this.constrainVertical = constrainVertical;
}
/**
* Returns true if a drag is in progress.
*
* @return the drag state
*/
public boolean isDragging() {
return dragging;
}
/**
* Returns true if enabled.
*
* @return the enable state
*/
public boolean isEnabled() {
return enabled;
}
/**
* Enables dragging if the argument is true, and disables it
* otherwise.
*
* @param enabled the new enabled state
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Returns true if the drag widget is moved after a proxy drag.
*
* @return the move after proxy state
*/
public boolean isMoveAfterProxyDrag() {
return moveAfterProxyDrag;
}
/**
* True to move source widget after a proxy drag (defaults to true).
*
* @param moveAfterProxyDrag true to move after a proxy drag
*/
public void setMoveAfterProxyDrag(boolean moveAfterProxyDrag) {
this.moveAfterProxyDrag = moveAfterProxyDrag;
}
/**
* Returns true if the proxy element is sized to match the drag widget.
*
* @return the size proxy to source state
*/
public boolean isSizeProxyToSource() {
return sizeProxyToSource;
}
/**
* True to set proxy dimensions the same as the drag widget (defaults to
* true).
*
* @param sizeProxyToSource true to update proxy size
*/
public void setSizeProxyToSource(boolean sizeProxyToSource) {
this.sizeProxyToSource = sizeProxyToSource;
}
/**
* Returns true if the z-index is updated after a drag.
*
* @return the update z-index state
*/
public boolean isUpdateZIndex() {
return updateZIndex;
}
/**
* True if the CSS z-index should be updated on the widget being dragged.
* Setting this value to true will ensure that the dragged
* element is always displayed over all other widgets (defaults to true).
*
* @param updateZIndex true update the z-index
*/
public void setUpdateZIndex(boolean updateZIndex) {
this.updateZIndex = updateZIndex;
}
/**
* Returns true if proxy element is enabled.
*
* @return the use proxy state
*/
public boolean isUseProxy() {
return useProxy;
}
/**
* True to use a proxy widget during drag operation (defaults to true).
*
* @param useProxy true use a proxy
*/
public void setUseProxy(boolean useProxy) {
this.useProxy = useProxy;
}
/**
* Removes the drag handles.
*/
public void release() {
cancelDrag();
handlerRegistrations.removeHandler();
}
/**
* Sets the proxy element.
*
* @param element the proxy element
*/
public void setProxy(XElement element) {
proxyEl = element;
}
public void setProxyStyle(String proxyClass) {
appearance.setProxyStyle(proxyClass);
}
/**
* Constrains the horizontal travel.
*
* @param left the number of pixels the element can move to the left
* @param right the number of pixels the element can move to the right
*/
public void setXConstraint(int left, int right) {
xLeft = left;
xRight = right;
}
/**
* Constrains the vertical travel.
*
* @param top the number of pixels the element can move to the up
* @param bottom the number of pixels the element can move to the down
*/
public void setYConstraint(int top, int bottom) {
xTop = top;
xBottom = bottom;
}
protected void afterDrag() {
appearance.removeUnselectableStyle(Document.get().getBody());
Shim.get().uncover();
}
protected XElement createProxy() {
return proxyEl = appearance.createProxy().cast();
}
protected void handleStart(int x, int y, Event event, Element target) {
startBounds = dragWidgetElement.getBounds();
startElement = target;
dragStartX = x;
dragStartY = y;
eventPreview.add();
clientWidth = Window.getClientWidth() + XDOM.getBodyScrollLeft();
clientHeight = Window.getClientHeight() + XDOM.getBodyScrollTop();
if (container != null) {
conX = container.getAbsoluteLeft();
conY = container.getAbsoluteTop();
conWidth = container.getOffsetWidth();
conHeight = container.getOffsetHeight();
}
if (startDragDistance == 0) {
startDrag(event);
}
}
protected void onMouseDown(MouseDownEvent e) {
if (!enabled || e.getNativeEvent().getButton() != Event.BUTTON_LEFT) {
return;
}
Element target = e.getNativeEvent().getEventTarget().cast();
// Skip dragging using class name CommonStyles.get().nodrag().
if (hasClassName(target, CommonStyles.get().nodrag())) {
// Skip Dragging on this target
return;
}
// Allow the option of overriding text selection
// This would happen in the event it's a grid inline editing with dnd
if (!target.getPropertyBoolean("ignoreTextSelection")) {
// still allow text selection, prevent drag of other elements
if ((!"input".equalsIgnoreCase(target.getTagName()) && !"textarea".equalsIgnoreCase(target.getTagName()))
|| target.getPropertyBoolean("disabled")) {
e.getNativeEvent().preventDefault();
}
}
handleStart(e.getClientX(), e.getClientY(), e.getNativeEvent().<Event> cast(), target);
}
protected void handleMove(int x, int y, Event event) {
if (!dragging && (Math.abs(dragStartX - x) > startDragDistance || Math.abs(dragStartY - y) > startDragDistance)) {
startDrag(event);
}
if (dragging) {
int left = constrainHorizontal ? startBounds.getX() : startBounds.getX() + (x - dragStartX);
int top = constrainVertical ? startBounds.getY() : startBounds.getY() + (y - dragStartY);
if (constrainClient) {
if (!constrainHorizontal) {
int width = startBounds.getWidth();
left = Math.max(left, 0);
left = Math.max(0, Math.min(clientWidth - width, left));
}
if (!constrainVertical) {
top = Math.max(top, 0);
int height = startBounds.getHeight();
if (Math.min(clientHeight - height, top) > 0) {
top = Math.max(2, Math.min(clientHeight - height, top));
}
}
}
if (container != null) {
int width = startBounds.getWidth();
int height = startBounds.getHeight();
if (!constrainHorizontal) {
left = Math.max(left, conX);
left = Math.min(conX + conWidth - width, left);
}
if (!constrainVertical) {
top = Math.min(conY + conHeight - height, top);
top = Math.max(top, conY);
}
}
if (!constrainHorizontal) {
if (xLeft != Style.DEFAULT) {
left = Math.max(startBounds.getX() - xLeft, left);
}
if (xRight != Style.DEFAULT) {
left = Math.min(startBounds.getX() + xRight, left);
}
}
if (!constrainVertical) {
if (xTop != Style.DEFAULT) {
top = Math.max(startBounds.getY() - xTop, top);
}
if (xBottom != Style.DEFAULT) {
top = Math.min(startBounds.getY() + xBottom, top);
}
}
lastX = left;
lastY = top;
DragMoveEvent evt = new DragMoveEvent(dragWidget, startElement, lastX, lastY, event);
ensureHandlers().fireEventFromSource(evt, this);
if (evt.isCancelled()) {
cancelDrag();
return;
}
int tl = evt.getX() != lastX ? evt.getX() : lastX;
int tt = evt.getY() != lastY ? evt.getY() : lastY;
if (useProxy) {
proxyEl.setXY(tl, tt);
} else {
dragWidgetElement.setXY(tl, tt);
}
}
}
protected void onMouseMove(Event event) {
Element elem = event.getEventTarget().cast();
// elem.getClassName throwing GWT exception when dragged widget is over SVG / VML
if (hasClassName(elem, "x-insert")) {
return;
}
handleMove(event.getClientX(), event.getClientY(), event);
}
protected void startDrag(Event event) {
DragStartEvent de = new DragStartEvent(dragWidget, startElement, startBounds.getX(), startBounds.getY(), event);
ensureHandlers().fireEventFromSource(de, this);
if (de.isCancelled()) {
cancelDrag();
return;
}
dragging = true;
appearance.addUnselectableStyle(Document.get().getBody());
if (!useProxy) {
dragWidget.getElement().<XElement> cast().makePositionable();
}
if (event != null) {
event.preventDefault();
}
Shim.get().cover(true);
lastX = startBounds.getX();
lastY = startBounds.getY();
if (useProxy) {
if (proxyEl == null) {
createProxy();
}
if (container == null) {
Document.get().getBody().appendChild(proxyEl);
} else {
container.getElement().appendChild(proxyEl);
}
proxyEl.setVisibility(true);
proxyEl.setZIndex(XDOM.getTopZIndex());
proxyEl.makePositionable(true);
if (sizeProxyToSource) {
proxyEl.setBounds(startBounds);
} else {
proxyEl.setXY(startBounds.getX() + 50, startBounds.getY() + 50);
}
// did listeners change size?
if (de.getHeight() > 0 && de.getWidth() > 0) {
proxyEl.setSize(de.getWidth(), de.getHeight(), true);
} else if (de.getHeight() > 0) {
proxyEl.setHeight(de.getHeight(), true);
} else if (de.getWidth() > 0) {
proxyEl.setWidth(de.getWidth(), true);
}
} else if (updateZIndex) {
dragWidget.getElement().<XElement> cast().setZIndex(XDOM.getTopZIndex());
}
}
protected void stopDrag(Event event) {
eventPreview.remove();
if (dragging) {
dragging = false;
if (isUseProxy()) {
if (isMoveAfterProxyDrag()) {
Rectangle rect = proxyEl.getBounds();
dragWidget.getElement().<XElement> cast().setXY(rect.getX(), rect.getY());
}
proxyEl.setVisibility(false);
proxyEl.disableTextSelection(false);
proxyEl.removeFromParent();
}
DragEndEvent de = new DragEndEvent(dragWidget, startElement, lastX, lastY, event);
ensureHandlers().fireEventFromSource(de, this);
afterDrag();
}
startElement = null;
}
SimpleEventBus ensureHandlers() {
return eventBus == null ? eventBus = new SimpleEventBus() : eventBus;
}
protected native boolean hasClassName(Element elem, String className) /*-{
return !!elem.hasAttribute && elem.hasAttribute("class") && elem.getAttribute("class").indexOf(className) != -1;
}-*/;
}
package com.sencha.gxt.widget.core.client.grid.editing;
import java.util.logging.Logger;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.GXT;
import com.sencha.gxt.core.client.GXTLogConfiguration;
import com.sencha.gxt.core.client.Style.Side;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.util.KeyNav;
import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration;
import com.sencha.gxt.data.shared.Converter;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.widget.core.client.Component;
import com.sencha.gxt.widget.core.client.ComponentHelper;
import com.sencha.gxt.widget.core.client.event.BeforeStartEditEvent;
import com.sencha.gxt.widget.core.client.event.BlurEvent;
import com.sencha.gxt.widget.core.client.event.BlurEvent.BlurHandler;
import com.sencha.gxt.widget.core.client.event.CancelEditEvent;
import com.sencha.gxt.widget.core.client.event.CompleteEditEvent;
import com.sencha.gxt.widget.core.client.event.HeaderMouseDownEvent;
import com.sencha.gxt.widget.core.client.event.StartEditEvent;
import com.sencha.gxt.widget.core.client.form.CheckBox;
import com.sencha.gxt.widget.core.client.form.Field;
import com.sencha.gxt.widget.core.client.form.IsField;
import com.sencha.gxt.widget.core.client.form.TextArea;
import com.sencha.gxt.widget.core.client.form.TriggerField;
import com.sencha.gxt.widget.core.client.form.ValueBaseField;
import com.sencha.gxt.widget.core.client.form.error.HasErrorHandler;
import com.sencha.gxt.widget.core.client.grid.CellSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.Grid.GridCell;
import com.sencha.gxt.widget.core.client.grid.GridSelectionModel;
import com.sencha.gxt.widget.core.client.selection.CellSelection;
import com.sencha.gxt.widget.core.client.tips.ToolTip;
import com.sencha.gxt.widget.core.client.tips.ToolTipConfig;
/**
* Cell based inline editing.
*
* @param <M> the model type
*/
public class GridInlineEditing<M> extends AbstractGridEditing<M> {
protected class GridEditingKeyNav extends AbstractGridEditingKeyNav {
@Override
public void onTab(NativeEvent evt) {
GridInlineEditing.this.onTab(evt);
}
}
protected static Logger logger = Logger.getLogger(GridInlineEditing.class.getName());
protected GroupingHandlerRegistration fieldRegistration = new GroupingHandlerRegistration();
protected boolean ignoreScroll;
protected boolean activeEdit;
protected boolean rowUpdated;
protected boolean ignoreNextEnter;
protected boolean focusOnComplete;
protected boolean revertInvalid = false;
public GridInlineEditing(Grid<M> editableGrid) {
setEditableGrid(editableGrid);
}
@Override
public void cancelEditing() {
ignoreScroll = false;
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("cancelEditing active is " + (activeCell == null ? "null" : "no null"));
}
if (activeCell != null) {
Element elem = getEditableGrid().getView().getCell(activeCell.getRow(), activeCell.getCol());
elem.getFirstChildElement().getStyle().setVisibility(Style.Visibility.VISIBLE);
cancelEditing(activeCell);
final GridCell gc = activeCell;
activeCell = null;
fireEvent(new CancelEditEvent<M>(gc));
if (focusOnComplete) {
focusOnComplete = false;
focusGrid();
// Focus of grid not working after canceling an edit in IE.
// something is stealing focus and the only fix so far is to run focus call in a timer. Deferred does not fix.
// Need to find why focus is not staying on first call.
if (GXT.isIE()) {
Timer t = new Timer() {
@Override
public void run() {
focusGrid();
}
};
t.schedule(100);
}
}
}
stopMonitoring();
}
protected void cancelEditing(GridCell activeCell) {
ColumnConfig<M, ?> columnConfig = columnModel.getColumn(activeCell.getCol());
final IsField<?> field = getEditor(columnConfig);
removeEditor(activeCell, field);
if (GXT.isiOS()) {
field.clear();
} else {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
field.clear();
}
});
}
}
@Override
public void completeEditing() {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("completeEditing active is " + (activeCell == null ? "null" : "no null"));
}
if (activeCell != null) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("completeEditing");
}
Element elem = getEditableGrid().getView().getCell(activeCell.getRow(), activeCell.getCol());
elem.getFirstChildElement().getStyle().setVisibility(Style.Visibility.VISIBLE);
doCompleteEditing();
}
stopMonitoring();
}
/**
* Returns {@code true} of the editor reverts the value to the start value on invalid.
*
* @return the revert invalid state
*/
public boolean isRevertInvalid() {
return revertInvalid;
}
/**
* True to automatically revert the field value and cancel the edit when the user completes an edit and the field
* validation fails (defaults to {@code false}).
*
* @param revertInvalid true to revert
*/
public void setRevertInvalid(boolean revertInvalid) {
this.revertInvalid = revertInvalid;
}
@Override
public void startEditing(final GridCell cell) {
if (getEditableGrid() != null && getEditableGrid().isAttached() && cell != null) {
ColumnConfig<M, ?> c = columnModel.getColumn(cell.getCol());
M value = getEditableGrid().getStore().get(cell.getRow());
// editable
if (value != null && getEditor(c) != null) {
BeforeStartEditEvent<M> ce = new BeforeStartEditEvent<M>(cell);
fireEvent(ce);
if (ce.isCancelled()) {
return;
}
if (getEditableGrid().getSelectionModel() instanceof CellSelectionModel) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("startEditing selectCell");
}
((CellSelectionModel<?>) getEditableGrid().getSelectionModel()).selectCell(cell.getRow(), cell.getCol());
}
Element elem = getEditableGrid().getView().getCell(cell.getRow(), cell.getCol());
elem.getFirstChildElement().getStyle().setVisibility(Style.Visibility.HIDDEN);
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("startEditing call cancelEditing, ignoreScroll true, ensure visible");
}
cancelEditing();
ignoreScroll = true;
getEditableGrid().getView().ensureVisible(cell.getRow(), cell.getCol(), true);
doStartEditing(cell);
}
}
}
@SuppressWarnings("unchecked")
protected <N, O> void doCompleteEditing() {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("doCompleteEditing activeCell is " + (activeCell != null ? " is not null" : "is null"));
}
if (activeCell != null) {
final ColumnConfig<M, N> c = columnModel.getColumn(activeCell.getCol());
IsField<O> field = getEditor(c);
if (field != null) {
Converter<N, O> converter = getConverter(c);
if (!field.isValid(false) && revertInvalid) {
cancelEditing();
return;
}
O fieldValue = null;
if (field instanceof ValueBaseField) {
fieldValue = ((ValueBaseField<O>) field).getCurrentValue();
} else {
fieldValue = field.getValue();
}
final N convertedValue;
if (converter != null) {
convertedValue = converter.convertFieldValue(fieldValue);
} else {
convertedValue = (N) fieldValue;
}
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("Converted value: " + convertedValue);
}
removeEditor(activeCell, field);
ListStore<M> store = getEditableGrid().getStore();
ListStore<M>.Record record = store.getRecord(store.get(activeCell.getRow()));
rowUpdated = true;
record.addChange(c.getValueProvider(), convertedValue);
fireEvent(new CompleteEditEvent<M>(activeCell));
if (focusOnComplete) {
focusOnComplete = false;
focusGrid();
}
}
activeCell = null;
}
}
protected void doFocus(IsWidget field) {
try {
Widget widget = field.asWidget();
if (widget instanceof Component) {
((Component) widget).focus();
} else if (widget instanceof Focusable) {
((Focusable) widget).setFocus(true);
} else {
widget.getElement().focus();
}
} catch (Exception e) {
// IE throws exception if element not focusable
}
}
@SuppressWarnings("unchecked")
protected <N, O> void doStartEditing(final GridCell cell) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("doStartEditing");
}
if (getEditableGrid() != null && getEditableGrid().isAttached() && cell != null) {
M value = getEditableGrid().getStore().get(cell.getRow());
ColumnConfig<M, N> columnConfig = columnModel.getColumn(cell.getCol());
if (columnConfig != null && value != null) {
Converter<N, O> converter = getConverter(columnConfig);
ValueProvider<? super M, N> valueProvider = columnConfig.getValueProvider();
N colValue = getEditableGrid().getStore().hasRecord(value)
? getEditableGrid().getStore().getRecord(value).getValue(valueProvider)
: valueProvider.getValue(value);
O convertedValue;
if (converter != null) {
convertedValue = converter.convertModelValue(colValue);
} else {
convertedValue = (O) colValue;
}
IsField<O> field = getEditor(columnConfig);
if (field != null) {
handleStartEditing(cell, columnConfig, field, convertedValue);
}
}
}
}
/**
* @since 4.0.3
*/
protected <N, O> void handleStartEditing(final GridCell cell, ColumnConfig<M, N> columnConfig, final IsField<O> field,
O convertedValue) {
if (field instanceof HasErrorHandler) {
((HasErrorHandler) field).setErrorSupport(null);
}
// Used with onTab
previousActiveCell = cell;
activeCell = cell;
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("doStartEditing convertedValue: " + convertedValue);
}
field.setValue(convertedValue);
if (field instanceof TriggerField<?>) {
((TriggerField<?>) field).setMonitorTab(false);
}
if (field instanceof CheckBox) {
((CheckBox) field).setBorders(true);
}
final Widget widget = field.asWidget();
getEditableGrid().getView().getEditorParent().appendChild(widget.getElement());
ComponentHelper.setParent(getEditableGrid(), widget);
ComponentHelper.doAttach(widget);
widget.getElement().<XElement>cast().makePositionable(true);
Element cellEl = getEditableGrid().getView().getCell(cell.getRow(), cell.getCol());
Element rowEl = getEditableGrid().getView().getRow(cell.getRow());
final int widgetWidth = cellEl.getOffsetWidth();
final int widgetHeight = cellEl.getOffsetHeight();
int left = 0;
for (int i = 0; i < cell.getCol(); i++) {
if (!columnModel.isHidden(i)) {
left += columnModel.getColumnWidth(i);
}
}
int top = rowEl.getAbsoluteTop() - getEditableGrid().getView().getBody().getAbsoluteTop();
widget.getElement().<XElement>cast().setLeftTop(left, top);
// If the widget has already been sized in a previous edit
// This is a workaround for changing the size after it's been set, this will trigger on widget.onResize
widget.setPixelSize(widgetWidth + 2, widgetHeight + 2);
widget.setPixelSize(widgetWidth, widgetHeight);
if (field instanceof CheckBox) {
int borderWidth = widget.getElement().<XElement>cast().getBorders(Side.TOP, Side.BOTTOM);
widget.getElement().<XElement>cast().setHeight(rowEl.getOffsetHeight() - borderWidth);
widget.getElement().<XElement>cast().getStyle().setLineHeight(rowEl.getOffsetHeight() - borderWidth, Unit.PX);
}
field.asWidget().setVisible(true);
startMonitoring();
if (GXT.isiOS()) {
// iOS can not have a timer in the loop. So focus will draw the keyboard.
deferHandleStartEditing(cell, field);
} else {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
deferHandleStartEditing(cell, field);
}
});
}
}
/**
* @since 4.0.3
*/
protected <N, O> void deferHandleStartEditing(final GridCell cell, final IsField<O> field) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("doStartEditing scheduleDeferred doFocus ");
}
// browsers select all when tabbing into a input and put cursor at location when clicking into an input
// with inline editing, the field is not visible at time of click so we select all. we ignore
// field.isSelectOnFocus as this only applies when clicking into a field
if (field instanceof ValueBaseField<?> && !GXT.isiOS()) {
ValueBaseField<?> valueBaseField = (ValueBaseField<?>) field;
valueBaseField.selectAll();
}
// Calling doFocus before selectAll is causing blur to fire which ends the edit immediately
// after it starts
doFocus(field);
ignoreScroll = false;
fieldRegistration.removeHandler();
fieldRegistration.add(field.addValueChangeHandler(new ValueChangeHandler<O>() {
@Override
public void onValueChange(ValueChangeEvent<O> event) {
onEditingValueChange(cell, field, event);
}
}));
fieldRegistration.add(field.addBlurHandler(new BlurHandler() {
@Override
public void onBlur(final BlurEvent event) {
onEditingBlur(cell, field, event);
}
}));
fireEvent(new StartEditEvent<M>(cell));
afterStartEdit(field);
}
/**
* @since 4.0.3
*/
protected <N, O> void onEditingValueChange(GridCell cell, IsField<O> field, ValueChangeEvent<O> event) {
// if enter key cause value change we want to ignore the next enter key otherwise
// new edit will start by onEnter
ignoreNextEnter = true;
Timer timer = new Timer() {
@Override
public void run() {
ignoreNextEnter = false;
}
};
completeEditing();
timer.schedule(100);
}
/**
* @since 4.0.3
*/
protected <N, O> void onEditingBlur(GridCell cell, IsField<O> field, BlurEvent event) {
if (field instanceof CheckBox) {
ignoreNextEnter = true;
Timer timer = new Timer() {
@Override
public void run() {
ignoreNextEnter = false;
cancelEditing();
}
};
timer.schedule(100);
} else {
ignoreNextEnter = true;
Timer timer = new Timer() {
@Override
public void run() {
ignoreNextEnter = false;
}
};
cancelEditing();
timer.schedule(100);
}
}
protected void afterStartEdit(IsField<?> field) {
}
@Override
protected KeyNav ensureInternalKeyNav() {
if (keyNav == null) {
keyNav = new GridEditingKeyNav();
}
return keyNav;
}
@Override
protected SafeHtml getErrorHtml() {
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant("<ul>");
ColumnConfig<M, ?> c = columnModel.getColumn(activeCell.getCol());
IsField<?> f = getEditor(c);
getErrorMessage(f, sb, c.getHeader());
sb.appendHtmlConstant("</ul>");
return sb.toSafeHtml();
}
@Override
protected <N, O> void handleHeaderMouseDown(HeaderMouseDownEvent event) {
if (activeCell != null) {
final ColumnConfig<M, N> c = columnModel.getColumn(activeCell.getCol());
IsField<O> field = getEditor(c);
// Rather than calling completeEditing directly, have the field
// finish editing which will cause completeEditing to be called in the correct sequence
field.finishEditing();
}
}
@Override
protected boolean isValid() {
if (activeCell == null) {
return true;
}
ColumnConfig<M, ?> c = columnModel.getColumn(activeCell.getCol());
IsWidget w = getEditor(c);
if (w instanceof ValueBaseField<?>) {
ValueBaseField<?> f = (ValueBaseField<?>) w;
if (!f.isCurrentValid(true)) {
return false;
}
} else if (w instanceof Field<?>) {
Field<?> f = (Field<?>) w;
if (!f.isValid(true)) {
return false;
}
}
return true;
}
@Override
protected void onEnter(NativeEvent evt) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onEnter");
}
if (ignoreNextEnter) {
ignoreNextEnter = false;
focusGrid();
return;
}
// enter key with no value changed fired
if (activeCell != null) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onEnter activeCell not null (enter key no value change), cancel edit");
}
ColumnConfig<M, ?> c = columnModel.getColumn(activeCell.getCol());
IsField<?> f = getEditor(c);
if (f instanceof TextArea) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onEnter editor type TextArea so ignoring");
}
return;
}
focusOnComplete = true;
cancelEditing();
return;
}
GridSelectionModel<M> sm = getEditableGrid().getSelectionModel();
if (sm instanceof CellSelectionModel) {
CellSelection<M> cell = ((CellSelectionModel<M>) sm).getSelectCell();
if (cell != null) {
evt.preventDefault();
startEditing(new GridCell(cell.getRow(), cell.getCell()));
}
}
}
@Override
protected void onEsc(NativeEvent evt) {
if (activeCell != null) {
focusOnComplete = true;
super.onEsc(evt);
}
}
@Override
protected void onMouseDown(MouseDownEvent event) {
turnOffTextSelectionInDraggable(event);
// do we have an active edit at time of mouse down
activeEdit = activeCell != null;
rowUpdated = false;
}
/**
* Turn off text selection in draggable.
*
* @param event The mouse down event.
* @since 4.0.4
*/
protected void turnOffTextSelectionInDraggable(MouseDownEvent event) {
XElement target = event.getNativeEvent().getEventTarget().cast();
// This will prevent text selection.
// See Draggable mouse down handler.
// At this time there is no way to support both dnd and text selection
// And the reason for that, if not ignored, then a blur will not happen, and cause all kinds of side effects.
if (target != null) {
target.setPropertyBoolean("ignoreTextSelection", true);
}
}
@Override
protected void onMouseUp(MouseUpEvent event) {
// there was an active edit on mouse down and that edit has ended
// we do not get a "click" event if the previous edit caused the row to be updated
if (getClicksToEdit() == ClicksToEdit.ONE && activeEdit && rowUpdated && activeCell == null) {
Element target = event.getNativeEvent().getEventTarget().cast();
startEditing(target);
}
activeEdit = false;
rowUpdated = false;
}
@Override
protected void onScroll(ScrollEvent event) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onScroll ignoreScroll " + (ignoreScroll ? "true" : "false calling cancelEditing"));
}
if (!ignoreScroll) {
cancelEditing();
}
}
protected void onTab(NativeEvent event) {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onTab");
}
// Start by finish editing on the previous cell to start with
if (previousActiveCell != null) {
ColumnConfig<M, ?> columnConfig = columnModel.getColumn(previousActiveCell.getCol());
IsField<?> field = getEditor(columnConfig);
event.preventDefault();
field.finishEditing();
}
// Get rid of listening to blur and change events from the previous editing
fieldRegistration.removeHandler();
// Keep active cell since we manually fire blur (finishEditing) which will call cancel edit
// clearing active cell
final GridCell active = previousActiveCell;
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onTab activeCell is " + (activeCell == null ? "null" : "not null"));
}
if (activeCell != null) {
ColumnConfig<M, ?> columnConfig = columnModel.getColumn(activeCell.getCol());
IsField<?> field = getEditor(columnConfig);
// We handle navigation programmatically
event.preventDefault();
// Since we are preventingDefault on tab key, the field will not blur on its
// own, which means the value change event will not fire so we manually blur
// the field, so we call finishEditing
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onTab calling field.finishEditing()");
}
field.finishEditing();
}
if (active != null) {
GridCell newCell = null;
if (event.getShiftKey()) {
newCell = getEditableGrid().walkCells(active.getRow(), active.getCol() - 1, -1, callback);
} else {
newCell = getEditableGrid().walkCells(active.getRow(), active.getCol() + 1, 1, callback);
}
if (newCell != null) {
final GridCell gridCell = newCell;
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest("onTab scheduleFinally startEditing");
}
startEditing(gridCell);
}
});
} else {
// when tabbing and no next cell to start edit, current edit is not ending
// the focusCell call is not causing field to blur and finish editing
if (isEditing()) {
completeEditing();
}
getEditableGrid().getView().focusCell(active.getRow(), active.getCol(), true);
}
}
}
@Override
protected void showTooltip(SafeHtml msg) {
if (activeCell == null) {
return;
}
ColumnConfig<M, ?> c = columnModel.getColumn(activeCell.getCol());
IsField<?> f = getEditor(c);
if (tooltip == null) {
ToolTipConfig config = new ToolTipConfig();
config.setAutoHide(false);
config.setMouseOffsetX(0);
config.setMouseOffsetY(0);
config.setAnchor(Side.LEFT);
tooltip = new ToolTip(f.asWidget(), config);
tooltip.setMaxWidth(600);
}
tooltip.initTarget(f.asWidget());
ToolTipConfig config = tooltip.getToolTipConfig();
config.setBody(msg);
tooltip.update(config);
tooltip.enable();
if (!tooltip.isAttached()) {
tooltip.show();
}
}
protected void removeEditor(final GridCell cell, final IsField<?> field) {
assert field != null;
removeFieldBlurHandler();
if (GXT.isIE() && field instanceof ValueBaseField<?>) {
ValueBaseField<?> valueBaseField = (ValueBaseField<?>) field;
valueBaseField.getCell().getInputElement(valueBaseField.getElement()).blur();
}
Widget widget = field.asWidget();
if (field != null && widget.isAttached()) {
field.asWidget().setVisible(false);
ComponentHelper.setParent(null, widget);
ComponentHelper.doDetach(widget);
}
}
protected void removeFieldBlurHandler() {
fieldRegistration.removeHandler();
}
}
@branflake2267
Copy link
Author

To use this workaround for Grid inline editing and DND.

  • Copy these classes to your source folder with the same package name.
  • These classes will override the provided jar classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment