1/*! 2 * Feature Carousel, Version 1.0 3 * http://www.bkosolutions.com 4 * 5 * Copyright 2010 Brian Osborne 6 * Licensed under GPL version 3 7 * 8 * http://www.gnu.org/licenses/gpl.txt 9 */ 10(function($) { 11 12 $.fn.featureCarousel = function (options) { 13 14 // override the default options with user defined options 15 options = $.extend({}, $.fn.featureCarousel.defaults, options || {}); 16 17 return $(this).each(function () { 18 19 /* These are univeral values that are used throughout the plugin. Do not modify them 20 * unless you know what you're doing. Most of them feed off the options 21 * so most customization can be achieved by modifying the options values */ 22 var pluginData = { 23 currentCenterNum: options.startingFeature, 24 containerWidth: 0, 25 containerHeight: 0, 26 largeFeatureWidth: 0, 27 largeFeatureHeight: 0, 28 smallFeatureWidth: 0, 29 smallFeatureHeight: 0, 30 totalFeatureCount: $(this).children("div").length, 31 currentlyMoving: false, 32 featuresContainer: $(this), 33 featuresArray: [], 34 containerIDTag: "#"+$(this).attr("id"), 35 timeoutVar: null, 36 rotationsRemaining: 0, 37 itemsToAnimate: 0, 38 borderWidth: 0 39 }; 40 41 setupFeatureDimensions(); 42 setupCarousel(); 43 setupFeaturePositions(); 44 setupBlips(); 45 initiateMove(true,1); 46 47 /** 48 * Function to preload the images in the carousel if desired. 49 * This is not recommended if there are a lot of images in the carousel because 50 * it may take a while. Functionality does not depend on preloading the images 51 */ 52 function preload(callback) { 53 // user may not want to preload images 54 if (options.preload == true) { 55 var $imageElements = pluginData.featuresContainer.find("img"); 56 var loadedImages = 0; 57 var totalImages = $imageElements.length; 58 59 $imageElements.each(function () { 60 // Attempt to load the images 61 $(this).load(function () { 62 // Add to number of images loaded and see if they are all done yet 63 loadedImages++; 64 if (loadedImages == totalImages) { 65 // All done, perform callback 66 callback(); 67 } 68 }); 69 // The images may already be cached in the browser, in which case they 70 // would have a 'true' complete value and the load callback would never be 71 // fired. This will fire it manually. 72 if (this.complete) { 73 $(this).trigger('load'); 74 } 75 }); 76 } else { 77 // if user doesn't want preloader, then just go right to callback 78 callback(); 79 } 80 } 81 82 // Gets the feature container based on the number 83 function getContainer(featureNum) { 84 return pluginData.featuresArray[featureNum-1]; 85 } 86 87 // get a feature given it's set position (the position that doesn't change) 88 function getBySetPos(position) { 89 $.each(pluginData.featuresArray, function () { 90 if ($(this).data().setPosition == position) 91 return $(this); 92 }); 93 } 94 95 // get previous feature number 96 function getPreviousNum(num) { 97 if ((num - 1) == 0) { 98 return pluginData.totalFeatureCount; 99 } else { 100 return num - 1; 101 } 102 } 103 104 // get next feature number 105 function getNextNum(num) { 106 if ((num + 1) > pluginData.totalFeatureCount) { 107 return 1; 108 } else { 109 return num + 1; 110 } 111 } 112 113 /** 114 * Because there are several options the user can set for the width and height 115 * of the feature images, this function is used to determine which options were set 116 * and to set the appropriate dimensions used for a small and large feature 117 */ 118 function setupFeatureDimensions() { 119 // Set the height and width of the entire carousel container 120 pluginData.containerWidth = pluginData.featuresContainer.outerWidth(); 121 pluginData.containerHeight = pluginData.featuresContainer.outerHeight(); 122 123 // Grab the first image for reference 124 var $firstFeatureImage = $(pluginData.containerIDTag).find("div img:first"); 125 126 // Large Feature Width 127 if (options.largeFeatureWidth > 1) 128 pluginData.largeFeatureWidth = options.largeFeatureWidth; 129 else if (options.largeFeatureWidth > 0 && options.largeFeatureWidth < 1) 130 pluginData.largeFeatureWidth = $firstFeatureImage.
130width() * options.largeFeatureWidth; 131 else 132 pluginData.largeFeatureWidth = $firstFeatureImage.outerWidth(); 133 // Large Feature Height 134 if (options.largeFeatureHeight > 1) 135 pluginData.largeFeatureHeight = options.largeFeatureHeight; 136 else if (options.largeFeatureHeight > 0 && options.largeFeatureHeight < 1) 137 pluginData.largeFeatureHeight = $firstFeatureImage.height() * options.largeFeatureHeight; 138 else 139 pluginData.largeFeatureHeight = $firstFeatureImage.outerHeight(); 140 // Small Feature Width 141 if (options.smallFeatureWidth > 1) 142 pluginData.smallFeatureWidth = options.smallFeatureWidth; 143 else if (options.smallFeatureWidth > 0 && options.smallFeatureWidth < 1) 144 pluginData.smallFeatureWidth = $firstFeatureImage.width() * options.smallFeatureWidth; 145 else 146 pluginData.smallFeatureWidth = $firstFeatureImage.outerWidth() / 2; 147 // Small Feature Height 148 if (options.smallFeatureHeight > 1) 149 pluginData.smallFeatureHeight = options.smallFeatureHeight; 150 else if (options.smallFeatureHeight > 0 && options.smallFeatureHeight < 1) 151 pluginData.smallFeatureHeight = $firstFeatureImage.height() * options.smallFeatureHeight; 152 else 153 pluginData.smallFeatureHeight = $firstFeatureImage.outerHeight() / 2; 154 } 155 156 /** 157 * Function to take care of setting up various aspects of the carousel, 158 * most importantly the default positions for the features 159 */ 160 function setupCarousel() { 161 // Set the total feature count to the amount the user wanted to cutoff 162 if (options.displayCutoff > 0 && options.displayCutoff < pluginData.totalFeatureCount) { 163 pluginData.totalFeatureCount = options.displayCutoff; 164 } 165 166 // fill in the features array 167 pluginData.featuresContainer.children("div").each(function (index) { 168 if (index < pluginData.totalFeatureCount) { 169 pluginData.featuresArray[index] = $(this); 170 } 171 }); 172 173 // Determine the total border width around the feature if there is one 174 if (pluginData.featuresContainer.children("div").first().css("borderLeftWidth") != "medium") { 175 pluginData.borderWidth = parseInt(pluginData.featuresContainer.children("div").first().css("borderLeftWidth"))*2; 176 } 177 178 // Place all the features in a center hidden position to start off 179 pluginData.featuresContainer 180 // Have to make the container relative positioning 181 .children("div").each(function () { 182 // Center all the features in the middle and hide them 183 $(this).css({ 184 'left': (pluginData.containerWidth / 2) - (pluginData.smallFeatureWidth / 2) - (pluginData.borderWidth / 2), 185 'width': pluginData.smallFeatureWidth, 186 'height': pluginData.smallFeatureHeight, 187 'top': options.smallFeatureOffset + options.topPadding, 188 'opacity': 0 189 }); 190 }) 191 // Set all the images to small feature size 192 .find("img:first").css({ 193 'width': pluginData.smallFeatureWidth 194 }); 195 196 // figure out number of items that will rotate each time 197 if (pluginData.totalFeatureCount < 4) { 198 pluginData.itemsToAnimate = pluginData.totalFeatureCount; 199 } else { 200 pluginData.itemsToAnimate = 4; 201 } 202 203 // Hide story info and set the proper positioning 204 pluginData.featuresContainer.find("div > div") 205 .hide(); 206 } 207 208 /** 209 * Here all the position data is set for the features. 210 * This is an important part of the carousel to keep track of where 211 * each feature within the carousel is 212 */ 213 function setupFeaturePositions() {
214 // give all features a set number that won't change so they remember their 215 // original order 216 $.each(pluginData.featuresArray, function (i) { 217 $(this).data('setPosition',i+1); 218 }); 219 220 // Go back one - This is done because we call the move function right away, which 221 // shifts everything to the right. So we set the current center back one, so that 222 // it displays in the center when that happens 223 var oneBeforeStarting = getPreviousNum(options.startingFeature); 224 pluginData.currentCenterNum = oneBeforeStarting; 225 226 // Center feature will be position 1 227 var $centerFeature = getContainer(oneBeforeStarting); 228 $centerFeature.data('position',1); 229 230 // Everything before that center feature... 231 var $prevFeatures = $centerFeature.prevAll(); 232 $prevFeatures.each(function (i) { 233 $(this).data('position',(pluginData.totalFeatureCount - i
233)); 234 }); 235 236 // And everything after that center feature... 237 var $nextFeatures = $centerFeature.nextAll(); 238 $nextFeatures.each(function (i) { 239 if ($(this).data('setPosition') != undefined) { 240 $(this).data('position',(i + 2)); 241 } 242 }); 243 244 // if the counter style is for including number tags in description... 245 if (options.counterStyle == 3) { 246 $.each(pluginData.featuresArray, function () { 247 var pos = getPreviousNum($(this).data('position')); 248 var $numberTag = $("<span></span>"); 249 $numberTag.addClass("numberTag"); 250 $numberTag.html("("+ pos + " of " + pluginData.totalFeatureCount + ") "); 251 $(this).find('div p').prepend($numberTag); 252 }); 253 } 254 } 255 256 /** 257 * The blips are built using this function. The position and look 258 * of the blips are completely determined by the CSS file 259 */ 260 function setupBlips() 261 { 262 // Only setup the blips if the counter style is 1 or 2 263 if (options.counterStyle == 1 || options.counterStyle == 2) { 264 // construct the blip list 265 var $list = $("<ul></ul>"); 266 $list.addClass("blipsContainer"); 267 for (var i = 0; i < pluginData.totalFeatureCount; i++) { 268 // Counter style 1 has no numbers, while 2 does 269 var counter; 270 if (options.counterStyle == 2) 271 counter = ""; 272 else 273 counter = i+1; 274 275 // Build the DOM for the blip list 276 var $blip = $("<div>"+counter+"</div>"); 277 $blip.addClass("blip"); 278 $blip.css("cursor","pointer"); 279 $blip.attr("id","blip_"+(i+1)); 280 var $listEntry = $("<li></li>"); 281 $listEntry.append($blip); 282 $listEntry.css("float","left"); 283 $listEntry.css("list-style-type","none"); 284 $list.append($listEntry); 285 } 286 // add the blip list and then make sure it's visible 287 $(pluginData.containerIDTag).append($list); 288 $list.hide().show(); 289 } 290 } 291 292 // Move the highlighted blip to the currently centered feature 293 function changeBlip(oldCenter, newCenter) 294 { 295 // get selectors for the two blips 296 var $blipsContainer = pluginData.featuresContainer.find(".blipsContainer"); 297 var $oldCenter = $blipsContainer.find("#blip_"+oldCenter); 298 var $newCenter = $blipsContainer.find("#blip_"+newCenter); 299 300 // change classes 301 $oldCenter.removeClass("blipSelected"); 302 $newCenter.addClass("blipSelected"); 303 } 304 305 /** 306 * This function will set the autoplay for the carousel to 307 * automatically rotate it given the time in the options 308 */ 309 function autoPlay() { 310 // clear the timeout var if it exists 311 if (pluginData.timeoutVar != null) { 312 pluginData.timeoutVar = clearTimeout(pluginData.timeoutVar); 313 } 314 315 // set interval for moving if autoplay is set 316 if (options.autoPlay != 0) { 317 var autoTime = (Math.abs(options.autoPlay) < options.carouselSpeed) ? options.carouselSpeed : Math.abs(options.autoPlay); 318 pluginData.timeoutVar = setTimeout(function () { 319 if (options.autoPlay > 0) 320 initiateMove(true,1); 321 else if (options.autoPlay < 0) 322 initiateMove(false,1); 323 }, autoTime); 324 } 325 } 326 327 // This is a helper function for the animateFeature function that 328 // will update the positions of all the features based on the direction 329 function rotatePositions(direction) { 330 $.each(pluginData.featuresArray, function () { 331 var newPos; 332 if (direction == false) { 333 newPos = getNextNum($(this).data().position); 334 } else { 335 newPos = getPreviousNum($(this).data().position); 336 } 337 $(this).data('position',newPos); 338 }); 339 } 340 341 /** 342 * This function is used to animate the given feature to the given 343 * location. Valid locations are "left", "right", "center", "hidden" 344 */
345 function animateFeature($feature, direction) 346 { 347 var new_width, new_height, new_top, new_left, new_zindex, new_padding, new_fade; 348 349 // Determine the old and new positions of the feature 350 var oldPosition = $feature.data('position'); 351 var newPosition; 352 if (direction == true) 353 newPosition = getPreviousNum(oldPosition); 354 else 355 newPosition = getNextNum(oldPosition); 356 357 // Caculate new new css values depending on where the feature will be located 358 if (newPosition == 1) { 359 new_width = pluginData.largeFeatureWidth; 360 new_height = pluginData.largeFeatureHeight; 361 new_top = options.topPadding; 362 new_zindex = $feature.css("z-index"); 363 new_left = (pluginData.containerWidth / 2) - (pluginData.largeFeatureWidth / 2) - (pluginData.borderWidth / 2); 364 new_fade = 1.0; 365 } else { 366 new_width = pluginData.smallFeatureWidth; 367 new_height = pluginData.smallFeatureHeight; 368 new_top = options.smallFeatureOffset + options.topPadding; 369 new_zindex = 1; 370 new_fade = 0.4; 371 // some info is different for the left, right, and hidden positions 372 // left 373 if (newPosition == pluginData.totalFeatureCount) { 374 new_left = options.sidePadding; 375 // right 376 } else if (newPosition == 2) { 377 new_left = pluginData.containerWidth - pluginData.smallFeatureWidth - options.sidePadding - pluginData.borderWidth; 378 // hidden 379 } else { 380 new_left = (pluginData.containerWidth / 2) - (pluginData.smallFeatureWidth / 2) - (pluginData.borderWidth / 2); 381 new_fade = 0; 382 } 383 } 384 // This code block takes care of hiding the feature information if the feature is 385 // NO LONGER going to be in the center 386 if (newPosition != 1) { 387 // Slide up the story information 388 $feature.find("div") 389 .hide(); 390 } 391 392 // Animate the feature div to its new location 393 $feature 394 .animate( 395 { 396 width: new_width, 397 height: new_height, 398 top: new_top, 399 left: new_left, 400 opacity: new_fade 401 }, 402 options.carouselSpeed, 403 options.animationEasing, 404 function () { 405 // Take feature info out of hiding if new position is center 406 if (newPosition == 1) { 407 // fade in the feature information 408 $feature.find("div") 409 .fadeTo("fast",0.85); 410 } 411 // decrement the animation queue 412 pluginData.rotationsRemaining = pluginData.rotationsRemaining - 1; 413 // have to change the z-index after the animation is done 414 $feature.css("z-index", new_zindex); 415 // change blips if using them 416 if (options.counterStyle == 1 || options.counterStyle == 2) { 417 if (newPosition == 1) { 418 // figure out what item was just in the center, and what item is now in the center 419 var newCenterItemNum = pluginData.featuresContainer.children("div").index($feature) + 1; 420 var oldCenterItemNum; 421 if (direction == false) 422 oldCenterItemNum = getNextNum(newCenterItemNum); 423 else 424 oldCenterItemNum = getPreviousNum(newCenterItemNum); 425 // now change the active blip 426 changeBlip(oldCenterItemNum, newCenterItemNum); 427 } 428 } 429 430 // did all the the animations finish yet? 431 var divide = pluginData.rotationsRemaining / pluginData.itemsToAnimate; 432 if (divide % 1 == 0) { 433 // if so, set moving to false... 434 pluginData.currentlyMoving = false;
435 // change positions for all items... 436 rotatePositions(direction); 437 438 // and move carousel again if queue is not empty 439 if (pluginData.rotationsRemaining > 0) 440 move(direction); 441 } 442 443 // call autoplay again 444 autoPlay(); 445 } 446 ) 447 // select the image within the feature 448 .find("img:first") 449 // animate its size down 450 .animate({ 451 width: new_width, 452 height: new_height 453 }, 454 options.carouselSpeed, 455 options.animationEasing) 456 .end(); 457 } 458 459 /** 460 * move the carousel to the left or to the right. The features that 461 * will move into the four positions are calculated and then animated 462 * rotate to the RIGHT when direction is TRUE and 463 * rotate to the LEFT when direction is FALSE 464 */ 465 function move(direction) 466 { 467 // Set the carousel to currently moving 468 pluginData.currentlyMoving = true; 469 470 // Obtain the new feature positions based on the direction that the carousel is moving 471 var $newCenter, $newLeft, $newRight, $newHidden; 472 if (direction == true) { 473 // Shift features to the left 474 $newCenter = getContainer(getNextNum(pluginData.currentCenterNum)); 475 $newLeft = getContainer(pluginData.currentCenterNum); 476 $newRight = getContainer(getNextNum(getNextNum(pluginData.currentCenterNum))); 477 $newHidden = getContainer(getPreviousNum(pluginData.currentCenterNum)); 478 pluginData.currentCenterNum = getNextNum(pluginData.currentCenterNum); 479 } else { 480 $newCenter = getContainer(getPreviousNum(pluginData.currentCenterNum)); 481 $newLeft = getContainer(getPreviousNum(getPreviousNum(pluginData.currentCenterNum))); 482 $newRight = getContainer(pluginData.currentCenterNum); 483 $newHidden = getContainer(getNextNum(pluginData.currentCenterNum)); 484 pluginData.currentCenterNum = getPreviousNum(pluginData.currentCenterNum); 485 } 486 487 // The z-index must be set before animations take place for certain movements 488 // this makes the animations look nicer 489 if (direction) { 490 $newLeft.css("z-index", 3); 491 } else { 492 $newRight.css("z-index", 3); 493 } 494 $newCenter.css("z-index", 4); 495 496 // Animate the features into their new positions 497 animateFeature($newLeft, direction); 498 animateFeature($newCenter, direction); 499 animateFeature($newRight, direction); 500 // Only want to animate the "hidden" feature if there are more than three 501 if (pluginData.totalFeatureCount > 3) { 502 animateFeature($newHidden, direction); 503 } 504 } 505 506 // This is used to relegate carousel movement throughout the plugin 507 // It will only initiate a move if the carousel isn't currently moving 508 // It will set the animation queue to the number of rotations given 509 function initiateMove(direction, rotations) { 510 if (pluginData.currentlyMoving == false) { 511 var queue = rotations * pluginData.itemsToAnimate; 512 pluginData.rotationsRemaining = queue; 513 move(direction); 514 } 515 } 516 517 /** 518 * This will find the shortest distance to travel the carousel from 519 * one position to another position. It will return the shortest distance 520 * in number form, and will be positive to go to the right and negative for left 521 */ 522 function findShortestDistance(from, to) { 523 var goingToLeft = 1, goingToRight = 1, tracker; 524 tracker = from; 525 // see how long it takes to go to the left 526 while ((tracker = getPreviousNum(tracker)) != to) { 527 goingToLeft++; 528 } 529 530 tracker = from; 531 // see how long it takes to to to the right 532 while ((tracker = getNextNum(tracker)) != to) { 533 goingToRight++; 534 } 535 536 // whichever is shorter 537 return (goingToLeft < goingToRight) ? goingToLeft*-1 : goingToRight; 538 } 539 540 // Move to the left if left button clicked 541 $(".leftButton").click(function () { 542 initiateMove(false,1); 543 }); 544 545 // Move to right if right button clicked 546 $(".rightButton").click(function () { 547 initiateMove(true,1); 548 }); 549 550 // These are the click and hover events for the features 551 pluginData.featuresContainer.children("div") 552 .click(function () { 553 var position = $(this).data('position'); 554 if (position == 2) { 555 initiateMove(true,1); 556 } else if (position == pluginData.totalFeatureCount) { 557 initiateMove(false,1); 558 } 559 }) 560 .mouseover(function () { 561 if (pluginData.currentlyMoving == false) { 562 var position = $(this).data('position'); 563 if (position == 2 || position == pluginData.totalFeatureCount) { 564 $(this).css("opacity",0.8); 565 } 566 } 567 }) 568 .mouseout(function () { 569 if (pluginData.currentlyMoving == false) { 570 var position = $(this).data('position'); 571 if (position == 2 || position == pluginData.totalFeatureCount) { 572 $(this).css("opacity",0.4); 573 } 574 } 575 }); 576 577 // Add event listener to all clicks within the features container 578 // This is done to disable any links that aren't within the center feature 579 $("a", pluginData.containerIDTag).live("click", function (event) { 580 // travel up to the container 581 var $parents = $(this).parentsUntil(pluginData.containerIDTag); 582 // now check each of the feature divs within it 583 $parents.each(function () { 584 var position = $(this).data('position'); 585 // if there are more than just feature divs within the container, they will 586 // not have a position and it may come back as undefined. Throw these out 587 if (position != undefined) { 588 // if any of the links on a feature OTHER THAN the center feature were clicked, 589 // initiate a carousel move but then throw the link action away 590 // if the position WAS the center (i.e. 1), then do nothing and let the link pass 591 if (position != 1) { 592 if (position == pluginData.totalFeatureCount) { 593 initiateMove(false,1); 594 } else if (position == 2) { 595 initiateMove(true,1); 596 } 597 event.preventDefault(); 598 return false;
599 } 600 } 601 }); 602 }); 603 604 $(".blip").live("click",function () { 605 // grab the position # that was clicked 606 var goTo = $(this).attr("id").substring(5); 607 // find out where that feature # actually is in the carousel right now 608 var whereIsIt = pluginData.featuresContainer.children("div").eq(goTo-1).data('position'); 609 // which feature # is currently in the center 610 var currentlyAt = pluginData.currentCenterNum; 611 // if the blip was clicked for the current center feature, do nothing 612 if (goTo != currentlyAt) { 613 // find the shortest distance to move the carousel 614 var shortest = findShortestDistance(1, whereIsIt); 615 // initiate a move in that direction with given number of rotations 616 if (shortest < 0) { 617 initiateMove(false,(shortest*-1)); 618 } else { 619 initiateMove(true,shortest); 620 } 621 } 622 623 }); 624 }); 625 }; 626 627 $.fn.featureCarousel.defaults = { 628 // If zero, take original width and height of image 629 // If between 0 and 1, multiply by original width and height (to get smaller size) 630 // If greater than one, use in place of original pixel dimensions 631 largeFeatureWidth : 0, 632 largeFeatureHeight: 0, 633 smallFeatureWidth: 0, 634 smallFeatureHeight: 0, 635 // how much to pad the top of the carousel 636 topPadding: 20, 637 // spacing between the sides of the container (pixels) 638 sidePadding: 30, 639 // the additional offset to pad the side features from the top of the carousel 640 smallFeatureOffset: 50, 641 // indicates which feature to start the carousel at 642 startingFeature: 1, 643 // speed in milliseconds it takes to rotate the carousel 644 carouselSpeed: 1000, 645 // time in milliseconds to set interval to autorotate the carousel 646 // set to zero to disable it, negative to go left 647 autoPlay: 0, 648 // set to true to enable the creation of blips to indicate how many 649 // features there are 650 counterStyle: 1, 651 // true to preload all images in the carousel before displaying anything 652 preload: true, 653 // Will only display this many features in the carousel 654 // set to zero to disable 655 displayCutoff: 0, 656 // an easing can be specified for the animation of the carousel 657 animationEasing: 'swing' 658 }; 659 660})(jQuery);
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.