@qooxdoo/framework
Version:
The JS Framework for Coders
381 lines (315 loc) • 10.8 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2011 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:
* Tino Butz (tbtz)
************************************************************************ */
/* ************************************************************************
************************************************************************ */
/**
* Mixin for the {@link Scroll} container. Used when the variant
* <code>qx.mobile.nativescroll</code> is set to "off". Uses the iScroll script to simulate
* the CSS position:fixed style. Position fixed is not available in iOS and
* Android < 2.2.
*
* @ignore(iScroll)
* @asset(qx/mobile/js/iscroll*.js)
*/
qx.Mixin.define("qx.ui.mobile.container.MIScroll",
{
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct : function()
{
this.__initScroll();
this.__registerEventListeners();
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__scroll : null,
/**
* Mixin method. Creates the scroll element.
*
* @return {Element} The scroll element
*/
_createScrollElement : function()
{
var scroll = qx.dom.Element.create("div");
qx.bom.element.Class.add(scroll,"iscroll");
return scroll;
},
/**
* Mixin method. Returns the scroll content element..
*
* @return {Element} The scroll content element
*/
_getScrollContentElement : function()
{
return this.getContainerElement().childNodes[0];
},
/**
* Returns the current scroll position
* @return {Array} an array with the <code>[scrollLeft,scrollTop]</code>.
*/
_getPosition : function() {
return [this._currentX, this._currentY];
},
/**
* Returns the scrolling height of the inner container.
* @return {Number} the scrolling height.
*/
_getScrollHeight : function() {
if(!this.getContainerElement()) {
return 0;
}
return this._getScrollContentElement().scrollHeight - this.getContainerElement().offsetHeight;
},
/**
* Returns the scrolling width of the inner container.
* @return {Number} the scrolling width.
*/
_getScrollWidth : function() {
if(!this.getContainerElement()) {
return 0;
}
return this._getScrollContentElement().scrollWidth - this.getContainerElement().offsetWidth;
},
/**
* Scrolls the wrapper contents to the x/y coordinates in a given period.
*
* @param x {Integer} X coordinate to scroll to.
* @param y {Integer} Y coordinate to scroll to.
* @param time {Integer} Time slice in which scrolling should
* be done.
*/
_scrollTo : function(x, y, time)
{
if (this._isScrollable()) {
// Normalize scrollable values
var lowerLimitY = qx.bom.element.Dimension.getHeight(this._getScrollContentElement()) - this.getContainerElement().offsetHeight;
if (y > lowerLimitY) {
y = lowerLimitY;
}
var lowerLimitX = qx.bom.element.Dimension.getWidth(this._getScrollContentElement()) - this.getContainerElement().offsetWidth;
if (x > lowerLimitX) {
x = lowerLimitX;
}
if(this.__scroll) {
this.__scroll.scrollTo(-x, -y, time);
} else {
// Case when iScroll is not loaded yet, but user tries
// to set a different scroll position. Position is applied on "__onScrollLoaded".
this._setCurrentY(x);
this._setCurrentY(y);
}
}
},
/**
* Loads and inits the iScroll instance.
*
* @ignore(iScroll)
*/
__initScroll : function()
{
if (!window.iScroll)
{
if (qx.core.Environment.get("qx.debug"))
{
var resource = "qx/mobile/js/iscroll.js";
} else {
var resource = "qx/mobile/js/iscroll.min.js";
}
var path = qx.util.ResourceManager.getInstance().toUri(resource);
if (qx.core.Environment.get("qx.debug"))
{
path += "?" + new Date().getTime();
}
var loader = new qx.bom.request.Script();
loader.on("load", this.__onScrollLoaded, this);
loader.open("GET", path);
loader.send();
} else {
this.addListenerOnce("appear", function() {
this._setScroll(this.__createScrollInstance());
}, this);
}
},
/**
* Creates the iScroll instance.
*
* @return {Object} The iScroll instance
* @ignore(iScroll)
*/
__createScrollInstance : function()
{
var defaultScrollProperties = this._getDefaultScrollProperties();
var customScrollProperties = {};
if(this._scrollProperties != null) {
customScrollProperties = this._scrollProperties;
}
var iScrollProperties = qx.lang.Object.mergeWith(defaultScrollProperties, customScrollProperties, true);
return new iScroll(this.getContainerElement(), iScrollProperties);
},
/**
* Returns a map with default iScroll properties for the iScroll instance.
* @return {Object} Map with default iScroll properties
*/
_getDefaultScrollProperties : function() {
var container = this;
return {
hideScrollbar: true,
fadeScrollbar: true,
hScrollbar: false,
scrollbarClass: "scrollbar",
useTransform: true,
useTransition: true,
onScrollEnd: function () {
// Alert interested parties that we scrolled to end of page.
if (qx.core.Environment.get("qx.mobile.nativescroll") == false) {
container._setCurrentX(-this.x);
container._setCurrentY(-this.y);
container.fireEvent("scrollEnd");
if (this.y == this.maxScrollY) {
container.fireEvent("pageEnd");
}
}
},
onScrollMove: function () {
// Alert interested parties that we scrolled to end of page.
if (qx.core.Environment.get("qx.mobile.nativescroll") == false) {
container._setCurrentX(-this.x);
container._setCurrentY(-this.y);
if (this.y == this.maxScrollY) {
container.fireEvent("pageEnd");
}
}
},
onBeforeScrollStart: function (e) {
// QOOXDOO ENHANCEMENT: Do not prevent default for form elements
/* When updating iScroll, please check out that doubleTapTimer is not active (commented out)
* in code. DoubleTapTimer creates a fake click event. Android 4.1. and newer
* is able to fire native events, which create side effect with the fake event of iScroll. */
var target = e.target;
while (target.nodeType != 1) {
target = target.parentNode;
}
if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA' && target.tagName != 'LABEL') {
// Remove focus from input elements, so that the keyboard and the mouse cursor is hidden
var elements = [];
var inputElements = qx.lang.Array.cast(document.getElementsByTagName("input"), Array);
var textAreaElements = qx.lang.Array.cast(document.getElementsByTagName("textarea"), Array);
elements = elements.concat(inputElements);
elements = elements.concat(textAreaElements);
for (var i = 0, length = elements.length; i < length; i++) {
elements[i].blur();
}
e.preventDefault();
}
}
};
},
/**
* Registers all needed event listener.
*/
__registerEventListeners : function()
{
qx.event.Registration.addListener(window, "orientationchange", this._refresh, this);
qx.event.Registration.addListener(window, "resize", this._refresh, this);
this.addListener("touchmove", qx.bom.Event.stopPropagation);
this.addListener("domupdated", this._refresh, this);
},
/**
* Unregisters all needed event listener.
*/
__unregisterEventListeners : function()
{
qx.event.Registration.removeListener(window, "orientationchange", this._refresh, this);
qx.event.Registration.removeListener(window, "resize", this._refresh, this);
this.removeListener("touchmove", qx.bom.Event.stopPropagation);
this.removeListener("domupdated", this._refresh, this);
},
/**
* Load callback. Called when the iScroll script is loaded.
*
* @param request {qx.bom.request.Script} The Script request object
*/
__onScrollLoaded : function(request)
{
if (request.status < 400)
{
if(!this.isDisposed()) {
this._setScroll(this.__createScrollInstance());
this._scrollTo(this._currentX, this._currentY);
}
} else {
if (qx.core.Environment.get("qx.debug"))
{
this.error("Could not load iScroll");
}
}
},
/**
* Setter for the scroll instance.
*
* @param scroll {Object} iScroll instance.
*/
_setScroll : function(scroll)
{
this.__scroll = scroll;
},
/**
* Delegation method for iScroll. Disabled the iScroll objects.
* Prevents any further scrolling of this container.
*/
disable : function() {
if(this.__scroll) {
this.__scroll.disable();
}
},
/**
* Delegation method for iScroll. Enables the iScroll object.
*/
enable : function() {
if(this.__scroll) {
this.__scroll.enable();
}
},
/**
* Calls the refresh function of iScroll. Needed to recalculate the
* scrolling container.
*/
_refresh : function()
{
if (this.__scroll) {
this.__scroll.refresh();
}
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct : function()
{
this.__unregisterEventListeners();
// Cleanup iScroll
if (this.__scroll) {
this.__scroll.destroy();
}
this.__scroll = null;
}
});