/*

 * jqDock jQuery plugin

 * Version : 1.2

 * Author : Roger Barrett

 * Date : June 2008

 *

 * Inspired by:

 *   iconDock jQuery plugin

 *   http://icon.cat/software/iconDock

 *   version: 0.8 beta

 *   date: 2/05/2007

 *   Copyright (c) 2007 Isaac Roca & icon.cat (iroca@icon.cat)

 *   Dual licensed under the MIT-LICENSE.txt and GPL-LICENSE.txt

 *   http://www.opensource.org/licenses/mit-license.php

 *   http://www.gnu.org/licenses/gpl.html

 *

 * Dual licensed under the MIT-LICENSE.txt and GPL-LICENSE.txt

 * http://www.opensource.org/licenses/mit-license.php

 * http://www.gnu.org/licenses/gpl.html

 *

 * Change Log :

 * v1.2

 *    - Fixes for Opera v9.5 - many thanks to Rubel Mujica

 * v1.1

 *    - some speed optimisation within the functions called by the event handler

 *    - added positioning of labels (top/middle/bottom and left/center/right)

 *    - added click handler to label (triggers click event on related image)

 *    - added jqDockLabel(Link|Image) class to label, depending on type of current image

 *    - updated demo and documentation for label positioning and clicking on labels

 */

;

(function($){

if(!$.fn.jqDock){ //can't see why it should be, but it doesn't hurt to check



  var jqDock = function(){

    //return an object...

    return {

        version : 1.2

      , defaults : { //can be set at runtime, per menu

          size : 45 //[px] maximum minor axis dimension of image (width or height depending on 'align' : vertical menu = width, horizontal = height)

        , distance : 60 //[px] attenuation distance from cursor

        , coefficient : 1.5 //attenuation coefficient

        , duration : 500 //[ms] duration of initial expansion and off-menu shrinkage

        , align : 'bottom' //[top/middle/bottom or left/center/right] fixes horizontal/vertical expansion axis

        , labels : true //enable/disable display of a label on the current image

        , source : false //function: given context of relevant image element; passed index of image within menu; required to return image source path, or false to use original

        , loader : null //overrides useJqLoader if set to 'image' or 'jquery'

        }

      , useJqLoader : $.browser.opera || $.browser.safari //use jQuery method for loading images, rather than "new Image()" method

      , shrinkInterval : 100 //(ms) the timer interval between each step of the off-menu shrinking

      , docks : [] //array of dock menus

      , X : 0 //mouse position from left

      , Y : 0 //mouse position from top

      //internals to cut down code and ease decision-making (mainly between vertical and horizontal menus)...

      , verthorz : { v: { wh:'height', xy:'Y', tl:'top', lead:'Top', trail:'Bottom', act:'ActualInv' } //Opts.align = left/center/right

                   , h: { wh:'width', xy:'X', tl:'left', lead:'Left', trail:'Right', act:'Actual' } //Opts.align = top/middle/bottom

                   }

      , elementCss : { position:'relative', borderWidth:0, borderStyle:'none', verticalAlign:'top' }

      , vanillaDiv : '<div style="position:relative;margin:0px;padding:0px;border:0px none;background-color:transparent;">'



  /* initDock()

  *  ==========

  * called by the image onload function, it stores and sets image height/width;

  * once all images have been loaded, it completes the setup of the dock menu

  * note: unless all images get loaded, the menu will stay hidden!

  * @context jqDock

  * @param integer (dock index)

  */

      , initDock : function(id){

      //========================================

          var ME = this

            , Dock = this.docks[id] //convenience

            , op = Dock.Opts //convenience

            , off = 0

            , AI = $('a, img', Dock.Menu)

            , i = 0

            , j, el, wh, acc, upad

            , opPre95 = ($.browser.opera && (1*($.browser.version.match(/^(\d+\.\d+)/)||[0,0])[1]) < 9.5) // v1.2 : need to distinguish Opera v9.5

            ;

          // things will screw up if we don't clear text nodes...

          this.removeText(Dock.Menu);

          //set some basic styles on the dock elements, otherwise it won't work

          if(op.orient.vh == 'h'){

            AI.css(this.elementCss);

            if(opPre95 || !$.boxModel){ //Opera (v1.2 : pre v9.5 only), and IE in quirks mode, can't handle floated blocks...

              AI.filter('a').css({lineHeight:0, fontSize:'0px'});

            }else{ //not Opera or IE in quirks mode...

              var hcss = {display:'block'};

              hcss['float'] = 'left'; //don't want any 'reserved word' problems from IE

              AI.filter('img').css(hcss);

            }

          }else{ //vertical docks require a div wrapper around each menu element (v1.2 : set anchors/images to display block)...

            AI.not($('a img', Dock.Menu)).wrap(this.vanillaDiv + '</div>').end().css(this.elementCss).css({display:'block'});

          }

          //resize each image and store various settings wrt main axis...

          while(i < Dock.Elem.length){

            el = Dock.Elem[i++];

            //resize the image to make the minor axis dimension meet the specified 'Opts.size'...

            wh = this.keepProportion(el, op.size, {vh:op.orient.inv, inv:op.orient.vh}); //inverted!

            el.Actual = el.Final = el.Initial = wh[op.vh.wh];

            el.SizeDiff = el[op.vh.wh] - el.Initial; //on main axis!

            el.Img.css(wh); //resize the image to its new shrunken setting

            //remove titles, alt text...

            el.Img.removeAttr('title').attr({alt:''}).parent('a').removeAttr('title');

            //calculate shrinkage step size

            el.ShrinkStep = Math.floor(el.SizeDiff * this.shrinkInterval / op.duration);

            //use inverts because we're after the minor axis dimension...

            Dock[op.vh.inv.wh] = Math.max(Dock[op.vh.inv.wh], op.size + el.Pad[op.vh.inv.lead] + el.Pad[op.vh.inv.trail]);



            el.Offset = off;

            el.Centre = el.Offset + el.Pad[op.vh.lead] + (el.Initial / 2);

            off += el.Initial + el.Pad[op.vh.lead] + el.Pad[op.vh.trail];

          }



          //'best guess' at calculating max 'spread' (main axis dimension - horizontal or vertical) of menu:

          //for each img element of the menu, call setSizes() with a forced cursor position of the centre of the image;

          //setSizes() will set each element's Final value, so tally them all, including user-applied padding, to give

          //an overall width/height for this cursor position; set dock width/height to be the largest width/height found

          i = 0;

          while(i < Dock.Elem.length){

            el = Dock.Elem[i++];

            acc = 0; //accumulator for main axis image dimensions

            upad = el.Pad[op.vh.lead] + el.Pad[op.vh.trail]; //user padding in main axis

            //tally the minimum widths...

            Dock.Spread += el.Initial + upad;

            //set sizes with an overridden cursor position...

            this.setSizes(id, el.Centre);

            //tally image widths/heights (plus padding)...

            j = Dock.Elem.length;

            while(j){

              //note that Final is an image dimension (in main axis) and does not include any user padding...

              acc += Dock.Elem[--j].Final + upad;

            }

            //keep largest main axis dock dimension...

            Dock[op.vh.wh] = Math.max(Dock[op.vh.wh], acc);

          }

          //reset Final for each image...

          while(i){

            el = Dock.Elem[--i];

            el.Final = el.Initial;

          }

          var wrap = [ this.vanillaDiv

                     , '<div class="jqDock" style="position:absolute;top:0px;left:0px;padding:0px;'

                     , 'margin:0px;overflow:visible;height:'

                     , Dock.height, 'px;width:', Dock.width

                     , 'px;"></div></div>'

                     ].join('');

          Dock.Yard = $(Dock.Menu).wrapInner(wrap).find('div.jqDock');

          //let's see if the user has applied any css border styling to div.jqDock...

          $.each([op.vh.lead, op.vh.trail], function(n, v){

              Dock.Borders[v] = ME.asNumber(Dock.Yard.css('border'+v+'Width'));

            });

          //if div.jqDock has a border we need to shift it a bit so the border doesn't get lost...

          if(Dock.Borders[op.vh.lead]){

            Dock.Yard.css(op.vh.tl, Math.ceil(Dock.Borders[op.vh.lead] / 2));

          }

          //shrink all images down to 'at rest' size, and add appropriate identifying class...

          while(i < Dock.Elem.length){

            el = Dock.Elem[i];

            this.changeSize(id, i, el.Final, true); //force

            el.Img.addClass('jqDockMouse'+id+'_'+(i++));

          }

          //show the menu now...

          $(Dock.Menu).show();

          //now that the menu is visible we can set up labels and get label widths...

          if(Dock.Opts.labels){

            $.each(Dock.Elem, function(i){

                ME.setLabel(id, this.Label);

              });

            Dock.Label.hide();

          }

          //bind a mousehandler to the menu...

          Dock.Yard.bind('mouseover mouseout mousemove', function(e){ ME.mouseHandler(e); });

        } //end function initDock()



  /* altImage()

  *  ==========

  * tests to see if an image has an alt attribute that looks like an image path, returning it if found, else false

  * note: context of the image element

  * @context DOM element (image)

  * @return string (image path) or false

  */

      , altImage : function(){

          var alt = $(this).attr('alt');

          return (alt && alt.match(/\.(gif|jpg|jpeg|png)$/i)) ? alt : false;



        } //end function altImage()



  /* removeText()

  *  ============

  * removes ALL text nodes from the menu, so that we don't get spacing issues between menu elements

  * note : this includes text within anchors

  * @context jqDock

  * @param DOM element

  * @recursive

  */

      , removeText : function(el){

      //==========================

          var i = el.childNodes.length

            , j

            ;

          while(i){

            j = el.childNodes[--i];

            if(j.childNodes && j.childNodes.length){

              this.removeText(j);

            }else if(j.nodeType == 3){

              el.removeChild(j);

            }

          }

        } //end function removeText()



  /* asNumber()

  *  ==========

  * returns numeric of leading digits in string argument

  * @context jqDock

  * @param string

  * @return integer

  */

      , asNumber : function(x){

      //=========================

          var r = parseInt(x, 10);

          return isNaN(r) ? 0 : r;

        } //end function asNumber()



  /* keepProportion()

  *  ================

  * returns an object containing width and height, with the one NOT represented by 'dim'

  * being calculated proportionately

  * if horizontal then attenuation is along vertical (x) axis, thereby setting the new

  * dimension for width, so the one to keep in proportion is height; and vice versa for

  * vertical menus, obviously!

  * @context jqDock

  * @param object (element of elements array)

  * @param integer (image dimension)

  * @param object (dock orientation)

  * @return integer (other image dimension)

  */

      , keepProportion : function(el, dim, orient){

      //===========================================

          var r = {}

            , vh = this.verthorz[orient.vh] //convenience

            , inv = this.verthorz[orient.inv] //convenience

            ;

          r[vh.wh] = dim;

          r[inv.wh] = Math.round(dim * el[inv.wh] / el[vh.wh]);

          return r;

        } //end function keepProportion()



  /* deltaXY()

  *  =========

  * translates this.X or this.Y into an offset within div.jqDock

  * note: doing it this way means that all attenuation is against the inital (shrunken) image positions,

  * but it saves having to find every image's offset() each time the cursor moves or an image changes size!

  * @context jqDock

  * @param integer (dock index)

  */

      , deltaXY : function(id){

      //=======================

          var Dock = this.docks[id]; //convenience

          if(Dock.Current !== false){

            var op = Dock.Opts //convenience

              , el = Dock.Elem[Dock.Current] //convenience

              , p = el.Pad[op.vh.lead] + el.Pad[op.vh.trail] //element's user-specified padding

              , off = el.Img.offset()

              ;

            //get the difference between the cursor position and the leading edge of the current image,

            //multiply by the full/shrunken ratio, and add the element's pre-calculated offset within div.jqDock...

            Dock.Delta = Math.floor((this[op.vh.xy] - off[op.vh.tl]) * (p + el.Initial) / (p + el.Actual)) + el.Offset;

            this.doLabel(id, off);

          }

        } //end function deltaXY()



  /* setLabel()

  *  ==========

  * sets up the labels, storing each image's label dimensions

  * @context jqDock

  * @param integer (dock index)

  * @param object (menu element's label settings)

  */

      , setLabel : function(id, label){

      //===============================

          var Dock = this.docks[id] //convenience

            , ME = this

            , pad = {}

            ;

          if(!Dock.Label){ //create the div.jqDockLabel and hide it...

            Dock.Label = $('<div class="jqDockLabel jqDockMouse'+id+'_00 jqDockLabelImage" style="position:absolute;margin:85px 0 0 0;"></div>')

                           .hide().bind('click', function(){ Dock.Elem[Dock.Current].Img.trigger('click'); }).appendTo(Dock.Yard);

          }

          if(label.txt){

            //insert the label text for this image, and find any user-styled padding...

            Dock.Label.text(label.txt);

            $.each(['Top', 'Right', 'Bottom', 'Left'], function(n, v){

                pad[v] = ME.asNumber(Dock.Label.css('padding'+v));

              });

            //store the label dimensions for this image...

            $.each(this.verthorz, function(vh, o){

                label[o.wh] = Dock.Label[o.wh]();

                label[o.wh+'Pad'] = pad[o.lead] + pad[o.trail]; //hold padding separately

              });

          }

        } //end function setLabel()



  /* doLabel()

  *  =========

  * if labels enabled, performs the appropriate action

  * @context jqDock

  * @param integer (dock index)

  * @param string (what action to do) or object (top/left offset of an image)

  */

      , doLabel : function(id, off){

      //============================

          var Dock = this.docks[id]; //convenience

          if(Dock.Opts.labels && Dock.Current !== false){ //only if labels are set and we're over an image

            var el = Dock.Elem[Dock.Current] //convenience

              , L = el.Label //convenience

              , op = Dock.Opts //convenience

              , what = typeof off == 'string' ? off : 'move'

              ;

            switch(what){

              case 'show': case 'hide' : //show or hide...

                Dock.Label[L.txt?what:'hide']();

                break;

              case 'change': //change the label text and set the appropriate dimensions for the current image...

                Dock.Label[0].className = Dock.Label[0].className.replace(/(jqDockLabel)(Link|Image)/, '$1'+(el.Linked ? 'Link' : 'Image'));

                Dock.Label.text(L.txt).css({width:L.width, height:L.height}).hide();

                break;

              default: //move the label...

                //can't avoid extra processing here because we have to get the dock's offsets realtime since simply

                //expanding/shrinking a dock can make scroll bars appear/disappear and thereby affect the dock's position

                var doff = Dock.Yard.offset()

                  , css = { top: off.top - doff.top

                          , left: off.left - doff.left

                          }

                  , splt = op.labels.split('')

                  ;

                //note: if vertically or horizontally centred then centre is based on the IMAGE only

                //(ie without including padding), otherwise, positioning includes anyimage padding

                if(splt[0] == 'm'){

                  css.top += Math.floor((el[op.vh.inv.act] - L.height - L.heightPad) / 2);

                }else if(splt[0] == 'b'){

                  css.top += el[op.vh.inv.act] + el.Pad.Top + el.Pad.Bottom - L.height - L.heightPad;

                }

                if(splt[1] == 'c'){

                  css.left += Math.floor((el[op.vh.act] - L.width - L.widthPad) / 2);

                }else if(splt[1] == 'r'){

                  css.left += el[op.vh.act] + el.Pad.Left + el.Pad.Right - L.width - L.widthPad;

                }

                Dock.Label.css(css);

            }

          }

        } //end function doLabel()



  /* mouseHandler()

  *  ==============

  * handler for all bound mouse events (move/over/out)

  * note: this handles both image and label events

  * note: when moving within a label Opera reports both a mousemove and a mouseover (presumably because the label has been moved?), but the mouseover does not have a relatedTarget!

  * @context jqDock

  * @param object (event)

  * @return null or false

  */

      , mouseHandler : function(e){

      //===========================

          var r = null

            , t = e.target.className.match(/jqDockMouse(\d+)_(\d+)/)

              //on a mouseout from an image onto a label, Opera reports relatedTarget as existing, but with tagName and className as 'undefined'!...

            , rt = !!(e.relatedTarget) && e.relatedTarget.tagName !== undefined

            ;

          if(t){

            r = false; //prevent the event going any further

            var id = 1*t[1] //convenience

              , Dock = this.docks[id] //convenience

              , idx = t[2] == '00' ? Dock.Current : 1*t[2] //note: label events have _00 suffix on the class name

              ;

            this.X = e.pageX;

            this.Y = e.pageY;

            if(e.type == 'mousemove'){

              if(idx == Dock.Current){ //precedence to mouseover/out processing...

                this.deltaXY(id);

                if(Dock.OnDock && Dock.Expanded){

                  this.setSizes(id);

                  this.factorSizes(id);

                }

              }

            }else{

              var rel = rt && e.relatedTarget.className.match(/jqDockMouse(\d+)_(\d+)/);

              //only do something on a mouseover if the current menu element has changed...

              if(e.type == 'mouseover' && (!Dock.OnDock || idx !== Dock.Current)){

                Dock.Current = idx;

                this.doLabel(id, 'change');

                this.deltaXY(id);

                if(Dock.Expanded){

                  this.doLabel(id, 'show');

                }

                if(rt && (!rel || rel[1] != id)){ //came from outside this menu...

                  Dock.Timestamp = (new Date()).getTime();

                  this.setSizes(id);

                  Dock.OnDock = true;

                  this.overDock(id); //sets Expanded when complete

                }

              //only do something on a mouseout if we can tell where we are mousing out to...

              }else if(rt && e.type == 'mouseout'){

                if(!rel || rel[1] != id){ //going outside this menu...

                  Dock.OnDock = false;

                  this.doLabel(id, 'hide');

                  //reset Final dims, per element, to the original (shrunken)...

                  var i = Dock.Elem.length;

                  while((i--)){

                    Dock.Elem[i].Final = Dock.Elem[i].Intial;

                  }

                  this.offDock(id); //clears Expanded and Current when complete

                }

              }

            }

          }

          return r;

        } //end function mouseHandler()



  /* overDock()

  *  ==========

  * checks for completed expansion (if OnDock)

  * if not completed, runs setSizes(), factorSizes(), and then itself on a 60ms timer

  * @context jqDock

  * @param integer (dock index)

  */

      , overDock : function(id){

      //========================

          var Dock = this.docks[id]; //convenience

          if(Dock.OnDock){

            var ME = this

              , el = Dock.Elem //convenience

              , i = el.length

              ;

            while((i--) && !(el[i].Actual < el[i].Final)){}

            if(i < 0){ //complete

              Dock.Expanded = true;

              this.deltaXY(id);

              this.doLabel(id, 'show');

            }else{

              this.setSizes(id);

              this.factorSizes(id);

              setTimeout(function(){ ME.overDock(id); }, 60);

            }

          }

        } //end function overDock()



  /* offDock()

  *  =========

  * called when cursor goes outside menu, and checks for completed shrinking of all menu elements

  * calls changeSize() on any menu element that has not finished shrinking

  * calls itself on a timer to complete the shrinkage

  * @context jqDock

  * @param integer (dock index)

  */

      , offDock : function(id){

      //=======================

          var Dock = this.docks[id]; //convenience

          if(!Dock.OnDock){

            var ME = this

              , done = true

              , i = Dock.Elem.length

              , el, sz

              ;

            while(i){

              el = Dock.Elem[--i];

              if(el.Actual > el.Initial){

                sz = el.Actual - el.ShrinkStep;

                if(sz > el.Initial){

                  done = false;

                }else{

                  sz = el.Initial;

                }

                this.changeSize(id, i, sz);

              }

            }

            //this is here for no other reason than that Opera leaves a 'shadow' residue of the expanded image unless/until Delta is recalculated!...

            this.deltaXY(id);

            if(done){

              //reset everything back to 'at rest' state...

              while(i < Dock.Elem.length){

                el = Dock.Elem[i++];

                el.Actual = el.Final = el.Initial;

              }

              Dock.Current = Dock.Expanded = false;

            }else{

              setTimeout(function(){ ME.offDock(id); }, this.shrinkInterval);

            }

          }

        } //end function offDock()



  /* setSizes()

  *  ==========

  * calculates the image sizes according to the current (translated) position of the cursor within div.jqDock

  * result stored in Final for each menu element

  * @context jqDock

  * @param integer (dock index)

  * @param integer (translated cursor offset in main axis)

  */

      , setSizes : function(id, mxy){

      //=============================

          var Dock = this.docks[id] //convenience

            , op = Dock.Opts //convenience

            , i = Dock.Elem.length

            , el, sz

            ;

          mxy = mxy || Dock.Delta; //if not forced, use current translated cursor position (main axis)

          while(i){

            el = Dock.Elem[--i];

            //if we're within the attenuation distance then sz will be less than the difference between the max and min dims

            //if we're smack on or beyond the attenuation distance then set to the min dim

            //note: set sz to an integer number, otherwise we could end up 'fluttering'

            sz = Math.floor(el.SizeDiff * Math.pow(Math.abs(mxy - el.Centre), op.coefficient) / op.attenuation);

            el.Final = (sz < el.SizeDiff ? el[op.vh.wh] - sz : el.Initial);

          }

        } //end function setSizes()



  /* factorSizes()

  *  =============

  * modifies the target sizes in proportion to 'duration' if still within the 'duration' period following a mouseover

  * calls changeSize() for each menu element (if more than 60ms since mouseover)

  * @context jqDock

  * @param integer (dock index)

  */

      , factorSizes : function(id){

      //===========================

          var Dock = this.docks[id] //convenience

            , op = Dock.Opts //convenience

            , lapse = op.duration + 60

            ;

          if(Dock.Timestamp){

            lapse = (new Date()).getTime() - Dock.Timestamp;

            //Timestamp only gets set on mouseover (onto menu) so there's no point continually checking Date once op.duration has passed...

            if(lapse >= op.duration){

              Dock.Timestamp = 0;

            }

          }

          if(lapse > 60){ //only if more than 60ms have passed since last mouseover

            var f = lapse < op.duration ? lapse / op.duration : 0

              , i = 0 //must go through the elements if logical order

              , el

              ;

            while(i < Dock.Elem.length){

              el = Dock.Elem[i];

              this.changeSize(id, i++, (f ? Math.floor(el.Initial + ((el.Final - el.Initial) * f)) : el.Final));

            }

          }

        } //end function factorSizes()



  /* changeSize()

  *  ============

  * sets the css for an individual image to effect its change in size

  * 'dim' is the new value for the main axis dimension as specified in Opts.vh.wh, so

  * the margin needs to be applied to the inverse of Opts.vh.wh!

  * note: 'force' is only set when called from initDock() to do the initial shrink

  * @context jqDock

  * @param integer (dock index)

  * @param integer (image index)

  * @param integer (main axis dimension of image)

  * @param boolean

  */

      , changeSize : function(id, idx, dim, force){

      //===========================================

          var Dock = this.docks[id] //convenience

            , el = Dock.Elem[idx] //convenience

            ;

          if(force || el.Actual != dim){

            var op = Dock.Opts //convenience

              //vertical menus, or IE in quirks mode, require border widths (if any) of the Dock to be added to the Dock's main axis dimension...

              , bdr = ($.boxModel || op.orient.vh == 'v') ? 0 : Dock.Borders[op.vh.lead] + Dock.Borders[op.vh.trail]

              ;

            //switch image source to large, if (a) it's different to small source, and (b) this is the first step of an expansion...

            if(el.Source[2] && !force && el.Actual == el.Initial){

              el.Img[0].src = el.Source[1];

            }

            if(Dock.OnDock){

              this.deltaXY(id); //recalculate deltaXY

            }

            Dock.Spread += dim - el.Actual; //adjust main axis dimension of dock

            var css = this.keepProportion(el, dim, op.orient)

              , diff = op.size - css[op.vh.inv.wh]

              , m = 'margin' //convenience

              , z = op.vh.inv //convenience

              ;

            //add minor axis margins according to alignment...

            //note: where diff is an odd number of pixels, for 'middle' or 'center' alignment put the odd pixel in the 'lead' margin

            switch(op.align){

              case 'bottom': case 'right' : css[m+z.lead] = diff; break;

              case 'middle': case 'center' : css[m+z.lead] = (diff + diff%2) / 2; css[m+z.trail] = (diff - diff%2) / 2; break;

              case 'top': case 'left': css[m+z.trail] = diff; break;

              default:

            }

            //set dock's main axis dimension...

            Dock.Yard[op.vh.wh](Dock.Spread + bdr);

            //change image size and margins...

            el.Img.css(css);

            //set dock's main axis 'lead' margin (v1.2: make sure that margin doesn't go negative!)...

            Dock.Yard.css('margin'+op.vh.lead, Math.floor(Math.max(0, (Dock[op.vh.wh] - Dock.Spread) / 2)));

            //store new dimensions...

            el.Actual = dim; //main axis

            el.ActualInv = css[op.vh.inv.wh]; //minor axis

            //switch image source to small, if (a) it's different to large source, and (b) this was the last step of a shrink...

            if(el.Source[2] && !force && el.Actual == el.Initial){

              el.Img[0].src = el.Source[0];

            }

          }

        } //end function changeSize()

      }; //end of return object

    }(); //run the function to set up jqDock



  /***************************************************************************************************

  *  jQuery.fn.jqDock()

  *  ==================

  * usage:    $(selector).jqDock(options);

  * options:  see jqDock.defaults (top of script)

  *

  * note: the aim is to do as little processing as possible after setup, because everything is

  * driven from the mousemove/over/out events and I don't want to kill the browser if I can help it!

  * hence the code below, and in jqDock.initDock(), sets up and stores everything it possibly can

  * which will avoid extra processing at runtime, and hopefully give as smooth animation as possible.

  ***************************************************************************************************/

  $.fn.jqDock = function(opts){

    return this.filter(function(){

        //check not already set up and has images...

        var i = jqDock.docks.length;

        while((i--) && this != jqDock.docks[i].Menu){}

        return (i < 0) && ($('img', this).length);

      }).hide() //hide it/them

      .each(function(){

        //add an object to the docks array for this new dock...

        var id = jqDock.docks.length;

        jqDock.docks[id] = { Elem : [] // an object per img menu option

                           , Menu : this //original containing element

                           , OnDock : false //indicates cursor over menu and initial sizes set

                           , Expanded : false //indicates completion of initial menu element expansions

                           , Timestamp : 0 //set on mouseover and used (within opts.duration) to proportion the menu element sizes

                           , width : 0 //width of div.jqDock container

                           , height : 0 //height of div.jqDock container

                           , Spread : 0 //main axis dimension (horizontal = width, vertical = height)

                           , Borders : {} //border widths (main axis) on div.jqDock

                           , Yard : false //jQuery of div.jqDock

                           , Opts : $.extend({}, jqDock.defaults, opts||{}) //options

                           , Current : false //current image index

                           , Delta : 0 //X or Y translated into horizontal or vertical offset within div.jqDock as if all images were unexpanded

                           , Loaded : 0 //count of images loaded

                           , Label : false //jQuery of label container (if Opts.labels is set)

                           };

        var Dock = jqDock.docks[id] //convenience

          , op = Dock.Opts //convenience

          ;

        //set up some extra Opts now, just to save some computing power later...

        op.attenuation = Math.pow(op.distance, op.coefficient); //straightforward, static calculation

        op.orient = ({left:1, center:1, right:1}[op.align]) ? {vh:'v', inv:'h'} : {vh:'h', inv:'v'}; //orientation based on 'align' option

        op.vh = $.extend({}, jqDock.verthorz[op.orient.vh], {inv:jqDock.verthorz[op.orient.inv]}); //main and minor axis internals

        op.loader = (op.loader) && typeof op.loader == 'string' && /^image|jquery$/i.test(op.loader) ? op.loader.toLowerCase() : ''; //image loader override

        op.labels = op.labels === true ? {top:'bc',left:'tr',right:'tl'}[op.align] || 'tc' : (typeof op.labels == 'string' && {tl:1,tc:1,tr:1,ml:1,mc:1,mr:1,bl:1,bc:1,br:1}[op.labels] ? op.labels : false);



        $('img', this).each(function(n){

            //add an object to the dock's elements array for each image...

            var me = $(this)

              , s0 = me.attr('src') //'small' image source

              , s1 = (op.source ? op.source.call(me[0], n) : false) || jqDock.altImage.call(this) || s0 //'large' image source?

              , tx = op.labels ? me.attr('title') || me.parent('a').attr('title') || '' : '' //label text?

              ;

            Dock.Elem[n] = { Img : me //jQuery of img element

                           , Source : [ s0, s1, !(s0 == s1) ] //array : [ small image path, large image path, different? ]

                           , Label : { txt: tx, width: 0, height: 0, widthPad: 0, heightPad: 0 } //label text, dimensions, user-applied padding

                           , Initial : 0 //width/height when fully shrunk; it's important to note that this is not necessarily the same as Opts.size!

                           , Actual : 0 //transitory width/height (main axis)

                           , ActualInv : 0 //transitory width/height (minor axis)

                           , Final : 0 //target width/height

                           , Offset : 0 //offset of 'lead' edge of the image within div.jqDock (including user-padding)

                           , Centre : 0 //'Offset' + 'lead' user-padding + half 'Initial' dimension

                           , Pad : {} //user-applied padding, set up below

                           , Linked : !!me.parent('a').length //image-within-link or not

                           , width : 0 //original width of img element (the one that expands)

                           , height : 0 //original height of img element (the one that expands)

                           };

            $.each(['Top', 'Right', 'Bottom', 'Left'], function(i, v){

                Dock.Elem[n].Pad[v] = jqDock.asNumber(me.css('padding'+v));

              });

          });

        //we have to run a 'loader' function for the images because the expanding image

        //may not be part of the current DOM. what this means though, is that if you

        //have a missing image in your dock, the entire dock will not be displayed!

        //however I've had a few problems with certain browsers: for instance, IE does

        //not like the jQuery method; and Opera was causing me problems with the native

        //method when reloading the page; I've also heard rumours that Safari 2 might cope better with

        //the jQuery method, but I cannot confirm since I no longer have Safari 2.

        //

        //anyway, I'm providing both methods. if anyone finds it doesn't work, try

        //overriding with option.loader, and/or changing jqDock.useJqLoader for the 

        //browser in question and let me know if that solves it.

        var jqld = (!op.loader && jqDock.useJqLoader) || op.loader == 'jquery';

        $.each(Dock.Elem, function(i){

            var me = this

              , iLoaded = function(){

                  //store 'large' width and height...

                  me.height = this.height;

                  me.width = this.width;

                  if(++Dock.Loaded >= Dock.Elem.length){ //check to see if all images are loaded...

                    setTimeout(function(){ jqDock.initDock(id); }, 0);

                  }

                }

              ;

            if(jqld){ //jQuery method...

              $('<img />').bind('load', iLoaded).attr({src:this.Source[1]});

            }else{ //native 'new Image()' method...

              var pre = new Image();

              pre.onload = function(){

                  iLoaded.call(this);

                  pre.onload = function(){}; //wipe out this onload function

                };

              pre.src = this.Source[1];

            }

          });

      })

      .end(); //revert the filter to maintain chaining

  }; //end jQuery.fn.jqDock()



  /***************************************************************************************************

  *  jQuery.jqDock()

  *  ===============

  * usage:    $.jqDock(property);

  * returns:  the jqDock object's property, or null

  * example:  var vsn = $.jqDock('version');

  ***************************************************************************************************/

  $.jqDock = function(x){

    return jqDock[x] ? jqDock[x] : null;

  }; //end jQuery.jqDock()

} //end of if()

})(jQuery);



