summaryrefslogtreecommitdiff
path: root/www/lib/angular-carousel/src/directives/rn-carousel.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/lib/angular-carousel/src/directives/rn-carousel.js')
-rwxr-xr-xwww/lib/angular-carousel/src/directives/rn-carousel.js595
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);
+ });
+ };
+ }
+ };
+ }
+ ]);
+})();