@qooxdoo/framework
Version:
The JS Framework for Coders
492 lines (414 loc) • 12.8 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() {
super();
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(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(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() {
return this._getChildren();
},
/*
---------------------------------------------------------------------------
EVENT LISTENER
---------------------------------------------------------------------------
*/
/**
* Event listener for resize event of content and container
*
* @param e {Event} Resize event object
*/
_onUpdate(e) {
this.fireEvent("update");
},
/**
* Event listener for scroll event of content
*
* @param e {qx.event.type.Event} Scroll event object
*/
_onScroll(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(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(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(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(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(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() {
return this.getChildren()[0].getBounds();
},
/*
---------------------------------------------------------------------------
SCROLL SUPPORT
---------------------------------------------------------------------------
*/
/**
* The maximum horizontal scroll position.
*
* @return {Integer} Maximum horizontal scroll position.
*/
getScrollMaxX() {
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() {
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(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(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(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(y, duration) {
this.scrollToY(this.getScrollY() + y, duration);
},
/**
* If an scroll animation is running, it will be stopped with that method.
*/
stopScrollAnimation() {
if (this.__frame) {
this.__frame.cancelSequence();
this.__frame = null;
}
},
/*
---------------------------------------------------------------------------
PROPERTY APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyScrollX(value) {
this.getContentElement().scrollToX(value);
},
/**
* Transform property
*
* @param value {Number} Value to transform
* @return {Number} Rounded value
*/
_transformScrollX(value) {
return Math.round(value);
},
// property apply
_applyScrollY(value) {
this.getContentElement().scrollToY(value);
},
/**
* Transform property
*
* @param value {Number} Value to transform
* @return {Number} Rounded value
*/
_transformScrollY(value) {
return Math.round(value);
}
}
});