1/* 2Terminology: 3 4min/max slide width : Defines the threshold for allowable widths of the slides. If they get too 5 narrow or too wide, the carousel is resized. 6 7gutter width : Defines the gap between slides at load. Can change slightly when carousel 8 flexes in width after load. 9 10Teaser : If there are n (where n >= 3) slides visible in a set of slides with length 11 'count', this option: 12 - if circular, moves slides[count] to be slides[0] 13 - chops off the first half of slides[0] and the last half of of slides[n] by 14 shifting the slider to the left by half a slide width 15 - places the arrows between slides[0] & slides[count] & slides[n-1] & slides[n] 16 17Circular : By default, the next/previous arrows are disabled if there are no more slides 18 to navigate to. If there are more slides than can be be displayed at once, this 19 option forces the next/previous arrows to always navigate to more slides. Also, 20 if a set of slides does not divide evenly into n groups, this option fills 21 the remaining slots with duplicate slides 22 23n : For any given width of the carousel, n is the maximum number of 24 _whole_ slides visible. 25 26j : Index of the currently visible slide in slot "1" 27 28Scenario [A] : count <= n 29 1+ slides visible, no arrows 30 { [1] [2] } 31 32Scenario [B] : count > n && ( n < 3 || !teaser ) 33 1+ slides visible, arrows 34 { < [1] [2] > } 35 36Scenario [C] : count > n && n >= 3 && teaser 37 3+ (whole) slides visible, arrows, teaser 38 {4] < [1] [2] > [3} 39 40*/ 41 42; (function ($, window, undefined) { 43 'use strict'; 44 45 var DEBUG = false; 46 var diag = function (msg) { 47 if (DEBUG && window.console) { 48 window.console.log('[ AdaptiveCarousel ] ' + msg); 49 } 50 }; 51 52 // Create the defaults once 53 var pluginName = "adaptiveCarousel", 54 defaults = { 55 // Defaults 56 minSlideWidth: 180, // in px 57 maxSlideWidth: 300, // in px 58 gutterWidth: 40, // in px 59 transitionTiming: 500, // in ms 60 easeMethod: 'easeInOutExpo', // Choose from: http://easings.net/ or http://gsgd.co.uk/sandbox/jquery/easing/ 61 teaser: false, // See Terminology 62 circular: false // See Terminology 63 }; 64 65 66 // The actual plugin constructor 67 function AdaptiveCarousel(options, element) { 68 var self = this; 69 70 this.element = element; 71 this._defaults = defaults; 72 this._name = pluginName; 73 this.settings = $.extend({}, defaults, options); 74 75 this.$carousel = $(this.element); 76 this.$window = self.$carousel.find('.cpWindow'); 77 this.$slider = self.$carousel.find('.cpSlider'); 78 this.slides = self.$slider.find('li'); 79 this.$firstSlide = self.$slider.find('li:first-child'); 80 this.count = self.slides.length; 81 this.origCount = self.count; 82 this.n = 0; 83 this.j = 0; 84 85 // Label the original slides on first run 86 for (var i = 0; i < self.count; i++) { 87 $(self.slides[i]).addClass('slide' + i); 88 } 89 90 // Initialize 91 this.init(); 92 93 this.$carousel.delegate('.arrow', 'click', function (e) { 94 e.preventDefault(); 95 diag('Before moving, j = ' + self.j); 96 if ($(this).hasClass('prev')) { 97 self.moveBack(); 98 } else if ($(this).hasClass('next')) { 99 self.moveForward(); 100 } 101 diag('After moving, j = ' + self.j); 102 }); 103 104 //Support swiping if framework is already loaded. Otherwise, it should take care on invoke after it is loaded (use: window.Pages.rwdReady) 105 if (typeof ($$) !== 'undefined' && self.$carousel.attr('id')) { 106 $$('#' + self.$carousel.attr('id')).on('swipeRight', function (e, a) { 107 self.adaptiveCarousel("moveBack"); 108 }); 109 $$('#' + self.$carousel.attr('id')).on('swipeLeft', function (e) { 110 self.adaptiveCarousel("moveForward"); 111 }); 112 } 113 114 // Recalculate the number of slides displayed on window resize 115 $(window).smartresize(function () { 116 self.onResize(); 117 }); 118 } 119 120 AdaptiveCarousel.prototype = { 121 init: function () { 122 123 // local variables 124 var self = this; 125 126 self.n = findN(); 127 sizeTheSlides(whichScenario()); 128 129 if (!self.settings.circular) 130 self.setArrowState(); 131 132 self.initImageNextAndPrev(); 133 134 // Finding the appropriate number of slides to display at one time 135 function findN() { 136 var n = 0; 137 if (self.$window.width() >= (self.settings.minSlideWidth + self.settings.gutterWidth)) { 138 var carouselWidth = self.$carousel.width(); 139 n = Math.floor((carouselWidth - self.settings.gutterWidth - self.settings.minSlideWidth) / (self.settings.gutterWidth + self.settings.minSlideWidth)); 140 } 141 return n > 0 ? n : 1; 142 } 143 144 // Determining which of the three scenarios (see Terminology) the carousel fits 145 function whichScenario() { 146 diag('n = ' + self.n); 147 if (self.count <= self.n) { 148 return 'A'; 149 } else { 150 if (self.n < 3 || !self.settings.teaser) { 151 return 'B'; 152 } else { 153 return 'C'; 154 } 155 } 156 } 157 158 // The heart of the script 159 function sizeTheSlides(scenario) { 160 /* Note: This may seem like excessive if statements, but the scenarios make it more clear 161 and many of these operation must occur in a specific order. 162 */ 163 164 var carouselWidth = self.$carousel.width(); 165 var sliderWidth; 166 if (scenario === 'A') { 167 diag('Fits [A]'); 168 sliderWidth = carouselWidth; 169 } else { 170 if (scenario === 'B') { 171 diag('Fits [B]'); 172 // Make sure there's room for the arrow (gutterWidth) on either side of the first/last 173 // visible slides 174 var windowMargin; 175 //The value below is based on pure trial/error to fix an issue with IE in cotext to carousel display 176 //https://system.netsuite.com/app/crm/support/issuedb/issue.nl?id=3211942&l=T 177 //IE basically rounds up the math calculations to 2 decimal places causing some display issues. 178 //Adding the error correction value gives the carousel arrows enough space to display right and not overlap 179 //the images. 180 var ieErrorCorrectionValue = .0095; 181 if (/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/.test(navigator.userAgent)) { 182 windowMargin = (((self.settings.gutterWidth / (2 * carouselWidth))) + ieErrorCorrectionValue) * 100; 183 } else { 184 windowMargin = ((self.settings.gutterWidth / (2 * carouselWidth))) * 100; 185 } 186 self.$window.css({ 187 margin: '0 ' + windowMargin + '%', 188 width: (100 - windowMargin * 2) + '%' 189 });
190 } 191 192 diag('Fits [B] or [C]'); 193 sliderWidth = self.$slider.width(); 194 } 195 196 var margin = (self.settings.gutterWidth / sliderWidth) * 100; 197 198 if (scenario === 'B' || scenario === 'C') { 199 diag('Fits [B] or [C]'); 200 if (self.settings.circular) { 201 diag('is circular'); 202 203 // Make clones 204 var startClones = self.slides.slice(self.count - self.n, self.count).clone().addClass('clone'), 205 endClones = self.slides.slice(0, self.n).clone().addClass('clone'); 206 207 // Attach clones 208 self.$slider.prepend(startClones); 209 self.$slider.append(endClones); 210 211 // Adjust starting position and count 212 self.j = self.n; 213 self.slides = self.$slider.find('li'); 214 self.count = self.slides.length; 215 } 216 217 // Adjust margin to be relative to n/count 218 margin = margin * (self.n / self.count); 219 220 // Adjust slider to hold slides in a row 221 self.$slider.width(self.count * (100 / self.n) + '%'); 222 sliderWidth = self.$slider.width(); 223 224 /* Couldn't quite figure this out 225 if ( Modernizr.csstransitions ) { 226 self.$window.before('<style>.js .animated .cpSlider { -webkit-transition-duration: ' + self.settings.transitionTiming + 'ms; transition-duration: ' + self.settings.transitionTiming + 'ms; }</style>'); 227 self.$carousel.addClass('animated'); 228 } 229 */ 230 self.$window.after('<a href="#" class="arrow next"><span class="visuallyHidden">Next</span></a><a href="#" class="arrow prev"><span class="visuallyHidden">Previous</span></a>'); 231 } 232 233 if (scenario === 'A' || scenario === 'B') { 234 235 diag('Fits [A] or [B]'); 236 237 var slideWidth = (100 - self.count * margin) / self.count; 238 self.slides.outerWidth(slideWidth + '%'); 239 240 var firstSlideWidth = self.$firstSlide.outerWidth(); 241 242 if (firstSlideWidth > self.settings.maxSlideWidth || firstSlideWidth < self.settings.minSlideWidth) { 243 if (firstSlideWidth > self.settings.maxSlideWidth) { 244 diag('Slides too wide'); 245 slideWidth = (self.settings.maxSlideWidth / sliderWidth) * 100; 246 } else { 247 diag('Slides too narrow'); 248 //Only enforce minimum width if margins wouldn't become negative 249 //Formula is PossibleNewMargin = (100 - self.count * PossibleNewSlideWidth) / self.count 250 if (((100 - self.count * ((self.settings.minSlideWidth / sliderWidth) * 100)) / self.count) > 0) { 251 slideWidth = (self.settings.minSlideWidth / sliderWidth) * 100; 252 } 253 } 254 self.slides.outerWidth(slideWidth + '%'); 255 margin = (100 - self.count * slideWidth) / self.count; 256 } 257 258 if (scenario === 'B') { 259 diag('Fits [B]'); 260 self.moveTheSlides('dir', 0); 261 self.$carousel.find('.arrow').width((self.settings.gutterWidth / carouselWidth) * 100 + '%'); 262 } 263 264 } else { 265 diag('Fits [C]'); 266 self.slides.outerWidth((100 - self.count * margin) / self.count + '%'); 267 self.$carousel.addClass('teaser'); 268 self.moveTheSlides('dir', 0); 269 self.$carousel.find('.arrow').width(((self.$firstSlide.outerWidth() / 2 + self.settings.gutterWidth / 2) / carouselWidth) * 100 + '%'); 270 } 271 272 self.slides.css('margin', '0 ' + margin / 2 + '%'); 273 } 274 }, 275 moveTheSlides: function (dir, speed) { 276 277 var self = this, 278 m = self.n, 279 offset = 0; 280 if (self.$carousel.hasClass('teaser')) { 281 m--; 282 offset = 0.5; 283 } 284 285 if (dir === 'back') { 286 self.j = self.j - m; 287 if (!self.settings.circular && self.j < 0) { 288 self.j = 0; 289 } 290 } else if (dir === 'forward') { 291 self.j = self.j + m; 292 if (!self.settings.circular && self.j > (self.count - m)) { 293 self.j = self.count - m; 294 } 295 } 296 297 var left = (-self.j + offset) * (100 / self.n); 298 299 /* Couldn't quite figure this out 300 if ( !Modernizr.csstransitions ) { 301 */ 302 303 self.$slider.animate({ 304 left: left + '%', 305 }, speed, self.settings.easeMethod); 306 /* Couldn't quite figure this out 307 } else { 308 309 if ( speed === 0 ) { 310 self.$carousel.removeClass('animated'); 311 } 312 self.$slider.css('left', left + '%'); 313 if ( speed === 0 ) { 314 setTimeout( function() { self.$carousel.addClass('animated') }, self.settings.transitionTiming ); 315 } 316 } 317 */ 318 319 if (dir === 'forward' || dir === 'back') { 320 if (self.settings.circular) { 321 322 // Checking to see if we're past the beginning or end of a circular carousel 323 if ((self.j < self.n) || (self.j > self.origCount)) { 324 325 if (self.j < self.n) { 326 diag("We're past the beginning"); 327 self.j = self.j + self.origCount; 328 } else { 329 diag("We're past the end"); 330 self.j = self.j - self.origCount; 331 } 332 333 // Switch to non-cloned position, instantly, after a delay equal to transition time 334 setTimeout(self.moveTheSlides('dir', 0), self.settings.transitionTiming); 335 } 336 } else { 337 diag('Set arrow state after movement'); 338 self.setArrowState(); 339 } 340 } 341 342 self.initImageNextAndPrev(); 343 344 return self.j; 345 346 },
347 // Sometimes the arrows need to enabled/disabled 348 setArrowState: function () { 349 var self = this; 350 self.$carousel.find('.arrow').removeClass('disabled'); 351 if (self.j === 0) { 352 self.$carousel.find('.arrow.prev').addClass('disabled'); 353 } else if (self.j >= (self.count - self.n)) { 354 self.$carousel.find('.arrow.next').addClass('disabled'); 355 } 356 }, 357 onResize: function () { 358 var self = this; 359 if (self.$firstSlide.outerWidth() < self.settings.minSlideWidth || self.$firstSlide.outerWidth() >= self.settings.maxSlideWidth) { 360 diag('Resize!'); 361 362 // Resetting things back to a fresh, known state for calculations 363 self.$window.css({ 364 margin: '0', 365 width: '100%' 366 }); 367 self.$slider.css({ 368 left: '0', 369 width: '100%' 370 }); 371 372 self.$carousel.removeClass('teaser'); 373 self.$carousel.find('.clone').remove(); 374 self.$carousel.find('.arrow').remove(); 375 self.slides = self.$slider.find('li'); 376 self.count = self.origCount; 377 378 // Re-initialize 379 self.init(); 380 } 381 }, 382 moveForward: function () { 383 var self = this; 384 if (self.$carousel.find('a.arrow.next').length == 1) 385 self.moveTheSlides('forward', self.settings.transitionTiming); 386 }, 387 moveBack: function () { 388 var self = this; 389 if (self.$carousel.find('a.arrow.prev').length == 1) 390 self.moveTheSlides('back', self.settings.transitionTiming); 391 }, 392 initImageNextAndPrev: function () { 393 var self = this; 394 395 var loadImage = function (widgetItem) { 396 var image = $(widgetItem).find('img'); 397 398 if (image.length !== 0) { 399 var $this = $(image), 400 delayLoadUrl = $this.attr('data-delay-load'), 401 imgSrc = $this.attr('src'); 402 403 if (delayLoadUrl !== imgSrc) { 404 if (delayLoadUrl !== undefined && delayLoadUrl !== '' && delayLoadUrl !== null) { 405 var imageUrl = document.createElement('a'); 406 imageUrl.href = delayLoadUrl; 407 408 if (imageUrl.protocol === 'http:' || imageUrl.protocol === 'https:') { 409 for (var imageIndex = 0; imageIndex < image.length; imageIndex++) { 410 image[imageIndex].src = imageUrl.href; 411 } 412 $this.removeAttr('data-delay-load'); 413 } 414 } 415 } 416 } 417 }; 418 419 if ($("img[data-delay-load]", self.element).length > 0) { 420 var $widgetItems = $("li.widgetItem", self.element); 421 422 for (var i = 0; i <= self.n; i++) { 423 var forward = self.j + i; 424 425 if (forward > $widgetItems.length) { 426 var diff = forward - $widgetItems.length; 427 loadImage($widgetItems[diff]); 428 } 429 else { 430 loadImage($widgetItems[forward]); 431 } 432 433 if (self.settings.circular) { 434 var backward = self.j - i; 435 436 if (backward < 0) { 437 var diff = $widgetItems.length + backward; 438 loadImage($widgetItems[diff]); 439 } 440 else { 441 loadImage($widgetItems[backward]); 442 } 443 } 444 } 445 } 446 } 447 }; 448 449 /* jQuery Plugin Support */ 450 $.fn.adaptiveCarousel = function (options) { 451 if (typeof options === 'string') { 452 var args = Array.prototype.slice.call(arguments, 1); 453 this.each(function () { 454 var instance = $.data(this, 'adaptiveCarousel'); 455 if (!instance) { 456 diag("cannot call methods on adaptiveCarousel prior to initialization; " + 457 "attempted to call method '" + options + "'"); 458 return; 459 } 460 if (!$.isFunction(instance[options]) || options.charAt(0) === "_") { 461 diag("no such method '" + options + "' for adaptiveCarousel instance"); 462 return; 463 } 464 instance[options].apply(instance, args); 465 }); 466 } 467 else { 468 this.each(function () { 469 var instance = $.data(this, 'adaptiveCarousel'); 470 if (instance) { 471 instance.init(options); 472 } 473 else { 474 instance = $.data(this, 'adaptiveCarousel', new AdaptiveCarousel(options, this)); 475 } 476 }); 477 } 478 479 return this; 480 }; 481 482})(jQuery, window); 483 484(function ($, sr) { 485 486 // debouncing function from John Hann 487 // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ 488 var debounce = function (func, threshold, execAsap) { 489 var timeout; 490 491 return function debounced() { 492 var obj = this, args = arguments; 493 function delayed() { 494 if (!execAsap) 495 func.apply(obj, args); 496 timeout = null; 497 }; 498 499 if (timeout) 500 clearTimeout(timeout); 501 else if (execAsap) 502 func.apply(obj, args); 503 504 timeout = setTimeout(delayed, threshold || 100); 505 }; 506 }; 507 // smartresize 508 jQuery.fn[sr] = function (fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); }; 509 510})(jQuery, 'smartresize');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.