/**
* jquery roundabout - v2.4.2
* http://fredhq.com/projects/roundabout
*
* moves list-items of enabled ordered and unordered lists long
* a chosen path. includes the default "lazysusan" path, that
* moves items long a spinning turntable.
*
* terms of use // jquery roundabout
*
* open source under the bsd license
*
* copyright (c) 2011-2012, fred leblanc
* all rights reserved.
*
* redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* - neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* this software is provided by the copyright holders and contributors "as is"
* and any express or implied warranties, including, but not limited to, the
* implied warranties of merchantability and fitness for a particular purpose
* are disclaimed. in no event shall the copyright holder or contributors be
* liable for any direct, indirect, incidental, special, exemplary, or
* consequential damages (including, but not limited to, procurement of
* substitute goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether in
* contract, strict liability, or tort (including negligence or otherwise)
* arising in any way out of the use of this software, even if advised of the
* possibility of such damage.
*/
(function($) {
"use strict";
var defaults, internaldata, methods;
// add default shape
$.extend({
roundaboutshapes: {
def: "lazysusan",
lazysusan: function (r, a, t) {
return {
x: math.sin(r + a),
y: (math.sin(r + 3 * math.pi / 2 + a) / 8) * t,
z: (math.cos(r + a) + 1) / 2,
scale: (math.sin(r + math.pi / 2 + a) / 2) + 0.5
};
}
}
});
defaults = {
bearing: 0.0,
tilt: 0.0,
minz: 100,
maxz: 280,
minopacity: 0.4,
maxopacity: 1.0,
minscale: 0.4,
maxscale: 1.0,
duration: 600,
btnnext: null,
btnnextcallback: function() {},
btnprev: null,
btnprevcallback: function() {},
btntoggleautoplay: null,
btnstartautoplay: null,
btnstopautoplay: null,
easing: "swing",
clicktofocus: true,
clicktofocuscallback: function() {},
focusbearing: 0.0,
shape: "lazysusan",
debug: false,
childselector: "li",
startingchild: null,
reflect: false,
floatcomparisonthreshold: 0.001,
autoplay: false,
autoplayduration: 1000,
autoplaypauseonhover: false,
autoplaycallback: function() {},
autoplayinitialdelay: 0,
enabledrag: false,
dropduration: 600,
dropeasing: "swing",
dropanimateto: "nearest",
dropcallback: function() {},
dragaxis: "x",
dragfactor: 4,
triggerfocusevents: true,
triggerblurevents: true,
responsive: false
};
internaldata = {
autoplayinterval: null,
autoplayisrunning: false,
autoplaystarttimeout: null,
animating: false,
childinfocus: -1,
touchmovestartposition: null,
stopanimation: false,
lastanimationstep: false
};
methods = {
// starters
// -----------------------------------------------------------------------
// init
// starts up roundabout
init: function(options, callback, relayout) {
var settings,
now = (new date()).gettime();
options = (typeof options === "object") ? options : {};
callback = ($.isfunction(callback)) ? callback : function() {};
callback = ($.isfunction(options)) ? options : callback;
settings = $.extend({}, defaults, options, internaldata);
return this
.each(function() {
// make options
var self = $(this),
childcount = self.children(settings.childselector).length,
period = 360.0 / childcount,
startingchild = (settings.startingchild && settings.startingchild > (childcount - 1)) ? (childcount - 1) : settings.startingchild,
startbearing = (settings.startingchild === null) ? settings.bearing : 360 - (startingchild * period),
holdercssposition = (self.css("position") !== "static") ? self.css("position") : "relative";
self
.css({ // starting styles
padding: 0,
position: holdercssposition
})
.addclass("roundabout-holder")
.data( // starting options
"roundabout",
$.extend(
{},
settings,
{
startingchild: startingchild,
bearing: startbearing,
oppositeoffocusbearing: methods.normalize.apply(null, [settings.focusbearing - 180]),
dragbearing: startbearing,
period: period
}
)
);
// unbind any events that we set if we're relaying out
if (relayout) {
self
.unbind(".roundabout")
.children(settings.childselector)
.unbind(".roundabout");
} else {
// bind responsive action
if (settings.responsive) {
$(window).bind("resize", function() {
methods.stopautoplay.apply(self);
methods.relayoutchildren.apply(self);
});
}
}
// bind click-to-focus
if (settings.clicktofocus) {
self
.children(settings.childselector)
.each(function(i) {
$(this)
.bind("click.roundabout", function() {
var degrees = methods.getplacement.apply(self, [i]);
if (!methods.isinfocus.apply(self, [degrees])) {
methods.stopanimation.apply($(this));
if (!self.data("roundabout").animating) {
methods.animatebearingtofocus.apply(self, [degrees, self.data("roundabout").clicktofocuscallback]);
}
return false;
}
});
});
}
// bind next buttons
if (settings.btnnext) {
$(settings.btnnext)
.bind("click.roundabout", function() {
if (!self.data("roundabout").animating) {
methods.animatetonextchild.apply(self, [self.data("roundabout").btnnextcallback]);
}
return false;
});
}
// bind previous buttons
if (settings.btnprev) {
$(settings.btnprev)
.bind("click.roundabout", function() {
methods.animatetopreviouschild.apply(self, [self.data("roundabout").btnprevcallback]);
return false;
});
}
// bind toggle autoplay buttons
if (settings.btntoggleautoplay) {
$(settings.btntoggleautoplay)
.bind("click.roundabout", function() {
methods.toggleautoplay.apply(self);
return false;
});
}
// bind start autoplay buttons
if (settings.btnstartautoplay) {
$(settings.btnstartautoplay)
.bind("click.roundabout", function() {
methods.startautoplay.apply(self);
return false;
});
}
// bind stop autoplay buttons
if (settings.btnstopautoplay) {
$(settings.btnstopautoplay)
.bind("click.roundabout", function() {
methods.stopautoplay.apply(self);
return false;
});
}
// autoplay pause on hover
if (settings.autoplaypauseonhover) {
self
.bind("mouseenter.roundabout.autoplay", function() {
methods.stopautoplay.apply(self, [true]);
})
.bind("mouseleave.roundabout.autoplay", function() {
methods.startautoplay.apply(self);
});
}
// drag and drop
if (settings.enabledrag) {
// on screen
if (!$.isfunction(self.drag)) {
if (settings.debug) {
alert("you do not have the drag plugin loaded.");
}
} else if (!$.isfunction(self.drop)) {
if (settings.debug) {
alert("you do not have the drop plugin loaded.");
}
} else {
self
.drag(function(e, properties) {
var data = self.data("roundabout"),
delta = (data.dragaxis.tolowercase() === "x") ? "deltax" : "deltay";
methods.stopanimation.apply(self);
methods.setbearing.apply(self, [data.dragbearing + properties[delta] / data.dragfactor]);
})
.drop(function(e) {
var data = self.data("roundabout"),
method = methods.getanimatetomethod(data.dropanimateto);
methods.allowanimation.apply(self);
methods[method].apply(self, [data.dropduration, data.dropeasing, data.dropcallback]);
data.dragbearing = data.period * methods.getnearestchild.apply(self);
});
}
// on mobile
self
.each(function() {
var element = $(this).get(0),
data = $(this).data("roundabout"),
page = (data.dragaxis.tolowercase() === "x") ? "pagex" : "pagey",
method = methods.getanimatetomethod(data.dropanimateto);
// some versions of ie don't like this
if (element.addeventlistener) {
element.addeventlistener("touchstart", function(e) {
data.touchmovestartposition = e.touches[0][page];
}, false);
element.addeventlistener("touchmove", function(e) {
var delta = (e.touches[0][page] - data.touchmovestartposition) / data.dragfactor;
e.preventdefault();
methods.stopanimation.apply($(this));
methods.setbearing.apply($(this), [data.dragbearing + delta]);
}, false);
element.addeventlistener("touchend", function(e) {
e.preventdefault();
methods.allowanimation.apply($(this));
method = methods.getanimatetomethod(data.dropanimateto);
methods[method].apply($(this), [data.dropduration, data.dropeasing, data.dropcallback]);
data.dragbearing = data.period * methods.getnearestchild.apply($(this));
}, false);
}
});
}
// start children
methods.initchildren.apply(self, [callback, relayout]);
});
},
// initchildren
// applys settings to child elements, starts roundabout
initchildren: function(callback, relayout) {
var self = $(this),
data = self.data("roundabout");
callback = callback || function() {};
self.children(data.childselector).each(function(i) {
var startwidth, startheight, startfontsize,
degrees = methods.getplacement.apply(self, [i]);
// on relayout, grab these values from current data
if (relayout && $(this).data("roundabout")) {
startwidth = $(this).data("roundabout").startwidth;
startheight = $(this).data("roundabout").startheight;
startfontsize = $(this).data("roundabout").startfontsize;
}
// apply classes and css first
$(this)
.addclass("roundabout-moveable-item")
.css("position", "absolute");
// now measure
$(this)
.data(
"roundabout",
{
startwidth: startwidth || $(this).width(),
startheight: startheight || $(this).height(),
startfontsize: startfontsize || parseint($(this).css("font-size"), 10),
degrees: degrees,
backdegrees: methods.normalize.apply(null, [degrees - 180]),
childnumber: i,
currentscale: 1,
parent: self
}
);
});
methods.updatechildren.apply(self);
// start autoplay if necessary
if (data.autoplay) {
data.autoplaystarttimeout = settimeout(function() {
methods.startautoplay.apply(self);
}, data.autoplayinitialdelay);
}
self.trigger('ready');
callback.apply(self);
return self;
},
// positioning
// -----------------------------------------------------------------------
// updatechildren
// move children elements into their proper locations
updatechildren: function() {
return this
.each(function() {
var self = $(this),
data = self.data("roundabout"),
infocus = -1,
info = {
bearing: data.bearing,
tilt: data.tilt,
stage: {
width: math.floor($(this).width() * 0.9),
height: math.floor($(this).height() * 0.9)
},
animating: data.animating,
infocus: data.childinfocus,
focusbearingradian: methods.degtorad.apply(null, [data.focusbearing]),
shape: $.roundaboutshapes[data.shape] || $.roundaboutshapes[$.roundaboutshapes.def]
};
// calculations
info.midstage = {
width: info.stage.width / 2,
height: info.stage.height / 2
};
info.nudge = {
width: info.midstage.width + (info.stage.width * 0.05),
height: info.midstage.height + (info.stage.height * 0.05)
};
info.zvalues = {
min: data.minz,
max: data.maxz,
diff: data.maxz - data.minz
};
info.opacity = {
min: data.minopacity,
max: data.maxopacity,
diff: data.maxopacity - data.minopacity
};
info.scale = {
min: data.minscale,
max: data.maxscale,
diff: data.maxscale - data.minscale
};
// update child positions
self.children(data.childselector)
.each(function(i) {
if (methods.updatechild.apply(self, [$(this), info, i, function() { $(this).trigger('ready'); }]) && (!info.animating || data.lastanimationstep)) {
infocus = i;
$(this).addclass("roundabout-in-focus");
} else {
$(this).removeclass("roundabout-in-focus");
}
});
if (infocus !== info.infocus) {
// blur old child
if (data.triggerblurevents) {
self.children(data.childselector)
.eq(info.infocus)
.trigger("blur");
}
data.childinfocus = infocus;
if (data.triggerfocusevents && infocus !== -1) {
// focus new child
self.children(data.childselector)
.eq(infocus)
.trigger("focus");
}
}
self.trigger("childrenupdated");
});
},
// updatechild
// repositions a child element into its new position
updatechild: function(childelement, info, childpos, callback) {
var factors,
self = this,
child = $(childelement),
data = child.data("roundabout"),
out = [],
rad = methods.degtorad.apply(null, [(360.0 - data.degrees) + info.bearing]);
callback = callback || function() {};
// adjust radians to be between 0 and math.pi * 2
rad = methods.normalizerad.apply(null, [rad]);
// get factors from shape
factors = info.shape(rad, info.focusbearingradian, info.tilt);
// correct
factors.scale = (factors.scale > 1) ? 1 : factors.scale;
factors.adjustedscale = (info.scale.min + (info.scale.diff * factors.scale)).tofixed(4);
factors.width = (factors.adjustedscale * data.startwidth).tofixed(4);
factors.height = (factors.adjustedscale * data.startheight).tofixed(4);
// update item
child
.css({
left: ((factors.x * info.midstage.width + info.nudge.width) - factors.width / 2.0).tofixed(0) + "px",
top: ((factors.y * info.midstage.height + info.nudge.height) - factors.height / 2.0).tofixed(0) + "px",
width: factors.width + "px",
height: factors.height + "px",
opacity: (info.opacity.min + (info.opacity.diff * factors.scale)).tofixed(2),
zindex: math.round(info.zvalues.min + (info.zvalues.diff * factors.z)),
fontsize: (factors.adjustedscale * data.startfontsize).tofixed(1) + "px"
});
data.currentscale = factors.adjustedscale;
// for debugging purposes
if (self.data("roundabout").debug) {
out.push("
");
out.push("child " + childpos + "
");
out.push("left: " + child.css("left") + "
");
out.push("top: " + child.css("top") + "
");
out.push("width: " + child.css("width") + "
");
out.push("opacity: " + child.css("opacity") + "
");
out.push("height: " + child.css("height") + "
");
out.push("z-index: " + child.css("z-index") + "
");
out.push("font-size: " + child.css("font-size") + "
");
out.push("scale: " + child.data("roundabout").currentscale);
out.push("
");
child.html(out.join(""));
}
// trigger event
child.trigger("reposition");
// callback
callback.apply(self);
return methods.isinfocus.apply(self, [data.degrees]);
},
// manipulation
// -----------------------------------------------------------------------
// setbearing
// changes the bearing of the roundabout
setbearing: function(bearing, callback) {
callback = callback || function() {};
bearing = methods.normalize.apply(null, [bearing]);
this
.each(function() {
var diff, lowervalue, highervalue,
self = $(this),
data = self.data("roundabout"),
oldbearing = data.bearing;
// set bearing
data.bearing = bearing;
self.trigger("bearingset");
methods.updatechildren.apply(self);
// not animating? we're done here
diff = math.abs(oldbearing - bearing);
if (!data.animating || diff > 180) {
return;
}
// check to see if any of the children went through the back
diff = math.abs(oldbearing - bearing);
self.children(data.childselector).each(function(i) {
var eventtype;
if (methods.ischildbackdegreesbetween.apply($(this), [bearing, oldbearing])) {
eventtype = (oldbearing > bearing) ? "clockwise" : "counterclockwise";
$(this).trigger("move" + eventtype + "throughback");
}
});
});
// call callback if one was given
callback.apply(this);
return this;
},
// adjustbearing
// change the bearing of the roundabout by a given degree
adjustbearing: function(delta, callback) {
callback = callback || function() {};
if (delta === 0) {
return this;
}
this
.each(function() {
methods.setbearing.apply($(this), [$(this).data("roundabout").bearing + delta]);
});
callback.apply(this);
return this;
},
// settilt
// changes the tilt of the roundabout
settilt: function(tilt, callback) {
callback = callback || function() {};
this
.each(function() {
$(this).data("roundabout").tilt = tilt;
methods.updatechildren.apply($(this));
});
// call callback if one was given
callback.apply(this);
return this;
},
// adjusttilt
// changes the tilt of the roundabout
adjusttilt: function(delta, callback) {
callback = callback || function() {};
this
.each(function() {
methods.settilt.apply($(this), [$(this).data("roundabout").tilt + delta]);
});
callback.apply(this);
return this;
},
// animation
// -----------------------------------------------------------------------
// animatetobearing
// animates the roundabout to a given bearing, all animations come through here
animatetobearing: function(bearing, duration, easing, passeddata, callback) {
var now = (new date()).gettime();
callback = callback || function() {};
// find callback function in arguments
if ($.isfunction(passeddata)) {
callback = passeddata;
passeddata = null;
} else if ($.isfunction(easing)) {
callback = easing;
easing = null;
} else if ($.isfunction(duration)) {
callback = duration;
duration = null;
}
this
.each(function() {
var timer, easingfn, newbearing,
self = $(this),
data = self.data("roundabout"),
thisduration = (!duration) ? data.duration : duration,
thiseasingtype = (easing) ? easing : data.easing || "swing";
// is this your first time?
if (!passeddata) {
passeddata = {
timerstart: now,
start: data.bearing,
totaltime: thisduration
};
}
// update the timer
timer = now - passeddata.timerstart;
if (data.stopanimation) {
methods.allowanimation.apply(self);
data.animating = false;
return;
}
// we need to animate more
if (timer < thisduration) {
if (!data.animating) {
self.trigger("animationstart");
}
data.animating = true;
if (typeof $.easing.def === "string") {
easingfn = $.easing[thiseasingtype] || $.easing[$.easing.def];
newbearing = easingfn(null, timer, passeddata.start, bearing - passeddata.start, passeddata.totaltime);
} else {
newbearing = $.easing[thiseasingtype]((timer / passeddata.totaltime), timer, passeddata.start, bearing - passeddata.start, passeddata.totaltime);
}
// fixes issue #24, animation changed as of jquery 1.7.2
// also addresses issue #29, using easing breaks "linear"
if (methods.compareversions.apply(null, [$().jquery, "1.7.2"]) >= 0 && !($.easing["easeoutback"])) {
newbearing = passeddata.start + ((bearing - passeddata.start) * newbearing);
}
newbearing = methods.normalize.apply(null, [newbearing]);
data.dragbearing = newbearing;
methods.setbearing.apply(self, [newbearing, function() {
settimeout(function() { // done with a timeout so that each step is displayed
methods.animatetobearing.apply(self, [bearing, thisduration, thiseasingtype, passeddata, callback]);
}, 0);
}]);
// we're done animating
} else {
data.lastanimationstep = true;
bearing = methods.normalize.apply(null, [bearing]);
methods.setbearing.apply(self, [bearing, function() {
self.trigger("animationend");
}]);
data.animating = false;
data.lastanimationstep = false;
data.dragbearing = bearing;
callback.apply(self);
}
});
return this;
},
// animatetonearbychild
// animates roundabout to a nearby child
animatetonearbychild: function(passedargs, which) {
var duration = passedargs[0],
easing = passedargs[1],
callback = passedargs[2] || function() {};
// find callback
if ($.isfunction(easing)) {
callback = easing;
easing = null;
} else if ($.isfunction(duration)) {
callback = duration;
duration = null;
}
return this
.each(function() {
var j, range,
self = $(this),
data = self.data("roundabout"),
bearing = (!data.reflect) ? data.bearing % 360 : data.bearing,
length = self.children(data.childselector).length;
if (!data.animating) {
// reflecting, not moving to previous || not reflecting, moving to next
if ((data.reflect && which === "previous") || (!data.reflect && which === "next")) {
// slightly adjust for rounding issues
bearing = (math.abs(bearing) < data.floatcomparisonthreshold) ? 360 : bearing;
// clockwise
for (j = 0; j < length; j += 1) {
range = {
lower: (data.period * j),
upper: (data.period * (j + 1))
};
range.upper = (j === length - 1) ? 360 : range.upper;
if (bearing <= math.ceil(range.upper) && bearing >= math.floor(range.lower)) {
if (length === 2 && bearing === 360) {
methods.animatetodelta.apply(self, [-180, duration, easing, callback]);
} else {
methods.animatebearingtofocus.apply(self, [range.lower, duration, easing, callback]);
}
break;
}
}
} else {
// slightly adjust for rounding issues
bearing = (math.abs(bearing) < data.floatcomparisonthreshold || 360 - math.abs(bearing) < data.floatcomparisonthreshold) ? 0 : bearing;
// counterclockwise
for (j = length - 1; j >= 0; j -= 1) {
range = {
lower: data.period * j,
upper: data.period * (j + 1)
};
range.upper = (j === length - 1) ? 360 : range.upper;
if (bearing >= math.floor(range.lower) && bearing < math.ceil(range.upper)) {
if (length === 2 && bearing === 360) {
methods.animatetodelta.apply(self, [180, duration, easing, callback]);
} else {
methods.animatebearingtofocus.apply(self, [range.upper, duration, easing, callback]);
}
break;
}
}
}
}
});
},
// animatetonearestchild
// animates roundabout to the nearest child
animatetonearestchild: function(duration, easing, callback) {
callback = callback || function() {};
// find callback
if ($.isfunction(easing)) {
callback = easing;
easing = null;
} else if ($.isfunction(duration)) {
callback = duration;
duration = null;
}
return this
.each(function() {
var nearest = methods.getnearestchild.apply($(this));
methods.animatetochild.apply($(this), [nearest, duration, easing, callback]);
});
},
// animatetochild
// animates roundabout to a given child position
animatetochild: function(childposition, duration, easing, callback) {
callback = callback || function() {};
// find callback
if ($.isfunction(easing)) {
callback = easing;
easing = null;
} else if ($.isfunction(duration)) {
callback = duration;
duration = null;
}
return this
.each(function() {
var child,
self = $(this),
data = self.data("roundabout");
if (data.childinfocus !== childposition && !data.animating) {
child = self.children(data.childselector).eq(childposition);
methods.animatebearingtofocus.apply(self, [child.data("roundabout").degrees, duration, easing, callback]);
}
});
},
// animatetonextchild
// animates roundabout to the next child
animatetonextchild: function(duration, easing, callback) {
return methods.animatetonearbychild.apply(this, [arguments, "next"]);
},
// animatetopreviouschild
// animates roundabout to the preious child
animatetopreviouschild: function(duration, easing, callback) {
return methods.animatetonearbychild.apply(this, [arguments, "previous"]);
},
// animatetodelta
// animates roundabout to a given delta (in degrees)
animatetodelta: function(degrees, duration, easing, callback) {
callback = callback || function() {};
// find callback
if ($.isfunction(easing)) {
callback = easing;
easing = null;
} else if ($.isfunction(duration)) {
callback = duration;
duration = null;
}
return this
.each(function() {
var delta = $(this).data("roundabout").bearing + degrees;
methods.animatetobearing.apply($(this), [delta, duration, easing, callback]);
});
},
// animatebearingtofocus
// animates roundabout to bring a given angle into focus
animatebearingtofocus: function(degrees, duration, easing, callback) {
callback = callback || function() {};
// find callback
if ($.isfunction(easing)) {
callback = easing;
easing = null;
} else if ($.isfunction(duration)) {
callback = duration;
duration = null;
}
return this
.each(function() {
var delta = $(this).data("roundabout").bearing - degrees;
delta = (math.abs(360 - delta) < math.abs(delta)) ? 360 - delta : -delta;
delta = (delta > 180) ? -(360 - delta) : delta;
if (delta !== 0) {
methods.animatetodelta.apply($(this), [delta, duration, easing, callback]);
}
});
},
// stopanimation
// if an animation is currently in progress, stop it
stopanimation: function() {
return this
.each(function() {
$(this).data("roundabout").stopanimation = true;
});
},
// allowanimation
// clears the stop-animation hold placed by stopanimation
allowanimation: function() {
return this
.each(function() {
$(this).data("roundabout").stopanimation = false;
});
},
// autoplay
// -----------------------------------------------------------------------
// startautoplay
// starts autoplaying this roundabout
startautoplay: function(callback) {
return this
.each(function() {
var self = $(this),
data = self.data("roundabout");
callback = callback || data.autoplaycallback || function() {};
clearinterval(data.autoplayinterval);
data.autoplayinterval = setinterval(function() {
methods.animatetonextchild.apply(self, [callback]);
}, data.autoplayduration);
data.autoplayisrunning = true;
self.trigger("autoplaystart");
});
},
// stopautoplay
// stops autoplaying this roundabout
stopautoplay: function(keepautoplaybindings) {
return this
.each(function() {
clearinterval($(this).data("roundabout").autoplayinterval);
$(this).data("roundabout").autoplayinterval = null;
$(this).data("roundabout").autoplayisrunning = false;
// this will prevent autoplaypauseonhover from restarting autoplay
if (!keepautoplaybindings) {
$(this).unbind(".autoplay");
}
$(this).trigger("autoplaystop");
});
},
// toggleautoplay
// toggles autoplay pause/resume
toggleautoplay: function(callback) {
return this
.each(function() {
var self = $(this),
data = self.data("roundabout");
callback = callback || data.autoplaycallback || function() {};
if (!methods.isautoplaying.apply($(this))) {
methods.startautoplay.apply($(this), [callback]);
} else {
methods.stopautoplay.apply($(this), [callback]);
}
});
},
// isautoplaying
// is this roundabout currently autoplaying?
isautoplaying: function() {
return (this.data("roundabout").autoplayisrunning);
},
// changeautoplayduration
// stops the autoplay, changes the duration, restarts autoplay
changeautoplayduration: function(duration) {
return this
.each(function() {
var self = $(this),
data = self.data("roundabout");
data.autoplayduration = duration;
if (methods.isautoplaying.apply(self)) {
methods.stopautoplay.apply(self);
settimeout(function() {
methods.startautoplay.apply(self);
}, 10);
}
});
},
// helpers
// -----------------------------------------------------------------------
// normalize
// regulates degrees to be >= 0.0 and < 360
normalize: function(degrees) {
var inrange = degrees % 360.0;
return (inrange < 0) ? 360 + inrange : inrange;
},
// normalizerad
// regulates radians to be >= 0 and < math.pi * 2
normalizerad: function(radians) {
while (radians < 0) {
radians += (math.pi * 2);
}
while (radians > (math.pi * 2)) {
radians -= (math.pi * 2);
}
return radians;
},
// ischildbackdegreesbetween
// checks that a given child's backdegrees is between two values
ischildbackdegreesbetween: function(value1, value2) {
var backdegrees = $(this).data("roundabout").backdegrees;
if (value1 > value2) {
return (backdegrees >= value2 && backdegrees < value1);
} else {
return (backdegrees < value2 && backdegrees >= value1);
}
},
// getanimatetomethod
// takes a user-entered option and maps it to an animation method
getanimatetomethod: function(effect) {
effect = effect.tolowercase();
if (effect === "next") {
return "animatetonextchild";
} else if (effect === "previous") {
return "animatetopreviouschild";
}
// default selection
return "animatetonearestchild";
},
// relayoutchildren
// lays out children again with new contextual information
relayoutchildren: function() {
return this
.each(function() {
var self = $(this),
settings = $.extend({}, self.data("roundabout"));
settings.startingchild = self.data("roundabout").childinfocus;
methods.init.apply(self, [settings, null, true]);
});
},
// getnearestchild
// gets the nearest child from the current bearing
getnearestchild: function() {
var self = $(this),
data = self.data("roundabout"),
length = self.children(data.childselector).length;
if (!data.reflect) {
return ((length) - (math.round(data.bearing / data.period) % length)) % length;
} else {
return (math.round(data.bearing / data.period) % length);
}
},
// degtorad
// converts degrees to radians
degtorad: function(degrees) {
return methods.normalize.apply(null, [degrees]) * math.pi / 180.0;
},
// getplacement
// returns the starting degree for a given child
getplacement: function(child) {
var data = this.data("roundabout");
return (!data.reflect) ? 360.0 - (data.period * child) : data.period * child;
},
// isinfocus
// is this roundabout currently in focus?
isinfocus: function(degrees) {
var diff,
self = this,
data = self.data("roundabout"),
bearing = methods.normalize.apply(null, [data.bearing]);
degrees = methods.normalize.apply(null, [degrees]);
diff = math.abs(bearing - degrees);
// this calculation gives a bit of room for javascript float rounding
// errors, it looks on both 0deg and 360deg ends of the spectrum
return (diff <= data.floatcomparisonthreshold || diff >= 360 - data.floatcomparisonthreshold);
},
// getchildinfocus
// returns the current child in focus, or false if none are in focus
getchildinfocus: function() {
var data = $(this).data("roundabout");
return (data.childinfocus > -1) ? data.childinfocus : false;
},
// compareversions
// compares a given version string with another
compareversions: function(baseversion, compareversion) {
var i,
base = baseversion.split(/\./i),
compare = compareversion.split(/\./i),
maxversionsegmentlength = (base.length > compare.length) ? base.length : compare.length;
for (i = 0; i <= maxversionsegmentlength; i++) {
if (base[i] && !compare[i] && parseint(base[i], 10) !== 0) {
// base is higher
return 1;
} else if (compare[i] && !base[i] && parseint(compare[i], 10) !== 0) {
// compare is higher
return -1;
} else if (base[i] === compare[i]) {
// these are the same, next
continue;
}
if (base[i] && compare[i]) {
if (parseint(base[i], 10) > parseint(compare[i], 10)) {
// base is higher
return 1;
} else {
// compare is higher
return -1;
}
}
}
// nothing was triggered, versions are the same
return 0;
}
};
// start the plugin
$.fn.roundabout = function(method) {
if (methods[method]) {
return methods[method].apply(this, array.prototype.slice.call(arguments, 1));
} else if (typeof method === "object" || $.isfunction(method) || !method) {
return methods.init.apply(this, arguments);
} else {
$.error("method " + method + " does not exist for jquery.roundabout.");
}
};
})(jquery);