diff options
| author | Pliable Pixels <pliablepixels@gmail.com> | 2017-09-21 12:49:18 -0400 |
|---|---|---|
| committer | Pliable Pixels <pliablepixels@gmail.com> | 2017-09-21 12:49:18 -0400 |
| commit | b28028ac4082842143b0f528d6bc539da6ccb419 (patch) | |
| tree | 1e26ea969a781ed8e323fca4e3c76345113fc694 /www/lib/angular-carousel/src/directives/rn-carousel.js | |
| parent | 676270d21beed31d767a06c89522198c77d5d865 (diff) | |
mega changes, including updates and X
Diffstat (limited to 'www/lib/angular-carousel/src/directives/rn-carousel.js')
| -rwxr-xr-x | www/lib/angular-carousel/src/directives/rn-carousel.js | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/www/lib/angular-carousel/src/directives/rn-carousel.js b/www/lib/angular-carousel/src/directives/rn-carousel.js new file mode 100755 index 00000000..c4e1e4ba --- /dev/null +++ b/www/lib/angular-carousel/src/directives/rn-carousel.js @@ -0,0 +1,595 @@ +(function() { + "use strict"; + + angular.module('angular-carousel') + + .service('DeviceCapabilities', function() { + + // TODO: merge in a single function + + // detect supported CSS property + function detectTransformProperty() { + var transformProperty = 'transform', + safariPropertyHack = 'webkitTransform'; + if (typeof document.body.style[transformProperty] !== 'undefined') { + + ['webkit', 'moz', 'o', 'ms'].every(function (prefix) { + var e = '-' + prefix + '-transform'; + if (typeof document.body.style[e] !== 'undefined') { + transformProperty = e; + return false; + } + return true; + }); + } else if (typeof document.body.style[safariPropertyHack] !== 'undefined') { + transformProperty = '-webkit-transform'; + } else { + transformProperty = undefined; + } + return transformProperty; + } + + //Detect support of translate3d + function detect3dSupport() { + var el = document.createElement('p'), + has3d, + transforms = { + 'webkitTransform': '-webkit-transform', + 'msTransform': '-ms-transform', + 'transform': 'transform' + }; + // Add it to the body to get the computed style + document.body.insertBefore(el, null); + for (var t in transforms) { + if (el.style[t] !== undefined) { + el.style[t] = 'translate3d(1px,1px,1px)'; + has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]); + } + } + document.body.removeChild(el); + return (has3d !== undefined && has3d.length > 0 && has3d !== "none"); + } + + return { + has3d: detect3dSupport(), + transformProperty: detectTransformProperty() + }; + + }) + + .service('computeCarouselSlideStyle', function(DeviceCapabilities) { + // compute transition transform properties for a given slide and global offset + return function(slideIndex, offset, transitionType) { + var style = { + display: 'inline-block' + }, + opacity, + absoluteLeft = (slideIndex * 100) + offset, + slideTransformValue = DeviceCapabilities.has3d ? 'translate3d(' + absoluteLeft + '%, 0, 0)' : 'translate3d(' + absoluteLeft + '%, 0)', + distance = ((100 - Math.abs(absoluteLeft)) / 100); + + if (!DeviceCapabilities.transformProperty) { + // fallback to default slide if transformProperty is not available + style['margin-left'] = absoluteLeft + '%'; + } else { + if (transitionType == 'fadeAndSlide') { + style[DeviceCapabilities.transformProperty] = slideTransformValue; + opacity = 0; + if (Math.abs(absoluteLeft) < 100) { + opacity = 0.3 + distance * 0.7; + } + style.opacity = opacity; + } else if (transitionType == 'hexagon') { + var transformFrom = 100, + degrees = 0, + maxDegrees = 60 * (distance - 1); + + transformFrom = offset < (slideIndex * -100) ? 100 : 0; + degrees = offset < (slideIndex * -100) ? maxDegrees : -maxDegrees; + style[DeviceCapabilities.transformProperty] = slideTransformValue + ' ' + 'rotateY(' + degrees + 'deg)'; + style[DeviceCapabilities.transformProperty + '-origin'] = transformFrom + '% 50%'; + } else if (transitionType == 'zoom') { + style[DeviceCapabilities.transformProperty] = slideTransformValue; + var scale = 1; + if (Math.abs(absoluteLeft) < 100) { + scale = 1 + ((1 - distance) * 2); + } + style[DeviceCapabilities.transformProperty] += ' scale(' + scale + ')'; + style[DeviceCapabilities.transformProperty + '-origin'] = '50% 50%'; + opacity = 0; + if (Math.abs(absoluteLeft) < 100) { + opacity = 0.3 + distance * 0.7; + } + style.opacity = opacity; + } else { + style[DeviceCapabilities.transformProperty] = slideTransformValue; + } + } + return style; + }; + }) + + .service('createStyleString', function() { + return function(object) { + var styles = []; + angular.forEach(object, function(value, key) { + styles.push(key + ':' + value); + }); + return styles.join(';'); + }; + }) + + .directive('rnCarousel', ['$swipe', '$window', '$document', '$parse', '$compile', '$timeout', '$interval', 'computeCarouselSlideStyle', 'createStyleString', 'Tweenable', + function($swipe, $window, $document, $parse, $compile, $timeout, $interval, computeCarouselSlideStyle, createStyleString, Tweenable) { + // internal ids to allow multiple instances + var carouselId = 0, + // in absolute pixels, at which distance the slide stick to the edge on release + rubberTreshold = 3; + + var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame; + + function getItemIndex(collection, target, defaultIndex) { + var result = defaultIndex; + collection.every(function(item, index) { + if (angular.equals(item, target)) { + result = index; + return false; + } + return true; + }); + return result; + } + + return { + restrict: 'A', + scope: true, + compile: function(tElement, tAttributes) { + // use the compile phase to customize the DOM + var firstChild = tElement[0].querySelector('li'), + firstChildAttributes = (firstChild) ? firstChild.attributes : [], + isRepeatBased = false, + isBuffered = false, + repeatItem, + repeatCollection; + + // try to find an ngRepeat expression + // at this point, the attributes are not yet normalized so we need to try various syntax + ['ng-repeat', 'data-ng-repeat', 'ng:repeat', 'x-ng-repeat'].every(function(attr) { + var repeatAttribute = firstChildAttributes[attr]; + if (angular.isDefined(repeatAttribute)) { + // ngRepeat regexp extracted from angular 1.2.7 src + var exprMatch = repeatAttribute.value.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), + trackProperty = exprMatch[3]; + + repeatItem = exprMatch[1]; + repeatCollection = exprMatch[2]; + + if (repeatItem) { + if (angular.isDefined(tAttributes['rnCarouselBuffered'])) { + // update the current ngRepeat expression and add a slice operator if buffered + isBuffered = true; + repeatAttribute.value = repeatItem + ' in ' + repeatCollection + '|carouselSlice:carouselBufferIndex:carouselBufferSize'; + if (trackProperty) { + repeatAttribute.value += ' track by ' + trackProperty; + } + } + isRepeatBased = true; + return false; + } + } + return true; + }); + + return function(scope, iElement, iAttributes, containerCtrl) { + + carouselId++; + + var defaultOptions = { + transitionType: iAttributes.rnCarouselTransition || 'slide', + transitionEasing: iAttributes.rnCarouselEasing || 'easeTo', + transitionDuration: parseInt(iAttributes.rnCarouselDuration, 10) || 300, + isSequential: true, + autoSlideDuration: 3, + bufferSize: 5, + /* in container % how much we need to drag to trigger the slide change */ + moveTreshold: 0.1, + defaultIndex: 0 + }; + + // TODO + var options = angular.extend({}, defaultOptions); + + var pressed, + startX, + isIndexBound = false, + offset = 0, + destination, + swipeMoved = false, + //animOnIndexChange = true, + currentSlides = [], + elWidth = null, + elX = null, + animateTransitions = true, + intialState = true, + animating = false, + mouseUpBound = false, + locked = false; + + //rn-swipe-disabled =true will only disable swipe events + if(iAttributes.rnSwipeDisabled !== "true") { + $swipe.bind(iElement, { + start: swipeStart, + move: swipeMove, + end: swipeEnd, + cancel: function(event) { + swipeEnd({}, event); + } + }); + } + + function getSlidesDOM() { + return iElement[0].querySelectorAll('ul[rn-carousel] > li'); + } + + function documentMouseUpEvent(event) { + // in case we click outside the carousel, trigger a fake swipeEnd + swipeMoved = true; + swipeEnd({ + x: event.clientX, + y: event.clientY + }, event); + } + + function updateSlidesPosition(offset) { + // manually apply transformation to carousel childrens + // todo : optim : apply only to visible items + var x = scope.carouselBufferIndex * 100 + offset; + angular.forEach(getSlidesDOM(), function(child, index) { + child.style.cssText = createStyleString(computeCarouselSlideStyle(index, x, options.transitionType)); + }); + } + + scope.nextSlide = function(slideOptions) { + var index = scope.carouselIndex + 1; + if (index > currentSlides.length - 1) { + index = 0; + } + if (!locked) { + goToSlide(index, slideOptions); + } + }; + + scope.prevSlide = function(slideOptions) { + var index = scope.carouselIndex - 1; + if (index < 0) { + index = currentSlides.length - 1; + } + goToSlide(index, slideOptions); + }; + + function goToSlide(index, slideOptions) { + //console.log('goToSlide', arguments); + // move a to the given slide index + if (index === undefined) { + index = scope.carouselIndex; + } + + slideOptions = slideOptions || {}; + if (slideOptions.animate === false || options.transitionType === 'none') { + locked = false; + offset = index * -100; + scope.carouselIndex = index; + updateBufferIndex(); + return; + } + + locked = true; + var tweenable = new Tweenable(); + tweenable.tween({ + from: { + 'x': offset + }, + to: { + 'x': index * -100 + }, + duration: options.transitionDuration, + easing: options.transitionEasing, + step: function(state) { + updateSlidesPosition(state.x); + }, + finish: function() { + scope.$apply(function() { + scope.carouselIndex = index; + offset = index * -100; + updateBufferIndex(); + $timeout(function () { + locked = false; + }, 0, false); + }); + } + }); + } + + function getContainerWidth() { + var rect = iElement[0].getBoundingClientRect(); + return rect.width ? rect.width : rect.right - rect.left; + } + + function updateContainerWidth() { + elWidth = getContainerWidth(); + } + + function bindMouseUpEvent() { + if (!mouseUpBound) { + mouseUpBound = true; + $document.bind('mouseup', documentMouseUpEvent); + } + } + + function unbindMouseUpEvent() { + if (mouseUpBound) { + mouseUpBound = false; + $document.unbind('mouseup', documentMouseUpEvent); + } + } + + function swipeStart(coords, event) { + // console.log('swipeStart', coords, event); + if (locked || currentSlides.length <= 1) { + return; + } + updateContainerWidth(); + elX = iElement[0].querySelector('li').getBoundingClientRect().left; + pressed = true; + startX = coords.x; + return false; + } + + function swipeMove(coords, event) { + //console.log('swipeMove', coords, event); + var x, delta; + bindMouseUpEvent(); + if (pressed) { + x = coords.x; + delta = startX - x; + if (delta > 2 || delta < -2) { + swipeMoved = true; + var moveOffset = offset + (-delta * 100 / elWidth); + updateSlidesPosition(moveOffset); + } + } + return false; + } + + var init = true; + scope.carouselIndex = 0; + + if (!isRepeatBased) { + // fake array when no ng-repeat + currentSlides = []; + angular.forEach(getSlidesDOM(), function(node, index) { + currentSlides.push({id: index}); + }); + } + + if (iAttributes.rnCarouselControls!==undefined) { + // dont use a directive for this + var canloop = ((isRepeatBased ? scope[repeatCollection.replace('::', '')].length : currentSlides.length) > 1) ? angular.isDefined(tAttributes['rnCarouselControlsAllowLoop']) : false; + var nextSlideIndexCompareValue = isRepeatBased ? repeatCollection.replace('::', '') + '.length - 1' : currentSlides.length - 1; + var tpl = '<div class="rn-carousel-controls">\n' + + ' <span class="rn-carousel-control rn-carousel-control-prev" ng-click="prevSlide()" ng-if="carouselIndex > 0 || ' + canloop + '"></span>\n' + + ' <span class="rn-carousel-control rn-carousel-control-next" ng-click="nextSlide()" ng-if="carouselIndex < ' + nextSlideIndexCompareValue + ' || ' + canloop + '"></span>\n' + + '</div>'; + iElement.parent().append($compile(angular.element(tpl))(scope)); + } + + if (iAttributes.rnCarouselAutoSlide!==undefined) { + var duration = parseInt(iAttributes.rnCarouselAutoSlide, 10) || options.autoSlideDuration; + scope.autoSlide = function() { + if (scope.autoSlider) { + $interval.cancel(scope.autoSlider); + scope.autoSlider = null; + } + scope.autoSlider = $interval(function() { + if (!locked && !pressed) { + scope.nextSlide(); + } + }, duration * 1000); + }; + } + + if (iAttributes.rnCarouselDefaultIndex) { + var defaultIndexModel = $parse(iAttributes.rnCarouselDefaultIndex); + options.defaultIndex = defaultIndexModel(scope.$parent) || 0; + } + + if (iAttributes.rnCarouselIndex) { + var updateParentIndex = function(value) { + indexModel.assign(scope.$parent, value); + }; + var indexModel = $parse(iAttributes.rnCarouselIndex); + if (angular.isFunction(indexModel.assign)) { + /* check if this property is assignable then watch it */ + scope.$watch('carouselIndex', function(newValue) { + updateParentIndex(newValue); + }); + scope.$parent.$watch(indexModel, function(newValue, oldValue) { + + if (newValue !== undefined && newValue !== null) { + if (currentSlides && currentSlides.length > 0 && newValue >= currentSlides.length) { + newValue = currentSlides.length - 1; + updateParentIndex(newValue); + } else if (currentSlides && newValue < 0) { + newValue = 0; + updateParentIndex(newValue); + } + if (!locked) { + goToSlide(newValue, { + animate: !init + }); + } + init = false; + } + }); + isIndexBound = true; + + if (options.defaultIndex) { + goToSlide(options.defaultIndex, { + animate: !init + }); + } + } else if (!isNaN(iAttributes.rnCarouselIndex)) { + /* if user just set an initial number, set it */ + goToSlide(parseInt(iAttributes.rnCarouselIndex, 10), { + animate: false + }); + } + } else { + goToSlide(options.defaultIndex, { + animate: !init + }); + init = false; + } + + if (iAttributes.rnCarouselLocked) { + scope.$watch(iAttributes.rnCarouselLocked, function(newValue, oldValue) { + // only bind swipe when it's not switched off + if(newValue === true) { + locked = true; + } else { + locked = false; + } + }); + } + + if (isRepeatBased) { + // use rn-carousel-deep-watch to fight the Angular $watchCollection weakness : https://github.com/angular/angular.js/issues/2621 + // optional because it have some performance impacts (deep watch) + var deepWatch = (iAttributes.rnCarouselDeepWatch!==undefined); + + scope[deepWatch?'$watch':'$watchCollection'](repeatCollection, function(newValue, oldValue) { + //console.log('repeatCollection', currentSlides); + currentSlides = newValue; + // if deepWatch ON ,manually compare objects to guess the new position + if (deepWatch && angular.isArray(newValue)) { + var activeElement = oldValue[scope.carouselIndex]; + var newIndex = getItemIndex(newValue, activeElement, scope.carouselIndex); + goToSlide(newIndex, {animate: false}); + } else { + goToSlide(scope.carouselIndex, {animate: false}); + } + }, true); + } + + function swipeEnd(coords, event, forceAnimation) { + // console.log('swipeEnd', 'scope.carouselIndex', scope.carouselIndex); + // Prevent clicks on buttons inside slider to trigger "swipeEnd" event on touchend/mouseup + // console.log(iAttributes.rnCarouselOnInfiniteScroll); + if (event && !swipeMoved) { + return; + } + unbindMouseUpEvent(); + pressed = false; + swipeMoved = false; + destination = startX - coords.x; + if (destination===0) { + return; + } + if (locked) { + return; + } + offset += (-destination * 100 / elWidth); + if (options.isSequential) { + var minMove = options.moveTreshold * elWidth, + absMove = -destination, + slidesMove = -Math[absMove >= 0 ? 'ceil' : 'floor'](absMove / elWidth), + shouldMove = Math.abs(absMove) > minMove; + + if (currentSlides && (slidesMove + scope.carouselIndex) >= currentSlides.length) { + slidesMove = currentSlides.length - 1 - scope.carouselIndex; + } + if ((slidesMove + scope.carouselIndex) < 0) { + slidesMove = -scope.carouselIndex; + } + var moveOffset = shouldMove ? slidesMove : 0; + + destination = (scope.carouselIndex + moveOffset); + + goToSlide(destination); + if(iAttributes.rnCarouselOnInfiniteScrollRight!==undefined && slidesMove === 0 && scope.carouselIndex !== 0) { + $parse(iAttributes.rnCarouselOnInfiniteScrollRight)(scope) + goToSlide(0); + } + if(iAttributes.rnCarouselOnInfiniteScrollLeft!==undefined && slidesMove === 0 && scope.carouselIndex === 0 && moveOffset === 0) { + $parse(iAttributes.rnCarouselOnInfiniteScrollLeft)(scope) + goToSlide(currentSlides.length); + } + + } else { + scope.$apply(function() { + scope.carouselIndex = parseInt(-offset / 100, 10); + updateBufferIndex(); + }); + + } + + } + + scope.$on('$destroy', function() { + unbindMouseUpEvent(); + }); + + scope.carouselBufferIndex = 0; + scope.carouselBufferSize = options.bufferSize; + + function updateBufferIndex() { + // update and cap te buffer index + var bufferIndex = 0; + var bufferEdgeSize = (scope.carouselBufferSize - 1) / 2; + if (isBuffered) { + if (scope.carouselIndex <= bufferEdgeSize) { + // first buffer part + bufferIndex = 0; + } else if (currentSlides && currentSlides.length < scope.carouselBufferSize) { + // smaller than buffer + bufferIndex = 0; + } else if (currentSlides && scope.carouselIndex > currentSlides.length - scope.carouselBufferSize) { + // last buffer part + bufferIndex = currentSlides.length - scope.carouselBufferSize; + } else { + // compute buffer start + bufferIndex = scope.carouselIndex - bufferEdgeSize; + } + + scope.carouselBufferIndex = bufferIndex; + $timeout(function() { + updateSlidesPosition(offset); + }, 0, false); + } else { + $timeout(function() { + updateSlidesPosition(offset); + }, 0, false); + } + } + + function onOrientationChange() { + updateContainerWidth(); + goToSlide(); + } + + // handle orientation change + var winEl = angular.element($window); + winEl.bind('orientationchange', onOrientationChange); + winEl.bind('resize', onOrientationChange); + + scope.$on('$destroy', function() { + unbindMouseUpEvent(); + winEl.unbind('orientationchange', onOrientationChange); + winEl.unbind('resize', onOrientationChange); + }); + }; + } + }; + } + ]); +})(); |
