@qooxdoo/framework
Version:
The JS Framework for Coders
531 lines (416 loc) • 12.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)
************************************************************************ */
/**
* This class represents a scroll able pane. This means that this widget
* may contain content which is bigger than the available (inner)
* dimensions of this widget. The widget also offer methods to control
* the scrolling position. It can only have exactly one child.
*/
qx.Class.define("qx.ui.core.scroll.ScrollPane",
{
extend : qx.ui.core.Widget,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct : function()
{
this.base(arguments);
this.set({
minWidth: 0,
minHeight: 0
});
// Automatically configure a "fixed" grow layout.
this._setLayout(new qx.ui.layout.Grow());
// Add resize listener to "translate" event
this.addListener("resize", this._onUpdate);
var contentEl = this.getContentElement();
// Synchronizes the DOM scroll position with the properties
contentEl.addListener("scroll", this._onScroll, this);
// Fixed some browser quirks e.g. correcting scroll position
// to the previous value on re-display of a pane
contentEl.addListener("appear", this._onAppear, this);
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events :
{
/** Fired on resize of both the container or the content. */
update : "qx.event.type.Event",
/** Fired on scroll animation end invoked by 'scroll*' methods. */
scrollAnimationEnd : "qx.event.type.Event"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
/** The horizontal scroll position */
scrollX :
{
check : "qx.lang.Type.isNumber(value)&&value>=0&&value<=this.getScrollMaxX()",
apply : "_applyScrollX",
transform: "_transformScrollX",
event : "scrollX",
init : 0
},
/** The vertical scroll position */
scrollY :
{
check : "qx.lang.Type.isNumber(value)&&value>=0&&value<=this.getScrollMaxY()",
apply : "_applyScrollY",
transform: "_transformScrollY",
event : "scrollY",
init : 0
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__frame : null,
/*
---------------------------------------------------------------------------
CONTENT MANAGEMENT
---------------------------------------------------------------------------
*/
/**
* Configures the content of the scroll pane. Replaces any existing child
* with the newly given one.
*
* @param widget {qx.ui.core.Widget?null} The content widget of the pane
*/
add : function(widget)
{
var old = this._getChildren()[0];
if (old)
{
this._remove(old);
old.removeListener("resize", this._onUpdate, this);
}
if (widget)
{
this._add(widget);
widget.addListener("resize", this._onUpdate, this);
}
},
/**
* Removes the given widget from the content. The pane is empty
* afterwards as only one child is supported by the pane.
*
* @param widget {qx.ui.core.Widget?null} The content widget of the pane
*/
remove : function(widget)
{
if (widget)
{
this._remove(widget);
widget.removeListener("resize", this._onUpdate, this);
}
},
/**
* Returns an array containing the current content.
*
* @return {Object[]} The content array
*/
getChildren : function() {
return this._getChildren();
},
/*
---------------------------------------------------------------------------
EVENT LISTENER
---------------------------------------------------------------------------
*/
/**
* Event listener for resize event of content and container
*
* @param e {Event} Resize event object
*/
_onUpdate : function(e) {
this.fireEvent("update");
},
/**
* Event listener for scroll event of content
*
* @param e {qx.event.type.Event} Scroll event object
*/
_onScroll : function(e)
{
var contentEl = this.getContentElement();
this.setScrollX(contentEl.getScrollX());
this.setScrollY(contentEl.getScrollY());
},
/**
* Event listener for appear event of content
*
* @param e {qx.event.type.Event} Appear event object
*/
_onAppear : function(e)
{
var contentEl = this.getContentElement();
var internalX = this.getScrollX();
var domX = contentEl.getScrollX();
if (internalX != domX) {
contentEl.scrollToX(internalX);
}
var internalY = this.getScrollY();
var domY = contentEl.getScrollY();
if (internalY != domY) {
contentEl.scrollToY(internalY);
}
},
/*
---------------------------------------------------------------------------
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)
{
var top = 0;
do
{
top += item.getBounds().top;
item = item.getLayoutParent();
}
while (item && item !== this);
return top;
},
/**
* 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.getItemTop(item) + item.getBounds().height;
},
/**
* 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)
{
var left = 0;
var parent;
do
{
left += item.getBounds().left;
parent = item.getLayoutParent();
if (parent) {
left += parent.getInsets().left;
}
item = parent;
}
while (item && item !== this);
return left;
},
/**
* 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.getItemLeft(item) + item.getBounds().width;
},
/*
---------------------------------------------------------------------------
DIMENSIONS
---------------------------------------------------------------------------
*/
/**
* The size (identical with the preferred size) of the content.
*
* @return {Map} Size of the content (keys: <code>width</code> and <code>height</code>)
*/
getScrollSize : function() {
return this.getChildren()[0].getBounds();
},
/*
---------------------------------------------------------------------------
SCROLL SUPPORT
---------------------------------------------------------------------------
*/
/**
* The maximum horizontal scroll position.
*
* @return {Integer} Maximum horizontal scroll position.
*/
getScrollMaxX : function()
{
var paneSize = this.getInnerSize();
var scrollSize = this.getScrollSize();
if (paneSize && scrollSize) {
return Math.max(0, scrollSize.width - paneSize.width);
}
return 0;
},
/**
* The maximum vertical scroll position.
*
* @return {Integer} Maximum vertical scroll position.
*/
getScrollMaxY : function()
{
var paneSize = this.getInnerSize();
var scrollSize = this.getScrollSize();
if (paneSize && scrollSize) {
return Math.max(0, scrollSize.height - paneSize.height);
}
return 0;
},
/**
* 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)
{
var max = this.getScrollMaxX();
if (value < 0) {
value = 0;
} else if (value > max) {
value = max;
}
this.stopScrollAnimation();
if (duration) {
var from = this.getScrollX();
this.__frame = new qx.bom.AnimationFrame();
this.__frame.on("end", function() {
this.setScrollX(value);
this.__frame = null;
this.fireEvent("scrollAnimationEnd");
}, this);
this.__frame.on("frame", function(timePassed) {
var newX = parseInt(timePassed/duration * (value - from) + from);
this.setScrollX(newX);
}, this);
this.__frame.startSequence(duration);
} else {
this.setScrollX(value);
}
},
/**
* 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)
{
var max = this.getScrollMaxY();
if (value < 0) {
value = 0;
} else if (value > max) {
value = max;
}
this.stopScrollAnimation();
if (duration) {
var from = this.getScrollY();
this.__frame = new qx.bom.AnimationFrame();
this.__frame.on("end", function() {
this.setScrollY(value);
this.__frame = null;
this.fireEvent("scrollAnimationEnd");
}, this);
this.__frame.on("frame", function(timePassed) {
var newY = parseInt(timePassed/duration * (value - from) + from);
this.setScrollY(newY);
}, this);
this.__frame.startSequence(duration);
} else {
this.setScrollY(value);
}
},
/**
* Scrolls the element's content horizontally by the given amount.
*
* @param x {Integer?0} Amount to scroll
* @param duration {Number?} The time in milliseconds the scroll to should take.
*/
scrollByX : function(x, duration) {
this.scrollToX(this.getScrollX() + x, duration);
},
/**
* Scrolls the element's content vertically by the given amount.
*
* @param y {Integer?0} Amount to scroll
* @param duration {Number?} The time in milliseconds the scroll to should take.
*/
scrollByY : function(y, duration) {
this.scrollToY(this.getScrollY() + y, duration);
},
/**
* If an scroll animation is running, it will be stopped with that method.
*/
stopScrollAnimation : function() {
if (this.__frame) {
this.__frame.cancelSequence();
this.__frame = null;
}
},
/*
---------------------------------------------------------------------------
PROPERTY APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyScrollX : function(value) {
this.getContentElement().scrollToX(value);
},
/**
* Transform property
*
* @param value {Number} Value to transform
* @return {Number} Rounded value
*/
_transformScrollX: function(value) {
return Math.round(value);
},
// property apply
_applyScrollY : function(value) {
this.getContentElement().scrollToY(value);
},
/**
* Transform property
*
* @param value {Number} Value to transform
* @return {Number} Rounded value
*/
_transformScrollY: function(value) {
return Math.round(value);
}
}
});