﻿/**
* the Carrousel class
*
* @author Klaas Dieleman
* @since jan 2011
* @author Mirjam Verloop
* @since april 2011
* @copyright eFocus
*
* @require jQuery 1.4.2 (http://www.jquery.com)
*
* @param Element; jQuery object: container of elements to slide
* @param Element; jQuery object: next button
* @param Element; jQuery object: previous button
* @param Object; options (optional)
*
*/
function Carrousel(elContainer, elNextBut, elPrevBut, elIndex, options) {

	this.container = jQuery(elContainer);
	this.nextBut = jQuery(elNextBut);
	this.prevBut = jQuery(elPrevBut);
	this.indexIndicator = jQuery(elIndex);

	var opt = options || {};

	this.options = {
		slides: this.container.children(), // slide elements, defaults to direct children of container
		viewport: this.container.parent(), 	// viewport element, defaults to direct parent of container
		disabledNavClass: 'disabled', 				// classname to be given to disabled nav elements
		activeClass: 'active', 					// used classname, necessary for determining active slide
		onScroll: null							// hook function fired when slide starts
	};

	// implement options
	for (i in this.options) {
		if (opt[i] != undefined) {
			this.options[i] = opt[i];
		}
	}

	this.slides = this.options.slides;
	this.viewport = this.options.viewport;
	this.viewportwidth = this.viewport.width();
	this.current = 0;

	// stop construction of class if mandatory DOM elements are not supplied
	if (this.container.length == 1 && this.nextBut.length >= 1 && this.prevBut.length >= 1 && this.slides.length > 1) {
		this.initialize();
	} else {
		this.container.css('visibility', 'visible');
		return false;
	}

};

Carrousel.prototype = {
	/**
	* initializes carrousel
	*/
	initialize: function () {

		this.container.css('visibility', 'hidden');

		this.initViewport();
		this.enableNav(this.nextBut);
		this.enableNav(this.prevBut);

		this.container.css('width', this.getContainerWidth());

		// if carrousel is less wide than viewport, disable all navigation and cancel further actions
		if (this.getContainerWidth() <= this.viewportwidth) {
			this.disableNav(this.nextBut);
			this.disableNav(this.prevBut);
			this.disableNav(this.indexIndicator);
			this.container.css('visibility', 'visible');
			return false;
		}

		this.setActiveInViewport();

		this.container.css('visibility', 'visible');

		// disable appropriate nav elements, if needed
		if (this.slides.last().position().left + this.slides.last().width() <= this.viewportwidth) {
			this.disableNav(this.nextBut);
		}
		if (parseInt(this.container.css('margin-left')) >= 0) {
			this.disableNav(this.prevBut);
		}

	},

	/**
	* sets required styles on viewport and container elements
	*/
	initViewport: function () {

		this.viewport.css({
			'overflow': 'hidden'
		});

		this.container.css({
			'position': 'static', // necessary for position measurement of slides
			'margin-left': 0
		});

	},

	/**
	* sets index navigation
	*/
	setActiveInIndex: function () {

		this.indexIndicator.children().removeClass('active');
		jQuery(this.indexIndicator.children()[this.current]).addClass('active');

	},

	/**
	* enables navigation button
	*
	* @param element, previous- or next-button
	*/
	enableNav: function (elNav) {

		var strDir;

		if (elNav == this.nextBut) {
			strDir = 'next';
		} else if (elNav == this.prevBut) {
			strDir = 'prev';
		}

		elNav.removeClass(this.options.disabledNavClass);

		elNav.click(jQuery.proxy(function (event) {
			event.stopImmediatePropagation();
			event.stopPropagation();
			this.moveCarrousel(strDir);
		}, this));


	},

	/**
	* disables navigation button
	*
	* @param element, previous- or next-button
	*/
	disableNav: function (elNav) {

		elNav.unbind('click');
		elNav.addClass(this.options.disabledNavClass);

	},

	/**
	* gets leftmost slide which is completely visible within viewport
	*
	* @return integer, index number of slide
	*/
	getFirstVisible: function () {

		var intFirst;

		for (var i = this.slides.length - 1; i >= 0; i--) {

			if (jQuery(this.slides[i]).position().left + jQuery(this.slides[i]).width() / 2 > 0) {
				intFirst = i;
			}
		}

		return intFirst;

	},

	/**
	* gets rightmost slide which is completely visible within viewport
	*
	* @return integer, index number of slide
	*/
	getLastVisible: function () {

		var intLast;

		for (var i = 0; i < this.slides.length; i++) {

			if (jQuery(this.slides[i]).position().left + jQuery(this.slides[i]).width() / 2 < this.viewportwidth) {
				intLast = i;
			}

		}

		return intLast;

	},

	/**
	* gets left-position of slide, relative to container
	*
	* @param element, slide to measure
	* @return integer, left position in px
	*/
	getSlidePos: function (elSlide) {

		var intSlidePos;

		this.container.css('position', 'relative');
		intSlidePos = elSlide.position().left;
		this.container.css('position', 'static');

		return intSlidePos;

	},

	/**
	* gets new position (margin-left) for carrousel to move to
	*
	* @param string, direction('next' or 'prev')
	* @return integer, new position
	*/
	getNewPos: function (strDir) {

		var elScrollTarget;
		var intNewPos;

		if (strDir == 'next') {

			this.enableNav(this.prevBut);

			var intLastVisible = this.getLastVisible();
			intNewPos = parseInt(this.container.css('marginLeft')) - this.viewportwidth;

			this.current++;

		} else if (strDir == 'prev') {

			this.enableNav(this.nextBut);

			var intFirstVisible = this.getFirstVisible();
			intNewPos = parseInt(this.container.css('marginLeft')) + this.viewportwidth;

			this.current--;

		}

		if (this.indexIndicator.length > 0) {
			this.setActiveInIndex();
		}

		return intNewPos;

	},

	/**
	* moves carrousel with animation
	*
	* @param string, direction('next' or 'prev')
	*/
	moveCarrousel: function (strDir) {
		
		this.container.stop();
		this.container.clearQueue();

		if (typeof (this.options.onScroll) == 'function') this.options.onScroll();

		this.container.animate({ 'margin-left': this.getNewPos(strDir) }, {
			complete: jQuery.proxy(function () {
				if (this.slides.last().position().left + this.slides.last().width() <= this.viewportwidth + 1) this.disableNav(this.nextBut)
				if (parseInt(this.container.css('margin-left')) >= -1) this.disableNav(this.prevBut)
			}, this)
		});

	},

	/**
	* gets width of complete carrousel, including invisible slides
	*
	* @return integer, width in px
	*/
	getContainerWidth: function () {

		var intWidth = 0;

		this.slides.each(function (index, item) {
			intWidth += jQuery(item).outerWidth();
		});
		return intWidth;

	},

	/**
	* forces active slide visible within viewport
	*/
	setActiveInViewport: function () {

		var elActiveSlide = this.slides.filter('.' + this.options.activeClass).first();
		if (elActiveSlide.length == 0 || this.isSlideVisible(elActiveSlide)) return false;

		var intNewPos = this.viewportwidth / 2 - this.getSlidePos(elActiveSlide) - elActiveSlide.width() / 2;

		if (intNewPos > 0) {
			intNewPos = 0;
		} else if (intNewPos < this.viewportwidth - this.getContainerWidth()) {
			intNewPos = this.viewportwidth - this.getContainerWidth();
		}

		this.container.css('margin-left', intNewPos);

	},

	/**
	* checks whether slide is completely visible within viewport
	*
	* @return boolean
	*/
	isSlideVisible: function (elSlide) {

		var blnVisible = true;

		if (0 - parseInt(this.container.css('margin-left')) > this.getSlidePos(elSlide)) {
			blnVisible = false;
		}

		if ((this.getSlidePos(elSlide) + elSlide.width()) > this.viewportwidth - parseInt(this.container.css('margin-left'))) {
			blnVisible = false;
		}

		return blnVisible;

	}
}
