﻿//////////////////////////////////////////////////////////////////////////////
//
// ksRotator, version 1.0 by Kerri Shotts
//
// (C) 2009 Kerri Shotts. Released under Creative Commons Attribution 
// Share Alike v3.0
//
///////////////////////////////////////////////////////////////////////////////

//
// CLASS Rotator
// ----------------------------------------------------------------------------
//
// The Rotator class provides the functions necessary for the creation and
// animation of items.
//
// Constructor Rotator( objectID, parameters )
//
//     objectID = the ID of the element that you wish to become a 
//     rotator.
//
//    parameters = an array containing various parameters that control
//    the rotator.
///////////////////////////////////////////////////////////////////////////////

function Rotator ( objectID, parms )
{
    this.element = document.getElementById( objectID );            // assign the element specified by ObjectID
    this.element.rot = this;                                       // and attach ourselves to the element
    
    this.id = objectID;

    if ( parms[0].width )         {this.width = parms[0].width;}                   else    {this.width = '650';}
    if ( parms[0].height)         {this.height = parms[0].height;}                 else    {this.height= '350';}
    if ( parms[0].controlBar )    {this.controlBar = parms[0].controlBar;}         else    {this.controlBar = 'yes';}
    if ( parms[0].duration )      {this.duration = parms[0].duration;}             else    {this.duration = '20000';}
    if ( parms[0].autoAdvance )   {this.autoAdvance = parms[0].autoAdvance;}       else    {this.autoAdvance = 'yes';}
    if ( parms[0].useCaptions )   {this.useCaptions = parms[0].useCaptions;}       else    {this.useCaptions = 'no';}
    if ( parms[0].shuffle)        {this.shuffle = parms[0].shuffle;}               else    {this.shuffle = 'no';}
    if ( parms[0].animationSpeed) {this.animationSpeed = parms[0].animationSpeed;} else    {this.animationSpeed = 25;}
    if ( parms[0].tryAgainDelay)  {this.tryAgainDelay = parms[0].tryAgainDelay;}   else    {this.tryAgainDelay = 1000;}
    if ( parms[0].navigation)     {this.navigation = parms[0].navigation;}         else    {this.navigation = 'yes';}
    if ( parms[0].useTitles )     {this.useTitles = parms[0].useTitles;}           else    {this.useTitles = 'yes';}
    
    this.items = [];
    
    return this;
};

//
// addItem ( item )
// 
// Adds a RotatorItem to the list of items in the Rotator.
///////////////////////////////////////////////////////////////////////////////

Rotator.prototype.addItem = function ( i )
{
    i.parentRot = this;
    this.items[ this.items.length ] = i;
};

//
// build()
//
// Called internally in order to build the elements and insert them
// into the DOM as children of the parent element specified when the
// object was created. 
///////////////////////////////////////////////////////////////////////////////

Rotator.prototype.build = function ()
{
    // a rotator consists of a parent DIV (supplied to us by the user) that contains
    // several internal DIVs. The majority of the DIVs are the images themselves, however, there 
    // are several other elements we need to build, such as the next/back controls, and direct
    // access controls.

    var i;
    var ae;
    
    // set our width and height
    this.element.style.width = this.width + "px";
    this.element.style.height = this.height + "px";
    
    // make us relative so that everything else absolutely positioned is relative to us.
    this.element.style.position = "relative";
    
    // clip everything outside... don't want things that shouldn't be seen to be seen.
    this.element.style.overflow = "hidden";
    
    // build all the items and append them to us.
    for (i = 0; i < this.items.length; i++)
    {
        this.element.appendChild ( this.items[i].build() );
    }
    
    // build the control area
    // the control ares consists of three DIVs: one at the bottom containing dots for each image,
    // and then a div on the left and right that allows back/forward control.
    
    var cb = document.createElement ("DIV");        // control bar or direct access
    var pe = document.createElement ("DIV");        // previous control
    var ne = document.createElement ("DIV");        // next control
    
    cb.className = "rotControlBar";                 // class of the direct access bar

    for (i=0;i<this.items.length;i++)               // for each element we create an A inside cb
    {                                               // that lets the user click it to go directly
        ae= document.createElement ("A");           // to the desired image
        this.items[i].anchor = ae;                  // let the item know about the new element
        ae.rot = this;                              // and let the element know about us
        ae.n = i;                                   // this is the # of the image
        if (this.useCaptions == "yes")
        {
            ae.innerHTML = this.items[i].imageCaption;  // use the caption if desired
        }
        else
        {
            ae.innerHTML = (i + 1 );                    // or just number each one (+1 to start at 1)
        }
        
        // and add the click event listener
        if (ae.addEventListener)
        {
            ae.addEventListener ("click", function() { this.rot.transitionTo (this.n); }, false );
        }
        else
        {
            ae.onclick = function() {this.rot.transitionTo (this.n); };
        }
        
        // and give it the appropriate class
        ae.className = "rotItemSelector";
        
        // append to cb
        cb.appendChild ( ae );
    }
    
    // cb should default to hidden
    cb.style.display = "none";

    // previous element
    pe.className = "rotPreviousItem";   // these are all classed (save display) to allow
    pe.style.display = "none";          // the end user to override how these elements
                                        // appear (i.e., instead of arrows use something else, etc.)
    // next element
    ne.className = "rotNextItem";
    ne.style.display = "none";

    // add the elements, let our parent element know about them, let them know about us, and set
    // the alt/titles for tooltips

    this.element.appendChild ( cb );    this.element.cb = cb;
    this.element.appendChild ( pe );    this.element.pe = pe;    pe.rot = this;    pe.setAttribute("alt","See Previous Item"); pe.setAttribute("title","See Previous Item");
    this.element.appendChild ( ne );    this.element.ne = ne;    ne.rot = this;    ne.setAttribute("alt","See Next Item");     ne.setAttribute("title","See Next Item");

    //Let the element know if the developer wanted to have a control/navigation area    
    this.element.controlBar = this.controlBar;
    this.element.navigation = this.navigation;

    // add necessary event handlers to the next/back arrows, as well as to the entire rotator to
    // hide/show the controls
    if (pe.addEventListener)
    {
        pe.addEventListener ("click", function() { this.rot.transitionTo (-2); }, false );  // -2 here means PREVIOUS
        ne.addEventListener ("click", function() { this.rot.transitionTo (-1); }, false );  // -1 here means NEXT
    }
    else
    {
        pe.onclick = function() {this.rot.transitionTo (-2); }; // -2: see above
        ne.onclick = function() {this.rot.transitionTo (-1); }; // -1: see above
    }

    if (this.element.addEventListener)
    {                
        this.element.addEventListener ("mouseover", function () { this.cb.style.display = (this.controlBar!="no") ? "block" : "none"; this.pe.style.display = (this.navigation!="no") ? "block" : "none"; this.ne.style.display = (this.navigation!="no") ? "block" : "none"; }, false);
        this.element.addEventListener ("mouseout",  function () { this.cb.style.display = "none"; this.pe.style.display = "none"; this.ne.style.display="none"; }, false) ;
    }
    else
    {    
        this.element.onmouseover=function() { this.cb.style.display = (this.controlBar!="no") ? "block" : "none"; this.pe.style.display = (this.navigation!="no") ? "block" : "none"; this.ne.style.display = (this.navigation!="no") ? "block" : "none";};
        this.element.onmouseout=function()  { this.cb.style.display = "none"; this.pe.style.display = "none"; this.ne.style.display="none";  };
    }

};

//
// transitionTo ( item )
//
// Instructs the Rotator to begin a transition from the current item to the
// specified item. If item is = -1, the Rotator will transition to the next
// item in the list, wrapping to the beginning if necessary. If item is = -2
// the Rotator will transition to the previous item in the list, wrapping to
// the end if necessary.
//
// If item is out-of-bounds (< -2, > # of items), the results are undefined. 
///////////////////////////////////////////////////////////////////////////////

Rotator.prototype.transitionTo = function ( e )
{
    var i;
    if (this.transitioning > 0 && e != this.curItem )
    {
        // we're currently busy, sorry. Try again in a few millseconds (tryAgainDelay)
        window.setTimeout ( "document.getElementById('" + this.id + "').rot.transitionTo (" + e + " );", this.tryAgainDelay );
    }
    else
    {
        if (e < 0)                                  // implies next (-1) or previous (-2) image
        {
            if (e == -1)
            {
                e = this.curItem + 1;
                if (e >= this.items.length)         // wrap around
                {
                    e = 0;
                }
            }
            else
            {
                e = this.curItem -1;
                if (e < 0 )                         // wrap around
                {
                    e = this.items.length - 1;
                }
            }
        }
        
        if (e != this.curItem)                      // um, can't transition to the same element, so make sure we're different
        {
            this.transitioning = 1;                 // make us busy
            this.prevItem = this.curItem;           // the previous item is our current item
            this.curItem = e;                       // and our current item is now e
            this.step = 20;                         // all transitions complete in 20 steps (frames)

            // mark the curItem as selected
            for (i=0; i<this.items.length; i++)
            {
                if (i==this.curItem)                // give us a selected class
                {
                    this.items[i].anchor.className = "rotItemSelector rotItemSelected";
                }
                else                                // otherwise deselect us
                {
                    this.items[i].anchor.className = "rotItemSelector";
                }
            }
            
            // start the transition
            window.setTimeout ("document.getElementById('" + this.id + "').rot.continueTransition ()", this.animationSpeed );
        }
    }
};

//
// continueTransition
//
// Instructs the Rotator to continue a transition already underway.
///////////////////////////////////////////////////////////////////////////////

Rotator.prototype.continueTransition = function ()
{
    // continuing transition from prevItem to curItem
    this.step--;                                    // drop our step by 1; think of step as a frame counter
    
    // Tell Previous Item to exit the stage
    if (this.prevItem > -1 )                        // When we are first called there is no prevItem, so
    {                                               // be sure to check and make sure we have one.
        this.items[this.prevItem].stageExit ( this.items[this.curItem].transition, this.step );
    }
    
    // Tell the Current Item to enter the stage
    this.items[this.curItem].stageEnter (this.items[this.curItem].transition, this.step);
    
    // When step is 0, we're essentially done with the transition. Deal with cleaning everything up
    // such as setting up the next transition (if autoAdvancing), and also flag us as no longer
    // transitioning.
    if (this.step == 0)
    {
        this.transitioning = 0;
        if (this.rotTimeoutID)
        {
            window.clearTimeout ( this.rotTimeoutID);
            this.rotTimeoutID = null;
        }
        if (this.autoAdvance != 'no')
        {
            this.rotTimeoutID = window.setTimeout ("document.getElementById('" + this.id + "').rot.transitionTo (-1)", this.duration);
        }
    }
    else
    {
        // step isn't zero, set a timeout to call us again in animationSpeed milliseconds
        window.setTimeout ("document.getElementById('" + this.id + "').rot.continueTransition ()", this.animationSpeed );
    }
};                       
        
//
// start()
//
// Instructs the Rotator to begin rotating through the various items.
// It will begin a transition to the first item as well.
///////////////////////////////////////////////////////////////////////////////

Rotator.prototype.start = function ()
{
    // build the object model and insert it into the DOM
    this.build();
    
    // not currently busy, nor do we have current or previous items
    this.transitioning = 0;
    this.curItem = -1;
    this.prevItem = -1;
    
    // transition to the FIRST item. (0 based)
    this.transitionTo ( 0 );
};

//
// CLASS RotatorItem
// ----------------------------------------------------------------------------
//
// The RotatorItem class is used to construct an object containing the
// necessary information to build an image with appropriate links and
// captions, as well as to specify various transition options, etc.
//
// All functions within the RotatorItem are designed to be chained for
// easier access. Each function will return a copy of the object so that
// the chaining is possible.
//
// constructor RotatorItem ( img )
//
// Creates a RotatorItem with the img src set to img.
///////////////////////////////////////////////////////////////////////////////

function RotatorItem ( img )
{
    this.imageSource = img;     // image URL
    this.imageCaption = "";     // no image caption
    this.imageTitle = "";       // or title (by default)
    this.linkHREF = "#";        // # is the default link
    this.linkTarget = "";       // no target by default
    this.transition = "fade";   // fade transition by default
    return this;
};

//
// setCaption ( s )
//
// Sets the caption of the image to s
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.setCaption = function ( s )
{
    this.imageCaption = s;
    return this;
};

//
// setTitle ( s )
//
// Sets the title of the image (via alt, title) to s
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.setTitle = function ( s )
{
    this.imageTitle = s;
    return this;
};

//
// setHREF ( s )
//
// sets the link location for the image. 
//
// if the item begins with http:// or https://, the target will be set to
// _blank.
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.setHREF = function ( s )
{
    this.linkHREF = s;
    if ( (s.toLowerCase().substr(0,7) == "http://") || (s.toLowerCase().substr(0,8) == "https://"))
    {
        this.linkTarget = "_blank";
    }
    else
    {
        this.linkTarget = "";
    }
    return this;
};

//
// setTransition (s)
//
// Indicates the transition that is to be used when transitioning
// to this item.
//
// Current supported Transitions: fade, slideLeft, slideRight, slideTop,
// slideBottom. Any other value is treated as a fade.
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.setTransition = function ( s )
{
    this.transition = s;
    return this;
};

//
// build()
//
// An internal function that builds a single DIV with an internal anchor
// that contains the image. It is called only by Rotator.build() as part of
// building the object tree that is appended to the Rotator's element.
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.build = function ()
{
    var o = document.createElement ( "div" );       // container
    var a = document.createElement ( "a" );         // anchor for image
    var img = document.createElement ( "img" );     // preloader for the images
    
    img.setAttribute ("src", this.imageSource);     // set to the image URL
    img.style.position = "absolute";                // position absolutely
    img.style.left = "-9999px";                     // and offscreen. Apparently browsers don't quite get that
                                                    // the image isn't visible, and loads it as O is added to
                                                    // the document. This means that if the image loads before
                                                    // the next transition it will be a smooth transition.
    
    // a is the actual image/link
    a.style.backgroundColor = "#fff";               // opaque background of #000
    a.style.backgroundImage = "url('" + this.imageSource + "')";    // image is on the backgroundImage style
    a.style.backgroundRepeat = "no-repeat";         // We don't want repetition
    a.style.backgroundPosition = "center center";   // if the image is too small or large, center it
    a.style.height = "100%";                        // fill our parent div (o)
    a.style.width = "100%";
    a.style.display = "block";                      // anchors are normally inline, make it a block
    a.style.outline = "none";                       // for Firefox - no outline so clicking doesn't look strange
    a.setAttribute("title",this.imageCaption);        // for tooltips
    a.setAttribute("alt",this.imageCaption);
    a.setAttribute("target", this.linkTarget);      // if the link is an http(s)://, this will be _blank
    a.setAttribute("href", this.linkHREF);          // link url
    
    o.appendChild (a);                              // add a to to o
    o.appendChild (img);                            // and the preloader img too.

    // if we've told our parent to use titles, build the title elements. These are constructed
    // in such away as to require five DIVs. Each DIV is slighly offset from the others to
    // create a shadow effect. Four of the five DIVs are colored BLACK, with the last colored
    // WHITE. The last is centered between the other four.
    //
    // If all browsers supported CSS3 we wouldn't have to do this. But oh well. It works on
    // most browsers, assuming font-smoothing is enabled. If it isn't the effect may not be
    // so pretty.
    if (this.parentRot.useTitles == 'yes')
    {
        var cap = new Array();                          // holds all our elements
        var xofs = new Array ( -2,  0,  2,  0,  0 );    // x-offsets in pixels
        var yofs = new Array (  0, -1,  0,  1,  0 );    // y-offsets in pixels
        
        var i =0;
        for (i=0; i<5; i++)                             // create five elements
        {
            cap[i] = document.createElement ( "div" );
            cap[i].innerHTML = this.imageTitle;
            cap[i].style.position = "absolute";
            cap[i].style.bottom = 25 + yofs[i]  + "px"; // 25px gets us out of the way of the control area
            cap[i].style.right = 35 + xofs[i] + "px";   // and 35px seems to be better than 25px - but this is subjective
            cap[i].style.color = "#000";
            cap[i].style.textAlign = "right";
            cap[i].className = "rotTitle";              // use rotTitle class so we can specify font elsewhere
            o.appendChild (cap[i]);                     // add to o
        }
        cap[4].style.color = "#FFF";                    // override color for the last element to black
    }    
    
    o.style.height = "100%";                            // Make o fill its parent
    o.style.width = "100%";
    o.style.position = "absolute";                      // And be absolutely positioned.
    o.style.display = "none";                           // and hide everything
    
    this.element = o;                                   // make sure the item knows about its element
    
    return o;
};

//
// stageExit ( step )
//
// Internal function used to cause this element to exit the stage.
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.stageExit = function (transition, step)
{
    var pe = this.element;
    pe.style.zIndex = 50;                               // 50 will be under the element entering (100)
    pe.style.display = "block";                         // make sure we're visible for the transition
    
    switch ( transition )
    {
        case "fade":
            this.setOpacity ( 19 - step );    break;    // fade us out in reverse. Assumes step = 20
        default:
            break;
    }
    
    if (step == 0)
    {
        pe.style.display = "none";                      // and hide the element when done exiting.
    }
};

//
// Internal function used to cause this element to enter the stage
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.stageEnter = function ( transition, step )
{
    var ne = this.element;
    ne.style.zIndex = 100;                              // ensures we're on top of the exiting element
    
    switch ( transition )
    {
        case "slideLeft":
            this.moveDirection ("left", step );         // move us left
            break;
        case "slideRight":
            this.moveDirection ("right", step );        // move us right
            break;
        case "slideUp":
            this.moveDirection ("top", step );          // move us up
            break;
        case "slideDown":
            this.moveDirection ("bottom", step );       // move us down
            break;
        case "fade":
        default:
            this.setOpacity ( step );    break;         // fade us in
    }

    ne.style.display = "block";                         // make us visible
};

//
// Sets the opacity of this item to a value specified by i (0 - 20)
///////////////////////////////////////////////////////////////////////////////

RotatorItem.prototype.setOpacity = function  ( i )
{
    this.element.style.opacity = (100 - (i * 5)) / 100; // this is for every other normal browser
    this.element.style.filter = "alpha (opacity=" + (100 - (i*5)) + ")";    // and this is for IE
};

//
// Moves the item in the direction specified by d by the appropriate amount (i)
///////////////////////////////////////////////////////////////////////////////
RotatorItem.prototype.moveDirection = function ( d, i )
{
    this.setOpacity ( 0 );        // make sure we're visible and not transparent!
    var p =  (i * 5);
    switch (d)
    {
        case "bottom":
            this.element.style.bottom = p + "%";
            break;
        case "left":
            this.element.style.left = p + "%";
            break;
        case "right":
            this.element.style.right = p + "%";
            break;
        case "top":
        default:
            this.element.style.top = p + "%";
    }
};
