(function($) {
$.fn.carousel = function (options) {
// Replace the default options with user defined options.
options = $.extend({}, $.fn.carousel.defaults, options || {});
return $(this).each(function () {
/* Do not modify the universal values!*/
var pluginData = {
currentCenterNum: options.startingFeature,
containerWidth: 0,
containerHeight: 0,
largeFeatureWidth: 0,
largeFeatureHeight: 0,
smallFeatureWidth: 0,
smallFeatureHeight: 0,
totalFeatureCount: $(this).children("div").length,
currentlyMoving: false,
featuresContainer: $(this),
featuresArray: [],
containerIDTag: "#"+$(this).attr("id"),
timeoutVar: null,
rotationsRemaining: 0,
itemsToAnimate: 0,
borderWidth: 0
};
preload(function () {
setupFeatureDimensions();
setupCarousel();
setupFeaturePositions();
setupBlips();
initiateMove(true,1);
});
// Function to preload the images.
function preload(callback) {
// Function to not preload the images.
if (options.preload == true) {
var $imageElements = pluginData.featuresContainer.find("img");
var loadedImages = 0;
var totalImages = $imageElements.length;
$imageElements.each(function () {
// Attempt to load the images
$(this).load(function () {
// Add to number of images loaded and see if they are all done yet
loadedImages++;
if (loadedImages == totalImages) {
// All done, perform callback
callback();
}
});
// The images may already be cached in the browser, in which case they
// would have a 'true' complete value and the load callback would never be
// fired. This will fire it manually.
if (this.complete) {
$(this).trigger('load');
}
});
} else {
// If user doesn't want preloader, then just go right to callback.
callback();
}
}
// Gets the feature container based on the number.
function getContainer(featureNum) {
return pluginData.featuresArray[featureNum-1];
}
function getBySetPos(position) {
$.each(pluginData.featuresArray, function () {
if ($(this).data().setPosition == position)
return $(this);
});
}
// Get previous feature number.
function getPreviousNum(num) {
if ((num - 1) == 0) {
return pluginData.totalFeatureCount;
} else {
return num - 1;
}
}
// Get next feature number.
function getNextNum(num) {
if ((num + 1) > pluginData.totalFeatureCount) {
return 1;
} else {
return num + 1;
}
}
/**
* Because there are several options the user can set for the width and height
* of the feature images, this function is used to determine which options were set
* and to set the appropriate dimensions.*/
function setupFeatureDimensions() {
// Set the height and width of the entire carousel container.
pluginData.containerWidth = pluginData.featuresContainer.width();
pluginData.containerHeight = pluginData.featuresContainer.height();
// Grab the first image for reference.
var $firstFeatureImage = $(pluginData.containerIDTag).find("div img:first");
// Large Feature Width.
if (options.largeFeatureWidth > 1)
pluginData.largeFeatureWidth = options.largeFeatureWidth;
else if (options.largeFeatureWidth > 0 && options.largeFeatureWidth < 1)
pluginData.largeFeatureWidth = $firstFeatureImage.width() * options.largeFeatureWidth;
else
pluginData.largeFeatureWidth = $firstFeatureImage.outerWidth();
// Large Feature Height.
if (options.largeFeatureHeight > 1)
pluginData.largeFeatureHeight = options.largeFeatureHeight;
else if (options.largeFeatureHeight > 0 && options.largeFeatureHeight < 1)
pluginData.largeFeatureHeight = $firstFeatureImage.height() * options.largeFeatureHeight;
else
pluginData.largeFeatureHeight = $firstFeatureImage.outerHeight();
// Small Feature Width.
if (options.smallFeatureWidth > 1)
pluginData.smallFeatureWidth = options.smallFeatureWidth;
else if (options.smallFeatureWidth > 0 && options.smallFeatureWidth < 1)
pluginData.smallFeatureWidth = $firstFeatureImage.width() * options.smallFeatureWidth;
else
pluginData.smallFeatureWidth = $firstFeatureImage.outerWidth() / 2;
// Small Feature Height.
if (options.smallFeatureHeight > 1)
pluginData.smallFeatureHeight = options.smallFeatureHeight;
else if (options.smallFeatureHeight > 0 && options.smallFeatureHeight < 1)
pluginData.smallFeatureHeight = $firstFeatureImage.height() * options.smallFeatureHeight;
else
pluginData.smallFeatureHeight = $firstFeatureImage.outerHeight() / 2;
}
/**
* Function to take care of setting up the default positions for the features.*/
function setupCarousel() {
// Set the total feature count to the amount the user cutoff.
if (options.displayCutoff > 0 && options.displayCutoff < pluginData.totalFeatureCount) {
pluginData.totalFeatureCount = options.displayCutoff;
}
// Fill in the features array.
pluginData.featuresContainer.children("div").each(function (index) {
if (index < pluginData.totalFeatureCount) {
pluginData.featuresArray[index] = $(this);
}
});
// Determine the total border width around the feature if there is one.
if (pluginData.featuresContainer.children("div").first().css("borderLeftWidth") != "medium") {
pluginData.borderWidth = parseInt(pluginData.featuresContainer.children("div").first().css("borderLeftWidth"))*2;
}
// Place all the features in a center hidden position to start off.
pluginData.featuresContainer
// Have to make the container relative positioning.
.children("div").each(function () {
// Center all the features in the middle and hide them.
$(this).css({
'left': (pluginData.containerWidth / 2) - (pluginData.smallFeatureWidth / 2) - (pluginData.borderWidth / 2),
'width': pluginData.smallFeatureWidth,
'height': pluginData.smallFeatureHeight,
'top': options.smallFeatureOffset + options.topPadding,
'opacity': 0
});
})
// Set all the images to small feature size.
.find("img:first").css({
'width': pluginData.smallFeatureWidth
});
// figure out number of items that will rotate each time.
if (pluginData.totalFeatureCount < 4) {
pluginData.itemsToAnimate = pluginData.totalFeatureCount;
} else {
pluginData.itemsToAnimate = 4;
}
// Hide story info and set the proper positioning.
pluginData.featuresContainer.find("div > div")
.hide();
}
//Here all the position data is set for the features.*/
function setupFeaturePositions() {
// Give all features a set number that won't change so they remember their
// original order.
$.each(pluginData.featuresArray, function (i) {
$(this).data('setPosition',i+1);
});
// Go back one.
var oneBeforeStarting = getPreviousNum(options.startingFeature);
pluginData.currentCenterNum = oneBeforeStarting;
// Center feature will be position 1.
var $centerFeature = getContainer(oneBeforeStarting);
$centerFeature.data('position',1);
// Everything before the center feature.
var $prevFeatures = $centerFeature.prevAll();
$prevFeatures.each(function (i) {
$(this).data('position',(pluginData.totalFeatureCount - i));
});
// And everything after the center feature.
var $nextFeatures = $centerFeature.nextAll();
$nextFeatures.each(function (i) {
if ($(this).data('setPosition') != undefined) {
$(this).data('position',(i + 2));
}
});
// If the counter style is for including number tags in description.
if (options.counterStyle == 3) {
$.each(pluginData.featuresArray, function () {
var pos = getPreviousNum($(this).data('position'));
var $numberTag = $("");
$numberTag.addClass("numberTag");
$numberTag.html("("+ pos + " of " + pluginData.totalFeatureCount + ") ");
$(this).find('div p').prepend($numberTag);
});
}
}
// Blips are created.
function setupBlips()
{
// Only setup the blips if the counter style is 1 or 2.
if (options.counterStyle == 1 || options.counterStyle == 2) {
// construct the blip list.
var $list = $("
");
$list.addClass("blipsContainer");
for (var i = 0; i < pluginData.totalFeatureCount; i++) {
// Counter style 1 has no numbers, while 2 does.
var counter;
if (options.counterStyle == 2)
counter = "";
else
counter = i+1;
// Build the DOM for the blip list.
var $blip = $(""+counter+"
");
$blip.addClass("blip");
$blip.css("cursor","pointer");
$blip.attr("id","blip_"+(i+1));
var $listEntry = $("");
$listEntry.append($blip);
$listEntry.css("float","left");
$listEntry.css("list-style-type","none");
$list.append($listEntry);
}
// add the blip list and then make sure it's visible.
$(pluginData.containerIDTag).append($list);
$list.hide().show();
}
}
// Move the highlighted blip to the currently centered feature.
function changeBlip(oldCenter, newCenter)
{
// get selectors for the two blips.
var $blipsContainer = pluginData.featuresContainer.find(".blipsContainer");
var $oldCenter = $blipsContainer.find("#blip_"+oldCenter);
var $newCenter = $blipsContainer.find("#blip_"+newCenter);
// Change classes.
$oldCenter.removeClass("blipSelected");
$newCenter.addClass("blipSelected");
}
// Sets the autoPlay for carousel.
function autoPlay() {
// Clear the timeout var if it exists.
if (pluginData.timeoutVar != null) {
pluginData.timeoutVar = clearTimeout(pluginData.timeoutVar);
}
// Set interval for moving if autoplay is set.
if (options.autoPlay != 0) {
var autoTime = (Math.abs(options.autoPlay) < options.carouselSpeed) ? options.carouselSpeed : Math.abs(options.autoPlay);
pluginData.timeoutVar = setTimeout(function () {
if (options.autoPlay > 0)
initiateMove(true,1);
else if (options.autoPlay < 0)
initiateMove(false,1);
}, autoTime);
}
}
// This is a helper function for the animateFeature function that
// will update the positions of all the features based on the direction.
function rotatePositions(direction) {
$.each(pluginData.featuresArray, function () {
var newPos;
if (direction == false) {
newPos = getNextNum($(this).data().position);
} else {
newPos = getPreviousNum($(this).data().position);
}
$(this).data('position',newPos);
});
}
/**
* This function is used to animate the given feature to the given
* location. Valid locations are "left", "right", "center", and "hidden".
*/
function animateFeature($feature, direction)
{
var new_width, new_height, new_top, new_left, new_zindex, new_padding, new_fade;
// Determine the old and new positions of the feature.
var oldPosition = $feature.data('position');
var newPosition;
if (direction == true)
newPosition = getPreviousNum(oldPosition);
else
newPosition = getNextNum(oldPosition);
// Caculate new new css values depending on where the feature will be located.
if (newPosition == 1) {
new_width = pluginData.largeFeatureWidth;
new_height = pluginData.largeFeatureHeight;
new_top = options.topPadding;
new_zindex = $feature.css("z-index");
new_left = (pluginData.containerWidth / 2) - (pluginData.largeFeatureWidth / 2) - (pluginData.borderWidth / 2);
new_fade = 1.0;
} else {
new_width = pluginData.smallFeatureWidth;
new_height = pluginData.smallFeatureHeight;
new_top = options.smallFeatureOffset + options.topPadding;
new_zindex = 1;
new_fade = 0.4;
// Left
if (newPosition == pluginData.totalFeatureCount) {
new_left = options.sidePadding;
// Right
} else if (newPosition == 2) {
new_left = pluginData.containerWidth - pluginData.smallFeatureWidth - options.sidePadding - pluginData.borderWidth;
// Hidden
} else {
new_left = (pluginData.containerWidth / 2) - (pluginData.smallFeatureWidth / 2) - (pluginData.borderWidth / 2);
new_fade = 0;
}
}
// This code block takes care of hiding the feature information.
if (newPosition != 1) {
$feature.find("div")
.hide();
}
// Animate the feature div to its new location.
$feature
.animate(
{
width: new_width,
height: new_height,
top: new_top,
left: new_left,
opacity: new_fade
},
options.carouselSpeed,
options.animationEasing,
function () {
// Take feature info out of hiding if new position is center.
if (newPosition == 1) {
// Fade in the feature information.
$feature.find("div")
.fadeTo("fast",0.85);
}
// Decrement the animation queue.
pluginData.rotationsRemaining = pluginData.rotationsRemaining - 1;
// Have to change the z-index after the animation is done.
$feature.css("z-index", new_zindex);
if (options.counterStyle == 1 || options.counterStyle == 2) {
if (newPosition == 1) {
var newCenterItemNum = pluginData.featuresContainer.children("div").index($feature) + 1;
var oldCenterItemNum;
if (direction == false)
oldCenterItemNum = getNextNum(newCenterItemNum);
else
oldCenterItemNum = getPreviousNum(newCenterItemNum);
// Change the active blip.
changeBlip(oldCenterItemNum, newCenterItemNum);
}
}
var divide = pluginData.rotationsRemaining / pluginData.itemsToAnimate;
if (divide % 1 == 0) {
pluginData.currentlyMoving = false;
rotatePositions(direction);
if (pluginData.rotationsRemaining > 0)
move(direction);
}
// Call autoplay again.
autoPlay();
}
)
.find("img:first")
.animate({
width: new_width,
height: new_height
},
options.carouselSpeed,
options.animationEasing)
.end();
}
// Carousel movement.
function move(direction)
{
// Set the carousel to currently moving.
pluginData.currentlyMoving = true;
// Obtain the new feature positions based on the direction that the carousel is moving.
var $newCenter, $newLeft, $newRight, $newHidden;
if (direction == true) {
// Shift features to the left.
$newCenter = getContainer(getNextNum(pluginData.currentCenterNum));
$newLeft = getContainer(pluginData.currentCenterNum);
$newRight = getContainer(getNextNum(getNextNum(pluginData.currentCenterNum)));
$newHidden = getContainer(getPreviousNum(pluginData.currentCenterNum));
pluginData.currentCenterNum = getNextNum(pluginData.currentCenterNum);
} else {
$newCenter = getContainer(getPreviousNum(pluginData.currentCenterNum));
$newLeft = getContainer(getPreviousNum(getPreviousNum(pluginData.currentCenterNum)));
$newRight = getContainer(pluginData.currentCenterNum);
$newHidden = getContainer(getNextNum(pluginData.currentCenterNum));
pluginData.currentCenterNum = getPreviousNum(pluginData.currentCenterNum);
}
// The z-index must be set before animations take place for certain movements.
if (direction) {
$newLeft.css("z-index", 3);
} else {
$newRight.css("z-index", 3);
}
$newCenter.css("z-index", 4);
// Animate the features into their new positions.
animateFeature($newLeft, direction);
animateFeature($newCenter, direction);
animateFeature($newRight, direction);
// Only want to animate the "hidden" feature if there are more than three.
if (pluginData.totalFeatureCount > 3) {
animateFeature($newHidden, direction);
}
}
// This is used to relegate carousel movement.
function initiateMove(direction, rotations) {
if (pluginData.currentlyMoving == false) {
var queue = rotations * pluginData.itemsToAnimate;
pluginData.rotationsRemaining = queue;
move(direction);
}
}
/**
* This will find the shortest distance to travel the carousel from
* one position to another position.*/
function findShortestDistance(from, to) {
var goingToLeft = 1, goingToRight = 1, tracker;
tracker = from;
// See how long it takes to go to the left.
while ((tracker = getPreviousNum(tracker)) != to) {
goingToLeft++;
}
tracker = from;
// See how long it takes to to to the right.
while ((tracker = getNextNum(tracker)) != to) {
goingToRight++;
}
// Whichever is shorter.
return (goingToLeft < goingToRight) ? goingToLeft*-1 : goingToRight;
}
// Move to the left if left button clicked.
$(".leftButton").click(function () {
initiateMove(false,1);
});
// Move to right if right button clicked.
$(".rightButton").click(function () {
initiateMove(true,1);
});
// These are the click and hover events for the features.
pluginData.featuresContainer.children("div")
.click(function () {
var position = $(this).data('position');
if (position == 2) {
initiateMove(true,1);
} else if (position == pluginData.totalFeatureCount) {
initiateMove(false,1);
}
})
.mouseover(function () {
if (pluginData.currentlyMoving == false) {
var position = $(this).data('position');
if (position == 2 || position == pluginData.totalFeatureCount) {
$(this).css("opacity",0.8);
}
}
})
.mouseout(function () {
if (pluginData.currentlyMoving == false) {
var position = $(this).data('position');
if (position == 2 || position == pluginData.totalFeatureCount) {
$(this).css("opacity",0.4);
}
}
});
// Add event listener to all clicks within the features container.
$("a", pluginData.containerIDTag).live("click", function (event) {
var $parents = $(this).parentsUntil(pluginData.containerIDTag);
$parents.each(function () {
var position = $(this).data('position');
if (position != undefined) {
if (position != 1) {
if (position == pluginData.totalFeatureCount) {
initiateMove(false,1);
} else if (position == 2) {
initiateMove(true,1);
}
event.preventDefault();
return false;
}
}
});
});
$(".blip").live("click",function () {
// Grab the position # that was clicked.
var goTo = $(this).attr("id").substring(5);
// Find out where that feature # actually is in the carousel right now.
var whereIsIt = pluginData.featuresContainer.children("div").eq(goTo-1).data('position');
// Which feature # is currently in the center.
var currentlyAt = pluginData.currentCenterNum;
// If the blip was clicked for the current center feature, do nothing.
if (goTo != currentlyAt) {
// Find the shortest distance to move the carousel.
var shortest = findShortestDistance(1, whereIsIt);
// Initiate a move in that direction with given number of rotations.
if (shortest < 0) {
initiateMove(false,(shortest*-1));
} else {
initiateMove(true,shortest);
}
}
});
});
};
$.fn.carousel.defaults = {
largeFeatureWidth : 0,
largeFeatureHeight: 0,
smallFeatureWidth: .5,
smallFeatureHeight: .5,
topPadding: 20,
sidePadding: 30,
smallFeatureOffset: 50,
startingFeature: 1,
carouselSpeed: 1000,
autoPlay: 5000,
counterStyle: 1,
preload: true,
displayCutoff: 0,
animationEasing: 'swing'
};
})(jQuery);