@qooxdoo/framework
Version:
The JS Framework for Coders
615 lines (527 loc) • 18 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 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:
* Sebastian Werner (wpbasti)
* Fabian Jakobs (fjakobs)
************************************************************************ */
/**
* The ScrollArea provides a container widget with on demand scroll bars
* if the content size exceeds the size of the container.
*
* @childControl pane {qx.ui.core.scroll.ScrollPane} pane which holds the content to scroll
* @childControl scrollbar-x {qx.ui.core.scroll.ScrollBar?qx.ui.core.scroll.NativeScrollBar} horizontal scrollbar
* @childControl scrollbar-y {qx.ui.core.scroll.ScrollBar?qx.ui.core.scroll.NativeScrollBar} vertical scrollbar
* @childControl corner {qx.ui.core.Widget} corner where no scrollbar is shown
*/
qx.Class.define("qx.ui.core.scroll.AbstractScrollArea", {
extend: qx.ui.core.Widget,
include: [
qx.ui.core.scroll.MScrollBarFactory,
qx.ui.core.scroll.MRoll,
qx.ui.core.MDragDropScrolling
],
type: "abstract",
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics: {
/**
* The default width which is used for the width of the scroll bar if
* overlaid.
*/
DEFAULT_SCROLLBAR_WIDTH: 14
},
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct() {
super();
if (qx.core.Environment.get("os.scrollBarOverlayed")) {
// use a plain canvas to overlay the scroll bars
this._setLayout(new qx.ui.layout.Canvas());
} else {
// Create 'fixed' grid layout
var grid = new qx.ui.layout.Grid();
grid.setColumnFlex(0, 1);
grid.setRowFlex(0, 1);
this._setLayout(grid);
}
// since the scroll container disregards the min size of the scrollbars
// we have to set the min size of the scroll area to ensure that the
// scrollbars always have an usable size.
var size =
qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH * 2 + 14;
this.set({ minHeight: size, minWidth: size });
// Roll listener for scrolling
this._addRollHandling();
},
events: {
/** Fired as soon as the scroll animation in X direction ends. */
scrollAnimationXEnd: "qx.event.type.Event",
/** Fired as soon as the scroll animation in Y direction ends. */
scrollAnimationYEnd: "qx.event.type.Event"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
// overridden
appearance: {
refine: true,
init: "scrollarea"
},
// overridden
width: {
refine: true,
init: 0
},
// overridden
height: {
refine: true,
init: 0
},
/**
* The policy, when the horizontal scrollbar should be shown.
* <ul>
* <li><b>auto</b>: Show scrollbar on demand</li>
* <li><b>on</b>: Always show the scrollbar</li>
* <li><b>off</b>: Never show the scrollbar</li>
* </ul>
*/
scrollbarX: {
check: ["auto", "on", "off"],
init: "auto",
themeable: true,
apply: "_computeScrollbars"
},
/**
* The policy, when the horizontal scrollbar should be shown.
* <ul>
* <li><b>auto</b>: Show scrollbar on demand</li>
* <li><b>on</b>: Always show the scrollbar</li>
* <li><b>off</b>: Never show the scrollbar</li>
* </ul>
*/
scrollbarY: {
check: ["auto", "on", "off"],
init: "auto",
themeable: true,
apply: "_computeScrollbars"
},
/**
* Group property, to set the overflow of both scroll bars.
*/
scrollbar: {
group: ["scrollbarX", "scrollbarY"]
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
/*
---------------------------------------------------------------------------
CHILD CONTROL SUPPORT
---------------------------------------------------------------------------
*/
// overridden
_createChildControlImpl(id, hash) {
var control;
switch (id) {
case "pane":
control = new qx.ui.core.scroll.ScrollPane();
control.addListener("update", this._computeScrollbars, this);
control.addListener("scrollX", this._onScrollPaneX, this);
control.addListener("scrollY", this._onScrollPaneY, this);
if (qx.core.Environment.get("os.scrollBarOverlayed")) {
this._add(control, { edge: 0 });
} else {
this._add(control, { row: 0, column: 0 });
}
break;
case "scrollbar-x":
control = this._createScrollBar("horizontal");
control.setMinWidth(0);
control.exclude();
control.addListener("scroll", this._onScrollBarX, this);
control.addListener(
"changeVisibility",
this._onChangeScrollbarXVisibility,
this
);
control.addListener(
"scrollAnimationEnd",
this._onScrollAnimationEnd.bind(this, "X")
);
if (qx.core.Environment.get("os.scrollBarOverlayed")) {
control.setMinHeight(
qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH
);
this._add(control, { bottom: 0, right: 0, left: 0 });
} else {
this._add(control, { row: 1, column: 0 });
}
break;
case "scrollbar-y":
control = this._createScrollBar("vertical");
control.setMinHeight(0);
control.exclude();
control.addListener("scroll", this._onScrollBarY, this);
control.addListener(
"changeVisibility",
this._onChangeScrollbarYVisibility,
this
);
control.addListener(
"scrollAnimationEnd",
this._onScrollAnimationEnd.bind(this, "Y")
);
if (qx.core.Environment.get("os.scrollBarOverlayed")) {
control.setMinWidth(
qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH
);
this._add(control, { right: 0, bottom: 0, top: 0 });
} else {
this._add(control, { row: 0, column: 1 });
}
break;
case "corner":
control = new qx.ui.core.Widget();
control.setWidth(0);
control.setHeight(0);
control.exclude();
if (!qx.core.Environment.get("os.scrollBarOverlayed")) {
// only add for non overlayed scroll bars
this._add(control, { row: 1, column: 1 });
}
break;
}
return control || super._createChildControlImpl(id);
},
/*
---------------------------------------------------------------------------
PANE SIZE
---------------------------------------------------------------------------
*/
/**
* Returns the dimensions of the pane.
*
* @return {Map|null} The pane dimension in pixel. Contains
* the keys <code>width</code> and <code>height</code>.
*/
getPaneSize() {
return this.getChildControl("pane").getInnerSize();
},
/*
---------------------------------------------------------------------------
ITEM LOCATION SUPPORT
---------------------------------------------------------------------------
*/
/**
* Returns the top offset of the given item in relation to the
* inner height of this widget.
*
* @param item {qx.ui.core.Widget} Item to query
* @return {Integer} Top offset
*/
getItemTop(item) {
return this.getChildControl("pane").getItemTop(item);
},
/**
* Returns the top offset of the end of the given item in relation to the
* inner height of this widget.
*
* @param item {qx.ui.core.Widget} Item to query
* @return {Integer} Top offset
*/
getItemBottom(item) {
return this.getChildControl("pane").getItemBottom(item);
},
/**
* Returns the left offset of the given item in relation to the
* inner width of this widget.
*
* @param item {qx.ui.core.Widget} Item to query
* @return {Integer} Top offset
*/
getItemLeft(item) {
return this.getChildControl("pane").getItemLeft(item);
},
/**
* Returns the left offset of the end of the given item in relation to the
* inner width of this widget.
*
* @param item {qx.ui.core.Widget} Item to query
* @return {Integer} Right offset
*/
getItemRight(item) {
return this.getChildControl("pane").getItemRight(item);
},
/*
---------------------------------------------------------------------------
SCROLL SUPPORT
---------------------------------------------------------------------------
*/
/**
* Scrolls the element's content to the given left coordinate
*
* @param value {Integer} The vertical position to scroll to.
* @param duration {Number?} The time in milliseconds the scroll to should take.
*/
scrollToX(value, duration) {
// First flush queue before scroll
qx.ui.core.queue.Manager.flush();
this.getChildControl("scrollbar-x").scrollTo(value, duration);
},
/**
* Scrolls the element's content by the given left offset
*
* @param value {Integer} The vertical position to scroll to.
* @param duration {Number?} The time in milliseconds the scroll to should take.
*/
scrollByX(value, duration) {
// First flush queue before scroll
qx.ui.core.queue.Manager.flush();
this.getChildControl("scrollbar-x").scrollBy(value, duration);
},
/**
* Returns the scroll left position of the content
*
* @return {Integer} Horizontal scroll position
*/
getScrollX() {
var scrollbar = this.getChildControl("scrollbar-x", true);
return scrollbar ? scrollbar.getPosition() : 0;
},
/**
* Scrolls the element's content to the given top coordinate
*
* @param value {Integer} The horizontal position to scroll to.
* @param duration {Number?} The time in milliseconds the scroll to should take.
*/
scrollToY(value, duration) {
// First flush queue before scroll
qx.ui.core.queue.Manager.flush();
this.getChildControl("scrollbar-y").scrollTo(value, duration);
},
/**
* Scrolls the element's content by the given top offset
*
* @param value {Integer} The horizontal position to scroll to.
* @param duration {Number?} The time in milliseconds the scroll to should take.
*/
scrollByY(value, duration) {
// First flush queue before scroll
qx.ui.core.queue.Manager.flush();
this.getChildControl("scrollbar-y").scrollBy(value, duration);
},
/**
* Returns the scroll top position of the content
*
* @return {Integer} Vertical scroll position
*/
getScrollY() {
var scrollbar = this.getChildControl("scrollbar-y", true);
return scrollbar ? scrollbar.getPosition() : 0;
},
/**
* In case a scroll animation is currently running in X direction,
* it will be stopped. If not, the method does nothing.
*/
stopScrollAnimationX() {
var scrollbar = this.getChildControl("scrollbar-x", true);
if (scrollbar) {
scrollbar.stopScrollAnimation();
}
},
/**
* In case a scroll animation is currently running in X direction,
* it will be stopped. If not, the method does nothing.
*/
stopScrollAnimationY() {
var scrollbar = this.getChildControl("scrollbar-y", true);
if (scrollbar) {
scrollbar.stopScrollAnimation();
}
},
/*
---------------------------------------------------------------------------
EVENT LISTENERS
---------------------------------------------------------------------------
*/
/**
* Event handler for the scroll animation end event for both scroll bars.
*
* @param direction {String} Either "X" or "Y".
*/
_onScrollAnimationEnd(direction) {
this.fireEvent("scrollAnimation" + direction + "End");
},
/**
* Event handler for the scroll event of the horizontal scrollbar
*
* @param e {qx.event.type.Data} The scroll event object
*/
_onScrollBarX(e) {
this.getChildControl("pane").scrollToX(e.getData());
},
/**
* Event handler for the scroll event of the vertical scrollbar
*
* @param e {qx.event.type.Data} The scroll event object
*/
_onScrollBarY(e) {
this.getChildControl("pane").scrollToY(e.getData());
},
/**
* Event handler for the horizontal scroll event of the pane
*
* @param e {qx.event.type.Data} The scroll event object
*/
_onScrollPaneX(e) {
var scrollbar = this.getChildControl("scrollbar-x");
if (scrollbar) {
scrollbar.updatePosition(e.getData());
}
},
/**
* Event handler for the vertical scroll event of the pane
*
* @param e {qx.event.type.Data} The scroll event object
*/
_onScrollPaneY(e) {
var scrollbar = this.getChildControl("scrollbar-y");
if (scrollbar) {
scrollbar.updatePosition(e.getData());
}
},
/**
* Event handler for visibility changes of horizontal scrollbar.
*
* @param e {qx.event.type.Event} Property change event
*/
_onChangeScrollbarXVisibility(e) {
var showX = this._isChildControlVisible("scrollbar-x");
var showY = this._isChildControlVisible("scrollbar-y");
if (!showX) {
this.scrollToX(0);
}
showX && showY
? this._showChildControl("corner")
: this._excludeChildControl("corner");
},
/**
* Event handler for visibility changes of horizontal scrollbar.
*
* @param e {qx.event.type.Event} Property change event
*/
_onChangeScrollbarYVisibility(e) {
var showX = this._isChildControlVisible("scrollbar-x");
var showY = this._isChildControlVisible("scrollbar-y");
if (!showY) {
this.scrollToY(0);
}
showX && showY
? this._showChildControl("corner")
: this._excludeChildControl("corner");
},
/*
---------------------------------------------------------------------------
HELPER METHODS
---------------------------------------------------------------------------
*/
/**
* Computes the visibility state for scrollbars.
*
*/
_computeScrollbars() {
var pane = this.getChildControl("pane");
var content = pane.getChildren()[0];
if (!content) {
this._excludeChildControl("scrollbar-x");
this._excludeChildControl("scrollbar-y");
return;
}
var innerSize = this.getInnerSize();
var paneSize = pane.getInnerSize();
var scrollSize = pane.getScrollSize();
// if the widget has not yet been rendered, return and try again in the
// resize event
if (!paneSize || !scrollSize) {
return;
}
var scrollbarX = this.getScrollbarX();
var scrollbarY = this.getScrollbarY();
if (scrollbarX === "auto" && scrollbarY === "auto") {
// Check if the container is big enough to show
// the full content.
var showX = scrollSize.width > innerSize.width;
var showY = scrollSize.height > innerSize.height;
// Dependency check
// We need a special intelligence here when only one
// of the autosized axis requires a scrollbar
// This scrollbar may then influence the need
// for the other one as well.
if ((showX || showY) && !(showX && showY)) {
if (showX) {
showY = scrollSize.height > paneSize.height;
} else if (showY) {
showX = scrollSize.width > paneSize.width;
}
}
} else {
var showX = scrollbarX === "on";
var showY = scrollbarY === "on";
// Check auto values afterwards with already
// corrected client dimensions
if (
scrollSize.width > (showX ? paneSize.width : innerSize.width) &&
scrollbarX === "auto"
) {
showX = true;
}
if (
scrollSize.height > (showX ? paneSize.height : innerSize.height) &&
scrollbarY === "auto"
) {
showY = true;
}
}
// Update scrollbars
if (showX) {
var barX = this.getChildControl("scrollbar-x");
barX.show();
barX.setMaximum(Math.max(0, scrollSize.width - paneSize.width));
barX.setKnobFactor(
scrollSize.width === 0 ? 0 : paneSize.width / scrollSize.width
);
} else {
this._excludeChildControl("scrollbar-x");
}
if (showY) {
var barY = this.getChildControl("scrollbar-y");
barY.show();
barY.setMaximum(Math.max(0, scrollSize.height - paneSize.height));
barY.setKnobFactor(
scrollSize.height === 0 ? 0 : paneSize.height / scrollSize.height
);
} else {
this._excludeChildControl("scrollbar-y");
}
}
}
});