﻿/**
* jQuery.smoothDivScroll - Smooth div scrolling using jQuery.
* This plugin is for turning a set of HTML elements's into a smooth scrolling area.
*
* Copyright (c) 2009 Thomas Kahn - thomas.kahn(at)karnhuset(dot)net
*
* This plugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This plugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details. <http://www.gnu.org/licenses/>.
*
* Date: 2009-04-20
* @author Thomas Kahn
* @version 0.8
*
* Changelog
* ---------------------------------------------
* 0.8   - Major update. New parameter setup. Lots of new autoscrolling capabilities and 
*       new parameters for controlling the scrolling speed. Made it possible to start 
*       the scroller at a specific element.
* 
* 0.7   - Added support for autoscrolling after the page has loaded. 
*         Added support for making the hot spots visible at start for X number of seconds
*         or visible all the time.
*
* 0.6   - First version.
*/

(function($) { 
   jQuery.fn.smoothDivScroll = function(options){

      var defaults = {
      scrollingHotSpotLeft: "div.scrollingHotSpotLeft", // The hot spot that triggers scrolling left
      scrollingHotSpotRight: "div.scrollingHotSpotRight", // The hot spot that triggers scrolling right
      scrollWrapper: "div.scrollWrapper", // The wrapper element that surrounds the scrollable area
      scrollableArea: "div.scrollableArea", // The actual element that is scrolled left or right
      hiddenOnStart: false, // True or false. Determines whether the element should be visible or hidden on start
      ajaxContentURL: "", // Optional. If supplied, content is fetched through AJAX using the supplied URL
      countOnlyClass: "", // Optional. If supplied, the function that calculates the width of the scrollable area will only count elements of this class
      scrollingSpeed: 25, // A way of controlling the scrolling speed. 1=slowest and 100= fastest.
      mouseDownSpeedBooster: 3, // 1 is normal speed (no speed boost), 2 is twice as fast, 3 is three times as fast, and so on
      autoScroll: "", // Optional. Leave it blank if you don't want any autoscroll. 
                  // Otherwise use the values "onstart" or "always". 
                  // onstart - the scrolling will start automatically after 
                  // the page has loaded and scroll according to the method you've selected 
                  // using the autoScrollDirection parameter. When the user moves the mouse 
                  // over the left or right hot spot the autoscroll will stop. After that 
                  // the scrolling will only be triggered by the host spots.
                  // always - the hot spots are disabled alltogether and the scrollable area 
                  // will only scroll automatically.
      autoScrollDirection: "right",    // This parameter controls the direction and behavior of the autoscrolling.   
                              // Optional. The values are:
                              // right - autoscrolls right and stops when it reaches the end
                              // left - autoscrolls left and stops when it reaches the end 
                              // (only relevant if you have set the parameter startAtElementId).
                              // backandforth - starts autoscrolling right and when it reaches 
                              // the end, switches to autoscrolling left and so on. Ping-pong style.
                              // endlessloop - continuous scrolling right. An endless loop of elements.
      autoScrollSpeed: 1,  //  1-2 = slow, 3-4 = medium, 5-13 = fast -- anything higher = superfast
      pauseAutoScroll: "", // Optional. Values mousedown and mouseover. Leave blank for no pausing abilities.
      visibleHotSpots: "",    // Optional. Leave it blank for invisible hot spots. 
                        // Otherwise use the values  "onstart" or "always". 
                        // onstart - makes the hot spots visible for X-number of seconds 
                        // after tha page has loaded and then they become invisible. 
                        // always - hot spots are visible all the time.
      hotSpotsVisibleTime: 5, // If you have selected "onstart" as the value for visibleHotSpots, 
                        // you set the number of seconds that you want the hot spots to be 
                        // visible after the page has loaded. After this time they will fade 
                        // away and become invisible again.
      startAtElementId: "" // Optional. Use this parameter if you want the offset of the 
                        // scrollable area to be positioned at a specific element directly 
                        // after the page has loaded. First give your element an ID in the 
                        // HTML code and then provide this ID as a parameter.
      };

      options = $.extend(defaults, options);

      /* Identify global variables so JSLint won't raise errors when verifying the code */
      /*global autoScrollInterval, autoScroll, clearInterval, doScrollLeft, doScrollRight, hideHotSpotBackgrounds, hideHotSpotBackgroundsInterval, hideLeftHotSpot, hideRightHotSpot, jQuery, makeHotSpotBackgroundsVisible, setHotSpotHeightForIE, setInterval, showHideHotSpots, window, windowIsResized */


      // Iterate and make each matched element a SmoothDivScroll
      return this.each(function() {
      
         // Create a variable for the current "mother element"
         var $mom = $(this);
         
         // Load the content of the scrollable area using the optional URL.
         // If no ajaxContentURL is supplied, we assume that the content of
         // the scrolling area is already in place.
         if(options.ajaxContentURL.length !== 0){
            $mom.scrollableAreaWidth = 0;
            $mom.find(options.scrollableArea).load((options.ajaxContentURL), function(){  
               $mom.find(options.scrollableArea).children((options.countOnlyClass)).each(function() {
                  $mom.scrollableAreaWidth = $mom.scrollableAreaWidth + $(this).outerWidth(true);
               });

               // Set the width of the scrollable area
               $mom.find(options.scrollableArea).css("width", ($mom.scrollableAreaWidth + "px"));
               
               // Hide the mother element if it shouldn't be visible on start
               if(options.hiddenOnStart) {
                  $mom.hide();
               }
               
               windowIsResized();
               
               setHotSpotHeightForIE();
            });      
         }
         
         // Some variables used for working with the scrolling
         var scrollXpos;
         var booster;
         
         // The left offset of the container on which you place 
         // the scrolling behavior.
         // This offset is used when calculating the mouse x-position 
         // in relation to scroll hot spots
         var motherElementOffset = $mom.offset().left;
         
         // A variable used for storing the current hot spot width.
         // It is used when calculating the scroll speed
         var hotSpotWidth = 0;
         
         // Set the booster value to normal (doesn't change until the user
         // holds down the mouse button over one of the hot spots)
         booster = 1;
         
         var hasExtended = false;
         
         // Stuff to do once on load
         $(window).one("load",function(){
            // If the content of the scrolling area is not loaded through ajax,
            // we assume it's already there and can run the code to calculate
            // the width of the scrolling area, resize it to that width
            if(options.ajaxContentURL.length === 0) {
               $mom.scrollableAreaWidth = 0;
               $mom.tempStartingPosition = 0;
               
               $mom.find(options.scrollableArea).children((options.countOnlyClass)).each(function() {
                  
                  // Check to see if the current element in the loop is the one where the scrolling should start
                  if( (options.startAtElementId.length !== 0) && (($(this).attr("id")) == options.startAtElementId) ) {
                  $mom.tempStartingPosition = $mom.scrollableAreaWidth;
                  }

                  // Add the width of the current element in the loop to the total width
                  $mom.scrollableAreaWidth = $mom.scrollableAreaWidth + $(this).outerWidth(true);
                  
               });
               
               // Set the width of the scrollableArea to the accumulated width
               $mom.find(options.scrollableArea).css("width", $mom.scrollableAreaWidth + "px");
               
               // Check to see if the whole thing should be hidden at start
               if(options.hiddenOnStart) {
                  $mom.hide();
               }
            }
            
            // Set the starting position of the scrollable area. If no startAtElementId is set, the starting position
            // will be the default value (zero)
            $mom.find(options.scrollWrapper).scrollLeft($mom.tempStartingPosition);
            
            // If the user has set the option autoScroll, the scollable area will
            // start scrolling automatically
            if(options.autoScroll !== "") {
               autoScrollInterval = setInterval(autoScroll, 6);
            }

            // If autoScroll is set to always, the hot spots should be disabled
            if(options.autoScroll == "always")
            {
               hideLeftHotSpot();
               hideRightHotSpot();
            }
   
            // If the user wants to have visible hot spots, here is where it's taken care of
            switch(options.visibleHotSpots)
            {
               case "always":
                  makeHotSpotBackgroundsVisible();
                  break;
               case "onstart":
                  makeHotSpotBackgroundsVisible();
                  hideHotSpotBackgroundsInterval = setInterval(hideHotSpotBackgrounds, (options.hotSpotsVisibleTime * 1000));
                  break;
               default:
                  break;   
            }
            
         });
         
         // If autoScroll is running, here's where it's stopped when the user positions the mouse over one of the hot spots
         $mom.find(options.scrollingHotSpotRight, options.scrollingHotSpotLeft).one('mouseover',function(){
            if(options.autoScroll == "onstart") {
               clearInterval(autoScrollInterval);
            }
         });   

         
         // EVENT - window resize
         $(window).bind("resize",function(){
            windowIsResized();
         });

         // A function for doing the stuff that needs to be
         // done when the browser window is resized
         function windowIsResized() {
         
            // If the scrollable area is not hidden on start, reset and recalculate the
            // width of the scrollable area
            if(!(options.hiddenOnStart))
            {
               $mom.scrollableAreaWidth = 0;
               $mom.find(options.scrollableArea).children((options.countOnlyClass)).each(function() {
                  $mom.scrollableAreaWidth = $mom.scrollableAreaWidth + $(this).outerWidth(true);
               });
               
               $mom.find(options.scrollableArea).css("width", $mom.scrollableAreaWidth + 'px');
            }

            // Reset the left offset of the scroll wrapper
            $mom.find(options.scrollWrapper).scrollLeft("0");
            
            // Get the width of the page (body)
            var bodyWidth = $("body").innerWidth();
            
            // If the scrollable area is shorter than the current
            // window width, both scroll hot spots should be hidden.
            // Otherwise, check which hot spots should be shown.
            if(options.autoScroll !== "always")
            {
               if($mom.scrollableAreaWidth < bodyWidth)
               {  
                  hideLeftHotSpot();
                  hideRightHotSpot();
               }
               else
               {
                  showHideHotSpots();
               }
            }
         }
         
         // HELPER FUNCTIONS FOR SHOWING AND HIDING HOT SPOTS
         function hideLeftHotSpot(){
            $mom.find(options.scrollingHotSpotLeft).hide();
         }
         
         function hideRightHotSpot(){
            $mom.find(options.scrollingHotSpotRight).hide();
         }
         
         function showLeftHotSpot(){
            $mom.find(options.scrollingHotSpotLeft).show();
            // Recalculate the hot spot width. Do it here because you can
            // be sure that the hot spot is visible and has a width
            if(hotSpotWidth <= 0) {
               hotSpotWidth = $mom.find(options.scrollingHotSpotLeft).width();
            }
         }
         
         function showRightHotSpot(){
            $mom.find(options.scrollingHotSpotRight).show();
            // Recalculate the hot spot width. Do it here because you can
            // be sure that the hot spot is visible and has a width
            if(hotSpotWidth <= 0) {
               hotSpotWidth = $mom.find(options.scrollingHotSpotRight).width();
            }
         }
         
         function setHotSpotHeightForIE()
         {
            // Some bugfixing for IE 6
            jQuery.each(jQuery.browser, function(i, val) {
               if(i=="msie" && jQuery.browser.version.substr(0,1)=="6")
               {
                  $mom.find(options.scrollingHotSpotLeft).css("height", ($mom.find(options.scrollableArea).innerHeight()));
                  $mom.find(options.scrollingHotSpotRight).css("height", ($mom.find(options.scrollableArea).innerHeight()));           
               }
            });
         }
         // **************************************************
         // EVENTS - scroll right
         // **************************************************
         
         // Check the mouse X position and calculate the relative X position inside the right hot spot
         $mom.find(options.scrollingHotSpotRight).bind('mousemove',function(e){
            var x = e.pageX - (this.offsetLeft + motherElementOffset);
            scrollXpos = Math.round((x/hotSpotWidth) * options.scrollingSpeed);
            if(scrollXpos === Infinity) {
               scrollXpos = 0;
            }

         });

         // mouseover right hot spot
         $mom.find(options.scrollingHotSpotRight).bind('mouseover',function(){
            if(options.autoScroll == "onstart") {
               clearInterval(autoScrollInterval);
            }
            rightScrollInterval = setInterval(doScrollRight, 6);
         });   
         
         // mouseout right hot spot
         $mom.find(options.scrollingHotSpotRight).bind('mouseout',function(){
            clearInterval(rightScrollInterval);
            scrollXpos = 0;
         });
         
         // scrolling speed booster right
         $mom.find(options.scrollingHotSpotRight).bind('mousedown',function(){
            booster = options.mouseDownSpeedBooster;
         });
         
         // stop boosting the scrolling speed
         $("*").bind('mouseup',function(){
            booster = 1;
         });
   
         
         // The function that does the actual scrolling right
         var doScrollRight = function()
         {  
            if(scrollXpos > 0) {
               $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() + (scrollXpos*booster));
            }
            showHideHotSpots();
         };
         
         // **************************************************
         // Autoscrolling
         // **************************************************

         if(options.pauseAutoScroll == "mousedown" && options.autoScroll == "always")
         {
            $mom.find(options.scrollWrapper).bind('mousedown',function(){
               clearInterval(autoScrollInterval);
            });
            
            $mom.find(options.scrollWrapper).bind('mouseup',function(){
               autoScrollInterval = setInterval(autoScroll, 6);
            });
         }
         else if(options.pauseAutoScroll == "mouseover" && options.autoScroll == "always")
         {
            $mom.find(options.scrollWrapper).bind('mouseover',function(){
               clearInterval(autoScrollInterval);
            });
            
            $mom.find(options.scrollWrapper).bind('mouseout',function(){
               autoScrollInterval = setInterval(autoScroll, 6);
            });
         }
         
         var previousScrollLeft = 0;
         var pingPongDirection = "right";
         var hasChanged = false;
         var swapAt;
         // The autoScroll function
         var autoScroll = function()
         {  
            if (options.autoScroll == "onstart") {
               showHideHotSpots();
            }
            
            switch(options.autoScrollDirection)
            {
               case "right":
                  $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() + options.autoScrollSpeed);
                  break;
                  
               case "left":
                  $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() - options.autoScrollSpeed);
                  break;
                  
               case "backandforth":
                  // Calculate an indicator variable to see if the scrolling has reached the end
                  previousScrollLeft = $mom.find(options.scrollWrapper).scrollLeft();
                  
                  if(pingPongDirection == "right") {
                     $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() + options.autoScrollSpeed);
                  }
                  else {
                     $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() - options.autoScrollSpeed);
                  }
                  
                  if(previousScrollLeft === $mom.find(options.scrollWrapper).scrollLeft())
                  {
                     if(pingPongDirection == "right") {
                        pingPongDirection = "left";
                     }
                     else {
                        pingPongDirection = "right";
                     }
                  }
                  break;
      
               case "endlessloop":
                  // Calculate an indicator variable to see if the scrolling has reached the end
                  previousScrollLeft = $mom.find(options.scrollWrapper).scrollLeft();
                  
                  if(!(hasChanged))
                  {
                     if(options.startAtElementId !== "") {
                        swapAt = $mom.find(options.scrollWrapper).scrollLeft() + $("#" + options.startAtElementId).outerWidth();
                     }
                     else {
                        swapAt = $mom.find(options.scrollWrapper).scrollLeft() + $mom.find(options.scrollableArea).children(":first-child").outerWidth();
                     }
                     hasChanged = true;                  
                  }
                  
                  // Do the autoscrolling
                  $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() + options.autoScrollSpeed);
                  
                  // Check to see if the first element in the scrollableArea needs to be moved to become the last or if the scrolling has reached the end
                  if((swapAt <= $mom.find(options.scrollWrapper).scrollLeft()) || ((options.startAtElementId !== "") && (previousScrollLeft === $mom.find(options.scrollWrapper).scrollLeft())))
                  {
                     // Clone the first element and append it last in the scrollableArea
                     $mom.find(options.scrollableArea).append($mom.find(options.scrollableArea).children(":first-child").clone());

                     // Compensate for the removal of the first element by
                     $mom.find(options.scrollWrapper).scrollLeft(($mom.find(options.scrollWrapper).scrollLeft() - $mom.find(options.scrollableArea).children(":first-child").outerWidth()));
                     
                     // Remove it from its original position as the first element
                     $mom.find(options.scrollableArea).children(":first-child").remove();
                     hasChanged = false;
                  }
                  
                  break;
               default:
                  break;
                  
            }

         };
         
         
         // **************************************************
         // EVENTS - scroll left
         // **************************************************
      
         // Check the mouse X position and calculate the relative X position inside the left hot spot
         $mom.find(options.scrollingHotSpotLeft).bind('mousemove',function(e){
            var x = $mom.find(options.scrollingHotSpotLeft).innerWidth() - (e.pageX - motherElementOffset);
            scrollXpos = Math.round((x/hotSpotWidth) * options.scrollingSpeed);
            if(scrollXpos === Infinity)
            {
               scrollXpos = 0;
            }
         });
         
         // mouseover left hot spot
         $mom.find(options.scrollingHotSpotLeft).bind('mouseover',function(){
            if(options.autoScroll == "onstart") {
               clearInterval(autoScrollInterval);
            }
            
            leftScrollInterval = setInterval(doScrollLeft, 6);
         });   
         
         // mouseout left hot spot
         $mom.find(options.scrollingHotSpotLeft).bind('mouseout',function(){
            clearInterval(leftScrollInterval);
            scrollXpos = 0;
         });
         
         // scrolling speed booster left
         $mom.find(options.scrollingHotSpotLeft).bind('mousedown',function(){
            booster = options.mouseDownSpeedBooster;
         });
         
         // The function that does the actual scrolling left
         var doScrollLeft = function()
         {  
            if(scrollXpos > 0) {
               $mom.find(options.scrollWrapper).scrollLeft($mom.find(options.scrollWrapper).scrollLeft() - (scrollXpos*booster));
            }
            showHideHotSpots();
         };
         
         // **************************************************
         // Hot spot functions
         // **************************************************
         
         // Function for showing and hiding hot spots depending on the
         // offset of the scrolling
         function showHideHotSpots()
         {
            // When you can't scroll further left
            // the left scroll hot spot should be hidden
            // and the right hot spot visible
            if($mom.find(options.scrollWrapper).scrollLeft() === 0)
            {
               hideLeftHotSpot();
               showRightHotSpot();
            }
            // When you can't scroll further right
            // the right scroll hot spot should be hidden
            // and the left hot spot visible
            else if(($mom.scrollableAreaWidth) <= ($mom.find(options.scrollWrapper).innerWidth() + $mom.find(options.scrollWrapper).scrollLeft()))
            {
               hideRightHotSpot();
               showLeftHotSpot();
            }
            // If you are somewhere in the middle of your
            // scrolling, both hot spots should be visible
            else
            {
               showRightHotSpot();
               showLeftHotSpot();
            }

         }
         
         // Function for making the hot spot background visible
         function makeHotSpotBackgroundsVisible()
         {
            // Alter the CSS (SmoothDivScroll.css) if you want to customize
            // the look'n'feel of the visible hot spots
            
            // The left hot spot
            $mom.find(options.scrollingHotSpotLeft).addClass("scrollingHotSpotLeftVisible");

            // The right hot spot
            $mom.find(options.scrollingHotSpotRight).addClass("scrollingHotSpotRightVisible");
         }
         
         // Hide the hot spot backgrounds.
         function hideHotSpotBackgrounds()
         {
            clearInterval(hideHotSpotBackgroundsInterval);
            
            // Fade out the left hot spot
            $mom.find(options.scrollingHotSpotLeft).fadeTo("slow", 0.0, function(){
               $mom.find(options.scrollingHotSpotLeft).removeClass("scrollingHotSpotLeftVisible");
            });

            // Fade out the right hot spot
            $mom.find(options.scrollingHotSpotRight).fadeTo("slow", 0.0, function(){
               $mom.find(options.scrollingHotSpotRight).removeClass("scrollingHotSpotRightVisible");
            });
         }
         
   });
};

})(jQuery);




