├── README.md
└── src
├── app.js
├── carousel.js
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | # A Lazy Loading, DOM-light AngularJS Carousel
2 |
3 | ## Where can I see a demo?
4 |
5 | Without further adieu, go check out our GitHub page for the [AngularJS-Carousel-Demo]. Then come back, and we'll
6 | talk about why it is so awesome.
7 |
8 | ## Why?
9 |
10 | Because everyone loves a carousel! And Twitter Bootstrap's carousel leaves a lot to be desired.
11 |
12 | But on a serious note, here's why we ended up creating Yet Another Carousel:
13 |
14 | * *Light on DOM* - We ran into a use case where we needed to display 1000's of items in a carousel. And make it
15 | load fast and be responsive. And whatever was out there wasn't cutting it.
16 | * *Lazy Loading* - As mentioned above, loading 1000 items upfront? Not a good idea. So we needed something that was
17 | smart about how it loaded items, and when it did so.
18 | * *Simple to use* - We needed to re use it multiple times, and rewriting it from scratch every time, or needing to
19 | tweak it slightly every time was not fun. So we made it as reusable as possible.
20 | * *Browser Compatibility* - While normal people use Google Chrome or Firefox, or even IE10 sometimes, we needed to
21 | support IE9 and **gasp** IE8. So CSS transitions were out!
22 |
23 | We ended up creating this directly, which was heavily inspired (and liberally borrows) from
24 | http://cubiq.org/swipeview[Cubiq's SwipeView]. Of course, we modified the guts of the beast, and replaced touch related
25 | events with click, and what not, but the core philosophy and the trick of using 3 divs for the carousel are all from him.
26 |
27 | ## Requirements
28 |
29 | Not too steep. All you need to get this working are
30 |
31 | 1. AngularJS (Duh!)
32 | 2. JQuery (To support transitions in IE9, and IE8!)
33 | 3. The directive code
34 |
35 | No extra CSS, all done with jQuery animations!
36 |
37 | ## Browser Support
38 |
39 | Ah, and here we get to the reason behind the jQuery necessity. The Carousel works on
40 |
41 | * Firefox
42 | * Chrome
43 | * Safari
44 | * IE8+ (probably 7 as well, but untested)
45 |
46 | ## Using it
47 |
48 | There are two parts to using the Carousel Directive, the HTML and the JS Controller code.
49 |
50 | Let us take a look at the HTML first:
51 |
52 | ```
53 |
57 |
58 | Content goes here
59 | {{scope.awesomeContent}}
60 |
61 |
62 | Previous
63 | Next
64 | ```
65 |
66 | There are five major attributes of note in the HTML
67 |
68 | 1. *carousel* : This marks the DOM element as a Carousel component. Without it, the rest are useless
69 | 2. *on-page-upcoming*: This is the function on the controller that is called whenever the carousel directive
70 | needs to load a certain page. It is called with the page number (Zero based, so page 1 is 0)
71 | 3. *give-carousel-to*: Now this function is called when the carousel is bootstrapped, and gives access to
72 | the controller to the directive. This is used for writing helpful functions to decide to show and hide
73 | the next / previous page links, and reset the carousel.
74 | 4. *carousel-class*: If you want to specify a class to be added to each page of the carousel.
75 | 5. *myCarouselPage*: This is the div that gets replicated for each page of the carousel. So, for every page
76 | that is there in the carousel, one copy of this div will get created.
77 |
78 | Now, let us take a look at the controller code to get this to work:
79 |
80 | ```
81 | // The following code is in the controller
82 |
83 | var carousel;
84 | $scope.loadPage = function(page, tmplCb) {
85 | var carouselPageScope = $scope.$new();
86 | // Assign / load all the items you want to carouselPageScope.
87 | // Do work
88 | // Ensure that the carousel knows how many total pages there are
89 | carousel.updatePageCount(totalPages);
90 | tmplCb(carouselPageScope);
91 | };
92 | $scope.onCarouselAvailable = function(car) {
93 | carousel = car;
94 | };
95 | $scope.hasNext = function() {
96 | return carousel ? carousel.hasNext() : false;
97 | };
98 | $scope.hasPrevious = function() {
99 | return carousel ? carousel.hasPrevious() : false;
100 | };
101 | $scope.next = function() {
102 | if (carousel) carousel.next();
103 | };
104 | $scope.prev = function() {
105 | if (carousel) carousel.prev();
106 | };
107 | ```
108 |
109 | We have quite a bit happening here, but there is really two major functions, and the rest are pretty boiler-plate:
110 |
111 | 1. *loadPage*: The load page function, as mentioned above takes two arguments. And it has two responsibilities. It needs
112 | to (ideally) create a new scope, and then fetch the contents for that particular page, and set it on this scope. The
113 | data on the scope can be set / reassigned at any point, as long as the reference to the scope is maintained. So async
114 | calls are not a problem!
115 | 2. *onCarouselAvailable*: THis gives the controller a handle on the carousel. Nothing more, nothing less.
116 | 3. *The pagination functions*: These are used to hide / show the next and previous links, based on the state of the carousel.
117 |
118 | ## Who are we?
119 |
120 | We are Fundoo Solutions, an awesome company based out of India who just love AngularJS. Check out our [website] or our [LinkedIn] page.
121 |
122 | ## License
123 |
124 | The code above is open sourced, and licensed under the MIT License, which is the simplest and easiest to understand, and most open.
125 | Basically, feel free to use this code or modify it as per your needs. The actual license is below:
126 |
127 | ### The MIT License
128 |
129 | > Copyright (C) 2011-2013 Vojta Jína.
130 | >
131 | > Permission is hereby granted, free of charge, to any person
132 | > obtaining a copy of this software and associated documentation files
133 | > (the "Software"), to deal in the Software without restriction,
134 | > including without limitation the rights to use, copy, modify, merge,
135 | > publish, distribute, sublicense, and/or sell copies of the Software,
136 | > and to permit persons to whom the Software is furnished to do so,
137 | > subject to the following conditions:
138 | >
139 | > The above copyright notice and this permission notice shall be
140 | > included in all copies or substantial portions of the Software.
141 | >
142 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
143 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
144 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
145 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
146 | > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
147 | > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
148 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
149 | > SOFTWARE.
150 |
151 |
152 |
153 | [AngularJS-Carousel-Demo]: http://fundoo-solutions.github.io/angularjs-carousel/
154 | [website]: http://www.befundoo.com
155 | [LinkedIn]: http://www.linkedin.com/company/fundoo-solutions
156 |
157 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | angular.module('FundooCarouselApp', ['fundoo.directives'])
2 | .controller('MainCtrl', ['$scope', 'FlickrApi', function($scope, flickr) {
3 | var carousel;
4 |
5 | $scope.hasPrevious = function() {
6 | return carousel ? carousel.hasPrevious() : false;
7 | };
8 | $scope.previous = function() {
9 | if (carousel) { carousel.prev(); }
10 | };
11 | $scope.hasNext = function() {
12 | return carousel ? carousel.hasNext() : false;
13 | };
14 | $scope.next = function() {
15 | if (carousel) { carousel.next(); }
16 | };
17 |
18 | var loadPhotos = function(carouselScope, page) {
19 | carousel.updatePageCount(6);
20 | carouselScope.photos = flickr.getPhotos(page);
21 | carouselScope.getPhotoUrl = function(photo) {
22 | return flickr.getPhotoUrl(photo);
23 | };
24 | };
25 | $scope.loadPage = function(page, tmplCb) {
26 | var newScope = $scope.$new();
27 | loadPhotos(newScope, page);
28 | tmplCb(newScope);
29 | };
30 | $scope.onCarouselAvailable = function(car) {
31 | carousel = car;
32 | };
33 | }]).factory('FlickrApi', function() {
34 | var pages = [
35 | [
36 | { "id": "8610594328", "secret": "e0a01f093f", "server": "8398", "farm": 9, "title": "Back from Work [Explored]", "isprimary": 0 },
37 | { "id": "8609770312", "secret": "ce2cc025ee", "server": "8392", "farm": 9, "title": "Chhau Masks of Charida [Explored]", "isprimary": 0 },
38 | { "id": "8605097271", "secret": "84b6ec7e0e", "server": "8120", "farm": 9, "title": "স্তব্ধতার গান......The Sound of Silence [Explored]", "isprimary": 0 },
39 | { "id": "8602385755", "secret": "b26323b04c", "server": "8532", "farm": 9, "title": "Sandakphu @ Sunset [Explored]", "isprimary": 0 },
40 | { "id": "8600778114", "secret": "6636515c02", "server": "8227", "farm": 9, "title": "Spring Colours [Explored]", "isprimary": 0 }
41 | ], [
42 | { "id": "8593917187", "secret": "a274587f5c", "server": "8241", "farm": 9, "title": "Celebration [Explored]", "isprimary": 0 },
43 | { "id": "8592654144", "secret": "3654566f78", "server": "8105", "farm": 9, "title": "Spring Garden [Explored]", "isprimary": 1 },
44 | { "id": "8590057990", "secret": "9948a167ea", "server": "8095", "farm": 9, "title": "Spring Solar System [Explored]", "isprimary": 0 },
45 | { "id": "8585649920", "secret": "43dc41c8bb", "server": "8518", "farm": 9, "title": "Cherry Blossom [Explored]", "isprimary": 0 },
46 | { "id": "8583184576", "secret": "52eb4819d8", "server": "8372", "farm": 9, "title": "Flambeau [Explored]", "isprimary": 0 }
47 | ], [
48 | { "id": "8580890561", "secret": "45c23c4225", "server": "8231", "farm": 9, "title": "Spring Garden [Explored]", "isprimary": 0 },
49 | { "id": "8428963019", "secret": "9525636b0f", "server": "8186", "farm": 9, "title": "Sandakphu [Explored]", "isprimary": 0 },
50 | { "id": "8426344513", "secret": "e2e6bb897f", "server": "8514", "farm": 9, "title": "Life process continues to flow... [Explored]", "isprimary": 0 },
51 | { "id": "8423110349", "secret": "ce1da3ab8f", "server": "8472", "farm": 9, "title": "Varanasi [Explored]", "isprimary": 0 },
52 | { "id": "8418889023", "secret": "a4e0cf98e2", "server": "8326", "farm": 9, "title": "To Get A Perfect Sunset.... [Explored]", "isprimary": 0 }
53 | ], [
54 | { "id": "8417868981", "secret": "7a4811fb3d", "server": "8212", "farm": 9, "title": "Murguma Lake at Sunset [Explored]", "isprimary": 0 },
55 | { "id": "8413140020", "secret": "c82a9a2bc5", "server": "8356", "farm": 9, "title": "The Peasant and The Photographer [Explored]", "isprimary": 0 },
56 | { "id": "8404264476", "secret": "9395407e0a", "server": "8466", "farm": 9, "title": "Every Day A New Beginning ! [Explored]", "isprimary": 0 },
57 | { "id": "8401996292", "secret": "dfc05d1414", "server": "8515", "farm": 9, "title": "Red In White [Explored]", "isprimary": 0 },
58 | { "id": "8398341696", "secret": "1e90a37771", "server": "8473", "farm": 9, "title": "Bengal in Winter [Explored]", "isprimary": 0 }
59 | ], [
60 | { "id": "8395243202", "secret": "faae05edf7", "server": "8227", "farm": 9, "title": "SEA.....SAND......SUNSET.....SHADOW [Explored]", "isprimary": 0 },
61 | { "id": "8233392271", "secret": "bcd284de10", "server": "8342", "farm": 9, "title": "Back from work.....[Explored]", "isprimary": 0 },
62 | { "id": "8230954133", "secret": "b34c08caab", "server": "8060", "farm": 9, "title": "নীলকন্ঠ পাখির খোঁজে [Explored]", "isprimary": 0 },
63 | { "id": "8210730110", "secret": "1145031dec", "server": "8057", "farm": 9, "title": "Catch The Dream [Explored]", "isprimary": 0 },
64 | { "id": "8205723341", "secret": "70fd23564a", "server": "8207", "farm": 9, "title": "The Gate [Explored]", "isprimary": 0 }
65 | ], [
66 | { "id": "8204067606", "secret": "afcaae887d", "server": "8347", "farm": 9, "title": "Life in the Mist [Explored]", "isprimary": 0 },
67 | { "id": "8201251807", "secret": "599bcd9439", "server": "8485", "farm": 9, "title": "Chhath......Festival Portrait [Explored]", "isprimary": 0 },
68 | { "id": "8193202609", "secret": "e187f099c4", "server": "8203", "farm": 9, "title": "The Temple Bells [Explored]", "isprimary": 0 },
69 | { "id": "8031568446", "secret": "0d1c1b0b83", "server": "8310", "farm": 9, "title": "তামট । Plain Tiger [Explored]", "isprimary": 0 },
70 | { "id": "8035496828", "secret": "0b950f69f6", "server": "8036", "farm": 9, "title": "Explore Front Page", "isprimary": 0 }
71 | ]
72 | ];
73 | return {
74 | getPhotos: function(page) {
75 | // Ideally, go off and fetch the next page of data fromt he server, but we'll do it locally in the sample
76 | return pages[page];
77 | },
78 | getPhotoUrl: function(photo) {
79 | return 'http://farm' + photo.farm + '.staticflickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_s.jpg';
80 | }
81 | };
82 | });
83 |
--------------------------------------------------------------------------------
/src/carousel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Heavily inspired by and liberally borrowed from Cubiq's SwipeView (http://cubiq.org/swipeview).
3 | * This takes SwipeView as the base, replaces CSS3 animations for jQuery animations
4 | * to enable IE8 and IE9 support, adds ability to have more divs instead
5 | * of a fixed 3, and uses jQuery data instead of HTML5 data for IE8 support.
6 | *
7 | * Lots of hooks for AngularJS directive support as well.
8 | */
9 |
10 | var Carousel = function (el, options) {
11 | var i,
12 | div,
13 | className,
14 | pageIndex;
15 |
16 | this.wrapper = typeof el === 'string' ? document.querySelector(el) : el;
17 | this.options = {
18 | text: null,
19 | numberOfPages: 3,
20 | numberOfDivsOnEachSide: 1,
21 | snapThreshold: null,
22 | hastyPageFlip: false,
23 | loop: false
24 | };
25 |
26 | // User defined options
27 | for (i in options) {
28 | if(options.hasOwnProperty(i)){
29 | this.options[i] = options[i];
30 | }
31 | }
32 |
33 | this.wrapper.style.overflow = 'hidden';
34 | this.wrapper.style.position = 'relative';
35 |
36 | this.masterPages = [];
37 | this.numDivs = (2 * this.options.numberOfDivsOnEachSide) + 1;
38 |
39 | div = document.createElement('div');
40 | div.id = 'carousel-slider';
41 | div.style.cssText = 'position:relative;top:0;height:100%;width:100%;';
42 | this.wrapper.appendChild(div);
43 | this.slider = div;
44 |
45 | this.refreshSize();
46 |
47 | var nonLoopPageCounter = 1;
48 |
49 | for (i=-this.options.numberOfDivsOnEachSide; i < (this.options.numberOfDivsOnEachSide + 1); i++) {
50 | div = document.createElement('div');
51 | div.id = 'carousel-masterpage-' + (i+ this.options.numberOfDivsOnEachSide);
52 | div.style.cssText = 'position:absolute;top:0;height:100%;width:100%;left:' + i*100 + '%';
53 | //if (!div.dataset) {div.dataset = {};}
54 | if (this.options.loop === true) {
55 | pageIndex = i < 0 ? this.options.numberOfPages + i : i;
56 | } else {
57 | pageIndex = i < 0 ? this.options.numberOfDivsOnEachSide + nonLoopPageCounter++ : i;
58 | }
59 | $(div).data('pageIndex', pageIndex);
60 | $(div).data('upcomingPageIndex', pageIndex);
61 |
62 | if (!this.options.loop && i === -1) {div.style.visibility = 'hidden';}
63 |
64 | this.slider.appendChild(div);
65 | this.masterPages.push(div);
66 | }
67 |
68 | className = this.masterPages[this.options.numberOfDivsOnEachSide].className;
69 | this.masterPages[this.options.numberOfDivsOnEachSide].className = !className ? 'carousel-active' : className + ' carousel-active';
70 | this.currentMasterPage = this.options.numberOfDivsOnEachSide;
71 | };
72 |
73 | Carousel.prototype = {
74 | currentMasterPage: 1,
75 | x: 0,
76 | page: 0,
77 | pageIndex: 0,
78 | customEvents: [],
79 |
80 | onFlip: function (fn) {
81 | $(this.wrapper).on('carouselflip', fn);
82 | this.customEvents.push(['flip', fn]);
83 | },
84 |
85 | onReset: function (fn) {
86 | $(this.wrapper).on('carouselreset', fn);
87 | this.customEvents.push(['reset', fn]);
88 | },
89 |
90 | destroy: function () {
91 | while ( this.customEvents.length ) {
92 | $(this.wrapper).off('carousel' + this.customEvents[0][0]);
93 | this.customEvents.shift();
94 | }
95 | },
96 |
97 | refreshSize: function () {
98 | this.wrapperWidth = this.wrapper.clientWidth;
99 | this.pageWidth = this.wrapperWidth;
100 | this.maxX = -this.options.numberOfPages * this.pageWidth + this.wrapperWidth;
101 | this.snapThreshold = this.options.snapThreshold === null ?
102 | Math.round(this.pageWidth * 0.15) :
103 | /%/.test(this.options.snapThreshold) ?
104 | Math.round(this.pageWidth * this.options.snapThreshold.replace('%', '') / 100) :
105 | this.options.snapThreshold;
106 | },
107 |
108 | updatePageCount: function (n) {
109 | this.options.numberOfPages = n;
110 | this.maxX = -this.options.numberOfPages * this.pageWidth + this.wrapperWidth;
111 | },
112 |
113 | goToPage: function (p, reset) {
114 | var i, index;
115 |
116 | if (reset) {
117 | this.updatePageCount(1);
118 | }
119 |
120 | this.masterPages[this.currentMasterPage].className = this.masterPages[this.currentMasterPage].className.replace(/(^|\s)carousel-active(\s|$)/, '');
121 | for (i=0; i this.options.numberOfPages-1 ? this.options.numberOfPages-1 : p;
130 | this.page = p;
131 | this.pageIndex = p;
132 | this.__pos(-p * this.pageWidth);
133 |
134 | this.currentMasterPage = (this.page + 1) - Math.floor((this.page + 1) / this.numDivs) * this.numDivs;
135 |
136 | this.masterPages[this.currentMasterPage].className = this.masterPages[this.currentMasterPage].className + ' carousel-active';
137 |
138 | var divsOnEachSide = Math.floor(this.numDivs / 2);
139 |
140 | var sequenceOfIndicesFromMaster = [];
141 | for (i = 0; i < this.numDivs; i++) {
142 | index = this.currentMasterPage + i;
143 | if (index >= this.numDivs) {
144 | index -= this.numDivs;
145 | }
146 | if (i <= divsOnEachSide) {
147 | sequenceOfIndicesFromMaster.push(index);
148 | } else {
149 | sequenceOfIndicesFromMaster.unshift(index);
150 | }
151 | }
152 |
153 | for (i = 0; i < sequenceOfIndicesFromMaster.length; i++) {
154 | index = sequenceOfIndicesFromMaster[i];
155 | if (i < divsOnEachSide) {
156 | var minusPages = (divsOnEachSide - i);
157 | this.masterPages[index].style.left = this.page * 100 - (minusPages * 100) + '%';
158 | $(this.masterPages[index]).data('upcomingPageIndex', this.page - minusPages < 0 ? this.options.numberOfPages - minusPages : this.page - minusPages);
159 | if ($(this.masterPages[index]).data('upcomingPageIndex') !== this.page - minusPages) {
160 | this.masterPages[index].style.visibility = 'hidden';
161 | }
162 | } else {
163 | var plusPages = (i - divsOnEachSide);
164 | this.masterPages[index].style.left = this.page * 100 + (plusPages * 100) + '%';
165 | $(this.masterPages[index]).data('upcomingPageIndex', this.page >= this.options.numberOfPages - plusPages ? plusPages : this.page + plusPages);
166 | }
167 | }
168 |
169 | if (reset) {
170 | this.__reset();
171 | }
172 | },
173 |
174 | hasNext: function() {
175 | return this.page < this.options.numberOfPages - 1;
176 | },
177 | hasPrevious: function() {
178 | return this.page > 0;
179 | },
180 | next: function () {
181 | if (!this.options.loop && this.x === this.maxX) {return;}
182 |
183 | this.directionX = -1;
184 | this.x -= 1;
185 | this.__checkPosition();
186 | },
187 |
188 | prev: function () {
189 | if (!this.options.loop && this.x === 0){ return;}
190 |
191 | this.directionX = 1;
192 | this.x += 1;
193 | this.__checkPosition();
194 | },
195 |
196 | /**
197 | *
198 | * Pseudo private methods
199 | *
200 | */
201 | __pos: function (x) {
202 | this.x = x;
203 | var self = this;
204 | $(this.slider).animate({left: x}, 500, 'swing', function() {
205 | self.__flip();
206 | });
207 | },
208 |
209 | __checkPosition: function () {
210 | this.refreshSize();
211 | var pageFlip,
212 | pageFlipIndex,
213 | className;
214 |
215 | this.masterPages[this.currentMasterPage].className = this.masterPages[this.currentMasterPage].className.replace(/(^|\s)carousel-active(\s|$)/, '');
216 | // Flip the page
217 | if (this.directionX > 0) {
218 | this.page = -Math.ceil(this.x / this.pageWidth);
219 | this.currentMasterPage = (this.options.numberOfDivsOnEachSide + ((this.page) % this.numDivs)) % this.numDivs;
220 |
221 | this.pageIndex = this.pageIndex === 0 ? this.options.numberOfPages - 1 : this.pageIndex - 1;
222 |
223 | pageFlip = this.currentMasterPage - 1;
224 | pageFlip = pageFlip < 0 ? this.numDivs - 1 : pageFlip;
225 | this.masterPages[pageFlip].style.left = this.page * 100 - 100 + '%';
226 |
227 | pageFlipIndex = this.page - 1;
228 | } else {
229 | this.page = -Math.floor(this.x / this.pageWidth);
230 | this.currentMasterPage = (this.options.numberOfDivsOnEachSide + ((this.page) % this.numDivs)) % this.numDivs;
231 |
232 | this.pageIndex = this.pageIndex === this.options.numberOfPages - 1 ? 0 : this.pageIndex + 1;
233 |
234 | pageFlip = this.currentMasterPage + 1;
235 | pageFlip = pageFlip > (this.numDivs - 1) ? 0 : pageFlip;
236 | this.masterPages[pageFlip].style.display = '';
237 | this.masterPages[pageFlip].style.left = this.page * 100 + 100 + '%';
238 |
239 | pageFlipIndex = this.page + 1;
240 | }
241 |
242 | // Add active class to current page
243 | className = this.masterPages[this.currentMasterPage].className;
244 | if(!/(^|\s)carousel-active(\s|$)/.test(className)) {
245 | this.masterPages[this.currentMasterPage].className = !className ? 'carousel-active' : className + ' carousel-active';
246 | }
247 |
248 | this.masterPages[this.currentMasterPage].style.visibility = '';
249 |
250 | // Add loading class to flipped page
251 | className = this.masterPages[pageFlip].className;
252 | if(!/(^|\s)carousel-loading(\s|$)/.test(className)) {
253 | this.masterPages[pageFlip].className = !className ? 'carousel-loading' : className + ' carousel-loading';
254 | }
255 |
256 | pageFlipIndex = pageFlipIndex - Math.floor(pageFlipIndex / this.options.numberOfPages) * this.options.numberOfPages;
257 | $(this.masterPages[pageFlip]).data('upcomingPageIndex', pageFlipIndex); // Index to be loaded in the newly flipped page
258 |
259 | var newX = -this.page * this.pageWidth;
260 |
261 | // Hide the next page if we decided to disable looping
262 | if (!this.options.loop) {
263 | this.masterPages[pageFlip].style.visibility = newX === 0 || newX === this.maxX ? 'hidden' : '';
264 | }
265 |
266 | this.__pos(newX);
267 | if (this.options.hastyPageFlip) {
268 | this.__flip();
269 | }
270 |
271 | },
272 |
273 | __flip: function () {
274 | this.__event('flip');
275 |
276 | for (var i=0; i');
310 |
311 | return function(scope, elem, attrs) {
312 | var carouselContainer = elem.find('>:first-child');
313 | carouselContainer.addClass(attrs.carouselClass);
314 | var carousel = new Carousel(carouselContainer[0], {numberOfPages: 1, loop: false});
315 | var tmplCbFunction = function(index) {
316 | return function(scope) {
317 | carouselHtmlTemplate(scope, function(clonedElem) {
318 | if (carousel.masterPages[index].firstChild) {
319 | carousel.masterPages[index].replaceChild(clonedElem[0], carousel.masterPages[index].firstChild);
320 | } else {
321 | carousel.masterPages[index].appendChild(clonedElem[0]);
322 | }
323 | });
324 | };
325 | };
326 | var initialCarouselLoad = function() {
327 |
328 | var nonLoopPageCounter = 1;
329 | for (var i = -carousel.options.numberOfDivsOnEachSide; i <= carousel.options.numberOfDivsOnEachSide; i++) {
330 | var pageIndex;
331 | if (carousel.options.loop === true) {
332 | pageIndex = i < 0 ? carousel.options.numberOfPages + i : i;
333 | } else {
334 | pageIndex = i < 0 ? carousel.options.numberOfDivsOnEachSide + nonLoopPageCounter++ : i;
335 | }
336 | scope.onPageUpcoming({page: pageIndex, tmplCb: tmplCbFunction(i + carousel.options.numberOfDivsOnEachSide)});
337 | }
338 | };
339 | scope.giveCarouselTo({carousel: carousel});
340 |
341 | initialCarouselLoad();
342 |
343 | carousel.onReset(function() {
344 | initialCarouselLoad();
345 | });
346 |
347 | carousel.onFlip(function () {
348 | var upcoming, i;
349 |
350 | for (i=0; i < carousel.numDivs; i++) {
351 | upcoming = $(carousel.masterPages[i]).data('upcomingPageIndex');
352 | if (upcoming !== $(carousel.masterPages[i]).data('pageIndex')) {
353 | scope.onPageUpcoming({page: upcoming, tmplCb: tmplCbFunction(i)});
354 | }
355 | }
356 | });
357 |
358 | // Handle resizing of the page
359 | function resizeCarousel() {
360 | carousel.refreshSize();
361 | carousel.goToPage(carousel.page);
362 | }
363 |
364 | // The browser resize event is sent multiple times while a user is
365 | // dragging the browser to resize. So we do this stuff to ensure that
366 | // we only do a resize once the user has finished his resizing activities.
367 | var rtime = new Date(1, 1, 2000, 12, 0, 0);
368 | var timeout = false;
369 | var delta = 200;
370 | $(window).resize(function() {
371 | rtime = new Date();
372 | if (timeout === false) {
373 | timeout = true;
374 | setTimeout(resizeend, delta);
375 | }
376 | });
377 |
378 | function resizeend() {
379 | if (new Date() - rtime < delta) {
380 | setTimeout(resizeend, delta);
381 | } else {
382 | timeout = false;
383 | resizeCarousel();
384 | }
385 | }
386 | };
387 | }
388 | };
389 | }]);
390 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AngularJS Carousel Directive Demo
5 |
6 |
16 |
17 |
18 |
19 |