﻿
function Animator(options){
this.setOptions(options);
var _this=this;
this.timerDelegate=function(){_this.onTimerEvent()};
this.subjects=[];
this.target=0;
this.state=0;};
Animator.prototype={
setOptions:function(options){
this.options=Animator.applyDefaults({
interval:40,
duration:400,
onComplete:function(){},
onStep:function(){},
transition:Animator.tx.easeInOut},options);},
seekTo:function(to){
this.seekFromTo(this.state,to);},
seekFromTo:function(from,to){
this.target=Math.max(0,Math.min(1,to));
this.state=Math.max(0,Math.min(1,from));
if(!this.intervalId){
this.intervalId=window.setInterval(this.timerDelegate,this.options.interval);}},
jumpTo:function(to){
this.target=this.state=Math.max(0,Math.min(1,to));
this.onTimerEvent();},
toggle:function(){
this.seekTo(1-this.target);},
addSubject:function(subject){
this.subjects[this.subjects.length]=subject;
return this;},
removeSubject:function(subject){
this.subjects=this.subjects.reject(function(item){return item==subject;});},
clearSubjects:function(){
this.subjects=[];},
propagate:function(){
var value=this.options.transition(this.state);
for(var i=0;i<this.subjects.length;i++){
if(this.subjects[i].setState){
this.subjects[i].setState(value);}else{
this.subjects[i](value);}}},
onTimerEvent:function(){
var movement=(this.options.interval/this.options.duration)*(this.state<this.target?1:-1);
if(Math.abs(movement)>=Math.abs(this.state-this.target)){
this.state=this.target;}else{
this.state+=movement;}
try{
this.propagate();}finally{
this.options.onStep.call(this);
if(this.target==this.state){
window.clearInterval(this.intervalId);
this.intervalId=null;
this.options.onComplete.call(this);}}},
play:function(){this.seekFromTo(0,1)},
reverse:function(){this.seekFromTo(1,0)}}
Animator.applyDefaults=function(defaults,prefs){
prefs=prefs||{};
var prop,result={};
for(prop in defaults)result[prop]=prefs[prop]||defaults[prop];
return result;}
Animator.makeArray=function(o){
if(o==null)return[];
if(!o.length)return[o];
var result=[];
for(var i=0;i<o.length;i++)result[i]=o[i];
return result;}
Animator.camelize=function(string){
var oStringList=string.split('-');
if(oStringList.length==1)return oStringList[0];
var camelizedString=string.indexOf('-')==0?oStringList[0].charAt(0).toUpperCase()+oStringList[0].substring(1):oStringList[0];
for(var i=1,len=oStringList.length;i<len;i++){
var s=oStringList[i];
camelizedString+=s.charAt(0).toUpperCase()+s.substring(1);}
return camelizedString;}
Animator.apply=function(el,style,options){
if(style instanceof Array){
return new Animator(options).addSubject(new CSSStyleSubject(el,style[0],style[1]));}
return new Animator(options).addSubject(new CSSStyleSubject(el,style));}
Animator.makeEaseIn=function(a){
return function(state){
return Math.pow(state,a*2);}}
Animator.makeEaseOut=function(a){
return function(state){
return 1-Math.pow(1-state,a*2);}}
Animator.makeElastic=function(bounces){
return function(state){
state=Animator.tx.easeInOut(state);
return((1-Math.cos(state*Math.PI*bounces))*(1-state))+state;}}
Animator.makeBounce=function(bounces){
fn=Animator.makeElastic(bounces);
return function(state){
state=fn(state);
return state<=1?state:2-state;}}
Animator.tx={
easeInOut:function(pos){
return((-Math.cos(pos*Math.PI)/2)+0.5);},
linear:function(x){
return x;},
easeIn:Animator.makeEaseIn(1.5),
easeOut:Animator.makeEaseOut(1.5),
strongEaseIn:Animator.makeEaseIn(2.5),
strongEaseOut:Animator.makeEaseOut(2.5),
elastic:Animator.makeElastic(1),
veryElastic:Animator.makeElastic(3),
bouncy:Animator.makeBounce(1),
veryBouncy:Animator.makeBounce(3)}
function NumericalStyleSubject(els,property,from,to,units){
this.els=Animator.makeArray(els);
if(property=='opacity'&&window.ActiveXObject){
this.property='filter';}else{
this.property=Animator.camelize(property);}
this.from=parseFloat(from);
this.to=parseFloat(to);
this.units=units||'px';}
NumericalStyleSubject.prototype={
setState:function(state){
var style=this.getStyle(state);
var visibility=(this.property=='opacity'&&state==0)?'hidden':'';
var j=0;
for(var i=0;i<this.els.length;i++){
this.els[i].style[this.property]=style;
if(j++>20)return;}},
getStyle:function(state){
state=this.from+((this.to-this.from)*state);
if(this.property=='filter')return "alpha(opacity="+Math.round(state*100)+")";
if(this.property=='opacity')return state;
return Math.round(state)+this.units;}}
function ColorStyleSubject(els,property,from,to){
this.els=Animator.makeArray(els);
this.property=Animator.camelize(property);
this.to=this.expandColor(to);
this.from=this.expandColor(from);}
ColorStyleSubject.prototype={
expandColor:function(color){
var hexColor,red,green,blue;
hexColor=ColorStyleSubject.parseColor(color);
if(hexColor){
red=parseInt(hexColor.slice(1,3),16);
green=parseInt(hexColor.slice(3,5),16);
blue=parseInt(hexColor.slice(5,7),16);
return[red,green,blue]}
if(window.DEBUG){
alert("Invalid colour: '"+color+"'");}},
getValueForState:function(color,state){
return Math.round(this.from[color]+((this.to[color]-this.from[color])*state));},
setState:function(state){
var color='#'+ColorStyleSubject.toColorPart(this.getValueForState(0,state))+ColorStyleSubject.toColorPart(this.getValueForState(1,state))+ColorStyleSubject.toColorPart(this.getValueForState(2,state));
for(var i=0;i<this.els.length;i++){
this.els[i].style[this.property]=color;}}}
ColorStyleSubject.parseColor=function(string){
var color='#',match;
if(match=ColorStyleSubject.parseColor.rgbRe.exec(string)){
var part;
for(var i=1;i<=3;i++){
part=Math.max(0,Math.min(255,parseInt(match[i])));
color+=ColorStyleSubject.toColorPart(part);}
return color;}
if(match=ColorStyleSubject.parseColor.hexRe.exec(string)){
if(match[1].length==3){
for(var i=0;i<3;i++){
color+=match[1].charAt(i)+match[1].charAt(i);}
return color;}
return '#'+match[1];}
return false;}
ColorStyleSubject.toColorPart=function(number){
if(number>255)number=255;
var digits=number.toString(16);
if(number<16)return '0'+digits;
return digits;}
ColorStyleSubject.parseColor.rgbRe=/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
ColorStyleSubject.parseColor.hexRe=/^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
function CSSStyleSubject(els,style1,style2){
els=Animator.makeArray(els);
this.subjects=[];
if(els.length==0)return;
var prop,toStyle,fromStyle;
if(style2){
fromStyle=this.parseStyle(style1);
toStyle=this.parseStyle(style2);}else{
toStyle=this.parseStyle(style1);
fromStyle={};
for(prop in toStyle){
fromStyle[prop]=CSSStyleSubject.getStyle(els[0],prop);}}
var prop,from,to,units,match,type;
for(prop in fromStyle){
if(!toStyle[prop]){
if(window.DEBUG)alert("No to style provided for '"+prop+'"');
continue;}
if(from=ColorStyleSubject.parseColor(fromStyle[prop])){
to=ColorStyleSubject.parseColor(toStyle[prop]);
type=ColorStyleSubject;}else if(match=CSSStyleSubject.numericalRe.exec(fromStyle[prop])){
from=parseInt(fromStyle[prop]);
to=parseInt(toStyle[prop]);
type=NumericalStyleSubject;
units=match[1]||CSSStyleSubject.numericalRe.exec(toStyle[prop])[1];}else{
if(window.DEBUG){
alert("Unrecognised format for value of "+prop+": '"+fromStyle[prop]+"'");}
continue;}
this.subjects[this.subjects.length]=new type(els,prop,from,to,units);}}
CSSStyleSubject.prototype={
parseStyle:function(style){
var styles=style.split(";");
var rtn={};
for(var i=0;i<styles.length;i++){
var parts=CSSStyleSubject.ruleRe.exec(styles[i]);
if(parts){
rtn[parts[1]]=parts[2];}}
return rtn;},
setState:function(state){
for(var i=0;i<this.subjects.length;i++){
this.subjects[i].setState(state);}}}
CSSStyleSubject.getStyle=function(el,rule){
var style;
if(document.defaultView&&document.defaultView.getComputedStyle){
style=document.defaultView.getComputedStyle(el,"").getPropertyValue(rule);
if(style){
return style;}}
rule=Animator.camelize(rule);
if(el.currentStyle){
style=el.currentStyle[rule];}
return style||el.style[rule]}
CSSStyleSubject.ruleRe=/^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/;
CSSStyleSubject.numericalRe=/^\d+(%|[a-zA-Z]{2})?$/;
function Accordion(options){
this.setOptions(options);
var selected=this.options.initialSection,current;
if(this.options.rememberance){
current=document.location.hash.substring(1);}
this.rememberanceTexts=[];
this.ans=[];
var _this=this;
for(var i=0;i<this.options.sections.length;i++){
var el=this.options.sections[i];
var an=new Animator(this.options.animatorOptions);
var from=this.options.from+(this.options.shift*i);
var to=this.options.to+(this.options.shift*i);
an.addSubject(new NumericalStyleSubject(el,this.options.property,from,to,this.options.units));
an.jumpTo(0);
var activator=this.options.getActivator(el);
activator.index=i;
activator.onclick=function(){_this.show(this.index)};
this.ans[this.ans.length]=an;
this.rememberanceTexts[i]=activator.innerHTML.replace(/\s/g,"");
if(this.rememberanceTexts[i]===current){
selected=i;}}
this.show(selected);}
Accordion.prototype={
setOptions:function(options){
this.options=Object.extend({
sections:null,
getActivator:function(el){return $(el.getAttribute("activator"))},
shift:0,
initialSection:0,
rememberance:true,
animatorOptions:{}},options||{});},
show:function(section){
for(var i=0;i<this.ans.length;i++){
this.ans[i].seekTo(i>section?1:0);}
if(this.options.rememberance){
document.location.hash=this.rememberanceTexts[section];}}}

