Headline
CVE-2023-5621: Changeset 1263536 for wp-responsive-slider-with-lightbox – WordPress Plugin Repository
The Thumbnail Slider With Lightbox plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Image Title field in versions up to, and including, 1.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.
2 * BxSlider v4.1.1 - Fully loaded, responsive content slider 3 * http://bxslider.com 4 * 5 * Copyright 2012, Steven Wanderski - http://stevenwanderski.com - http://bxcreative.com 2 * bxSlider v4.2.4 3 * Copyright 2013-2015 Steven Wanderski
7 * 8 * Released under the WTFPL license - http://sam.zoy.org/wtfpl/ 5 * Licensed under MIT (http://opensource.org/licenses/MIT)
11 ;(function($){ 12 13 var plugin = {}; 14 15 var defaults = { 16 17 // GENERAL 18 mode: 'horizontal’, 19 slideSelector: '’, 20 infiniteLoop: true, 21 hideControlOnEnd: false, 22 speed: 500, 23 easing: null, 24 slideMargin: 0, 25 startSlide: 0, 26 randomStart: false, 27 captions: false, 28 ticker: false, 29 tickerHover: false, 30 adaptiveHeight: false, 31 adaptiveHeightSpeed: 500, 32 video: false, 33 useCSS: true, 34 preloadImages: 'visible’, 35 responsive: true, 36 37 // TOUCH 38 touchEnabled: true, 39 swipeThreshold: 50, 40 oneToOneTouch: true, 41 preventDefaultSwipeX: true, 42 preventDefaultSwipeY: false, 43 44 // PAGER 45 pager: true, 46 pagerType: 'full’, 47 pagerShortSeparator: ' / ', 48 pagerSelector: null, 49 buildPager: null, 50 pagerCustom: null, 51 52 // CONTROLS 53 controls: true, 54 nextText: 'Next’, 55 prevText: 'Prev’, 56 nextSelector: null, 57 prevSelector: null, 58 autoControls: false, 59 startText: 'Start’, 60 stopText: 'Stop’, 61 autoControlsCombine: false, 62 autoControlsSelector: null, 63 64 // AUTO 65 auto: false, 66 pause: 4000, 67 autoStart: true, 68 autoDirection: 'next’, 69 autoHover: false, 70 autoDelay: 0, 71 72 // CAROUSEL 73 minSlides: 1, 74 maxSlides: 1, 75 moveSlides: 0, 76 slideWidth: 0, 77 78 // CALLBACKS 79 onSliderLoad: function() {}, 80 onSlideBefore: function() {}, 81 onSlideAfter: function() {}, 82 onSlideNext: function() {}, 83 onSlidePrev: function() {} 84 } 85 86 $.fn.bxSlider = function(options){ 87 88 if(this.length == 0) return this; 89 90 // support mutltiple elements 91 if(this.length > 1){ 92 this.each(function(){$(this).bxSlider(options)}); 93 return this; 94 } 95 96 // create a namespace to be used throughout the plugin 97 var slider = {}; 98 // set a reference to our slider element 99 var el = this; 100 plugin.el = this; 101 102 /** 103 * Makes slideshow responsive 104 */ 105 // first get the original window dimens (thanks alot IE) 106 var windowWidth = $(window).width(); 107 var windowHeight = $(window).height(); 108 109 110 111 /** 112 * =================================================================================== 113 * = PRIVATE FUNCTIONS 114 * =================================================================================== 115 */ 116 117 /** 118 * Initializes namespace settings to be used throughout plugin 119 */ 120 var init = function(){ 121 // merge user-supplied options with the defaults 122 slider.settings = $.extend({}, defaults, options); 123 // parse slideWidth setting 124 slider.settings.slideWidth = parseInt(slider.settings.slideWidth); 125 // store the original children 126 slider.children = el.children(slider.settings.slideSelector); 127 // check if actual number of slides is less than minSlides / maxSlides 128 if(slider.children.length < slider.settings.minSlides) slider.settings.minSlides = slider.children.length; 129 if(slider.children.length < slider.settings.maxSlides) slider.settings.maxSlides = slider.children.length; 130 // if random start, set the startSlide setting to random number 131 if(slider.settings.randomStart) slider.settings.startSlide = Math.floor(Math.random() * slider.children.length); 132 // store active slide information 133 slider.active = { index: slider.settings.startSlide } 134 // store if the slider is in carousel mode (displaying / moving multiple slides) 135 slider.carousel = slider.settings.minSlides > 1 || slider.settings.maxSlides > 1; 136 // if carousel, force preloadImages = ‘all’ 137 if(slider.carousel) slider.settings.preloadImages = 'all’; 138 // calculate the min / max width thresholds based on min / max number of slides 139 // used to setup and update carousel slides dimensions 140 slider.minThreshold = (slider.settings.minSlides * slider.settings.slideWidth) + ((slider.settings.minSlides - 1) * slider.settings.slideMargin); 141 slider.maxThreshold = (slider.settings.maxSlides * slider.settings.slideWidth) + ((slider.settings.maxSlides - 1) * slider.settings.slideMargin); 142 // store the current state of the slider (if currently animating, working is true) 143 slider.working = false; 144 // initialize the controls object 145 slider.controls = {}; 146 // initialize an auto interval 147 slider.interval = null; 148 // determine which property to use for transitions 149 slider.animProp = slider.settings.mode == ‘vertical’ ? ‘top’ : 'left’; 150 // determine if hardware acceleration can be used 151 slider.usingCSS = slider.settings.useCSS && slider.settings.mode != ‘fade’ && (function(){ 152 // create our test div element 153 var div = document.createElement(‘div’); 154 // css transition properties 155 var props = ['WebkitPerspective’, 'MozPerspective’, 'OPerspective’, ‘msPerspective’]; 156 // test for each property 157 for(var i in props){ 158 if(div.style[props[i]] !== undefined){ 159 slider.cssPrefix = props[i].replace(‘Perspective’, ‘’).toLowerCase(); 160 slider.animProp = '-' + slider.cssPrefix + '-transform’; 161 return true; 162 } 163 } 164 return false; 165 }()); 166 // if vertical mode always make maxSlides and minSlides equal 167 if(slider.settings.mode == ‘vertical’) slider.settings.maxSlides = slider.settings.minSlides; 168 // save original style data 169 el.data("origStyle", el.attr(“style”)); 170 el.children(slider.settings.slideSelector).each(function() { 171 $(this).data(“origStyle", $(this).attr(“style”)); 172 }); 173 // perform all DOM / CSS modifications 174 setup(); 175 } 176 177 /** 178 * Performs all DOM and CSS modifications 179 */ 180 var setup = function(){ 181 // wrap el in a wrapper 182 el.wrap(‘<div class="bx-wrapper"><div class="bx-viewport"></div></div>’); 183 // store a namspace reference to .bx-viewport 184 slider.viewport = el.parent(); 185 // add a loading div to display while images are loading 186 slider.loader = $('<div class="bx-loading” />’); 187 slider.viewport.prepend(slider.loader); 188 // set el to a massive width, to hold any needed slides 189 // also strip any margin and padding from el 190 el.css({ 191 width: slider.settings.mode == ‘horizontal’ ? (slider.children.length * 100 + 215) + ‘%’ : 'auto’, 192 position: ‘relative’ 193 }); 194 // if using CSS, add the easing property 195 if(slider.usingCSS && slider.settings.easing){ 196 el.css('-' + slider.cssPrefix + '-transition-timing-function’, slider.settings.easing); 197 // if not using CSS and no easing value was supplied, use the default JS animation easing (swing) 198 }else if(!slider.settings.easing){ 199 slider.settings.easing = 'swing’; 200 } 201 var slidesShowing = getNumberSlidesShowing(); 202 // make modifications to the viewport (.bx-viewport) 203 slider.viewport.css({ 204 width: '100%’, 205 overflow: 'hidden’, 206 position: ‘relative’ 207 }); 208 slider.viewport.parent().css({ 209 maxWidth: getViewportMaxWidth() 210 }); 211 // make modification to the wrapper (.bx-wrapper) 212 if(!slider.settings.pager) { 213 slider.viewport.parent().css({ 214 margin: ‘0 auto 0px’ 215 }); 216 } 217 // apply css to all slider children 218 slider.children.css({ 219 'float’: slider.settings.mode == ‘horizontal’ ? ‘left’ : 'none’, 220 listStyle: 'none’, 221 position: ‘relative’ 222 }); 223 // apply the calculated width after the float is applied to prevent scrollbar interference 224 slider.children.css('width’, getSlideWidth()); 225 // if slideMargin is supplied, add the css 226 if(slider.settings.mode == ‘horizontal’ && slider.settings.slideMargin > 0) slider.children.css('marginRight’, slider.settings.slideMargin); 227 if(slider.settings.mode == ‘vertical’ && slider.settings.slideMargin > 0) slider.children.css('marginBottom’, slider.settings.slideMargin); 228 // if “fade” mode, add positioning and z-index CSS 229 if(slider.settings.mode == ‘fade’){ 230 slider.children.css({ 231 position: 'absolute’, 232 zIndex: 0, 233 display: ‘none’ 234 }); 235 // prepare the z-index on the showing element 236 slider.children.eq(slider.settings.startSlide).css({zIndex: 50, display: 'block’}); 237 } 238 // create an element to contain all slider controls (pager, start / stop, etc) 239 slider.controls.el = $(‘<div class="bx-controls" />’); 240 // if captions are requested, add them 241 if(slider.settings.captions) appendCaptions(); 242 // check if startSlide is last slide 243 slider.active.last = slider.settings.startSlide == getPagerQty() - 1; 244 // if video is true, set up the fitVids plugin 245 if(slider.settings.video) el.fitVids(); 246 // set the default preload selector (visible) 247 var preloadSelector = slider.children.eq(slider.settings.startSlide); 248 if (slider.settings.preloadImages == “all”) preloadSelector = slider.children; 249 // only check for control addition if not in “ticker” mode 250 if(!slider.settings.ticker){ 251 // if pager is requested, add it 252 if(slider.settings.pager) appendPager(); 253 // if controls are requested, add them 254 if(slider.settings.controls) appendControls(); 255 // if auto is true, and auto controls are requested, add them 256 if(slider.settings.auto && slider.settings.autoControls) appendControlsAuto(); 257 // if any control option is requested, add the controls wrapper 258 if(slider.settings.controls || slider.settings.autoControls || slider.settings.pager) slider.viewport.after(slider.controls.el); 259 // if ticker mode, do not allow a pager 260 }else{ 261 slider.settings.pager = false; 262 } 263 // preload all images, then perform final DOM / CSS modifications that depend on images being loaded 264 loadElements(preloadSelector, start); 265 } 266 267 var loadElements = function(selector, callback){ 268 var total = selector.find(‘img, iframe’).length; 269 if (total == 0){ 270 callback(); 271 return; 272 } 273 var count = 0; 274 selector.find(‘img, iframe’).each(function(){ 275 if($(this).is(‘img’)) $(this).attr('src’, $(this).attr(‘src’) + ‘?timestamp=’ + new Date().getTime()); 276 $(this).load(function(){ 277 setTimeout(function(){ 278 if(++count == total) callback(); 279 }, 0) 280 }); 281 }); 282 } 283 284 /** 285 * Start the slider 286 */ 287 var start = function(){ 288 // if infinite loop, prepare additional slides 289 if(slider.settings.infiniteLoop && slider.settings.mode != ‘fade’ && !slider.settings.ticker){ 290 var slice = slider.settings.mode == ‘vertical’ ? slider.settings.minSlides : slider.settings.maxSlides; 291 var sliceAppend = slider.children.slice(0, slice).clone().addClass(‘bx-clone’); 292 var slicePrepend = slider.children.slice(-slice).clone().addClass(‘bx-clone’); 293 el.append(sliceAppend).prepend(slicePrepend); 294 } 295 // remove the loading DOM element 296 slider.loader.remove(); 297 // set the left / top position of “el” 298 setSlidePosition(); 299 // if “vertical” mode, always use adaptiveHeight to prevent odd behavior 300 if (slider.settings.mode == ‘vertical’) slider.settings.adaptiveHeight = true; 301 // set the viewport height 302 slider.viewport.height(getViewportHeight()); 303 // make sure everything is positioned just right (same as a window resize) 304 el.redrawSlider(); 305 // onSliderLoad callback 306 slider.settings.onSliderLoad(slider.active.index); 307 // slider has been fully initialized 308 slider.initialized = true; 309 // bind the resize call to the window 310 if (slider.settings.responsive) $(window).bind('resize’, resizeWindow); 311 // if auto is true, start the show 312 if (slider.settings.auto && slider.settings.autoStart) initAuto(); 313 // if ticker is true, start the ticker 314 if (slider.settings.ticker) initTicker(); 315 // if pager is requested, make the appropriate pager link active 316 if (slider.settings.pager) updatePagerActive(slider.settings.startSlide); 317 // check for any updates to the controls (like hideControlOnEnd updates) 318 if (slider.settings.controls) updateDirectionControls(); 319 // if touchEnabled is true, setup the touch events 320 if (slider.settings.touchEnabled && !slider.settings.ticker) initTouch(); 321 } 322 323 /** 324 * Returns the calculated height of the viewport, used to determine either adaptiveHeight or the maxHeight value 325 */ 326 var getViewportHeight = function(){ 327 var height = 0; 328 // first determine which children (slides) should be used in our height calculation 329 var children = $(); 330 // if mode is not “vertical” and adaptiveHeight is false, include all children 331 if(slider.settings.mode != ‘vertical’ && !slider.settings.adaptiveHeight){ 332 children = slider.children; 333 }else{ 334 // if not carousel, return the single active child 335 if(!slider.carousel){ 336 children = slider.children.eq(slider.active.index); 337 // if carousel, return a slice of children 338 }else{ 339 // get the individual slide index 340 var currentIndex = slider.settings.moveSlides == 1 ? slider.active.index : slider.active.index * getMoveBy(); 341 // add the current slide to the children 342 children = slider.children.eq(currentIndex); 343 // cycle through the remaining “showing” slides 344 for (i = 1; i <= slider.settings.maxSlides - 1; i++){ 345 // if looped back to the start 346 if(currentIndex + i >= slider.children.length){ 347 children = children.add(slider.children.eq(i - 1)); 348 }else{ 349 children = children.add(slider.children.eq(currentIndex + i)); 350 } 351 } 352 } 353 } 354 // if “vertical” mode, calculate the sum of the heights of the children 355 if(slider.settings.mode == ‘vertical’){ 356 children.each(function(index) { 357 height += $(this).outerHeight(); 358 }); 359 // add user-supplied margins 360 if(slider.settings.slideMargin > 0){ 361 height += slider.settings.slideMargin * (slider.settings.minSlides - 1); 362 } 363 // if not “vertical” mode, calculate the max height of the children 364 }else{ 365 height = Math.max.apply(Math, children.map(function(){ 366 return $(this).outerHeight(false); 367 }).get()); 368 } 369 return height; 370 } 371 372 /** 373 * Returns the calculated width to be used for the outer wrapper / viewport 374 */ 375 var getViewportMaxWidth = function(){ 376 var width = '100%’; 377 if(slider.settings.slideWidth > 0){ 378 if(slider.settings.mode == ‘horizontal’){ 379 width = (slider.settings.maxSlides * slider.settings.slideWidth) + ((slider.settings.maxSlides - 1) * slider.settings.slideMargin); 380 }else{ 381 width = slider.settings.slideWidth; 382 } 383 } 384 return width; 385 } 386 387 /** 388 * Returns the calculated width to be applied to each slide 389 */ 390 var getSlideWidth = function(){ 391 // start with any user-supplied slide width 392 var newElWidth = slider.settings.slideWidth; 393 // get the current viewport width 394 var wrapWidth = slider.viewport.width(); 395 // if slide width was not supplied, or is larger than the viewport use the viewport width 396 if(slider.settings.slideWidth == 0 || 397 (slider.settings.slideWidth > wrapWidth && !slider.carousel) || 398 slider.settings.mode == ‘vertical’){ 399 newElWidth = wrapWidth; 400 // if carousel, use the thresholds to determine the width 401 }else if(slider.settings.maxSlides > 1 && slider.settings.mode == ‘horizontal’){ 402 if(wrapWidth > slider.maxThreshold){ 403 // newElWidth = (wrapWidth - (slider.settings.slideMargin * (slider.settings.maxSlides - 1))) / slider.settings.maxSlides; 404 }else if(wrapWidth < slider.minThreshold){ 405 newElWidth = (wrapWidth - (slider.settings.slideMargin * (slider.settings.minSlides - 1))) / slider.settings.minSlides; 406 } 407 } 408 return newElWidth; 409 } 410 411 /** 412 * Returns the number of slides currently visible in the viewport (includes partially visible slides) 413 */ 414 var getNumberSlidesShowing = function(){ 415 var slidesShowing = 1; 416 if(slider.settings.mode == ‘horizontal’ && slider.settings.slideWidth > 0){ 417 // if viewport is smaller than minThreshold, return minSlides 418 if(slider.viewport.width() < slider.minThreshold){ 419 slidesShowing = slider.settings.minSlides; 420 // if viewport is larger than minThreshold, return maxSlides 421 }else if(slider.viewport.width() > slider.maxThreshold){ 422 slidesShowing = slider.settings.maxSlides; 423 // if viewport is between min / max thresholds, divide viewport width by first child width 424 }else{ 425 var childWidth = slider.children.first().width(); 426 slidesShowing = Math.floor(slider.viewport.width() / childWidth); 427 } 428 // if “vertical” mode, slides showing will always be minSlides 429 }else if(slider.settings.mode == ‘vertical’){ 430 slidesShowing = slider.settings.minSlides; 431 } 432 return slidesShowing; 433 } 434 435 /** 436 * Returns the number of pages (one full viewport of slides is one “page”) 437 */ 438 var getPagerQty = function(){ 439 var pagerQty = 0; 440 // if moveSlides is specified by the user 441 if(slider.settings.moveSlides > 0){ 442 if(slider.settings.infiniteLoop){ 443 pagerQty = slider.children.length / getMoveBy(); 444 }else{ 445 // use a while loop to determine pages 446 var breakPoint = 0; 447 var counter = 0 448 // when breakpoint goes above children length, counter is the number of pages 449 while (breakPoint < slider.children.length){ 450 ++pagerQty; 451 breakPoint = counter + getNumberSlidesShowing(); 452 counter += slider.settings.moveSlides <= getNumberSlidesShowing() ? slider.settings.moveSlides : getNumberSlidesShowing(); 453 } 454 } 455 // if moveSlides is 0 (auto) divide children length by sides showing, then round up 456 }else{ 457 pagerQty = Math.ceil(slider.children.length / getNumberSlidesShowing()); 458 } 459 return pagerQty; 460 } 461 462 /** 463 * Returns the number of indivual slides by which to shift the slider 464 */ 465 var getMoveBy = function(){ 466 // if moveSlides was set by the user and moveSlides is less than number of slides showing 467 if(slider.settings.moveSlides > 0 && slider.settings.moveSlides <= getNumberSlidesShowing()){ 468 return slider.settings.moveSlides; 469 } 470 // if moveSlides is 0 (auto) 471 return getNumberSlidesShowing(); 472 } 473 474 /** 475 * Sets the slider’s (el) left or top position 476 */ 477 var setSlidePosition = function(){ 478 // if last slide, not infinite loop, and number of children is larger than specified maxSlides 479 if(slider.children.length > slider.settings.maxSlides && slider.active.last && !slider.settings.infiniteLoop){ 480 if (slider.settings.mode == ‘horizontal’){ 481 // get the last child’s position 482 var lastChild = slider.children.last(); 483 var position = lastChild.position(); 484 // set the left position 485 setPositionProperty(-(position.left - (slider.viewport.width() - lastChild.width())), 'reset’, 0); 486 }else if(slider.settings.mode == ‘vertical’){ 487 // get the last showing index’s position 488 var lastShowingIndex = slider.children.length - slider.settings.minSlides; 489 var position = slider.children.eq(lastShowingIndex).position(); 490 // set the top position 491 setPositionProperty(-position.top, 'reset’, 0); 492 } 493 // if not last slide 494 }else{ 495 // get the position of the first showing slide 496 var position = slider.children.eq(slider.active.index * getMoveBy()).position(); 497 // check for last slide 498 if (slider.active.index == getPagerQty() - 1) slider.active.last = true; 499 // set the repective position 500 if (position != undefined){ 501 if (slider.settings.mode == ‘horizontal’) setPositionProperty(-position.left, 'reset’, 0); 502 else if (slider.settings.mode == ‘vertical’) setPositionProperty(-position.top, 'reset’, 0); 503 } 504 } 505 } 506 507 /** 508 * Sets the el’s animating property position (which in turn will sometimes animate el). 509 * If using CSS, sets the transform property. If not using CSS, sets the top / left property. 510 * 511 * @param value (int) 512 * - the animating property’s value 513 * 514 * @param type (string) 'slider’, 'reset’, ‘ticker’ 515 * - the type of instance for which the function is being 516 * 517 * @param duration (int) 518 * - the amount of time (in ms) the transition should occupy 519 * 520 * @param params (array) optional 521 * - an optional parameter containing any variables that need to be passed in 522 */ 523 var setPositionProperty = function(value, type, duration, params){ 524 // use CSS transform 525 if(slider.usingCSS){ 526 // determine the translate3d value 527 var propValue = slider.settings.mode == ‘vertical’ ? 'translate3d(0, ' + value + 'px, 0)' : 'translate3d(' + value + 'px, 0, 0)'; 528 // add the CSS transition-duration 529 el.css('-' + slider.cssPrefix + '-transition-duration’, duration / 1000 + ‘s’); 530 if(type == ‘slide’){ 531 // set the property value 532 el.css(slider.animProp, propValue); 533 // bind a callback method - executes when CSS transition completes 534 el.bind('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’, function(){ 535 // unbind the callback 536 el.unbind(‘transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’); 537 updateAfterSlideTransition(); 538 }); 539 }else if(type == ‘reset’){ 540 el.css(slider.animProp, propValue); 541 }else if(type == ‘ticker’){ 542 // make the transition use ‘linear’ 543 el.css('-' + slider.cssPrefix + '-transition-timing-function’, ‘linear’); 544 el.css(slider.animProp, propValue); 545 // bind a callback method - executes when CSS transition completes 546 el.bind('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’, function(){ 547 // unbind the callback 548 el.unbind(‘transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’); 549 // reset the position 550 setPositionProperty(params[‘resetValue’], 'reset’, 0); 551 // start the loop again 552 tickerLoop(); 553 }); 554 } 555 // use JS animate 556 }else{ 557 var animateObj = {}; 558 animateObj[slider.animProp] = value; 559 if(type == ‘slide’){ 560 el.animate(animateObj, duration, slider.settings.easing, function(){ 561 updateAfterSlideTransition(); 562 }); 563 }else if(type == ‘reset’){ 564 el.css(slider.animProp, value) 565 }else if(type == ‘ticker’){ 566 el.animate(animateObj, speed, 'linear’, function(){ 567 setPositionProperty(params[‘resetValue’], 'reset’, 0); 568 // run the recursive loop after animation 569 tickerLoop(); 570 }); 571 } 572 } 573 } 574 575 /** 576 * Populates the pager with proper amount of pages 577 */ 578 var populatePager = function(){ 579 var pagerHtml = '’; 580 var pagerQty = getPagerQty(); 581 // loop through each pager item 582 for(var i=0; i < pagerQty; i++){ 583 var linkContent = '’; 584 // if a buildPager function is supplied, use it to get pager link value, else use index + 1 585 if(slider.settings.buildPager && $.isFunction(slider.settings.buildPager)){ 586 linkContent = slider.settings.buildPager(i); 587 slider.pagerEl.addClass(‘bx-custom-pager’); 588 }else{ 589 linkContent = i + 1; 590 slider.pagerEl.addClass(‘bx-default-pager’); 591 } 592 // var linkContent = slider.settings.buildPager && $.isFunction(slider.settings.buildPager) ? slider.settings.buildPager(i) : i + 1; 593 // add the markup to the string 594 pagerHtml += ‘<div class="bx-pager-item"><a href="" data-slide-index="’ + i + ‘" class="bx-pager-link">’ + linkContent + '</a></div>’; 595 }; 596 // populate the pager element with pager links 597 slider.pagerEl.html(pagerHtml); 598 } 599 600 /** 601 * Appends the pager to the controls element 602 */ 603 var appendPager = function(){ 604 if(!slider.settings.pagerCustom){ 605 // create the pager DOM element 606 slider.pagerEl = $(‘<div class="bx-pager" />’); 607 // if a pager selector was supplied, populate it with the pager 608 if(slider.settings.pagerSelector){ 609 $(slider.settings.pagerSelector).html(slider.pagerEl); 610 // if no pager selector was supplied, add it after the wrapper 611 }else{ 612 slider.controls.el.addClass(‘bx-has-pager’).append(slider.pagerEl); 613 } 614 // populate the pager 615 populatePager(); 616 }else{ 617 slider.pagerEl = $(slider.settings.pagerCustom); 618 } 619 // assign the pager click binding 620 slider.pagerEl.delegate('a’, 'click’, clickPagerBind); 621 } 622 623 /** 624 * Appends prev / next controls to the controls element 625 */ 626 var appendControls = function(){ 627 slider.controls.next = $(‘<a class="bx-next" href="">’ + slider.settings.nextText + ‘</a>’); 628 slider.controls.prev = $(‘<a class="bx-prev" href="">’ + slider.settings.prevText + ‘</a>’); 629 // bind click actions to the controls 630 slider.controls.next.bind('click’, clickNextBind); 631 slider.controls.prev.bind('click’, clickPrevBind); 632 // if nextSlector was supplied, populate it 633 if(slider.settings.nextSelector){ 634 $(slider.settings.nextSelector).append(slider.controls.next); 635 } 636 // if prevSlector was supplied, populate it 637 if(slider.settings.prevSelector){ 638 $(slider.settings.prevSelector).append(slider.controls.prev); 639 } 640 // if no custom selectors were supplied 641 if(!slider.settings.nextSelector && !slider.settings.prevSelector){ 642 // add the controls to the DOM 643 slider.controls.directionEl = $(‘<div class="bx-controls-direction" />’); 644 // add the control elements to the directionEl 645 slider.controls.directionEl.append(slider.controls.prev).append(slider.controls.next); 646 // slider.viewport.append(slider.controls.directionEl); 647 slider.controls.el.addClass(‘bx-has-controls-direction’).append(slider.controls.directionEl); 648 } 649 } 650 651 /** 652 * Appends start / stop auto controls to the controls element 653 */ 654 var appendControlsAuto = function(){ 655 slider.controls.start = $(‘<div class="bx-controls-auto-item"><a class="bx-start" href="">’ + slider.settings.startText + ‘</a></div>’); 656 slider.controls.stop = $(‘<div class="bx-controls-auto-item"><a class="bx-stop" href="">’ + slider.settings.stopText + ‘</a></div>’); 657 // add the controls to the DOM 658 slider.controls.autoEl = $(‘<div class="bx-controls-auto" />’); 659 // bind click actions to the controls 660 slider.controls.autoEl.delegate('.bx-start’, 'click’, clickStartBind); 661 slider.controls.autoEl.delegate('.bx-stop’, 'click’, clickStopBind); 662 // if autoControlsCombine, insert only the “start” control 663 if(slider.settings.autoControlsCombine){ 664 slider.controls.autoEl.append(slider.controls.start); 665 // if autoControlsCombine is false, insert both controls 666 }else{ 667 slider.controls.autoEl.append(slider.controls.start).append(slider.controls.stop); 668 } 669 // if auto controls selector was supplied, populate it with the controls 670 if(slider.settings.autoControlsSelector){ 671 $(slider.settings.autoControlsSelector).html(slider.controls.autoEl); 672 // if auto controls selector was not supplied, add it after the wrapper 673 }else{ 674 slider.controls.el.addClass(‘bx-has-controls-auto’).append(slider.controls.autoEl); 675 } 676 // update the auto controls 677 updateAutoControls(slider.settings.autoStart ? ‘stop’ : ‘start’); 678 } 679 680 /** 681 * Appends image captions to the DOM 682 */ 683 var appendCaptions = function(){ 684 // cycle through each child 685 slider.children.each(function(index){ 686 // get the image title attribute 687 var title = $(this).find(‘img:first’).attr(‘alt’); 688 // append the caption 689 if (title != undefined && (‘’ + title).length) { 690 $(this).append(‘<div class="bx-caption"><span>’ + title + ‘</span></div>’); 691 } 692 }); 693 } 694 695 /** 696 * Click next binding 697 * 698 * @param e (event) 699 * - DOM event object 700 */ 701 var clickNextBind = function(e){ 702 // if auto show is running, stop it 703 if (slider.settings.auto) el.stopAuto(); 704 el.goToNextSlide(); 705 e.preventDefault(); 706 } 707 708 /** 709 * Click prev binding 710 * 711 * @param e (event) 712 * - DOM event object 713 */ 714 var clickPrevBind = function(e){ 715 // if auto show is running, stop it 716 if (slider.settings.auto) el.stopAuto(); 717 el.goToPrevSlide(); 718 e.preventDefault(); 719 } 720 721 /** 722 * Click start binding 723 * 724 * @param e (event) 725 * - DOM event object 726 */ 727 var clickStartBind = function(e){ 728 el.startAuto(); 729 e.preventDefault(); 730 } 731 732 /** 733 * Click stop binding 734 * 735 * @param e (event) 736 * - DOM event object 737 */ 738 var clickStopBind = function(e){ 739 el.stopAuto(); 740 e.preventDefault(); 741 } 742 743 /** 744 * Click pager binding 745 * 746 * @param e (event) 747 * - DOM event object 748 */ 749 var clickPagerBind = function(e){ 750 // if auto show is running, stop it 751 if (slider.settings.auto) el.stopAuto(); 752 var pagerLink = $(e.currentTarget); 753 var pagerIndex = parseInt(pagerLink.attr(‘data-slide-index’)); 754 // if clicked pager link is not active, continue with the goToSlide call 755 if(pagerIndex != slider.active.index) el.goToSlide(pagerIndex); 756 e.preventDefault(); 757 } 758 759 /** 760 * Updates the pager links with an active class 761 * 762 * @param slideIndex (int) 763 * - index of slide to make active 764 */ 765 var updatePagerActive = function(slideIndex){ 766 // if “short” pager type 767 var len = slider.children.length; // nb of children 768 if(slider.settings.pagerType == ‘short’){ 769 if(slider.settings.maxSlides > 1) { 770 len = Math.ceil(slider.children.length/slider.settings.maxSlides); 771 } 772 slider.pagerEl.html( (slideIndex + 1) + slider.settings.pagerShortSeparator + len); 773 return; 774 } 775 // remove all pager active classes 776 slider.pagerEl.find(‘a’).removeClass(‘active’); 777 // apply the active class for all pagers 778 slider.pagerEl.each(function(i, el) { $(el).find(‘a’).eq(slideIndex).addClass(‘active’); }); 779 } 780 781 /** 782 * Performs needed actions after a slide transition 783 */ 784 var updateAfterSlideTransition = function(){ 785 // if infinte loop is true 786 if(slider.settings.infiniteLoop){ 787 var position = '’; 788 // first slide 789 if(slider.active.index == 0){ 790 // set the new position 791 position = slider.children.eq(0).position(); 792 // carousel, last slide 793 }else if(slider.active.index == getPagerQty() - 1 && slider.carousel){ 794 position = slider.children.eq((getPagerQty() - 1) * getMoveBy()).position(); 795 // last slide 796 }else if(slider.active.index == slider.children.length - 1){ 797 position = slider.children.eq(slider.children.length - 1).position(); 798 } 799 if (slider.settings.mode == ‘horizontal’) { setPositionProperty(-position.left, 'reset’, 0);; } 800 else if (slider.settings.mode == ‘vertical’) { setPositionProperty(-position.top, ‘reset’, 0);; } 801 } 802 // declare that the transition is complete 803 slider.working = false; 804 // onSlideAfter callback 805 slider.settings.onSlideAfter(slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index); 806 } 807 808 /** 809 * Updates the auto controls state (either active, or combined switch) 810 * 811 * @param state (string) “start", “stop” 812 * - the new state of the auto show 813 */ 814 var updateAutoControls = function(state){ 815 // if autoControlsCombine is true, replace the current control with the new state 816 if(slider.settings.autoControlsCombine){ 817 slider.controls.autoEl.html(slider.controls[state]); 818 // if autoControlsCombine is false, apply the “active” class to the appropriate control 819 }else{ 820 slider.controls.autoEl.find(‘a’).removeClass(‘active’); 821 slider.controls.autoEl.find(‘a:not(.bx-' + state + ')').addClass(‘active’); 822 } 823 } 824 825 /** 826 * Updates the direction controls (checks if either should be hidden) 827 */ 828 var updateDirectionControls = function(){ 829 if(getPagerQty() == 1){ 830 slider.controls.prev.addClass(‘disabled’); 831 slider.controls.next.addClass(‘disabled’); 832 }else if(!slider.settings.infiniteLoop && slider.settings.hideControlOnEnd){ 833 // if first slide 834 if (slider.active.index == 0){ 835 slider.controls.prev.addClass(‘disabled’); 836 slider.controls.next.removeClass(‘disabled’); 837 // if last slide 838 }else if(slider.active.index == getPagerQty() - 1){ 839 slider.controls.next.addClass(‘disabled’); 840 slider.controls.prev.removeClass(‘disabled’); 841 // if any slide in the middle 842 }else{ 843 slider.controls.prev.removeClass(‘disabled’); 844 slider.controls.next.removeClass(‘disabled’); 845 } 846 } 847 } 848 849 /** 850 * Initialzes the auto process 851 */ 852 var initAuto = function(){ 853 // if autoDelay was supplied, launch the auto show using a setTimeout() call 854 if(slider.settings.autoDelay > 0){ 855 var timeout = setTimeout(el.startAuto, slider.settings.autoDelay); 856 // if autoDelay was not supplied, start the auto show normally 857 }else{ 858 el.startAuto(); 859 } 860 // if autoHover is requested 861 if(slider.settings.autoHover){ 862 // on el hover 863 el.hover(function(){ 864 // if the auto show is currently playing (has an active interval) 865 if(slider.interval){ 866 // stop the auto show and pass true agument which will prevent control update 867 el.stopAuto(true); 868 // create a new autoPaused value which will be used by the relative “mouseout” event 869 slider.autoPaused = true; 870 } 871 }, function(){ 872 // if the autoPaused value was created be the prior “mouseover” event 873 if(slider.autoPaused){ 874 // start the auto show and pass true agument which will prevent control update 875 el.startAuto(true); 876 // reset the autoPaused value 877 slider.autoPaused = null; 878 } 879 }); 880 } 881 } 882 883 /** 884 * Initialzes the ticker process 885 */ 886 var initTicker = function(){ 887 var startPosition = 0; 888 // if autoDirection is "next", append a clone of the entire slider 889 if(slider.settings.autoDirection == ‘next’){ 890 el.append(slider.children.clone().addClass(‘bx-clone’)); 891 // if autoDirection is “prev", prepend a clone of the entire slider, and set the left position 892 }else{ 893 el.prepend(slider.children.clone().addClass(‘bx-clone’)); 894 var position = slider.children.first().position(); 895 startPosition = slider.settings.mode == ‘horizontal’ ? -position.left : -position.top; 896 } 897 setPositionProperty(startPosition, 'reset’, 0); 898 // do not allow controls in ticker mode 899 slider.settings.pager = false; 900 slider.settings.controls = false; 901 slider.settings.autoControls = false; 902 // if autoHover is requested 903 if(slider.settings.tickerHover && !slider.usingCSS){ 904 // on el hover 905 slider.viewport.hover(function(){ 906 el.stop(); 907 }, function(){ 908 // calculate the total width of children (used to calculate the speed ratio) 909 var totalDimens = 0; 910 slider.children.each(function(index){ 911 totalDimens += slider.settings.mode == ‘horizontal’ ? $(this).outerWidth(true) : $(this).outerHeight(true); 912 }); 913 // calculate the speed ratio (used to determine the new speed to finish the paused animation) 914 var ratio = slider.settings.speed / totalDimens; 915 // determine which property to use 916 var property = slider.settings.mode == ‘horizontal’ ? ‘left’ : 'top’; 917 // calculate the new speed 918 var newSpeed = ratio * (totalDimens - (Math.abs(parseInt(el.css(property))))); 919 tickerLoop(newSpeed); 920 }); 921 } 922 // start the ticker loop 923 tickerLoop(); 924 } 925 926 /** 927 * Runs a continuous loop, news ticker-style 928 */ 929 var tickerLoop = function(resumeSpeed){ 930 speed = resumeSpeed ? resumeSpeed : slider.settings.speed; 931 var position = {left: 0, top: 0}; 932 var reset = {left: 0, top: 0}; 933 // if “next” animate left position to last child, then reset left to 0 934 if(slider.settings.autoDirection == ‘next’){ 935 position = el.find(‘.bx-clone’).first().position(); 936 // if “prev” animate left position to 0, then reset left to first non-clone child 937 }else{ 938 reset = slider.children.first().position(); 939 } 940 var animateProperty = slider.settings.mode == ‘horizontal’ ? -position.left : -position.top; 941 var resetValue = slider.settings.mode == ‘horizontal’ ? -reset.left : -reset.top; 942 var params = {resetValue: resetValue}; 943 setPositionProperty(animateProperty, 'ticker’, speed, params); 944 } 945 946 /** 947 * Initializes touch events 948 */ 949 var initTouch = function(){ 950 // initialize object to contain all touch values 951 slider.touch = { 952 start: {x: 0, y: 0}, 953 end: {x: 0, y: 0} 954 } 955 slider.viewport.bind('touchstart’, onTouchStart); 956 } 957 958 /** 959 * Event handler for “touchstart” 960 * 961 * @param e (event) 962 * - DOM event object 963 */ 964 var onTouchStart = function(e){ 965 if(slider.working){ 966 e.preventDefault(); 967 }else{ 968 // record the original position when touch starts 969 slider.touch.originalPos = el.position(); 970 var orig = e.originalEvent; 971 // record the starting touch x, y coordinates 972 slider.touch.start.x = orig.changedTouches[0].pageX; 973 slider.touch.start.y = orig.changedTouches[0].pageY; 974 // bind a “touchmove” event to the viewport 975 slider.viewport.bind('touchmove’, onTouchMove); 976 // bind a “touchend” event to the viewport 977 slider.viewport.bind('touchend’, onTouchEnd); 978 } 979 } 980 981 /** 982 * Event handler for “touchmove” 983 * 984 * @param e (event) 985 * - DOM event object 986 */ 987 var onTouchMove = function(e){ 988 var orig = e.originalEvent; 989 // if scrolling on y axis, do not prevent default 990 var xMovement = Math.abs(orig.changedTouches[0].pageX - slider.touch.start.x); 991 var yMovement = Math.abs(orig.changedTouches[0].pageY - slider.touch.start.y); 992 // x axis swipe 993 if((xMovement * 3) > yMovement && slider.settings.preventDefaultSwipeX){ 994 e.preventDefault(); 995 // y axis swipe 996 }else if((yMovement * 3) > xMovement && slider.settings.preventDefaultSwipeY){ 997 e.preventDefault(); 998 } 999 if(slider.settings.mode != ‘fade’ && slider.settings.oneToOneTouch){ 1000 var value = 0; 1001 // if horizontal, drag along x axis 1002 if(slider.settings.mode == ‘horizontal’){ 1003 var change = orig.changedTouches[0].pageX - slider.touch.start.x; 1004 value = slider.touch.originalPos.left + change; 1005 // if vertical, drag along y axis 1006 }else{ 1007 var change = orig.changedTouches[0].pageY - slider.touch.start.y; 1008 value = slider.touch.originalPos.top + change; 1009 } 1010 setPositionProperty(value, 'reset’, 0); 1011 } 1012 } 1013 1014 /** 1015 * Event handler for “touchend” 1016 * 1017 * @param e (event) 1018 * - DOM event object 1019 */ 1020 var onTouchEnd = function(e){ 1021 slider.viewport.unbind('touchmove’, onTouchMove); 1022 var orig = e.originalEvent; 1023 var value = 0; 1024 // record end x, y positions 1025 slider.touch.end.x = orig.changedTouches[0].pageX; 1026 slider.touch.end.y = orig.changedTouches[0].pageY; 1027 // if fade mode, check if absolute x distance clears the threshold 1028 if(slider.settings.mode == ‘fade’){ 1029 var distance = Math.abs(slider.touch.start.x - slider.touch.end.x); 1030 if(distance >= slider.settings.swipeThreshold){ 1031 slider.touch.start.x > slider.touch.end.x ? el.goToNextSlide() : el.goToPrevSlide(); 1032 el.stopAuto(); 1033 } 1034 // not fade mode 1035 }else{ 1036 var distance = 0; 1037 // calculate distance and el’s animate property 1038 if(slider.settings.mode == ‘horizontal’){ 1039 distance = slider.touch.end.x - slider.touch.start.x; 1040 value = slider.touch.originalPos.left; 1041 }else{ 1042 distance = slider.touch.end.y - slider.touch.start.y; 1043 value = slider.touch.originalPos.top; 1044 } 1045 // if not infinite loop and first / last slide, do not attempt a slide transition 1046 if(!slider.settings.infiniteLoop && ((slider.active.index == 0 && distance > 0) || (slider.active.last && distance < 0))){ 1047 setPositionProperty(value, 'reset’, 200); 1048 }else{ 1049 // check if distance clears threshold 1050 if(Math.abs(distance) >= slider.settings.swipeThreshold){ 1051 distance < 0 ? el.goToNextSlide() : el.goToPrevSlide(); 1052 el.stopAuto(); 1053 }else{ 1054 // el.animate(property, 200); 1055 setPositionProperty(value, 'reset’, 200); 1056 } 1057 } 1058 } 1059 slider.viewport.unbind('touchend’, onTouchEnd); 1060 } 1061 1062 /** 1063 * Window resize event callback 1064 */ 1065 var resizeWindow = function(e){ 1066 // get the new window dimens (again, thank you IE) 1067 var windowWidthNew = $(window).width(); 1068 var windowHeightNew = $(window).height(); 1069 // make sure that it is a true window resize 1070 // *we must check this because our dinosaur friend IE fires a window resize event when certain DOM elements 1071 // are resized. Can you just die already?* 1072 if(windowWidth != windowWidthNew || windowHeight != windowHeightNew){ 1073 // set the new window dimens 1074 windowWidth = windowWidthNew; 1075 windowHeight = windowHeightNew; 1076 // update all dynamic elements 1077 el.redrawSlider(); 1078 } 1079 } 1080 1081 /** 1082 * =================================================================================== 1083 * = PUBLIC FUNCTIONS 1084 * =================================================================================== 1085 */ 1086 1087 /** 1088 * Performs slide transition to the specified slide 1089 * 1090 * @param slideIndex (int) 1091 * - the destination slide’s index (zero-based) 1092 * 1093 * @param direction (string) 1094 * - INTERNAL USE ONLY - the direction of travel (“prev” / “next”) 1095 */ 1096 el.goToSlide = function(slideIndex, direction){ 1097 // if plugin is currently in motion, ignore request 1098 if(slider.working || slider.active.index == slideIndex) return; 1099 // declare that plugin is in motion 1100 slider.working = true; 1101 // store the old index 1102 slider.oldIndex = slider.active.index; 1103 // if slideIndex is less than zero, set active index to last child (this happens during infinite loop) 1104 if(slideIndex < 0){ 1105 slider.active.index = getPagerQty() - 1; 1106 // if slideIndex is greater than children length, set active index to 0 (this happens during infinite loop) 1107 }else if(slideIndex >= getPagerQty()){ 1108 slider.active.index = 0; 1109 // set active index to requested slide 1110 }else{ 1111 slider.active.index = slideIndex; 1112 } 1113 // onSlideBefore, onSlideNext, onSlidePrev callbacks 1114 slider.settings.onSlideBefore(slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index); 1115 if(direction == ‘next’){ 1116 slider.settings.onSlideNext(slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index); 1117 }else if(direction == ‘prev’){ 1118 slider.settings.onSlidePrev(slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index); 1119 } 1120 // check if last slide 1121 slider.active.last = slider.active.index >= getPagerQty() - 1; 1122 // update the pager with active class 1123 if(slider.settings.pager) updatePagerActive(slider.active.index); 1124 // // check for direction control update 1125 if(slider.settings.controls) updateDirectionControls(); 1126 // if slider is set to mode: “fade” 1127 if(slider.settings.mode == ‘fade’){ 1128 // if adaptiveHeight is true and next height is different from current height, animate to the new height 1129 if(slider.settings.adaptiveHeight && slider.viewport.height() != getViewportHeight()){ 1130 slider.viewport.animate({height: getViewportHeight()}, slider.settings.adaptiveHeightSpeed); 1131 } 1132 // fade out the visible child and reset its z-index value 1133 slider.children.filter(‘:visible’).fadeOut(slider.settings.speed).css({zIndex: 0}); 1134 // fade in the newly requested slide 1135 slider.children.eq(slider.active.index).css('zIndex’, 51).fadeIn(slider.settings.speed, function(){ 1136 $(this).css('zIndex’, 50); 1137 updateAfterSlideTransition(); 1138 }); 1139 // slider mode is not “fade” 1140 }else{ 1141 // if adaptiveHeight is true and next height is different from current height, animate to the new height 1142 if(slider.settings.adaptiveHeight && slider.viewport.height() != getViewportHeight()){ 1143 slider.viewport.animate({height: getViewportHeight()}, slider.settings.adaptiveHeightSpeed); 1144 } 1145 var moveBy = 0; 1146 var position = {left: 0, top: 0}; 1147 // if carousel and not infinite loop 1148 if(!slider.settings.infiniteLoop && slider.carousel && slider.active.last){ 1149 if(slider.settings.mode == ‘horizontal’){ 1150 // get the last child position 1151 var lastChild = slider.children.eq(slider.children.length - 1); 1152 position = lastChild.position(); 1153 // calculate the position of the last slide 1154 moveBy = slider.viewport.width() - lastChild.outerWidth(); 1155 }else{ 1156 // get last showing index position 1157 var lastShowingIndex = slider.children.length - slider.settings.minSlides; 1158 position = slider.children.eq(lastShowingIndex).position(); 1159 } 1160 // horizontal carousel, going previous while on first slide (infiniteLoop mode) 1161 }else if(slider.carousel && slider.active.last && direction == ‘prev’){ 1162 // get the last child position 1163 var eq = slider.settings.moveSlides == 1 ? slider.settings.maxSlides - getMoveBy() : ((getPagerQty() - 1) * getMoveBy()) - (slider.children.length - slider.settings.maxSlides); 1164 var lastChild = el.children(‘.bx-clone’).eq(eq); 1165 position = lastChild.position(); 1166 // if infinite loop and “Next” is clicked on the last slide 1167 }else if(direction == ‘next’ && slider.active.index == 0){ 1168 // get the last clone position 1169 position = el.find(‘> .bx-clone’).eq(slider.settings.maxSlides).position(); 1170 slider.active.last = false; 1171 // normal non-zero requests 1172 }else if(slideIndex >= 0){ 1173 var requestEl = slideIndex * getMoveBy(); 1174 position = slider.children.eq(requestEl).position(); 1175 } 1176 1177 /* If the position doesn’t exist 1178 * (e.g. if you destroy the slider on a next click), 1179 * it doesn’t throw an error. 1180 */ 1181 if (“undefined” !== typeof(position)) { 1182 var value = slider.settings.mode == ‘horizontal’ ? -(position.left - moveBy) : -position.top; 1183 // plugin values to be animated 1184 setPositionProperty(value, 'slide’, slider.settings.speed); 1185 } 1186 } 1187 } 1188 1189 /** 1190 * Transitions to the next slide in the show 1191 */ 1192 el.goToNextSlide = function(){ 1193 // if infiniteLoop is false and last page is showing, disregard call 1194 if (!slider.settings.infiniteLoop && slider.active.last) return; 1195 var pagerIndex = parseInt(slider.active.index) + 1; 1196 el.goToSlide(pagerIndex, ‘next’); 1197 } 1198 1199 /** 1200 * Transitions to the prev slide in the show 1201 */ 1202 el.goToPrevSlide = function(){ 1203 // if infiniteLoop is false and last page is showing, disregard call 1204 if (!slider.settings.infiniteLoop && slider.active.index == 0) return; 1205 var pagerIndex = parseInt(slider.active.index) - 1; 1206 el.goToSlide(pagerIndex, ‘prev’); 1207 } 1208 1209 /** 1210 * Starts the auto show 1211 * 1212 * @param preventControlUpdate (boolean) 1213 * - if true, auto controls state will not be updated 1214 */ 1215 el.startAuto = function(preventControlUpdate){ 1216 // if an interval already exists, disregard call 1217 if(slider.interval) return; 1218 // create an interval 1219 slider.interval = setInterval(function(){ 1220 slider.settings.autoDirection == ‘next’ ? el.goToNextSlide() : el.goToPrevSlide(); 1221 }, slider.settings.pause); 1222 // if auto controls are displayed and preventControlUpdate is not true 1223 if (slider.settings.autoControls && preventControlUpdate != true) updateAutoControls(‘stop’); 1224 } 1225 1226 /** 1227 * Stops the auto show 1228 * 1229 * @param preventControlUpdate (boolean) 1230 * - if true, auto controls state will not be updated 1231 */ 1232 el.stopAuto = function(preventControlUpdate){ 1233 // if no interval exists, disregard call 1234 if(!slider.interval) return; 1235 // clear the interval 1236 clearInterval(slider.interval); 1237 slider.interval = null; 1238 // if auto controls are displayed and preventControlUpdate is not true 1239 if (slider.settings.autoControls && preventControlUpdate != true) updateAutoControls(‘start’); 1240 } 1241 1242 /** 1243 * Returns current slide index (zero-based) 1244 */ 1245 el.getCurrentSlide = function(){ 1246 return slider.active.index; 1247 } 1248 1249 /** 1250 * Returns number of slides in show 1251 */ 1252 el.getSlideCount = function(){ 1253 return slider.children.length; 1254 } 1255 1256 /** 1257 * Update all dynamic slider elements 1258 */ 1259 el.redrawSlider = function(){ 1260 // resize all children in ratio to new screen size 1261 slider.children.add(el.find(‘.bx-clone’)).outerWidth(getSlideWidth()); 1262 // adjust the height 1263 slider.viewport.css('height’, getViewportHeight()); 1264 // update the slide position 1265 if(!slider.settings.ticker) setSlidePosition(); 1266 // if active.last was true before the screen resize, we want 1267 // to keep it last no matter what screen size we end on 1268 if (slider.active.last) slider.active.index = getPagerQty() - 1; 1269 // if the active index (page) no longer exists due to the resize, simply set the index as last 1270 if (slider.active.index >= getPagerQty()) slider.active.last = true; 1271 // if a pager is being displayed and a custom pager is not being used, update it 1272 if(slider.settings.pager && !slider.settings.pagerCustom){ 1273 populatePager(); 1274 updatePagerActive(slider.active.index); 1275 } 1276 } 1277 1278 /** 1279 * Destroy the current instance of the slider (revert everything back to original state) 1280 */ 1281 el.destroySlider = function(){ 1282 // don’t do anything if slider has already been destroyed 1283 if(!slider.initialized) return; 1284 slider.initialized = false; 1285 $(‘.bx-clone’, this).remove(); 1286 slider.children.each(function() { 1287 $(this).data(“origStyle”) != undefined ? $(this).attr("style", $(this).data(“origStyle”)) : $(this).removeAttr(‘style’); 1288 }); 1289 $(this).data(“origStyle”) != undefined ? this.attr(“style", $(this).data(“origStyle”)) : $(this).removeAttr(‘style’); 1290 $(this).unwrap().unwrap(); 1291 if(slider.controls.el) slider.controls.el.remove(); 1292 if(slider.controls.next) slider.controls.next.remove(); 1293 if(slider.controls.prev) slider.controls.prev.remove(); 1294 if(slider.pagerEl) slider.pagerEl.remove(); 1295 $('.bx-caption’, this).remove(); 1296 if(slider.controls.autoEl) slider.controls.autoEl.remove(); 1297 clearInterval(slider.interval); 1298 if(slider.settings.responsive) $(window).unbind('resize’, resizeWindow); 1299 } 1300 1301 /** 1302 * Reload the slider (revert all DOM changes, and re-initialize) 1303 */ 1304 el.reloadSlider = function(settings){ 1305 if (settings != undefined) options = settings; 1306 el.destroySlider(); 1307 init(); 1308 } 1309 1310 init(); 1311 1312 // returns the current jQuery object 1313 return this; 1314 } 8;(function($) { 9 10 var defaults = { 11 12 // GENERAL 13 mode: 'horizontal’, 14 slideSelector: '’, 15 infiniteLoop: true, 16 hideControlOnEnd: false, 17 speed: 500, 18 easing: null, 19 slideMargin: 0, 20 startSlide: 0, 21 randomStart: false, 22 captions: false, 23 ticker: false, 24 tickerHover: false, 25 adaptiveHeight: false, 26 adaptiveHeightSpeed: 500, 27 video: false, 28 useCSS: true, 29 preloadImages: 'visible’, 30 responsive: true, 31 slideZIndex: 50, 32 wrapperClass: 'bx-wrapper’, 33 34 // TOUCH 35 touchEnabled: true, 36 swipeThreshold: 50, 37 oneToOneTouch: true, 38 preventDefaultSwipeX: true, 39 preventDefaultSwipeY: false, 40 41 // ACCESSIBILITY 42 ariaLive: true, 43 ariaHidden: true, 44 45 // KEYBOARD 46 keyboardEnabled: false, 47 48 // PAGER 49 pager: true, 50 pagerType: 'full’, 51 pagerShortSeparator: ' / ', 52 pagerSelector: null, 53 buildPager: null, 54 pagerCustom: null, 55 56 // CONTROLS 57 controls: true, 58 nextText: 'Next’, 59 prevText: 'Prev’, 60 nextSelector: null, 61 prevSelector: null, 62 autoControls: false, 63 startText: 'Start’, 64 stopText: 'Stop’, 65 autoControlsCombine: false, 66 autoControlsSelector: null, 67 68 // AUTO 69 auto: false, 70 pause: 4000, 71 autoStart: true, 72 autoDirection: 'next’, 73 stopAutoOnClick: false, 74 autoHover: false, 75 autoDelay: 0, 76 autoSlideForOnePage: false, 77 78 // CAROUSEL 79 minSlides: 1, 80 maxSlides: 1, 81 moveSlides: 0, 82 slideWidth: 0, 83 shrinkItems: false, 84 85 // CALLBACKS 86 onSliderLoad: function() { return true; }, 87 onSlideBefore: function() { return true; }, 88 onSlideAfter: function() { return true; }, 89 onSlideNext: function() { return true; }, 90 onSlidePrev: function() { return true; }, 91 onSliderResize: function() { return true; } 92 }; 93 94 $.fn.bxSlider = function(options) { 95 96 if (this.length === 0) { 97 return this; 98 } 99 100 // support multiple elements 101 if (this.length > 1) { 102 this.each(function() { 103 $(this).bxSlider(options); 104 }); 105 return this; 106 } 107 108 // create a namespace to be used throughout the plugin 109 var slider = {}, 110 // set a reference to our slider element 111 el = this, 112 // get the original window dimens (thanks a lot IE) 113 windowWidth = $(window).width(), 114 windowHeight = $(window).height(); 115 116 // Return if slider is already initialized 117 if ($(el).data(‘bxSlider’)) { return; } 118 119 /** 120 * =================================================================================== 121 * = PRIVATE FUNCTIONS 122 * =================================================================================== 123 */ 124 125 /** 126 * Initializes namespace settings to be used throughout plugin 127 */ 128 var init = function() { 129 // Return if slider is already initialized 130 if ($(el).data(‘bxSlider’)) { return; } 131 // merge user-supplied options with the defaults 132 slider.settings = $.extend({}, defaults, options); 133 // parse slideWidth setting 134 slider.settings.slideWidth = parseInt(slider.settings.slideWidth); 135 // store the original children 136 slider.children = el.children(slider.settings.slideSelector); 137 // check if actual number of slides is less than minSlides / maxSlides 138 if (slider.children.length < slider.settings.minSlides) { slider.settings.minSlides = slider.children.length; } 139 if (slider.children.length < slider.settings.maxSlides) { slider.settings.maxSlides = slider.children.length; } 140 // if random start, set the startSlide setting to random number 141 if (slider.settings.randomStart) { slider.settings.startSlide = Math.floor(Math.random() * slider.children.length); } 142 // store active slide information 143 slider.active = { index: slider.settings.startSlide }; 144 // store if the slider is in carousel mode (displaying / moving multiple slides) 145 slider.carousel = slider.settings.minSlides > 1 || slider.settings.maxSlides > 1 ? true : false; 146 // if carousel, force preloadImages = ‘all’ 147 if (slider.carousel) { slider.settings.preloadImages = 'all’; } 148 // calculate the min / max width thresholds based on min / max number of slides 149 // used to setup and update carousel slides dimensions 150 slider.minThreshold = (slider.settings.minSlides * slider.settings.slideWidth) + ((slider.settings.minSlides - 1) * slider.settings.slideMargin); 151 slider.maxThreshold = (slider.settings.maxSlides * slider.settings.slideWidth) + ((slider.settings.maxSlides - 1) * slider.settings.slideMargin); 152 // store the current state of the slider (if currently animating, working is true) 153 slider.working = false; 154 // initialize the controls object 155 slider.controls = {}; 156 // initialize an auto interval 157 slider.interval = null; 158 // determine which property to use for transitions 159 slider.animProp = slider.settings.mode === ‘vertical’ ? ‘top’ : 'left’; 160 // determine if hardware acceleration can be used 161 slider.usingCSS = slider.settings.useCSS && slider.settings.mode !== ‘fade’ && (function() { 162 // create our test div element 163 var div = document.createElement(‘div’), 164 // css transition properties 165 props = ['WebkitPerspective’, 'MozPerspective’, 'OPerspective’, ‘msPerspective’]; 166 // test for each property 167 for (var i = 0; i < props.length; i++) { 168 if (div.style[props[i]] !== undefined) { 169 slider.cssPrefix = props[i].replace('Perspective’, ‘’).toLowerCase(); 170 slider.animProp = '-' + slider.cssPrefix + '-transform’; 171 return true; 172 } 173 } 174 return false; 175 }()); 176 // if vertical mode always make maxSlides and minSlides equal 177 if (slider.settings.mode === ‘vertical’) { slider.settings.maxSlides = slider.settings.minSlides; } 178 // save original style data 179 el.data('origStyle’, el.attr(‘style’)); 180 el.children(slider.settings.slideSelector).each(function() { 181 $(this).data('origStyle’, $(this).attr(‘style’)); 182 }); 183 184 // perform all DOM / CSS modifications 185 setup(); 186 }; 187 188 /** 189 * Performs all DOM and CSS modifications 190 */ 191 var setup = function() { 192 var preloadSelector = slider.children.eq(slider.settings.startSlide); // set the default preload selector (visible) 193 194 // wrap el in a wrapper 195 el.wrap(‘<div class="’ + slider.settings.wrapperClass + ‘"><div class="bx-viewport"></div></div>’); 196 // store a namespace reference to .bx-viewport 197 slider.viewport = el.parent(); 198 199 // add aria-live if the setting is enabled and ticker mode is disabled 200 if (slider.settings.ariaLive && !slider.settings.ticker) { 201 slider.viewport.attr('aria-live’, ‘polite’); 202 } 203 // add a loading div to display while images are loading 204 slider.loader = $('<div class="bx-loading” />’); 205 slider.viewport.prepend(slider.loader); 206 // set el to a massive width, to hold any needed slides 207 // also strip any margin and padding from el 208 el.css({ 209 width: slider.settings.mode === ‘horizontal’ ? (slider.children.length * 1000 + 215) + ‘%’ : 'auto’, 210 position: ‘relative’ 211 }); 212 // if using CSS, add the easing property 213 if (slider.usingCSS && slider.settings.easing) { 214 el.css('-' + slider.cssPrefix + '-transition-timing-function’, slider.settings.easing); 215 // if not using CSS and no easing value was supplied, use the default JS animation easing (swing) 216 } else if (!slider.settings.easing) { 217 slider.settings.easing = 'swing’; 218 } 219 // make modifications to the viewport (.bx-viewport) 220 slider.viewport.css({ 221 width: '100%’, 222 overflow: 'hidden’, 223 position: ‘relative’ 224 }); 225 slider.viewport.parent().css({ 226 maxWidth: getViewportMaxWidth() 227 }); 228 // make modification to the wrapper (.bx-wrapper) 229 if (!slider.settings.pager && !slider.settings.controls) { 230 slider.viewport.parent().css({ 231 margin: ‘0 auto 0px’ 232 }); 233 } 234 // apply css to all slider children 235 slider.children.css({ 236 float: slider.settings.mode === ‘horizontal’ ? ‘left’ : 'none’, 237 listStyle: 'none’, 238 position: ‘relative’ 239 }); 240 // apply the calculated width after the float is applied to prevent scrollbar interference 241 slider.children.css('width’, getSlideWidth()); 242 // if slideMargin is supplied, add the css 243 if (slider.settings.mode === ‘horizontal’ && slider.settings.slideMargin > 0) { slider.children.css('marginRight’, slider.settings.slideMargin); } 244 if (slider.settings.mode === ‘vertical’ && slider.settings.slideMargin > 0) { slider.children.css('marginBottom’, slider.settings.slideMargin); } 245 // if “fade” mode, add positioning and z-index CSS 246 if (slider.settings.mode === ‘fade’) { 247 slider.children.css({ 248 position: 'absolute’, 249 zIndex: 0, 250 display: ‘none’ 251 }); 252 // prepare the z-index on the showing element 253 slider.children.eq(slider.settings.startSlide).css({zIndex: slider.settings.slideZIndex, display: 'block’}); 254 } 255 // create an element to contain all slider controls (pager, start / stop, etc) 256 slider.controls.el = $('<div class="bx-controls” />’); 257 // if captions are requested, add them 258 if (slider.settings.captions) { appendCaptions(); } 259 // check if startSlide is last slide 260 slider.active.last = slider.settings.startSlide === getPagerQty() - 1; 261 // if video is true, set up the fitVids plugin 262 if (slider.settings.video) { el.fitVids(); } 263 if (slider.settings.preloadImages === ‘all’ || slider.settings.ticker) { preloadSelector = slider.children; } 264 // only check for control addition if not in “ticker” mode 265 if (!slider.settings.ticker) { 266 // if controls are requested, add them 267 if (slider.settings.controls) { appendControls(); } 268 // if auto is true, and auto controls are requested, add them 269 if (slider.settings.auto && slider.settings.autoControls) { appendControlsAuto(); } 270 // if pager is requested, add it 271 if (slider.settings.pager) { appendPager(); } 272 // if any control option is requested, add the controls wrapper 273 if (slider.settings.controls || slider.settings.autoControls || slider.settings.pager) { slider.viewport.after(slider.controls.el); } 274 // if ticker mode, do not allow a pager 275 } else { 276 slider.settings.pager = false; 277 } 278 loadElements(preloadSelector, start); 279 }; 280 281 var loadElements = function(selector, callback) { 282 var total = selector.find('img:not([src="”]), iframe’).length, 283 count = 0; 284 if (total === 0) { 285 callback(); 286 return; 287 } 288 selector.find(‘img:not([src=""]), iframe’).each(function() { 289 $(this).one('load error’, function() { 290 if (++count === total) { callback(); } 291 }).each(function() { 292 if (this.complete) { $(this).load(); } 293 }); 294 }); 295 }; 296 297 /** 298 * Start the slider 299 */ 300 var start = function() { 301 // if infinite loop, prepare additional slides 302 if (slider.settings.infiniteLoop && slider.settings.mode !== ‘fade’ && !slider.settings.ticker) { 303 var slice = slider.settings.mode === ‘vertical’ ? slider.settings.minSlides : slider.settings.maxSlides, 304 sliceAppend = slider.children.slice(0, slice).clone(true).addClass(‘bx-clone’), 305 slicePrepend = slider.children.slice(-slice).clone(true).addClass(‘bx-clone’); 306 if (slider.settings.ariaHidden) { 307 sliceAppend.attr('aria-hidden’, true); 308 slicePrepend.attr('aria-hidden’, true); 309 } 310 el.append(sliceAppend).prepend(slicePrepend); 311 } 312 // remove the loading DOM element 313 slider.loader.remove(); 314 // set the left / top position of “el” 315 setSlidePosition(); 316 // if “vertical” mode, always use adaptiveHeight to prevent odd behavior 317 if (slider.settings.mode === ‘vertical’) { slider.settings.adaptiveHeight = true; } 318 // set the viewport height 319 slider.viewport.height(getViewportHeight()); 320 // make sure everything is positioned just right (same as a window resize) 321 el.redrawSlider(); 322 // onSliderLoad callback 323 slider.settings.onSliderLoad.call(el, slider.active.index); 324 // slider has been fully initialized 325 slider.initialized = true; 326 // bind the resize call to the window 327 if (slider.settings.responsive) { $(window).bind('resize’, resizeWindow); } 328 // if auto is true and has more than 1 page, start the show 329 if (slider.settings.auto && slider.settings.autoStart && (getPagerQty() > 1 || slider.settings.autoSlideForOnePage)) { initAuto(); } 330 // if ticker is true, start the ticker 331 if (slider.settings.ticker) { initTicker(); } 332 // if pager is requested, make the appropriate pager link active 333 if (slider.settings.pager) { updatePagerActive(slider.settings.startSlide); } 334 // check for any updates to the controls (like hideControlOnEnd updates) 335 if (slider.settings.controls) { updateDirectionControls(); } 336 // if touchEnabled is true, setup the touch events 337 if (slider.settings.touchEnabled && !slider.settings.ticker) { initTouch(); } 338 // if keyboardEnabled is true, setup the keyboard events 339 if (slider.settings.keyboardEnabled && !slider.settings.ticker) { 340 $(document).keydown(keyPress); 341 } 342 }; 343 344 /** 345 * Returns the calculated height of the viewport, used to determine either adaptiveHeight or the maxHeight value 346 */ 347 var getViewportHeight = function() { 348 var height = el.outerHeight(), 349 currentIndex = null, 350 // first determine which children (slides) should be used in our height calculation 351 children = $(); 352 // if mode is not “vertical” and adaptiveHeight is false, include all children 353 if (slider.settings.mode !== ‘vertical’ && !slider.settings.adaptiveHeight) { 354 children = slider.children; 355 } else { 356 // if not carousel, return the single active child 357 if (!slider.carousel) { 358 children = slider.children.eq(slider.active.index); 359 // if carousel, return a slice of children 360 } else { 361 // get the individual slide index 362 currentIndex = slider.settings.moveSlides === 1 ? slider.active.index : slider.active.index * getMoveBy(); 363 // add the current slide to the children 364 children = slider.children.eq(currentIndex); 365 // cycle through the remaining “showing” slides 366 for (var i = 1; i <= slider.settings.maxSlides - 1; i++) { 367 // if looped back to the start 368 if (currentIndex + i >= slider.children.length) { 369 children = children.add(slider.children.eq(currentIndex + i - slider.children.length)); 370 } else { 371 children = children.add(slider.children.eq(currentIndex + i)); 372 } 373 } 374 } 375 } 376 // if “vertical” mode, calculate the sum of the heights of the children 377 if (slider.settings.mode === ‘vertical’) { 378 children.each(function(index) { 379 height += $(this).outerHeight(); 380 }); 381 // add user-supplied margins 382 if (slider.settings.slideMargin > 0) { 383 height += slider.settings.slideMargin * (slider.settings.minSlides - 1); 384 } 385 // if not “vertical” mode, calculate the max height of the children 386 } else { 387 height = Math.max.apply(Math, children.map(function() { 388 return $(this).outerHeight(false); 389 }).get()); 390 } 391 392 if (slider.viewport.css(‘box-sizing’) === ‘border-box’) { 393 height += parseFloat(slider.viewport.css(‘padding-top’)) + parseFloat(slider.viewport.css(‘padding-bottom’)) + 394 parseFloat(slider.viewport.css(‘border-top-width’)) + parseFloat(slider.viewport.css(‘border-bottom-width’)); 395 } else if (slider.viewport.css(‘box-sizing’) === ‘padding-box’) { 396 height += parseFloat(slider.viewport.css(‘padding-top’)) + parseFloat(slider.viewport.css(‘padding-bottom’)); 397 } 398 399 return height; 400 }; 401 402 /** 403 * Returns the calculated width to be used for the outer wrapper / viewport 404 */ 405 var getViewportMaxWidth = function() { 406 var width = '100%’; 407 if (slider.settings.slideWidth > 0) { 408 if (slider.settings.mode === ‘horizontal’) { 409 width = (slider.settings.maxSlides * slider.settings.slideWidth) + ((slider.settings.maxSlides - 1) * slider.settings.slideMargin); 410 } else { 411 width = slider.settings.slideWidth; 412 } 413 } 414 return width; 415 }; 416 417 /** 418 * Returns the calculated width to be applied to each slide 419 */ 420 var getSlideWidth = function() { 421 var newElWidth = slider.settings.slideWidth, // start with any user-supplied slide width 422 wrapWidth = slider.viewport.width(); // get the current viewport width 423 // if slide width was not supplied, or is larger than the viewport use the viewport width 424 if (slider.settings.slideWidth === 0 || 425 (slider.settings.slideWidth > wrapWidth && !slider.carousel) || 426 slider.settings.mode === ‘vertical’) { 427 newElWidth = wrapWidth; 428 // if carousel, use the thresholds to determine the width 429 } else if (slider.settings.maxSlides > 1 && slider.settings.mode === ‘horizontal’) { 430 if (wrapWidth > slider.maxThreshold) { 431 return newElWidth; 432 } else if (wrapWidth < slider.minThreshold) { 433 newElWidth = (wrapWidth - (slider.settings.slideMargin * (slider.settings.minSlides - 1))) / slider.settings.minSlides; 434 } else if (slider.settings.shrinkItems) { 435 newElWidth = Math.floor((wrapWidth + slider.settings.slideMargin) / (Math.ceil((wrapWidth + slider.settings.slideMargin) / (newElWidth + slider.settings.slideMargin))) - slider.settings.slideMargin); 436 } 437 } 438 return newElWidth; 439 }; 440 441 /** 442 * Returns the number of slides currently visible in the viewport (includes partially visible slides) 443 */ 444 var getNumberSlidesShowing = function() { 445 var slidesShowing = 1, 446 childWidth = null; 447 if (slider.settings.mode === ‘horizontal’ && slider.settings.slideWidth > 0) { 448 // if viewport is smaller than minThreshold, return minSlides 449 if (slider.viewport.width() < slider.minThreshold) { 450 slidesShowing = slider.settings.minSlides; 451 // if viewport is larger than maxThreshold, return maxSlides 452 } else if (slider.viewport.width() > slider.maxThreshold) { 453 slidesShowing = slider.settings.maxSlides; 454 // if viewport is between min / max thresholds, divide viewport width by first child width 455 } else { 456 childWidth = slider.children.first().width() + slider.settings.slideMargin; 457 slidesShowing = Math.floor((slider.viewport.width() + 458 slider.settings.slideMargin) / childWidth); 459 } 460 // if “vertical” mode, slides showing will always be minSlides 461 } else if (slider.settings.mode === ‘vertical’) { 462 slidesShowing = slider.settings.minSlides; 463 } 464 return slidesShowing; 465 }; 466 467 /** 468 * Returns the number of pages (one full viewport of slides is one “page”) 469 */ 470 var getPagerQty = function() { 471 var pagerQty = 0, 472 breakPoint = 0, 473 counter = 0; 474 // if moveSlides is specified by the user 475 if (slider.settings.moveSlides > 0) { 476 if (slider.settings.infiniteLoop) { 477 pagerQty = Math.ceil(slider.children.length / getMoveBy()); 478 } else { 479 // when breakpoint goes above children length, counter is the number of pages 480 while (breakPoint < slider.children.length) { 481 ++pagerQty; 482 breakPoint = counter + getNumberSlidesShowing(); 483 counter += slider.settings.moveSlides <= getNumberSlidesShowing() ? slider.settings.moveSlides : getNumberSlidesShowing(); 484 } 485 } 486 // if moveSlides is 0 (auto) divide children length by sides showing, then round up 487 } else { 488 pagerQty = Math.ceil(slider.children.length / getNumberSlidesShowing()); 489 } 490 return pagerQty; 491 }; 492 493 /** 494 * Returns the number of individual slides by which to shift the slider 495 */ 496 var getMoveBy = function() { 497 // if moveSlides was set by the user and moveSlides is less than number of slides showing 498 if (slider.settings.moveSlides > 0 && slider.settings.moveSlides <= getNumberSlidesShowing()) { 499 return slider.settings.moveSlides; 500 } 501 // if moveSlides is 0 (auto) 502 return getNumberSlidesShowing(); 503 }; 504 505 /** 506 * Sets the slider’s (el) left or top position 507 */ 508 var setSlidePosition = function() { 509 var position, lastChild, lastShowingIndex; 510 // if last slide, not infinite loop, and number of children is larger than specified maxSlides 511 if (slider.children.length > slider.settings.maxSlides && slider.active.last && !slider.settings.infiniteLoop) { 512 if (slider.settings.mode === ‘horizontal’) { 513 // get the last child’s position 514 lastChild = slider.children.last(); 515 position = lastChild.position(); 516 // set the left position 517 setPositionProperty(-(position.left - (slider.viewport.width() - lastChild.outerWidth())), 'reset’, 0); 518 } else if (slider.settings.mode === ‘vertical’) { 519 // get the last showing index’s position 520 lastShowingIndex = slider.children.length - slider.settings.minSlides; 521 position = slider.children.eq(lastShowingIndex).position(); 522 // set the top position 523 setPositionProperty(-position.top, 'reset’, 0); 524 } 525 // if not last slide 526 } else { 527 // get the position of the first showing slide 528 position = slider.children.eq(slider.active.index * getMoveBy()).position(); 529 // check for last slide 530 if (slider.active.index === getPagerQty() - 1) { slider.active.last = true; } 531 // set the respective position 532 if (position !== undefined) { 533 if (slider.settings.mode === ‘horizontal’) { setPositionProperty(-position.left, 'reset’, 0); } 534 else if (slider.settings.mode === ‘vertical’) { setPositionProperty(-position.top, 'reset’, 0); } 535 } 536 } 537 }; 538 539 /** 540 * Sets the el’s animating property position (which in turn will sometimes animate el). 541 * If using CSS, sets the transform property. If not using CSS, sets the top / left property. 542 * 543 * @param value (int) 544 * - the animating property’s value 545 * 546 * @param type (string) 'slide’, 'reset’, ‘ticker’ 547 * - the type of instance for which the function is being 548 * 549 * @param duration (int) 550 * - the amount of time (in ms) the transition should occupy 551 * 552 * @param params (array) optional 553 * - an optional parameter containing any variables that need to be passed in 554 */ 555 var setPositionProperty = function(value, type, duration, params) { 556 var animateObj, propValue; 557 // use CSS transform 558 if (slider.usingCSS) { 559 // determine the translate3d value 560 propValue = slider.settings.mode === ‘vertical’ ? 'translate3d(0, ' + value + 'px, 0)' : 'translate3d(' + value + 'px, 0, 0)'; 561 // add the CSS transition-duration 562 el.css('-' + slider.cssPrefix + '-transition-duration’, duration / 1000 + ‘s’); 563 if (type === ‘slide’) { 564 // set the property value 565 el.css(slider.animProp, propValue); 566 // bind a callback method - executes when CSS transition completes 567 el.bind('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’, function(e) { 568 //make sure it’s the correct one 569 if (!$(e.target).is(el)) { return; } 570 // unbind the callback 571 el.unbind(‘transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’); 572 updateAfterSlideTransition(); 573 }); 574 } else if (type === ‘reset’) { 575 el.css(slider.animProp, propValue); 576 } else if (type === ‘ticker’) { 577 // make the transition use ‘linear’ 578 el.css('-' + slider.cssPrefix + '-transition-timing-function’, ‘linear’); 579 el.css(slider.animProp, propValue); 580 // bind a callback method - executes when CSS transition completes 581 el.bind('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’, function(e) { 582 //make sure it’s the correct one 583 if (!$(e.target).is(el)) { return; } 584 // unbind the callback 585 el.unbind(‘transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd’); 586 // reset the position 587 setPositionProperty(params.resetValue, 'reset’, 0); 588 // start the loop again 589 tickerLoop(); 590 }); 591 } 592 // use JS animate 593 } else { 594 animateObj = {}; 595 animateObj[slider.animProp] = value; 596 if (type === ‘slide’) { 597 el.animate(animateObj, duration, slider.settings.easing, function() { 598 updateAfterSlideTransition(); 599 }); 600 } else if (type === ‘reset’) { 601 el.css(slider.animProp, value); 602 } else if (type === ‘ticker’) { 603 el.animate(animateObj, duration, 'linear’, function() { 604 setPositionProperty(params.resetValue, 'reset’, 0); 605 // run the recursive loop after animation 606 tickerLoop(); 607 }); 608 } 609 } 610 }; 611 612 /** 613 * Populates the pager with proper amount of pages 614 */ 615 var populatePager = function() { 616 var pagerHtml = '’, 617 linkContent = '’, 618 pagerQty = getPagerQty(); 619 // loop through each pager item 620 for (var i = 0; i < pagerQty; i++) { 621 linkContent = '’; 622 // if a buildPager function is supplied, use it to get pager link value, else use index + 1 623 if (slider.settings.buildPager && $.isFunction(slider.settings.buildPager) || slider.settings.pagerCustom) { 624 linkContent = slider.settings.buildPager(i); 625 slider.pagerEl.addClass(‘bx-custom-pager’); 626 } else { 627 linkContent = i + 1; 628 slider.pagerEl.addClass(‘bx-default-pager’); 629 } 630 // var linkContent = slider.settings.buildPager && $.isFunction(slider.settings.buildPager) ? slider.settings.buildPager(i) : i + 1; 631 // add the markup to the string 632 pagerHtml += ‘<div class="bx-pager-item"><a href="" data-slide-index="’ + i + ‘" class="bx-pager-link">’ + linkContent + '</a></div>’; 633 } 634 // populate the pager element with pager links 635 slider.pagerEl.html(pagerHtml); 636 }; 637 638 /** 639 * Appends the pager to the controls element 640 */ 641 var appendPager = function() { 642 if (!slider.settings.pagerCustom) { 643 // create the pager DOM element 644 slider.pagerEl = $(‘<div class="bx-pager" />’); 645 // if a pager selector was supplied, populate it with the pager 646 if (slider.settings.pagerSelector) { 647 $(slider.settings.pagerSelector).html(slider.pagerEl); 648 // if no pager selector was supplied, add it after the wrapper 649 } else { 650 slider.controls.el.addClass(‘bx-has-pager’).append(slider.pagerEl); 651 } 652 // populate the pager 653 populatePager(); 654 } else { 655 slider.pagerEl = $(slider.settings.pagerCustom); 656 } 657 // assign the pager click binding 658 slider.pagerEl.on('click touchend’, 'a’, clickPagerBind); 659 }; 660 661 /** 662 * Appends prev / next controls to the controls element 663 */ 664 var appendControls = function() { 665 slider.controls.next = $(‘<a class="bx-next" href="">’ + slider.settings.nextText + ‘</a>’); 666 slider.controls.prev = $(‘<a class="bx-prev" href="">’ + slider.settings.prevText + ‘</a>’); 667 // bind click actions to the controls 668 slider.controls.next.bind('click touchend’, clickNextBind); 669 slider.controls.prev.bind('click touchend’, clickPrevBind); 670 // if nextSelector was supplied, populate it 671 if (slider.settings.nextSelector) { 672 $(slider.settings.nextSelector).append(slider.controls.next); 673 } 674 // if prevSelector was supplied, populate it 675 if (slider.settings.prevSelector) { 676 $(slider.settings.prevSelector).append(slider.controls.prev); 677 } 678 // if no custom selectors were supplied 679 if (!slider.settings.nextSelector && !slider.settings.prevSelector) { 680 // add the controls to the DOM 681 slider.controls.directionEl = $(‘<div class="bx-controls-direction" />’); 682 // add the control elements to the directionEl 683 slider.controls.directionEl.append(slider.controls.prev).append(slider.controls.next); 684 // slider.viewport.append(slider.controls.directionEl); 685 slider.controls.el.addClass(‘bx-has-controls-direction’).append(slider.controls.directionEl); 686 } 687 }; 688 689 /** 690 * Appends start / stop auto controls to the controls element 691 */ 692 var appendControlsAuto = function() { 693 slider.controls.start = $(‘<div class="bx-controls-auto-item"><a class="bx-start" href="">’ + slider.settings.startText + ‘</a></div>’); 694 slider.controls.stop = $(‘<div class="bx-controls-auto-item"><a class="bx-stop" href="">’ + slider.settings.stopText + ‘</a></div>’); 695 // add the controls to the DOM 696 slider.controls.autoEl = $(‘<div class="bx-controls-auto" />’); 697 // bind click actions to the controls 698 slider.controls.autoEl.on('click’, '.bx-start’, clickStartBind); 699 slider.controls.autoEl.on('click’, '.bx-stop’, clickStopBind); 700 // if autoControlsCombine, insert only the “start” control 701 if (slider.settings.autoControlsCombine) { 702 slider.controls.autoEl.append(slider.controls.start); 703 // if autoControlsCombine is false, insert both controls 704 } else { 705 slider.controls.autoEl.append(slider.controls.start).append(slider.controls.stop); 706 } 707 // if auto controls selector was supplied, populate it with the controls 708 if (slider.settings.autoControlsSelector) { 709 $(slider.settings.autoControlsSelector).html(slider.controls.autoEl); 710 // if auto controls selector was not supplied, add it after the wrapper 711 } else { 712 slider.controls.el.addClass(‘bx-has-controls-auto’).append(slider.controls.autoEl); 713 } 714 // update the auto controls 715 updateAutoControls(slider.settings.autoStart ? ‘stop’ : ‘start’); 716 }; 717 718 /** 719 * Appends image captions to the DOM 720 */ 721 var appendCaptions = function() { 722 // cycle through each child 723 slider.children.each(function(index) { 724 // get the image title attribute 725 var title = $(this).find(‘img:first’).attr(‘title’); 726 // append the caption 727 if (title !== undefined && (‘’ + title).length) { 728 $(this).append(‘<div class="bx-caption"><span>’ + title + ‘</span></div>’); 729 } 730 }); 731 }; 732 733 /** 734 * Click next binding 735 * 736 * @param e (event) 737 * - DOM event object 738 */ 739 var clickNextBind = function(e) { 740 e.preventDefault(); 741 if (slider.controls.el.hasClass(‘disabled’)) { return; } 742 // if auto show is running, stop it 743 if (slider.settings.auto && slider.settings.stopAutoOnClick) { el.stopAuto(); } 744 el.goToNextSlide(); 745 }; 746 747 /** 748 * Click prev binding 749 * 750 * @param e (event) 751 * - DOM event object 752 */ 753 var clickPrevBind = function(e) { 754 e.preventDefault(); 755 if (slider.controls.el.hasClass(‘disabled’)) { return; } 756 // if auto show is running, stop it 757 if (slider.settings.auto && slider.settings.stopAutoOnClick) { el.stopAuto(); } 758 el.goToPrevSlide(); 759 }; 760 761 /** 762 * Click start binding 763 * 764 * @param e (event) 765 * - DOM event object 766 */ 767 var clickStartBind = function(e) { 768 el.startAuto(); 769 e.preventDefault(); 770 }; 771 772 /** 773 * Click stop binding 774 * 775 * @param e (event) 776 * - DOM event object 777 */ 778 var clickStopBind = function(e) { 779 el.stopAuto(); 780 e.preventDefault(); 781 }; 782 783 /** 784 * Click pager binding 785 * 786 * @param e (event) 787 * - DOM event object 788 */ 789 var clickPagerBind = function(e) { 790 var pagerLink, pagerIndex; 791 e.preventDefault(); 792 if (slider.controls.el.hasClass(‘disabled’)) { 793 return; 794 } 795 // if auto show is running, stop it 796 if (slider.settings.auto && slider.settings.stopAutoOnClick) { el.stopAuto(); } 797 pagerLink = $(e.currentTarget); 798 if (pagerLink.attr(‘data-slide-index’) !== undefined) { 799 pagerIndex = parseInt(pagerLink.attr(‘data-slide-index’)); 800 // if clicked pager link is not active, continue with the goToSlide call 801 if (pagerIndex !== slider.active.index) { el.goToSlide(pagerIndex); } 802 } 803 }; 804 805 /** 806 * Updates the pager links with an active class 807 * 808 * @param slideIndex (int) 809 * - index of slide to make active 810 */ 811 var updatePagerActive = function(slideIndex) { 812 // if “short” pager type 813 var len = slider.children.length; // nb of children 814 if (slider.settings.pagerType === ‘short’) { 815 if (slider.settings.maxSlides > 1) { 816 len = Math.ceil(slider.children.length / slider.settings.maxSlides); 817 } 818 slider.pagerEl.html((slideIndex + 1) + slider.settings.pagerShortSeparator + len); 819 return; 820 } 821 // remove all pager active classes 822 slider.pagerEl.find(‘a’).removeClass(‘active’); 823 // apply the active class for all pagers 824 slider.pagerEl.each(function(i, el) { $(el).find(‘a’).eq(slideIndex).addClass(‘active’); }); 825 }; 826 827 /** 828 * Performs needed actions after a slide transition 829 */ 830 var updateAfterSlideTransition = function() { 831 // if infinite loop is true 832 if (slider.settings.infiniteLoop) { 833 var position = '’; 834 // first slide 835 if (slider.active.index === 0) { 836 // set the new position 837 position = slider.children.eq(0).position(); 838 // carousel, last slide 839 } else if (slider.active.index === getPagerQty() - 1 && slider.carousel) { 840 position = slider.children.eq((getPagerQty() - 1) * getMoveBy()).position(); 841 // last slide 842 } else if (slider.active.index === slider.children.length - 1) { 843 position = slider.children.eq(slider.children.length - 1).position(); 844 } 845 if (position) { 846 if (slider.settings.mode === ‘horizontal’) { setPositionProperty(-position.left, 'reset’, 0); } 847 else if (slider.settings.mode === ‘vertical’) { setPositionProperty(-position.top, 'reset’, 0); } 848 } 849 } 850 // declare that the transition is complete 851 slider.working = false; 852 // onSlideAfter callback 853 slider.settings.onSlideAfter.call(el, slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index); 854 }; 855 856 /** 857 * Updates the auto controls state (either active, or combined switch) 858 * 859 * @param state (string) "start", “stop” 860 * - the new state of the auto show 861 */ 862 var updateAutoControls = function(state) { 863 // if autoControlsCombine is true, replace the current control with the new state 864 if (slider.settings.autoControlsCombine) { 865 slider.controls.autoEl.html(slider.controls[state]); 866 // if autoControlsCombine is false, apply the “active” class to the appropriate control 867 } else { 868 slider.controls.autoEl.find(‘a’).removeClass(‘active’); 869 slider.controls.autoEl.find('a:not(.bx-' + state + ')').addClass(‘active’); 870 } 871 }; 872 873 /** 874 * Updates the direction controls (checks if either should be hidden) 875 */ 876 var updateDirectionControls = function() { 877 if (getPagerQty() === 1) { 878 slider.controls.prev.addClass(‘disabled’); 879 slider.controls.next.addClass(‘disabled’); 880 } else if (!slider.settings.infiniteLoop && slider.settings.hideControlOnEnd) { 881 // if first slide 882 if (slider.active.index === 0) { 883 slider.controls.prev.addClass(‘disabled’); 884 slider.controls.next.removeClass(‘disabled’); 885 // if last slide 886 } else if (slider.active.index === getPagerQty() - 1) { 887 slider.controls.next.addClass(‘disabled’); 888 slider.controls.prev.removeClass(‘disabled’); 889 // if any slide in the middle 890 } else { 891 slider.controls.prev.removeClass(‘disabled’); 892 slider.controls.next.removeClass(‘disabled’); 893 } 894 } 895 }; 896 897 /** 898 * Initializes the auto process 899 */ 900 var initAuto = function() { 901 // if autoDelay was supplied, launch the auto show using a setTimeout() call 902 if (slider.settings.autoDelay > 0) { 903 var timeout = setTimeout(el.startAuto, slider.settings.autoDelay); 904 // if autoDelay was not supplied, start the auto show normally 905 } else { 906 el.startAuto(); 907 908 //add focus and blur events to ensure its running if timeout gets paused 909 $(window).focus(function() { 910 el.startAuto(); 911 }).blur(function() { 912 el.stopAuto(); 913 }); 914 } 915 // if autoHover is requested 916 if (slider.settings.autoHover) { 917 // on el hover 918 el.hover(function() { 919 // if the auto show is currently playing (has an active interval) 920 if (slider.interval) { 921 // stop the auto show and pass true argument which will prevent control update 922 el.stopAuto(true); 923 // create a new autoPaused value which will be used by the relative “mouseout” event 924 slider.autoPaused = true; 925 } 926 }, function() { 927 // if the autoPaused value was created be the prior “mouseover” event 928 if (slider.autoPaused) { 929 // start the auto show and pass true argument which will prevent control update 930 el.startAuto(true); 931 // reset the autoPaused value 932 slider.autoPaused = null; 933 } 934 }); 935 } 936 }; 937 938 /** 939 * Initializes the ticker process 940 */ 941 var initTicker = function() { 942 var startPosition = 0, 943 position, transform, value, idx, ratio, property, newSpeed, totalDimens; 944 // if autoDirection is "next", append a clone of the entire slider 945 if (slider.settings.autoDirection === ‘next’) { 946 el.append(slider.children.clone().addClass(‘bx-clone’)); 947 // if autoDirection is "prev", prepend a clone of the entire slider, and set the left position 948 } else { 949 el.prepend(slider.children.clone().addClass(‘bx-clone’)); 950 position = slider.children.first().position(); 951 startPosition = slider.settings.mode === ‘horizontal’ ? -position.left : -position.top; 952 } 953 setPositionProperty(startPosition, ‘reset’, 0); 954 // do not allow controls in ticker mode 955 slider.settings.pager = false; 956 slider.settings.controls = false; 957 slider.settings.autoControls = false; 958 // if autoHover is requested 959 if (slider.settings.tickerHover) { 960 if (slider.usingCSS) { 961 idx = slider.settings.mode === ‘horizontal’ ? 4 : 5; 962 slider.viewport.hover(function() { 963 transform = el.css('-' + slider.cssPrefix + '-transform’); 964 value = parseFloat(transform.split(‘,’)[idx]); 965 setPositionProperty(value, 'reset’, 0); 966 }, function() { 967 totalDimens = 0; 968 slider.children.each(function(index) { 969 totalDimens += slider.settings.mode === ‘horizontal’ ? $(this).outerWidth(true) : $(this).outerHeight(true); 970 }); 971 // calculate the speed ratio (used to determine the new speed to finish the paused animation) 972 ratio = slider.settings.speed / totalDimens; 973 // determine which property to use 974 property = slider.settings.mode === ‘horizontal’ ? ‘left’ : 'top’; 975 // calculate the new speed 976 newSpeed = ratio * (totalDimens - (Math.abs(parseInt(value)))); 977 tickerLoop(newSpeed); 978 }); 979 } else { 980 // on el hover 981 slider.viewport.hover(function() { 982 el.stop(); 983 }, function() { 984 // calculate the total width of children (used to calculate the speed ratio) 985 totalDimens = 0; 986 slider.children.each(function(index) { 987 totalDimens += slider.settings.mode === ‘horizontal’ ? $(this).outerWidth(true) : $(this).outerHeight(true); 988 }); 989 // calculate the speed ratio (used to determine the new speed to finish the paused animation) 990 ratio = slider.settings.speed / totalDimens; 991 // determine which property to use 992 property = slider.settings.mode === ‘horizontal’ ? ‘left’ : 'top’; 993 // calculate the new speed 994 newSpeed = ratio * (totalDimens - (Math.abs(parseInt(el.css(property))))); 995 tickerLoop(newSpeed); 996 }); 997 } 998 } 999 // start the ticker loop 1000 tickerLoop(); 1001 }; 1002 1003 /** 1004 * Runs a continuous loop, news ticker-style 1005 */ 1006 var tickerLoop = function(resumeSpeed) { 1007 var speed = resumeSpeed ? resumeSpeed : slider.settings.speed, 1008 position = {left: 0, top: 0}, 1009 reset = {left: 0, top: 0}, 1010 animateProperty, resetValue, params; 1011 1012 // if “next” animate left position to last child, then reset left to 0 1013 if (slider.settings.autoDirection === ‘next’) { 1014 position = el.find(‘.bx-clone’).first().position(); 1015 // if “prev” animate left position to 0, then reset left to first non-clone child 1016 } else { 1017 reset = slider.children.first().position(); 1018 } 1019 animateProperty = slider.settings.mode === ‘horizontal’ ? -position.left : -position.top; 1020 resetValue = slider.settings.mode === ‘horizontal’ ? -reset.left : -reset.top; 1021 params = {resetValue: resetValue}; 1022 setPositionProperty(animateProperty, 'ticker’, speed, params); 1023 }; 1024 1025 /** 1026 * Check if el is on screen 1027 */ 1028 var isOnScreen = function(el) { 1029 var win = $(window), 1030 viewport = { 1031 top: win.scrollTop(), 1032 left: win.scrollLeft() 1033 }, 1034 bounds = el.offset(); 1035 1036 viewport.right = viewport.left + win.width(); 1037 viewport.bottom = viewport.top + win.height(); 1038 bounds.right = bounds.left + el.outerWidth(); 1039 bounds.bottom = bounds.top + el.outerHeight(); 1040 1041 return (!(viewport.right < bounds.left || viewport.left > bounds.right || viewport.bottom < bounds.top || viewport.top > bounds.bottom)); 1042 }; 1043 1044 /** 1045 * Initializes keyboard events 1046 */ 1047 var keyPress = function(e) { 1048 var activeElementTag = document.activeElement.tagName.toLowerCase(), 1049 tagFilters = 'input|textarea’, 1050 p = new RegExp(activeElementTag,[‘i’]), 1051 result = p.exec(tagFilters); 1052 1053 if (result == null && isOnScreen(el)) { 1054 if (e.keyCode === 39) { 1055 clickNextBind(e); 1056 return false; 1057 } else if (e.keyCode === 37) { 1058 clickPrevBind(e); 1059 return false; 1060 } 1061 } 1062 }; 1063 1064 /** 1065 * Initializes touch events 1066 */ 1067 var initTouch = function() { 1068 // initialize object to contain all touch values 1069 slider.touch = { 1070 start: {x: 0, y: 0}, 1071 end: {x: 0, y: 0} 1072 }; 1073 slider.viewport.bind('touchstart MSPointerDown pointerdown’, onTouchStart); 1074 1075 //for browsers that have implemented pointer events and fire a click after 1076 //every pointerup regardless of whether pointerup is on same screen location as pointerdown or not 1077 slider.viewport.on('click’, '.bxslider a’, function(e) { 1078 if (slider.viewport.hasClass(‘click-disabled’)) { 1079 e.preventDefault(); 1080 slider.viewport.removeClass(‘click-disabled’); 1081 } 1082 }); 1083 }; 1084 1085 /** 1086 * Event handler for “touchstart” 1087 * 1088 * @param e (event) 1089 * - DOM event object 1090 */ 1091 var onTouchStart = function(e) { 1092 //disable slider controls while user is interacting with slides to avoid slider freeze that happens on touch devices when a slide swipe happens immediately after interacting with slider controls 1093 slider.controls.el.addClass(‘disabled’); 1094 1095 if (slider.working) { 1096 e.preventDefault(); 1097 slider.controls.el.removeClass(‘disabled’); 1098 } else { 1099 // record the original position when touch starts 1100 slider.touch.originalPos = el.position(); 1101 var orig = e.originalEvent, 1102 touchPoints = (typeof orig.changedTouches !== ‘undefined’) ? orig.changedTouches : [orig]; 1103 // record the starting touch x, y coordinates 1104 slider.touch.start.x = touchPoints[0].pageX; 1105 slider.touch.start.y = touchPoints[0].pageY; 1106 1107 if (slider.viewport.get(0).setPointerCapture) { 1108 slider.pointerId = orig.pointerId; 1109 slider.viewport.get(0).setPointerCapture(slider.pointerId); 1110 } 1111 // bind a “touchmove” event to the viewport 1112 slider.viewport.bind('touchmove MSPointerMove pointermove’, onTouchMove); 1113 // bind a “touchend” event to the viewport 1114 slider.viewport.bind('touchend MSPointerUp pointerup’, onTouchEnd); 1115 slider.viewport.bind('MSPointerCancel pointercancel’, onPointerCancel); 1116 } 1117 }; 1118 1119 /** 1120 * Cancel Pointer for Windows Phone 1121 * 1122 * @param e (event) 1123 * - DOM event object 1124 */ 1125 var onPointerCancel = function(e) { 1126 /* onPointerCancel handler is needed to deal with situations when a touchend 1127 doesn’t fire after a touchstart (this happens on windows phones only) */ 1128 setPositionProperty(slider.touch.originalPos.left, 'reset’, 0); 1129 1130 //remove handlers 1131 slider.controls.el.removeClass(‘disabled’); 1132 slider.viewport.unbind('MSPointerCancel pointercancel’, onPointerCancel); 1133 slider.viewport.unbind('touchmove MSPointerMove pointermove’, onTouchMove); 1134 slider.viewport.unbind('touchend MSPointerUp pointerup’, onTouchEnd); 1135 if (slider.viewport.get(0).releasePointerCapture) { 1136 slider.viewport.get(0).releasePointerCapture(slider.pointerId); 1137 } 1138 }; 1139 1140 /** 1141 * Event handler for “touchmove” 1142 * 1143 * @param e (event) 1144 * - DOM event object 1145 */ 1146 var onTouchMove = function(e) { 1147 var orig = e.originalEvent, 1148 touchPoints = (typeof orig.changedTouches !== ‘undefined’) ? orig.changedTouches : [orig], 1149 // if scrolling on y axis, do not prevent default 1150 xMovement = Math.abs(touchPoints[0].pageX - slider.touch.start.x), 1151 yMovement = Math.abs(touchPoints[0].pageY - slider.touch.start.y), 1152 value = 0, 1153 change = 0; 1154 1155 // x axis swipe 1156 if ((xMovement * 3) > yMovement && slider.settings.preventDefaultSwipeX) { 1157 e.preventDefault(); 1158 // y axis swipe 1159 } else if ((yMovement * 3) > xMovement && slider.settings.preventDefaultSwipeY) { 1160 e.preventDefault(); 1161 } 1162 if (slider.settings.mode !== ‘fade’ && slider.settings.oneToOneTouch) { 1163 // if horizontal, drag along x axis 1164 if (slider.settings.mode === ‘horizontal’) { 1165 change = touchPoints[0].pageX - slider.touch.start.x; 1166 value = slider.touch.originalPos.left + change; 1167 // if vertical, drag along y axis 1168 } else { 1169 change = touchPoints[0].pageY - slider.touch.start.y; 1170 value = slider.touch.originalPos.top + change; 1171 } 1172 setPositionProperty(value, 'reset’, 0); 1173 } 1174 }; 1175 1176 /** 1177 * Event handler for “touchend” 1178 * 1179 * @param e (event) 1180 * - DOM event object 1181 */ 1182 var onTouchEnd = function(e) { 1183 slider.viewport.unbind('touchmove MSPointerMove pointermove’, onTouchMove); 1184 //enable slider controls as soon as user stops interacing with slides 1185 slider.controls.el.removeClass(‘disabled’); 1186 var orig = e.originalEvent, 1187 touchPoints = (typeof orig.changedTouches !== ‘undefined’) ? orig.changedTouches : [orig], 1188 value = 0, 1189 distance = 0; 1190 // record end x, y positions 1191 slider.touch.end.x = touchPoints[0].pageX; 1192 slider.touch.end.y = touchPoints[0].pageY; 1193 // if fade mode, check if absolute x distance clears the threshold 1194 if (slider.settings.mode === ‘fade’) { 1195 distance = Math.abs(slider.touch.start.x - slider.touch.end.x); 1196 if (distance >= slider.settings.swipeThreshold) { 1197 if (slider.touch.start.x > slider.touch.end.x) { 1198 el.goToNextSlide(); 1199 } else { 1200 el.goToPrevSlide(); 1201 } 1202 el.stopAuto(); 1203 } 1204 // not fade mode 1205 } else { 1206 // calculate distance and el’s animate property 1207 if (slider.settings.mode === ‘horizontal’) { 1208 distance = slider.touch.end.x - slider.touch.start.x; 1209 value = slider.touch.originalPos.left; 1210 } else { 1211 distance = slider.touch.end.y - slider.touch.start.y; 1212 value = slider.touch.originalPos.top; 1213 } 1214 // if not infinite loop and first / last slide, do not attempt a slide transition 1215 if (!slider.settings.infiniteLoop && ((slider.active.index === 0 && distance > 0) || (slider.active.last && distance < 0))) { 1216 setPositionProperty(value, 'reset’, 200); 1217 } else { 1218 // check if distance clears threshold 1219 if (Math.abs(distance) >= slider.settings.swipeThreshold) { 1220 if (distance < 0) { 1221 el.goToNextSlide(); 1222 } else { 1223 el.goToPrevSlide(); 1224 } 1225 el.stopAuto(); 1226 } else { 1227 // el.animate(property, 200); 1228 setPositionProperty(value, 'reset’, 200); 1229 } 1230 } 1231 } 1232 slider.viewport.unbind('touchend MSPointerUp pointerup’, onTouchEnd); 1233 if (slider.viewport.get(0).releasePointerCapture) { 1234 slider.viewport.get(0).releasePointerCapture(slider.pointerId); 1235 } 1236 }; 1237 1238 /** 1239 * Window resize event callback 1240 */ 1241 var resizeWindow = function(e) { 1242 // don’t do anything if slider isn’t initialized. 1243 if (!slider.initialized) { return; } 1244 // Delay if slider working. 1245 if (slider.working) { 1246 window.setTimeout(resizeWindow, 10); 1247 } else { 1248 // get the new window dimens (again, thank you IE) 1249 var windowWidthNew = $(window).width(), 1250 windowHeightNew = $(window).height(); 1251 // make sure that it is a true window resize 1252 // *we must check this because our dinosaur friend IE fires a window resize event when certain DOM elements 1253 // are resized. Can you just die already?* 1254 if (windowWidth !== windowWidthNew || windowHeight !== windowHeightNew) { 1255 // set the new window dimens 1256 windowWidth = windowWidthNew; 1257 windowHeight = windowHeightNew; 1258 // update all dynamic elements 1259 el.redrawSlider(); 1260 // Call user resize handler 1261 slider.settings.onSliderResize.call(el, slider.active.index); 1262 } 1263 } 1264 }; 1265 1266 /** 1267 * Adds an aria-hidden=true attribute to each element 1268 * 1269 * @param startVisibleIndex (int) 1270 * - the first visible element’s index 1271 */ 1272 var applyAriaHiddenAttributes = function(startVisibleIndex) { 1273 var numberOfSlidesShowing = getNumberSlidesShowing(); 1274 // only apply attributes if the setting is enabled and not in ticker mode 1275 if (slider.settings.ariaHidden && !slider.settings.ticker) { 1276 // add aria-hidden=true to all elements 1277 slider.children.attr('aria-hidden’, ‘true’); 1278 // get the visible elements and change to aria-hidden=false 1279 slider.children.slice(startVisibleIndex, startVisibleIndex + numberOfSlidesShowing).attr('aria-hidden’, ‘false’); 1280 } 1281 }; 1282 1283 /** 1284 * =================================================================================== 1285 * = PUBLIC FUNCTIONS 1286 * =================================================================================== 1287 */ 1288 1289 /** 1290 * Performs slide transition to the specified slide 1291 * 1292 * @param slideIndex (int) 1293 * - the destination slide’s index (zero-based) 1294 * 1295 * @param direction (string) 1296 * - INTERNAL USE ONLY - the direction of travel (“prev” / “next”) 1297 */ 1298 el.goToSlide = function(slideIndex, direction) { 1299 // onSlideBefore, onSlideNext, onSlidePrev callbacks 1300 // Allow transition canceling based on returned value 1301 var performTransition = true, 1302 moveBy = 0, 1303 position = {left: 0, top: 0}, 1304 lastChild = null, 1305 lastShowingIndex, eq, value, requestEl; 1306 1307 // if plugin is currently in motion, ignore request 1308 if (slider.working || slider.active.index === slideIndex) { return; } 1309 // declare that plugin is in motion 1310 slider.working = true; 1311 // store the old index 1312 slider.oldIndex = slider.active.index; 1313 // if slideIndex is less than zero, set active index to last child (this happens during infinite loop) 1314 if (slideIndex < 0) { 1315 slider.active.index = getPagerQty() - 1; 1316 // if slideIndex is greater than children length, set active index to 0 (this happens during infinite loop) 1317 } else if (slideIndex >= getPagerQty()) { 1318 slider.active.index = 0; 1319 // set active index to requested slide 1320 } else { 1321 slider.active.index = slideIndex; 1322 } 1323 1324 performTransition = slider.settings.onSlideBefore.call(el, slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index); 1325 1326 if (typeof (performTransition) !== ‘undefined’ && !performTransition) { 1327 slider.active.index = slider.oldIndex; // restore old index 1328 slider.working = false; // is not in motion 1329 return; 1330 } 1331 if (direction === ‘next’) { 1332 // Prevent canceling in future functions or lack there-of from negating previous commands to cancel 1333 if (!slider.settings.onSlideNext.call(el, slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index)) { 1334 performTransition = false; 1335 } 1336 } else if (direction === ‘prev’) { 1337 // Prevent canceling in future functions or lack there-of from negating previous commands to cancel 1338 if (!slider.settings.onSlidePrev.call(el, slider.children.eq(slider.active.index), slider.oldIndex, slider.active.index)) { 1339 performTransition = false; 1340 } 1341 } 1342 1343 // If transitions canceled, reset and return 1344 if (typeof (performTransition) !== ‘undefined’ && !performTransition) { 1345 slider.active.index = slider.oldIndex; // restore old index 1346 slider.working = false; // is not in motion 1347 return; 1348 } 1349 1350 // check if last slide 1351 slider.active.last = slider.active.index >= getPagerQty() - 1; 1352 // update the pager with active class 1353 if (slider.settings.pager || slider.settings.pagerCustom) { updatePagerActive(slider.active.index); } 1354 // // check for direction control update 1355 if (slider.settings.controls) { updateDirectionControls(); } 1356 // if slider is set to mode: “fade” 1357 if (slider.settings.mode === ‘fade’) { 1358 // if adaptiveHeight is true and next height is different from current height, animate to the new height 1359 if (slider.settings.adaptiveHeight && slider.viewport.height() !== getViewportHeight()) { 1360 slider.viewport.animate({height: getViewportHeight()}, slider.settings.adaptiveHeightSpeed); 1361 } 1362 // fade out the visible child and reset its z-index value 1363 slider.children.filter(‘:visible’).fadeOut(slider.settings.speed).css({zIndex: 0}); 1364 // fade in the newly requested slide 1365 slider.children.eq(slider.active.index).css('zIndex’, slider.settings.slideZIndex + 1).fadeIn(slider.settings.speed, function() { 1366 $(this).css('zIndex’, slider.settings.slideZIndex); 1367 updateAfterSlideTransition(); 1368 }); 1369 // slider mode is not “fade” 1370 } else { 1371 // if adaptiveHeight is true and next height is different from current height, animate to the new height 1372 if (slider.settings.adaptiveHeight && slider.viewport.height() !== getViewportHeight()) { 1373 slider.viewport.animate({height: getViewportHeight()}, slider.settings.adaptiveHeightSpeed); 1374 } 1375 // if carousel and not infinite loop 1376 if (!slider.settings.infiniteLoop && slider.carousel && slider.active.last) { 1377 if (slider.settings.mode === ‘horizontal’) { 1378 // get the last child position 1379 lastChild = slider.children.eq(slider.children.length - 1); 1380 position = lastChild.position(); 1381 // calculate the position of the last slide 1382 moveBy = slider.viewport.width() - lastChild.outerWidth(); 1383 } else { 1384 // get last showing index position 1385 lastShowingIndex = slider.children.length - slider.settings.minSlides; 1386 position = slider.children.eq(lastShowingIndex).position(); 1387 } 1388 // horizontal carousel, going previous while on first slide (infiniteLoop mode) 1389 } else if (slider.carousel && slider.active.last && direction === ‘prev’) { 1390 // get the last child position 1391 eq = slider.settings.moveSlides === 1 ? slider.settings.maxSlides - getMoveBy() : ((getPagerQty() - 1) * getMoveBy()) - (slider.children.length - slider.settings.maxSlides); 1392 lastChild = el.children(‘.bx-clone’).eq(eq); 1393 position = lastChild.position(); 1394 // if infinite loop and “Next” is clicked on the last slide 1395 } else if (direction === ‘next’ && slider.active.index === 0) { 1396 // get the last clone position 1397 position = el.find(‘> .bx-clone’).eq(slider.settings.maxSlides).position(); 1398 slider.active.last = false; 1399 // normal non-zero requests 1400 } else if (slideIndex >= 0) { 1401 requestEl = slideIndex * getMoveBy(); 1402 position = slider.children.eq(requestEl).position(); 1403 } 1404 1405 /* If the position doesn’t exist 1406 * (e.g. if you destroy the slider on a next click), 1407 * it doesn’t throw an error. 1408 */ 1409 if (typeof (position) !== undefined) { 1410 value = slider.settings.mode === ‘horizontal’ ? -(position.left - moveBy) : -position.top; 1411 // plugin values to be animated 1412 setPositionProperty(value, 'slide’, slider.settings.speed); 1413 } 1414 } 1415 if (slider.settings.ariaHidden) { applyAriaHiddenAttributes(slider.active.index * getMoveBy()); } 1416 }; 1417 1418 /** 1419 * Transitions to the next slide in the show 1420 */ 1421 el.goToNextSlide = function() { 1422 // if infiniteLoop is false and last page is showing, disregard call 1423 if (!slider.settings.infiniteLoop && slider.active.last) { return; } 1424 var pagerIndex = parseInt(slider.active.index) + 1; 1425 el.goToSlide(pagerIndex, ‘next’); 1426 }; 1427 1428 /** 1429 * Transitions to the prev slide in the show 1430 */ 1431 el.goToPrevSlide = function() { 1432 // if infiniteLoop is false and last page is showing, disregard call 1433 if (!slider.settings.infiniteLoop && slider.active.index === 0) { return; } 1434 var pagerIndex = parseInt(slider.active.index) - 1; 1435 el.goToSlide(pagerIndex, ‘prev’); 1436 }; 1437 1438 /** 1439 * Starts the auto show 1440 * 1441 * @param preventControlUpdate (boolean) 1442 * - if true, auto controls state will not be updated 1443 */ 1444 el.startAuto = function(preventControlUpdate) { 1445 // if an interval already exists, disregard call 1446 if (slider.interval) { return; } 1447 // create an interval 1448 slider.interval = setInterval(function() { 1449 if (slider.settings.autoDirection === ‘next’) { 1450 el.goToNextSlide(); 1451 } else { 1452 el.goToPrevSlide(); 1453 } 1454 }, slider.settings.pause); 1455 // if auto controls are displayed and preventControlUpdate is not true 1456 if (slider.settings.autoControls && preventControlUpdate !== true) { updateAutoControls(‘stop’); } 1457 }; 1458 1459 /** 1460 * Stops the auto show 1461 * 1462 * @param preventControlUpdate (boolean) 1463 * - if true, auto controls state will not be updated 1464 */ 1465 el.stopAuto = function(preventControlUpdate) { 1466 // if no interval exists, disregard call 1467 if (!slider.interval) { return; } 1468 // clear the interval 1469 clearInterval(slider.interval); 1470 slider.interval = null; 1471 // if auto controls are displayed and preventControlUpdate is not true 1472 if (slider.settings.autoControls && preventControlUpdate !== true) { updateAutoControls(‘start’); } 1473 }; 1474 1475 /** 1476 * Returns current slide index (zero-based) 1477 */ 1478 el.getCurrentSlide = function() { 1479 return slider.active.index; 1480 }; 1481 1482 /** 1483 * Returns current slide element 1484 */ 1485 el.getCurrentSlideElement = function() { 1486 return slider.children.eq(slider.active.index); 1487 }; 1488 1489 /** 1490 * Returns a slide element 1491 * @param index (int) 1492 * - The index (zero-based) of the element you want returned. 1493 */ 1494 el.getSlideElement = function(index) { 1495 return slider.children.eq(index); 1496 }; 1497 1498 /** 1499 * Returns number of slides in show 1500 */ 1501 el.getSlideCount = function() { 1502 return slider.children.length; 1503 }; 1504 1505 /** 1506 * Return slider.working variable 1507 */ 1508 el.isWorking = function() { 1509 return slider.working; 1510 }; 1511 1512 /** 1513 * Update all dynamic slider elements 1514 */ 1515 el.redrawSlider = function() { 1516 // resize all children in ratio to new screen size 1517 slider.children.add(el.find(‘.bx-clone’)).outerWidth(getSlideWidth()); 1518 // adjust the height 1519 slider.viewport.css('height’, getViewportHeight()); 1520 // update the slide position 1521 if (!slider.settings.ticker) { setSlidePosition(); } 1522 // if active.last was true before the screen resize, we want 1523 // to keep it last no matter what screen size we end on 1524 if (slider.active.last) { slider.active.index = getPagerQty() - 1; } 1525 // if the active index (page) no longer exists due to the resize, simply set the index as last 1526 if (slider.active.index >= getPagerQty()) { slider.active.last = true; } 1527 // if a pager is being displayed and a custom pager is not being used, update it 1528 if (slider.settings.pager && !slider.settings.pagerCustom) { 1529 populatePager(); 1530 updatePagerActive(slider.active.index); 1531 } 1532 if (slider.settings.ariaHidden) { applyAriaHiddenAttributes(slider.active.index * getMoveBy()); } 1533 }; 1534 1535 /** 1536 * Destroy the current instance of the slider (revert everything back to original state) 1537 */ 1538 el.destroySlider = function() { 1539 // don’t do anything if slider has already been destroyed 1540 if (!slider.initialized) { return; } 1541 slider.initialized = false; 1542 $('.bx-clone’, this).remove(); 1543 slider.children.each(function() { 1544 if ($(this).data(‘origStyle’) !== undefined) { 1545 $(this).attr('style’, $(this).data(‘origStyle’)); 1546 } else { 1547 $(this).removeAttr(‘style’); 1548 } 1549 }); 1550 if ($(this).data(‘origStyle’) !== undefined) { 1551 this.attr('style’, $(this).data(‘origStyle’)); 1552 } else { 1553 $(this).removeAttr(‘style’); 1554 } 1555 $(this).unwrap().unwrap(); 1556 if (slider.controls.el) { slider.controls.el.remove(); } 1557 if (slider.controls.next) { slider.controls.next.remove(); } 1558 if (slider.controls.prev) { slider.controls.prev.remove(); } 1559 if (slider.pagerEl && slider.settings.controls && !slider.settings.pagerCustom) { slider.pagerEl.remove(); } 1560 $('.bx-caption’, this).remove(); 1561 if (slider.controls.autoEl) { slider.controls.autoEl.remove(); } 1562 clearInterval(slider.interval); 1563 if (slider.settings.responsive) { $(window).unbind('resize’, resizeWindow); } 1564 if (slider.settings.keyboardEnabled) { $(document).unbind('keydown’, keyPress); } 1565 //remove self reference in data 1566 $(this).removeData(‘bxSlider’); 1567 }; 1568 1569 /** 1570 * Reload the slider (revert all DOM changes, and re-initialize) 1571 */ 1572 el.reloadSlider = function(settings) { 1573 if (settings !== undefined) { options = settings; } 1574 el.destroySlider(); 1575 init(); 1576 //store reference to self in order to access public functions later 1577 $(el).data('bxSlider’, this); 1578 }; 1579 1580 init(); 1581 1582 $(el).data('bxSlider’, this); 1583 1584 // returns the current jQuery object 1585 return this; 1586 };