@qooxdoo/framework
Version:
The JS Framework for Coders
385 lines (324 loc) • 10.1 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2009 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Fabian Jakobs (fjakobs)
************************************************************************ */
/**
* The scroll bar widget wraps the native browser scroll bars as a qooxdoo widget.
* It can be uses instead of the styled qooxdoo scroll bars.
*
* Scroll bars are used by the {@link qx.ui.container.Scroll} container. Usually
* a scroll bar is not used directly.
*
* *Example*
*
* Here is a little example of how to use the widget.
*
* <pre class='javascript'>
* var scrollBar = new qx.ui.core.scroll.NativeScrollBar("horizontal");
* scrollBar.set({
* maximum: 500
* })
* this.getRoot().add(scrollBar);
* </pre>
*
* This example creates a horizontal scroll bar with a maximum value of 500.
*
* *External Documentation*
*
* <a href='http://qooxdoo.org/docs/#desktop/widget/scrollbar.md' target='_blank'>
* Documentation of this widget in the qooxdoo manual.</a>
*/
qx.Class.define("qx.ui.core.scroll.NativeScrollBar", {
extend: qx.ui.core.Widget,
implement: qx.ui.core.scroll.IScrollBar,
/**
* @param orientation {String?"horizontal"} The initial scroll bar orientation
*/
construct(orientation) {
super();
this.addState("native");
this.getContentElement().addListener("scroll", this._onScroll, this);
this.addListener("pointerdown", this._stopPropagation, this);
this.addListener("pointerup", this._stopPropagation, this);
this.addListener("pointermove", this._stopPropagation, this);
this.addListener("appear", this._onAppear, this);
this.getContentElement().add(this._getScrollPaneElement());
this.getContentElement().setStyle("box-sizing", "content-box");
// Configure orientation
if (orientation != null) {
this.setOrientation(orientation);
} else {
this.initOrientation();
}
// prevent drag & drop on scrolling
this.addListener("track", e => {
e.stopPropagation();
});
},
events: {
/**
* Fired as soon as the scroll animation ended.
*/
scrollAnimationEnd: "qx.event.type.Event"
},
properties: {
// overridden
appearance: {
refine: true,
init: "scrollbar"
},
// interface implementation
orientation: {
check: ["horizontal", "vertical"],
init: "horizontal",
apply: "_applyOrientation"
},
// interface implementation
maximum: {
check: "PositiveInteger",
apply: "_applyMaximum",
init: 100
},
// interface implementation
position: {
check: "Number",
init: 0,
apply: "_applyPosition",
event: "scroll"
},
/**
* Step size for each tap on the up/down or left/right buttons.
*/
singleStep: {
check: "Integer",
init: 20
},
// interface implementation
knobFactor: {
check: "PositiveNumber",
nullable: true
}
},
members: {
__isHorizontal: null,
__scrollPaneElement: null,
__requestId: null,
__scrollAnimationframe: null,
/**
* Get the scroll pane html element.
*
* @return {qx.html.Element} The element
*/
_getScrollPaneElement() {
if (!this.__scrollPaneElement) {
this.__scrollPaneElement = new qx.html.Element();
}
return this.__scrollPaneElement;
},
/*
---------------------------------------------------------------------------
WIDGET API
---------------------------------------------------------------------------
*/
// overridden
renderLayout(left, top, width, height) {
var changes = super.renderLayout(left, top, width, height);
this._updateScrollBar();
return changes;
},
// overridden
_getContentHint() {
var scrollbarWidth = qx.bom.element.Scroll.getScrollbarWidth();
return {
width: this.__isHorizontal ? 100 : scrollbarWidth,
maxWidth: this.__isHorizontal ? null : scrollbarWidth,
minWidth: this.__isHorizontal ? null : scrollbarWidth,
height: this.__isHorizontal ? scrollbarWidth : 100,
maxHeight: this.__isHorizontal ? scrollbarWidth : null,
minHeight: this.__isHorizontal ? scrollbarWidth : null
};
},
// overridden
_applyEnabled(value, old) {
super._applyEnabled(value, old);
this._updateScrollBar();
},
/*
---------------------------------------------------------------------------
PROPERTY APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyMaximum(value) {
this._updateScrollBar();
},
// property apply
_applyPosition(value) {
var content = this.getContentElement();
if (this.__isHorizontal) {
content.scrollToX(value);
} else {
content.scrollToY(value);
}
},
// property apply
_applyOrientation(value, old) {
// ARIA attrs
this.getContentElement().setAttribute("aria-orientation", value);
var isHorizontal = (this.__isHorizontal = value === "horizontal");
this.set({
allowGrowX: isHorizontal,
allowShrinkX: isHorizontal,
allowGrowY: !isHorizontal,
allowShrinkY: !isHorizontal
});
if (isHorizontal) {
this.replaceState("vertical", "horizontal");
} else {
this.replaceState("horizontal", "vertical");
}
this.getContentElement().setStyles({
overflowX: isHorizontal ? "scroll" : "hidden",
overflowY: isHorizontal ? "hidden" : "scroll"
});
// Update layout
qx.ui.core.queue.Layout.add(this);
},
/**
* Update the scroll bar according to its current size, max value and
* enabled state.
*/
_updateScrollBar() {
var isHorizontal = this.__isHorizontal;
var bounds = this.getBounds();
if (!bounds) {
return;
}
if (this.isEnabled()) {
var containerSize = isHorizontal ? bounds.width : bounds.height;
var innerSize = this.getMaximum() + containerSize;
} else {
innerSize = 0;
}
// Scrollbars don't work properly in IE/Edge if the element with overflow has
// exactly the size of the scrollbar. Thus we move the element one pixel
// out of the view and increase the size by one.
if (
qx.core.Environment.get("engine.name") == "mshtml" ||
qx.core.Environment.get("browser.name") == "edge"
) {
var bounds = this.getBounds();
this.getContentElement().setStyles({
left: (isHorizontal ? bounds.left : bounds.left - 1) + "px",
top: (isHorizontal ? bounds.top - 1 : bounds.top) + "px",
width: (isHorizontal ? bounds.width : bounds.width + 1) + "px",
height: (isHorizontal ? bounds.height + 1 : bounds.height) + "px"
});
}
this._getScrollPaneElement().setStyles({
left: 0,
top: 0,
width: (isHorizontal ? innerSize : 1) + "px",
height: (isHorizontal ? 1 : innerSize) + "px"
});
this.updatePosition(this.getPosition());
},
// interface implementation
scrollTo(position, duration) {
// if a user sets a new position, stop any animation
this.stopScrollAnimation();
if (duration) {
var from = this.getPosition();
this.__scrollAnimationframe = new qx.bom.AnimationFrame();
this.__scrollAnimationframe.on(
"frame",
function (timePassed) {
var newPos = parseInt(
(timePassed / duration) * (position - from) + from
);
this.updatePosition(newPos);
},
this
);
this.__scrollAnimationframe.on(
"end",
function () {
this.setPosition(
Math.max(0, Math.min(this.getMaximum(), position))
);
this.__scrollAnimationframe = null;
this.fireEvent("scrollAnimationEnd");
},
this
);
this.__scrollAnimationframe.startSequence(duration);
} else {
this.updatePosition(position);
}
},
/**
* Helper to set the new position taking care of min and max values.
* @param position {Number} The new position.
*/
updatePosition(position) {
this.setPosition(Math.max(0, Math.min(this.getMaximum(), position)));
},
// interface implementation
scrollBy(offset, duration) {
this.scrollTo(this.getPosition() + offset, duration);
},
// interface implementation
scrollBySteps(steps, duration) {
var size = this.getSingleStep();
this.scrollBy(steps * size, duration);
},
/**
* If a scroll animation is running, it will be stopped.
*/
stopScrollAnimation() {
if (this.__scrollAnimationframe) {
this.__scrollAnimationframe.cancelSequence();
this.__scrollAnimationframe = null;
}
},
/**
* Scroll event handler
*
* @param e {qx.event.type.Event} the scroll event
*/
_onScroll(e) {
var container = this.getContentElement();
var position = this.__isHorizontal
? container.getScrollX()
: container.getScrollY();
this.setPosition(position);
},
/**
* Listener for appear which ensured the scroll bar is positioned right
* on appear.
*
* @param e {qx.event.type.Data} Incoming event object
*/
_onAppear(e) {
this._applyPosition(this.getPosition());
},
/**
* Stops propagation on the given even
*
* @param e {qx.event.type.Event} the event
*/
_stopPropagation(e) {
e.stopPropagation();
}
},
destruct() {
this._disposeObjects("__scrollPaneElement");
}
});