var Prototype={Version:'1.6.0.2',Browser:{IE:!!(window.attachEvent&&!window.opera),Opera:!!window.opera,WebKit:navigator.userAgent.indexOf('AppleWebKit/')>-1,Gecko:navigator.userAgent.indexOf('Gecko')>-1&&navigator.userAgent.indexOf('KHTML')==-1,MobileSafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/)},BrowserFeatures:{XPath:!!document.evaluate,ElementExtensions:!!window.HTMLElement,SpecificElementExtensions:document.createElement('div').__proto__&&document.createElement('div').__proto__!==document.createElement('form').__proto__},ScriptFragment:'
> ...
*/
UI.Carousel = Class.create(UI.Options, {
// Group: Options
options: {
// Property: direction
// Can be horizontal or vertical, horizontal by default
direction : "horizontal",
// Property: previousButton
// Selector of previous button inside carousel element, ".previous_button" by default,
// set it to false to ignore previous button
previousButton : ".previous_button",
// Property: nextButton
// Selector of next button inside carousel element, ".next_button" by default,
// set it to false to ignore next button
nextButton : ".next_button",
// Property: container
// Selector of carousel container inside carousel element, ".container" by default,
container : ".container",
// Property: scrollInc
// Define the maximum number of elements that gonna scroll each time, auto by default
scrollInc : "auto",
// Property: disabledButtonSuffix
// Define the suffix classanme used when a button get disabled, to '_disabled' by default
// Previous button classname will be previous_button_disabled
disabledButtonSuffix : '_disabled',
// Property: overButtonSuffix
// Define the suffix classanme used when a button has a rollover status, '_over' by default
// Previous button classname will be previous_button_over
overButtonSuffix : '_over'
},
/*
Group: Attributes
Property: element
DOM element containing the carousel
Property: id
DOM id of the carousel's element
Property: container
DOM element containing the carousel's elements
Property: elements
Array containing the carousel's elements as DOM elements
Property: previousButton
DOM id of the previous button
Property: nextButton
DOM id of the next button
Property: posAttribute
Define if the positions are from left or top
Property: dimAttribute
Define if the dimensions are horizontal or vertical
Property: elementSize
Size of each element, it's an integer
Property: nbVisible
Number of visible elements, it's a float
Property: animating
Define whether the carousel is in animation or not
*/
/*
Group: Events
List of events fired by a carousel
Notice: Carousel custom events are automatically namespaced in "carousel:" (see Prototype custom events).
Examples:
This example will observe all carousels
> document.observe('carousel:scroll:ended', function(event) {
> alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled");
> });
This example will observe only this carousel
> new UI.Carousel('horizontal_carousel').observe('scroll:ended', function(event) {
> alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled");
> });
Property: previousButton:enabled
Fired when the previous button has just been enabled
Property: previousButton:disabled
Fired when the previous button has just been disabled
Property: nextButton:enabled
Fired when the next button has just been enabled
Property: nextButton:disabled
Fired when the next button has just been disabled
Property: scroll:started
Fired when a scroll has just started
Property: scroll:ended
Fired when a scroll has been done,
memo.shift = number of elements scrolled, it's a float
Property: sizeUpdated
Fired when the carousel size has just been updated.
Tips: memo.carousel.currentSize() = the new carousel size
*/
// Group: Constructor
/*
Method: initialize
Constructor function, should not be called directly
Parameters:
element - DOM element
options - (Hash) list of optional parameters
Returns:
this
*/
initialize: function(element, options) {
this.setOptions(options);
this.element = $(element);
this.id = this.element.id;
this.container = this.element.down(this.options.container).firstDescendant();
this.elements = this.container.childElements();
this.previousButton = this.options.previousButton == false ? null : this.element.down(this.options.previousButton);
this.nextButton = this.options.nextButton == false ? null : this.element.down(this.options.nextButton);
this.posAttribute = (this.options.direction == "horizontal" ? "left" : "top");
this.dimAttribute = (this.options.direction == "horizontal" ? "width" : "height");
this.elementSize = this.computeElementSize();
this.nbVisible = this.currentSize() / this.elementSize;
var scrollInc = this.options.scrollInc;
if (scrollInc == "auto")
scrollInc = Math.floor(this.nbVisible);
[ this.previousButton, this.nextButton ].each(function(button) {
if (!button) return;
var className = (button == this.nextButton ? "next_button" : "previous_button") + this.options.overButtonSuffix;
button.clickHandler = this.scroll.bind(this, (button == this.nextButton ? -1 : 1) * scrollInc * this.elementSize);
button.observe("click", button.clickHandler)
.observe("mouseover", function() {button.addClassName(className)}.bind(this))
.observe("mouseout", function() {button.removeClassName(className)}.bind(this));
}, this);
this.updateButtons();
},
// Group: Destructor
/*
Method: destroy
Cleans up DOM and memory
*/
destroy: function($super) {
[ this.previousButton, this.nextButton ].each(function(button) {
if (!button) return;
button.stopObserving("click", button.clickHandler);
}, this);
this.element.remove();
this.fire('destroyed');
},
// Group: Event handling
/*
Method: fire
Fires a carousel custom event automatically namespaced in "carousel:" (see Prototype custom events).
The memo object contains a "carousel" property referring to the carousel.
Example:
> document.observe('carousel:scroll:ended', function(event) {
> alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled");
> });
Parameters:
eventName - an event name
memo - a memo object
Returns:
fired event
*/
fire: function(eventName, memo) {
memo = memo || { };
memo.carousel = this;
return this.element.fire('carousel:' + eventName, memo);
},
/*
Method: observe
Observe a carousel event with a handler function automatically bound to the carousel
Parameters:
eventName - an event name
handler - a handler function
Returns:
this
*/
observe: function(eventName, handler) {
this.element.observe('carousel:' + eventName, handler.bind(this));
return this;
},
/*
Method: stopObserving
Unregisters a carousel event, it must take the same parameters as this.observe (see Prototype stopObserving).
Parameters:
eventName - an event name
handler - a handler function
Returns:
this
*/
stopObserving: function(eventName, handler) {
this.element.stopObserving('carousel:' + eventName, handler);
return this;
},
// Group: Actions
/*
Method: checkScroll
Check scroll position to avoid unused space at right or bottom
Parameters:
position - position to check
updatePosition - should the container position be updated ? true/false
Returns:
position
*/
checkScroll: function(position, updatePosition) {
if (position > 0)
position = 0;
else {
var limit = this.elements.last().positionedOffset()[this.posAttribute] + this.elementSize;
var carouselSize = this.currentSize();
if (this.options.direction == "vertical") {
if((position + limit) <= 0)
position = position+this.elementSize;
} else {
if (position + limit < carouselSize)
position -= carouselSize - (position + limit);
}
position = Math.min(position, 0);
}
if (updatePosition)
this.container.style[this.posAttribute] = position + "px";
return position;
},
/*
Method: scroll
Scrolls carousel from maximum deltaPixel
Parameters:
deltaPixel - a float
Returns:
this
*/
scroll: function(deltaPixel) {
if (this.animating)
return this;
// Compute new position
var position = this.currentPosition() - deltaPixel;
// Check bounds
position = this.checkScroll(position, false);
// Compute shift to apply
deltaPixel = position - this.currentPosition();
if (deltaPixel != 0) {
this.animating = true;
this.fire("scroll:started");
var that = this;
// Move effects
this.container.morph("opacity:0.5", {duration: 0.2, afterFinish: function() {
that.container.morph(that.posAttribute + ": " + position + "px", {
duration: 0.5,
delay: 0.2,
transition:Effect.Transitions.easeInOutQuad,
//transition:Effect.Transitions.easeOutBack,
afterFinish: function() {
that.container.morph("opacity:1", {
duration: 0.2,
afterFinish: function() {
that.animating = false;
that.updateButtons()
.fire("scroll:ended", { shift: deltaPixel / that.currentSize() });
}
});
}
});
}});
}
return this;
},
/*
Method: scrollTo
Scrolls carousel, so that element with specified index is the left-most.
This method is convenient when using carousel in a tabbed navigation.
Clicking on first tab should scroll first container into view, clicking on a fifth - fifth one, etc.
Indexing starts with 0.
Parameters:
Index of an element which will be a left-most visible in the carousel
Returns:
this
*/
scrollTo: function(index) {
if (this.animating || index < 0 || index > this.elements.length || index == this.currentIndex() || isNaN(parseInt(index)))
return this;
return this.scroll((this.currentIndex() - index) * this.elementSize);
},
/*
Method: updateButtons
Update buttons status to enabled or disabled
Them status is defined by classNames and fired as carousel's custom events
Returns:
this
*/
updateButtons: function() {
this.updatePreviousButton();
this.updateNextButton();
return this;
},
updatePreviousButton: function() {
var position = this.currentPosition();
var lastPosition = this.currentLastPosition();
var size = this.currentSize();
var previousClassName = "previous_button" + this.options.disabledButtonSuffix;
if (this.options.direction == "vertical") {
if (this.previousButton.hasClassName(previousClassName) && (lastPosition - this.elementSize) != 0) {
this.previousButton.removeClassName(previousClassName);
this.fire('previousButton:enabled');
}
if (!this.previousButton.hasClassName(previousClassName) && (lastPosition - this.elementSize) == 0) {
this.previousButton.addClassName(previousClassName);
this.fire('previousButton:disabled');
}
}else {
if (this.previousButton.hasClassName(previousClassName) && position != 0) {
this.previousButton.removeClassName(previousClassName);
this.fire('previousButton:enabled');
}
if (!this.previousButton.hasClassName(previousClassName) && position == 0) {
this.previousButton.addClassName(previousClassName);
this.fire('previousButton:disabled');
}
}
},
updateNextButton: function() {
var lastPosition = this.currentLastPosition();
var size = this.currentSize();
var position = this.currentPosition();
var nextClassName = "next_button" + this.options.disabledButtonSuffix;
if (this.options.direction == "vertical") {
if (this.nextButton.hasClassName(nextClassName) && position != 0) {
this.nextButton.removeClassName(nextClassName);
this.fire('nextButton:enabled');
}
if (!this.nextButton.hasClassName(nextClassName) && position == 0) {
this.nextButton.addClassName(nextClassName);
this.fire('nextButton:disabled');
}
}else {
if (this.nextButton.hasClassName(nextClassName) && lastPosition != size) {
this.nextButton.removeClassName(nextClassName);
this.fire('nextButton:enabled');
}
if (!this.nextButton.hasClassName(nextClassName) && lastPosition == size) {
this.nextButton.addClassName(nextClassName);
this.fire('nextButton:disabled');
}
}
},
// Group: Size and Position
/*
Method: computeElementSize
Return elements size in pixel, height or width depends on carousel orientation.
Returns:
an integer value
*/
computeElementSize: function() {
return this.elements.first().getDimensions()[this.dimAttribute];
},
/*
Method: currentIndex
Returns current visible index of a carousel.
For example, a horizontal carousel with image #3 on left will return 3 and with half of image #3 will return 3.5
Don't forget that the first image have an index 0
Returns:
a float value
*/
currentIndex: function() {
return - this.currentPosition() / this.elementSize;
},
/*
Method: currentLastPosition
Returns the current position from the end of the last element. This value is in pixel.
Returns:
an integer value, if no images a present it will return 0
*/
currentLastPosition: function() {
if (this.container.childElements().empty())
return 0;
return this.currentPosition() +
this.elements.last().positionedOffset()[this.posAttribute] +
this.elementSize;
},
/*
Method: currentPosition
Returns the current position in pixel.
Tips: To get the position in elements use currentIndex()
Returns:
an integer value
*/
currentPosition: function() {
return this.container.getNumStyle(this.posAttribute);
},
/*
Method: currentSize
Returns the current size of the carousel in pixel
Returns:
Carousel's size in pixel
*/
currentSize: function() {
return this.container.parentNode.getDimensions()[this.dimAttribute];
},
/*
Method: updateSize
Should be called if carousel size has been changed (usually called with a liquid layout)
Returns:
this
*/
updateSize: function() {
this.nbVisible = this.currentSize() / this.elementSize;
var scrollInc = this.options.scrollInc;
if (scrollInc == "auto")
scrollInc = Math.floor(this.nbVisible);
[ this.previousButton, this.nextButton ].each(function(button) {
if (!button) return;
button.stopObserving("click", button.clickHandler);
button.clickHandler = this.scroll.bind(this, (button == this.nextButton ? -1 : 1) * scrollInc * this.elementSize);
button.observe("click", button.clickHandler);
}, this);
this.checkScroll(this.currentPosition(), true);
this.updateButtons().fire('sizeUpdated');
return this;
}
});
/*
Class: UI.Ajax.Carousel
Gives the AJAX power to carousels. An AJAX carousel :
* Use AJAX to add new elements on the fly
Example:
> new UI.Ajax.Carousel("horizontal_carousel",
> {url: "get-more-elements", elementSize: 250});
*/
UI.Ajax.Carousel = Class.create(UI.Carousel, {
// Group: Options
//
// Notice:
// It also include of all carousel's options
options: {
// Property: elementSize
// Required, it define the size of all elements
elementSize : -1,
// Property: url
// Required, it define the URL used by AJAX carousel to request new elements details
url : null
},
/*
Group: Attributes
Notice:
It also include of all carousel's attributes
Property: elementSize
Size of each elements, it's an integer
Property: endIndex
Index of the last loaded element
Property: hasMore
Flag to define if there's still more elements to load
Property: requestRunning
Define whether a request is processing or not
Property: updateHandler
Callback to update carousel, usually used after request success
Property: url
URL used to request additional elements
*/
/*
Group: Events
List of events fired by an AJAX carousel, it also include of all carousel's custom events
Property: request:started
Fired when the request has just started
Property: request:ended
Fired when the request has succeed
*/
// Group: Constructor
/*
Method: initialize
Constructor function, should not be called directly
Parameters:
element - DOM element
options - (Hash) list of optional parameters
Returns:
this
*/
initialize: function($super, element, options) {
if (!options.url)
throw("url option is required for UI.Ajax.Carousel");
if (!options.elementSize)
throw("elementSize option is required for UI.Ajax.Carousel");
$super(element, options);
this.endIndex = 0;
this.hasMore = true;
// Cache handlers
this.updateHandler = this.update.bind(this);
this.updateAndScrollHandler = function(nbElements, transport, json) {
this.update(transport, json);
this.scroll(nbElements);
}.bind(this);
// Run first ajax request to fill the carousel
this.runRequest.bind(this).defer({parameters: {from: 0, to: Math.ceil(this.nbVisible) - 1}, onSuccess: this.updateHandler});
},
// Group: Actions
/*
Method: runRequest
Request the new elements details
Parameters:
options - (Hash) list of optional parameters
Returns:
this
*/
runRequest: function(options) {
this.requestRunning = true;
new Ajax.Request(this.options.url, Object.extend({method: "GET"}, options));
this.fire("request:started");
return this;
},
/*
Method: scroll
Scrolls carousel from maximum deltaPixel
Parameters:
deltaPixel - a float
Returns:
this
*/
scroll: function($super, deltaPixel) {
if (this.animating || this.requestRunning)
return this;
var nbElements = (-deltaPixel) / this.elementSize;
// Check if there is not enough
if (this.hasMore && nbElements > 0 && this.currentIndex() + this.nbVisible + nbElements - 1 > this.endIndex) {
var from = this.endIndex + 1;
var to = Math.ceil(from + this.nbVisible - 1);
this.runRequest({parameters: {from: from, to: to}, onSuccess: this.updateAndScrollHandler.curry(deltaPixel).bind(this)});
return this;
}
else
$super(deltaPixel);
},
/*
Method: update
Update the carousel
Parameters:
transport - XMLHttpRequest object
json - JSON object
Returns:
this
*/
update: function(transport, json) {
this.requestRunning = false;
this.fire("request:ended");
if (!json)
json = transport.responseJSON;
this.hasMore = json.more;
this.endIndex = Math.max(this.endIndex, json.to);
this.elements = this.container.insert({bottom: json.html}).childElements();
return this.updateButtons();
},
// Group: Size and Position
/*
Method: computeElementSize
Return elements size in pixel
Returns:
an integer value
*/
computeElementSize: function() {
return this.options.elementSize;
},
/*
Method: updateSize
Should be called if carousel size has been changed (usually called with a liquid layout)
Returns:
this
*/
updateSize: function($super) {
var nbVisible = this.nbVisible;
$super();
// If we have enough space for at least a new element
if (Math.floor(this.nbVisible) - Math.floor(nbVisible) >= 1 && this.hasMore) {
if (this.currentIndex() + Math.floor(this.nbVisible) >= this.endIndex) {
var nbNew = Math.floor(this.currentIndex() + Math.floor(this.nbVisible) - this.endIndex);
this.runRequest({parameters: {from: this.endIndex + 1, to: this.endIndex + nbNew}, onSuccess: this.updateHandler});
}
}
return this;
},
updateNextButton: function($super) {
var lastPosition = this.currentLastPosition();
var size = this.currentSize();
var nextClassName = "next_button" + this.options.disabledButtonSuffix;
if (this.nextButton.hasClassName(nextClassName) && lastPosition != size) {
this.nextButton.removeClassName(nextClassName);
this.fire('nextButton:enabled');
}
if (!this.nextButton.hasClassName(nextClassName) && lastPosition == size && !this.hasMore) {
this.nextButton.addClassName(nextClassName);
this.fire('nextButton:disabled');
}
}
});
/**
* Easing Equations for Script.aculo.us
* @author Brian Crescimanno
* @version 0.8.1
* @revised November 20, 2008
* @copyright 2008 Brian Crescimanno, all rights reserved
*
* Released under terms of the BSD License
* http://www.opensource.org/licenses/bsd-license.php
*
* The math for these equations was created by Robert Penner
* http://www.robertpenner.com/profmx
*
* -----------------------------------------------------------------------------------------
* Do not remove any comments above this line, below comments may be removed to save space.
*
* An adaptation of Robert Penner's "easing equations" as seen in many Flash animations for
* Script.aculo.us 1.8. One of my great pains in working with Script.aculo.us over other
* libraries was the lack of these easing equations so I set about to port as many of the
* equations as I could.
*
* Imlements from Penner's equations:
* Quadratic
* Cubic
* Quartic
* Quintic
* Sinusoidal
* Exponential
* Circular
* Bounce (easeOut only)
* Back
*
* Does not implement (yet)
* Elastic
* Bounce (easeIn, easeInOut)
*
* Ken Snyder provided a few reference implementations of Penner equations for
* Script.aculo.us; these reference implementations aided my work in porting a
* (more) complete set.
*
*/
/****** Quadratic ******/
Effect.Transitions.easeInQuad = function(pos){
return Math.pow(pos, 2);
}
Effect.Transitions.easeOutQuad = function(pos){
return -(Math.pow((pos-1), 2) -1);
}
Effect.Transitions.easeInOutQuad = function(pos){
if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,2);
return -0.5 * ((pos-=2)*pos - 2);
}
/****** Cubic ******/
Effect.Transitions.easeInCubic = function(pos){
return Math.pow(pos, 3);
}
Effect.Transitions.easeOutCubic = function(pos){
return (Math.pow((pos-1), 3) +1);
}
Effect.Transitions.easeInOutCubic = function(pos){
if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
return 0.5 * (Math.pow((pos-2),3) + 2);
}
/****** Quartic ******/
Effect.Transitions.easeInQuart = function(pos){
return Math.pow(pos, 4);
}
Effect.Transitions.easeOutQuart = function(pos){
return -(Math.pow((pos-1), 4) -1)
}
Effect.Transitions.easeInOutQuart = function(pos){
if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
}
/****** Quintic ******/
Effect.Transitions.easeInQuint = function(pos){
return Math.pow(pos, 5);
}
Effect.Transitions.easeOutQuint = function(pos){
return (Math.pow((pos-1), 5) +1);
}
Effect.Transitions.easeInOutQuint = function(pos){
if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,5);
return 0.5 * (Math.pow((pos-2),5) + 2);
}
/****** Sinusoidal ******/
Effect.Transitions.easeInSine = function(pos){
return -Math.cos(pos * (Math.PI/2)) + 1;
}
Effect.Transitions.easeOutSine = function(pos){
return Math.sin(pos * (Math.PI/2));
}
Effect.Transitions.easeInOutSine = function(pos){
return (-.5 * (Math.cos(Math.PI*pos) -1));
}
/****** Exponential ******/
Effect.Transitions.easeInExpo = function(pos){
return (pos==0) ? 0 : Math.pow(2, 10 * (pos - 1));
}
Effect.Transitions.easeOutExpo = function(pos){
return (pos==1) ? 1 : -Math.pow(2, -10 * pos) + 1;
}
Effect.Transitions.easeInOutExpo = function(pos){
if(pos==0) return 0;
if(pos==1) return 1;
if((pos/=0.5) < 1) return 0.5 * Math.pow(2,10 * (pos-1));
return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
}
/****** Circular ******/
Effect.Transitions.easeInCirc = function(pos){
return -(Math.sqrt(1 - (pos*pos)) - 1);
}
Effect.Transitions.easeOutCirc = function(pos){
return Math.sqrt(1 - Math.pow((pos-1), 2))
}
Effect.Transitions.easeInOutCirc = function(pos){
if((pos/=0.5) < 1) return -0.5 * (Math.sqrt(1 - pos*pos) - 1);
return 0.5 * (Math.sqrt(1 - (pos-=2)*pos) + 1);
}
/****** Bounce ******/
Effect.Transitions.easeInBounce = function(pos){
return 1;
}
Effect.Transitions.easeOutBounce = function(pos){
if ((pos) < (1/2.75)) {
return (7.5625*pos*pos);
} else if (pos < (2/2.75)) {
return (7.5625*(pos-=(1.5/2.75))*pos + .75);
} else if (pos < (2.5/2.75)) {
return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
} else {
return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
}
}
Effect.Transitions.easeInOutBounce = function(pos){
return 1;
}
/****** Back ******/
Effect.Transitions.easeInBack = function(pos){
var s = 1.70158;
return (pos)*pos*((s+1)*pos - s);
}
Effect.Transitions.easeOutBack = function(pos){
var s = 1.70158;
return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
}
Effect.Transitions.easeInOutBack = function(pos){
var s = 1.70158;
if((pos/=0.5) < 1) return 0.5*(pos*pos*(((s*=(1.525))+1)*pos -s));
return 0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos +s) +2);
}
/****** Elastic ******/
Effect.Transitions.easeInElastic = function(pos){
return 1;
}
Effect.Transitions.easeOutElastic = function(pos){
return 1;
}
Effect.Transitions.easeInOutElastic = function(pos){
return 1;
}