This commit is contained in:
Adam Bradley
2013-11-11 22:37:51 -06:00
22 changed files with 743 additions and 302 deletions

35
dist/css/ionic.css vendored
View File

@ -2282,8 +2282,37 @@ body, .ionic-body {
text-align: center;
position: relative; }
.ionic-refresher-content {
height: 100%;
width: 100%;
text-align: center;
position: relative; }
.ionic-refresher {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #4a87ee;
position: absolute;
left: 50%;
margin-left: -5px;
bottom: 25px; }
.scroll-refreshing {
-webkit-transition: height 0.1s ease-in-out; }
.scroll-refreshing .ionic-refresher {
-webkit-animation: refresher-pulsate 1.5s linear;
-webkit-animation-iteration-count: infinite; }
@-webkit-keyframes refresher-pulsate {
0% {
-webkit-transform: scale(2, 2); }
50% {
-webkit-transform: scale(1.5, 1.5); }
100% {
-webkit-transform: scale(2, 2); } }
.overflow-scroll {
overflow: auto;
@ -4936,7 +4965,7 @@ a.button {
@-webkit-keyframes slideOutRight {
0% {
-webkit-transform: translate3d(0%, 0, 0); }
-webkit-transform: translate3d(0, 0, 0); }
100% {
-webkit-transform: translate3d(100%, 0, 0); } }
@ -4975,12 +5004,12 @@ a.button {
-webkit-animation-name: slideOutRight; }
.slide-out-right.ng-enter, .slide-out-right > .ng-enter {
-webkit-animation-duration: 2250ms;
-webkit-animation-duration: 250ms;
-webkit-animation-fill-mode: both;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-name: slideOutRight; }
.slide-out-right.ng-leave, .slide-out-right > .ng-leave {
-webkit-animation-duration: 2250ms;
-webkit-animation-duration: 250ms;
-webkit-animation-fill-mode: both;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-name: slideInRight; }

View File

@ -548,11 +548,39 @@ angular.module('ionic.ui.content', [])
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="scroll-refresher"><div class="scroll-refresher-content" ng-transclude></div></div>'
require: ['^?content', '^?list'],
template: '<div class="scroll-refresher"><div class="ionic-refresher-content"><div class="ionic-refresher"></div></div></div>',
scope: true,
link: function($scope, $element, $attr, scrollCtrl) {
var icon = $element[0].querySelector('.ionic-refresher');
// Scale up the refreshing icon
var onRefreshOpening = ionic.throttle(function(e, amt) {
icon.style[ionic.CSS.TRANSFORM] = 'scale(' + Math.min((1 + amt), 2) + ')';
}, 100);
$scope.$on('onRefreshing', function(e) {
icon.style[ionic.CSS.TRANSFORM] = 'scale(2)';
});
$scope.$on('onRefresh', function(e) {
icon.style[ionic.CSS.TRANSFORM] = 'scale(1)';
});
$scope.$on('onRefreshOpening', onRefreshOpening);
}
}
})
.directive('scroll-refresher', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="scroll-refresher"><div class="scroll-refresher-content"></div></div>'
}
});
})();
;
(function() {
@ -560,10 +588,10 @@ angular.module('ionic.ui.content', [])
angular.module('ionic.ui.list', ['ngAnimate'])
.directive('listItem', ['$timeout', function($timeout) {
.directive('item', ['$timeout', function($timeout) {
return {
restrict: 'E',
require: ['?^list', '?^virtualList'],
require: ['?^list'],
replace: true,
transclude: true,
scope: {
@ -574,8 +602,9 @@ angular.module('ionic.ui.list', ['ngAnimate'])
canReorder: '@',
canSwipe: '@',
buttons: '=',
type: '@'
},
template: '<a href="#" class="item item-slider">\
template: '<a href="#" class="item">\
<div class="item-edit" ng-if="canDelete && isEditing">\
<button class="button button-icon" ng-click="onDelete()"><i ng-class="deleteIcon"></i></button>\
</div>\
@ -589,20 +618,6 @@ angular.module('ionic.ui.list', ['ngAnimate'])
</div>\
</a>',
/*
template: '<li class="list-item">\
<div class="list-item-edit" ng-if="canDelete && isEditing">\
<button class="button button-icon" ng-click="onDelete()"><i ng-class="deleteIcon"></i></button>\
</div>\
<div class="list-item-content" ng-transclude>\
</div>\
<div class="list-item-drag" ng-if="canReorder && isEditing">\
<button data-ionic-action="reorder" class="button button-icon"><i ng-class="reorderIcon"></i></button>\
</div>\
<div class="list-item-buttons" ng-if="canSwipe && !isEditing">\
<button ng-click="buttonClicked(button)" class="button" ng-class="button.type" ng-repeat="button in buttons">{{button.text}}</button>\
</div>\
</li>',*/
link: function($scope, $element, $attr, list) {
// Grab the parent list controller
if(list[0]) {
@ -611,6 +626,13 @@ angular.module('ionic.ui.list', ['ngAnimate'])
list = list[1];
}
// Add the list item type class
$element.addClass($attr.type || 'item-slider');
if($attr.type !== 'item-slider') {
$scope.canSwipe = false;
}
$scope.isEditing = false;
$scope.deleteIcon = list.scope.deleteIcon;
$scope.reorderIcon = list.scope.reorderIcon;
@ -647,6 +669,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
isEditing: '=',
deleteIcon: '@',
reorderIcon: '@',
hasPullToRefresh: '@',
onRefresh: '&',
onRefreshOpening: '&'
},
@ -669,12 +692,14 @@ angular.module('ionic.ui.list', ['ngAnimate'])
var lv = new ionic.views.ListView({
el: $element[0],
listEl: $element[0].children[0],
hasPullToRefresh: (typeof $scope.onRefresh !== 'undefined'),
hasPullToRefresh: ($scope.hasPullToRefresh !== 'false'),
onRefresh: function() {
$scope.onRefresh();
$scope.$parent.$broadcast('onRefresh');
},
onRefreshOpening: function(amt) {
$scope.onRefreshOpening({amount: amt});
$scope.$parent.$broadcast('onRefreshOpening', amt);
}
});
@ -723,6 +748,31 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
angular.extend(this, ionic.controllers.NavController.prototype);
/**
* Push a template onto the navigation stack.
* @param {string} templateUrl the URL of the template to load.
*/
this.pushFromTemplate = ionic.debounce(function(templateUrl) {
var childScope = $scope.$new();
childScope.isVisible = true;
// Load the given template
TemplateLoader.load(templateUrl).then(function(templateString) {
// Compile the template with the new scrope, and append it to the navigation's content area
var el = $compile(templateString)(childScope, function(cloned, scope) {
var content = angular.element($element[0].querySelector('.content, .scroll'));
$animate.enter(cloned, angular.element(content));
});
});
}, 300, true);
// Pop function, debounced
this.popController = ionic.debounce(function() {
_this.pop();
}, 300, true);
ionic.controllers.NavController.call(this, {
content: {
},
@ -746,7 +796,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
// Support Android hardware back button (native only, not mobile web)
var onHardwareBackButton = function(e) {
$scope.$apply(function() {
_this.pop();
_this.popController();
});
}
Platform.onHardwareBackButton(onHardwareBackButton);
@ -759,28 +809,6 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
this.endDrag = function(e) {
};
/**
* Push a template onto the navigation stack.
* @param {string} templateUrl the URL of the template to load.
*/
this.pushFromTemplate = function(templateUrl) {
var childScope = $scope.$new();
childScope.isVisible = true;
// Load the given template
TemplateLoader.load(templateUrl).then(function(templateString) {
// Compile the template with the new scrope, and append it to the navigation's content area
var el = $compile(templateString)(childScope, function(cloned, scope) {
var content = angular.element($element[0].querySelector('.content'));
//content.append(cloned);
//angular.element(content).append(cloned);
$animate.enter(cloned, angular.element(content));
});
});
};
/**
* Push a controller to the stack. This is called by the child
* nav-content directive when it is linked to a scope on the page.
@ -827,7 +855,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
scope.$watch('navController.controllers.length', function(value) {
});
scope.goBack = function() {
navCtrl.pop();
navCtrl.popController();
};
}
};
@ -884,7 +912,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
// Store that we should go forwards on the animation. This toggles
// based on the visibility sequence (to support reverse transitions)
var wasVisible = null;
var lastDirection = null;
$scope.title = $attr.title;
$scope.pushAnimation = $attr.pushAnimation || 'slide-in-left';
@ -900,6 +928,24 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
navCtrl.showNavBar();
}
$scope.visibilityChanged = function(direction) {
lastDirection = direction;
if(!childElement) {
return;
}
var clone = childElement;
if(direction == 'push') {
clone.addClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
} else if(direction == 'pop') {
clone.addClass(childScope.popAnimation);
clone.removeClass(childScope.pushAnimation);
}
};
// Push this controller onto the stack
$scope.pushController($scope, $element);
@ -911,39 +957,33 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
transclude(childScope, function(clone) {
childElement = clone;
// Check if this is visible, and if so, create it and show it
if(wasVisible === false) {
clone.removeClass(childScope.pushAnimation);
clone.addClass(childScope.popAnimation);
} else {
if(lastDirection == 'push') {
clone.addClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
} else if(lastDirection == 'pop') {
clone.addClass(childScope.popAnimation);
}
$animate.enter(clone, $element.parent(), $element);
wasVisible = true;
$animate.enter(clone, $element.parent(), $element, function() {
clone.removeClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
});
});
} else {
// Taken from ngIf
if(childElement) {
var clone = childElement;
// Check if this is visible, and if so, create it and show it
if(wasVisible === false) {
clone.removeClass(childScope.pushAnimation);
clone.addClass(childScope.popAnimation);
} else {
clone.addClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
$animate.leave(childElement, function() {
if(childScope) {
childElement.removeClass(childScope.pushAnimation);
childElement.removeClass(childScope.popAnimation);
}
$animate.leave(childElement);
});
childElement = undefined;
wasVisible = false;
}
if(childScope) {
childScope.$destroy();
childScope = undefined;
}
}
});
}
@ -975,27 +1015,18 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
angular.extend(this, ionic.controllers.SideMenuController.prototype);
ionic.controllers.SideMenuController.call(this, {
// Our quick implementation of the left side menu
left: {
width: 270,
pushDown: function() {
$scope.leftZIndex = -1;
},
bringUp: function() {
$scope.leftZIndex = 0;
}
},
// Our quick implementation of the right side menu
right: {
width: 270,
pushDown: function() {
$scope.rightZIndex = -1;
},
bringUp: function() {
$scope.rightZIndex = 0;
}
}
});
$scope.contentTranslateX = 0;
$scope.sideMenuContentTranslateX = 0;
$scope.sideMenuCtrl = this;
})
@ -1027,28 +1058,32 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
defaultPrevented = e.defaultPrevented;
});
Gesture.on('drag', function(e) {
var dragFn = function(e) {
if(defaultPrevented) {
return;
}
sideMenuCtrl._handleDrag(e);
}, $element[0]);
};
Gesture.on('release', function(e) {
Gesture.on('drag', dragFn, $element[0]);
var dragReleaseFn = function(e) {
if(!defaultPrevented) {
sideMenuCtrl._endDrag(e);
}
defaultPrevented = false;
}, $element[0]);
};
Gesture.on('release', dragReleaseFn, $element[0]);
sideMenuCtrl.setContent({
onDrag: function(e) {},
endDrag: function(e) {},
getTranslateX: function() {
return $scope.contentTranslateX || 0;
return $scope.sideMenuContentTranslateX || 0;
},
setTranslateX: function(amount) {
$scope.contentTranslateX = amount;
$scope.sideMenuContentTranslateX = amount;
$element[0].style.webkitTransform = 'translate3d(' + amount + 'px, 0, 0)';
},
enableAnimation: function() {
@ -1062,6 +1097,12 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
$element[0].classList.remove('menu-animated');
}
});
// Cleanup
$scope.$on('$destroy', function() {
Gesture.off('drag', dragFn);
Gesture.off('release', dragReleaseFn);
});
};
}
};
@ -1080,10 +1121,23 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
return function($scope, $element, $attr, sideMenuCtrl) {
$scope.side = $attr.side;
if($scope.side == 'left') {
sideMenuCtrl.left.isEnabled = true;
sideMenuCtrl.left.pushDown = function() {
$element[0].style.zIndex = -1;
};
sideMenuCtrl.left.bringUp = function() {
$element[0].style.zIndex = 0;
};
} else if($scope.side == 'right') {
sideMenuCtrl.right.isEnabled = true;
sideMenuCtrl.right.pushDown = function() {
$element[0].style.zIndex = -1;
};
sideMenuCtrl.right.bringUp = function() {
$element[0].style.zIndex = 0;
};
}
$element.append(transclude($scope));

177
dist/js/ionic.js vendored
View File

@ -8,6 +8,8 @@ http://ionicframework.com/
By @maxlynch, @helloimben, @adamdbradley <3
Licensed under the MIT license. Please see LICENSE for more information.
Make awesome shit.
*/
;
@ -1835,7 +1837,58 @@ window.ionic = {
;
(function(ionic) {
/**
* Various utilities used throughout Ionic
*
* Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
*/
ionic.Utils = {
/**
* Return a function that will be called with the given context
*/
proxy: function(func, context) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
};
},
/**
* Only call a function once in the given interval.
*
* @param func {Function} the function to call
* @param wait {int} how long to wait before/after to allow function calls
* @param immediate {boolean} whether to call immediately or after the wait interval
*/
debounce: function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
return function() {
context = this;
args = arguments;
timestamp = new Date();
var later = function() {
var last = (new Date()) - timestamp;
if (last < wait) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) result = func.apply(context, args);
}
};
var callNow = immediate && !timeout;
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) result = func.apply(context, args);
return result;
};
},
/**
* Throttle the given fun, only allowing it to be
* called at most every `wait` ms.
*/
throttle: function(func, wait, options) {
var context, args, result;
var timeout = null;
@ -1915,9 +1968,12 @@ window.ionic = {
}
};
// Bind a few of the most useful functions to the ionic scope
ionic.inherit = ionic.Utils.inherit;
ionic.extend = ionic.Utils.extend;
ionic.throttle = ionic.Utils.throttle;
ionic.proxy = ionic.Utils.proxy;
ionic.debounce = ionic.Utils.debounce;
})(window.ionic);
;
@ -1977,7 +2033,7 @@ window.ionic = {
dragThreshold: 10,
// Resistance when scrolling too far up or down
rubberBandResistance: 3,
rubberBandResistance: 2,
// Scroll event names. These are custom so can be configured
scrollEventName: 'momentumScrolled',
@ -1985,6 +2041,10 @@ window.ionic = {
hasPullToRefresh: true,
// Whether to disable overflow rubber banding when content is small
// enough to fit in the viewport (i.e. doesn't need scrolling)
disableNonOverflowRubberBand: false,
// Called as the refresher is opened, an amount is passed
onRefreshOpening: function() {},
// Called when let go and is refreshing
@ -2178,6 +2238,10 @@ window.ionic = {
}
},
/**
* Check if the current scroll bounds needs to be brought back to the min/max
* allowable given the total scrollable area.
*/
needsWrapping: function() {
var _this = this;
@ -2400,14 +2464,6 @@ window.ionic = {
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;
@ -2444,6 +2500,16 @@ window.ionic = {
resist: 1,
startTime: Date.now()
};
if(this.disableNonOverflowRubberBand === true) {
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)) {
this._drag.noRubberBand = true;
}
}
},
/**
@ -2511,10 +2577,23 @@ window.ionic = {
var newX = _this.x + deltaX;
var newY = _this.y + deltaY;
if(drag.noRubberBand === true) {
if(newY > 0) {
newY = 0;
} else if(newY < maxY) {
newY = maxY;
}
if(newX > 0) {
newX = 0;
} else if(newX < maxX) {
newX = maxX;
}
} else {
// Check if the dragging is beyond the bottom or top
if(newY > 0 || (-newY + parentHeight) > totalHeight) {
newY = _this.y + deltaY / _this.rubberBandResistance;
}
}
if(!_this.isHorizontalEnabled) {
newX = 0;
@ -2525,7 +2604,6 @@ window.ionic = {
if(_this._refresher && newY > 0) {
// We are pulling to refresh, so update the refresher
//_this._refresher.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + newY + 'px, 0)';
if(_this._isRefresherHidden) {
// Show it only in a drag and if we haven't showed it yet
_this._refresher.style.display = 'block';
@ -2542,7 +2620,7 @@ window.ionic = {
}
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
_this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
} else {
_this._isHoldingRefresh = false;
@ -2553,7 +2631,7 @@ window.ionic = {
_this._isRefresherHidden = true;
}
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
_this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
}
// Store the last points
@ -3165,8 +3243,6 @@ window.ionic = {
this._isDragging = false;
console.log(e.gesture.direction);
// Check if this is a reorder drag
if(ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_DRAG_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
var item = this._getItem(e.target);
@ -3467,6 +3543,39 @@ window.ionic = {
}
});
ionic.views.SideMenuContent = ionic.views.View.inherit({
initialize: function(opts) {
var _this = this;
ionic.extend(this, {
animationClass: 'menu-animated',
onDrag: function(e) {},
onEndDrag: function(e) {},
}, opts);
ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
},
_onDrag: function(e) {
this.onDrag && this.onDrag(e);
},
_onEndDrag: function(e) {
this.onEndDrag && this.onEndDrag(e);
},
disableAnimation: function() {
this.el.classList.remove(this.animationClass);
},
enableAnimation: function() {
this.el.classList.add(this.animationClass);
},
getTranslateX: function() {
return parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]);
},
setTranslateX: function(x) {
this.el.style.webkitTransform = 'translate3d(' + x + 'px, 0, 0)';
}
});
})(ionic);
;
/**
@ -4142,21 +4251,17 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
return;
// Actually switch the active controllers
// Remove the old one
//last && last.detach();
if(last) {
last.isVisible = false;
last.visibilityChanged && last.visibilityChanged();
last.visibilityChanged && last.visibilityChanged('push');
}
// Grab the top controller on the stack
var next = this.controllers[this.controllers.length - 1];
// TODO: No DOM stuff here
//this.content.el.appendChild(next.el);
next.isVisible = true;
next.visibilityChanged && next.visibilityChanged();
// Trigger visibility change, but send 'first' if this is the first page
next.visibilityChanged && next.visibilityChanged(last ? 'push' : 'first');
this._updateNavBar();
@ -4181,7 +4286,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
last = this.controllers.pop();
if(last) {
last.isVisible = false;
last.visibilityChanged && last.visibilityChanged();
last.visibilityChanged && last.visibilityChanged('pop');
}
// Remove the old one
@ -4192,7 +4297,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
// TODO: No DOM stuff here
//this.content.el.appendChild(next.el);
next.isVisible = true;
next.visibilityChanged && next.visibilityChanged();
next.visibilityChanged && next.visibilityChanged('pop');
// Switch to it (TODO: Animate or such things here)
@ -4264,7 +4369,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
self._handleDrag(e);
};
this.content.endDrag = function(e) {
this.content.onEndDrag =function(e) {
self._endDrag(e);
};
}
@ -4354,12 +4459,12 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
*/
openPercentage: function(percentage) {
var p = percentage / 100;
var maxLeft = this.left.width;
if(this.left && percentage >= 0) {
this.openAmount(this.left.width * p);
} else if(this.right && percentage < 0) {
var maxRight = this.right.width;
if(percentage >= 0) {
this.openAmount(maxLeft * p);
} else {
this.openAmount(maxRight * p);
this.openAmount(this.right.width * p);
}
},
@ -4369,11 +4474,11 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
* negative value for right menu (only one menu will be visible at a time).
*/
openAmount: function(amount) {
var maxLeft = this.left.width;
var maxRight = this.right.width;
var maxLeft = this.left && this.left.width || 0;
var maxRight = this.right && this.right.width || 0;
// Check if we can move to that side, depending if the left/right panel is enabled
if((!this.left.isEnabled && amount > 0) || (!this.right.isEnabled && amount < 0)) {
if((!(this.left && this.left.isEnabled) && amount > 0) || (!(this.right && this.right.isEnabled) && amount < 0)) {
return;
}
@ -4388,17 +4493,17 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
this._rightShowing = false;
// Push the z-index of the right menu down
this.right.pushDown();
this.right && this.right.pushDown();
// Bring the z-index of the left menu up
this.left.bringUp();
this.left && this.left.bringUp();
} else {
this._rightShowing = true;
this._leftShowing = false;
// Bring the z-index of the right menu up
this.right.bringUp();
this.right && this.right.bringUp();
// Push the z-index of the left menu down
this.left.pushDown();
this.left && this.left.pushDown();
}
},

View File

@ -8,4 +8,6 @@ http://ionicframework.com/
By @maxlynch, @helloimben, @adamdbradley <3
Licensed under the MIT license. Please see LICENSE for more information.
Make awesome shit.
*/

View File

@ -60,21 +60,17 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
return;
// Actually switch the active controllers
// Remove the old one
//last && last.detach();
if(last) {
last.isVisible = false;
last.visibilityChanged && last.visibilityChanged();
last.visibilityChanged && last.visibilityChanged('push');
}
// Grab the top controller on the stack
var next = this.controllers[this.controllers.length - 1];
// TODO: No DOM stuff here
//this.content.el.appendChild(next.el);
next.isVisible = true;
next.visibilityChanged && next.visibilityChanged();
// Trigger visibility change, but send 'first' if this is the first page
next.visibilityChanged && next.visibilityChanged(last ? 'push' : 'first');
this._updateNavBar();
@ -99,7 +95,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
last = this.controllers.pop();
if(last) {
last.isVisible = false;
last.visibilityChanged && last.visibilityChanged();
last.visibilityChanged && last.visibilityChanged('pop');
}
// Remove the old one
@ -110,7 +106,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({
// TODO: No DOM stuff here
//this.content.el.appendChild(next.el);
next.isVisible = true;
next.visibilityChanged && next.visibilityChanged();
next.visibilityChanged && next.visibilityChanged('pop');
// Switch to it (TODO: Animate or such things here)

View File

@ -25,7 +25,7 @@
self._handleDrag(e);
};
this.content.endDrag = function(e) {
this.content.onEndDrag =function(e) {
self._endDrag(e);
};
}
@ -115,12 +115,12 @@
*/
openPercentage: function(percentage) {
var p = percentage / 100;
var maxLeft = this.left.width;
if(this.left && percentage >= 0) {
this.openAmount(this.left.width * p);
} else if(this.right && percentage < 0) {
var maxRight = this.right.width;
if(percentage >= 0) {
this.openAmount(maxLeft * p);
} else {
this.openAmount(maxRight * p);
this.openAmount(this.right.width * p);
}
},
@ -130,11 +130,11 @@
* negative value for right menu (only one menu will be visible at a time).
*/
openAmount: function(amount) {
var maxLeft = this.left.width;
var maxRight = this.right.width;
var maxLeft = this.left && this.left.width || 0;
var maxRight = this.right && this.right.width || 0;
// Check if we can move to that side, depending if the left/right panel is enabled
if((!this.left.isEnabled && amount > 0) || (!this.right.isEnabled && amount < 0)) {
if((!(this.left && this.left.isEnabled) && amount > 0) || (!(this.right && this.right.isEnabled) && amount < 0)) {
return;
}
@ -149,17 +149,17 @@
this._rightShowing = false;
// Push the z-index of the right menu down
this.right.pushDown();
this.right && this.right.pushDown();
// Bring the z-index of the left menu up
this.left.bringUp();
this.left && this.left.bringUp();
} else {
this._rightShowing = true;
this._leftShowing = false;
// Bring the z-index of the right menu up
this.right.bringUp();
this.right && this.right.bringUp();
// Push the z-index of the left menu down
this.left.pushDown();
this.left && this.left.pushDown();
}
},

View File

@ -76,9 +76,37 @@ angular.module('ionic.ui.content', [])
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="scroll-refresher"><div class="scroll-refresher-content" ng-transclude></div></div>'
require: ['^?content', '^?list'],
template: '<div class="scroll-refresher"><div class="ionic-refresher-content"><div class="ionic-refresher"></div></div></div>',
scope: true,
link: function($scope, $element, $attr, scrollCtrl) {
var icon = $element[0].querySelector('.ionic-refresher');
// Scale up the refreshing icon
var onRefreshOpening = ionic.throttle(function(e, amt) {
icon.style[ionic.CSS.TRANSFORM] = 'scale(' + Math.min((1 + amt), 2) + ')';
}, 100);
$scope.$on('onRefreshing', function(e) {
icon.style[ionic.CSS.TRANSFORM] = 'scale(2)';
});
$scope.$on('onRefresh', function(e) {
icon.style[ionic.CSS.TRANSFORM] = 'scale(1)';
});
$scope.$on('onRefreshOpening', onRefreshOpening);
}
}
})
.directive('scroll-refresher', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="scroll-refresher"><div class="scroll-refresher-content"></div></div>'
}
});
})();

View File

@ -3,10 +3,10 @@
angular.module('ionic.ui.list', ['ngAnimate'])
.directive('listItem', ['$timeout', function($timeout) {
.directive('item', ['$timeout', function($timeout) {
return {
restrict: 'E',
require: ['?^list', '?^virtualList'],
require: ['?^list'],
replace: true,
transclude: true,
scope: {
@ -17,8 +17,9 @@ angular.module('ionic.ui.list', ['ngAnimate'])
canReorder: '@',
canSwipe: '@',
buttons: '=',
type: '@'
},
template: '<a href="#" class="item item-slider">\
template: '<a href="#" class="item">\
<div class="item-edit" ng-if="canDelete && isEditing">\
<button class="button button-icon" ng-click="onDelete()"><i ng-class="deleteIcon"></i></button>\
</div>\
@ -32,20 +33,6 @@ angular.module('ionic.ui.list', ['ngAnimate'])
</div>\
</a>',
/*
template: '<li class="list-item">\
<div class="list-item-edit" ng-if="canDelete && isEditing">\
<button class="button button-icon" ng-click="onDelete()"><i ng-class="deleteIcon"></i></button>\
</div>\
<div class="list-item-content" ng-transclude>\
</div>\
<div class="list-item-drag" ng-if="canReorder && isEditing">\
<button data-ionic-action="reorder" class="button button-icon"><i ng-class="reorderIcon"></i></button>\
</div>\
<div class="list-item-buttons" ng-if="canSwipe && !isEditing">\
<button ng-click="buttonClicked(button)" class="button" ng-class="button.type" ng-repeat="button in buttons">{{button.text}}</button>\
</div>\
</li>',*/
link: function($scope, $element, $attr, list) {
// Grab the parent list controller
if(list[0]) {
@ -54,6 +41,13 @@ angular.module('ionic.ui.list', ['ngAnimate'])
list = list[1];
}
// Add the list item type class
$element.addClass($attr.type || 'item-slider');
if($attr.type !== 'item-slider') {
$scope.canSwipe = false;
}
$scope.isEditing = false;
$scope.deleteIcon = list.scope.deleteIcon;
$scope.reorderIcon = list.scope.reorderIcon;
@ -90,6 +84,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
isEditing: '=',
deleteIcon: '@',
reorderIcon: '@',
hasPullToRefresh: '@',
onRefresh: '&',
onRefreshOpening: '&'
},
@ -112,12 +107,14 @@ angular.module('ionic.ui.list', ['ngAnimate'])
var lv = new ionic.views.ListView({
el: $element[0],
listEl: $element[0].children[0],
hasPullToRefresh: (typeof $scope.onRefresh !== 'undefined'),
hasPullToRefresh: ($scope.hasPullToRefresh !== 'false'),
onRefresh: function() {
$scope.onRefresh();
$scope.$parent.$broadcast('onRefresh');
},
onRefreshOpening: function(amt) {
$scope.onRefreshOpening({amount: amt});
$scope.$parent.$broadcast('onRefreshOpening', amt);
}
});

View File

@ -8,6 +8,31 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
angular.extend(this, ionic.controllers.NavController.prototype);
/**
* Push a template onto the navigation stack.
* @param {string} templateUrl the URL of the template to load.
*/
this.pushFromTemplate = ionic.debounce(function(templateUrl) {
var childScope = $scope.$new();
childScope.isVisible = true;
// Load the given template
TemplateLoader.load(templateUrl).then(function(templateString) {
// Compile the template with the new scrope, and append it to the navigation's content area
var el = $compile(templateString)(childScope, function(cloned, scope) {
var content = angular.element($element[0].querySelector('.content, .scroll'));
$animate.enter(cloned, angular.element(content));
});
});
}, 300, true);
// Pop function, debounced
this.popController = ionic.debounce(function() {
_this.pop();
}, 300, true);
ionic.controllers.NavController.call(this, {
content: {
},
@ -31,7 +56,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
// Support Android hardware back button (native only, not mobile web)
var onHardwareBackButton = function(e) {
$scope.$apply(function() {
_this.pop();
_this.popController();
});
}
Platform.onHardwareBackButton(onHardwareBackButton);
@ -44,28 +69,6 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
this.endDrag = function(e) {
};
/**
* Push a template onto the navigation stack.
* @param {string} templateUrl the URL of the template to load.
*/
this.pushFromTemplate = function(templateUrl) {
var childScope = $scope.$new();
childScope.isVisible = true;
// Load the given template
TemplateLoader.load(templateUrl).then(function(templateString) {
// Compile the template with the new scrope, and append it to the navigation's content area
var el = $compile(templateString)(childScope, function(cloned, scope) {
var content = angular.element($element[0].querySelector('.content'));
//content.append(cloned);
//angular.element(content).append(cloned);
$animate.enter(cloned, angular.element(content));
});
});
};
/**
* Push a controller to the stack. This is called by the child
* nav-content directive when it is linked to a scope on the page.
@ -112,7 +115,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
scope.$watch('navController.controllers.length', function(value) {
});
scope.goBack = function() {
navCtrl.pop();
navCtrl.popController();
};
}
};
@ -169,7 +172,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
// Store that we should go forwards on the animation. This toggles
// based on the visibility sequence (to support reverse transitions)
var wasVisible = null;
var lastDirection = null;
$scope.title = $attr.title;
$scope.pushAnimation = $attr.pushAnimation || 'slide-in-left';
@ -185,6 +188,24 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
navCtrl.showNavBar();
}
$scope.visibilityChanged = function(direction) {
lastDirection = direction;
if(!childElement) {
return;
}
var clone = childElement;
if(direction == 'push') {
clone.addClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
} else if(direction == 'pop') {
clone.addClass(childScope.popAnimation);
clone.removeClass(childScope.pushAnimation);
}
};
// Push this controller onto the stack
$scope.pushController($scope, $element);
@ -196,39 +217,33 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges
transclude(childScope, function(clone) {
childElement = clone;
// Check if this is visible, and if so, create it and show it
if(wasVisible === false) {
clone.removeClass(childScope.pushAnimation);
clone.addClass(childScope.popAnimation);
} else {
if(lastDirection == 'push') {
clone.addClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
} else if(lastDirection == 'pop') {
clone.addClass(childScope.popAnimation);
}
$animate.enter(clone, $element.parent(), $element);
wasVisible = true;
$animate.enter(clone, $element.parent(), $element, function() {
clone.removeClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
});
});
} else {
// Taken from ngIf
if(childElement) {
var clone = childElement;
// Check if this is visible, and if so, create it and show it
if(wasVisible === false) {
clone.removeClass(childScope.pushAnimation);
clone.addClass(childScope.popAnimation);
} else {
clone.addClass(childScope.pushAnimation);
clone.removeClass(childScope.popAnimation);
$animate.leave(childElement, function() {
if(childScope) {
childElement.removeClass(childScope.pushAnimation);
childElement.removeClass(childScope.popAnimation);
}
$animate.leave(childElement);
});
childElement = undefined;
wasVisible = false;
}
if(childScope) {
childScope.$destroy();
childScope = undefined;
}
}
});
}

View File

@ -20,27 +20,18 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
angular.extend(this, ionic.controllers.SideMenuController.prototype);
ionic.controllers.SideMenuController.call(this, {
// Our quick implementation of the left side menu
left: {
width: 270,
pushDown: function() {
$scope.leftZIndex = -1;
},
bringUp: function() {
$scope.leftZIndex = 0;
}
},
// Our quick implementation of the right side menu
right: {
width: 270,
pushDown: function() {
$scope.rightZIndex = -1;
},
bringUp: function() {
$scope.rightZIndex = 0;
}
}
});
$scope.contentTranslateX = 0;
$scope.sideMenuContentTranslateX = 0;
$scope.sideMenuCtrl = this;
})
@ -72,28 +63,32 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
defaultPrevented = e.defaultPrevented;
});
Gesture.on('drag', function(e) {
var dragFn = function(e) {
if(defaultPrevented) {
return;
}
sideMenuCtrl._handleDrag(e);
}, $element[0]);
};
Gesture.on('release', function(e) {
Gesture.on('drag', dragFn, $element[0]);
var dragReleaseFn = function(e) {
if(!defaultPrevented) {
sideMenuCtrl._endDrag(e);
}
defaultPrevented = false;
}, $element[0]);
};
Gesture.on('release', dragReleaseFn, $element[0]);
sideMenuCtrl.setContent({
onDrag: function(e) {},
endDrag: function(e) {},
getTranslateX: function() {
return $scope.contentTranslateX || 0;
return $scope.sideMenuContentTranslateX || 0;
},
setTranslateX: function(amount) {
$scope.contentTranslateX = amount;
$scope.sideMenuContentTranslateX = amount;
$element[0].style.webkitTransform = 'translate3d(' + amount + 'px, 0, 0)';
},
enableAnimation: function() {
@ -107,6 +102,12 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
$element[0].classList.remove('menu-animated');
}
});
// Cleanup
$scope.$on('$destroy', function() {
Gesture.off('drag', dragFn);
Gesture.off('release', dragReleaseFn);
});
};
}
};
@ -125,10 +126,23 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
return function($scope, $element, $attr, sideMenuCtrl) {
$scope.side = $attr.side;
if($scope.side == 'left') {
sideMenuCtrl.left.isEnabled = true;
sideMenuCtrl.left.pushDown = function() {
$element[0].style.zIndex = -1;
};
sideMenuCtrl.left.bringUp = function() {
$element[0].style.zIndex = 0;
};
} else if($scope.side == 'right') {
sideMenuCtrl.right.isEnabled = true;
sideMenuCtrl.right.pushDown = function() {
$element[0].style.zIndex = -1;
};
sideMenuCtrl.right.bringUp = function() {
$element[0].style.zIndex = 0;
};
}
$element.append(transclude($scope));

View File

@ -64,19 +64,31 @@
<body>
<div ng-controller="TestCtrl" class="reveal-animation scroll-content">
<list is-editing="isEditingItems" on-refresh-holding="almostRefreshing()" on-refresh-opening="almostRefreshProjects(ratio)" on-refresh="refreshProjects()" animation="my-repeat-animation" delete-icon="icon ion-minus-circled" reorder-icon="icon ion-navicon">
<list-refresher>
</list-refresher>
<list-item ng-repeat="item in items"
<list is-editing="isEditingItems"
on-refresh-holding="almostRefreshing()"
on-refresh-opening="almostRefreshProjects(ratio)"
on-refresh="refreshProjects()"
animation="my-repeat-animation"
delete-icon="icon ion-minus-circled"
reorder-icon="icon ion-navicon">
<refresher>
<div id="refresh-content">
<i class="icon ion-ios7-reloading"></i>
</div>
</refresher>
<item
ng-repeat="item in items"
buttons="item.buttons"
can-delete="true"
can-reorder="true"
can-swipe="true"
on-delete="deleteProject(project)"
on-select="selectProject(project)">
<i class="icon ion-email ion-primary"></i>
{{item.text}}
<i class="{{item.icon}}"></i>
</list-item>
</item>
</list>
<button ng-click="edit()" class="button button-success">Edit</button>
</div>
@ -86,32 +98,6 @@
<script>
angular.module('navTest', ['ionic.ui.list', 'ionic.ui.content', 'ngAnimate'])
.directive('spinner', function() {
return {
restrict: 'E',
replace: true,
scope: {
ratio: '='
},
template: '<div class="spinner"><div class="spin-thing"></div></div>',
link: function($scope, $element, $attr) {
$scope.$watch('ratio', function(value) {
if(value > 0.97) {
value = 1;
}
var a = (value * 360) % 360;
var r = (a * Math.PI) / 180;
var x = (Math.sin(r) * 20) + 14;
var y = (Math.cos(r) * -20) + 14;
$element[0].firstElementChild.style.webkitTransform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
//$element[0].firstElementChild.setAttribute('d', anim);
});
}
}
})
.controller('TestCtrl', function($scope) {
$scope.refreshRatio = { ratio: 0 };
var removeItem = function(item) {

View File

@ -7,9 +7,9 @@
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link href="/vendor/font-awesome/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="../../../../dist/css/ionic.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular-touch.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular-animate.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-touch.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-animate.js"></script>
<style>
.view {
position: fixed;
@ -34,8 +34,10 @@
<navs>
<nav-bar></nav-bar>
<content has-header="true" ng-controller="AppCtrl">
<div ng-controller="AppCtrl">
<content has-header="true">
</content>
</div>
</navs>
<script id="page.html" type="text/ng-template">
@ -65,7 +67,7 @@
}
angular.module('navTest', ['ionic.ui.nav', 'ionic.ui.content', 'ionic.ui.list', 'ngAnimate'])
angular.module('navTest', ['ionic', 'ngAnimate'])
.controller('AppCtrl', function($scope, $compile, $element) {
$scope.navController.pushFromTemplate('page.html');

View File

@ -16,24 +16,37 @@
<div ng-controller="MenuCtrl">
<side-menu>
<pane side-menu-content>
<header class="bar bar-header bar-dark">
<button class="button" ng-click="openLeft()"><i class="icon-reorder"></i></button>
<header class="bar bar-header bar-primary">
<button class="button button-icon" ng-click="openLeft()"><i class="icon ion-navicon"></i></button>
<h1 class="title">Slide me</h1>
</header>
<div class="content has-header">
<h1>Slide me side to side!</h1>
<h1>Content</h1>
</div>
</pane>
<menu side="left">
<h2>Left</h2>
<header class="bar bar-header bar-primary">
<h1 class="title">Left</h1>
</header>
<content has-header="true">
<ul class="list">
<a href="#" class="list-item" ng-repeat="item in list">
<a href="#" class="item" ng-repeat="item in list">
{{item.text}}
</a>
</ul>
</content>
</menu>
<menu side="right">
<h2>Items</h2>
<header class="bar bar-header bar-primary">
<h1 class="title">Right</h1>
</header>
<content has-header="true">
<ul class="list">
<a href="#" class="item" ng-repeat="item in list">
{{item.text}}
</a>
</ul>
</content>
</menu>
</side-menu>
</div>
@ -43,6 +56,12 @@
angular.module('sideMenuTest', ['ionic'])
.controller('MenuCtrl', function($scope) {
$scope.list = [];
for(var i = 0; i < 20; i++) {
$scope.list.push({
text: 'Item ' + i
});
}
$scope.openLeft = function() {
$scope.sideMenuCtrl.toggleLeft();
};

View File

@ -60,7 +60,7 @@
<h1 class="title">Tasks</h1>
<button class="button button-clear button-primary" ng-click="isEditingItems = !isEditingItems">Edit</button>
</header>
<content has-header="true" has-tabs="true" scroll="false">
<content has-header="true" has-footer="true" scroll="false">
<list on-refresh="onRefresh()" is-editing="isEditingItems" animation="fade-out" delete-icon="icon ion-minus-circled" reorder-icon="icon ion-navicon">
<refresher>
<div id="refresh-content">
@ -68,7 +68,7 @@
<i class="icon ion-ios7-reloading"></i>
</div>
</refresher>
<list-item ng-repeat="item in items"
<item ng-repeat="item in items"
item="item"
buttons="item.buttons"
can-delete="true"

View File

@ -1,6 +1,57 @@
(function(ionic) {
/**
* Various utilities used throughout Ionic
*
* Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
*/
ionic.Utils = {
/**
* Return a function that will be called with the given context
*/
proxy: function(func, context) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
};
},
/**
* Only call a function once in the given interval.
*
* @param func {Function} the function to call
* @param wait {int} how long to wait before/after to allow function calls
* @param immediate {boolean} whether to call immediately or after the wait interval
*/
debounce: function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
return function() {
context = this;
args = arguments;
timestamp = new Date();
var later = function() {
var last = (new Date()) - timestamp;
if (last < wait) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) result = func.apply(context, args);
}
};
var callNow = immediate && !timeout;
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) result = func.apply(context, args);
return result;
};
},
/**
* Throttle the given fun, only allowing it to be
* called at most every `wait` ms.
*/
throttle: function(func, wait, options) {
var context, args, result;
var timeout = null;
@ -80,8 +131,11 @@
}
};
// Bind a few of the most useful functions to the ionic scope
ionic.inherit = ionic.Utils.inherit;
ionic.extend = ionic.Utils.extend;
ionic.throttle = ionic.Utils.throttle;
ionic.proxy = ionic.Utils.proxy;
ionic.debounce = ionic.Utils.debounce;
})(window.ionic);

View File

@ -376,8 +376,6 @@
this._isDragging = false;
console.log(e.gesture.direction);
// Check if this is a reorder drag
if(ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_DRAG_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
var item = this._getItem(e.target);

View File

@ -40,7 +40,7 @@
dragThreshold: 10,
// Resistance when scrolling too far up or down
rubberBandResistance: 3,
rubberBandResistance: 2,
// Scroll event names. These are custom so can be configured
scrollEventName: 'momentumScrolled',
@ -48,6 +48,10 @@
hasPullToRefresh: true,
// Whether to disable overflow rubber banding when content is small
// enough to fit in the viewport (i.e. doesn't need scrolling)
disableNonOverflowRubberBand: false,
// Called as the refresher is opened, an amount is passed
onRefreshOpening: function() {},
// Called when let go and is refreshing
@ -241,6 +245,10 @@
}
},
/**
* Check if the current scroll bounds needs to be brought back to the min/max
* allowable given the total scrollable area.
*/
needsWrapping: function() {
var _this = this;
@ -463,14 +471,6 @@
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;
@ -507,6 +507,16 @@
resist: 1,
startTime: Date.now()
};
if(this.disableNonOverflowRubberBand === true) {
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)) {
this._drag.noRubberBand = true;
}
}
},
/**
@ -574,10 +584,23 @@
var newX = _this.x + deltaX;
var newY = _this.y + deltaY;
if(drag.noRubberBand === true) {
if(newY > 0) {
newY = 0;
} else if(newY < maxY) {
newY = maxY;
}
if(newX > 0) {
newX = 0;
} else if(newX < maxX) {
newX = maxX;
}
} else {
// Check if the dragging is beyond the bottom or top
if(newY > 0 || (-newY + parentHeight) > totalHeight) {
newY = _this.y + deltaY / _this.rubberBandResistance;
}
}
if(!_this.isHorizontalEnabled) {
newX = 0;
@ -588,7 +611,6 @@
if(_this._refresher && newY > 0) {
// We are pulling to refresh, so update the refresher
//_this._refresher.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + newY + 'px, 0)';
if(_this._isRefresherHidden) {
// Show it only in a drag and if we haven't showed it yet
_this._refresher.style.display = 'block';
@ -605,7 +627,7 @@
}
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
_this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
} else {
_this._isHoldingRefresh = false;
@ -616,7 +638,7 @@
_this._isRefresherHidden = true;
}
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
_this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
}
// Store the last points

View File

@ -22,4 +22,37 @@
}
});
ionic.views.SideMenuContent = ionic.views.View.inherit({
initialize: function(opts) {
var _this = this;
ionic.extend(this, {
animationClass: 'menu-animated',
onDrag: function(e) {},
onEndDrag: function(e) {},
}, opts);
ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
},
_onDrag: function(e) {
this.onDrag && this.onDrag(e);
},
_onEndDrag: function(e) {
this.onEndDrag && this.onEndDrag(e);
},
disableAnimation: function() {
this.el.classList.remove(this.animationClass);
},
enableAnimation: function() {
this.el.classList.add(this.animationClass);
},
getTranslateX: function() {
return parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]);
},
setTranslateX: function(x) {
this.el.style.webkitTransform = 'translate3d(' + x + 'px, 0, 0)';
}
});
})(ionic);

View File

@ -33,7 +33,7 @@
}
@-webkit-keyframes slideOutRight {
0% {
-webkit-transform: translate3d(0%,0,0);
-webkit-transform: translate3d(0,0,0);
}
100% {
-webkit-transform: translate3d(100%,0,0);
@ -87,13 +87,13 @@
.slide-out-right {
&.ng-enter, > .ng-enter {
-webkit-animation-duration: 2250ms;
-webkit-animation-duration: 250ms;
-webkit-animation-fill-mode: both;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-name: slideOutRight;
}
&.ng-leave, > .ng-leave {
-webkit-animation-duration: 2250ms;
-webkit-animation-duration: 250ms;
-webkit-animation-fill-mode: both;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-name: slideInRight;

View File

@ -122,8 +122,36 @@ body, .ionic-body {
//@include display-flex();
//@include align-items(center);
}
.ionic-refresher-content {
height: 100%;
width: 100%;
text-align: center;
position: relative;
}
.ionic-refresher {
// A custom refresher
width: 10px;
height: 10px;
border-radius: 50%;
background-color: $brand-primary;
position: absolute;
left: 50%;
margin-left: -5px;
bottom: 25px;
}
.scroll-refreshing {
-webkit-transition: height 0.1s ease-in-out;
.ionic-refresher {
-webkit-animation: refresher-pulsate 1.5s linear;
-webkit-animation-iteration-count: infinite;
}
}
@-webkit-keyframes refresher-pulsate {
0% {-webkit-transform: scale(2, 2); }
50% {-webkit-transform: scale(1.5, 1.5); }
100% {-webkit-transform: scale(2, 2); }
}
.overflow-scroll {

View File

@ -21,7 +21,7 @@
</header>
<div id="list">
<main id="content" class="scroll">
<main id="content" class="content overflow-scroll">
<div class="list">
@ -500,6 +500,8 @@
return false;
});
</script>
</body>

57
test/side-menus.html Normal file
View File

@ -0,0 +1,57 @@
<html>
<head>
<meta charset="utf-8">
<title>Side Menus</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link href="../dist/css/ionic.css" rel="stylesheet">
</head>
<body>
<div id="content" class="pane">
<header class="bar bar-header bar-dark">
<h1 class="title">Center</h1>
</header>
</div>
<div id="menu-left" class="menu menu-left">
<header class="bar bar-header bar-dark">
<h1 class="title">Left</h1>
</header>
</div>
<div id="menu-right" class="menu menu-right">
<header class="bar bar-header bar-dark">
<h1 class="title">Right</h1>
</header>
</div>
<script src="../dist/js/ionic.js"></script>
<script>
var contentEl = document.getElementById('content');
var content = new ionic.views.SideMenuContent({
el: contentEl
});
var leftMenuEl = document.getElementById('menu-left');
var leftMenu = new ionic.views.SideMenu({
el: leftMenuEl,
width: 270
});
var rightMenuEl = document.getElementById('menu-right');
var rightMenu = new ionic.views.SideMenu({
el: rightMenuEl,
width: 270
});
var sm = new ionic.controllers.SideMenuController({
content: content,
left: leftMenu,
right: rightMenu
});
</script>
</body>
</html>