﻿/**
 * CJ Carousel - offering a semantic carousel with a number of options
 *
 * Copyright (c) 2011 Creative Jar & Ben MacGowan (http://www.creative-jar.com)
 *
 * Version: 0.1.0
 *
 * Dependancies:
 * Paul Irish's imagesLoaded jquery plugin - fixes browser inconsistencies of image load callback functions: https://gist.github.com/797120/b7359a8ba0ab5be298875215d07819fe61f87399
 *
 * TO DO:
 * - Fix pager and caption when looping is set to true
 * - Enable choice of transitions
 *
 */

(function ($) {
    $.fn.cjCarousel = function (options) {
        options = $.extend({
            container: "ul", // Item's container
            items: "li", // Items
            visible: 1, // Number of visible items
            speed: 200, // Global animation speed
            btnOutside: false, // Place next/prev buttons inside or outside of element
            btnNextClass: null, // Next button CSS class
            btnNextCallback : null, // Callback function after next button has been clicked
            btnPrevClass: null, // Previous button CSS class
            btnPrevCallback: null, // Callback function after prev button has been clicked
            btnDisabledClass: "disabled", // Disabled button CSS class
            pagerClass: null, // Paging CSS class
            pagerCurrentClass: "current", // Current CSS class for paging
            navigation: null, // Carousel navigation element
            navigationCurrentClass: "current", // Current CSS class for navigation element
            navigationCallback: null, // Callback function after navigation link has been clicked
            imageCaptionClass: null, // Image caption
            loop: false, // Loop through items
            resizeHeight: false,  // Animate height of container on change
            scrollTo: null, // Scroll to value on change
            fixedControls: false, // Fix controls
            autoPlaySpeed: false // Auto play speed
        }, options || {});

        return this.each(function () {
            var oElement = $(this), // Set main element
            oContainer = $(options.container, oElement), // Set item's container within element
            oItems = $(options.items, oContainer), // Set items collection within container
            oItemsLength = $(oItems).size(), // Get number of items
            oItemsWidth = $(oItems).outerWidth(), // Get width of items
            oContainerWidth = oItemsWidth * oItemsLength,
            oCurrent = 1, // Set current item to 1
            oScrolling = false, // Set carousel currently scrolling to false, will be used later to prevent queuing
            oUniqueID = Math.floor(Math.random() * 101);

            if (oItemsLength > options.visible) { // Check if previous and next controls are needed
                if (options.btnNextClass) { // Check if next button class exists
                    if (!options.btnOutside) {
                        if (oCurrent == oItemsLength && !options.loop) { // Check if at end
                            $(oElement).append("<a class='" + options.btnNextClass + " " + options.btnDisabledClass + "'>Next</a>"); // Append next button with disabled CSS class
                        }
                        else {
                            $(oElement).append("<a class='" + options.btnNextClass + "'>Next</a>"); // Append next button
                        }
                        var oBtnNext = $("a." + options.btnNextClass, oElement);
                    }
                    else {
                        if (oCurrent == oItemsLength && !options.loop) { // Check if at end
                            $(oElement).after("<a id='" + options.btnNextClass + "-" + oUniqueID + "' class='" + options.btnNextClass + " " + options.btnDisabledClass + "'>Next</a>"); // Append next button with disabled CSS class
                        }
                        else {
                            $(oElement).after("<a id='" + options.btnNextClass + "-" + oUniqueID + "' class='" + options.btnNextClass + "'>Next</a>"); // Append next button
                        }
                        var oBtnNext = $("a#" + options.btnNextClass + "-" + oUniqueID);
                    }
                    $(oBtnNext).fadeIn(options.speed).click(function () { // Fade next button in and set click function
                        if (!$(this).hasClass(options.btnDisabledClass)) { // Check if disabled
                            runScroll("-", null, options.btnNextCallback); // Moving items to left
                        }
                    });
                }
                if (options.btnPrevClass) { // Check if previous button class exists
                    if (!options.btnOutside) {
                        if (oCurrent == "1" && !options.loop) { // Check if at beginning
                            $(oElement).prepend("<a class='" + options.btnPrevClass + " " + options.btnDisabledClass + "'>Previous</a>"); // Append previous button with disabled CSS class
                        }
                        else {
                            $(oElement).prepend("<a class='" + options.btnPrevClass + "'>Previous</a>"); // Append previous button
                        }
                        var oBtnPrev = $("a." + options.btnPrevClass, oElement);
                    }
                    else {
                        if (oCurrent == "1" && !options.loop) { // Check if at beginning
                            $(oElement).before("<a id='" + options.btnPrevClass + "-" + oUniqueID + "'  class='" + options.btnPrevClass + " " + options.btnDisabledClass + "'>Previous</a>"); // Append previous button with disabled CSS class
                        }
                        else {
                            $(oElement).before("<a id='" + options.btnPrevClass + "-" + oUniqueID + "'  class='" + options.btnPrevClass + "'>Previous</a>"); // Append previous button
                        }
                        var oBtnPrev = $("a#" + options.btnPrevClass + "-" + oUniqueID);
                    }
                    $(oBtnPrev).fadeIn(options.speed).click(function () { // Fade previous button in and set click function
                        if (!$(this).hasClass(options.btnDisabledClass)) { // Check if disabled
                            runScroll("+", null, options.btnNextCallback); // Moving items to right
                        }
                    });
                }
            }

            if (options.pagerClass) { // Check if pager class exists
                $(oElement).after("<ul class='" + options.pagerClass + "'></ul>"); // Append pager container to after element
                oSlide = 1; // Set slide to 1
                $(options.items, oContainer).each(function () { // For each item
                    if (oSlide == oCurrent) { // Check if slide is current
                        $("ul." + options.pagerClass).append("<li><a class='" + options.pagerCurrentClass + "'>" + oSlide + "</a></li>"); // Append item to pager with current CSS class
                    } else {
                        $("ul." + options.pagerClass).append("<li><a>" + oSlide + "</a></li>"); // Append item to pager
                    }
                    oSlide++; // Increase slide by 1
                });

                $("ul." + options.pagerClass + " li a").click(function () { // Pager click event
                    if (!$(this).hasClass(options.pagerCurrentClass)) { // Check if item does not have CSS class of current
                        runScroll(null, $(this).html()); // Jump items to selected slide
                    }
                });
            }

            if (options.navigation) { // Check if the navigation element exists
                $(options.navigation + " a").click(function () {
                    var target = $(this).attr("href");
                    var targetIndex = $(oItems).index($(target)) + 1;
                    if (!$(this).hasClass(options.navigationCurrentClass)) {
                        runScroll(null, targetIndex, options.navigationCallback);
                    }
                    return false;
                });
            }

            if (options.imageCaptionClass) { // Check if pager class exists
                oImageCaption = $(options.items + ":first-child img", oContainer).attr("alt"); // Get image caption from alt attribute
                $(oElement).after("<p class='" + options.imageCaptionClass + "'>Page <em>" + oCurrent + "</em> of " + oItemsLength + " - <span>" + oImageCaption + "</span></p>"); // Append caption after element
            }

            if (options.resizeHeight) { // Check if resizing height of carousel
                oInitialHeight = $(options.items + ":first-child img", oContainer).height(); // Get height of first item
                (oElement).css({ "height": oInitialHeight + "px" }); // Animate height of element
                if ($(options.items + ":first-child img", oContainer).size() > 0) { // Check if first item has image
                    $(options.items + ":first-child", oContainer).imagesLoaded(function () { // Wait until image has loaded
                        oInitialHeight = $(this).height(); // Get height of first item image
                        $(oElement).css({ "height": oInitialHeight + "px" }); // Animate height of element
                    });
                }
            }

            if (options.fixedControls && oItemsLength > options.visible) { // Check if controls should be fixed
                var oControls = $(oBtnNext).add(oBtnPrev),  // Set controls
                oControlsTop = $(oControls).offset().top, // Get offset from top of controls
                oElementBottom = $(oElement).offset().top + $(oElement).height() - $(oControls).height(); // Get bottom of element's offset from top
                if (options.resizeHeight) {// Check if resizing height of carousel
                    $(options.items + ":first-child", oContainer).imagesLoaded(function () {// Wait until image has loaded
                        oElementBottom = $(oElement).offset().top + $(oElement).height() - $(oControls).height(); // Get bottom of element's offset from top
                    });
                }
                if (options.btnPrevClass) { // Check if previous button class exists
                    oBtnPrevLeft = $(oBtnPrev).offset().left; // Get left offset of previous button
                }
                if (options.btnNextClass) { // Check if next button class exists
                    oBtnNextLeft = $(oBtnNext).offset().left; // Get left offset of next button
                }

                $(window).scroll(function (event) { // When window scrolls
                    var y = $(window).scrollTop(); // Get scroll position
                    if (y >= oControlsTop && y < oElementBottom) { // If scroll position is greater than top of controls, and less than bottom of element
                        $(oControls).addClass('fixed').removeClass('bottom-fixed'); // Add CSS class of 'fixed' to controls, remove CSS class of 'bottom-fixed'
                        if (options.btnPrevClass) { // Check if previous button class exists
                            $(oBtnPrev).css({ "left": oBtnPrevLeft + "px" }); // Set left offset of previous button
                        }
                        if (options.btnNextClass) { // Check if next button class exists
                            $(oBtnNext).css({ "left": oBtnNextLeft + "px" }); // Set left offset of next button
                        }
                    } else if (y >= oElementBottom) { // If scroll position is great than bottom of element
                        $(oControls).removeClass('fixed').addClass('bottom-fixed').css({ "left": "" }); // Remove CSS class of 'fixed' from controls, add CSS class of 'bottom-fixed'
                    } else {
                        $(oControls).removeClass('fixed bottom-fixed').css({ "left": "" }); // Remove CSS classes from controls corresponding to fixed positions
                    }
                });

                $(window).resize(function (event) { // When window resizes
                    if (options.btnPrevClass) { // Check if previous button class exists
                        oBtnPrevLeft = $(oElement).offset().left; // Get left offset of previous button using left offset of element (will only work if previous button is position to left of element)
                    }
                    if (options.btnNextClass) { // Check if next button class exists
                        oBtnNextLeft = oBtnPrevLeft + $(oElement).width() - $(oBtnNext).width(); // Get left offset of next button using left offset of element, width of element and removing width of next button (will only work if next button is position to right of element)
                    }
                    if ($(oControls).hasClass("fixed")) { // Check if controls has CSS class of 'fixed'
                        $(oBtnPrev).css({ "left": oBtnPrevLeft + "px" }); // Set left offset of previous button
                        $(oBtnNext).css({ "left": oBtnNextLeft + "px" }); // Set left offset of next button
                    }
                });
            }

            if (options.autoPlaySpeed) { // Check if autoplay is enabled
                oTransitionPause = false; // Set transition pause to false
                $(oElement).hover(function () {
                    oTransitionPause = true; // Set transition pause to true if element is being hovered over
                }, function () {
                    oTransitionPause = false; // Reset transition pause to false on mouse out
                });

                $("ul." + options.pagerClass + ", p." + options.imageCaptionClass).hover(function () {
                    oTransitionPause = true; // Set transition pause to true if element is being hovered over
                }, function () {
                    oTransitionPause = false; // Reset transition pause to false on mouse out
                });

                setInterval(function () { // Set repeating function based on autoplay speed variable
                    if (oTransitionPause != true) { // If not paused
                        if (oCurrent == oItemsLength) { // If last item
                            runScroll("-", null, function () { // Move items left, but with callback function
                                $(oContainer).css({ "left": "0px" }); // Set container to beginning
                            });
                        }
                        else {
                            runScroll("-"); // Move items to left
                        }
                    }
                }, options.autoPlaySpeed);

            }

            if (options.autoPlaySpeed && !options.loop) { // Check if autoplay is enabled, and not looped
                oFirstItemDuplicate = $(oItems).first().clone().appendTo(oContainer); // Clone first item and append to end of container
            }

            if (options.loop) { // Check if looping is enabled
                oItemsDuplicateBefore = oItemsDuplicateAfter = $(oItems).clone(); // Get initial items
                oContainer.prepend(oItemsDuplicateBefore).append(oItemsDuplicateAfter).css({ "left": "-" + oContainerWidth + "px" }); // Append, prepend cloned items, set left position
                oCurrent = oItemsLength + 1, // Set current item
                oItems = $(options.items, oContainer), // Set items collection within container (reset)
                oItemsLength = $(oItems).size(); // Get number of items (reset)
            }

            function runScroll(direction, jump, callback) {
                if (!oScrolling) {
                    oScrolling = true; // Set scrolling to true, to prevent queuing
                    if (options.scrollTo != null) { // Check if scrolling to value
                        $("html,body").animate({ scrollTop: options.scrollTo }, options.speed); // Scroll body to value
                    }
                    if (direction && !jump) {
                        oAnimationResult = direction + "=" + oItemsWidth + "px";
                    }
                    else if (!direction && jump) {
                        oAnimationResult = "-" + (jump - 1) * oItemsWidth + "px";
                    }
                    oContainer.animate({ "left": oAnimationResult }, options.speed, function () { // Animate items in correct direction
                        oScrolling = false; // Set scrolling to finished
                        if (direction == "-" && oCurrent == (oItemsLength - (options.visible - 1)) && options.autoPlaySpeed) { // If moving to right, at the end, and autoplay is enabled
                            oCurrent = 1;
                        }
                        else if (direction == "+" && !jump) {
                            oCurrent--; // Decrease current item
                        }
                        else if (direction == "-" && !jump) {
                            oCurrent++; // Increase current item
                        }
                        else if (!direction && jump) {
                            oCurrent = jump; // Set current item after jump
                        }
                        if (!options.loop) { // Check if not looping carousel
                            if (oCurrent == (oItemsLength - (options.visible - 1))) { // Check if current item is at end
                                $(oBtnNext).addClass(options.btnDisabledClass); // Add disabled class to next button
                                $(oBtnPrev).removeClass(options.btnDisabledClass); // Remove disabled class from previous button
                            }
                            else if (oCurrent == 1) { // Check if current item is at beginning
                                $(oBtnPrev).addClass(options.btnDisabledClass); // Add disabled class to previous button
                                $(oBtnNext).removeClass(options.btnDisabledClass); // Remove disabled class from next button
                            }
                            else {
                                $(oBtnNext).add(oBtnPrev).removeClass(options.btnDisabledClass); // Remove disabled class from previous and next button
                            }
                        }
                        else { // If looping
                            if (direction == "+") { // If moving to right
                                $(options.items, oContainer).last().clone().prependTo(oContainer); // Clone last item and prepend to beginning
                                $(options.items, oContainer).last().remove(); // Remove last item
                                var oContainerPosition = parseInt($(oContainer).css("left")) - parseInt(oItemsWidth); // Calculate new container position
                            }
                            else if (direction == "-") { // If moving to left
                                $(options.items, oContainer).first().clone().appendTo(oContainer); // Clone first item and append to end
                                $(options.items, oContainer).first().remove(); // Remove first item
                                var oContainerPosition = parseInt($(oContainer).css("left")) + parseInt(oItemsWidth); // Calculate new container position
                            }
                            $(oContainer).css({ "left": oContainerPosition + "px" }); // Set position of container with new position
                        }
                        if (options.pagerClass) { // Check if pager class exists
                            $("ul." + options.pagerClass + " li a").removeClass(options.pagerCurrentClass); // Remove current item CSS class from all items
                            $("ul." + options.pagerClass + "  li:nth-child(" + oCurrent + ") a").addClass(options.pagerCurrentClass); // Add current item CSS class to target item
                        }
                        if (options.navigation) { // Check if navigation exists
                            $(options.navigation + " a").removeClass(options.navigationCurrentClass); // Remove current item CSS class from all items
                            $(options.navigation + " li:nth-child(" + oCurrent + ") a").addClass(options.navigationCurrentClass);
                        }
                        if (options.imageCaptionClass) { // Check if image caption class exists
                            var oImageCaption = $(options.items + ":nth-child(" + oCurrent + ") img", oContainer).attr("alt"); // Get caption of target image
                            $("p." + options.imageCaptionClass + " em").html(oCurrent); // Set slide number to updated value
                            $("p." + options.imageCaptionClass + " span").html(oImageCaption); // Update caption
                        }
                        if (options.resizeHeight) { // Check if resizing height of carousel
                            var yDimension = $(options.items + ":nth-child(" + oCurrent + ")", oContainer).height(); // Get height of next item
                            $(oElement).animate({ "height": yDimension + "px" }, options.speed, function () { // Animate height of element
                                if (options.fixedControls) { // Check if controls should be fixed
                                    oElementBottom = $(oElement).offset().top + $(oElement).height() - $(oControls).height(); // Update element's bottom value after resize
                                }
                            });
                        }
                        if (typeof callback == "function") {
                            callback.call(this);
                        }
                    });
                }
            }
        });
    };
})(jQuery);
