@qooxdoo/framework
Version:
The JS Framework for Coders
640 lines (510 loc) • 17.9 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 : function()
{
this.base(arguments);
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"
},
/**
* 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 : function(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 || this.base(arguments, 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 : function() {
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 : function(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 : function(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 : function(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 : function(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 : function(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 : function(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 : function()
{
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 : function(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 : function(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 : function()
{
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 : function() {
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 : function() {
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 : function(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 : function(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 : function(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 : function(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 : function(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 : function(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 : function(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 : function()
{
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");
}
}
}
});