(function(ionic) {
var NOOP = function() {};
var depreciated = function(name) {
console.error('Method not available in native scrolling: ' + name);
};
ionic.views.ScrollNative = ionic.views.View.inherit({
initialize: function(options) {
var self = this;
self.__container = self.el = options.el;
self.__content = options.el.firstElementChild;
self.isNative = true;
self.__scrollTop = self.el.scrollTop;
self.__scrollLeft = self.el.scrollLeft;
self.__clientHeight = self.__content.clientHeight,
self.__clientWidth = self.__content.clientWidth,
self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0),
self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0),
self.options = {
freeze: false,
getContentWidth: function() {
return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
},
getContentHeight: function() {
return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
}
};
for (var key in options) {
self.options[key] = options[key];
}
/**
* Sets isScrolling to true, and automatically deactivates if not called again in 80ms.
*/
self.onScroll = function(event) {
if (!ionic.scroll.isScrolling) {
ionic.scroll.isScrolling = true;
}
clearTimeout(self.scrollTimer);
self.scrollTimer = setTimeout(function() {
ionic.scroll.isScrolling = false;
}, 80);
};
self.freeze = NOOP;
self.__initEventHandlers();
},
/** Methods not used in native scrolling */
__callback: function() {depreciated('__callback');},
zoomTo: function() {depreciated('zoomTo');},
zoomBy: function() {depreciated('zoomBy');},
activatePullToRefresh: function() {depreciated('activatePullToRefresh');},
/**
* Returns the scroll position and zooming values
*
* @return {Map} `left` and `top` scroll position and `zoom` level
*/
resize: function(continueScrolling) {
var self = this;
if (!self.__container || !self.options) return;
// Update Scroller dimensions for changed content
// Add padding to bottom of content
self.setDimensions(
self.__container.clientWidth,
self.__container.clientHeight,
self.options.getContentWidth(),
self.options.getContentHeight(),
continueScrolling
);
},
/**
* Initialize the scrollview
* In native scrolling, this only means we need to gather size information
*/
run: function() {
this.resize();
},
/**
* Returns the scroll position and zooming values
*
* @return {Map} `left` and `top` scroll position and `zoom` level
*/
getValues: function() {
var self = this;
self.update();
return {
left: self.__scrollLeft,
top: self.__scrollTop,
zoom: 1
};
},
/**
* Updates the __scrollLeft and __scrollTop values to el's current value
*/
update: function() {
var self = this;
self.__scrollLeft = self.el.scrollLeft;
self.__scrollTop = self.el.scrollTop;
},
/**
* Configures the dimensions of the client (outer) and content (inner) elements.
* Requires the available space for the outer element and the outer size of the inner element.
* All values which are falsy (null or zero etc.) are ignored and the old value is kept.
*
* @param clientWidth {Integer} Inner width of outer element
* @param clientHeight {Integer} Inner height of outer element
* @param contentWidth {Integer} Outer width of inner element
* @param contentHeight {Integer} Outer height of inner element
*/
setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
var self = this;
if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
// this scrollview isn't rendered, don't bother
return;
}
// Only update values which are defined
if (clientWidth === +clientWidth) {
self.__clientWidth = clientWidth;
}
if (clientHeight === +clientHeight) {
self.__clientHeight = clientHeight;
}
if (contentWidth === +contentWidth) {
self.__contentWidth = contentWidth;
}
if (contentHeight === +contentHeight) {
self.__contentHeight = contentHeight;
}
// Refresh maximums
self.__computeScrollMax();
},
/**
* Returns the maximum scroll values
*
* @return {Map} `left` and `top` maximum scroll values
*/
getScrollMax: function() {
return {
left: this.__maxScrollLeft,
top: this.__maxScrollTop
};
},
/**
* Scrolls by the given amount in px.
*
* @param left {Number} Horizontal scroll position, keeps current if value is null
* @param top {Number} Vertical scroll position, keeps current if value is null
* @param animate {Boolean} Whether the scrolling should happen using an animation
*/
scrollBy: function(left, top, animate) {
var self = this;
// update scroll vars before refferencing them
self.update();
var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
},
/**
* Scrolls to the given position in px.
*
* @param left {Number} Horizontal scroll position, keeps current if value is null
* @param top {Number} Vertical scroll position, keeps current if value is null
* @param animate {Boolean} Whether the scrolling should happen using an animation
*/
scrollTo: function(left, top, animate) {
var self = this;
if (!animate) {
self.el.scrollTop = top;
self.el.scrollLeft = left;
self.resize();
return;
}
animateScroll(top, left);
function animateScroll(Y, X) {
// scroll animation loop w/ easing
// credit https://gist.github.com/dezinezync/5487119
var start = Date.now(),
duration = 1000, //milliseconds
fromY = self.el.scrollTop,
fromX = self.el.scrollLeft;
if (fromY === Y && fromX === X) {
self.resize();
return; /* Prevent scrolling to the Y point if already there */
}
// decelerating to zero velocity
function easeOutCubic(t) {
return (--t) * t * t + 1;
}
// scroll loop
function animateScrollStep() {
var currentTime = Date.now(),
time = Math.min(1, ((currentTime - start) / duration)),
// where .5 would be 50% of time on a linear scale easedT gives a
// fraction based on the easing method
easedT = easeOutCubic(time);
if (fromY != Y) {
self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10);
}
if (fromX != X) {
self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10);
}
if (time < 1) {
ionic.requestAnimationFrame(animateScrollStep);
} else {
// done
self.resize();
}
}
// start scroll loop
ionic.requestAnimationFrame(animateScrollStep);
}
},
/*
---------------------------------------------------------------------------
PRIVATE API
---------------------------------------------------------------------------
*/
/**
* If the scroll view isn't sized correctly on start, wait until we have at least some size
*/
__waitForSize: function() {
var self = this;
clearTimeout(self.__sizerTimeout);
var sizer = function() {
self.resize(true);
};
sizer();
self.__sizerTimeout = setTimeout(sizer, 500);
},
/**
* Recomputes scroll minimum values based on client dimensions and content dimensions.
*/
__computeScrollMax: function() {
var self = this;
self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
self.__didWaitForSize = true;
self.__waitForSize();
}
},
__initEventHandlers: function() {
var self = this;
// Event Handler
var container = self.__container;
// should be unnecessary in native scrolling, but keep in case bugs show up
self.scrollChildIntoView = NOOP;
self.resetScrollView = function(e) {
//return scrollview to original height once keyboard has hidden
if (self.isScrolledIntoView) {
self.isScrolledIntoView = false;
container.style.height = "";
container.style.overflow = "";
self.resize();
ionic.scroll.isScrolling = false;
}
};
container.addEventListener('resetScrollView', self.resetScrollView);
container.addEventListener('scroll', self.onScroll);
//Broadcasted when keyboard is shown on some platforms.
//See js/utils/keyboard.js
container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
container.addEventListener('resetScrollView', self.resetScrollView);
},
__cleanup: function() {
var self = this;
var container = self.__container;
container.removeEventListener('resetScrollView', self.resetScrollView);
container.removeEventListener('scroll', self.onScroll);
container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
container.removeEventListener('resetScrollView', self.resetScrollView);
ionic.tap.removeClonedInputs(container, self);
delete self.__container;
delete self.__content;
delete self.__indicatorX;
delete self.__indicatorY;
delete self.options.el;
self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
container = null;
}
});
})(ionic);