1/* jshint browser: true, jquery: true, devel: true */ 2/* global window, jQuery */ 3 4(function($, window, undefined) { 5 'use strict'; 6 7 function throttle(func, delay) { 8 var timer = null; 9 10 return function() { 11 var context = this, 12 args = arguments; 13 14 if ( timer === null ) { 15 timer = setTimeout(function() { 16 func.apply(context, args); 17 timer = null; 18 }, delay); 19 } 20 }; 21 } 22 23 // Check for browser CSS support and cache the result for subsequent calls. 24 var checkStyleSupport = (function() { 25 var support = {}; 26 return function(prop) { 27 if ( support[prop] !== undefined ) { return support[prop]; } 28 29 var div = document.createElement('div'), 30 style = div.style, 31 ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 32 prefixes = ["webkit", "moz", "ms", "o"], 33 props = (prop + ' ' + (prefixes).join(ucProp + ' ') + ucProp).split(' '); 34 35 for (var i in props) { 36 if ( props[i] in style ) { return support[prop] = props[i]; } 37 } 38 39 return support[prop] = false; 40 }; 41 }()); 42 43 var svgNS = 'http://www.w3.org/2000/svg', 44 svgSupport = (function() { 45 var support; 46 return function() { 47 if ( support !== undefined ) { return support; } 48 var div = document.createElement('div'); 49 div.innerHTML = '<svg/>'; 50 support = ( div.firstChild && div.firstChild.namespaceURI === svgNS ); 51 return support; 52 }; 53 }()); 54 55 var $window = $(window), 56 57 transformSupport = checkStyleSupport('transform'), 58 59 defaults = { 60 itemContainer: 'ul', 61 // [string|object] 62 // Selector for the container of the flippin' items. 63 64 itemSelector: 'li', 65 // [string|object] 66 // Selector for children of `itemContainer` to flip 67 68 start: 'start', 69 // ['center'|number] 70 // Zero based index of the starting item, or use 'center' to start in the middle 71 72 fadeIn: 0, 73 // [milliseconds] 74 // Speed of the fade in animation after items have been setup 75 76 loop: false, 77 // [true|false] 78 // Loop around when the start or end is reached 79 80 autoplay: false, 81 // [false|milliseconds] 82 // If a positive number, Flipster will automatically advance to next item after that number of milliseconds 83 84 pauseOnHover: true, 85 // [true|false] 86 // If true, autoplay advancement will pause when Flipster is hovered 87 88 style: 'coverflow', 89 // [coverflow|carousel|flat|...] 90 // Adds a class (e.g. flipster--coverflow) to the flipster element to switch between display styles 91 // Create your own theme in CSS and use this setting to have Flipster add the custom class 92 93 spacing: -0.6, 94 // [number] 95 // Space between items relative to each item's width. 0 for no spacing, negative values to overlap 96 97 click: true, 98 // [true|false] 99 // Clicking an item switches to that item 100 101 keyboard: true, 102 // [true|false] 103 // Enable left/right arrow navigation 104 105 scrollwheel: true, 106 // [true|false] 107 // Enable mousewheel/trackpad navigation; up/left = previous, down/right = next 108 109 touch: true, 110 // [true|false] 111 // Enable swipe navigation for touch devices 112 113 nav: false, 114 // [true|false|'before'|'after'] 115 // If not false, Flipster will build an unordered list of the items 116 // Values true or 'before' will insert the navigation before the items, 'after' will append the navigation after the items 117
118 buttons: false, 119 // [true|false|'custom'] 120 // If true, Flipster will insert Previous / Next buttons with SVG arrows 121 // If 'custom', Flipster will not insert the arrows and will instead use the values of `buttonPrev` and `buttonNext` 122 123 buttonPrev: 'Previous', 124 // [text|html] 125 // Changes the text for the Previous button 126 127 buttonNext: 'Next', 128 // [text|html] 129 // Changes the text for the Next button 130 131 onItemSwitch: false 132 133 // [function] 134 // Callback function when items are switched 135 // Arguments received: [currentItem, previousItem] 136 }, 137 138 classes = { 139 main: 'flipster', 140 active: 'flipster--active', 141 container: 'flipster__container', 142 143 nav: 'flipster__nav', 144 navChild: 'flipster__nav__child', 145 navItem: 'flipster__nav__item', 146 navLink: 'flipster__nav__link', 147 navCurrent: 'flipster__nav__item--current', 148 navCategory: 'flipster__nav__item--category', 149 navCategoryLink: 'flipster__nav__link--category', 150 151 button: 'flipster__button', 152 buttonPrev: 'flipster__button--prev', 153 buttonNext: 'flipster__button--next', 154 155 item: 'flipster__item', 156 itemCurrent: 'flipster__item--current', 157 itemPast: 'flipster__item--past', 158 itemFuture: 'flipster__item--future', 159 itemContent: 'flipster__item__content' 160 }, 161 162 classRemover = new RegExp('\\b(' + classes.itemCurrent + '|' + classes.itemPast + '|' + classes.itemFuture + ')(.*?)(\\s|$)', 'g'), 163 whiteSpaceRemover = new RegExp('\\s\\s+', 'g'); 164 165 $.fn.flipster = function(options) { 166 var isMethodCall = (typeof options === 'string' ? true : false); 167 168 if ( isMethodCall ) { 169 var args = Array.prototype.slice.call(arguments, 1); 170 return this.each(function() { 171 var methods = $(this).data('methods'); 172 if ( methods[options] ) { 173 return methods[options].apply(this, args); 174 } else { 175 return this; 176 } 177 }); 178 } 179 180 var settings = $.extend({}, defaults, options); 181 182 return this.each(function() { 183 184 var self = $(this), 185 methods, 186 187 _container, 188 _containerWidth, 189 _resizeInterval, 190 191 _items, 192 _itemOffsets = [], 193 _currentItem, 194 _currentIndex = 0, 195 196 _nav, 197 _navItems, 198 _navLinks, 199 200 _playing = false, 201 _startDrag = false; 202 203 function buildButtonContent(dir) { 204 var text = ( dir === 'next' ? settings.buttonNext : settings.buttonPrev ); 205 206 if ( settings.buttons === 'custom' || !svgSupport ) { return text; } 207 208 return '<svg viewBox="0 0 13 20" xmlns="' + svgNS + '" aria-labelledby="title"><title>' + text + '</title><polyline points="10,3 3,10 10,17"' + (dir === 'next' ? ' transform="rotate(180 6.5,10)"' : '') + '/></svg>'; 209 } 210 211 function buildButton(dir) { 212 dir = dir || 'next'; 213 214 return $('<button class="' + classes.button + ' ' + ( dir === 'next' ? classes.buttonNext : classes.buttonPrev ) + '" role="button" />') 215 .html(buildButtonContent(dir)) 216 .on('click', function(e) { 217 jump(dir); 218 e.preventDefault(); 219 }); 220 221 } 222 223 function buildButtons() { 224 if ( settings.buttons && _items.length > 1 ) { 225 self.find('.' + classes.button).remove(); 226 self.append(buildButton('prev'), buildButton('next')); 227 } 228 } 229 230 function buildNav() { 231 var navCategories = {}; 232 233 if ( !settings.nav || _items.length <= 1 ) { return; } 234 235 if ( _nav ) { _nav.remove(); } 236 237 _nav = $('<ul class="' + classes.nav + '" role="navigation" />'); 238 _navLinks = $(''); 239 240 _items.each(function(i) { 241 var item = $(this), 242 category = item.data('flip-category'), 243 itemTitle = item.data('flip-title') || item.attr('title') || i, 244 navLink = $('<a href="#" class="' + classes.navLink + '">' + itemTitle + '</a>') 245 .data('index', i); 246 247 _navLinks = _navLinks.add(navLink); 248 249 if ( category ) { 250 251 if ( !navCategories[category] ) { 252 253 var categoryItem = $('<li class="' + classes.navItem + ' ' + classes.navCategory + '">'); 254 var categoryLink = $('<a href="#" class="' + classes.navLink + ' ' + classes.navCategoryLink + '" data-flip-category="' + category + '">' + category + '</a>') 255 .data('category', category) 256 .data('index', i); 257 258 navCategories[category] = $('<ul class="' + classes.navChild + '" />'); 259 260 _navLinks = _navLinks.add(categoryLink); 261 262 categoryItem 263 .append(categoryLink, navCategories[category]) 264 .appendTo(_nav); 265 } 266 267 navCategories[category].append(navLink); 268 } else { 269 _nav.append(navLink); 270 } 271 272 navLink.wrap('<li class="' + classes.navItem + '">'); 273 274 }); 275 276 _nav.on('click', 'a', function(e) { 277 var index = $(this).data('index'); 278 if ( index >= 0 ) { 279 jump(index); 280 e.preventDefault(); 281 } 282 }); 283 284 if ( settings.nav === 'after' ) { self.append(_nav); } 285 else { self.prepend(_nav); } 286 287 _navItems = _nav.find('.' + classes.navItem); 288 } 289 290 function updateNav() { 291 if ( settings.nav ) { 292 293 var category = _currentItem.data('flip-category'); 294
295 _navItems.removeClass(classes.navCurrent); 296 297 _navLinks 298 .filter(function() { 299 return ($(this).data('index') === _currentIndex || (category && $(this).data('category') === category)); 300 }) 301 .parent() 302 .addClass(classes.navCurrent); 303 304 } 305 } 306 307 function noTransition() { 308 self.css('transition', 'none'); 309 _container.css('transition', 'none'); 310 _items.css('transition', 'none'); 311 } 312 313 function resetTransition() { 314 self.css('transition', ''); 315 _container.css('transition', ''); 316 _items.css('transition', ''); 317 } 318 319 function calculateBiggestItemHeight() { 320 var biggestHeight = 0, 321 itemHeight; 322 323 _items.each(function() { 324 itemHeight = $(this).height(); 325 if ( itemHeight > biggestHeight ) { biggestHeight = itemHeight; } 326 }); 327 return biggestHeight; 328 } 329 330 function resize(skipTransition) { 331 if ( skipTransition ) { noTransition(); } 332 333 _containerWidth = _container.width(); 334 _container.height(calculateBiggestItemHeight()); 335 336 // Prevent maximum callstack error. #79 #74 337 if ( !_containerWidth ) { 338 _resizeInterval = _resizeInterval || setInterval(function(){ resize(skipTransition); },500); 339 return; 340 } else if ( _resizeInterval ) { 341 clearInterval(_resizeInterval); 342 _resizeInterval = false; 343 } 344 345 _items.each(function(i) { 346 var item = $(this), 347 width, 348 left; 349 350 item.attr('class', function(i, c) { 351 return c && c.replace(classRemover, '').replace(whiteSpaceRemover, ' '); 352 }); 353 354 width = item.outerWidth(); 355 356 if (settings.spacing !== 0 && _items.length > 1 ) { 357 item.css('margin-right', ( width * settings.spacing ) + 'px'); 358 } 359 360 left = item.position().left; 361 _itemOffsets[i] = -1 * ((left + (width / 2)) - (_containerWidth / 2)); 362 363 if ( i === _items.length - 1 ) { 364 center(); 365 if ( skipTransition ) { setTimeout(resetTransition, 1); } 366 } 367 }); 368 } 369 370 function center() { 371 var total = _items.length; 372 var item; 373 var newClass; 374 var zIndex; 375 376 _items.each(function (i) { 377 item = $(this); 378 newClass = ' '; 379 380 if (i === _currentIndex) 381 { 382 newClass += classes.itemCurrent; 383 zIndex = (total + 1); 384 } 385 else if (i < _currentIndex) 386 { 387 388 newClass += classes.itemPast + ' ' + 389 classes.itemPast + '-' + (_currentIndex - i); 390 zIndex = total - (_currentIndex - i); 391 } 392 else 393 { 394 newClass += classes.itemFuture + ' ' + 395 classes.itemFuture + '-' + (i - _currentIndex); 396 397 zIndex = total - (i - _currentIndex); 398 } 399 400 item.css('z-index', zIndex ) 401 .attr('class',function(i, c){ 402 return c && c.replace(classRemover, '').replace(whiteSpaceRemover,' ') + newClass; 403 }); 404 }); 405
406 if ( _currentIndex >= 0 ) { 407 if ( !_containerWidth || _itemOffsets[_currentIndex] === undefined ) { resize(true); } 408 409 if ( transformSupport ) { 410 _container.css('transform', 'translateX(' + _itemOffsets[_currentIndex] + 'px)'); 411 } else { 412 _container.css({ 'left': _itemOffsets[_currentIndex] + 'px' }); 413 } 414 } 415 416 updateNav(); 417 } 418 419 function jump(to) { 420 var _previous = _currentIndex; 421 422 if ( _items.length <= 0 ) { return; } 423 424 if (to === 'prev') { 425 if ( _currentIndex > 0 ) { _currentIndex--; } 426 else if ( settings.loop ) { _currentIndex = _items.length - 1; } 427 } else if (to === 'next') { 428 if ( _currentIndex < _items.length - 1 ) { _currentIndex++; } 429 else if ( settings.loop ) { _currentIndex = 0; } 430 } else if (typeof to === 'number') { 431 _currentIndex = to; 432 } else if ( to !== undefined ) { 433 // if object is sent, get its index 434 _currentIndex = _items.index(to); 435 if (settings.loop && _previous != _currentIndex) { 436 if (_previous == (_items.length - 1) && _currentIndex != (_items.length - 2)) _currentIndex = 0; 437 if (_previous == 0 && _currentIndex != 1) _currentIndex = (_items.length - 1); 438 } 439 } 440 441 _currentItem = _items.eq(_currentIndex); 442 443 if ( _currentIndex !== _previous && settings.onItemSwitch ) { 444 settings.onItemSwitch.call(self, _items[_currentIndex], _items[_previous]); 445 } 446 447 center(); 448 449 return self; 450 } 451 452 function play(interval) { 453 settings.autoplay = interval || settings.autoplay; 454 455 clearInterval(_playing); 456 457 _playing = setInterval(function() { 458 var prev = _currentIndex; 459 jump('next'); 460 if ( prev === _currentIndex && !settings.loop ) { clearInterval(_playing); } 461 }, settings.autoplay); 462 463 return self; 464 } 465 466 function stop(){ 467 clearInterval(_playing); 468 _playing = 0; 469 return self; 470 } 471 472 function pause(forced) { 473 stop(); 474 if ( settings.autoplay && forced ) { _playing = -1; } 475 return self; 476 } 477 478 function show() { 479 resize(true); 480 self.hide() 481 .css('visibility', '')
482 .addClass(classes.active) 483 .fadeIn(settings.fadeIn); 484 } 485 486 function index() { 487 488 _container = self.find(settings.itemContainer).addClass(classes.container); 489 490 _items = _container.find(settings.itemSelector); 491 492 if ( _items.length <= 0 ) { return; } 493 494 _items 495 .addClass(classes.item) 496 // Wrap inner content 497 .each(function() { 498 var item = $(this); 499 if ( !item.children('.' + classes.itemContent ).length) { 500 item.wrapInner('<div class="' + classes.itemContent + '" />'); 501 } 502 }); 503 504 // Navigate directly to an item by clicking 505 if ( settings.click ) { 506 _items.on('click.flipster touchend.flipster', function (e) { 507 if ( !_startDrag ) { 508 if ( !$(this).hasClass(classe
508s.itemCurrent) ) { e.preventDefault(); } 509 jump(this); 510 } 511 }); 512 } 513 514 // Insert navigation if enabled. 515 buildButtons(); 516 buildNav(); 517 518 if ( _currentIndex >= 0 ) { jump(_currentIndex); } 519 520 return self; 521 } 522 523 function keyboardEvents(elem) { 524 if ( settings.keyboard ) { 525 elem[0].tabIndex = 0; 526 elem.on('keydown.flipster', throttle(function(e) { 527 var code = e.which; 528 if ( code === 37 || code === 39 ) { 529 jump( code === 37 ? 'prev' : 'next' ); 530 e.preventDefault(); 531 } 532 }, 250, true)); 533 } 534 } 535 536 function wheelEvents(elem) { 537 if ( settings.scrollwheel ) { 538 var _wheelInside = false, 539 _actionThrottle = 0, 540 _throttleTimeout = 0, 541 _delta = 0, 542 _dir, _lastDir, 543 _isMozilla = /mozilla/.test(navigator.userAgent.toLowerCase()) && !/webkit/.test(navigator.userAgent.toLowerCase()); 544 545 elem 546 .on('mousewheel.flipster wheel.flipster', function() { _wheelInside = true; }) 547 .on('mousewheel.flipster wheel.flipster', throttle(function(e) { 548 549 // Reset after a period without scrolling. 550 clearTimeout(_throttleTimeout); 551 _throttleTimeout = setTimeout(function() { 552 _actionThrottle = 0; 553 _delta = 0; 554 }, 300); 555 556 e = e.originalEvent; 557 558 // Add to delta (+=) so that continuous small events can still get past the speed limit, and quick direction reversals get cancelled out 559 _delta += (e.wheelDelta || (e.deltaY + e.deltaX) * -1); // Invert numbers for Firefox 560 561 // Don't trigger unless the scroll is decent speed. 562 // There's need to check if _isMozilla because of different working delta in Firefox (in FF scroll delta is less than 25 or more than -25) 563 if ( Math.abs(_delta) < 25 && ! _isMozilla) { return; } 564 565 _actionThrottle++; 566 567 _dir = (_delta > 0 ? 'prev' : 'next'); 568 569 // Reset throttle if direction changed. 570 if ( _lastDir !== _dir ) { _actionThrottle = 0; } 571 _lastDir = _dir; 572 573 // Regular scroll wheels trigger less events, so they don't need to be throttled. Trackpads trigger many events (inertia), so only trigger jump every three times to slow things down. 574 if ( _actionThrottle < 6 || _actionThrottle % 3 === 0 ) { jump(_dir); } 575 576 _delta = 0; 577 578 }, 50)); 579 580 // Disable mousewheel on window if event began in elem. 581 $window.on('mousewheel.flipster wheel.flipster', function(e) { 582 if ( _wheelInside ) { 583 e.preventDefault(); 584 _wheelInside = false; 585 } 586 }); 587 } 588 } 589 590 function touchEvents(elem) { 591 if (settings.touch) { 592 var _startDragX, _startDragY, x, y, offsetY, offsetX; 593 594 elem.on({ 595 'touchstart.flipster': function (e) { 596 // e.preventDefault(); 597 e = e.originalEvent; 598 _startDragX = (e.touches ? e.touches[0].clientX : e.clientX); 599 _startDragY = (e.touches ? e.touches[0].clientY : e.clientY); 600 }, 601 602 'touchmove.flipster': function (e) { 603 // e.preventDefault(); 604 e = e.originalEvent; 605 x = (e.touches ? e.touches[0].clientX : e.clientX); 606 y = (e.touches ? e.touches[0].clientY : e.clientY); 607 offsetX = x - _startDragX; 608 offsetY = y - _startDragY; 609 if (Math.abs(offsetX) > 30 && Math.abs(offsetY) < 100) { 610 e.preventDefault(); 611 } 612 }, 613 614 'touchend.flipster touchcancel.flipster ': function () { 615 offsetX = x - _startDragX; 616 offsetY = y - _startDragY; 617 if (Math.abs(offsetX) > 30 && Math.abs(offsetY) < 100) { 618 if (offsetX > 0) { 619 jump('prev'); 620 } 621 else { 622 jump('next'); 623 } 624 } 625 } 626 }); 627 } 628 } 629
630 function init() { 631 632 var style; 633 634 self.css('visibility', 'hidden'); 635 636 index(); 637 638 if ( _items.length <= 0 ) { 639 self.css('visibility', ''); 640 return; 641 } 642 643 style = (settings.style ? 'flipster--' + settings.style.split(' ').join(' flipster--') : false); 644 645 self.addClass([ 646 classes.main, 647 (transformSupport ? 'flipster--transform' : ' flipster--no-transform'), 648 style, // 'flipster--'+settings.style : '' ), 649 (settings.click ? 'flipster--click' : '') 650 ].join(' ')); 651 652 // Set the starting item 653 if ( settings.start ) { 654 // Find the middle item if start = center 655 _currentIndex = ( settings.start === 'center' ? Math.floor(_items.length / 2) : settings.start ); 656 } 657 658 jump(_currentIndex); 659 660 var images = self.find('img'); 661 662 if ( images.length ) { 663 var imagesLoaded = 0; 664 665 // Resize after all images have loaded. 666 images.on('load', function() { 667 imagesLoaded++; 668 if ( imagesLoaded >= images.length ) { show(); } 669 }); 670 671 // Fallback to show Flipster while images load in case it takes a while. 672 setTimeout(show, 750); 673 } else { 674 show(); 675 } 676 677 // Attach event bindings. 678 $window.on('resize.flipster', throttle(resize, 400)); 679 // Attach scroll event 1StepAhead. 680 $window.on('scroll.flipster', doTheScroll); 681 if ( settings.autoplay ) { play(); } 682 683 if ( settings.pauseOnHover ) { 684 _container 685 .on('mouseenter.flipster', function(){ 686 if (_playing) { pause(true); } 687 else { stop() } 688 }) 689 .on('mouseleave.flipster', function() { 690 if ( _playing === -1 ) { play(); } 691 }); 692 } 693 694 // Stop flipster 695 function doTheScroll() { 696 stop(); 697 return false; 698 } 699 700 keyboardEvents(self); 701 wheelEvents(_container); 702 touchEvents(_container); 703 } 704 705 // public methods 706 methods = { 707 jump: jump, 708 next: function() { return jump('next'); }, 709 prev: function() { return jump('prev'); }, 710 play: play, 711 stop: stop, 712 pause: pause, 713 index: index 714 }; 715 self.data('methods', methods); 716 717 // Initialize if flipster is not already active.
718 if ( !self.hasClass(classes.active) ) { init(); } 719 }); 720 }; 721})(jQuery, window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.