angular-ui-carousel
Version:
A simple, lightweight carousel for angularjs
731 lines (626 loc) • 22.9 kB
JavaScript
var global = Function("return this;")();
/*!
* Ender: open module JavaScript framework (client-lib)
* copyright Dustin Diaz & Jacob Thornton 2011 (@ded @fat)
* http://ender.no.de
* License MIT
*/
!function (context) {
// a global object for node.js module compatiblity
// ============================================
context['global'] = context
// Implements simple module system
// losely based on CommonJS Modules spec v1.1.1
// ============================================
var modules = {}
, old = context.$
function require (identifier) {
// modules can be required from ender's build system, or found on the window
var module = modules[identifier] || window[identifier]
if (!module) throw new Error("Requested module '" + identifier + "' has not been defined.")
return module
}
function provide (name, what) {
return (modules[name] = what)
}
context['provide'] = provide
context['require'] = require
function aug(o, o2) {
for (var k in o2) k != 'noConflict' && k != '_VERSION' && (o[k] = o2[k])
return o
}
function boosh(s, r, els) {
// string || node || nodelist || window
if (typeof s == 'string' || s.nodeName || (s.length && 'item' in s) || s == window) {
els = ender._select(s, r)
els.selector = s
} else els = isFinite(s.length) ? s : [s]
return aug(els, boosh)
}
function ender(s, r) {
return boosh(s, r)
}
aug(ender, {
_VERSION: '0.3.6'
, fn: boosh // for easy compat to jQuery plugins
, ender: function (o, chain) {
aug(chain ? boosh : ender, o)
}
, _select: function (s, r) {
return (r || document).querySelectorAll(s)
}
})
aug(boosh, {
forEach: function (fn, scope, i) {
// opt out of native forEach so we can intentionally call our own scope
// defaulting to the current item and be able to return self
for (i = 0, l = this.length; i < l; ++i) i in this && fn.call(scope || this[i], this[i], i, this)
// return self for chaining
return this
},
$: ender // handy reference to self
})
ender.noConflict = function () {
context.$ = old
return this
}
if (typeof module !== 'undefined' && module.exports) module.exports = ender
// use subscript notation as extern for Closure compilation
context['ender'] = context['$'] = context['ender'] || ender
}(this);
// pakmanager:angular
(function (context) {
var module = { exports: {} }, exports = module.exports
, $ = require("ender")
;
require('angular');
module.exports = angular;
provide("angular", module.exports);
}(global));
// pakmanager:angular-ui-carousel
(function (context) {
var module = { exports: {} }, exports = module.exports
, $ = require("ender")
;
'use strict';
(function (angular) {
// Create all modules and define dependencies to make sure they exist
// and are loaded in the correct order to satisfy dependency injection
// before all nested files are concatenated by Gulp
// Config
angular.module('ui.carousel.config', []).value('ui.carousel.config', {
debug: true
});
// Modules
angular.module('ui.carousel.providers', []);
angular.module('ui.carousel.controllers', []);
angular.module('ui.carousel.directives', []);
angular.module('ui.carousel', ['ui.carousel.config', 'ui.carousel.directives', 'ui.carousel.controllers', 'ui.carousel.providers']);
})(angular);
'use strict';
/**
* angular-ui-carousel
* for example:
* length = 8, show = 4, scroll = 3, current = 0
* ---------
* | |
* |4|5|6|7|0|1|2|3|4|5|6|7|1|2|3|4
* | |
* ---------
* rectangle is visible for users
*/
angular.module('ui.carousel.controllers').controller('CarouselController', ['$scope', '$element', '$timeout', '$q', 'Carousel', function ($scope, $element, $timeout, $q, Carousel) {
var _this = this;
/**
* initial carousel
*/
this.init = function () {
_this.initOptions();
_this.initRanges();
_this.setProps();
_this.setupInfinite();
if (_this.onInit) {
_this.onInit();
}
};
/**
* init option
* directive conifig
*/
this.initOptions = function () {
_this.options = angular.extend({}, Carousel.getOptions());
// TODO customize attribute from directive
if (_this.initialSlide !== undefined) {
_this.options.initialSlide = _this.initialSlide;
}
if (_this.fade !== undefined) {
_this.options.fade = _this.fade;
}
if (_this.autoplay !== undefined) {
_this.options.autoplay = _this.autoplay;
}
if (_this.autoplaySpeed !== undefined) {
_this.options.autoplaySpeed = _this.autoplaySpeed;
}
if (_this.cssEase !== undefined) {
_this.options.cssEase = _this.cssEase;
}
if (_this.speed !== undefined) {
_this.options.speed = _this.speed;
}
if (_this.infinite !== undefined) {
_this.options.infinite = _this.infinite;
}
if (_this.arrows !== undefined) {
_this.options.arrows = _this.arrows;
}
if (_this.dots !== undefined) {
_this.options.dots = _this.dots;
}
// TODO write more options for fade mode
// In fade mode we have to setting slides-to-show and slides-to-scroll
// to 1 slide
if (_this.options.fade) {
_this.options.slidesToShow = 1;
_this.options.slidesToScroll = 1;
} else {
if (_this.show) {
_this.options.slidesToShow = _this.show;
}
if (_this.scroll) {
_this.options.slidesToScroll = _this.scroll;
}
}
};
/**
* init variables, slides, ..
*/
this.initRanges = function () {
if (!_this.slides) {
_this.slides = [];
}
_this.isCarouselReady = false;
_this.isTrackMoving = false;
_this.track = $element.find('.track');
_this.width = 1; // Fake width
_this.currentSlide = _this.options.initialSlide;
_this.trackStyle = {};
_this.slideStyle = {};
_this.isVisibleDots = false;
_this.isVisiblePrev = false;
_this.isVisibleNext = false;
_this.animType = null;
_this.transformType = null;
_this.transitionType = null;
_this.slidesInTrack = angular.copy(_this.slides);
};
/**
* Init UI and carousel track
*/
this.initUI = function () {
$timeout(function () {
_this.width = $element[0].clientWidth;
_this.updateItemStyle();
_this.initTrack();
});
};
/**
* update common style for each carousel item
*/
this.updateItemStyle = function () {
_this.itemWidth = _this.width / _this.options.slidesToShow;
_this.slideStyle = {
'width': _this.itemWidth + 'px'
};
};
/**
* init carousel track
* also make Carousel is Ready
*/
this.initTrack = function () {
var itemWidth = _this.width / _this.options.slidesToShow;
var trackWidth = itemWidth * _this.slidesInTrack.length;
_this.trackStyle.width = trackWidth + 'px';
_this.slideHandler(_this.currentSlide).finally(function () {
_this.isCarouselReady = true;
if (!_this.options.fade) {
_this.trackStyle[_this.transitionType] = _this.transformType + ' ' + _this.options.speed + 'ms ' + _this.options.cssEase;
}
});
};
/**
* @see https://github.com/kenwheeler/slick/blob/master/slick/slick.js#L680
*
* Sync slide to place it should be
* for example:
* - 9 total, 3 show, 3 scroll, current 1
* => next index = 3 (previous index counted = 0)
*
* and scroll to next page:
* - 6 total, 1 show, 1 scroll, current 0 => next index = 1
* - 9 total, 3 show, 3 scroll, current 1 => next index = 3
* - 9 total, 3 show, 3 scroll, current 3 => next index = 6
* - 9 total, 3 show, 3 scroll, current 8 => next index = 3
* - 8 total, 4 show, 3 scroll, current 1 => next index = 4
*/
this.next = function () {
var scrollOffset = _this.slides.length % _this.options.slidesToScroll !== 0;
var indexOffset = scrollOffset ? 0 : (_this.slides.length - _this.currentSlide) % _this.options.slidesToScroll;
var slideOffset = indexOffset === 0 ? _this.options.slidesToScroll : indexOffset;
_this.slideHandler(_this.currentSlide + slideOffset);
};
/**
* move to previous slide
* same calculate with next
* @see next function
*/
this.prev = function () {
var scrollOffset = _this.slides.length % _this.options.slidesToScroll !== 0;
var indexOffset = scrollOffset ? 0 : (_this.slides.length - _this.currentSlide) % _this.options.slidesToScroll;
var slideOffset = indexOffset === 0 ? _this.options.slidesToScroll : _this.options.slidesToShow - indexOffset;
_this.slideHandler(_this.currentSlide - slideOffset);
};
/**
* move to page
* @params int page
* Page counter from 0 (start = 0)
*/
this.movePage = function (page) {
var target = _this.options.slidesToScroll * page;
_this.slideHandler(target);
};
/**
* hanlder carousel
* @description move carousel to correct page
*
* @params int index
*/
this.slideHandler = function (index) {
// TODO prevent when slides not exists
if (!_this.slides) {
return $q.reject('Carousel not fully setup');
}
// TODO Prevent when track is moving
if (_this.isTrackMoving) {
return $q.reject('Track is moving');
}
var len = _this.slides.length;
var show = _this.options.slidesToShow;
if (len <= show) {
return $q.reject('Leng of slides smaller than slides to show');
}
// We need target to destination
// and a anim slide to translate track
//
// anim = animSlide (which we use to move)
// target = targetSlide
var anim = index;
var target = null;
if (anim < 0) {
if (len % _this.options.slidesToScroll !== 0) {
target = len - len % _this.options.slidesToScroll;
} else {
target = len + anim;
}
} else if (anim >= len) {
if (len % _this.options.slidesToScroll !== 0) {
target = 0;
} else {
target = anim - len;
}
} else {
target = anim;
}
if (_this.onBeforeChange) {
_this.onBeforeChange(_this.currentSlide, target);
}
// Fade handler
if (_this.options.fade) {
_this.currentSlide = target;
// XXX
// afterChange method
// fire after faded
// Should be revised
$timeout(function () {
if (_this.onAfterChange) {
_this.onAfterChange(_this.currentSlide);
}
}, _this.options.speed);
return $q.resolve('Handler fade');
}
// No-fade handler
var left = -1 * target * _this.itemWidth;
if (_this.options.infinite) {
left = -1 * (anim + show) * _this.itemWidth;
}
_this.isTrackMoving = true;
return _this.moveTrack(left).then(function () {
_this.isTrackMoving = false;
_this.currentSlide = target;
_this.autoplayTrack();
if (target !== anim) {
_this.correctTrack();
}
// XXX
// afterChange method
// fire after 200ms wakeup and correct track
// Should be revised
$timeout(function () {
if (_this.onAfterChange) {
_this.onAfterChange(_this.currentSlide);
}
}, 200);
});
};
/**
* moveTrack
* move track to left position using css3 translate
* for example left: -1000px
*/
this.moveTrack = function (left) {
var deferred = $q.defer();
if (_this.options.vertical === false) {
_this.trackStyle[_this.animType] = 'translate3d(' + left + 'px, 0px, 0px)';
} else {
_this.trackStyle[_this.animType] = 'translate3d(0px, ' + left + 'px, 0px)';
}
$timeout(function () {
deferred.resolve('Track moved');
}, _this.options.speed);
return deferred.promise;
};
/**
* correctTrack
* @description correct track after move to animSlide we have to move track
* to exactly its position
*/
this.correctTrack = function () {
if (_this.options.infinite) {
(function () {
var left = -1 * (_this.currentSlide + _this.options.slidesToShow) * _this.itemWidth;
// Move without anim
_this.trackStyle[_this.transitionType] = _this.transformType + ' ' + 0 + 'ms ' + _this.options.cssEase;
_this.isTrackMoving = true;
$timeout(function () {
_this.trackStyle[_this.animType] = 'translate3d(' + left + 'px, 0, 0px)';
// Revert animation
$timeout(function () {
_this.trackStyle[_this.transitionType] = _this.transformType + ' ' + _this.options.speed + 'ms ' + _this.options.cssEase;
_this.isTrackMoving = false;
}, 200);
});
})();
}
};
/**
* autoplay track
* @description autoplay = true
*/
this.autoplayTrack = function () {
if (_this.options.autoplay) {
if (_this.timeout) {
$timeout.cancel(_this.timeout);
}
_this.timeout = $timeout(function () {
_this.next();
$timeout.cancel(_this.timeout);
_this.timeout = null;
}, _this.options.autoplaySpeed);
}
};
this.getSlideStyle = function (index) {
var style = _this.slideStyle;
if (_this.options.fade) {
var left = -1 * index * _this.itemWidth;
var uniqueStyle = {
position: 'relative',
top: '0px',
left: left + 'px',
'z-index': index === _this.currentSlide ? 10 : 9,
opacity: index === _this.currentSlide ? 1 : 0
};
if (index >= _this.currentSlide - 1 && index <= _this.currentSlide + 1) {
uniqueStyle.transition = 'opacity 250ms linear';
}
style = angular.extend(style, uniqueStyle);
}
return style;
};
/**
* setupInfinite
* To make carouse infinite we need close number of slidesToShow elements to
* previous elements and to after elements
*
* length = 8, show = 4, scroll = 3, current = 0
* ---------
* | |
* |4|5|6|7|0|1|2|3|4|5|6|7|1|2|3|4
* | |
* ---------
*/
this.setupInfinite = function () {
// Clone
var len = _this.slides.length;
var show = _this.options.slidesToShow;
if (_this.options.infinite && _this.options.fade === false) {
if (len > show) {
var number = show;
for (var i = 0; i < number; i++) {
_this.slidesInTrack.push(angular.copy(_this.slides[i]));
}
for (var _i = len - 1; _i >= len - show; _i--) {
_this.slidesInTrack.unshift(angular.copy(_this.slides[_i]));
}
}
}
};
/**
* get number of dosts
*
* @return Array
*/
this.getDots = function () {
if (!_this.slides) {
return [];
}
var dots = Math.ceil(_this.slides.length / _this.options.slidesToScroll);
var res = [];
for (var i = 0; i < dots; i++) {
res.push(i);
}
return res;
};
/**
* set carousel property
*
* - animType
* - transformType
* - transitionType
*/
this.setProps = function () {
var bodyStyle = document.body.style;
if (bodyStyle.OTransform !== undefined) {
_this.animType = 'OTransform';
_this.transformType = '-o-transform';
_this.transitionType = 'OTransition';
}
if (bodyStyle.MozTransform !== undefined) {
_this.animType = 'MozTransform';
_this.transformType = '-moz-transform';
_this.transitionType = 'MozTransition';
}
if (bodyStyle.webkitTransform !== undefined) {
_this.animType = 'webkitTransform';
_this.transformType = '-webkit-transform';
_this.transitionType = 'webkitTransition';
}
if (bodyStyle.msTransform !== undefined) {
_this.animType = 'msTransform';
_this.transformType = '-ms-transform';
_this.transitionType = 'msTransition';
}
if (bodyStyle.transform !== undefined && _this.animType !== false) {
_this.animType = 'transform';
_this.transformType = 'transform';
_this.transitionType = 'transition';
}
_this.transformsEnabled = true;
};
this.refreshCarousel = function () {
if (_this.slides && _this.slides.length && _this.slides.length > _this.options.slidesToShow) {
_this.isVisibleDots = true;
_this.isVisiblePrev = true;
_this.isVisibleNext = true;
}
// Re-init UI
_this.initUI();
};
/**
* refresh model
*/
$scope.$watch('ctrl.slides', function () {
_this.refreshCarousel();
});
this.init();
}]);
'use strict';
angular.module('ui.carousel.directives').directive('uiCarousel', ['$compile', '$templateCache', '$sce', function ($compile, $templateCache, $sce) {
return { restrict: 'AE',
scope: true,
bindToController: {
name: '=?',
slides: '=',
show: '=?slidesToShow',
scroll: '=?slidesToScroll',
classes: '@',
fade: '=?',
onChange: '=?',
disableArrow: '=?',
autoplay: '=?',
autoplaySpeed: '=?',
cssEase: '=?',
speed: '=?',
infinite: '=?',
arrows: '=?',
dots: '=?',
initialSlide: '=?',
// Method
onBeforeChange: '&',
onAfterChange: '&',
onInit: '&'
},
compile: function compile(el) {
var template = angular.element($templateCache.get('ui-carousel/carousel.template.html'));
// dynamic injections to override the inner layers' components
var injectComponentMap = {
'carousel-item': '.carousel-item',
'carousel-prev': '.carousel-prev',
'carousel-next': '.carousel-next'
};
var templateInstance = template.clone();
angular.forEach(injectComponentMap, function (innerSelector, outerSelector) {
var outerElement = el[0].querySelector(outerSelector);
if (outerElement) {
angular.element(templateInstance[0].querySelector(innerSelector)).html(outerElement.innerHTML);
}
});
return function ($scope, el) {
// Compile
var compiledElement = $compile(templateInstance)($scope);
el.addClass('ui-carousel').html('').append(compiledElement);
};
},
controller: 'CarouselController',
controllerAs: 'ctrl'
};
}]);
'use strict';
angular.module('ui.carousel.providers').provider('Carousel', function () {
var _this = this;
this.options = {
// Init like Slick carousel
// XXX Should be revised
arrows: true,
autoplay: false,
autoplaySpeed: 3000,
cssEase: 'ease',
dots: false,
draggable: true,
easing: 'linear',
fade: false,
infinite: true,
initialSlide: 0,
lazyLoad: 'ondemand',
slidesToShow: 1,
slidesToScroll: 1,
speed: 500,
swipe: true,
swipeToSlide: false,
touchMove: true,
vertical: false,
verticalSwiping: false
};
this.$get = [function () {
return {
setOptions: function setOptions(options) {
_this.options = angular.extend(_this.options, options);
},
getOptions: function getOptions() {
return _this.options;
}
};
}];
});
'use strict';
(function (module) {
try {
module = angular.module('ui.carousel');
} catch (e) {
module = angular.module('ui.carousel', []);
}
module.run(['$templateCache', function ($templateCache) {
$templateCache.put('ui-carousel/carousel.template.html', '<div class="carousel-wrapper" ng-show="ctrl.isCarouselReady"><div class="track-wrapper"><div class="track" ng-style="ctrl.trackStyle"><div class="slide" ng-repeat="item in ctrl.slidesInTrack track by $index" ng-style="ctrl.getSlideStyle($index)"><div class="carousel-item"></div></div></div></div><div class="carousel-prev" ng-if="!ctrl.disableArrow" ng-show="ctrl.isVisiblePrev && ctrl.options.arrows" ng-click="ctrl.prev()"><button class="carousel-btn"><i class="ui-icon-prev"></i></button></div><div class="carousel-next" ng-if="!ctrl.disableArrow" ng-show="ctrl.isVisibleNext && ctrl.options.arrows" ng-click="ctrl.next()"><button class="carousel-btn"><i class="ui-icon-next"></i></button></div><ul class="carousel-dots" ng-show="ctrl.isVisibleDots && ctrl.options.dots"><li ng-repeat="dot in ctrl.getDots()" ng-class="{ \'carousel-active\': dot == ctrl.currentSlide/ctrl.options.slidesToScroll }" ng-click="ctrl.movePage(dot)"><button>{{ dot }}</button></li></ul></div>');
}]);
})();
provide("angular-ui-carousel", module.exports);
}(global));