/*
* 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 : 36 //[px] maximum minor axis dimension of image (width or height depending on 'align' : vertical menu = width, horizontal = height)
, distance : 54 //[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 : false //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 : '
'
/* 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 + '
').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
, ''
].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 = $('')
.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...
$('
').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);