package com.vaadin.terminal.gwt.client.ui;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.HasScrollHandlers;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.terminal.gwt.client.BrowserInfo;
/**
* A scrollhandlers similar to {@link ScrollPanel}.
*
*/
public class FocusableScrollPanel extends SimpleFocusablePanel implements
- HasScrollHandlers {
+ HasScrollHandlers, ScrollHandler {
public FocusableScrollPanel() {
// Prevent IE standard mode bug when a AbsolutePanel is contained.
style.setPosition(Position.RELATIVE);
}
+ private DivElement focusElement;
+
+ public FocusableScrollPanel(boolean useFakeFocusElement) {
+ this();
+ if (useFakeFocusElement) {
+ focusElement = Document.get().createDivElement();
+ }
+ }
+
+ private boolean useFakeFocusElement() {
+ return focusElement != null;
+ }
+
+ @Override
+ public void setWidget(Widget w) {
+ super.setWidget(w);
+ if (useFakeFocusElement()) {
+ if (focusElement.getParentElement() == null) {
+ Style style = focusElement.getStyle();
+ if (BrowserInfo.get().isIE6()) {
+ style.setOverflow(Overflow.HIDDEN);
+ style.setHeight(0, Unit.PX);
+ style.setWidth(0, Unit.PX);
+ style.setPosition(Position.ABSOLUTE);
+
+ addScrollHandler(this);
+ } else {
+ style.setPosition(Position.FIXED);
+ style.setTop(0, Unit.PX);
+ style.setLeft(0, Unit.PX);
+ }
+ getElement().appendChild(focusElement);
+ /* Sink from focusElemet too as focusa and blur don't bubble */
+ DOM.sinkEvents(
+ (com.google.gwt.user.client.Element) focusElement
+ .cast(), Event.FOCUSEVENTS);
+ // revert to original, not focusable
+ getElement().setPropertyObject("tabIndex", null);
+
+ } else {
+ moveFocusElementAfterWidget();
+ }
+ }
+ }
+
+ /**
+ * Helper to keep focus element always in domChild[1]. Aids testing.
+ */
+ private void moveFocusElementAfterWidget() {
+ getElement().insertAfter(focusElement, getWidget().getElement());
+ }
+
+ @Override
+ public void setFocus(boolean focus) {
+ if (useFakeFocusElement()) {
+ if (focus) {
+ FocusImpl.getFocusImplForPanel().focus(
+ (Element) focusElement.cast());
+ } else {
+ FocusImpl.getFocusImplForPanel().blur(
+ (Element) focusElement.cast());
+ }
+ } else {
+ super.setFocus(focus);
+ }
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ if (useFakeFocusElement()) {
+ getElement().setTabIndex(-1);
+ if (focusElement != null) {
+ focusElement.setTabIndex(tabIndex);
+ }
+ } else {
+ super.setTabIndex(tabIndex);
+ }
+ }
+
public HandlerRegistration addScrollHandler(ScrollHandler handler) {
return addDomHandler(handler, ScrollEvent.getType());
}
getElement().setScrollTop(position);
}
+ public void onScroll(ScrollEvent event) {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ focusElement.getStyle().setTop(getScrollPosition(), Unit.PX);
+ focusElement.getStyle().setLeft(getHorizontalScrollPosition(),
+ Unit.PX);
+ }
+ });
+ }
+
}
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.NodeList;
* TODO implement unregistering for child components in Cells
*/
public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
- VHasDropHandler, KeyPressHandler, KeyDownHandler, FocusHandler,
- BlurHandler, Focusable, KeyUpHandler {
+ VHasDropHandler, FocusHandler, BlurHandler, Focusable {
public static final String CLASSNAME = "v-table";
public static final String CLASSNAME_SELECTION_FOCUS = CLASSNAME + "-focus";
private final TableFooter tFoot = new TableFooter();
- private final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel();
+ private final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(
+ true);
+ private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
+ public void onKeyPress(KeyPressEvent keyPressEvent) {
+ // This is used for Firefox only, since Firefox auto-repeat
+ // works correctly only if we use a key press handler, other
+ // browsers handle it correctly when using a key down handler
+ if (!BrowserInfo.get().isGecko()) {
+ return;
+ }
+
+ NativeEvent event = keyPressEvent.getNativeEvent();
+ if (!enabled) {
+ // Cancel default keyboard events on a disabled Table
+ // (prevents scrolling)
+ event.preventDefault();
+ } else if (hasFocus) {
+ // Key code in Firefox/onKeyPress is present only for
+ // special keys, otherwise 0 is returned
+ int keyCode = event.getKeyCode();
+ if (keyCode == 0 && event.getCharCode() == ' ') {
+ // Provide a keyCode for space to be compatible with
+ // FireFox keypress event
+ keyCode = CHARCODE_SPACE;
+ }
+
+ if (handleNavigation(keyCode,
+ event.getCtrlKey() || event.getMetaKey(),
+ event.getShiftKey())) {
+ event.preventDefault();
+ }
+
+ startScrollingVelocityTimer();
+ }
+ }
+
+ };
+
+ private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
+
+ public void onKeyUp(KeyUpEvent keyUpEvent) {
+ NativeEvent event = keyUpEvent.getNativeEvent();
+ int keyCode = event.getKeyCode();
+
+ if (!isFocusable()) {
+ cancelScrollingVelocityTimer();
+ } else if (isNavigationKey(keyCode)) {
+ if (keyCode == getNavigationDownKey()
+ || keyCode == getNavigationUpKey()) {
+ /*
+ * in multiselect mode the server may still have value from
+ * previous page. Clear it unless doing multiselection or
+ * just moving focus.
+ */
+ if (!event.getShiftKey() && !event.getCtrlKey()) {
+ instructServerToForgetPreviousSelections();
+ }
+ sendSelectedRows();
+ }
+ cancelScrollingVelocityTimer();
+ navKeyDown = false;
+ }
+ }
+ };
+
+ private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
+
+ public void onKeyDown(KeyDownEvent keyDownEvent) {
+ NativeEvent event = keyDownEvent.getNativeEvent();
+ // This is not used for Firefox
+ if (BrowserInfo.get().isGecko()) {
+ return;
+ }
+
+ if (!enabled) {
+ // Cancel default keyboard events on a disabled Table
+ // (prevents scrolling)
+ event.preventDefault();
+ } else if (hasFocus) {
+ if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
+ || event.getMetaKey(), event.getShiftKey())) {
+ navKeyDown = true;
+ event.preventDefault();
+ }
+
+ startScrollingVelocityTimer();
+ }
+ }
+ };
private int totalRows;
private Set<String> collapsedColumns;
public VScrollTable() {
scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper");
+ scrollBodyPanel.addFocusHandler(this);
+ scrollBodyPanel.addBlurHandler(this);
+
+ scrollBodyPanel.addScrollHandler(this);
+ scrollBodyPanel.setStyleName(CLASSNAME + "-body");
/*
* Firefox auto-repeat works correctly only if we use a key press
* handler
*/
if (BrowserInfo.get().isGecko()) {
- scrollBodyPanel.addKeyPressHandler(this);
+ scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
} else {
- scrollBodyPanel.addKeyDownHandler(this);
+ scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
}
- scrollBodyPanel.addKeyUpHandler(this);
-
- scrollBodyPanel.addFocusHandler(this);
- scrollBodyPanel.addBlurHandler(this);
-
- scrollBodyPanel.addScrollHandler(this);
+ scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS);
scrollBodyPanel.addDomHandler(new TouchStartHandler() {
}
}, TouchStartEvent.getType());
- scrollBodyPanel.setStyleName(CLASSNAME + "-body");
-
setStyleName(CLASSNAME);
add(tHead);
if (focusedRow != null) {
if (!focusedRow.isAttached()) {
- // focused row has orphaned, can't focus
- focusedRow = null;
- if (SELECT_MODE_SINGLE == selectMode
- && selectedRowKeys.size() > 0) {
- // try to focusa row currently selected and in viewport
- String selectedRowKey = selectedRowKeys.iterator().next();
- if (selectedRowKey != null) {
- setRowFocus(getRenderedRowByKey(selectedRowKey));
- }
- }
- // TODO what should happen in multiselect mode?
+ // focused row has been orphaned, can't focus
+ focusRowFromBody();
}
}
}
+ private void focusRowFromBody() {
+ if (selectedRowKeys.size() == 1) {
+ // try to focus a row currently selected and in viewport
+ String selectedRowKey = selectedRowKeys.iterator().next();
+ if (selectedRowKey != null) {
+ VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey);
+ if (renderedRow == null || !renderedRow.isInViewPort()) {
+ setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
+ } else {
+ setRowFocus(renderedRow);
+ }
+ }
+ } else {
+ // multiselect mode
+ setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
+ }
+ }
+
protected VScrollTableBody createScrollBody() {
return new VScrollTableBody();
}
String colKey;
private boolean collapsed;
+ private VScrollTableRow currentlyFocusedRow;
public VisibleColumnAction(String colKey) {
super(VScrollTable.TableHead.this);
this.colKey = colKey;
caption = tHead.getHeaderCell(colKey).getCaption();
+ currentlyFocusedRow = focusedRow;
}
@Override
.size()]), false);
// let rowRequestHandler determine proper rows
rowRequestHandler.refreshContent();
+ lazyRevertFocusToRow(currentlyFocusedRow);
}
public void setCollapsed(boolean b) {
setElement(rowElement);
DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
| Event.TOUCHEVENTS | Event.ONDBLCLICK
- | Event.ONCONTEXTMENU | Event.ONKEYDOWN);
+ | Event.ONCONTEXTMENU);
+ }
+
+ /**
+ * Detects whether row is visible in tables viewport.
+ *
+ * @return
+ */
+ public boolean isInViewPort() {
+ int absoluteTop = getAbsoluteTop();
+ int scrollPosition = scrollBodyPanel.getScrollPosition();
+ if (absoluteTop < scrollPosition) {
+ return false;
+ }
+ int maxVisible = scrollPosition
+ + scrollBodyPanel.getOffsetHeight() - getOffsetHeight();
+ if (absoluteTop > maxVisible) {
+ return false;
+ }
+ return true;
}
/**
if (targetCellOrRowFound) {
mDown = false;
handleClickEvent(event, targetTdOrTr);
- scrollBodyPanel.setFocus(true);
if (event.getButton() == Event.BUTTON_LEFT
&& isSelectable()) {
case Event.ONMOUSEDOWN:
if (targetCellOrRowFound) {
setRowFocus(this);
+ ensureFocus();
if (dragmode != 0
&& (event.getButton() == NativeEvent.BUTTON_LEFT)) {
startRowDrag(event, type, targetTdOrTr);
&& selectMode == SELECT_MODE_MULTI
&& multiselectmode == MULTISELECT_MODE_DEFAULT) {
- // because we are preventing the default (due to
- // prevent text selection) we must ensure
- // gaining the focus.
- ensureFocus();
// Prevent default text selection in Firefox
event.preventDefault();
} else {
ev.createDragImage(getElement(), true);
}
- // because we are preventing the default (due to
- // prevent text selection) we must ensure
- // gaining the focus.
- ensureFocus();
if (type == Event.ONMOUSEDOWN) {
event.preventDefault();
}
for (int i = 0; i < actions.length; i++) {
final String actionKey = actionKeys[i];
final TreeAction a = new TreeAction(this,
- String.valueOf(rowKey), actionKey);
+ String.valueOf(rowKey), actionKey) {
+ @Override
+ public void execute() {
+ super.execute();
+ lazyRevertFocusToRow(VScrollTableRow.this);
+ }
+ };
a.setCaption(getActionCaption(actionKey));
a.setIconUrl(getActionIcon(actionKey));
actions[i] = a;
* focus only if not currently focused.
*/
protected void ensureFocus() {
- focus();
+ if (!hasFocus) {
+ scrollBodyPanel.setFocus(true);
+ }
}
}
selectedRowRanges.clear();
// also notify server that it clears all previous selections (the client
// side does not know about the invisible ones)
- instructServerToForgotPreviousSelections();
+ instructServerToForgetPreviousSelections();
}
/**
* Used in multiselect mode when the client side knows that all selections
* are in the next request.
*/
- private void instructServerToForgotPreviousSelections() {
+ private void instructServerToForgetPreviousSelections() {
client.updateVariable(paintableId, "clearSelections", true, false);
}
Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
}
});
+
+ if (BrowserInfo.get().isIE()) {
+ /*
+ * IE does not fire onscroll event if scroll position is
+ * reverted to 0 due to the content element size growth. Ensure
+ * headers are in sync with content manually. Safe to use null
+ * event as we don't actually use the event object in listener.
+ */
+ onScroll(null);
+ }
}
};
// Apply focus style to new selection
row.addStyleName(CLASSNAME_SELECTION_FOCUS);
- // Trying to set focus on already focused row
+ /*
+ * Trying to set focus on already focused row
+ */
if (row == focusedRow) {
return false;
}
scrollBodyPanel.setScrollPosition(newPixels);
}
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
- * .gwt.event.dom.client.KeyPressEvent)
- */
- public void onKeyPress(KeyPressEvent event) {
- // This is used for Firefox only
- if (!BrowserInfo.get().isGecko()) {
- return;
- }
-
- if (!enabled) {
- // Cancel default keyboard events on a disabled Table (prevents
- // scrolling)
- event.preventDefault();
- } else if (hasFocus) {
- // Key code in Firefox/onKeyPress is present only for special keys,
- // otherwise 0 is returned
- NativeEvent nativeEvent = event.getNativeEvent();
- int keyCode = nativeEvent.getKeyCode();
- if (keyCode == 0 && nativeEvent.getCharCode() == ' ') {
- // Provide a keyCode for space to be compatible with FireFox
- // keypress event
- keyCode = CHARCODE_SPACE;
- }
-
- if (handleNavigation(keyCode,
- event.isControlKeyDown() || event.isMetaKeyDown(),
- event.isShiftKeyDown())) {
- event.preventDefault();
- }
-
- // Start the velocityTimer
- if (scrollingVelocityTimer == null) {
- scrollingVelocityTimer = new Timer() {
- @Override
- public void run() {
- scrollingVelocity++;
- }
- };
- scrollingVelocityTimer.scheduleRepeating(100);
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
- * .event.dom.client.KeyDownEvent)
- */
- public void onKeyDown(KeyDownEvent event) {
- if (!enabled) {
- // Cancel default keyboard events on a disabled Table (prevents
- // scrolling)
- event.preventDefault();
- } else if (hasFocus) {
- if (handleNavigation(event.getNativeEvent().getKeyCode(),
- event.isControlKeyDown() || event.isMetaKeyDown(),
- event.isShiftKeyDown())) {
- navKeyDown = true;
- event.preventDefault();
- }
-
- // Start the velocityTimer
- if (scrollingVelocityTimer == null) {
- scrollingVelocityTimer = new Timer() {
- @Override
- public void run() {
- scrollingVelocity++;
- }
- };
- scrollingVelocityTimer.scheduleRepeating(100);
- }
- }
- }
-
/*
* (non-Javadoc)
*
*/
public void onFocus(FocusEvent event) {
if (isFocusable()) {
- scrollBodyPanel.addStyleName("focused");
hasFocus = true;
// Focus a row if no row is in focus
if (focusedRow == null) {
- setRowFocus((VScrollTableRow) scrollBody.iterator().next());
+ focusRowFromBody();
} else {
setRowFocus(focusedRow);
}
* .dom.client.BlurEvent)
*/
public void onBlur(BlurEvent event) {
- scrollBodyPanel.removeStyleName("focused");
hasFocus = false;
navKeyDown = false;
}
if (tabIndex == 0 && !isFocusable()) {
- scrollBodyPanel.getElement().setTabIndex(-1);
+ scrollBodyPanel.setTabIndex(-1);
} else {
- scrollBodyPanel.getElement().setTabIndex(tabIndex);
+ scrollBodyPanel.setTabIndex(tabIndex);
}
if (BrowserInfo.get().getOperaVersion() >= 11) {
}
}
- public void onKeyUp(KeyUpEvent event) {
- int keyCode = event.getNativeKeyCode();
-
- if (!isFocusable()) {
- if (scrollingVelocityTimer != null) {
- // Remove velocityTimer if it exists and the Table is disabled
- scrollingVelocityTimer.cancel();
- scrollingVelocityTimer = null;
- scrollingVelocity = 10;
- }
- } else if (isNavigationKey(keyCode)) {
- if (keyCode == getNavigationDownKey()
- || keyCode == getNavigationUpKey()) {
- /*
- * in multiselect mode the server may still have value from
- * previous page. Clear it unless doing multiselection or just
- * moving focus.
- */
- if (!event.getNativeEvent().getShiftKey()
- && !event.getNativeEvent().getCtrlKey()) {
- instructServerToForgotPreviousSelections();
+ public void startScrollingVelocityTimer() {
+ if (scrollingVelocityTimer == null) {
+ scrollingVelocityTimer = new Timer() {
+ @Override
+ public void run() {
+ scrollingVelocity++;
}
- sendSelectedRows();
- }
- if (scrollingVelocityTimer != null) {
- scrollingVelocityTimer.cancel();
- scrollingVelocityTimer = null;
- scrollingVelocity = 10;
- }
- navKeyDown = false;
+ };
+ scrollingVelocityTimer.scheduleRepeating(100);
+ }
+ }
+
+ public void cancelScrollingVelocityTimer() {
+ if (scrollingVelocityTimer != null) {
+ // Remove velocityTimer if it exists and the Table is disabled
+ scrollingVelocityTimer.cancel();
+ scrollingVelocityTimer = null;
+ scrollingVelocity = 10;
}
}
|| keyCode == getNavigationEndKey()
|| keyCode == getNavigationStartKey();
}
+
+ public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) {
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+ public void execute() {
+ if (currentlyFocusedRow != null) {
+ setRowFocus(currentlyFocusedRow);
+ } else {
+ VConsole.log("no row?");
+ focusRowFromBody();
+ }
+ scrollBody.ensureFocus();
+ }
+ });
+ }
}