gaf-mobile
Version:
GAF mobile Web site
596 lines (531 loc) • 30.1 kB
JavaScript
(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);
});
};
}
};
}
]);
})();