﻿/**
* jQuery.serialScroll
* Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
* Dual licensed under MIT and GPL.
* Date: 3/20/2008
*
* @projectDescription Animated scrolling of series.
* @author Ariel Flesler
* @version 1.2.1
*
* @id jQuery.serialScroll
* @id jQuery.fn.serialScroll
* @param {Object} settings Hash of settings, it is passed in to jQuery.ScrollTo, none is required.
* @return {jQuery} Returns the same jQuery object, for chaining.
*
* http://flesler.blogspot.com/2008/02/jqueryserialscroll.html
*
* Notes:
*	- The plugin requires jQuery.ScrollTo.
*	- The hash of settings, is passed to jQuery.ScrollTo, so its settings can be used as well.
*/
; (function($) {

	var $serialScroll = $.serialScroll = function(settings) {
		$.scrollTo.window().serialScroll(settings);
	};

	//Many of these defaults, belong to jQuery.ScrollTo, check it's demo for an example of each option.
	//@see http://flesler.webs/jQuery.ScrollTo/
	$serialScroll.defaults = {//the defaults are public and can be overriden.
		duration: 1000, //how long to animate.
		axis: 'x', //which of top and left should be scrolled
		event: 'click', //on which event to react.
		start: 0, //first element (zero-based index)
		step: 1, //how many elements to scroll on each action
		lock: true, //ignore events if already animating
		cycle: true, //cycle endlessly ( constant velocity )
		constant: true //use contant speed ?
		/*
		navigation:null,//if specified, it's a selector a collection of items to navigate the container
		target:null, //if specified, it's a selector to the element to be scrolled.
		interval:0, //it's the number of milliseconds to automatically go to the next
		lazy:false,//go find the elements each time (allows AJAX or JS content, or reordering)
		stop:false, //stop any previous animations to avoid queueing
		force:false,//force the scroll to the first element on start ?
		jump: false,//if true, when the event is triggered on an element, the pane scrolls to it
		items:null, //selector to the items (relative to the matched elements)
		prev:null, //selector to the 'prev' button
		next:null, //selector to the 'next' button
		onBefore: function(){}, //function called before scrolling, if it returns false, the event is ignored
		exclude:0 //exclude the last x elements, so we cannot scroll past the end
		*/
	};

	$.fn.serialScroll = function(settings) {
		settings = $.extend({}, $serialScroll.defaults, settings);
		var event = settings.event, //this one is just to get shorter code when compressed
			step = settings.step, // idem
			lazy = settings.lazy; //idem

		return this.each(function() {
			var 
				context = settings.target ? this : document, //if a target is specified, then everything's relative to 'this'.
				$pane = $(settings.target || this, context), //the element to be scrolled (will carry all the events)
				pane = $pane[0], //will be reused, save it into a variable
				items = settings.items, //will hold a lazy list of elements
				active = settings.start, //active index
				auto = settings.interval, //boolean, do auto or not
				nav = settings.navigation, //save it now to make the code shorter
				timer; //holds the interval id

			if (!lazy)//if not lazy, go get the items now
				items = getItems();

			if (settings.force)
				jump({}, active); //generate an initial call

			// Button binding, optionall
			$(settings.prev || [], context).bind(event, -step, move);
			$(settings.next || [], context).bind(event, step, move);

			// Custom events bound to the container
			if (!pane.ssbound)//don't bind more than once
				$pane
					.bind('prev.serialScroll', -step, move) //you can trigger with just 'prev'
					.bind('next.serialScroll', step, move) //for example: $(container).trigger('next');
					.bind('goto.serialScroll', jump); //for example: $(container).trigger('goto', [4] );
			if (auto)
				$pane
					.bind('start.serialScroll', function(e) {
						if (!auto) {
							clear();
							auto = true;
							next();
						}
					})
					.bind('stop.serialScroll', function() {//stop a current animation
						clear();
						auto = false;
					});
			$pane.bind('notify.serialScroll', function(e, elem) {//let serialScroll know that the index changed externally
				var i = index(elem);
				if (i > -1)
					active = i;
			});
			pane.ssbound = true; //avoid many bindings

			if (settings.jump)//can't use jump if using lazy items and a non-bubbling event
				(lazy ? $pane : getItems()).bind(event, function(e) {
					jump(e, index(e.target));
				});

			if (nav)
				nav = $(nav, context).bind(event, function(e) {
					e.data = Math.round(getItems().length / nav.length) * nav.index(this);
					jump(e, this);
				});

			function move(e) {
				e.data += active;
				jump(e, this);
			};
			function jump(e, button) {
				if (!isNaN(button)) {//initial or special call from the outside $(container).trigger('goto',[index]);
					e.data = button;
					button = pane;
				}

				var 
					pos = e.data, n,
					real = e.type, //is a real event triggering ?
					$items = settings.exclude ? getItems().slice(0, -settings.exclude) : getItems(), //handle a possible exclude
					limit = $items.length,
					elem = $items[pos],
					duration = settings.duration;

				if (real)//real event object
					e.preventDefault();

				if (auto) {
					clear(); //clear any possible automatic scrolling.
					timer = setTimeout(next, settings.interval);
				}

				if (!elem) { //exceeded the limits
					n = pos < 0 ? 0 : limit - 1;
					if (active != n)//we exceeded for the first time
						pos = n;
					else if (!settings.cycle)//this is a bad case
						return;
					else
						pos = limit - n - 1; //invert, go to the other side
					elem = $items[pos];
				}

				if (!elem || real && active == pos || //could happen, save some CPU cycles in vain
					settings.lock && $pane.is(':animated') || //no animations while busy
					real && settings.onBefore && //callback returns false ?
					settings.onBefore.call(button, e, elem, $pane, getItems(), pos) === false) return;

				if (settings.stop)
					$pane.queue('fx', []).stop(); //remove all its animations

				if (settings.constant)
					duration = Math.abs(duration / step * (active - pos)); //keep constant velocity

				$pane
					.scrollTo(elem, duration, settings)//do scroll
					.trigger('notify.serialScroll', [pos]); //in case serialScroll was called on this elem more than once.
			};
			function next() {//I'll use the namespace to avoid conflicts
				$pane.trigger('next.serialScroll');
			};
			function clear() {
				clearTimeout(timer);
			};
			function getItems() {
				return $(items, pane);
			};
			function index(elem) {
				if (!isNaN(elem)) return elem; //number
				var $items = getItems(), i;
				while ((i = $items.index(elem)) == -1 && elem != pane)//see if it matches or one of its ancestors
					elem = elem.parentNode;
				return i;
			};
		});
	};

})(jQuery);