com.vaadin.client.widget.escalator.ScrollbarBundle Maven / Gradle / Ivy
Show all versions of vaadin-client Show documentation
/*
* 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.client.widget.escalator;
import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.animation.client.AnimationScheduler.AnimationSupportDetector;
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.Style.Display;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.shared.EventHandler;
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.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Timer;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.DeferredWorker;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.widget.grid.events.ScrollEvent;
import com.vaadin.client.widget.grid.events.ScrollHandler;
/**
* An element-like bundle representing a configurable and visual scrollbar in
* one axis.
*
* @since 7.4
* @author Vaadin Ltd
* @see VerticalScrollbarBundle
* @see HorizontalScrollbarBundle
*/
public abstract class ScrollbarBundle implements DeferredWorker {
private static final boolean supportsRequestAnimationFrame = new AnimationSupportDetector()
.isNativelySupported();
private class ScrollEventFirer {
private final ScheduledCommand fireEventCommand = new ScheduledCommand() {
@Override
public void execute() {
/*
* Some kind of native-scroll-event related asynchronous problem
* occurs here (at least on desktops) where the internal
* bookkeeping isn't up to date with the real scroll position.
* The weird thing is, that happens only once, and if you drag
* scrollbar fast enough. After it has failed once, it never
* fails again.
*
* Theory: the user drags the scrollbar, and this command is
* executed before the browser has a chance to fire a scroll
* event (which normally would correct this situation). This
* would explain why slow scrolling doesn't trigger the problem,
* while fast scrolling does.
*
* To make absolutely sure that we have the latest scroll
* position, let's update the internal value.
*
* This might lead to a slight performance hit (on my computer
* it was never more than 3ms on either of Chrome 38 or Firefox
* 31). It also _slightly_ counteracts the purpose of the
* internal bookkeeping. But since getScrollPos is called 3
* times (on one direction) per scroll loop, it's still better
* to have take this small penalty than removing it altogether.
*/
updateScrollPosFromDom();
getHandlerManager().fireEvent(new ScrollEvent());
isBeingFired = false;
}
};
private boolean isBeingFired;
public void scheduleEvent() {
if (!isBeingFired) {
/*
* We'll gather all the scroll events, and only fire once, once
* everything has calmed down.
*/
if (supportsRequestAnimationFrame) {
// Chrome MUST use this as deferred commands will sometimes
// be run with a 300+ ms delay when scrolling.
AnimationScheduler.get().requestAnimationFrame(
timestamp -> fireEventCommand.execute());
} else {
// Does not support requestAnimationFrame and the fallback
// uses a delay of 16ms, we stick to the old deferred
// command which uses a delay of 0ms
Scheduler.get().scheduleDeferred(fireEventCommand);
}
isBeingFired = true;
}
}
}
/**
* The orientation of the scrollbar.
*/
public enum Direction {
VERTICAL, HORIZONTAL;
}
private class TemporaryResizer {
private static final int TEMPORARY_RESIZE_DELAY = 1000;
private final Timer timer = new Timer() {
@Override
public void run() {
internalSetScrollbarThickness(1);
root.getStyle().setVisibility(Visibility.HIDDEN);
}
};
public void show() {
internalSetScrollbarThickness(OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX);
root.getStyle().setVisibility(Visibility.VISIBLE);
timer.schedule(TEMPORARY_RESIZE_DELAY);
}
}
/**
* A means to listen to when the scrollbar handle in a
* {@link ScrollbarBundle} either appears or is removed.
*/
public interface VisibilityHandler extends EventHandler {
/**
* This method is called whenever the scrollbar handle's visibility is
* changed in a {@link ScrollbarBundle}.
*
* @param event
* the {@link VisibilityChangeEvent}
*/
void visibilityChanged(VisibilityChangeEvent event);
}
public static class VisibilityChangeEvent
extends GwtEvent {
public static final Type TYPE = new Type() {
@Override
public String toString() {
return "VisibilityChangeEvent";
}
};
private final boolean isScrollerVisible;
private VisibilityChangeEvent(boolean isScrollerVisible) {
this.isScrollerVisible = isScrollerVisible;
}
/**
* Checks whether the scroll handle is currently visible or not
*
* @return true
if the scroll handle is currently visible.
* false
if not.
*/
public boolean isScrollerVisible() {
return isScrollerVisible;
}
@Override
public Type getAssociatedType() {
return TYPE;
}
@Override
protected void dispatch(VisibilityHandler handler) {
handler.visibilityChanged(this);
}
}
/**
* The pixel size for OSX's invisible scrollbars.
*
* Touch devices don't show a scrollbar at all, so the scrollbar size is
* irrelevant in their case. There doesn't seem to be any other popular
* platforms that has scrollbars similar to OSX. Thus, this behavior is
* tailored for OSX only, until additional platforms start behaving this
* way.
*/
private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13;
/**
* A representation of a single vertical scrollbar.
*
* @see VerticalScrollbarBundle#getElement()
*/
public final static class VerticalScrollbarBundle extends ScrollbarBundle {
@Override
public void setStylePrimaryName(String primaryStyleName) {
super.setStylePrimaryName(primaryStyleName);
root.addClassName(primaryStyleName + "-scroller-vertical");
}
@Override
protected void internalSetScrollPos(int px) {
root.setScrollTop(px);
}
@Override
protected int internalGetScrollPos() {
return root.getScrollTop();
}
@Override
protected void internalSetScrollSize(double px) {
scrollSizeElement.getStyle().setHeight(px, Unit.PX);
}
@Override
protected String internalGetScrollSize() {
return scrollSizeElement.getStyle().getHeight();
}
@Override
protected void internalSetOffsetSize(double px) {
root.getStyle().setHeight(px, Unit.PX);
}
@Override
public String internalGetOffsetSize() {
return root.getStyle().getHeight();
}
@Override
protected void internalSetScrollbarThickness(double px) {
root.getStyle().setPaddingRight(px, Unit.PX);
root.getStyle().setWidth(0, Unit.PX);
scrollSizeElement.getStyle().setWidth(px, Unit.PX);
}
@Override
protected String internalGetScrollbarThickness() {
return scrollSizeElement.getStyle().getWidth();
}
@Override
protected void internalForceScrollbar(boolean enable) {
if (enable) {
root.getStyle().setOverflowY(Overflow.SCROLL);
} else {
root.getStyle().clearOverflowY();
}
}
@Override
public Direction getDirection() {
return Direction.VERTICAL;
}
}
/**
* A representation of a single horizontal scrollbar.
*
* @see HorizontalScrollbarBundle#getElement()
*/
public final static class HorizontalScrollbarBundle
extends ScrollbarBundle {
@Override
public void setStylePrimaryName(String primaryStyleName) {
super.setStylePrimaryName(primaryStyleName);
root.addClassName(primaryStyleName + "-scroller-horizontal");
}
@Override
protected void internalSetScrollPos(int px) {
root.setScrollLeft(px);
}
@Override
protected int internalGetScrollPos() {
return root.getScrollLeft();
}
@Override
protected void internalSetScrollSize(double px) {
scrollSizeElement.getStyle().setWidth(px, Unit.PX);
}
@Override
protected String internalGetScrollSize() {
return scrollSizeElement.getStyle().getWidth();
}
@Override
protected void internalSetOffsetSize(double px) {
root.getStyle().setWidth(px, Unit.PX);
}
@Override
public String internalGetOffsetSize() {
return root.getStyle().getWidth();
}
@Override
protected void internalSetScrollbarThickness(double px) {
root.getStyle().setPaddingBottom(px, Unit.PX);
root.getStyle().setHeight(0, Unit.PX);
scrollSizeElement.getStyle().setHeight(px, Unit.PX);
}
@Override
protected String internalGetScrollbarThickness() {
return scrollSizeElement.getStyle().getHeight();
}
@Override
protected void internalForceScrollbar(boolean enable) {
if (enable) {
root.getStyle().setOverflowX(Overflow.SCROLL);
} else {
root.getStyle().clearOverflowX();
}
}
@Override
public Direction getDirection() {
return Direction.HORIZONTAL;
}
}
protected final Element root = DOM.createDiv();
protected final Element scrollSizeElement = DOM.createDiv();
protected boolean isInvisibleScrollbar = false;
private double scrollPos = 0;
private double maxScrollPos = 0;
private boolean scrollHandleIsVisible = false;
private boolean isLocked = false;
/** @deprecated access via {@link #getHandlerManager()} instead. */
@Deprecated
private HandlerManager handlerManager;
private TemporaryResizer invisibleScrollbarTemporaryResizer = new TemporaryResizer();
private final ScrollEventFirer scrollEventFirer = new ScrollEventFirer();
private HandlerRegistration scrollSizeTemporaryScrollHandler;
private HandlerRegistration offsetSizeTemporaryScrollHandler;
private HandlerRegistration scrollInProgress;
private ScrollbarBundle() {
root.appendChild(scrollSizeElement);
root.getStyle().setDisplay(Display.NONE);
root.setTabIndex(-1);
}
protected abstract String internalGetScrollSize();
/**
* Sets the primary style name
*
* @param primaryStyleName
* The primary style name to use
*/
public void setStylePrimaryName(String primaryStyleName) {
root.setClassName(primaryStyleName + "-scroller");
}
/**
* Gets the root element of this scrollbar-composition.
*
* @return the root element
*/
public final Element getElement() {
return root;
}
/**
* Modifies the scroll position of this scrollbar by a number of pixels.
*
* Note: Even though {@code double} values are used, they are
* currently only used as integers as large {@code int} (or small but fast
* {@code long}). This means, all values are truncated to zero decimal
* places.
*
* @param delta
* the delta in pixels to change the scroll position by
*/
public final void setScrollPosByDelta(double delta) {
if (delta != 0) {
setScrollPos(getScrollPos() + delta);
}
}
/**
* Modifies {@link #root root's} dimensions in the axis the scrollbar is
* representing.
*
* @param px
* the new size of {@link #root} in the dimension this scrollbar
* is representing
*/
protected abstract void internalSetOffsetSize(double px);
/**
* Sets the length of the scrollbar.
*
* @param px
* the length of the scrollbar in pixels
*/
public final void setOffsetSize(final double px) {
/*
* This needs to be made step-by-step because IE8 flat-out refuses to
* fire a scroll event when the scroll size becomes smaller than the
* offset size. All other browser need to suffer alongside.
*/
boolean newOffsetSizeIsGreaterThanScrollSize = px > getScrollSize();
boolean offsetSizeBecomesGreaterThanScrollSize = showsScrollHandle()
&& newOffsetSizeIsGreaterThanScrollSize;
if (offsetSizeBecomesGreaterThanScrollSize && getScrollPos() != 0) {
if (offsetSizeTemporaryScrollHandler != null) {
offsetSizeTemporaryScrollHandler.removeHandler();
}
// must be a field because Java insists.
offsetSizeTemporaryScrollHandler = addScrollHandler(
new ScrollHandler() {
@Override
public void onScroll(ScrollEvent event) {
setOffsetSizeNow(px);
}
});
setScrollPos(0);
} else {
setOffsetSizeNow(px);
}
}
private void setOffsetSizeNow(double px) {
internalSetOffsetSize(Math.max(0, px));
recalculateMaxScrollPos();
forceScrollbar(showsScrollHandle());
fireVisibilityChangeIfNeeded();
if (offsetSizeTemporaryScrollHandler != null) {
offsetSizeTemporaryScrollHandler.removeHandler();
offsetSizeTemporaryScrollHandler = null;
}
}
/**
* Force the scrollbar to be visible with CSS. In practice, this means to
* set either overflow-x
or overflow-y
to "
* scroll
" in the scrollbar's direction.
*
* This method is an IE8 workaround, since it doesn't always show scrollbars
* with overflow: auto
enabled.
*
* Firefox on the other hand loses pending scroll events when the scrollbar
* is hidden, so the event must be fired manually.
*
* When IE8 support is dropped, this should really be simplified.
*/
protected void forceScrollbar(boolean enable) {
if (enable) {
root.getStyle().clearDisplay();
} else {
if (BrowserInfo.get().isFirefox()) {
/*
* This is related to the Firefox workaround in setScrollSize
* for setScrollPos(0)
*/
scrollEventFirer.scheduleEvent();
}
root.getStyle().setDisplay(Display.NONE);
}
internalForceScrollbar(enable);
}
protected abstract void internalForceScrollbar(boolean enable);
/**
* Gets the length of the scrollbar
*
* @return the length of the scrollbar in pixels
*/
public double getOffsetSize() {
return parseCssDimensionToPixels(internalGetOffsetSize());
}
public abstract String internalGetOffsetSize();
/**
* Sets the scroll position of the scrollbar in the axis the scrollbar is
* representing.
*
* Note: Even though {@code double} values are used, they are
* currently only used as integers as large {@code int} (or small but fast
* {@code long}). This means, all values are truncated to zero decimal
* places.
*
* @param px
* the new scroll position in pixels
*/
public final void setScrollPos(double px) {
if (isLocked()) {
return;
}
double oldScrollPos = scrollPos;
scrollPos = Math.max(0, Math.min(maxScrollPos, truncate(px)));
if (!WidgetUtil.pixelValuesEqual(oldScrollPos, scrollPos)) {
if (scrollInProgress == null) {
// Only used for tracking that there is "workPending"
scrollInProgress = addScrollHandler(new ScrollHandler() {
@Override
public void onScroll(ScrollEvent event) {
scrollInProgress.removeHandler();
scrollInProgress = null;
}
});
}
if (isInvisibleScrollbar) {
invisibleScrollbarTemporaryResizer.show();
}
/*
* This is where the value needs to be converted into an integer no
* matter how we flip it, since GWT expects an integer value.
* There's no point making a JSNI method that accepts doubles as the
* scroll position, since the browsers themselves don't support such
* large numbers (as of today, 25.3.2014). This double-ranged is
* only facilitating future virtual scrollbars.
*/
internalSetScrollPos(toInt32(scrollPos));
}
}
/**
* Should be called whenever this bundle is attached to the DOM (typically,
* from the onLoad of the containing widget). Used to ensure the DOM scroll
* position is maintained when detaching and reattaching the bundle.
*
* @since 7.4.1
*/
public void onLoad() {
internalSetScrollPos(toInt32(scrollPos));
}
/**
* Truncates a double such that no decimal places are retained.
*
* E.g. {@code trunc(2.3d) == 2.0d} and {@code trunc(-2.3d) == -2.0d}.
*
* @param num
* the double value to be truncated
* @return the {@code num} value without any decimal digits
*/
private static double truncate(double num) {
if (num > 0) {
return Math.floor(num);
} else {
return Math.ceil(num);
}
}
/**
* Modifies the element's scroll position (scrollTop or scrollLeft).
*
* Note: The parameter here is a type of integer (instead of a
* double) by design. The browsers internally convert all double values into
* an integer value. To make this fact explicit, this API has chosen to
* force integers already at this level.
*
* @param px
* integer pixel value to scroll to
*/
protected abstract void internalSetScrollPos(int px);
/**
* Gets the scroll position of the scrollbar in the axis the scrollbar is
* representing.
*
* @return the new scroll position in pixels
*/
public final double getScrollPos() {
assert internalGetScrollPos() == toInt32(
scrollPos) : "calculated scroll position (" + scrollPos
+ ") did not match the DOM element scroll position ("
+ internalGetScrollPos() + ")";
return scrollPos;
}
/**
* Retrieves the element's scroll position (scrollTop or scrollLeft).
*
* Note: The parameter here is a type of integer (instead of a
* double) by design. The browsers internally convert all double values into
* an integer value. To make this fact explicit, this API has chosen to
* force integers already at this level.
*
* @return integer pixel value of the scroll position
*/
protected abstract int internalGetScrollPos();
/**
* Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in
* such a way that the scrollbar is able to scroll a certain number of
* pixels in the axis it is representing.
*
* @param px
* the new size of {@link #scrollSizeElement} in the dimension
* this scrollbar is representing
*/
protected abstract void internalSetScrollSize(double px);
/**
* Sets the amount of pixels the scrollbar needs to be able to scroll
* through.
*
* @param px
* the number of pixels the scrollbar should be able to scroll
* through
*/
public final void setScrollSize(final double px) {
/*
* This needs to be made step-by-step because IE8 flat-out refuses to
* fire a scroll event when the scroll size becomes smaller than the
* offset size. All other browser need to suffer alongside.
*
* This really should be changed to not use any temporary scroll
* handlers at all once IE8 support is dropped, like now done only for
* Firefox.
*/
boolean newScrollSizeIsSmallerThanOffsetSize = px <= getOffsetSize();
boolean scrollSizeBecomesSmallerThanOffsetSize = showsScrollHandle()
&& newScrollSizeIsSmallerThanOffsetSize;
if (scrollSizeBecomesSmallerThanOffsetSize && getScrollPos() != 0) {
/*
* For whatever reason, Firefox loses the scroll event in this case
* and the onscroll handler is never called (happens when reducing
* size from 1000 items to 1 while being scrolled a bit down, see
* #19802). Based on the comment above, only IE8 should really use
* 'delayedSizeSet'
*/
boolean delayedSizeSet = !BrowserInfo.get().isFirefox();
if (delayedSizeSet) {
if (scrollSizeTemporaryScrollHandler != null) {
scrollSizeTemporaryScrollHandler.removeHandler();
}
scrollSizeTemporaryScrollHandler = addScrollHandler(
new ScrollHandler() {
@Override
public void onScroll(ScrollEvent event) {
setScrollSizeNow(px);
}
});
}
setScrollPos(0);
if (!delayedSizeSet) {
setScrollSizeNow(px);
}
} else {
setScrollSizeNow(px);
}
}
private void setScrollSizeNow(double px) {
internalSetScrollSize(Math.max(0, px));
recalculateMaxScrollPos();
forceScrollbar(showsScrollHandle());
fireVisibilityChangeIfNeeded();
if (scrollSizeTemporaryScrollHandler != null) {
scrollSizeTemporaryScrollHandler.removeHandler();
scrollSizeTemporaryScrollHandler = null;
}
}
/**
* Gets the amount of pixels the scrollbar needs to be able to scroll
* through.
*
* @return the number of pixels the scrollbar should be able to scroll
* through
*/
public double getScrollSize() {
return parseCssDimensionToPixels(internalGetScrollSize());
}
/**
* Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the
* opposite axis to what the scrollbar is representing.
*
* @param px
* the dimension that {@link #scrollSizeElement} should take in
* the opposite axis to what the scrollbar is representing
*/
protected abstract void internalSetScrollbarThickness(double px);
/**
* Sets the scrollbar's thickness.
*
* If the thickness is set to 0, the scrollbar will be treated as an
* "invisible" scrollbar. This means, the DOM structure will be given a
* non-zero size, but {@link #getScrollbarThickness()} will still return the
* value 0.
*
* @param px
* the scrollbar's thickness in pixels
*/
public final void setScrollbarThickness(double px) {
isInvisibleScrollbar = (px == 0);
if (isInvisibleScrollbar) {
Event.sinkEvents(root, Event.ONSCROLL);
Event.setEventListener(root, new EventListener() {
@Override
public void onBrowserEvent(Event event) {
invisibleScrollbarTemporaryResizer.show();
}
});
root.getStyle().setVisibility(Visibility.HIDDEN);
} else {
Event.sinkEvents(root, 0);
Event.setEventListener(root, null);
root.getStyle().clearVisibility();
}
internalSetScrollbarThickness(Math.max(1d, px));
}
/**
* Gets the scrollbar's thickness as defined in the DOM.
*
* @return the scrollbar's thickness as defined in the DOM, in pixels
*/
protected abstract String internalGetScrollbarThickness();
/**
* Gets the scrollbar's thickness.
*
* This value will differ from the value in the DOM, if the thickness was
* set to 0 with {@link #setScrollbarThickness(double)}, as the scrollbar is
* then treated as "invisible."
*
* @return the scrollbar's thickness in pixels
*/
public final double getScrollbarThickness() {
if (!isInvisibleScrollbar) {
return parseCssDimensionToPixels(internalGetScrollbarThickness());
} else {
return 0;
}
}
/**
* Checks whether the scrollbar's handle is visible.
*
* In other words, this method checks whether the contents is larger than
* can visually fit in the element.
*
* @return true
iff the scrollbar's handle is visible
*/
public boolean showsScrollHandle() {
return getScrollSize() - getOffsetSize() > WidgetUtil.PIXEL_EPSILON;
}
public void recalculateMaxScrollPos() {
double scrollSize = getScrollSize();
double offsetSize = getOffsetSize();
maxScrollPos = Math.max(0, scrollSize - offsetSize);
// make sure that the correct max scroll position is maintained.
setScrollPos(scrollPos);
}
/**
* This is a method that JSNI can call to synchronize the object state from
* the DOM.
*/
private final void updateScrollPosFromDom() {
/*
* TODO: this method probably shouldn't be called from Escalator's JSNI,
* but probably could be handled internally by this listening to its own
* element. Would clean up the code quite a bit. Needs further
* investigation.
*/
int newScrollPos = internalGetScrollPos();
if (!isLocked()) {
scrollPos = newScrollPos;
scrollEventFirer.scheduleEvent();
} else if (scrollPos != newScrollPos) {
// we need to actually undo the setting of the scroll.
internalSetScrollPos(toInt32(scrollPos));
}
}
protected HandlerManager getHandlerManager() {
if (handlerManager == null) {
handlerManager = new HandlerManager(this);
}
return handlerManager;
}
/**
* Adds handler for the scrollbar handle visibility.
*
* @param handler
* the {@link VisibilityHandler} to add
* @return {@link HandlerRegistration} used to remove the handler
*/
public HandlerRegistration addVisibilityHandler(
final VisibilityHandler handler) {
return getHandlerManager().addHandler(VisibilityChangeEvent.TYPE,
handler);
}
private void fireVisibilityChangeIfNeeded() {
final boolean oldHandleIsVisible = scrollHandleIsVisible;
scrollHandleIsVisible = showsScrollHandle();
if (oldHandleIsVisible != scrollHandleIsVisible) {
final VisibilityChangeEvent event = new VisibilityChangeEvent(
scrollHandleIsVisible);
getHandlerManager().fireEvent(event);
}
}
/**
* Converts a double into an integer by JavaScript's terms.
*
* Implementation copied from {@link Element#toInt32(double)}.
*
* @param val
* the double value to convert into an integer
* @return the double value converted to an integer
*/
private static native int toInt32(double val)
/*-{
return Math.round(val) | 0;
}-*/;
/**
* Locks or unlocks the scrollbar bundle.
*
* A locked scrollbar bundle will refuse to scroll, both programmatically
* and via user-triggered events.
*
* @param isLocked
* true
to lock, false
to unlock
*/
public void setLocked(boolean isLocked) {
this.isLocked = isLocked;
}
/**
* Checks whether the scrollbar bundle is locked or not.
*
* @return true
iff the scrollbar bundle is locked
*/
public boolean isLocked() {
return isLocked;
}
/**
* Returns the scroll direction of this scrollbar bundle.
*
* @return the scroll direction of this scrollbar bundle
*/
public abstract Direction getDirection();
/**
* Adds a scroll handler to the scrollbar bundle.
*
* @param handler
* the handler to add
* @return the registration object for the handler registration
*/
public HandlerRegistration addScrollHandler(final ScrollHandler handler) {
return getHandlerManager().addHandler(ScrollEvent.TYPE, handler);
}
private static double parseCssDimensionToPixels(String size) {
/*
* Sizes of elements are calculated from CSS rather than
* element.getOffset*() because those values are 0 whenever display:
* none. Because we know that all elements have populated
* CSS-dimensions, it's better to do it that way.
*
* Another solution would be to make the elements visible while
* measuring and then re-hide them, but that would cause unnecessary
* reflows that would probably kill the performance dead.
*/
if (size.isEmpty()) {
return 0;
} else {
assert size.endsWith("px") : "Can't parse CSS dimension \"" + size
+ "\"";
return Double.parseDouble(size.substring(0, size.length() - 2));
}
}
@Override
public boolean isWorkPending() {
// Need to include scrollEventFirer.isBeingFired as it might use
// requestAnimationFrame - which is not automatically checked
return scrollSizeTemporaryScrollHandler != null
|| offsetSizeTemporaryScrollHandler != null
|| scrollInProgress != null || scrollEventFirer.isBeingFired;
}
}