// Copyright 2011 Tart. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview tart.Carousel is an event driven Carousel/Image Slider class
* which handles next and previous events and gets visible items on viewport.
*
* Example usage:
*
* var items = [
* {name : 'one'},
* {name : 'two'},
* {name : 'three'},
* {name : 'four'},
* {name : 'five'},
* {name : 'six'},
* {name : 'seven'}
* ]; //seven items
*
* var carousel = new tart.Carousel(items);
*
* carousel.setItemPerViewport(2); //only 2 items is visibile
*
* goog.events.listen(carousel, tart.Carousel.EventTypes.NEXT, function (e) {
* console.info('items moved next');
* console.log (e.itemsToBeRemoved);
* console.log (e.itemsToBeInserted);
* console.info(carousel.getVisibleItems());
* });
*
* goog.events.listen(carousel, tart.Carousel.EventTypes.PREV, function (e) {
* console.info('items moved prev');
* console.log (e.itemsToBeRemoved);
* console.log (e.itemsToBeInserted);
* console.info(carousel.getVisibleItems());
* });
*
* //items : 'one', 'two'
* carousel.next(1);
* //items : 'two', 'three'
* carousel.next(4);
* //items : 'six', 'seven'
* carousel.next(1);
* //items : 'six', 'seven' which is end of items, for circular navigation use tart.CircularCarousel instead
* carousel.prev(1);
* //items : 'five', 'six'
* carousel.prev(99999);
* //items : 'one', 'two'
*/
goog.provide('tart.Carousel');
goog.provide('tart.Carousel.EventTypes');
goog.require('goog.debug.ErrorHandler');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
/**
* Pagination class to handle all paging events
*
* @param {Array.<*>=} items array of items.
* @extends {goog.events.EventTarget}
* @constructor
*/
tart.Carousel = function(items) {
goog.events.EventTarget.call(this);
/** @protected */
this.items = items;
/** @protected */
this.itemCount = this.items.length;
/** @protected */
this.itemPerViewport = 1;
/**
* First visible item index in viewport
*
* @protected
* */
this.firstVisible = 0;
/**
* Last visible item index in viewport
*
* @protected
* */
this.lastVisible = this.firstVisible + this.itemPerViewport;
};
goog.inherits(tart.Carousel, goog.events.EventTarget);
/**
* Event types enumaration
*
* @enum {string}
*/
tart.Carousel.EventTypes = {
MOVED: 'moved',
PREV: 'prev',
NEXT: 'next'
};
/**
* Item per visible viewport
*
* @param {number} itemPerViewport number of visible items.
* @return {tart.Carousel} itself for chaining.
*/
tart.Carousel.prototype.setItemPerViewport = function(itemPerViewport) {
this.itemPerViewport = itemPerViewport;
this.lastVisible = this.firstVisible + itemPerViewport;
return this;
};
/**
* Get number of visible items in viewport
*
* @return {number} number of visible items.
*/
tart.Carousel.prototype.getItemPerViewport = function() {
return this.itemPerViewport;
};
/**
* Get visible items
*
* @return {Array.<*>} visible items array.
*/
tart.Carousel.prototype.getVisibleItems = function() {
return this.items.slice(this.firstVisible, this.lastVisible);
};
/**
* Get visible items indexes
*
* @return {Object} visible items array.
*/
tart.Carousel.prototype.getVisibleItemIndexes = function() {
var indexes = {
first: this.firstVisible,
last: this.lastVisible
};
return indexes;
};
/**
* Calculate max move count
*
* @param {string} direction 'next' or 'prev' direction.
* @param {number} moveCount number of movement.
* @return {number} max move count.
* @private
*/
tart.Carousel.prototype.getMaxMoveCount_ = function(direction, moveCount) {
var maxMoveCount;
if (direction == 'next') {
maxMoveCount = this.itemCount - this.lastVisible;
}
else {
maxMoveCount = this.firstVisible;
}
return maxMoveCount;
};
/**
* Find which items to be removed and inserted after move
*
* @param {number} moveCount item move count.
* @return {Object} object literal which has itemsToBeInserted and itemsToBeRemoved nodes.
*/
tart.Carousel.prototype.getItemsToBeInsertedAndRemoved = function(moveCount) {
var i,
previousItemsIndex = [],
nextItemsIndex = [];
for (i = this.firstVisible; i < this.lastVisible; i++) {
previousItemsIndex.push(i);
nextItemsIndex.push(i + moveCount);
}
var moveDiff = this.getMoveDiff(previousItemsIndex, nextItemsIndex, moveCount);
return moveDiff;
};
/**
* Get difference between visible items, after move and before move
*
* @param {Array.<number>} a1 first array.
* @param {Array.<number>} a2 second array.
* @param {number} moveCount item move count.
* @return {Object} generated diff.
* @protected
*/
tart.Carousel.prototype.getMoveDiff = function(a1, a2, moveCount) {
var direction = (moveCount > 0) ? 'next' : 'prev';
moveCount = Math.abs(moveCount);
var i = 0,
index = 0,
itemsToBeInserted = [],
itemsToBeRemoved = [],
itemCount = this.itemCount;
var tmpItems;
if (direction == 'prev') {
tmpItems = {
toBeInserted: a2.slice(0, moveCount),
toBeRemoved: a1.slice(-1 * moveCount, a1.length)
};
}
else {
tmpItems = {
toBeRemoved: a1.slice(0, moveCount),
toBeInserted: a2.slice(-1 * moveCount, a2.length)
};
}
for (i = 0; i < tmpItems.toBeInserted.length; i++) {
index = (tmpItems.toBeInserted[i] + itemCount) % itemCount;
itemsToBeInserted.push(this.items[index]);
}
for (i = 0; i < tmpItems.toBeRemoved.length; i++) {
index = (tmpItems.toBeRemoved[i] + itemCount) % itemCount;
itemsToBeRemoved.push(this.items[index]);
}
return {
itemsToBeInserted: itemsToBeInserted,
itemsToBeRemoved: itemsToBeRemoved
};
};
/**
* Move cursor to next or previous item
*
* @param {string} direction 'next' or 'prev' movement direction.
* @param {*} moveCount cursor move count.
* @protected
*/
tart.Carousel.prototype.move = function(direction, moveCount) {
moveCount = moveCount || 1;
moveCount = Math.abs(moveCount);
var maxMoveCount = this.getMaxMoveCount_(direction, moveCount);
moveCount = moveCount <= maxMoveCount ? moveCount : maxMoveCount;
var eventToDispatch = tart.Carousel.EventTypes.NEXT;
if (direction == 'prev') {
moveCount = moveCount * -1;
eventToDispatch = tart.Carousel.EventTypes.PREV;
}
var moveDiff = this.getItemsToBeInsertedAndRemoved(moveCount);
this.firstVisible = this.firstVisible + moveCount;
this.lastVisible = this.lastVisible + moveCount;
var eventObj = {type: eventToDispatch,
itemsToBeRemoved: moveDiff.itemsToBeRemoved,
itemsToBeInserted: moveDiff.itemsToBeInserted};
this.dispatchEvent(eventObj);
};
/**
* Move cursor to next
*
* @param {number|*} moveCount cursor move count.
*/
tart.Carousel.prototype.next = function(moveCount) {
this.move('next', moveCount);
};
/**
* Move cursor to previous
*
* @param {number|*} moveCount cursor move count.
*/
tart.Carousel.prototype.prev = function(moveCount) {
this.move('prev', moveCount);
};