/*
* jquery.rs.carousel.js v0.8.6
*
* Copyright (c) 2011 Richard Scarrott
* http://www.richardscarrott.co.uk
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Depends:
* jquery.js v1.4+
* jquery.ui.widget.js v1.8+
*
*/
(function ($, undefined) {
var _super = $.Widget.prototype,
horizontal = {
pos: 'left',
pos2: 'right',
dim: 'width'
},
vertical = {
pos: 'top',
pos2: 'bottom',
dim: 'height'
};
$.widget('rs.carousel', {
options: {
itemsPerPage: 'auto',
itemsPerTransition: 'auto',
orientation: 'horizontal',
loop: false,
nextPrevActions: true,
insertPrevAction: function () {
return $('').appendTo(this);
},
insertNextAction: function () {
return $('').appendTo(this);
},
pagination: true,
insertPagination: function (pagination) {
return $(pagination).insertAfter($(this).find('.rs-carousel-mask'));
},
speed: 'normal',
easing: 'swing',
// callbacks
create: null,
before: null,
after: null
},
_create: function () {
this.page = 1;
this._elements();
this._defineOrientation();
this._addMask();
this._addNextPrevActions();
this.refresh(false);
return;
},
// caches DOM elements
_elements: function () {
var elems = this.elements = {},
baseClass = '.' + this.widgetBaseClass;
elems.mask = this.element.children(baseClass + '-mask');
elems.runner = this.element.find(baseClass + '-runner').first();
elems.items = elems.runner.children(baseClass + '-item');
elems.pagination = undefined;
elems.nextAction = undefined;
elems.prevAction = undefined;
return;
},
_addClasses: function () {
if (!this.oldClass) {
this.oldClass = this.element[0].className;
}
this._removeClasses();
var baseClass = this.widgetBaseClass,
classes = [];
classes.push(baseClass);
classes.push(baseClass + '-' + this.options.orientation);
classes.push(baseClass + '-items-' + this.options.itemsPerPage);
this.element.addClass(classes.join(' '));
return;
},
// removes rs-carousel* classes
_removeClasses: function () {
var self = this,
widgetClasses = [];
this.element.removeClass(function (i, classes) {
$.each(classes.split(' '), function (i, value) {
if (value.indexOf(self.widgetBaseClass) !== -1) {
widgetClasses.push(value);
}
});
return widgetClasses.join(' ');
});
return;
},
// defines obj to hold strings based on orientation for dynamic method calls
_defineOrientation: function () {
if (this.options.orientation === 'horizontal') {
this.isHorizontal = true;
this.helperStr = horizontal;
}
else {
this.isHorizontal = false;
this.helperStr = vertical;
}
return;
},
// adds masking div (aka clipper)
_addMask: function () {
var elems = this.elements;
// already exists in markup
if (elems.mask.length) {
return;
}
elems.mask = elems.runner
.wrap('
')
.parent();
// indicates whether mask was dynamically added or already existed in mark-up
this.maskAdded = true;
return;
},
// sets runners width
_setRunnerWidth: function () {
if (!this.isHorizontal) {
return;
}
var self = this;
this.elements.runner.width(function () {
return self._getItemDim() * self.getNoOfItems();
});
return;
},
// sets itemDim to the dimension of first item incl. margin
_getItemDim: function () {
// is this ridiculous??
return this.elements.items
['outer' + this.helperStr.dim.charAt(0).toUpperCase() + this.helperStr.dim.slice(1)](true);
},
getNoOfItems: function () {
return this.elements.items.length;
},
// adds next and prev links
_addNextPrevActions: function () {
if (!this.options.nextPrevActions) {
return;
}
var self = this,
elems = this.elements,
opts = this.options;
this._removeNextPrevActions();
elems.prevAction = opts.insertPrevAction.apply(this.element[0])
.bind('click.' + this.widgetName, function (e) {
e.preventDefault();
self.prev();
});
elems.nextAction = opts.insertNextAction.apply(this.element[0])
.bind('click.' + this.widgetName, function (e) {
e.preventDefault();
self.next();
});
return;
},
_removeNextPrevActions: function () {
var elems = this.elements;
if (elems.nextAction) {
elems.nextAction.remove();
elems.nextAction = undefined;
}
if (elems.prevAction) {
elems.prevAction.remove();
elems.prevAction = undefined;
}
return;
},
// adds pagination links and binds associated events
_addPagination: function () {
if (!this.options.pagination) {
return;
}
var self = this,
elems = this.elements,
opts = this.options,
baseClass = this.widgetBaseClass,
pagination = $(''),
links = [],
noOfPages = this.getNoOfPages(),
i;
this._removePagination();
for (i = 1; i <= noOfPages; i++) {
links[i] = '';
}
pagination
.append(links.join(''))
.delegate('a', 'click.' + this.widgetName, function (e) {
e.preventDefault();
self.goToPage(parseInt(this.hash.split('-')[1], 10));
});
this.elements.pagination = this.options.insertPagination.call(this.element[0], pagination);
return;
},
_removePagination: function () {
if (this.elements.pagination) {
this.elements.pagination.remove();
this.elements.pagination = undefined;
}
return;
},
// sets array of pages
_setPages: function () {
var index = 1,
page = 0,
noOfPages = this.getNoOfPages();
this.pages = [];
while (page < noOfPages) {
// if index is greater than total number of items just go to last
if (index > this.getNoOfItems()) {
index = this.getNoOfItems();
}
this.pages[page] = index;
index += this.getItemsPerTransition(); // this.getItemsPerPage(index);
page++;
}
return;
},
getPages: function () {
return this.pages;
},
// returns noOfPages
getNoOfPages: function () {
var itemsPerTransition = this.getItemsPerTransition();
// #18 - ensure we don't return Infinity
if (itemsPerTransition <= 0) {
return 0;
}
return Math.ceil((this.getNoOfItems() - this.getItemsPerPage()) / itemsPerTransition) + 1;
},
// returns options.itemsPerPage. If not a number it's calculated based on maskdim
getItemsPerPage: function () {
// if itemsPerPage of type number don't dynamically calculate
if (typeof this.options.itemsPerPage === 'number') {
return this.options.itemsPerPage;
}
return Math.floor(this._getMaskDim() / this._getItemDim());
},
getItemsPerTransition: function () {
if (typeof this.options.itemsPerTransition === 'number') {
return this.options.itemsPerTransition;
}
return this.getItemsPerPage();
},
_getMaskDim: function () {
return this.elements.mask[this.helperStr.dim]();
},
next: function (animate) {
var page = this.page + 1;
if (this.options.loop && page > this.getNoOfPages()) {
page = 1;
}
this.goToPage(page, animate);
return;
},
prev: function (animate) {
var page = this.page - 1;
if (this.options.loop && page < 1) {
page = this.getNoOfPages();
}
this.goToPage(page, animate);
return;
},
// shows specific page (one based)
goToPage: function (page, animate) {
if (!this.options.disabled && this._isValid(page)) {
this.prevPage = this.page;
this.page = page;
this._go(animate);
}
return;
},
// returns true if page index is valid, false if not
_isValid: function (page) {
if (page <= this.getNoOfPages() && page >= 1) {
return true;
}
return false;
},
// returns valid page index
_makeValid: function (page) {
if (page < 1) {
page = 1;
}
else if (page > this.getNoOfPages()) {
page = this.getNoOfPages();
}
return page;
},
// abstract _slide to easily override within extensions
_go: function (animate) {
this._slide(animate);
return;
},
_slide: function (animate) {
var self = this,
animate = animate === false ? false : true, // undefined should pass as true
speed = animate ? this.options.speed : 0,
animateProps = {},
lastPos = this._getAbsoluteLastPos(),
pos = this.elements.items
.eq(this.pages[this.page - 1] - 1) // arrays and .eq() are zero based, carousel is 1 based
.position()[this.helperStr.pos];
// check pos doesn't go past last posible pos
if (pos > lastPos) {
pos = lastPos;
}
// might be nice to put animate on event object:
// $.Event('slide', { animate: animate }) - would require jQuery 1.6+
this._trigger('before', null, {
elements: this.elements,
animate: animate
});
animateProps[this.helperStr.pos] = -pos;
this.elements.runner
.stop()
.animate(animateProps, speed, this.options.easing, function () {
self._trigger('after', null, {
elements: self.elements,
animate: animate
});
});
this._updateUi();
return;
},
// gets lastPos to ensure runner doesn't move beyond mask (allowing mask to be any width and the use of margins)
_getAbsoluteLastPos: function () {
var lastItem = this.elements.items.eq(this.getNoOfItems() - 1);
return lastItem.position()[this.helperStr.pos] + this._getItemDim() -
this._getMaskDim() - parseInt(lastItem.css('margin-' + this.helperStr.pos2), 10);
},
// updates pagination, next and prev link status classes
_updateUi: function () {
if (this.options.pagination) {
this._updatePagination();
}
if (this.options.nextPrevActions) {
this._updateNextPrevActions();
}
return;
},
_updatePagination: function () {
var baseClass = this.widgetBaseClass,
activeClass = baseClass + '-pagination-link-active';
this.elements.pagination
.children('.' + baseClass + '-pagination-link')
.removeClass(activeClass)
.eq(this.page - 1)
.addClass(activeClass);
return;
},
_updateNextPrevActions: function () {
var elems = this.elements,
page = this.page,
disabledClass = this.widgetBaseClass + '-action-disabled';
elems.nextAction
.add(elems.prevAction)
.removeClass(disabledClass);
if (!this.options.loop) {
if (page === this.getNoOfPages()) {
elems.nextAction.addClass(disabledClass);
}
else if (page === 1) {
elems.prevAction.addClass(disabledClass);
}
}
return;
},
// formalise appending items as continuous adding complexity by inserting
// cloned items
add: function (items) {
this.elements.runner.append(items);
this.refresh();
return;
},
remove: function (selector) {
if (this.getNoOfItems() > 0) {
this.elements.items
.filter(selector)
.remove();
this.refresh();
}
return;
},
// handles option updates
_setOption: function (option, value) {
var requiresRefresh = [
'itemsPerPage',
'itemsPerTransition',
'orientation'
];
_super._setOption.apply(this, arguments);
switch (option) {
case 'orientation':
this.elements.runner
.css(this.helperStr.pos, '')
.width('');
this._defineOrientation();
break;
case 'pagination':
if (value) {
this._addPagination();
this._updateUi();
}
else {
this._removePagination();
}
break;
case 'nextPrevActions':
if (value) {
this._addNextPrevActions();
this._updateUi();
}
else {
this._removeNextPrevActions();
}
break;
case 'loop':
this._updateUi();
break;
}
if ($.inArray(option, requiresRefresh) !== -1) {
this.refresh();
}
return;
},
// if no of items is less than items per page we disable carousel
_checkDisabled: function () {
if (this.getNoOfItems() <= this.getItemsPerPage()) {
this.elements.runner.css(this.helperStr.pos, '');
this.disable();
}
else {
this.enable();
}
return;
},
// refresh carousel
refresh: function (recache) {
// assume true (undefined should pass condition)
if (recache !== false) {
this._recacheItems();
}
this._addClasses();
this._setPages();
this._addPagination();
this._checkDisabled();
this._setRunnerWidth();
this.page = this._makeValid(this.page);
this.goToPage(this.page, false);
return;
},
// re-cache items in case new items have been added,
// moved to own method so continuous can easily override
// to avoid clones
_recacheItems: function () {
this.elements.items = this.elements.runner
.children('.' + this.widgetBaseClass + '-item');
return;
},
// returns carousel to original state
destroy: function () {
var elems = this.elements,
cssProps = {};
this.element
.removeClass()
.addClass(this.oldClass);
if (this.maskAdded) {
elems.runner
.unwrap('.' + this.widgetBaseClass + '-mask');
}
cssProps[this.helperStr.pos] = '';
cssProps[this.helperStr.dim] = '';
elems.runner.css(cssProps);
this._removePagination();
this._removeNextPrevActions();
_super.destroy.apply(this, arguments);
return;
},
getPage: function () {
return this.page;
},
getPrevPage: function () {
return this.prevPage;
},
// item can be $obj, element or 1 based index
goToItem: function (index, animate) {
// assume element or jQuery obj
if (typeof index !== 'number') {
index = this.elements.items.index(index) + 1;
}
if (index <= this.getNoOfItems()) {
this.goToPage(Math.ceil(index / this.getItemsPerTransition()), animate);
}
return;
}
});
$.rs.carousel.version = '0.8.6';
})(jQuery);