diff --git a/config/build.config.js b/config/build.config.js
index 51f9230722f..4e21108f991 100644
--- a/config/build.config.js
+++ b/config/build.config.js
@@ -47,6 +47,7 @@ module.exports = {
'js/utils/tap.js',
'js/utils/activator.js',
'js/utils/utils.js',
+ 'js/utils/list.js',
'js/utils/keyboard.js',
'js/utils/viewport.js',
@@ -57,12 +58,7 @@ module.exports = {
'js/views/listView.js',
'js/views/modalView.js',
'js/views/sideMenuView.js',
- 'js/views/sliderView.js',
'js/views/toggleView.js',
-
- // Controllers
- 'js/controllers/viewController.js',
- 'js/controllers/sideMenuController.js',
],
angularIonicFiles: [
diff --git a/config/docs/templates/api_menu_version.template.html b/config/docs/templates/api_menu_version.template.html
index 47aa7c82580..a995e61053d 100644
--- a/config/docs/templates/api_menu_version.template.html
+++ b/config/docs/templates/api_menu_version.template.html
@@ -508,6 +508,18 @@
+
+
+ ion-slide-pager
+
+
+
+
+
+ ion-slide
+
+
+
$ionicSlideBoxDelegate
diff --git a/js/angular/controller/slideBoxController.js b/js/angular/controller/slideBoxController.js
new file mode 100644
index 00000000000..b078aff3ba6
--- /dev/null
+++ b/js/angular/controller/slideBoxController.js
@@ -0,0 +1,263 @@
+IonicModule
+.controller('$ionSlideBox', [
+ '$scope',
+ '$element',
+ '$$ionicAttachDrag',
+ '$interval',
+ /*
+ * This can be abstracted into a controller that will work for views, tabs, and
+ * slidebox.
+ */
+function(scope, element, $$ionicAttachDrag, $interval) {
+ var self = this;
+ var slideList = ionic.Utils.list([]);
+ var selectedIndex = -1;
+ var slidesParent = angular.element(element[0].querySelector('.slider-slides'));
+
+ // Successful slide requires velocity to be greater than this amount
+ var SLIDE_SUCCESS_VELOCITY = (1 / 4); // pixels / ms
+ var SLIDE_TRANSITION_DURATION = 250; //ms
+
+ $$ionicAttachDrag(scope, element, {
+ getDistance: function() { return slidesParent.prop('offsetWidth'); },
+ onDrag: onDrag,
+ onDragEnd: onDragEnd
+ });
+
+ self.element = element;
+ self.isRelevant = isRelevant;
+ self.previous = previous;
+ self.next = next;
+
+ // Methods calling straight back to Utils.list
+ self.at = slideList.at;
+ self.count = slideList.count;
+ self.indexOf = slideList.indexOf;
+ self.isInRange = slideList.isInRange;
+ self.loop = slideList.loop;
+ self.delta = slideList.delta;
+
+ self.enableSlide = enableSlide;
+ self.autoPlay = autoPlay;
+ self.add = add;
+ self.remove = remove;
+ self.move = move;
+ self.selected = selected;
+ self.select = select;
+ self.onDrag = onDrag;
+ self.onDragEnd = onDragEnd;
+
+ // ***
+ // Public Methods
+ // ***
+
+ // Gets whether the given index is relevant to selected
+ // That is, whether the given index is previous, selected, or next
+ function isRelevant(index) {
+ return slideList.isRelevant(index, selectedIndex);
+ }
+
+ // Gets the index to the previous of the given slide, default selectedIndex
+ function previous(index) {
+ index = arguments.length ? index : selectedIndex;
+ // If we only have two slides and loop is enabled, we cannot have a previous
+ // because previous === next. In this case, return -1.
+ if (self.loop() && self.count() === 2) {
+ return -1;
+ }
+ return slideList.previous(index);
+ }
+
+ // Gets the index to the next of the given slide, default selectedIndex
+ function next(index) {
+ index = arguments.length ? index : selectedIndex;
+ return slideList.next(index);
+ }
+
+ function enableSlide(isEnabled) {
+ if (arguments.length) {
+ self.dragDisabled = !isEnabled;
+ }
+ return !!self.dragDisabled;
+ }
+
+ function autoPlay(newInterval) {
+ $interval.cancel(self.autoPlayTimeout);
+
+ if (angular.isNumber(newInterval) && newInterval > 0) {
+ self.autoPlayTimeout = $interval(function() {
+ self.select(self.next());
+ }, newInterval);
+ }
+ }
+
+ /*
+ * Add/remove/move slides
+ */
+ function add(slide, index) {
+ var newIndex = slideList.add(slide, index);
+ slide.onAdded(slidesParent);
+
+ if (selectedIndex === -1) {
+ self.select(newIndex);
+ } else if (newIndex === self.previous() || newIndex === self.next()) {
+ // if the new slide is adjacent to selected, refresh the selection
+ enqueueRefresh();
+ }
+ }
+ function remove(slide) {
+ var index = self.indexOf(slide);
+ if (index === -1) return;
+
+ var isSelected = self.selected() === index;
+ slideList.remove(index);
+ slide.onRemoved();
+
+ if (isSelected) {
+ self.select( self.isInRange(selectedIndex) ? selectedIndex : selectedIndex - 1 );
+ }
+ }
+ function move(slide, targetIndex) {
+ var index = self.indexOf(slide);
+ if (index === -1) return;
+
+ // If the slide is current, next, or previous, save so we can re-select after moving.
+ var isRelevant = self.isRelevant(targetIndex);
+ slideList.remove(index);
+ slideList.add(slide, targetIndex);
+
+ if (isRelevant) {
+ enqueueRefresh();
+ }
+ }
+
+ function selected() {
+ return selectedIndex;
+ }
+
+ /*
+ * Select and change slides
+ */
+ function select(newIndex, transitionDuration) {
+ if (!self.isInRange(newIndex)) return;
+
+ var delta = self.delta(selectedIndex, newIndex);
+
+ slidesParent.css(
+ ionic.CSS.TRANSITION_DURATION,
+ (transitionDuration || SLIDE_TRANSITION_DURATION) + 'ms'
+ );
+ selectedIndex = newIndex;
+
+ if (self.isInRange(selectedIndex) && Math.abs(delta) > 1) {
+ // if the new slide is > 1 away, then it is currently not attached to the DOM.
+ // Attach it in the position from which it will slide in.
+ self.at(newIndex).setState(delta > 1 ? 'next' : 'previous');
+ // Wait one frame so the new slide can 'settle' in its new place and
+ // be ready to properly transition in
+ ionic.requestAnimationFrame(doSelect);
+ } else {
+ doSelect();
+ }
+
+ function doSelect() {
+ // If a new selection has happened before this frame, abort.
+ if (selectedIndex !== newIndex) return;
+ scope.$evalAsync(function() {
+ if (selectedIndex !== newIndex) return;
+ arrangeSlides(newIndex);
+ });
+ }
+ }
+
+ // percent is negative 0-1 for backward slide
+ // positive 0-1 for forward slide
+ function onDrag(percent) {
+ if (self.dragDisabled) return;
+
+ var target = self.at(percent > 0 ? self.next() : self.previous());
+ var current = self.at(self.selected());
+
+ target && target.transform(percent);
+ current && current.transform(percent);
+ }
+
+ function onDragEnd(percent, velocity) {
+ var nextIndex = -1;
+ if (Math.abs(percent) > 0.5 || velocity > SLIDE_SUCCESS_VELOCITY) {
+ nextIndex = percent > 0 ? self.next() : self.previous();
+ }
+ var transitionDuration = Math.min(
+ slidesParent.prop('offsetWidth') / (3 * velocity),
+ SLIDE_TRANSITION_DURATION
+ );
+
+ // Select a new slide if it's avaiable
+ self.select(
+ self.isInRange(nextIndex) ? nextIndex : self.selected(),
+ transitionDuration
+ );
+ }
+
+ // ***
+ // Private Methods
+ // ***
+
+ var oldSlides;
+ function arrangeSlides(newShownIndex) {
+ var newSlides = {
+ previous: self.at(self.previous(newShownIndex)),
+ selected: self.at(newShownIndex),
+ next: self.at(self.next(newShownIndex))
+ };
+
+ newSlides.previous && newSlides.previous.setState('previous');
+ newSlides.selected && newSlides.selected.setState('selected');
+ newSlides.next && newSlides.next.setState('next');
+
+ if (oldSlides) {
+ var oldShown = oldSlides.selected;
+ var delta = self.delta(self.indexOf(oldSlides.selected), self.indexOf(newSlides.selected));
+ if (Math.abs(delta) > 1) {
+ // If we're changing by more than one slide, we need to manually transition
+ // the current slide out and then put it into its new state.
+ oldShown.setState(delta > 1 ? 'previous' : 'next').then(function() {
+ oldShown.setState(
+ newSlides.previous === oldShown ? 'previous' :
+ newSlides.next === oldShown ? 'next' :
+ 'detached'
+ );
+ });
+ } else {
+ detachIfUnused(oldSlides.selected);
+ }
+ //Additionally, we need to detach both of the old slides.
+ detachIfUnused(oldSlides.previous);
+ detachIfUnused(oldSlides.next);
+ }
+
+ function detachIfUnused(oldSlide) {
+ if (oldSlide && oldSlide !== newSlides.previous &&
+ oldSlide !== newSlides.selected &&
+ oldSlide !== newSlides.next) {
+ oldSlide.setState('detached');
+ }
+ }
+
+ oldSlides = newSlides;
+ }
+
+ // When adding/moving slides, we sometimes need to refresh
+ // the currently selected slides to reflect new data.
+ // We don't want to refresh more than once per digest cycle,
+ // so we do this.
+ function enqueueRefresh() {
+ if (!enqueueRefresh.queued) {
+ enqueueRefresh.queued = true;
+ scope.$$postDigest(function() {
+ self.select(selectedIndex);
+ enqueueRefresh.queued = false;
+ });
+ }
+ }
+}]);
diff --git a/js/angular/controller/slideController.js b/js/angular/controller/slideController.js
new file mode 100644
index 00000000000..6dfc1714b58
--- /dev/null
+++ b/js/angular/controller/slideController.js
@@ -0,0 +1,122 @@
+IonicModule
+.controller('$ionSlide', [
+ '$scope',
+ '$element',
+ '$q',
+function(scope, element, $q) {
+ var self = this;
+
+ scope.$on('$destroy', function() {
+ element.removeData();
+ detachSlide();
+ });
+ element.on(ionic.CSS.TRANSITIONEND, onTransitionEnd);
+
+ self.element = element;
+
+ self.onAdded = onAdded;
+ self.onRemoved = onRemoved;
+
+ self.transform = transform;
+
+ self.state = '';
+ self.setState = setState;
+
+ // ***
+ // Public Methods
+ // ***
+
+ function onAdded(parentElement) {
+ self.parentElement = parentElement;
+
+ // Set default state
+ self.setState('detached');
+ }
+ function onRemoved() {
+ self.setState('detached');
+ }
+
+ var isTransforming;
+ // percent is negative 0-1 for dragging left
+ // percent is positive 0-1 for dragging right
+ function transform(percent) {
+ if (!isTransforming) {
+ self.element.addClass('no-animate');
+ isTransforming = true;
+ }
+
+ var startPercent = self.state === 'previous' ? -1 :
+ self.state === 'next' ? 1 :
+ 0;
+ self.element.css(
+ ionic.CSS.TRANSFORM,
+ 'translate3d(' + (100 * (startPercent - percent)) + '%, 0, 0)'
+ );
+ }
+
+ function setState(newState) {
+ if (newState !== self.state) {
+ self.state && self.element.attr('slide-previous-state', self.state);
+ self.element.attr('slide-state', newState);
+ }
+ self.element.css(ionic.CSS.TRANSFORM, '');
+ self.element.removeClass('no-animate');
+ isTransforming = false;
+
+ self.previousState = self.state;
+ self.state = newState;
+
+ switch(newState) {
+ case 'detached':
+ detachSlide();
+ break;
+ case 'previous':
+ case 'next':
+ case 'selected':
+ attachSlide();
+ break;
+ }
+
+ return getTransitionPromise();
+ }
+
+ // ***
+ // Private Methods
+ // ***
+
+ function attachSlide() {
+ if (!self.element[0].parentNode) {
+ self.parentElement.append(self.element);
+ ionic.Utils.reconnectScope(scope);
+ }
+ }
+
+ function detachSlide() {
+ // Don't use self.element.remove(), that will destroy the element's data
+ var parent = self.element[0].parentNode;
+ if (parent) {
+ parent.removeChild(self.element[0]);
+ ionic.Utils.disconnectScope(scope);
+ }
+ }
+
+ var transitionDeferred;
+ function getTransitionPromise() {
+ // If we aren't transitioning to or from selected, there's no transition, so instantly resolve.
+ if (self.previousState !== 'selected' && self.state !== 'selected') {
+ return $q.when();
+ }
+
+ // Interrupt current promise if a new state was set.
+ transitionDeferred && transitionDeferred.reject();
+ transitionDeferred = $q.defer();
+
+ return transitionDeferred.promise;
+ }
+
+ function onTransitionEnd(ev) {
+ if (ev.target !== element[0]) return; //don't let the event bubble up from children
+ transitionDeferred && transitionDeferred.resolve();
+ }
+
+}]);
diff --git a/js/angular/directive/infiniteScroll.js b/js/angular/directive/infiniteScroll.js
index 93cadc71196..1466c1f96dc 100644
--- a/js/angular/directive/infiniteScroll.js
+++ b/js/angular/directive/infiniteScroll.js
@@ -119,7 +119,6 @@ IonicModule
});
$scope.$on('$destroy', function() {
- console.log(scrollCtrl);
if(scrollCtrl && scrollCtrl.$element)scrollCtrl.$element.off('scroll', checkBounds);
});
diff --git a/js/angular/directive/slide.js b/js/angular/directive/slide.js
new file mode 100644
index 00000000000..10368d77556
--- /dev/null
+++ b/js/angular/directive/slide.js
@@ -0,0 +1,68 @@
+/**
+ * @ngdoc directive
+ * @name ionSlide
+ * @parent ionic.directive:ionSlideBox
+ * @module ionic
+ *
+ * @description
+ * Displays a slide inside of a slidebox.
+ *
+ * For more complete examples, see {@link ionic.directive:ionSlideBox}.
+ *
+ * @usage
+ * ```html
+ *
+ * 1
+ * 2
+ *
+ * ```
+ */
+IonicModule
+.directive('ionSlide', [function() {
+ return {
+ restrict: 'E',
+ controller: '$ionSlide',
+ scope: true,
+ require: ['^ionSlideBox', 'ionSlide'],
+ link: postLink
+ };
+
+ function postLink(scope, element, attr, ctrls) {
+ var slideBoxCtrl = ctrls[0];
+ var slideCtrl = ctrls[1];
+
+ element.addClass('slider-slide');
+
+ slideBoxCtrl.add(slideCtrl);
+ scope.$on('$destroy', function() {
+ slideBoxCtrl.remove(slideCtrl);
+ });
+
+ element.one('$animate:after', watchNgRepeatIndexOnInsertElement);
+ element.on('$animate:after', refreshStateOnInsertElement);
+
+ // If this element is inserted later by an ng-if or ng-repeat, remove it
+ // from the DOM again if it's irrelevant (not selected or adjacent).
+ function refreshStateOnInsertElement() {
+ var slideIndex = slideBoxCtrl.indexOf(slideCtrl);
+ if (!slideBoxCtrl.isRelevant(slideIndex)) {
+ slideCtrl.setState('detached');
+ }
+ }
+
+ // Move with ng-repeat if this slide is part of ng-repeat.
+ // scope.$index only appears after the first time ng-repaet inserts the element.
+ function watchNgRepeatIndexOnInsertElement() {
+ if (angular.isNumber(scope.$index)) {
+ scope.$watch('$index', function(newIndex, oldIndex) {
+ if (!isDefined(oldIndex)) return;
+ var difference = newIndex - oldIndex;
+ var currentIndex = slideBoxCtrl.indexOf(slideCtrl);
+
+ slideBoxCtrl.move(slideCtrl, currentIndex + difference);
+ });
+ }
+ }
+
+ }
+}]);
diff --git a/js/angular/directive/slideBox.js b/js/angular/directive/slideBox.js
index c9b7587875b..da5d5bc90eb 100644
--- a/js/angular/directive/slideBox.js
+++ b/js/angular/directive/slideBox.js
@@ -1,4 +1,3 @@
-
/**
* @ngdoc directive
* @name ionSlideBox
@@ -10,177 +9,129 @@
*
* ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
*
+ * Note: The slideBox will always take up all of the space within its parent scroll
+ * container. If you wish to have a smaller slidebox, create a custom-sized parent
+ * element.
+ *
* @usage
* ```html
- *
- *
- * BLUE
- *
- *
- * YELLOW
- *
- *
- * PINK
- *
- *
+ *
+ *
+ *
+ * BLUE
+ *
+ *
+ * YELLOW
+ *
+ *
+ * PINK
+ *
+ *
+ *
* ```
*
- * @param {string=} delegate-handle The handle used to identify this slideBox
+ * @param {expression=} selected A model bound to the selected slide index.
* with {@link ionic.service:$ionicSlideBoxDelegate}.
- * @param {boolean=} does-continue Whether the slide box should loop.
- * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
- * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
- * @param {boolean=} show-pager Whether a pager should be shown for this slide box.
- * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
+ * @param {boolean=} loop Whether the slide box should loop. Default false.
+ * @param {number=} auto-play If a positive number, then every time the given number of milliseconds have passed, slideBox will go to the next slide. Set to a non-positive number to disable. Default: -1.
* @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed an '$index' variable.
- * @param {expression=} active-slide Model to bind the current slide to.
+ * @param {string=} delegate-handle The handle used to identify this slideBox with
+ * {@link ionic.service:$ionicSlideBoxDelegate}.
*/
IonicModule
.directive('ionSlideBox', [
- '$timeout',
- '$compile',
'$ionicSlideBoxDelegate',
-function($timeout, $compile, $ionicSlideBoxDelegate) {
+ '$window',
+function($ionicSlideBoxDelegate, $window) {
+
return {
restrict: 'E',
- replace: true,
+ controller: '$ionSlideBox',
+ require: ['ionSlideBox', '^$ionicScroll'],
transclude: true,
scope: {
- autoPlay: '=',
- doesContinue: '@',
- slideInterval: '@',
- showPager: '@',
- pagerClick: '&',
- disableScroll: '@',
- onSlideChanged: '&',
- activeSlide: '=?'
+ selectedIndex: '=?selected',
+ onSlideChanged: '&'
},
- controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
- var _this = this;
-
- var continuous = $scope.$eval($scope.doesContinue) === true;
- var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
- var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
-
- var slider = new ionic.views.Slider({
- el: $element[0],
- auto: slideInterval,
- continuous: continuous,
- startSlide: $scope.activeSlide,
- slidesChanged: function() {
- $scope.currentSlide = slider.currentIndex();
-
- // Try to trigger a digest
- $timeout(function() {});
- },
- callback: function(slideIndex) {
- $scope.currentSlide = slideIndex;
- $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
- $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
- $scope.activeSlide = slideIndex;
- // Try to trigger a digest
- $timeout(function() {});
- }
- });
+ template: '',
+ compile: compile
+ };
- slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
+ function compile(element, attr) {
+ // DEPRECATED attr.doesContinue
+ isDefined(attr.doesContinue) && attr.$set('loop', attr.doesContinue);
- $scope.$watch('activeSlide', function(nv) {
- if(angular.isDefined(nv)){
- slider.slide(nv);
- }
- });
+ return postLink;
+ }
- $scope.$on('slideBox.nextSlide', function() {
- slider.next();
- });
+ function postLink(scope, element, attr, ctrls) {
+ var slideBoxCtrl = ctrls[0];
+ var scrollCtrl = ctrls[1];
- $scope.$on('slideBox.prevSlide', function() {
- slider.prev();
- });
+ element.addClass('slider');
- $scope.$on('slideBox.setSlide', function(e, index) {
- slider.slide(index);
- });
+ var deregister = $ionicSlideBoxDelegate._registerInstance(slideBoxCtrl, attr.delegateHandle);
+ scope.$on('$destroy', deregister);
- //Exposed for testing
- this.__slider = slider;
+ isDefined(attr.loop) && watchLoop();
+ isDefined(attr.selected) && watchSelected();
+ isDefined(attr.autoPlay) && watchAutoPlay();
- var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(slider, $attrs.delegateHandle);
- $scope.$on('$destroy', deregisterInstance);
+ var throttledReposition = ionic.animationFrameThrottle(repositionSlideBox);
+ throttledReposition();
- this.slidesCount = function() {
- return slider.slidesCount();
- };
+ var oldScrollingY = scrollCtrl.scrollView.options.scrollingY;
+ scrollCtrl.scrollView.options.scrollingY = false;
- this.onPagerClick = function(index) {
- console.log('pagerClick', index);
- $scope.pagerClick({index: index});
- };
+ angular.element($window).on('resize', throttledReposition);
+ scope.$on('$destroy', function() {
+ angular.element($window).off('resize', throttledReposition);
+ scrollCtrl.scrollView.options.scrollingY = oldScrollingY;
+ });
- $timeout(function() {
- slider.load();
+ // ***
+ // Methods
+ // ***
+
+ // There is no way to make the slidebox stretch to a large enough size
+ // when its children are all position: absolute elements.
+ // We just make it so the slidebox is *always* as large as its parent scroll
+ // container.
+ function repositionSlideBox() {
+ element.css({
+ width: scrollCtrl.$element.prop('offsetWidth') + 'px',
+ height: scrollCtrl.$element.prop('offsetHeight') + 'px'
});
- }],
- template: '',
-
- link: function($scope, $element, $attr, slideBoxCtrl) {
- // If the pager should show, append it to the slide box
- if($scope.$eval($scope.showPager) !== false) {
- var childScope = $scope.$new();
- var pager = jqLite('');
- $element.append(pager);
- $compile(pager)(childScope);
- }
}
- };
-}])
-.directive('ionSlide', function() {
- return {
- restrict: 'E',
- require: '^ionSlideBox',
- compile: function(element, attr) {
- element.addClass('slider-slide');
- return function($scope, $element, $attr) {
- };
- },
- };
-})
-.directive('ionPager', function() {
- return {
- restrict: 'E',
- replace: true,
- require: '^ionSlideBox',
- template: '',
- link: function($scope, $element, $attr, slideBox) {
- var selectPage = function(index) {
- var children = $element[0].children;
- var length = children.length;
- for(var i = 0; i < length; i++) {
- if(i == index) {
- children[i].classList.add('active');
- } else {
- children[i].classList.remove('active');
- }
+ function watchLoop() {
+ var unwatchParent = scope.$parent.$watch(attr.loop, slideBoxCtrl.loop);
+ scope.$on('$destroy', unwatchParent);
+ }
+
+ function watchSelected() {
+ scope.$watch('selectedIndex', function selectedAttrWatchAction(newIndex) {
+ if (slideBoxCtrl.isInRange(newIndex) &&
+ slideBoxCtrl.selected() !== newIndex) {
+ slideBoxCtrl.select(newIndex);
}
- };
+ });
+ scope.$watch(slideBoxCtrl.selected, function shownWatchAction(newIndex) {
+ scope.selectedIndex = newIndex;
+ scope.onSlideChanged({
+ $index: newIndex
+ });
+ });
+ }
- $scope.pagerClick = function(index) {
- slideBox.onPagerClick(index);
- };
+ function watchAutoPlay() {
+ var unwatchParent = scope.$parent.$watch(attr.autoPlay, slideBoxCtrl.autoPlay);
+ scope.$on('$destroy', unwatchParent);
+ }
+ }
- $scope.numSlides = function() {
- return new Array(slideBox.slidesCount());
- };
+}]);
- $scope.$watch('currentSlide', function(v) {
- selectPage(v);
- });
- }
- };
-});
diff --git a/js/angular/directive/slideBoxPager.js b/js/angular/directive/slideBoxPager.js
new file mode 100644
index 00000000000..08e24517682
--- /dev/null
+++ b/js/angular/directive/slideBoxPager.js
@@ -0,0 +1,77 @@
+/**
+ * @ngdoc directive
+ * @name ionSlidePager
+ * @parent ionic.directive:ionSlideBox
+ * @module ionic
+ * @description
+ * Shows a pager for the slidebox.
+ *
+ * A pager is a row of small buttons at the bottom of the slidebox, each
+ * representing one slide. When the user clicks a pager, that slide will
+ * be selected.
+ *
+ * For more complete examples, see {@link ionic.directive:ionSlideBox}.
+ *
+ * @usage
+ * This will show four pager buttons, one for each slide.
+ *
+ * ```html
+ *
+ *
+ * 1
+ * 2
+ * 3
+ * 4
+ *
+ * ```
+ *
+ * @param {expression=} ng-click By default, clicking a pager will select the corresponding
+ * slide. You can override this by providing an ng-click expression. The ng-click
+ * expression will be provided a `$slideIndex` variable, signifying the slide index
+ * matching the click.
+ */
+IonicModule.directive('ionSlidePager', [
+ '$parse',
+function($parse) {
+ return {
+ restrict: 'E',
+ require: '^ionSlideBox',
+ scope: {},
+ template:
+ '',
+ link: postLink
+ };
+
+ function postLink(scope, element, attr, slideBoxCtrl) {
+ var clickFn = attr.ngClick ?
+ $parse(attr.ngClick) :
+ function(scope, locals) {
+ slideBoxCtrl.select(locals.$slideIndex);
+ };
+
+ element.addClass('slider-pager');
+ scope.slideBoxCtrl = slideBoxCtrl;
+ scope.pages = [];
+
+ scope.click = onPagerClicked;
+ scope.$watch(slideBoxCtrl.count, watchCountAction);
+
+ function onPagerClicked(index) {
+ clickFn(scope.$parent, {
+ $slideIndex: index
+ });
+ }
+
+ function watchCountAction(slidesCount) {
+ scope.pages.length = slidesCount;
+ for (var i = 0; i < slidesCount; i++) {
+ scope.pages[i] = i;
+ }
+ }
+ }
+
+}]);
diff --git a/js/angular/service/attachDrag.js b/js/angular/service/attachDrag.js
new file mode 100644
index 00000000000..665e984ab70
--- /dev/null
+++ b/js/angular/service/attachDrag.js
@@ -0,0 +1,68 @@
+
+/**
+ * @private
+ */
+IonicModule
+.factory('$$ionicAttachDrag', [function() {
+
+ return attachDrag;
+
+ function attachDrag(scope, element, options) {
+ var opts = extend({}, {
+ getDistance: function() { return opts.element.prop('offsetWidth'); },
+ onDragStart: angular.noop,
+ onDrag: angular.noop,
+ onDragEnd: angular.noop,
+ }, options);
+
+ var dragStartGesture = ionic.onGesture('dragstart', handleDragStart, element[0]);
+ var dragGesture = ionic.onGesture('drag', handleDrag, element[0]);
+ var dragEndGesture = ionic.onGesture('dragend', handleDragEnd, element[0]);
+
+ scope.$on('$destroy', function() {
+ ionic.offGesture(dragStartGesture);
+ ionic.offGesture(dragGesture);
+ ionic.offGesture(dragEndGesture);
+ });
+
+ var dragState;
+ function handleDragStart(ev) {
+ if (dragState) return;
+ dragState = {
+ startX: ev.gesture.center.pageX,
+ startY: ev.gesture.center.pageY,
+ distance: opts.getDistance()
+ };
+ opts.onDragStart();
+ }
+ function handleDrag(ev) {
+ if (!dragState) return;
+ var deltaX = dragState.startX - ev.gesture.center.pageX;
+ var deltaY = dragState.startY - ev.gesture.center.pageY;
+ var isVertical = ev.gesture.direction === 'up' || ev.gesture.direction === 'down';
+
+ if (isVertical && Math.abs(deltaY) > Math.abs(deltaX)) {
+ handleDragEnd(ev);
+ return;
+ }
+ dragState.dragging = true;
+
+ var percent = getDragPercent(ev.gesture.center.pageX);
+ opts.onDrag(percent);
+ }
+ function handleDragEnd(ev) {
+ if (!dragState) return;
+ var percent = getDragPercent(ev.gesture.center.pageX);
+ options.onDragEnd(percent, ev.gesture.velocityX);
+
+ dragState = null;
+ }
+
+ function getDragPercent(x) {
+ var delta = dragState.startX - x;
+ var percent = delta / dragState.distance;
+ return percent;
+ }
+ }
+
+}]);
diff --git a/js/angular/service/collectionRepeatDataSource.js b/js/angular/service/collectionRepeatDataSource.js
index f10ca42d131..56ef42bea61 100644
--- a/js/angular/service/collectionRepeatDataSource.js
+++ b/js/angular/service/collectionRepeatDataSource.js
@@ -72,7 +72,7 @@ function($cacheFactory, $parse, $rootScope) {
if ( (item = this.attachedItems[index]) ) {
//do nothing, the item is good
} else if ( (item = this.backupItemsArray.pop()) ) {
- reconnectScope(item.scope);
+ ionic.Utils.reconnectScope(item.scope);
} else {
item = this.createItem();
}
@@ -131,7 +131,7 @@ function($cacheFactory, $parse, $rootScope) {
this.backupItemsArray.push(item);
hideWithTransform(item.element);
//Don't .$destroy(), just stop watchers and events firing
- disconnectScope(item.scope);
+ ionic.Utils.disconnectScope(item.scope);
}
},
@@ -153,44 +153,3 @@ function($cacheFactory, $parse, $rootScope) {
return CollectionRepeatDataSource;
}]);
-
-function disconnectScope(scope) {
- if (scope.$root === scope) {
- return; // we can't disconnect the root node;
- }
- var parent = scope.$parent;
- scope.$$disconnected = true;
- // See Scope.$destroy
- if (parent.$$childHead === scope) {
- parent.$$childHead = scope.$$nextSibling;
- }
- if (parent.$$childTail === scope) {
- parent.$$childTail = scope.$$prevSibling;
- }
- if (scope.$$prevSibling) {
- scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
- }
- if (scope.$$nextSibling) {
- scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
- }
- scope.$$nextSibling = scope.$$prevSibling = null;
-}
-
-function reconnectScope(scope) {
- if (scope.$root === scope) {
- return; // we can't disconnect the root node;
- }
- if (!scope.$$disconnected) {
- return;
- }
- var parent = scope.$parent;
- scope.$$disconnected = false;
- // See Scope.$new for this logic...
- scope.$$prevSibling = parent.$$childTail;
- if (parent.$$childHead) {
- parent.$$childTail.$$nextSibling = scope;
- parent.$$childTail = scope;
- } else {
- parent.$$childHead = parent.$$childTail = scope;
- }
-}
diff --git a/js/angular/service/slideBoxDelegate.js b/js/angular/service/slideBoxDelegate.js
index 52e6908f48d..4f4db0b9d2e 100644
--- a/js/angular/service/slideBoxDelegate.js
+++ b/js/angular/service/slideBoxDelegate.js
@@ -29,7 +29,7 @@
* ```js
* function MyCtrl($scope, $ionicSlideBoxDelegate) {
* $scope.nextSlide = function() {
- * $ionicSlideBoxDelegate.next();
+ * $ionicSlideBoxDelegate.select( $ionicSlideBoxDelegate.next() );
* }
* }
* ```
@@ -38,63 +38,57 @@ IonicModule
.service('$ionicSlideBoxDelegate', delegateService([
/**
* @ngdoc method
- * @name $ionicSlideBoxDelegate#update
- * @description
- * Update the slidebox (for example if using Angular with ng-repeat,
- * resize it for the elements inside).
+ * @name $ionicSlideBoxDelegate#select
+ * @param {number} slideIndex The index to select.
*/
- 'update',
+ 'select',
/**
* @ngdoc method
- * @name $ionicSlideBoxDelegate#slide
- * @param {number} to The index to slide to.
- * @param {number=} speed The number of milliseconds for the change to take.
+ * @name $ionicSlideBoxDelegate#selected
+ * @returns `slideIndex` The index of the currently selected slide.
*/
- 'slide',
+ 'selected',
/**
* @ngdoc method
- * @name $ionicSlideBoxDelegate#enableSlide
- * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
- * @returns {boolean} Whether sliding is enabled.
+ * @name $ionicSlideBoxDelegate#loop
+ * @description Sets/gets the looping state of the slidebox (whether going next from the last slide will go back to the first slide, and vice versa).
+ * @param {boolean=} shouldLoop Set whether the slidebox should loop.
+ * @returns `isLoop` Whether looping is currently enabled.
*/
- 'enableSlide',
+ 'loop',
/**
* @ngdoc method
* @name $ionicSlideBoxDelegate#previous
- * @description Go to the previous slide. Wraps around if at the beginning.
+ * @returns `slideIndex` The index of the previous slide. Wraps around if loop is enabled.
*/
'previous',
/**
* @ngdoc method
* @name $ionicSlideBoxDelegate#next
- * @description Go to the next slide. Wraps around if at the end.
+ * @returns `slideIndex` The index of the next slide. Wraps around if loop is enabled.
*/
'next',
/**
* @ngdoc method
- * @name $ionicSlideBoxDelegate#stop
- * @description Stop sliding. The slideBox will not move again until
- * explicitly told to do so.
+ * @name $ionicSlideBoxDelegate#autoPlay
+ * @description Set whether the slidebox should automatically play, and at what rate.
+ * @param {*} autoPlayInterval How many milliseconds delay until changing to the next slide.
+ * Set to zero or false to stop autoPlay.
*/
- 'stop',
+ 'autoPlay',
/**
* @ngdoc method
- * @name $ionicSlideBoxDelegate#start
- * @description Start sliding again if the slideBox was stopped.
- */
- 'start',
- /**
- * @ngdoc method
- * @name $ionicSlideBoxDelegate#currentIndex
- * @returns number The index of the current slide.
+ * @name $ionicSlideBoxDelegate#enableSlide
+ * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
+ * @returns `boolean` Whether sliding is enabled.
*/
- 'currentIndex',
+ 'enableSlide',
/**
* @ngdoc method
- * @name $ionicSlideBoxDelegate#slidesCount
- * @returns number The number of slides there are currently.
+ * @name $ionicSlideBoxDelegate#count
+ * @returns `number` The number of slides there are currently.
*/
- 'slidesCount'
+ 'count'
/**
* @ngdoc method
* @name $ionicSlideBoxDelegate#$getByHandle
@@ -103,7 +97,7 @@ IonicModule
* {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
* the given handle.
*
- * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
+ * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').select(0);`
*/
]));
diff --git a/js/utils/gestures.js b/js/utils/gestures.js
index ab921e3d202..3dcb9078080 100644
--- a/js/utils/gestures.js
+++ b/js/utils/gestures.js
@@ -430,7 +430,7 @@
}
if(this.srcEvent.preventDefault) {
- //this.srcEvent.preventDefault();
+ this.srcEvent.preventDefault();
}
},
diff --git a/js/utils/list.js b/js/utils/list.js
new file mode 100644
index 00000000000..47091037ec9
--- /dev/null
+++ b/js/utils/list.js
@@ -0,0 +1,150 @@
+(function() {
+
+function trueFn() { return true; }
+
+ionic.Utils.list = list;
+
+function list(initialArray) {
+
+ var array = angular.isArray(initialArray) ? initialArray : [];
+ var self = {};
+ var isLooping = false;
+
+ // The Basics
+ self.items = items;
+ self.add = add;
+ self.remove = remove;
+ self.at = at;
+ self.count = count;
+ self.indexOf = angular.bind(array, array.indexOf);
+ self.isInRange = isInRange;
+ self.loop = loop;
+
+ // The Crazy Ones
+ self.delta = delta;
+ self.isRelevant = isRelevant;
+ self.previous = previous;
+ self.next = next;
+
+ return self;
+
+ // ***************
+ // Public methods
+ // ***************
+ function items() {
+ return array;
+ }
+ function add(item, index) {
+ if (!self.isInRange(index)) index = array.length;
+ array.splice(index, 0, item);
+ return index;
+ }
+
+ function remove(index) {
+ if (!self.isInRange(index)) return;
+ array.splice(index, 1);
+ }
+
+ function at(index) {
+ return array[index];
+ }
+
+ function count() {
+ return array.length;
+ }
+
+ function isInRange(index) {
+ return index > -1 && index < array.length;
+ }
+
+ function loop(newIsLooping) {
+ if (arguments.length) {
+ isLooping = !!newIsLooping;
+ }
+ return isLooping;
+ }
+
+ function delta(fromIndex, toIndex) {
+ var difference = toIndex - fromIndex;
+ if (!isLooping) return difference;
+
+ // Is looping on? Then check for the looped difference.
+ // For example, going from the first item to the last item
+ // is actually a change of -1.
+ var loopedDifference = 0;
+ if (toIndex > fromIndex) {
+ loopedDifference = toIndex - fromIndex - self.count();
+ } else {
+ loopedDifference = self.count() - fromIndex + toIndex;
+ }
+
+ if (Math.abs(loopedDifference) < Math.abs(difference)) {
+ return loopedDifference;
+ }
+ return difference;
+ }
+
+ // Returns whether an index is 'relevant' to some index.
+ // Will return true if the index is equal to `someIndex`, the index
+ // previous of that index, or the index next of that index.
+ function isRelevant(index, someIndex) {
+ return self.isInRange(index) && (
+ index === someIndex ||
+ index === self.previous(someIndex) ||
+ index === self.next(someIndex)
+ );
+ }
+
+ // Get the index after the given index.
+ // Takes looping and the given filterFn into account.
+ function next(index, filterFn) {
+ filterFn = filterFn || trueFn;
+ if (!self.isInRange(index)) return -1;
+
+ // Keep adding 1 to index, trying to find an index that passes filterFn.
+ // If we loop through *everything* and get back to our original index, return -1.
+ // We don't use recursion here because Javascript sucks at recursion.
+ var nextIndex = index + 1;
+ while ( nextIndex !== index ) {
+
+ if (nextIndex === array.length) {
+ if (isLooping) nextIndex -= array.length;
+ else break;
+ } else {
+ if (filterFn(array[nextIndex], nextIndex)) {
+ return nextIndex;
+ }
+ nextIndex++;
+ }
+ }
+ return -1;
+ }
+
+ // Get the index before the given index.
+ // Takes looping and the given filterFn into account.
+ function previous(index, filterFn) {
+ filterFn = filterFn || trueFn;
+ if (!self.isInRange(index)) return -1;
+
+ // Keep subtracting 1 from index, trying to find an index that passes filterFn.
+ // If we loop through *everything* and get back to our original index, return -1.
+ // We don't use recursion here because Javascript sucks at recursion.
+ var prevIndex = index - 1;
+ while ( prevIndex !== index ) {
+
+ if (prevIndex === -1) {
+ if (isLooping) prevIndex += array.length;
+ else break;
+ } else {
+ if (filterFn(array[prevIndex], prevIndex)) {
+ return prevIndex;
+ }
+ prevIndex--;
+ }
+ }
+ return -1;
+ }
+
+}
+
+})();
diff --git a/js/utils/poly.js b/js/utils/poly.js
index 5cf7f0447af..57c6a969f25 100644
--- a/js/utils/poly.js
+++ b/js/utils/poly.js
@@ -11,7 +11,7 @@
'-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform'];
for(i = 0; i < keys.length; i++) {
- if(document.documentElement.style[keys[i]] !== undefined) {
+ if (document.documentElement.style[keys[i]] !== undefined) {
ionic.CSS.TRANSFORM = keys[i];
break;
}
@@ -20,12 +20,20 @@
// transition
keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition'];
for(i = 0; i < keys.length; i++) {
- if(document.documentElement.style[keys[i]] !== undefined) {
+ if (document.documentElement.style[keys[i]] !== undefined) {
ionic.CSS.TRANSITION = keys[i];
break;
}
}
+ // The only prefix we care about is webkit for transitions.
+ var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1;
+
+ // transition duration
+ ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration';
+
+ // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events
+ ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend';
})();
// classList polyfill for them older Androids
diff --git a/js/utils/utils.js b/js/utils/utils.js
index 96e2a821134..9bd5dd18985 100644
--- a/js/utils/utils.js
+++ b/js/utils/utils.js
@@ -172,6 +172,51 @@
}
uid.unshift('0');
return uid.join('');
+ },
+
+ disconnectScope: function disconnectScope(scope) {
+ if (!scope) return;
+
+ if (scope.$root === scope) {
+ return; // we can't disconnect the root node;
+ }
+ var parent = scope.$parent;
+ scope.$$disconnected = true;
+ // See Scope.$destroy
+ if (parent.$$childHead === scope) {
+ parent.$$childHead = scope.$$nextSibling;
+ }
+ if (parent.$$childTail === scope) {
+ parent.$$childTail = scope.$$prevSibling;
+ }
+ if (scope.$$prevSibling) {
+ scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
+ }
+ if (scope.$$nextSibling) {
+ scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
+ }
+ scope.$$nextSibling = scope.$$prevSibling = null;
+ },
+
+ reconnectScope: function reconnectScope(scope) {
+ if (!scope) return;
+
+ if (scope.$root === scope) {
+ return; // we can't disconnect the root node;
+ }
+ if (!scope.$$disconnected) {
+ return;
+ }
+ var parent = scope.$parent;
+ scope.$$disconnected = false;
+ // See Scope.$new for this logic...
+ scope.$$prevSibling = parent.$$childTail;
+ if (parent.$$childHead) {
+ parent.$$childTail.$$nextSibling = scope;
+ parent.$$childTail = scope;
+ } else {
+ parent.$$childHead = parent.$$childTail = scope;
+ }
}
};
diff --git a/js/views/sliderView.js b/js/views/sliderView.js
deleted file mode 100644
index 54d26c475fc..00000000000
--- a/js/views/sliderView.js
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * Adapted from Swipe.js 2.0
- *
- * Brad Birdsall
- * Copyright 2013, MIT License
- *
-*/
-
-(function(ionic) {
-'use strict';
-
-ionic.views.Slider = ionic.views.View.inherit({
- initialize: function (options) {
- var slider = this;
-
- // utilities
- var noop = function() {}; // simple no operation function
- var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution
-
- // check browser capabilities
- var browser = {
- addEventListener: !!window.addEventListener,
- touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
- transitions: (function(temp) {
- var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
- for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
- return false;
- })(document.createElement('swipe'))
- };
-
-
- var container = options.el;
-
- // quit if no root element
- if (!container) return;
- var element = container.children[0];
- var slides, slidePos, width, length;
- options = options || {};
- var index = parseInt(options.startSlide, 10) || 0;
- var speed = options.speed || 300;
- options.continuous = options.continuous !== undefined ? options.continuous : true;
-
- function setup() {
-
- // cache slides
- slides = element.children;
- length = slides.length;
-
- // set continuous to false if only one slide
- if (slides.length < 2) options.continuous = false;
-
- //special case if two slides
- if (browser.transitions && options.continuous && slides.length < 3) {
- element.appendChild(slides[0].cloneNode(true));
- element.appendChild(element.children[1].cloneNode(true));
- slides = element.children;
- }
-
- // create an array to store current positions of each slide
- slidePos = new Array(slides.length);
-
- // determine width of each slide
- width = container.offsetWidth || container.getBoundingClientRect().width;
-
- element.style.width = (slides.length * width) + 'px';
-
- // stack elements
- var pos = slides.length;
- while(pos--) {
-
- var slide = slides[pos];
-
- slide.style.width = width + 'px';
- slide.setAttribute('data-index', pos);
-
- if (browser.transitions) {
- slide.style.left = (pos * -width) + 'px';
- move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
- }
-
- }
-
- // reposition elements before and after index
- if (options.continuous && browser.transitions) {
- move(circle(index-1), -width, 0);
- move(circle(index+1), width, 0);
- }
-
- if (!browser.transitions) element.style.left = (index * -width) + 'px';
-
- container.style.visibility = 'visible';
-
- options.slidesChanged && options.slidesChanged();
- }
-
- function prev() {
-
- if (options.continuous) slide(index-1);
- else if (index) slide(index-1);
-
- }
-
- function next() {
-
- if (options.continuous) slide(index+1);
- else if (index < slides.length - 1) slide(index+1);
-
- }
-
- function circle(index) {
-
- // a simple positive modulo using slides.length
- return (slides.length + (index % slides.length)) % slides.length;
-
- }
-
- function slide(to, slideSpeed) {
-
- // do nothing if already on requested slide
- if (index == to) return;
-
- if (browser.transitions) {
-
- var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward
-
- // get the actual position of the slide
- if (options.continuous) {
- var natural_direction = direction;
- direction = -slidePos[circle(to)] / width;
-
- // if going forward but to < index, use to = slides.length + to
- // if going backward but to > index, use to = -slides.length + to
- if (direction !== natural_direction) to = -direction * slides.length + to;
-
- }
-
- var diff = Math.abs(index-to) - 1;
-
- // move all the slides between index and to in the right direction
- while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
-
- to = circle(to);
-
- move(index, width * direction, slideSpeed || speed);
- move(to, 0, slideSpeed || speed);
-
- if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
-
- } else {
-
- to = circle(to);
- animate(index * -width, to * -width, slideSpeed || speed);
- //no fallback for a circular continuous if the browser does not accept transitions
- }
-
- index = to;
- offloadFn(options.callback && options.callback(index, slides[index]));
- }
-
- function move(index, dist, speed) {
-
- translate(index, dist, speed);
- slidePos[index] = dist;
-
- }
-
- function translate(index, dist, speed) {
-
- var slide = slides[index];
- var style = slide && slide.style;
-
- if (!style) return;
-
- style.webkitTransitionDuration =
- style.MozTransitionDuration =
- style.msTransitionDuration =
- style.OTransitionDuration =
- style.transitionDuration = speed + 'ms';
-
- style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
- style.msTransform =
- style.MozTransform =
- style.OTransform = 'translateX(' + dist + 'px)';
-
- }
-
- function animate(from, to, speed) {
-
- // if not an animation, just reposition
- if (!speed) {
-
- element.style.left = to + 'px';
- return;
-
- }
-
- var start = +new Date();
-
- var timer = setInterval(function() {
-
- var timeElap = +new Date() - start;
-
- if (timeElap > speed) {
-
- element.style.left = to + 'px';
-
- if (delay) begin();
-
- options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
-
- clearInterval(timer);
- return;
-
- }
-
- element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
-
- }, 4);
-
- }
-
- // setup auto slideshow
- var delay = options.auto || 0;
- var interval;
-
- function begin() {
-
- interval = setTimeout(next, delay);
-
- }
-
- function stop() {
-
- delay = options.auto || 0;
- clearTimeout(interval);
-
- }
-
-
- // setup initial vars
- var start = {};
- var delta = {};
- var isScrolling;
-
- // setup event capturing
- var events = {
-
- handleEvent: function(event) {
- if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
- event.touches = [{
- pageX: event.pageX,
- pageY: event.pageY
- }];
- }
-
- switch (event.type) {
- case 'mousedown': this.start(event); break;
- case 'touchstart': this.start(event); break;
- case 'touchmove': this.touchmove(event); break;
- case 'mousemove': this.touchmove(event); break;
- case 'touchend': offloadFn(this.end(event)); break;
- case 'mouseup': offloadFn(this.end(event)); break;
- case 'webkitTransitionEnd':
- case 'msTransitionEnd':
- case 'oTransitionEnd':
- case 'otransitionend':
- case 'transitionend': offloadFn(this.transitionEnd(event)); break;
- case 'resize': offloadFn(setup); break;
- }
-
- if (options.stopPropagation) event.stopPropagation();
-
- },
- start: function(event) {
-
- var touches = event.touches[0];
-
- // measure start values
- start = {
-
- // get initial touch coords
- x: touches.pageX,
- y: touches.pageY,
-
- // store time to determine touch duration
- time: +new Date()
-
- };
-
- // used for testing first move event
- isScrolling = undefined;
-
- // reset delta and end measurements
- delta = {};
-
- // attach touchmove and touchend listeners
- if(browser.touch) {
- element.addEventListener('touchmove', this, false);
- element.addEventListener('touchend', this, false);
- } else {
- element.addEventListener('mousemove', this, false);
- element.addEventListener('mouseup', this, false);
- document.addEventListener('mouseup', this, false);
- }
- },
- touchmove: function(event) {
-
- // ensure swiping with one touch and not pinching
- // ensure sliding is enabled
- if (event.touches.length > 1 ||
- event.scale && event.scale !== 1 ||
- slider.slideIsDisabled) {
- return;
- }
-
- if (options.disableScroll) event.preventDefault();
-
- var touches = event.touches[0];
-
- // measure change in x and y
- delta = {
- x: touches.pageX - start.x,
- y: touches.pageY - start.y
- };
-
- // determine if scrolling test has run - one time test
- if ( typeof isScrolling == 'undefined') {
- isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
- }
-
- // if user is not trying to scroll vertically
- if (!isScrolling) {
-
- // prevent native scrolling
- event.preventDefault();
-
- // stop slideshow
- stop();
-
- // increase resistance if first or last slide
- if (options.continuous) { // we don't add resistance at the end
-
- translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
- translate(index, delta.x + slidePos[index], 0);
- translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);
-
- } else {
-
- delta.x =
- delta.x /
- ( (!index && delta.x > 0 || // if first slide and sliding left
- index == slides.length - 1 && // or if last slide and sliding right
- delta.x < 0 // and if sliding at all
- ) ?
- ( Math.abs(delta.x) / width + 1 ) // determine resistance level
- : 1 ); // no resistance if false
-
- // translate 1:1
- translate(index-1, delta.x + slidePos[index-1], 0);
- translate(index, delta.x + slidePos[index], 0);
- translate(index+1, delta.x + slidePos[index+1], 0);
- }
-
- }
-
- },
- end: function(event) {
-
- // measure duration
- var duration = +new Date() - start.time;
-
- // determine if slide attempt triggers next/prev slide
- var isValidSlide =
- Number(duration) < 250 && // if slide duration is less than 250ms
- Math.abs(delta.x) > 20 || // and if slide amt is greater than 20px
- Math.abs(delta.x) > width/2; // or if slide amt is greater than half the width
-
- // determine if slide attempt is past start and end
- var isPastBounds = (!index && delta.x > 0) || // if first slide and slide amt is greater than 0
- (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0
-
- if (options.continuous) isPastBounds = false;
-
- // determine direction of swipe (true:right, false:left)
- var direction = delta.x < 0;
-
- // if not scrolling vertically
- if (!isScrolling) {
-
- if (isValidSlide && !isPastBounds) {
-
- if (direction) {
-
- if (options.continuous) { // we need to get the next in this direction in place
-
- move(circle(index-1), -width, 0);
- move(circle(index+2), width, 0);
-
- } else {
- move(index-1, -width, 0);
- }
-
- move(index, slidePos[index]-width, speed);
- move(circle(index+1), slidePos[circle(index+1)]-width, speed);
- index = circle(index+1);
-
- } else {
- if (options.continuous) { // we need to get the next in this direction in place
-
- move(circle(index+1), width, 0);
- move(circle(index-2), -width, 0);
-
- } else {
- move(index+1, width, 0);
- }
-
- move(index, slidePos[index]+width, speed);
- move(circle(index-1), slidePos[circle(index-1)]+width, speed);
- index = circle(index-1);
-
- }
-
- options.callback && options.callback(index, slides[index]);
-
- } else {
-
- if (options.continuous) {
-
- move(circle(index-1), -width, speed);
- move(index, 0, speed);
- move(circle(index+1), width, speed);
-
- } else {
-
- move(index-1, -width, speed);
- move(index, 0, speed);
- move(index+1, width, speed);
- }
-
- }
-
- }
-
- // kill touchmove and touchend event listeners until touchstart called again
- if(browser.touch) {
- element.removeEventListener('touchmove', events, false);
- element.removeEventListener('touchend', events, false);
- } else {
- element.removeEventListener('mousemove', events, false);
- element.removeEventListener('mouseup', events, false);
- document.removeEventListener('mouseup', events, false);
- }
-
- },
- transitionEnd: function(event) {
-
- if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
-
- if (delay) begin();
-
- options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
-
- }
-
- }
-
- };
-
- // Public API
- this.update = function() {
- setTimeout(setup);
- };
- this.setup = function() {
- setup();
- };
-
- this.enableSlide = function(shouldEnable) {
- if (arguments.length) {
- this.slideIsDisabled = !shouldEnable;
- }
- return !this.slideIsDisabled;
- },
- this.slide = function(to, speed) {
- // cancel slideshow
- stop();
-
- slide(to, speed);
- };
-
- this.prev = this.previous = function() {
- // cancel slideshow
- stop();
-
- prev();
- };
-
- this.next = function() {
- // cancel slideshow
- stop();
-
- next();
- };
-
- this.stop = function() {
- // cancel slideshow
- stop();
- };
-
- this.start = function() {
- begin();
- };
-
- this.currentIndex = function() {
- // return current index position
- return index;
- };
-
- this.slidesCount = function() {
- // return total number of slides
- return length;
- };
-
- this.kill = function() {
- // cancel slideshow
- stop();
-
- // reset element
- element.style.width = '';
- element.style.left = '';
-
- // reset slides
- var pos = slides.length;
- while(pos--) {
-
- var slide = slides[pos];
- slide.style.width = '';
- slide.style.left = '';
-
- if (browser.transitions) translate(pos, 0, 0);
-
- }
-
- // removed event listeners
- if (browser.addEventListener) {
-
- // remove current event listeners
- element.removeEventListener('touchstart', events, false);
- element.removeEventListener('webkitTransitionEnd', events, false);
- element.removeEventListener('msTransitionEnd', events, false);
- element.removeEventListener('oTransitionEnd', events, false);
- element.removeEventListener('otransitionend', events, false);
- element.removeEventListener('transitionend', events, false);
- window.removeEventListener('resize', events, false);
-
- }
- else {
-
- window.onresize = null;
-
- }
- };
-
- this.load = function() {
- // trigger setup
- setup();
-
- // start auto slideshow if applicable
- if (delay) begin();
-
-
- // add event listeners
- if (browser.addEventListener) {
-
- // set touchstart event on element
- if (browser.touch) {
- element.addEventListener('touchstart', events, false);
- } else {
- element.addEventListener('mousedown', events, false);
- }
-
- if (browser.transitions) {
- element.addEventListener('webkitTransitionEnd', events, false);
- element.addEventListener('msTransitionEnd', events, false);
- element.addEventListener('oTransitionEnd', events, false);
- element.addEventListener('otransitionend', events, false);
- element.addEventListener('transitionend', events, false);
- }
-
- // set resize event on window
- window.addEventListener('resize', events, false);
-
- } else {
-
- window.onresize = function () { setup(); }; // to play nice with old IE
-
- }
- };
-
- }
-});
-
-})(ionic);
diff --git a/package.json b/package.json
index 3b152894ef0..4e04d3912e6 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,6 @@
"url": "git://github.com/driftyco/ionic.git"
},
"devDependencies": {
- "karma": "^0.12.22",
- "karma-chrome-launcher": "^0.1.4",
"karma-jasmine": "~0.1.5",
"karma-phantomjs-launcher": "~0.1.2",
"karma-sauce-launcher": "~0.2.0",
@@ -54,7 +52,9 @@
"js-yaml": "^3.0.2",
"protractor": "^0.23.1",
"q": "^1.0.1",
- "github": "^0.2.1"
+ "github": "^0.2.1",
+ "karma-chrome-launcher": "^0.1.4",
+ "karma": "^0.12.23"
},
"licenses": [
{
diff --git a/scss/_mixins.scss b/scss/_mixins.scss
index 1b297cbb5d8..9bc17da83fb 100644
--- a/scss/_mixins.scss
+++ b/scss/_mixins.scss
@@ -616,6 +616,12 @@
justify-content: $value;
}
+@mixin flex-order($n) {
+ -webkit-order: $n;
+ -ms-flex-order: $n;
+ order: $n;
+}
+
@mixin responsive-grid-break($selector, $max-width) {
@media (max-width: $max-width) {
#{$selector} {
diff --git a/scss/_slide-box.scss b/scss/_slide-box.scss
index f6dfa113a09..f464ef4e385 100644
--- a/scss/_slide-box.scss
+++ b/scss/_slide-box.scss
@@ -5,24 +5,48 @@
*/
.slider {
- position: relative;
- visibility: hidden;
- // Make sure items don't scroll over ever
+ display: block;
overflow: hidden;
}
.slider-slides {
- position: relative;
height: 100%;
+ width: 100%;
+ position: relative;
}
.slider-slide {
- position: relative;
display: block;
- float: left;
- width: 100%;
- height: 100%;
- vertical-align: top;
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+
+ &[slide-state="previous"] {
+ @include translate3d(-100%, 0, 0);
+ }
+ &[slide-state="selected"] {
+ @include translate3d(0, 0, 0);
+ }
+ &[slide-state="next"] {
+ @include translate3d(100%, 0, 0);
+ }
+ &[slide-state="detached"] {
+ display: none;
+ }
+
+ // Only transition elements going into or out of selection.
+ &[slide-previous-state="selected"],
+ &[slide-state="selected"] {
+ transition: transform ease-out;
+ -webkit-transition: -webkit-transform ease-out;
+ @include transition-duration(inherit);
+ z-index: 1;
+ }
+ &.no-animate {
+ @include transition-duration(0ms !important);
+ }
}
.slider-slide-image {
@@ -38,12 +62,18 @@
width: 100%;
height: 15px;
text-align: center;
+ z-index: 2;
+
+ @include display-flex();
+ @include align-items(center);
+ @include justify-content(center);
.slider-pager-page {
- display: inline-block;
margin: 0px 3px;
width: 15px;
- color: #000;
+ height: 15px;
+ border-radius: 15px;
+ background-color: black;
text-decoration: none;
opacity: 0.3;
diff --git a/test/html/slideBox.html b/test/html/slideBox.html
index 5b3d46e35b1..861d01efe8b 100644
--- a/test/html/slideBox.html
+++ b/test/html/slideBox.html
@@ -44,10 +44,8 @@
-
-
-
-
+
+
Thank you for choosing the Awesome App!
@@ -83,99 +81,32 @@
Just three steps:
+
+ item {{i}}
+
+
+
-
-
-
-
-
-
-
+