',
+
+ compile: function(element, attr, transclude) {
+ return function($scope, $element, $attr) {
+ var lv = new ionic.views.ListView({
+ el: $element[0],
+ listEl: $element[0].children[0],
+ isVirtual: true,
+ itemHeight: $scope.itemHeight,
+ renderViewport: function(high, low, start, end) {
+ console.log('RENDER VIEWPORT', high, low, start, end);
+ }
+ });
+
if(attr.animation) {
$element.addClass(attr.animation);
}
diff --git a/dist/js/ionic.js b/dist/js/ionic.js
index b7f642680e..509bf60e4a 100644
--- a/dist/js/ionic.js
+++ b/dist/js/ionic.js
@@ -1865,6 +1865,693 @@ window.ionic = {
})(window.ionic);
;
(function(ionic) {
+'use strict';
+ ionic.views.View = function() {
+ this.initialize.apply(this, arguments);
+ };
+
+ ionic.views.View.inherit = ionic.inherit;
+
+ ionic.extend(ionic.views.View.prototype, {
+ initialize: function() {}
+ });
+
+})(window.ionic);
+;
+/**
+ * ionic.views.Scroll. Portions lovingly adapted from the great iScroll 5, which is
+ * also MIT licensed.
+ * iScroll v5.0.5 ~ (c) 2008-2013 Matteo Spinelli ~ http://cubiq.org/license
+ *
+ * Think of ionic.views.Scroll like a Javascript version of UIScrollView or any
+ * scroll container in any UI library. You could just use -webkit-overflow-scrolling: touch,
+ * but you lose control over scroll behavior that native developers have with things
+ * like UIScrollView, and you don't get events after the finger stops touching the
+ * device (after a flick, for example).
+ *
+ * Some people are afraid of using Javascript powered scrolling, but
+ * with today's devices, Javascript is probably the best solution for
+ * scrolling in hybrid apps. Someone's code is running somewhere, even on native, right?
+ */
+(function(ionic) {
+'use strict';
+
+ // Some easing functions for animations
+ var EASING_FUNCTIONS = {
+ quadratic: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
+ circular: 'cubic-bezier(0.1, 0.57, 0.1, 1)',
+ circular2: 'cubic-bezier(0.075, 0.82, 0.165, 1)',
+
+ bounce: 'cubic-bezier(.02,.69,.67,1)',
+
+ // It closes like a high-end toilet seat. Fast, then nice and slow.
+ // Thanks to our @xtheglobe for that.
+ toiletSeat: 'cubic-bezier(0.05, 0.60, 0.05, 0.60)'
+ };
+
+ ionic.views.Scroll = ionic.views.View.inherit({
+
+ initialize: function(opts) {
+ var _this = this;
+
+ // Extend the options with our defaults
+ opts = ionic.Utils.extend({
+ decelerationRate: ionic.views.Scroll.prototype.DECEL_RATE_NORMAL,
+ dragThreshold: 10,
+
+ // Resistance when scrolling too far up or down
+ rubberBandResistance: 3,
+
+ // Scroll event names. These are custom so can be configured
+ scrollEventName: 'momentumScrolled',
+ scrollEndEventName: 'momentumScrollEnd',
+
+ // How frequently to fire scroll events in the case of
+ // a flick or momentum scroll where the finger is no longer
+ // touching the screen. If your event handler is a performance
+ // hog, change this millisecond value to cut down on the frequency
+ // of events triggered in those instances.
+ inertialEventInterval: 50,
+
+ // How quickly to scroll with a mouse wheel. 20 is a good default
+ mouseWheelSpeed: 20,
+
+ // Invert the mouse wheel? This makes sense on new Macbooks, but
+ // nowhere else.
+ invertWheel: false,
+
+ // Enable vertical scrolling
+ isVerticalEnabled: true,
+
+ // Enable horizontal scrolling
+ isHorizontalEnabled: false,
+
+ // The easing function to use for bouncing up or down on the bounds
+ // of the scrolling area
+ bounceEasing: EASING_FUNCTIONS.bounce,
+
+ //how long to take when bouncing back in a rubber band
+ bounceTime: 600
+ }, opts);
+
+ ionic.extend(this, opts);
+
+ this.el = opts.el;
+
+ this.y = 0;
+ this.x = 0;
+
+ // Listen for drag and release events
+ ionic.onGesture('drag', function(e) {
+ _this._handleDrag(e);
+ }, this.el);
+ ionic.onGesture('release', function(e) {
+ _this._handleEndDrag(e);
+ }, this.el);
+ ionic.on('mousewheel', function(e) {
+ _this._wheel(e);
+ }, this.el);
+ ionic.on('DOMMouseScroll', function(e) {
+ _this._wheel(e);
+ }, this.el);
+ ionic.on(this.scrollEndEventName, function(e) {
+ _this._onScrollEnd(e);
+ }, this.el);
+ ionic.on('webkitTransitionEnd', function(e) {
+ _this._onTransitionEnd(e);
+ });
+ },
+
+ /**
+ * Scroll to the given X and Y point, taking
+ * the given amount of time, with the given
+ * easing function defined as a CSS3 timing function.
+ *
+ * Note: the x and y values will be converted to negative offsets due to
+ * the way scrolling works internally.
+ *
+ * @param {float} the x position to scroll to (CURRENTLY NOT SUPPORTED!)
+ * @param {float} the y position to scroll to
+ * @param {float} the time to take scrolling to the new position
+ * @param {easing} the animation function to use for easing
+ */
+ scrollTo: function(x, y, time, easing) {
+ this._scrollTo(-x, -y, time, easing);
+ },
+
+ _scrollTo: function(x, y, time, easing) {
+ var _this = this;
+
+ time = time || 0;
+
+ var start = Date.now();
+
+ easing = easing || 'cubic-bezier(0.1, 0.57, 0.1, 1)';
+ var easingValues = easing.replace('cubic-bezier(', '').replace(')', '').split(',');
+ easingValues = [parseFloat(easingValues[0]), parseFloat(easingValues[1]), parseFloat(easingValues[2]), parseFloat(easingValues[3])];
+
+ var cubicBezierFunction = ionic.Animator.getCubicBezier(easingValues[0], easingValues[1], easingValues[2], easingValues[3], time);
+
+ var ox = this.x, oy = this.y;
+
+
+ var el = this.el;
+
+ if(x !== null) {
+ this.x = x;
+ } else {
+ x = this.x;
+ }
+ if(y !== null) {
+ this.y = y;
+ } else {
+ y = this.y;
+ }
+
+ if(ox == x && oy == y) {
+ time = 0;
+ }
+
+ if(time == Infinity) {
+ debugger;
+ }
+
+ var dx = ox - x;
+ var dy = oy - y;
+
+ el.offsetHeight;
+ el.style.webkitTransitionTimingFunction = easing;
+ el.style.webkitTransitionDuration = time;
+ el.style.webkitTransform = 'translate3d(' + x + 'px,' + y + 'px, 0)';
+
+ // Stop any other momentum event callbacks
+ clearTimeout(this._momentumStepTimeout);
+
+ // Start triggering events as the element scrolls from inertia.
+ // This is important because we need to receive scroll events
+ // even after a "flick" and adjust, etc.
+ if(time > 0) {
+ this._momentumStepTimeout = setTimeout(function eventNotify() {
+ // Calculate where in the animation process we might be
+ var diff = Math.min(time, Math.abs(Date.now() - start));
+
+ // How far along in time have we moved
+ var timeRatio = diff / time;
+
+ // Interpolate the transition values, using the same
+ // cubic bezier animation function used in the transition.
+ var bx = ox - dx * cubicBezierFunction(timeRatio);
+ var by = oy - dy * cubicBezierFunction(timeRatio);
+
+ _this.didScroll && _this.didScroll({
+ target: _this.el,
+ scrollLeft: -bx,
+ scrollTop: -by
+ });
+ ionic.trigger(_this.scrollEventName, {
+ target: _this.el,
+ scrollLeft: -bx,
+ scrollTop: -by
+ });
+
+ if(_this._isDragging) {
+ _this._momentumStepTimeout = setTimeout(eventNotify, _this.inertialEventInterval);
+ }
+ }, this.inertialEventInterval);
+ } else {
+ this.didScroll && this.didScroll({
+ target: this.el,
+ scrollLeft: -this.x,
+ scrollTop: -this.y
+ });
+ ionic.trigger(this.scrollEventName, {
+ target: this.el,
+ scrollLeft: -this.x,
+ scrollTop: -this.y
+ });
+ }
+ },
+
+ needsWrapping: function() {
+ var _this = this;
+
+ var totalWidth = this.el.scrollWidth;
+ var totalHeight = this.el.scrollHeight;
+ var parentWidth = this.el.parentNode.offsetWidth;
+ var parentHeight = this.el.parentNode.offsetHeight;
+
+ var maxX = Math.min(0, (-totalWidth + parentWidth));
+ var maxY = Math.min(0, (-totalHeight + parentHeight));
+
+ if (this.isHorizontalEnabled && (this.x > 0 || this.x < maxX)) {
+ return true;
+ }
+
+ if (this.isVerticalEnabled && (this.y > 0 || this.y < maxY)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * If the scroll position is outside the current bounds,
+ * animate it back.
+ */
+ wrapScrollPosition: function(transitionTime) {
+ var _this = this;
+
+ var totalWidth = _this.el.scrollWidth;
+ var totalHeight = _this.el.scrollHeight;
+ var parentWidth = _this.el.parentNode.offsetWidth;
+ var parentHeight = _this.el.parentNode.offsetHeight;
+
+ var maxX = Math.min(0, (-totalWidth + parentWidth));
+ var maxY = Math.min(0, (-totalHeight + parentHeight));
+
+ //this._execEvent('scrollEnd');
+ var x = _this.x, y = _this.y;
+
+ if (!_this.isHorizontalEnabled || _this.x > 0) {
+ x = 0;
+ } else if ( _this.x < maxX) {
+ x = maxX;
+ }
+
+ if (!_this.isVerticalEnabled || _this.y > 0) {
+ y = 0;
+ } else if (_this.y < maxY) {
+ y = maxY;
+ }
+
+ // No change
+ if (x == _this.x && y == _this.y) {
+ return false;
+ }
+ _this._scrollTo(x, y, transitionTime || 0, _this.bounceEasing);
+
+ return true;
+ },
+
+ _wheel: function(e) {
+ var wheelDeltaX, wheelDeltaY,
+ newX, newY,
+ that = this;
+
+ var totalWidth = this.el.scrollWidth;
+ var totalHeight = this.el.scrollHeight;
+ var parentWidth = this.el.parentNode.offsetWidth;
+ var parentHeight = this.el.parentNode.offsetHeight;
+
+ var maxX = Math.min(0, (-totalWidth + parentWidth));
+ var maxY = Math.min(0, (-totalHeight + parentHeight));
+
+ // Execute the scrollEnd event after 400ms the wheel stopped scrolling
+ clearTimeout(this.wheelTimeout);
+ this.wheelTimeout = setTimeout(function () {
+ that._doneScrolling();
+ }, 400);
+
+ e.preventDefault();
+
+ if('wheelDeltaX' in e) {
+ wheelDeltaX = e.wheelDeltaX / 120;
+ wheelDeltaY = e.wheelDeltaY / 120;
+ } else if ('wheelDelta' in e) {
+ wheelDeltaX = wheelDeltaY = e.wheelDelta / 120;
+ } else if ('detail' in e) {
+ wheelDeltaX = wheelDeltaY = -e.detail / 3;
+ } else {
+ return;
+ }
+
+ wheelDeltaX *= this.mouseWheelSpeed;
+ wheelDeltaY *= this.mouseWheelSpeed;
+
+ if(!this.isVerticalEnabled) {
+ wheelDeltaX = wheelDeltaY;
+ wheelDeltaY = 0;
+ }
+
+ newX = this.x + (this.isHorizontalEnabled ? wheelDeltaX * (this.invertWheel ? -1 : 1) : 0);
+ newY = this.y + (this.isVerticalEnabled ? wheelDeltaY * (this.invertWheel ? -1 : 1) : 0);
+
+ if(newX > 0) {
+ newX = 0;
+ } else if (newX < maxX) {
+ newX = maxX;
+ }
+
+ if(newY > 0) {
+ newY = 0;
+ } else if (newY < maxY) {
+ newY = maxY;
+ }
+
+ this._scrollTo(newX, newY, 0);
+ },
+
+ _getMomentum: function (current, start, time, lowerMargin, wrapperSize) {
+ var distance = current - start,
+ speed = Math.abs(distance) / time,
+ destination,
+ duration,
+ deceleration = 0.0006;
+
+ // Calculate the final desination
+ destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
+ duration = speed / deceleration;
+
+ if(speed === 0) {
+ return {
+ destination: current,
+ duration: 0
+ };
+ }
+
+ // Check if the final destination needs to be rubber banded
+ if ( destination < lowerMargin ) {
+ // We have dragged too far down, snap back to the maximum
+ destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
+ distance = Math.abs(destination - current);
+ duration = distance / speed;
+ } else if ( destination > 0 ) {
+
+ // We have dragged too far up, snap back to 0
+ destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
+ distance = Math.abs(current) + destination;
+ duration = distance / speed;
+ }
+
+ return {
+ destination: Math.round(destination),
+ duration: duration
+ };
+ },
+
+ _onTransitionEnd: function(e) {
+ var _this = this;
+
+ if (e.target != this.el) {
+ return;
+ }
+
+ var needsWrapping = this.needsWrapping();
+
+ // Triggered to end scroll, once the final animation has ended
+ if(needsWrapping && this._didEndScroll) {
+ this._didEndScroll = false;
+ this._doneScrolling();
+ } else if(!needsWrapping) {
+ this._didEndScroll = false;
+ this._doneScrolling();
+ }
+
+ this.el.style.webkitTransitionDuration = '0';
+
+ window.requestAnimationFrame(function() {
+ if(_this.wrapScrollPosition(_this.bounceTime)) {
+ _this._didEndScroll = true;
+ }
+ });
+ },
+
+ _onScrollEnd: function() {
+ this._isDragging = false;
+ this._drag = null;
+ this.el.classList.remove('scroll-scrolling');
+
+ this.el.style.webkitTransitionDuration = '0';
+
+ clearTimeout(this._momentumStepTimeout)
+ },
+
+
+ _initDrag: function() {
+ this._onScrollEnd();
+ this._isStopped = false;
+ },
+
+
+ /**
+ * Initialize a drag by grabbing the content area to drag, and any other
+ * info we might need for the dragging.
+ */
+ _startDrag: function(e) {
+ var offsetX, content;
+
+ this._initDrag();
+
+ var scrollLeft = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
+ var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0;
+
+ var totalWidth = this.el.scrollWidth;
+ var totalHeight = this.el.scrollHeight;
+ var parentWidth = this.el.parentNode.offsetWidth;
+ var parentHeight = this.el.parentNode.offsetHeight;
+
+ var maxX = Math.min(0, (-totalWidth + parentWidth));
+ var maxY = Math.min(0, (-totalHeight + parentHeight));
+
+ // Check if we even have enough content to scroll, if not, don't start the drag
+ if((this.isHorizontalEnabled && maxX == 0) || (this.isVerticalEnabled && maxY == 0)) {
+ return;
+ }
+
+ this.x = scrollLeft;
+ this.y = scrollTop;
+
+ this._drag = {
+ direction: 'v',
+ pointX: e.gesture.touches[0].pageX,
+ pointY: e.gesture.touches[0].pageY,
+ startX: scrollLeft,
+ startY: scrollTop,
+ resist: 1,
+ startTime: Date.now()
+ };
+ },
+
+
+
+ /**
+ * Process the drag event to move the item to the left or right.
+ *
+ * This function needs to be as fast as possible to make sure scrolling
+ * performance is high.
+ */
+ _handleDrag: function(e) {
+ var _this = this;
+
+ var content;
+
+ // The drag stopped already, don't process this one
+ if(_this._isStopped) {
+ _this._initDrag();
+ return;
+ }
+
+ // We really aren't dragging
+ if(!_this._drag) {
+ _this._startDrag(e);
+ if(!_this._drag) { return; }
+ }
+
+ // Stop any default events during the drag
+ e.preventDefault();
+
+ var px = e.gesture.touches[0].pageX;
+ var py = e.gesture.touches[0].pageY;
+
+ var deltaX = px - _this._drag.pointX;
+ var deltaY = py - _this._drag.pointY;
+
+ _this._drag.pointX = px;
+ _this._drag.pointY = py;
+
+ // Check if we should start dragging. Check if we've dragged past the threshold.
+ if(!_this._isDragging &&
+ ((Math.abs(e.gesture.deltaY) > _this.dragThreshold) ||
+ (Math.abs(e.gesture.deltaX) > _this.dragThreshold))) {
+ _this._isDragging = true;
+ }
+
+ if(_this._isDragging) {
+ var drag = _this._drag;
+
+ // Request an animation frame to batch DOM reads/writes
+ window.requestAnimationFrame(function() {
+ // We are dragging, grab the current content height
+
+ var totalWidth = _this.el.scrollWidth;
+ var totalHeight = _this.el.scrollHeight;
+ var parentWidth = _this.el.parentNode.offsetWidth;
+ var parentHeight = _this.el.parentNode.offsetHeight;
+ var maxX = Math.min(0, (-totalWidth + parentWidth));
+ var maxY = Math.min(0, (-totalHeight + parentHeight));
+
+ // Grab current timestamp to keep our speend, etc.
+ // calculations in a window
+ var timestamp = Date.now();
+
+ // Calculate the new Y point for the container
+ // TODO: Only enable certain axes
+ var newX = _this.x + deltaX;
+ var newY = _this.y + deltaY;
+
+ if(newX > 0 || (-newX + parentWidth) > totalWidth) {
+ // Rubber band
+ newX = _this.x + deltaX / _this.rubberBandResistance;
+ }
+ // Check if the dragging is beyond the bottom or top
+ if(newY > 0 || (-newY + parentHeight) > totalHeight) {
+ // Rubber band
+ newY = _this.y + deltaY / _this.rubberBandResistance;
+ }
+
+ if(!_this.isHorizontalEnabled) {
+ newX = 0;
+ }
+ if(!_this.isVerticalEnabled) {
+ newY = 0;
+ }
+
+ // Update the new translated Y point of the container
+ _this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
+
+ // Store the last points
+ _this.x = newX;
+ _this.y = newY;
+
+ // Check if we need to reset the drag initial states if we've
+ // been dragging for a bit
+ if(timestamp - drag.startTime > 300) {
+ drag.startTime = timestamp;
+ drag.startX = _this.x;
+ drag.startY = _this.y;
+ }
+
+ _this.didScroll && _this.didScroll({
+ target: _this.el,
+ scrollLeft: -newX,
+ scrollTop: -newY
+ });
+
+ // Trigger a scroll event
+ ionic.trigger(_this.scrollEventName, {
+ target: _this.el,
+ scrollLeft: -newX,
+ scrollTop: -newY
+ });
+ });
+ }
+ },
+
+
+
+ _handleEndDrag: function(e) {
+ // We didn't have a drag, so just init and leave
+ if(!this._drag) {
+ this._initDrag();
+ return;
+ }
+
+ // Set a flag in case we don't cleanup completely after the
+ // drag animation so we can cleanup the next time a drag starts
+ this._isStopped = true;
+
+ // Animate to the finishing point
+ this._animateToStop(e);
+
+ },
+
+
+ // Find the stopping point given the current velocity and acceleration rate, and
+ // animate to that position
+ _animateToStop: function(e) {
+ var _this = this;
+ window.requestAnimationFrame(function() {
+
+ var drag = _this._drag;
+
+ // Calculate the viewport height and the height of the content
+ var totalWidth = _this.el.scrollWidth;
+ var totalHeight = _this.el.scrollHeight;
+
+ // The parent bounding box helps us figure max/min scroll amounts
+ var parentWidth = _this.el.parentNode.offsetWidth;
+ var parentHeight = _this.el.parentNode.offsetHeight;
+
+ // Calculate how long we've been dragging for, with a max of 300ms
+ var duration = Date.now() - _this._drag.startTime;
+ var time = 0;
+ var easing = '';
+
+ var newX = Math.round(_this.x);
+ var newY = Math.round(_this.y);
+
+ _this._scrollTo(newX, newY);
+
+
+ // Check if we just snap back to bounds
+ if(_this.wrapScrollPosition(_this.bounceTime)) {
+ return;
+ }
+
+ // If the duration is within reasonable bounds, enable momentum scrolling so we
+ // can "slide" to a finishing point
+ if(duration < 300) {
+ var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth);
+ var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight);
+ //var newX = momentumX.destination;
+ newX = momentumX.destination;
+ newY = momentumY.destination;
+
+ // Calculate the longest required time for the momentum animation and
+ // use that.
+ time = Math.max(momentumX.duration, momentumY.duration);
+ }
+
+ // If we've moved, we will need to scroll
+ if(newX != _this.x || newY != _this.y) {
+ // If the end position is out of bounds, change the function we use for easing
+ // to get a different animation for the rubber banding
+ if ( newX > 0 || newX < (-totalWidth + parentWidth) || newY > 0 || newY < (-totalHeight + parentHeight)) {
+ easing = EASING_FUNCTIONS.bounce;
+ }
+
+ _this._scrollTo(newX, newY, time, easing);
+ } else {
+ // We are done
+ _this._doneScrolling();
+ }
+ });
+ },
+
+ /**
+ * Trigger a done scrolling event.
+ */
+ _doneScrolling: function() {
+ this.didStopScrolling && this.didStopScrolling({
+ target: this.el,
+ scrollLeft: this.x,
+ scrollTop: this.y
+ });
+ ionic.trigger(this.scrollEndEventName, {
+ target: this.el,
+ scrollLeft: this.x,
+ scrollTop: this.y
+ });
+ }
+ }, {
+ DECEL_RATE_NORMAL: 0.998,
+ DECEL_RATE_FAST: 0.99,
+ DECEL_RATE_SLOW: 0.996,
+ });
+
+})(ionic);
+;
+(function(ionic) {
'use strict';
/**
* An ActionSheet is the slide up menu popularized on iOS.
@@ -2836,26 +3523,46 @@ window.ionic = {
refresher.style.height = '0px';
},
+ /**
+ * If we scrolled and have virtual mode enabled, compute the window
+ * of active elements in order to figure out the viewport to render.
+ */
didScroll: function(e) {
if(this.isVirtual) {
var itemHeight = this.itemHeight;
+
+ // TODO: This would be inaccurate if we are windowed
var totalItems = this.listEl.children.length;
+ // Grab the total height of the list
var scrollHeight = e.target.scrollHeight;
+
+ // Get the viewport height
var viewportHeight = this.el.parentNode.offsetHeight;
+ // scrollTop is the current scroll position
var scrollTop = e.scrollTop;
+ // High water is the pixel position of the first element to include (everything before
+ // that will be removed)
var highWater = Math.max(0, e.scrollTop + this.virtualRemoveThreshold);
+
+ // Low water is the pixel position of the last element to include (everything after
+ // that will be removed)
var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + this.virtualAddThreshold);
+ // Compute how many items per viewport size can show
var itemsPerViewport = Math.floor((lowWater - highWater) / itemHeight);
+
+ // Get the first and last elements in the list based on how many can fit
+ // between the pixel range of lowWater and highWater
var first = parseInt(Math.abs(highWater / itemHeight));
var last = parseInt(Math.abs(lowWater / itemHeight));
-
+ // Get the items we need to remove
this._virtualItemsToRemove = Array.prototype.slice.call(this.listEl.children, 0, first);
+ // Grab the nodes we will be showing
var nodes = Array.prototype.slice.call(this.listEl.children, first, first + itemsPerViewport);
this.renderViewport && this.renderViewport(highWater, lowWater, first, last);
@@ -3127,663 +3834,6 @@ window.ionic = {
}
};
-})(ionic);
-;
-/**
- * ionic.views.Scroll. Portions lovingly adapted from the great iScroll 5, which is
- * also MIT licensed.
- * iScroll v5.0.5 ~ (c) 2008-2013 Matteo Spinelli ~ http://cubiq.org/license
- *
- * Think of ionic.views.Scroll like a Javascript version of UIScrollView or any
- * scroll container in any UI library. You could just use -webkit-overflow-scrolling: touch,
- * but you lose control over scroll behavior that native developers have with things
- * like UIScrollView, and you don't get events after the finger stops touching the
- * device (after a flick, for example).
- *
- * Some people are afraid of using Javascript powered scrolling, but
- * with today's devices, Javascript is probably the best solution for
- * scrolling in hybrid apps. Someone's code is running somewhere, even on native, right?
- */
-(function(ionic) {
-'use strict';
-
- // Some easing functions for animations
- var EASING_FUNCTIONS = {
- quadratic: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
- circular: 'cubic-bezier(0.1, 0.57, 0.1, 1)',
- circular2: 'cubic-bezier(0.075, 0.82, 0.165, 1)',
-
- bounce: 'cubic-bezier(.02,.69,.67,1)',
-
- // It closes like a high-end toilet seat. Fast, then nice and slow.
- // Thanks to our @xtheglobe for that.
- toiletSeat: 'cubic-bezier(0.05, 0.60, 0.05, 0.60)'
- };
-
- ionic.views.Scroll = ionic.views.View.inherit({
-
- initialize: function(opts) {
- var _this = this;
-
- // Extend the options with our defaults
- opts = ionic.Utils.extend({
- decelerationRate: ionic.views.Scroll.prototype.DECEL_RATE_NORMAL,
- dragThreshold: 10,
-
- // Resistance when scrolling too far up or down
- rubberBandResistance: 3,
-
- // Scroll event names. These are custom so can be configured
- scrollEventName: 'momentumScrolled',
- scrollEndEventName: 'momentumScrollEnd',
-
- // How frequently to fire scroll events in the case of
- // a flick or momentum scroll where the finger is no longer
- // touching the screen. If your event handler is a performance
- // hog, change this millisecond value to cut down on the frequency
- // of events triggered in those instances.
- inertialEventInterval: 50,
-
- // How quickly to scroll with a mouse wheel. 20 is a good default
- mouseWheelSpeed: 20,
-
- // Invert the mouse wheel? This makes sense on new Macbooks, but
- // nowhere else.
- invertWheel: false,
-
- // Enable vertical scrolling
- isVerticalEnabled: true,
-
- // Enable horizontal scrolling
- isHorizontalEnabled: false,
-
- // The easing function to use for bouncing up or down on the bounds
- // of the scrolling area
- bounceEasing: EASING_FUNCTIONS.bounce,
-
- //how long to take when bouncing back in a rubber band
- bounceTime: 600
- }, opts);
-
- ionic.extend(this, opts);
-
- this.el = opts.el;
-
- this.y = 0;
- this.x = 0;
-
- // Listen for drag and release events
- ionic.onGesture('drag', function(e) {
- _this._handleDrag(e);
- }, this.el);
- ionic.onGesture('release', function(e) {
- _this._handleEndDrag(e);
- }, this.el);
- ionic.on('mousewheel', function(e) {
- _this._wheel(e);
- }, this.el);
- ionic.on('DOMMouseScroll', function(e) {
- _this._wheel(e);
- }, this.el);
- ionic.on(this.scrollEndEventName, function(e) {
- _this._onScrollEnd(e);
- }, this.el);
- ionic.on('webkitTransitionEnd', function(e) {
- _this._onTransitionEnd(e);
- });
- },
-
- /**
- * Scroll to the given X and Y point, taking
- * the given amount of time, with the given
- * easing function defined as a CSS3 timing function.
- *
- * Note: the x and y values will be converted to negative offsets due to
- * the way scrolling works internally.
- *
- * @param {float} the x position to scroll to (CURRENTLY NOT SUPPORTED!)
- * @param {float} the y position to scroll to
- * @param {float} the time to take scrolling to the new position
- * @param {easing} the animation function to use for easing
- */
- scrollTo: function(x, y, time, easing) {
- this._scrollTo(-x, -y, time, easing);
- },
-
- _scrollTo: function(x, y, time, easing) {
- var _this = this;
-
- var start = Date.now();
-
- easing = easing || 'cubic-bezier(0.1, 0.57, 0.1, 1)';
- var easingValues = easing.replace('cubic-bezier(', '').replace(')', '').split(',');
- easingValues = [parseFloat(easingValues[0]), parseFloat(easingValues[1]), parseFloat(easingValues[2]), parseFloat(easingValues[3])];
-
- var cubicBezierFunction = ionic.Animator.getCubicBezier(easingValues[0], easingValues[1], easingValues[2], easingValues[3], time);
-
- var ox = this.x, oy = this.y;
-
-
- var el = this.el;
-
- if(x !== null) {
- this.x = x;
- } else {
- x = this.x;
- }
- if(y !== null) {
- this.y = y;
- } else {
- y = this.y;
- }
-
- if(ox == x && oy == y) {
- time = 0;
- }
-
- var dx = ox - x;
- var dy = oy - y;
-
- el.offsetHeight;
- el.style.webkitTransitionTimingFunction = easing;
- el.style.webkitTransitionDuration = time;
- el.style.webkitTransform = 'translate3d(' + x + 'px,' + y + 'px, 0)';
-
- // Stop any other momentum event callbacks
- clearTimeout(this._momentumStepTimeout);
-
- // Start triggering events as the element scrolls from inertia.
- // This is important because we need to receive scroll events
- // even after a "flick" and adjust, etc.
- if(time > 0) {
- this._momentumStepTimeout = setTimeout(function eventNotify() {
- // Calculate where in the animation process we might be
- var diff = Math.min(time, Math.abs(Date.now() - start));
-
- // How far along in time have we moved
- var timeRatio = diff / time;
-
- // Interpolate the transition values, using the same
- // cubic bezier animation function used in the transition.
- var bx = ox - dx * cubicBezierFunction(timeRatio);
- var by = oy - dy * cubicBezierFunction(timeRatio);
-
- _this.didScroll && _this.didScroll({
- target: _this.el,
- scrollLeft: -bx,
- scrollTop: -by
- });
- ionic.trigger(_this.scrollEventName, {
- target: _this.el,
- scrollLeft: -bx,
- scrollTop: -by
- });
-
- if(_this._isDragging) {
- _this._momentumStepTimeout = setTimeout(eventNotify, _this.inertialEventInterval);
- }
- }, this.inertialEventInterval);
- } else {
- this.didScroll && this.didScroll({
- target: this.el,
- scrollLeft: -this.x,
- scrollTop: -this.y
- });
- ionic.trigger(this.scrollEventName, {
- target: this.el,
- scrollLeft: -this.x,
- scrollTop: -this.y
- });
- }
- },
-
- needsWrapping: function() {
- var _this = this;
-
- var totalWidth = this.el.scrollWidth;
- var totalHeight = this.el.scrollHeight;
- var parentWidth = this.el.parentNode.offsetWidth;
- var parentHeight = this.el.parentNode.offsetHeight;
-
- var maxX = Math.min(0, (-totalWidth + parentWidth));
- var maxY = Math.min(0, (-totalHeight + parentHeight));
-
- if (this.isHorizontalEnabled && (this.x > 0 || this.x < maxX)) {
- return true;
- }
-
- if (this.isVerticalEnabled && (this.y > 0 || this.y < maxY)) {
- return true;
- }
-
- return false;
- },
-
- /**
- * If the scroll position is outside the current bounds,
- * animate it back.
- */
- wrapScrollPosition: function(transitionTime) {
- var _this = this;
-
- var totalWidth = _this.el.scrollWidth;
- var totalHeight = _this.el.scrollHeight;
- var parentWidth = _this.el.parentNode.offsetWidth;
- var parentHeight = _this.el.parentNode.offsetHeight;
-
- var maxX = Math.min(0, (-totalWidth + parentWidth));
- var maxY = Math.min(0, (-totalHeight + parentHeight));
-
- //this._execEvent('scrollEnd');
- var x = _this.x, y = _this.y;
-
- if (!_this.isHorizontalEnabled || _this.x > 0) {
- x = 0;
- } else if ( _this.x < maxX) {
- x = maxX;
- }
-
- if (!_this.isVerticalEnabled || _this.y > 0) {
- y = 0;
- } else if (_this.y < maxY) {
- y = maxY;
- }
-
- // No change
- if (x == _this.x && y == _this.y) {
- return false;
- }
- _this._scrollTo(x, y, transitionTime || 0, _this.bounceEasing);
-
- return true;
- },
-
- _wheel: function(e) {
- var wheelDeltaX, wheelDeltaY,
- newX, newY,
- that = this;
-
- var totalWidth = this.el.scrollWidth;
- var totalHeight = this.el.scrollHeight;
- var parentWidth = this.el.parentNode.offsetWidth;
- var parentHeight = this.el.parentNode.offsetHeight;
-
- var maxX = Math.min(0, (-totalWidth + parentWidth));
- var maxY = Math.min(0, (-totalHeight + parentHeight));
-
- // Execute the scrollEnd event after 400ms the wheel stopped scrolling
- clearTimeout(this.wheelTimeout);
- this.wheelTimeout = setTimeout(function () {
- that._doneScrolling();
- }, 400);
-
- e.preventDefault();
-
- if('wheelDeltaX' in e) {
- wheelDeltaX = e.wheelDeltaX / 120;
- wheelDeltaY = e.wheelDeltaY / 120;
- } else if ('wheelDelta' in e) {
- wheelDeltaX = wheelDeltaY = e.wheelDelta / 120;
- } else if ('detail' in e) {
- wheelDeltaX = wheelDeltaY = -e.detail / 3;
- } else {
- return;
- }
-
- wheelDeltaX *= this.mouseWheelSpeed;
- wheelDeltaY *= this.mouseWheelSpeed;
-
- if(!this.isVerticalEnabled) {
- wheelDeltaX = wheelDeltaY;
- wheelDeltaY = 0;
- }
-
- newX = this.x + (this.isHorizontalEnabled ? wheelDeltaX * (this.invertWheel ? -1 : 1) : 0);
- newY = this.y + (this.isVerticalEnabled ? wheelDeltaY * (this.invertWheel ? -1 : 1) : 0);
-
- if(newX > 0) {
- newX = 0;
- } else if (newX < maxX) {
- newX = maxX;
- }
-
- if(newY > 0) {
- newY = 0;
- } else if (newY < maxY) {
- newY = maxY;
- }
-
- this._scrollTo(newX, newY, 0);
- },
-
- _getMomentum: function (current, start, time, lowerMargin, wrapperSize) {
- var distance = current - start,
- speed = Math.abs(distance) / time,
- destination,
- duration,
- deceleration = 0.0006;
-
- // Calculate the final desination
- destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
- duration = speed / deceleration;
-
- // Check if the final destination needs to be rubber banded
- if ( destination < lowerMargin ) {
- // We have dragged too far down, snap back to the maximum
- destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
- distance = Math.abs(destination - current);
- duration = distance / speed;
- } else if ( destination > 0 ) {
-
- // We have dragged too far up, snap back to 0
- destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
- distance = Math.abs(current) + destination;
- duration = distance / speed;
- }
-
- return {
- destination: Math.round(destination),
- duration: duration
- };
- },
-
- _onTransitionEnd: function(e) {
- var _this = this;
-
- if (e.target != this.el) {
- return;
- }
-
- var needsWrapping = this.needsWrapping();
-
- // Triggered to end scroll, once the final animation has ended
- if(needsWrapping && this._didEndScroll) {
- this._didEndScroll = false;
- this._doneScrolling();
- } else if(!needsWrapping) {
- this._didEndScroll = false;
- this._doneScrolling();
- }
-
- this.el.style.webkitTransitionDuration = '0';
-
- window.requestAnimationFrame(function() {
- if(_this.wrapScrollPosition(_this.bounceTime)) {
- _this._didEndScroll = true;
- }
- });
- },
-
- _onScrollEnd: function() {
- this._isDragging = false;
- this._drag = null;
- this.el.classList.remove('scroll-scrolling');
-
- this.el.style.webkitTransitionDuration = '0';
-
- clearTimeout(this._momentumStepTimeout)
- },
-
-
- _initDrag: function() {
- this._onScrollEnd();
- this._isStopped = false;
- },
-
-
- /**
- * Initialize a drag by grabbing the content area to drag, and any other
- * info we might need for the dragging.
- */
- _startDrag: function(e) {
- var offsetX, content;
-
- this._initDrag();
-
- var scrollLeft = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
- var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0;
-
- var totalWidth = this.el.scrollWidth;
- var totalHeight = this.el.scrollHeight;
- var parentWidth = this.el.parentNode.offsetWidth;
- var parentHeight = this.el.parentNode.offsetHeight;
-
- var maxX = Math.min(0, (-totalWidth + parentWidth));
- var maxY = Math.min(0, (-totalHeight + parentHeight));
-
- // Check if we even have enough content to scroll, if not, don't start the drag
- if((this.isHorizontalEnabled && maxX == 0) || (this.isVerticalEnabled && maxY == 0)) {
- return;
- }
-
- this.x = scrollLeft;
- this.y = scrollTop;
-
- this._drag = {
- direction: 'v',
- pointX: e.gesture.touches[0].pageX,
- pointY: e.gesture.touches[0].pageY,
- startX: scrollLeft,
- startY: scrollTop,
- resist: 1,
- startTime: Date.now()
- };
- },
-
-
-
- /**
- * Process the drag event to move the item to the left or right.
- *
- * This function needs to be as fast as possible to make sure scrolling
- * performance is high.
- */
- _handleDrag: function(e) {
- var _this = this;
-
- var content;
-
- // The drag stopped already, don't process this one
- if(_this._isStopped) {
- _this._initDrag();
- return;
- }
-
- // We really aren't dragging
- if(!_this._drag) {
- _this._startDrag(e);
- if(!_this._drag) { return; }
- }
-
- // Stop any default events during the drag
- e.preventDefault();
-
- var px = e.gesture.touches[0].pageX;
- var py = e.gesture.touches[0].pageY;
-
- var deltaX = px - _this._drag.pointX;
- var deltaY = py - _this._drag.pointY;
-
- _this._drag.pointX = px;
- _this._drag.pointY = py;
-
- // Check if we should start dragging. Check if we've dragged past the threshold.
- if(!_this._isDragging &&
- ((Math.abs(e.gesture.deltaY) > _this.dragThreshold) ||
- (Math.abs(e.gesture.deltaX) > _this.dragThreshold))) {
- _this._isDragging = true;
- }
-
- if(_this._isDragging) {
- var drag = _this._drag;
-
- // Request an animation frame to batch DOM reads/writes
- window.requestAnimationFrame(function() {
- // We are dragging, grab the current content height
-
- var totalWidth = _this.el.scrollWidth;
- var totalHeight = _this.el.scrollHeight;
- var parentWidth = _this.el.parentNode.offsetWidth;
- var parentHeight = _this.el.parentNode.offsetHeight;
- var maxX = Math.min(0, (-totalWidth + parentWidth));
- var maxY = Math.min(0, (-totalHeight + parentHeight));
-
- // Grab current timestamp to keep our speend, etc.
- // calculations in a window
- var timestamp = Date.now();
-
- // Calculate the new Y point for the container
- // TODO: Only enable certain axes
- var newX = _this.x + deltaX;
- var newY = _this.y + deltaY;
-
- if(newX > 0 || (-newX + parentWidth) > totalWidth) {
- // Rubber band
- newX = _this.x + deltaX / _this.rubberBandResistance;
- }
- // Check if the dragging is beyond the bottom or top
- if(newY > 0 || (-newY + parentHeight) > totalHeight) {
- // Rubber band
- newY = _this.y + deltaY / _this.rubberBandResistance;
- }
-
- if(!_this.isHorizontalEnabled) {
- newX = 0;
- }
- if(!_this.isVerticalEnabled) {
- newY = 0;
- }
-
- // Update the new translated Y point of the container
- _this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
-
- // Store the last points
- _this.x = newX;
- _this.y = newY;
-
- // Check if we need to reset the drag initial states if we've
- // been dragging for a bit
- if(timestamp - drag.startTime > 300) {
- drag.startTime = timestamp;
- drag.startX = _this.x;
- drag.startY = _this.y;
- }
-
- _this.didScroll && _this.didScroll({
- target: _this.el,
- scrollLeft: -newX,
- scrollTop: -newY
- });
-
- // Trigger a scroll event
- ionic.trigger(_this.scrollEventName, {
- target: _this.el,
- scrollLeft: -newX,
- scrollTop: -newY
- });
- });
- }
- },
-
-
-
- _handleEndDrag: function(e) {
- // We didn't have a drag, so just init and leave
- if(!this._drag) {
- this._initDrag();
- return;
- }
-
- // Set a flag in case we don't cleanup completely after the
- // drag animation so we can cleanup the next time a drag starts
- this._isStopped = true;
-
- // Animate to the finishing point
- this._animateToStop(e);
-
- },
-
-
- // Find the stopping point given the current velocity and acceleration rate, and
- // animate to that position
- _animateToStop: function(e) {
- var _this = this;
- window.requestAnimationFrame(function() {
-
- var drag = _this._drag;
-
- // Calculate the viewport height and the height of the content
- var totalWidth = _this.el.scrollWidth;
- var totalHeight = _this.el.scrollHeight;
-
- // The parent bounding box helps us figure max/min scroll amounts
- var parentWidth = _this.el.parentNode.offsetWidth;
- var parentHeight = _this.el.parentNode.offsetHeight;
-
- // Calculate how long we've been dragging for, with a max of 300ms
- var duration = Date.now() - _this._drag.startTime;
- var time = 0;
- var easing = '';
-
- var newX = Math.round(_this.x);
- var newY = Math.round(_this.y);
-
- _this._scrollTo(newX, newY);
-
-
- // Check if we just snap back to bounds
- if(_this.wrapScrollPosition(_this.bounceTime)) {
- return;
- }
-
- // If the duration is within reasonable bounds, enable momentum scrolling so we
- // can "slide" to a finishing point
- if(duration < 300) {
- var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth);
- var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight);
- //var newX = momentumX.destination;
- newX = momentumX.destination;
- newY = momentumY.destination;
-
- // Calculate the longest required time for the momentum animation and
- // use that.
- time = Math.max(momentumX.duration, momentumY.duration);
- }
-
- // If we've moved, we will need to scroll
- if(newX != _this.x || newY != _this.y) {
- // If the end position is out of bounds, change the function we use for easing
- // to get a different animation for the rubber banding
- if ( newX > 0 || newX < (-totalWidth + parentWidth) || newY > 0 || newY < (-totalHeight + parentHeight)) {
- easing = EASING_FUNCTIONS.bounce;
- }
-
- _this._scrollTo(newX, newY, time, easing);
- } else {
- // We are done
- _this._doneScrolling();
- }
- });
- },
-
- _doneScrolling: function() {
- this.didStopScrolling && this.didStopScrolling({
- target: this.el,
- scrollLeft: this.x,
- scrollTop: this.y
- });
- ionic.trigger(this.scrollEndEventName, {
- target: this.el,
- scrollLeft: this.x,
- scrollTop: this.y
- });
- }
- }, {
- DECEL_RATE_NORMAL: 0.998,
- DECEL_RATE_FAST: 0.99,
- DECEL_RATE_SLOW: 0.996,
- });
-
})(ionic);
;
(function(ionic) {
@@ -4412,14 +4462,17 @@ ionic.views.TabBar.prototype = {
;
(function(ionic) {
'use strict';
- ionic.views.View = function() {
+ ionic.controllers.ViewController = function(options) {
this.initialize.apply(this, arguments);
};
- ionic.views.View.inherit = ionic.inherit;
+ ionic.controllers.ViewController.inherit = ionic.inherit;
- ionic.extend(ionic.views.View.prototype, {
- initialize: function() {}
+ ionic.extend(ionic.controllers.ViewController.prototype, {
+ initialize: function() {},
+ // Destroy this view controller, including all child views
+ destroy: function() {
+ }
});
})(window.ionic);
@@ -4579,122 +4632,6 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
}
});
-})(window.ionic);
-;
-/**
- * Adapted from Backbone.js
- */
-(function(ionic) {
-'use strict';
- var optionalParam = /\((.*?)\)/g;
- var namedParam = /(\(\?)?:\w+/g;
- var splatParam = /\*\w+/g;
- var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
-
- // Cached regex for stripping a leading hash/slash and trailing space.
- var routeStripper = /^[#\/]|\s+$/g;
-
- // Cached regex for stripping leading and trailing slashes.
- var rootStripper = /^\/+|\/+$/g;
-
- // Cached regex for removing a trailing slash.
- var trailingSlash = /\/$/;
-
- ionic.controllers.RouteViewController = ionic.controllers.ViewController.inherit({
- initialize: function(options) {
- this.options = options;
-
- this.root = this.options.root || '/';
- this.root = ('/' + this.root + '/').replace(rootStripper, '/');
-
- this.handlers = [];
-
- this._bindEvents();
-
- this.location = window.location;
- this.history = window.history;
- },
-
- when: function(route, callback) {
- var _this = this;
-
- route = this._routeToRegExp(route);
-
- this.handlers.unshift({
- route: route,
- callback: function(fragment) {
- var args = _this._extractParameters(route, fragment);
- callback && callback.apply(_this, args);
- }
- });
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp: function(route) {
- route = route.replace(escapeRegExp, '\\$&')
- .replace(optionalParam, '(?:$1)?')
- .replace(namedParam, function(match, optional){
- return optional ? match : '([^\/]+)';
- })
- .replace(splatParam, '(.*?)');
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted decoded parameters. Empty or unmatched parameters will be
- // treated as `null` to normalize cross-browser behavior.
- _extractParameters: function(route, fragment) {
- var params = route.exec(fragment).slice(1);
- var extracted = [];
- for(var i = 0; i < params.length; i++) {
- if(param) {
- extracted.push(decodeURIComponent(param));
- }
- }
- },
-
- _bindEvents: function() {
- var _this = this;
-
- window.addEventListener('popstate', function(event) {
- _this.checkUrl(event);
- });
- },
- checkUrl: function(e) {
- var current = this.getFragment();
- if (current === this.fragment) return false;
- this.loadUrl() || this.loadUrl(this.getHash());
- },
- getFragment: function(fragment, forcePushState) {
- if (fragment == null) {
- fragment = this.location.pathname;
- var root = this.root.replace(this.trailingSlash, '');
- if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
- }
- return fragment.replace(routeStripper, '');
- },
- getHash: function(window) {
- var match = (window || this).location.href.match(/#(.*)$/);
- return match ? match[1] : '';
- },
-
- // Attempt to load the current URL fragment. If a route succeeds with a
- // match, returns `true`. If no defined routes matches the fragment,
- // returns `false`.
- loadUrl: function(fragmentOverride) {
- var fragment = this.fragment = this.getFragment(fragmentOverride);
- var matched = false;
- for(var i = 0; i < this.handlers.length; i++) {
- var h = this.handlers[i];
- if (h.route.test(fragment)) {
- h.callback(fragment);
- matched = true;
- }
- }
- return matched;
- },
- });
})(window.ionic);
;
(function(ionic) {
@@ -5107,20 +5044,3 @@ ionic.controllers.TabBarController = ionic.controllers.ViewController.inherit({
});
})(window.ionic);
-;
-(function(ionic) {
-'use strict';
- ionic.controllers.ViewController = function(options) {
- this.initialize.apply(this, arguments);
- };
-
- ionic.controllers.ViewController.inherit = ionic.inherit;
-
- ionic.extend(ionic.controllers.ViewController.prototype, {
- initialize: function() {},
- // Destroy this view controller, including all child views
- destroy: function() {
- }
- });
-
-})(window.ionic);
diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js
index 897b1767df..a2d69197a8 100644
--- a/js/ext/angular/src/directive/ionicList.js
+++ b/js/ext/angular/src/directive/ionicList.js
@@ -6,7 +6,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
.directive('listItem', function() {
return {
restrict: 'E',
- require: '^list',
+ require: ['?^list', '?^virtualList'],
replace: true,
transclude: true,
scope: {
@@ -41,6 +41,13 @@ angular.module('ionic.ui.list', ['ngAnimate'])
\
',*/
link: function($scope, $element, $attr, list) {
+ // Grab the parent list controller
+ if(list[0]) {
+ list = list[0];
+ } else if(list[1]) {
+ list = list[1];
+ }
+
$scope.isEditing = false;
$scope.deleteIcon = list.scope.deleteIcon;
$scope.reorderIcon = list.scope.reorderIcon;
@@ -86,6 +93,53 @@ angular.module('ionic.ui.list', ['ngAnimate'])
return function($scope, $element, $attr) {
var lv = new ionic.views.List({el: $element[0]});
+ if(attr.animation) {
+ $element.addClass(attr.animation);
+ }
+ };
+ }
+ };
+})
+
+.directive('virtualList', function() {
+ return {
+ restrict: 'E',
+ replace: true,
+ transclude: true,
+
+ scope: {
+ isEditing: '=',
+ deleteIcon: '@',
+ reorderIcon: '@',
+ itemHeight: '@'
+ },
+
+ // So we can require being under this
+ controller: function($scope) {
+ var _this = this;
+
+ this.scope = $scope;
+
+ $scope.$watch('isEditing', function(v) {
+ _this.isEditing = true;
+ });
+ },
+
+ template: '
\
+
',
+
+ compile: function(element, attr, transclude) {
+ return function($scope, $element, $attr) {
+ var lv = new ionic.views.ListView({
+ el: $element[0],
+ listEl: $element[0].children[0],
+ isVirtual: true,
+ itemHeight: $scope.itemHeight,
+ renderViewport: function(high, low, start, end) {
+ console.log('RENDER VIEWPORT', high, low, start, end);
+ }
+ });
+
if(attr.animation) {
$element.addClass(attr.animation);
}
diff --git a/js/ext/angular/test/biglist.html b/js/ext/angular/test/biglist.html
new file mode 100644
index 0000000000..23dbdac6c0
--- /dev/null
+++ b/js/ext/angular/test/biglist.html
@@ -0,0 +1,161 @@
+
+
+
+ Big List
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.text}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/views/listViewScroll.js b/js/views/listViewScroll.js
index 2a6e116d2c..1434bea96b 100644
--- a/js/views/listViewScroll.js
+++ b/js/views/listViewScroll.js
@@ -403,26 +403,46 @@
refresher.style.height = '0px';
},
+ /**
+ * If we scrolled and have virtual mode enabled, compute the window
+ * of active elements in order to figure out the viewport to render.
+ */
didScroll: function(e) {
if(this.isVirtual) {
var itemHeight = this.itemHeight;
+
+ // TODO: This would be inaccurate if we are windowed
var totalItems = this.listEl.children.length;
+ // Grab the total height of the list
var scrollHeight = e.target.scrollHeight;
+
+ // Get the viewport height
var viewportHeight = this.el.parentNode.offsetHeight;
+ // scrollTop is the current scroll position
var scrollTop = e.scrollTop;
+ // High water is the pixel position of the first element to include (everything before
+ // that will be removed)
var highWater = Math.max(0, e.scrollTop + this.virtualRemoveThreshold);
+
+ // Low water is the pixel position of the last element to include (everything after
+ // that will be removed)
var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + this.virtualAddThreshold);
+ // Compute how many items per viewport size can show
var itemsPerViewport = Math.floor((lowWater - highWater) / itemHeight);
+
+ // Get the first and last elements in the list based on how many can fit
+ // between the pixel range of lowWater and highWater
var first = parseInt(Math.abs(highWater / itemHeight));
var last = parseInt(Math.abs(lowWater / itemHeight));
-
+ // Get the items we need to remove
this._virtualItemsToRemove = Array.prototype.slice.call(this.listEl.children, 0, first);
+ // Grab the nodes we will be showing
var nodes = Array.prototype.slice.call(this.listEl.children, first, first + itemsPerViewport);
this.renderViewport && this.renderViewport(highWater, lowWater, first, last);
diff --git a/js/views/scrollView.js b/js/views/scrollView.js
index a7fbc07891..9de62dce88 100644
--- a/js/views/scrollView.js
+++ b/js/views/scrollView.js
@@ -122,6 +122,8 @@
_scrollTo: function(x, y, time, easing) {
var _this = this;
+ time = time || 0;
+
var start = Date.now();
easing = easing || 'cubic-bezier(0.1, 0.57, 0.1, 1)';
@@ -150,6 +152,10 @@
time = 0;
}
+ if(time == Infinity) {
+ debugger;
+ }
+
var dx = ox - x;
var dy = oy - y;
@@ -336,6 +342,13 @@
destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
duration = speed / deceleration;
+ if(speed === 0) {
+ return {
+ destination: current,
+ duration: 0
+ };
+ }
+
// Check if the final destination needs to be rubber banded
if ( destination < lowerMargin ) {
// We have dragged too far down, snap back to the maximum
@@ -635,6 +648,9 @@
});
},
+ /**
+ * Trigger a done scrolling event.
+ */
_doneScrolling: function() {
this.didStopScrolling && this.didStopScrolling({
target: this.el,
diff --git a/test/biglists.html b/test/biglists.html
index 259fe4ffb8..cb672d27fb 100644
--- a/test/biglists.html
+++ b/test/biglists.html
@@ -22,7 +22,7 @@
-