File "front-carousel.js"

Full Path: /var/www/bvnghean.vn/save_bvnghean.vn/wp-content/plugins/sneeit-framework/js/front-carousel.js
File size: 21.65 KB
MIME-type: text/plain
Charset: utf-8

(function( $ ){
	/**
	 * 
	 * @param {type} wrapper
	 * @param {type} options
	 * @returns {sneeit-carousel_L6.SneeitCarousel}
	 * 
	 * @todo
	 * - cross browser
	 * - bug dots move out of AB after drag	
	 * - use refresh rate for responsive to 
	 * calculate exactly column and margin 	 
	 */
	function SneeitCarousel(ele, opt) {		
		this._ = ele;
		this.wid = null;
		this.debug = false;
		
		/**
		 * only original items,
		 * before cloning
		 */
		this.oItemNum = null;
		
		/**
		 * all item, included clones
		 */
		this.items = null;		
		this.outer = null;
		this.stage = null;
		this.dots = null;
		this.itemNum = null;				
		this.x = 0;		
		
		this.xStart = 0;
		this.xEnd = 0;
		
		this.totalDots = 0;		
		/* Javascript float has problem with precision
		 * we will use this to eleminate that precision
		 * The epsilon will never larger than 1/10 pixel */
		this.epsilon = 0;
		
		/* Reset Distance from xStart to xEnd		  
		 */
		this.resetAB = 0;
		
		this.prevColNum = -1;
		
		if (typeof(opt) != 'object') {
			opt = {};
		}
		
		this.rtl = (this._.css('direction') == 'rtl');
				
		
		/*
		 * Total original item must be always 
		 * larger than column number,
		 * Because if it's lower, so we don't 
		 * need to run carousel because
		 * the reader is seeing all items
		 * 
		 * It's simple don't make sense
		 */	
		
		this.opt = $.extend({}, SneeitCarousel.Defaults, opt);
		this.colNum = this.opt.columnNumber;		
		this.margin = this.opt.margin;
		this.wrapperClass = '.' + this.opt.wrapperClass;
		this.outerClass = '.' + this.opt.outerClass;
		this.stageClass = '.' + this.opt.stageClass;
		this.itemClass = '.' + this.opt.itemClass;				
		this.dotsClass = '.' + this.opt.dotsClass;
		this.dotClass = '.' + this.opt.dotClass;				
		this.navClass = '.' + this.opt.navClass;
		this.nextClass = '.' + this.opt.nextClass;
		this.prevClass = '.' + this.opt.prevClass;
		this.activeClass = '.' + this.opt.activeClass;
		
		// validate duration
		if (isNaN(this.opt.displayDuration)) {
			this.opt.displayDuration = SneeitCarousel.Defaults.displayDuration;
		} else {
			this.opt.displayDuration = Number(this.opt.displayDuration);
		}
		if (isNaN(this.opt.animateDuration)) {
			this.opt.animateDuration = SneeitCarousel.Defaults.animateDuration;
		} else {
			this.opt.animateDuration = Number(this.opt.animateDuration);
		}
				
		/* real left */
		this.left = this.rtl ? 'right' : 'left';
		
		/* we use LEFT css instead of 
		 * margin-left because safari not work */
		this.xCSS = this.left;
//		this.xCSS = 'margin-' + this.left;
				
		/* timer for displaying */		
		this.displaying = null;
		
		/* animate */
		this.animating = false;
		
		/* hover */
		this.hover = false;
		
		/* drag */
		this.drag = false;		
		this.dragging = false;		
		
		this.init(false);					
	}
	
	
	/**
	 * Default options for the carousel.
	 * @public
	 */
	SneeitCarousel.Defaults = {
		/* number of columns you want to display */
		columnNumber: 1,
		
		/* item width must be not smaller than 
		 * these amount of pixels or we have 
		 * to auto decrease column number
		 * good for responsive designs
		 * */
		minColumnWidth: 150,		
		
		/* margin between columns, in pixel */
		margin: 20,
		
		/*
		 * The PERCENTAGE of margin width related to
		 * wrapper width. The gap will never
		 * larger than this width
		 * 
		 * The Unit is PERCENT %
		 */
		maxMargin: 6.25,
		
		/* How long we will stop to display before animate to 
		 * other items. Integer number, unit is millsecond
		 */
		displayDuration: 3000,								
		
		/* How long we will need to animate to
		 * other items. Integer number, unit is millsecond
		 */
		animateDuration: 500,
				
		/* dots */		
		dotsClass: 'sneeit-carousel-dots',
		dotClass: 'sneeit-carousel-dot',
		dotText: function (args) {
			return '•'
		},
				
		/* navigation arrows */
		navClass: 'sneeit-carousel-nav',
		prevClass: 'sneeit-carousel-prev',
		nextClass: 'sneeit-carousel-next',				
		prevText: function (args) {
			return '⟨';
		},
		nextText: function (args) {
			return '⟩';
		},
		
		/* classes */
		wrapperClass: 'sneeit-carousel',
		outerClass: 'sneeit-carousel-outer',
		stageClass: 'sneeit-carousel-stage',		
		itemClass: 'sneeit-carousel-item',
		activeClass: 'active',
		
		/* callback */
		/* fire before start animate */
		//animate: function(args) {}
		/* fire after finish animate */
		//animated: function(args) {}
	};
	
	SneeitCarousel.prototype.animate = function (x, duration) {
		// we don't need carousel when all items were displayed 
		if (this.colNum >= this.oItemNum) {			
			return;
		}
		
		this.animating = x;
		if (typeof(x) == 'undefined') {
			x = this.x - this.iWid;
		}		
		this.activeDot(x);
		this.activeNav(x);				
		
		var animate_option = {};
		animate_option[this.xCSS] = x + '%';
				
		if (typeof(duration) == 'undefined') {
			duration = this.opt.animateDuration;
		}
		
		var that = this;		
		
		if (typeof(this.opt.animate) == 'function') {		
			this.opt.animate({
				this: that,
				x: x			
			});
		}
		
		if (this.debug) {			
			console.log('animate', x, duration, animate_option, this);	
		}		
		
		this.stage.stop().animate(animate_option, duration, function(){									
			that.x = x;			
			that.animating = false;
			if (typeof(that.opt.animated) == 'function') {		
				that.opt.animated({
					this: that		
				});
			}
			that.display();
		});
	}
	
	SneeitCarousel.prototype.getX = function() {
		if (this.debug) {
			console.log('getX');
		}
		
		
		var x = this.stage.css(this.xCSS);
		x = Number(x.replace('px', ''));		
		
		return x * 100 / this.wid;
	}
	
	
	/* reset current index if 
	 * the window is near the end
	 * of clones 
	 * */
	SneeitCarousel.prototype.resetX = function() {		
	
		var reset = false;
		if (this.x <= this.xEnd) {
			this.x += this.resetAB;
			reset = true;
		} else if (this.x >= this.xStart) {
			this.x -= this.resetAB;
			reset = true;
		}		
		
		if (reset) {
			this.stage.css(
				this.xCSS, this.x + '%'
			);
		}
		
		if (this.debug) {			
			console.log('resetX', reset, this);
		}
	}
	SneeitCarousel.prototype.activeItem = function () {		
		
		/* set active items 
		 * only items have X in Start-End 
		 * will be set as activ
		 * */
		var x = 0;
		var xStart = this.x;
		var xEnd = this.x - this.colNum * this.iWid;
		for (var i = 0; i < this.itemNum; i++) {
			x = - (i * this.iWid + this.iWid / 2);
			
			if (x < xStart && 
				x > xEnd
			) {			
				$(this.items[i]).addClass(this.opt.activeClass);
			} else {				
				$(this.items[i]).removeClass(this.opt.activeClass);
			}			
		}
		
		if (this.debug) {
			console.log('activeItem', x, xStart, xEnd, this);
		}
	}
	SneeitCarousel.prototype.display = function() {	
		
		this.resetX();		
		var that = this;		
		
		/* active dots, navigation and items */
		this.activeDot(this.x);
		this.activeNav(this.x);
		this.activeItem();
		
		if (this.debug) {			
			console.log('display', this);
			return;
		}
		
		
		/* run to next slider */						
		this.displaying = setTimeout(function(){			
			clearTimeout(that.displaying);
			that.displaying = null;
			
			if (that.hover) {
				return;
			}
			
			that.animate();
		}, this.opt.displayDuration);		
		
	}
	
	SneeitCarousel.prototype.stop = function() {	
		if (this.debug) {
			console.log('stop');
		}
		this.stage.stop();
		if (this.displaying != null) {
			clearTimeout(this.displaying);
			this.displaying = null;
		}
		this.x = this.getX();
	}
	
	
	/* we don't need to reinit if the 
	 * column did not change than previous 
	 * but we need to update the values
	 * that have unit in pixel
	 * */
	SneeitCarousel.prototype.init = function(reinit) {		
		
		if (reinit) {			
			this.stop();
			this.colNum = this.opt.columnNumber;
			this.margin = this.opt.margin;
			this.stage.css(this.xCSS, 0);
			this.x = 0;
		}
		
		/* validate column number */
		var items = null;
		
		if (!reinit) {
			items = this._.find('>*');
			this.oItemNum = items.length;
			if (!this.oItemNum) {
				return;
			}

			/* the number must not larger than number of items */
			if (!this.colNum) {
				this.colNum = 1;
			}
			if (this.colNum > this.oItemNum) {
				this.colNum = this.oItemNum;
			}
		}
		
		/* the margin must not larger than max margin */
		this.wid = this._.width();
		this._.css('width', this.wid + 'px');
		if (!this.wid) {		
			return;
		}		
				
		
		if (100 * this.margin / this.wid > this.opt.maxMargin) {
			this.margin = this.opt.maxMargin * this.wid / 100;
		}
				
		/* decrease column number if item width smaller than min */
		var marSum = (this.colNum - 1) * this.margin;
		if (this.colNum <= 1) {
			marSum = 0;
		}	
		
		/* item width in pixel */
		var iWid = (this.wid - marSum) / this.colNum;
		
		for (	;	
				this.colNum > 1 && 
				iWid < this.opt.minColumnWidth;
				this.colNum--
		) {
			marSum = (this.colNum - 1) * this.margin;			
			if (this.colNum <= 1) {
				marSum = 0;
			}
			iWid = this.wid / this.colNum;
			if (this.colNum <= 1 || iWid >= this.opt.minColumnWidth) {
				break;
			}
		}

		if (0 == this.colNum) {
			this.colNum = 1;
		}
		
		/* update again the item width to 
		 * suitable with new column number
		 * marSum is margin sum in pixel
		 * iWid is item width in pixel
		 * */
		marSum = (this.colNum - 1) * this.margin;
		iWid = (this.wid - marSum) / this.colNum;
		
		if (reinit && this.colNum == this.prevColNum) {						
			return;
		}
		this.prevColNum = this.colNum;
				
		
		if (!reinit) {
			/* wrap content with properly class */
			items.wrap('<div class="'+this.opt.itemClass+'"></div>');
			this._.wrapInner('<div class="'+this.opt.stageClass+'"></div>');		
			this._.wrapInner('<div class="'+this.opt.outerClass+'"></div>');

			/* save element objects */
			items = this._.find(this.itemClass);		
			this.stage = this._.find(this.stageClass);
			this.outer = this._.find(this.outerClass);

			/* clone items */		
			items.clone().prependTo(this.stage);
			items.clone().appendTo(this.stage);
			this.items = this._.find(this.itemClass);
			this.itemNum = this.items.length;			
		}
		
		/* CALCULATE EVERYTHING IN PERCENT 
		 * */				
		/* stage width in pixel
		 * */
		var stageWidPx = this.itemNum * iWid + (this.itemNum - 1) * this.margin;		
		
		/* margin width related to stage in percent 
		 * */
		var iMarStage = this.margin * 100 / stageWidPx;
		
		/* item width related to wrapper in percent 
		 * = item width in pixel + margin in pixel convert to percent
		 * */
		this.iWid = (iWid * 100 / this.wid) + (this.margin * 100 / this.wid);
		
		/* accept precision in JavaScript float */
		this.epsilon = 100 / this.wid / 10;
		
		/* the distance that allows you jump
		 * to similar item index in carousel 
		 * */
		this.resetAB = this.oItemNum * this.iWid;
		
		/* the boudaries of x that you need 
		 * to reset if x go out of these */
		this.xStart = 
			- (this.oItemNum - this.colNum - 1) * this.iWid;
		this.xEnd = 
			- (this.oItemNum * 2 + this.colNum - 2) * this.iWid;
		this.totalDots = Math.ceil(this.oItemNum / this.colNum);		
				

		/* init styles */
		this.outer.css({
			'overflow' : 'hidden',
			'position': 'relative'
		});		
		
		this.stage.css({
			'position' :'relative',
			'width': (stageWidPx * 100 / this.wid) + '%',
			'max-width' : 'none'
		});
		
		
		this.items.css({
			'width':  (iWid * 100 / stageWidPx) + '%',
			'float': this.left
		});		
		
		if (iMarStage) {
			this.items.css('margin-' + this.left, iMarStage + '%');
		}
		
		$(this.items[0]).css('margin-' + this.left, 0);
		if (this.debug) {
			console.log('init', this);
		}		
		
		/* display and enable everything */
		this.display();		
		this.enableDots();		
		
		if (!reinit) {
			this.enableDrag();
			this.enableKeyboard();
			this.enableResponsive();
			this.enableNav();
			this.enableHover();
			
			/* add this to fire finish event */
			this._.addClass(this.opt.wrapperClass);
		}
		
		
	}	
	SneeitCarousel.prototype.getPageX = function(e) {		
		if (this.debug) {
			console.log('getPageX');
		}
		
		if (typeof(e['pageX']) == 'undefined' && 
			typeof(e['originalEvent']['touches'][0]['clientX']) != 'undefined') {
			return e['originalEvent']['touches'][0]['clientX'];
		}
		return e.pageX;
	}
	
	/**
	 * Detect 
	 * @returns {undefined}
	 */
	SneeitCarousel.prototype.dragStart = function() {
		
	}
	SneeitCarousel.prototype.dragMove = function() {
		
	}
	SneeitCarousel.prototype.dragMove = function() {
		
	}
	SneeitCarousel.prototype.enableDrag = function() {		
		if (this.debug) {
			console.log('enableDrag');
		}
		/* process mouse drag */
		var that = this;
	
		this.stage.on('mousedown touchstart', function(e){						
			e.preventDefault();			
			if (that.drag !== false) {				
				return false;
			}
						
			
			/* process if touch event */
			e.pageX = that.getPageX(e);				

			/* stop stage, update X and curMargin */
			that.drag = e.pageX;			
			that.stop();
						
			
			return false;
		});
		$(document).on('mousemove touchmove', function(e){			
			if (that.drag === false) {				
				return;
			}			
			
			e.preventDefault();
			
			/* process if touch event */
			e.pageX = that.getPageX(e);
			
			/* calculate for target x */
			if (that.rtl) {				
				that.x -= (e.pageX - that.drag) * 100 / that.wid;
			} else {
				that.x += (e.pageX - that.drag) * 100 / that.wid;
			}
			
			if (e.pageX < that.drag) {
				that.dragging = 'left';
			} else if (e.pageX > that.drag) {
				that.dragging = 'right';
			}			
			
			that.drag = e.pageX;			
			that.stage.css(that.xCSS, that.x + '%');
			that.resetX();
			
			return false;
		});		

		/* preventing move to href after ending drag on an A tag */
		this.stage.find('a[href]').on('click', function(e){			
			if (that.dragging) {				
				e.preventDefault();
				return false;
			}
		});
		
		/* when mouse up (end of mouse drag), we will recalculate x,
		 * also animate item back to nearest item border 
		 * Priority animate to direction of the mouse move
		 * */
		$(document).on('mouseup touchend', function(e){			
			if (that.drag === false) {				
				return;
			}
						
			
			that.x = that.getX();
			
			var x = 0;			
			for (var i = 0; i < that.itemNum - 1; i++) {				
				x = - (i * that.iWid);																
				
				/* find the item which contain current x point */
				if (that.x < x - that.epsilon && 
					that.x > x - that.iWid + that.epsilon) {
					break;
				}
			}
			
			if (i >= that.itemNum - 1) {
				that.drag = false;
				that.dragging = false;			
				that.display();
				return;
			}
			
			/* animate depending on drag direction and site direction 
			 * to animate to right position */
			var x = - (i * that.iWid);
			if (that.dragging == that.left) {
				x -= that.iWid;
			}										
			that.animate(x);
			
			/* we don't set down immediately 
			 * to prevent click events from a tags */
			setTimeout(function(){
				that.drag = false;
				that.dragging = false;
			}, 5);	
		});
	}
	
	
	SneeitCarousel.prototype.activeDot = function(x) {
		if (this.debug) {
			return;
			console.log('activeDot', x, this);
		}
		if (!this.dots) {			
			return;
		}
		
		var i = Math.round(Math.abs(x / this.iWid)) % this.oItemNum;
		i = Math.floor(i / this.colNum);
		this.dots.removeClass(this.opt.activeClass);
		$(this.dots[i]).addClass(this.opt.activeClass);
	}
	
	/**
	 * Enable dots as pagination for the carousel
	 * @returns {undefined}
	 */
	SneeitCarousel.prototype.enableDots = function() {		
		if (this.debug) {
			console.log('enableDots');
		}
		if (!this.totalDots || 
			!this.opt.dotsClass ||			
			!this.opt.dotClass
		) {
			return;
		}
		
		
		/* append dot raw HTML content to wrapper */
		var dots = '';
		var that = this;
		for (var i = 0; i < this.totalDots; i++) {
			var dot = '';
			if (typeof(this.opt.dotText) == 'function') {
				dot = this.opt.dotText({
					this: that,
					dotIndex: i,					
				});
			} else {
				dot = this.opt.dotText;
			}
			
			/* seem the user disable the dot, so just return */
			if (dot === false) {				
				return;
			}
			dots += '<div class="' + this.opt.dotClass + '" data-index="'+i+'">'+dot+'</div>';
		}
		
		/* if reinit, we have to 
		 * remove all and remake html */
		if (this.dots != null) {
			this._.find(this.dotsClass).html(dots);
		} else {
			dots = '<div class="' + this.opt.dotsClass + '">'+dots+'</div>';
			this._.append(dots);
		}
		
		this.dots = this._.find(this.dotClass);
		$(this.dots[0]).addClass(this.opt.activeClass);
		
		
		/* action when click dots */		
		this.dots.click(function(){						
			var index = $(this).attr('data-index');			
			if (typeof(index) == 'undefined' || isNaN(index)) {
				return;
			}
			
			that.stop();
			
			/* set dot index and active state */
			index = Number(index);						
//			that.dots.removeClass(that.opt.activeClass);
//			$(this).addClass(that.opt.activeClass);
			
			/* calculate nearest target x */				
			var x = - index * that.colNum * that.iWid;	
			var AB = x - that.x;
			var maxMatchPos = that.itemNum / that.oItemNum;
			var stepMatch = that.resetAB;
			for (var i = 1; i < maxMatchPos; i++) {
				var _x = x - i * stepMatch;
				var _AB = _x - that.x;
				if (Math.abs(_AB) < Math.abs(AB)) {
					AB = _AB;
				}
			}
			x = AB + that.x;
			
			/* it already stayed at the right position */
			if (Math.abs(AB) < that.epsilon) {				
				that.display();
				return;
			}			
			
			that.animate(x);
		});
	}
	
	SneeitCarousel.prototype.activeNav = function(x) {				
		if (this.debug) {
			return;
			console.log('activeNav', x, this);
		}
		/* previous arrow */
		var prevText = '';
		var that = this;
		if (typeof(this.opt.prevText) == 'function') {
			prevText = this.opt.prevText({
				this: that
			});
		} else {
			prevText = this.opt.prevText;
		}
		if (prevText !== false && this.opt.prevClass) {
			this._.find(this.prevClass).html(prevText);
		}				
		
		
		/* next arrow */
		var nextText = '';
		if (typeof(this.opt.nextText) == 'function') {
			nextText = this.opt.nextText({
				this: that
			});
		} else {
			nextText = this.opt.nextText;
		}
		if (nextText !== false && this.opt.nextClass) {
			this._.find(this.nextClass).html(nextText);
		}
	}
	
	SneeitCarousel.prototype.enableNav = function() {		
		if (this.debug) {
			console.log('enableNav');
		}
		if (!this.opt.navClass) {
			return;
		}
		/* append arrow HTML content to wrapper */
		var arrowsContent = '';		
		var that = this;
		
		/* previous arrow */
		var prevText = '';
		if (typeof(this.opt.prevText) == 'function') {
			prevText = this.opt.prevText({
				this: that
			});
		} else {
			prevText = this.opt.prevText;
		}
		if (prevText && this.opt.prevClass) {
			arrowsContent += '<div class="' + this.opt.prevClass + '">'+prevText+'</div>';
		}		
		
		
		/* next arrow */
		var nextText = '';
		if (typeof(this.opt.nextText) == 'function') {
			nextText = this.opt.nextText({
				this: that
			});
		} else {
			nextText = this.opt.nextText;
		}
		if (nextText && this.opt.nextClass) {
			arrowsContent += '<div class="' + this.opt.nextClass + '">'+nextText+'</div>';			
		}
		
		if (!arrowsContent) {
			return;
		}		
				
		this._.append('<div class="' + this.opt.navClass + '">' + arrowsContent + '</div>');		
		
		/* action when click arrows */		
		/* when click Previous Arrow */
		if (this.opt.prevClass) {
			this._.find(this.prevClass).click(function(){								
				that.prev();
			});
		}
		
		/* when click Next Arrow */		
		if (this.opt.nextClass) {
			this._.find(this.nextClass).click(function(){				
				that.next();
			});
		}
	}
	
	SneeitCarousel.prototype.prev = function() {		
		if (this.debug) {
			console.log('prev');
		}
		this.stop();		
		var i = Math.floor(this.x / this.iWid);
		var x = (i + 1) * this.iWid;
		
		/* the distance must be at least larger 
		 * than one item width */
		if (this.x - x > - this.iWid + this.epsilon) {
			x += this.iWid;
		}
		
		/* reset here to prevent move 
		 * out of the view area */
		if (x > this.xStart) {
			this.x -= this.resetAB;
			x -= this.resetAB;
			this.stage.css(this.xCSS, this.x +'%');
		}		

		// animate to the target
		this.animate(x, 90);
	}
	SneeitCarousel.prototype.next = function() {		
		if (this.debug) {
			console.log('next');
		}
		this.stop();		
		var i = Math.floor(Math.abs(this.x / this.iWid));
		var x = -(i + 1) * this.iWid;
		
		/* the distance must be at least larger 
		 * than one item width */
		if (Math.abs(this.x) > i * this.iWid + this.epsilon) {					
			x -= this.iWid;
		}
		
		/* reset here to prevent move 
		 * out of the view area */
		if (x < this.xEnd) {
			this.x += this.resetAB;
			x += this.resetAB;
			this.stage.css(this.xCSS, this.x +'%');
		}				
		// animate to the target
		this.animate(x, 90);
		
	}
	
	SneeitCarousel.prototype.enableKeyboard = function() {		
		if (this.debug) {
			console.log('enableKeyboard');
		}
		var that = this;
		$(document).keydown(function(e){			
			switch(e.which) {
				case 37: // left
					that.prev();
					break;				

				case 39: // right
					that.next();
					break;

				default: return; // exit this handler for other keys
			}
			e.preventDefault(); // prevent the default action (scroll / move caret)
		});
	}
	
	SneeitCarousel.prototype.enableResponsive = function() {		
		if (this.debug) {
			console.log('enableResponsive');
		}
		var that = this;
		$(window).resize(function(){			
			that._.css('width', '');
			that.init(true);
		});
	}
	
	/**
	 * stop and resume when mouse hover
	 * @returns {undefined}
	 */
	SneeitCarousel.prototype.enableHover = function() {		
		if (this.debug) {
			console.log('enableHover');
		}
		var that = this;
		this._.on('mouseenter', function(){									
			that.hover = true;	
		});
		this._.on('mouseleave', function(){						
			that.hover = false;						
			/* only resume if the slider is not displaying
			 * (which will auto animate after the end) 
			 * and not animating (because if animating 
			 * then we don't need to resume
			 * */
			if (!that.displaying && that.animating === false) {
				that.display();
			}
		});
	}
	
	
	/**
	 * The jQuery Plugin for the Sneeit Carousel
	 * @todo Navigation plugin `next` and `prev`
	 * @public
	 */
	$.fn.sneeitCarousel = function(option) {						
		return this.each(function() {			
			new SneeitCarousel($(this), option);
		});		
	};
})( jQuery );