﻿/**
 * the Carrousel class
 *
 * @author Klaas Dieleman
 * @since jan 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, options) {
	
	this.container = jQuery(elContainer);
	this.nextBut = jQuery(elNextBut);
	this.prevBut = jQuery(elPrevBut);
	
	var opt = options || {};
	
	this.options = {
		slides				: this.container.children(),	// slide elements, defaults to direct children of container
		viewport			: this.container.parent(),		// viewport elements, 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
	};
	
	// 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();
	
	// 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);
		
		// 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.container.css('visibility', 'visible');
			return false;
		}
	
		// preset carrousel position from Querystring, if found
		this.container.css('margin-left', this.getQSposition());

		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);
		}

		this.addLinksQS();
		
	},
	
	/**
	 * 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
		});
	
	},
	
	/**
     * 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();
			
			if (jQuery(this.slides[intLastVisible + 1]).length > 0) {
				elScrollTarget = jQuery(this.slides[intLastVisible + 1]);
			} else {
				elScrollTarget = jQuery(this.slides[intLastVisible]);
			}
			
			intNewPos = this.viewportwidth - this.getSlidePos(elScrollTarget) - elScrollTarget.outerWidth();
			
		} else if (strDir == 'prev') {
		
			this.enableNav(this.nextBut);
			
			var intFirstVisible = this.getFirstVisible();
		
			if (jQuery(this.slides[intFirstVisible - 1]).length > 0) {
				elScrollTarget = jQuery(this.slides[intFirstVisible - 1]);
			} else {
				elScrollTarget = jQuery(this.slides[intFirstVisible]);
			}
			
			intNewPos = 0 - this.getSlidePos(elScrollTarget);
		
		}
		
		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.addLinksQS();
			}, 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;
	
	},

	/**
     * reads position from Querystring
     *
     * @return integer - negative margin for carrousel position (0 if not found in Querystring or more than width of carrousel)
     */
	getQSposition: function() {

		if(this.container.attr('id') == '') return;
		
		var intPos = 0;
		var startIndex = location.search.indexOf(this.container.attr('id') + '_pos=');

		if(startIndex != -1) {
			intPos = parseInt(location.search.substring(startIndex).split('&')[0].replace(this.container.attr('id') + '_pos=', ''));
		}

		if(0 - intPos + this.viewportwidth > this.getContainerWidth()) {
			intPos = 0;
		}

		return intPos;

	},

	/**
     * adds or replaces position in Querystring on links within Carrousel
     */
	addLinksQS: function() {

		if(this.container.attr('id') == '') return;

		var intPos = parseInt(this.container.css('margin-left'));

		this.container.find('a[href*="http"]').each(jQuery.proxy(function(index, item) {
			
			var startIndex = jQuery(item).attr('href').indexOf(this.container.attr('id') + '_pos=');
			if(startIndex != -1) {
				
				var strOldPosVal = '=' + parseInt(jQuery(item).attr('href').substring(startIndex).split('&')[0].split('=')[1]);
				jQuery(item).attr('href', jQuery(item).attr('href').replace(strOldPosVal, '=' + intPos));

			} else {
				
				var strQS = encodeURI('?' + this.container.attr('id') + '_pos=' + intPos);
				if(jQuery(item).attr('href').indexOf('?') != -1) strQS = strQS.replace('?', '&')
				jQuery(item).attr('href', jQuery(item).attr('href') + strQS);
			}
			
		}, this));

	}

}
