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!