/*
* jCoverflip - Present your featured content elegantly.
* Version: 1.0.2
* Copyright 2010 New Signature
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see .
*
* You can contact New Signature by electronic mail at labs@newsignature.com
* or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
*/
( function( $ ){
//
// Helpers
//
var undefined; // safeguards against anyone who wants to change the value of undefined in the global space
var nofn = function(){};
var proxy = function( context, fn ){
return function( ){
if( $.isFunction( fn ) ){
return fn.apply( context, arguments );
} else {
return context[ fn ].apply( context, arguments );
}
};
};
//
// Animation queue
//
animationqueue = { };
/**
* Animation Queue
*
* The AnimationQueue holds list of AnimationSets that each perform a set of animations. The sets
* run in ordering when the AnimationQueue is animating. As well, the running time is distributed
* across all the sets when the AnimationQueue is animating.
*
* As well, the AnimationQueue can be stopped and started again at any time. AnimationSets can be
* removed when stopped when they are not needed anymore.
*
*/
animationqueue.AnimationQueue = function(){
this.sets = [];
this.isRunning = false;
this.current = 0;
this.totalTime = 0;
this.elapsedTime = 0;
this.startTime = 1;
this.poll = null;
};
animationqueue.AnimationQueue.prototype = {
/**
* Add an AnimationSet to the end of the queue.
*
* @param animationSet
* The AnimationSet object to add to the end of the queue.
*/
queue: function( animationSet ){
this.sets.push( animationSet );
},
/**
* Start the animation.
*
* @param time
* The total running time of the animation in milliseconds.
*/
start: function( time, callback ){
this.totalTime = time;
this.elapsedTime = 0;
this.isRunning = true;
callback = $.isFunction( callback )? callback : nofn;
// calculate the how to divide the time between the sets
// Each set gets 1 part of the time except for the first which can get less if "paused."
var timeShare = 1; // total number of parts to divide the time by
var firstTimeShare = 1; // the part for the first item
if( this.sets.length > 0 ){
timeShare = this.sets.length;
firstTimeShare = this.sets[ 0 ].totalTime > 0? Math.max( 0, Math.min( 1, 1-(this.sets[ 0 ].elapsedTime / this.sets[ 0 ].totalTime) ) ): 1;
//timeShare += firstTimeShare;
}
var startTime = (new Date( )).getTime( );
if( this.sets.length > 0 ){
this.sets[ 0 ].start( firstTimeShare/timeShare*this.totalTime );
}
(function( self, totalTime, timeShare ){
function poll( ){
self.elapsedTime = (new Date( )).getTime( ) - startTime;
if( self.sets.length && !self.sets[ 0 ].isRunning ){
self.sets.shift( );
if( self.sets.length ){
self.sets[ 0 ].start( totalTime/timeShare );
}
}
if( self.elapsedTime >= self.totalTime && self.sets.length == 0 ){
callback( self.elapsedTime );
self.stop( );
}
}
self.poll = setInterval( poll, 16 );
} )( this, this.totalTime, timeShare );
},
/**
* Stop the animation.
*/
stop: function( ){
if( this.isRunning ){
this.isRunning = false;
var i = this.sets.length;
while( i-- ){
this.sets[ i ].stop( );
}
clearInterval( this.poll );
}
},
/**
* Remove an AnimationSet from the queue.
*
* @param animationSet
* The AnimationSet to remove from the queue.
*/
remove: function( animationSet ){
var i = this.sets.length;
while( i-- ){
if( this.sets[ i ] == animationSet ){
var newSets = this.sets.slice( 0, i );
this.animationSet = newSets.push( this.sets.slice( i + 1 ) );
}
}
},
/**
* Get an array of all the AnimationSets in order. The array is not live, but the AnimationSets are.
*
* @return array of AnimationSets
*/
get: function( ){
return this.sets.slice( 0 ); // .slice is to clone the array but not the objects
}
};
/**
*
*
*
*/
animationqueue.AnimationSet = function(){
this.steps = [ ];
this.isStepsSorted = true;
this.currentStep = -1;
this.animations = [ ];
this.isRunning = false;
this.totalTime = 0;
this.elapsedTime = 0;
this.poll = null;
this.data = { };
};
animationqueue.AnimationSet.prototype = {
/**
* Add an AnimationStep or Animation for this set.
*
* @param anim
* The AnimationStep or Animation to add.
*/
add: function( anim ){
if( anim instanceof animationqueue.AnimationStep ){
this.steps.push( anim );
this.isStepsSorted = false;
++this.currentStep;
} else if( anim instanceof animationqueue.Animation ){
this.animations.push( anim );
}
},
/**
* Start the set's animation.
*
* @param time
* The total running time of the animation.
*/
start: function( time ){
// Scale the previous elapsedTime to the new time
this.elapsedTime = this.totalTime == 0? 0: this.elapsedTime/this.totalTime*time;
this.totalTime = time;
// prepare this to run
if( !this.isStepsSorted ){
// Reverse sort
this.steps.sort( function( a, b ){
return b.moment - a.moment;
} );
this.isStepsSorted = true;
}
this.isRunning = true;
// Start the time up here right before any of the animation starts
this.startTime = (new Date()).getTime() - this.elapsedTime;
// Start up the animations
var i = this.animations.length;
while( i-- ){
this.animations[ i ].start( this.totalTime );
}
// The polling function: this will run the steps at the right time and
// check for when the animations are finished.
var self = this;
var animationsIndex = this.animations.length-1;
function poll( timeSince ){
self.elapsedTime = (new Date()).getTime() - self.startTime;
// Run any steps that should be run
while( self.currentStep >= 0 && self.steps[ self.currentStep ].getTime( self.totalTime ) <= self.elapsedTime ){
self.steps[ self.currentStep ].doIt( );
--self.currentStep;
}
// Check if all the animations are finished
if( self.elapsedTime >= self.totalTime && self.currentStep < 0){
while( animationsIndex >= 0 && !self.animations[ animationsIndex ].isRunning ){
--animationsIndex;
}
if( animationsIndex < 0 ){
// finished
self.reset( self.elapsedTime );
}
}
}
this.poll = setInterval( poll, 16 );
},
/**
* Stop the animation.
*/
stop: function( ){
if( this.isRunning ){
this.isRunning = false;
if( this.poll ){
clearInterval( this.poll );
}
var i = this.animations.length;
while( i-- ){
this.animations[ i ].stop( );
}
}
},
/**
* Set meta data for the set.
*
* @param key
* The key for the meta data.
*
* @param data
* The data.
*/
setData: function( key, data ){
this.data[ key ] = data;
},
/**
* Get meta data for the set.
*
* @param key
* The key used to save the meta data.
*
* @return The value of the meta data
*/
getData: function( key ){
return this.data[ key ];
},
/**
* Resets the set to beginning.
*/
reset: function( ){
this.stop( );
this.elapsedTime = 0;
this.currentStep = this.steps.length-1;
}
};
/**
* A single step to occur during a set's animation.
*
* This is useful for handling one off things during a set's animation such
* as switch the z-index.
*
* @param $element
* The jQuery object for the element(s) to update their CSS
*
* @param cssParams
* A key/value object of style properties. @see http://docs.jquery.com/CSS/css#properties
*
* @param moment
* A number from 0 to 1 for when as a percentage of the set's running time the
* step should happen.
*/
animationqueue.AnimationStep = function( $element, cssParams, moment ){
this.$element = $element;
this.cssParams = cssParams;
this.moment = Math.min( 1, Math.max( 0, moment ) );
};
animationqueue.AnimationStep.prototype = {
/**
* Get the time of execution
*
* @param totalTime
* The total of time for the set this belongs to in milliseconds.
*
* @return The time in milliseconds this step needs to execute.
*/
getTime: function( totalTime ){
return this.moment * totalTime;
},
/**
* Does the step action.
*/
doIt: function( ){
this.$element.css( this.cssParams );
}
};
/**
* A single animation object for a jQuery object.
*
* @param $element
* The jQuery object for the element(s) to animate
*
* @param animateParams
* The object of CSS values to animate. @see http://docs.jquery.com/Effects/animate
*/
animationqueue.Animation = function( $element, animateParams ){
this.$element = $element;
this.animateParams = animateParams;
this.isRunning = false;
};
animationqueue.Animation.prototype = {
/**
* Start the animation.
*
* @param time
* The running time of the animation (and the step) in milliseconds.
*/
start: function( time ){
this.$element.stop( );
if( time === 0 ){
this.$element.css( this.animateParams );
self.isRunning = false;
} else {
this.isRunning = true;
this.$element.animate( this.animateParams, time, proxy( this, function( ){
this.isRunning = false;
} ) );
}
},
/**
* Stop the animation.
*/
stop: function( ){
this.$element.stop( );
this.isRunning = false;
}
};
//
// The widget
//
//
// Static methods
$.jcoverflip = {
/**
* Used for wrapping the animation for an element for returned by beforeCss, afterCss and
* currentCss options.
*
* @param element
* The jQuery element to run the animation on.
*
* @param animate
* An object with CSS keys and values to animate to.
*
* @param steps
* An object with keys from 0 to 1 (0 to 100%) for how far along in the animation (0: start,
* 0.5: half way through, 1: end) with the value being an object of CSS keys and values to change.
* This is for discrete values that need to change such as z-index.
*
*/
animationElement: function( element, animate, steps ){
return { element: element, animate: animate, steps: steps };
},
/**
* Find the item element and index number that the element is associated.
*
* @param element
* The element that either is the item element or descendant element of the item element.
*
* @return
* null - if no item element is found
* { element: , index: }
*
*/
getItemFromElement: function( element ){
element = $( element );
var item = element.hasClass( 'ui-jcoverflip--item' )? element : element.parents( '.ui-jcoverflip--item' );
if( item.size( ) == 0 ){
return null;
} else {
return { element: item, index: item.data( 'jcoverflip__index' ) };
}
}
};
// The widget
$.widget( 'ui.jcoverflip', {
_init: function( ){
// init some internal values
this.animationQueue = new animationqueue.AnimationQueue( );
this.isInit = false; // used for setting up the CSS
// Used to queue up overlapping goTo() calls since they come in async
this.goToPoll = { id: null };
this.goToQueue = [ ];
// Setup the elements
var items = this.items( );
// add classes
this.element.addClass( 'ui-jcoverflip' );
items.addClass( 'ui-jcoverflip--item' );
// Get the title for each item
var i = items.size( );
while( i-- ){
var el = items.eq( i );
// Tell the item what its index is
el.data( 'jcoverflip__index', i );
// Create the titles for the coverflow items
var title = this.options.titles.create( el );
title.css( { display: 'none' } ).addClass( 'ui-jcoverflip--title' ).appendTo( this.element );
el.data( 'jcoverflip__titleElement', title );
}
// Bind the click action for when the user clicks on the item to change the current
this.element.click( proxy( this, this._clickItem ) );
// setup the positioning of the elements, pass 0 for time, pass true to flag to init
this._goTo( this.options.current, 0, true );
// Add any addition controls (such as a scroll bar)
this.options.controls.create( this.element, this.length() );
},
/**
* The click event for an item. If the item is not current,
* then it calls the current() and stops the event.
*/
_clickItem: function( event ){
if( this.options.disabled == true ){
return;
}
var item = $.jcoverflip.getItemFromElement( event.target );
if( item !== null && item.index != this.current( ) ){
this.current( item.index, event );
event.preventDefault();
return false;
}
return true;
},
/**
* Parses the parameters for next and previous methods. Any of the parameters are optional.
*/
_nextAndPrevParameters: function( by, wrapAround, callback, originalEvent ){
// originalEvent is an object
if( typeof by == 'object' ){
originalEvent = by;
} else if( typeof wrapAround == 'object' ){
originalEvent = wrapAround;
} else if( typeof callback == 'object' ){
originalEvent = callback;
} else if( typeof originalEvent == 'object' ){
originalEvent = originalEvent;
} else {
originalEvent = { };
}
// callback is a function
if( $.isFunction( by ) ){
callback = by;
} else if( $.isFunction( wrapAround ) ){
callback = wrapAround;
} else if( $.isFunction( callback ) ){
callback = callback;
} else {
callback = nofn;
}
// wrapAround is boolean
if( typeof( by ) == 'boolean' ) {
wrapAround = by;
} else if( typeof( wrapAround ) == 'boolean' ){
wrapAround = wrapAround;
} else {
wrapAround = true;
}
// by is a number
by = isNaN( parseInt( by ) )? 1 : parseInt( by );
return { by: by, wrapAround: wrapAround, callback: callback, originalEvent: originalEvent };
},
/**
* Step to the right from the current.
*
* @param by
* (optional) An integer to step to the right by. Defaults to 1.
*
* @param wrapAround
* (optional) A boolean flag to wrap around if moving past the end. Defaults to true.
*
* @return
* New current number
*/
next: function( by, wrapAround, callback, originalEvent ){
if( this.options.disabled == true ){
return;
}
var params = this._nextAndPrevParameters( by, wrapAround, callback, originalEvent );
return this._nextAux( params.by, params.wrapAround, params.callback, params.originalEvent, 'next' );
},
_nextAux: function( by, wrapAround, callback, originalEvent, eventType ){
by = by === undefined && isNaN( by ) ? 1 : parseInt( by );
wrapAround = wrapAround !== false;
var current = this.current( );
var oldCurrent = current;
var length = this.length( );
if( wrapAround ){
current = (current + by) % length;
// If "current + by" is negative, then the result of "%" is between -(length-1) and -1.
// Add the length, if negative, to bring the index back to a valid number
current = current < 0 ? current + length : current;
} else {
current = Math.min( length-1, Math.max( 0, current + by ) );
}
if( current != this.current( ) ){
this.current( current, originalEvent );
}
if( eventType && oldCurrent != current ){
var event = $.Event( originalEvent );
event.type = this.widgetEventPrefix + eventType;
callback.call( this.element, event, { from: oldCurrent, to: current } );
this._trigger( eventType, originalEvent, { from: oldCurrent, to: current } );
}
return current;
},
/**
* Step to the left from the current.
*
* @param by
* (optional) An integer to step to the left by. Defaults to 1.
*
* @param wrapAround
* (optional) A boolean flag to wrap around if moving past the end. Defaults to true.
*
* @return
* New current number
*
*/
previous: function( by, wrapAround, callback, originalEvent ){
if( this.options.disabled == true ){
return;
}
var params = this._nextAndPrevParameters( by, wrapAround, callback, originalEvent );
return this._nextAux( -1*params.by, params.wrapAround, params.callback, params.originalEvent, 'previous' );
},
/**
* Go all the way to the left.
*/
first: function( callback, originalEvent ){
if( this.options.disabled == true ){
return;
}
if( typeof callback == 'object' ){
originalEvent = callback
} else if( typeof originalEvent == 'object' ){
originalEvent = originalEvent;
} else {
originalEvent = { };
}
callback = $.isFunction( callback ) ? callback : nofn;
var from = this.current( );
var to = this.current( 0, originalEvent );
if( from != to ){
var event = $.Event( originalEvent );
event.type = this.widgetEventPrefix + 'first';
callback.call( this.element, event, { from: from, to: to } );
this._trigger( 'first', originalEvent, { from: from, to: to } );
}
},
/**
* Go all the way to the right.
*/
last: function( callback, originalEvent ){
if( this.options.disabled == true ){
return;
}
if( typeof callback == 'object' ){
originalEvent = callback
} else if( typeof originalEvent == 'object' ){
originalEvent = originalEvent;
} else {
originalEvent = { };
}
callback = $.isFunction( callback ) ? callback : nofn;
var from = this.current( );
var to = this.current( this.length( ) - 1, originalEvent );
if( from != to ){
var event = $.Event( originalEvent );
event.type = this.widgetEventPrefix + 'last';
callback.call( this.element, event, { from: from, to: to } );
this._trigger( 'last', originalEvent, { from: from, to: to } );
}
},
/**
* Gets or sets the current item.
*
* @param originalEvent (optional)
* Pass an event object along to be assigned to the originalEvent for the event object passed
* along with the triggered events of start, stop and change.
*/
current: function( newCurrent, originalEvent ){
if( newCurrent !== undefined && !isNaN( newCurrent ) && !this.options.disabled && newCurrent != this.options.current ){
this._goTo( newCurrent, undefined, false, originalEvent );
}
return this.options.current;
},
destroy: function( ){
if( this.options.disabled == true ){
return;
}
// let others clean up first
this._trigger( 'destroy', {} );
// container element
this.element.removeClass( 'ui-jcoverflip' );
// titles
var items = this.items( );
var titleEl;
var i = items.length;
while( i-- ){
titleEl = items.eq( i ).data( 'jcoverflip__titleElement' );
this.options.titles.destroy( titleEl );
}
// items
// aggressively remove all inline styles
items
.removeClass( 'ui-jcoverflip--item' )
.find( '*' ).add( items.get( ) )
.each( function( ){
this.removeAttribute( 'style' );
} );
// controls
this.options.controls.destroy( this.element );
// default action
$.widget.prototype.destroy.apply( this, arguments );
},
enable: function( ){
$.widget.prototype.enable.apply( this, arguments );
this._trigger( 'enable', {} );
},
disable: function( ){
$.widget.prototype.disable.apply( this, arguments );
this._trigger( 'disable', {} );
},
option: function( name, value ){
// getter
if( typeof value == 'undefined' ){
return $.widget.prototype.option.apply( this, arguments );
}
// setter
// current
if( name == 'current' ){
return this.current( value );
}
// TODO: dynamic changing of the options: items, titles, controls
// items, titles, controls
if( name in { 'items': '', 'titles': '', 'controls': '' } ){
return this.options.items;
}
// beforeCss, afterCss, currentCss
if( name in { 'beforeCss': '', 'afterCss': '', 'currentCss': '' } ){
this.options[ name ] = value;
// force update positioning
this._goTo( this.current( ), 0, true );
}
// time
if( name == 'time' && isNaN( parseInt( value ) ) && parseInt( value ) < 0 ){
return this.options.time;
}
// Default action
return $.widget.prototype.option.apply( this, arguments );
},
/**
* Go to a particular coverflow item.
*
* @param index
* The item index.
*
* @param time
* Optional. The time to do the animation to the new item in.
*
*/
_goTo: function( index, time, force, originalEvent ){
if( this.options.disabled == true ){
return;
}
force = !!force;
originalEvent = originalEvent == undefined? { } : originalEvent;
// Get the time to run
time = time === undefined? this.options.time: parseInt( time );
// Setup current and oldCurrent
var oldCurrent = this.options.current;
var current = Math.floor( Math.max( 0, Math.min( index, this.length( )-1 ) ) );
this.options.current = current;
// Start working on the animation queue
// 1. Stop the current animation
// 2. Remove sets that are moving away from the current item
// 3. Add needed sets to move towards the current item
// 4. Start the animation queue
this.animationQueue.stop( );
// Clear out any sets that are moving away from the current item
var animationSets = this.animationQueue.get( );
var i = animationSets.length;
while( i-- ){
var to = animationSets[ i ].getData( 'to' );
var goingToTheRight = animationSets[ i ].getData( 'goingToTheRight' );
var rightOfCurrent = to > current;
if( rightOfCurrent != goingToTheRight ){
this.animationQueue.remove( animationSets[ i ] );
}
}
animationSets = this.animationQueue.get( ); // update it since we may have changed the it by removing sets above
// How many steps from the old current item to the new current item
var stepsToCurrent = animationSets.length > 0? animationSets.pop( ).getData( 'to' ) : oldCurrent;
var goingToTheRight = stepsToCurrent < current; // direction of movement
stepsToCurrent += goingToTheRight? 1: -1; // advance to the next since we don't need to animate to our current position
// Special case for the first run
if( force ){
stepsToCurrent = current;
}
var items = this.items( );
// Add sets for each step
// The test works for moving in both directions
while( ( goingToTheRight && stepsToCurrent <= current ) || ( !goingToTheRight && stepsToCurrent >= current ) || ( force && stepsToCurrent == current ) ){
// Create a set
var animationSet = new animationqueue.AnimationSet( );
this.animationQueue.queue( animationSet );
animationSet.setData( 'goingToTheRight', goingToTheRight );
animationSet.setData( 'to', stepsToCurrent );
// Setup animation for all the items
var i = items.length;
while( i-- ){
var el = items.eq( i );
if( i < stepsToCurrent ){
var css = this.options.beforeCss( el, this.element, stepsToCurrent-i-1 );
} else if( i > stepsToCurrent ){
var css = this.options.afterCss( el, this.element, i-stepsToCurrent-1 );
} else { // i == stepsToCurrent
var css = this.options.currentCss( el, this.element, i-stepsToCurrent-1 );
}
// Push all the animation info onto the animation queue
var j = css.length;
while( j-- ){
var cssI = css[ j ];
animationSet.add( new animationqueue.Animation( cssI.element, cssI.animate ) );
for( var step in cssI.steps ){
animationSet.add( new animationqueue.AnimationStep( cssI.element, cssI.steps[ step ], parseFloat( step ) ) );
}
}
} // endwhile( i-- ) End the looping through all the items
stepsToCurrent += goingToTheRight? 1: -1;
} // endwhile( ) End looping through all the steps from current to i
// hide/show the title
var titleElement = items.eq( current ).data( 'jcoverflip__titleElement' );
if( titleElement ){
this.options.titleAnimateIn( titleElement, time, goingToTheRight );
}
if( current != oldCurrent ){ // prevent the case where current and oldCurrent are the same
var titleElement = items.eq( oldCurrent ).data( 'jcoverflip__titleElement' );
if( titleElement ){
this.options.titleAnimateOut( titleElement, time, goingToTheRight );
}
}
if( !force ){
// Trigger the start event
this._trigger( 'start', originalEvent, { to: current, from: oldCurrent } );
// run the animation and set a callback to trigger the stop event
this.animationQueue.start( time, proxy( this, function( timeElapsed ){
this._trigger( 'stop', originalEvent, { to: current, from: oldCurrent, time: timeElapsed} );
} ) );
this._trigger( 'change', originalEvent, { to: current, from: oldCurrent } );
} else {
this.animationQueue.start( time, nofn );
}
// Used to create the functions for creating AnimationSteps
function stepFactory( el, css ){
return function( ){
el.css( css );
};
};
},
/**
* Get the item elements
*
* Returns the items based on the selector string found in options.items, if not defined, then
* the children of the jcoverflip element will be the items.
*
* @param reload - boolean flag to clear the cache of elements that are the items
*
* @return jQuery object of items
*/
items: function( reload ){
if( this.itemsCache === undefined || !!reload ){
if( this.options.items ){
this.itemsCache = this.element.find( this.options.items );
} else {
this.itemsCache = this.element.children( );
}
}
return this.itemsCache;
},
length: function( ){
var items = this.items( );
return items.length;
}
} ) ;
$.ui.jcoverflip.defaults = {
items: '',
beforeCss: function( el, container, offset ){
return [
$.jcoverflip.animationElement( el, { left: ( container.width( )/2 - 210 - 110*offset )+'px', bottom: '20px' }, { } ),
$.jcoverflip.animationElement( el.find( 'img' ), { opacity: 0.5, width: '100px' }, {} )
];
},
afterCss: function( el, container, offset ){
return [
$.jcoverflip.animationElement( el, { left: ( container.width( )/2 + 110 + 110*offset )+'px', bottom: '20px' }, { } ),
$.jcoverflip.animationElement( el.find( 'img' ), { opacity: 0.5, width: '100px' }, {} )
];
},
currentCss: function( el, container ){
return [
$.jcoverflip.animationElement( el, { left: ( container.width( )/2 - 100 )+'px', bottom: 0 }, { } ),
$.jcoverflip.animationElement( el.find( 'img' ), { opacity: 1, width: '200px' }, { } )
];
},
time: 500, // half a second
titles: {
/**
*
* @param el - item element
*
* @return jQuery element object of the title
*
* Order for finding the title
* 1) An element with a class of "title"
* 2) The title attribute of the item
* 3) The alt attribute of the item
* 4) The first title or alt attribute of a child element of the item
*/
create: function( el ){
var titleText = '';
var title = $( [] );
var titleEl = el.find( '.title:first' );
if( titleEl.size( ) == 1 ){
title = titleEl.clone( true );
titleEl.css( 'display', 'none' );
title.data( 'jcoverflip__origin', 'cloned' );
title.data( 'jcoverflip__source', titleEl );
} else if( el.attr( 'title' ) ) {
titleText = el.attr( 'title' );
} else if( el.attr( 'alt' ) ) {
titleText = el.attr( 'alt' );
} else {
titleEl = el.find( '[title], [alt]' ).eq( 0 );
if( titleEl.size( ) == 1 ){
titleText = titleEl.attr( 'title' ) || titleEl.attr( 'alt' ) || '';
}
}
if( title.size( ) ){
title.css( { 'opacity': 0, 'display': 'block' } );
} else {
title = $( '' + titleText + '' );
title.data( 'jcoverflip__origin', 'attribute' );
}
return title;
},
/**
*
* @param el - title element
*/
destroy: function( el ){
if( el.data( 'jcoverflip__origin' ) == 'cloned' ){
el.data( 'jcoverflip__source' ).css( 'display', '' );
}
el.remove( );
}
},
titleAnimateIn: function( titleElement, time, offset ){
if( titleElement.css( 'display' ) == 'none' ){
titleElement.css({opacity: 0, display: 'block'});
}
titleElement.stop( ).animate({opacity: 1}, time );
},
titleAnimateOut: function( titleElement, time, offset ){
titleElement.stop( ).animate( {opacity: 0 }, time, function(){
$(this).css('display', 'none');
} );
},
controls: {
/**
* @param containerElement - the jQuery object for the jcoverflip
* @param length - the number of items
*/
create: nofn,
/**
* @param containerElement - the jQuery object for the jcoverflip
*/
destroy: nofn
},
current: 0
};
// specify the getters
$.ui.jcoverflip.getter = [ 'length', 'current' ];
} )( jQuery );