/*
Kwicks for jQuery
Copyright (c) 2008 Jeremy Martin
http://www.jeremymartin.name/projects.php?project=kwicks
Licensed under the MIT license:
http://www.opensource.org/licenses/mit-license.php

Any and all use of this script must be accompanied by this copyright/license notice in its present form.

11/8/2010: version 2.0 - Changed plugin format, updated & added a github repository - Rob Garrison
*/

(function($){
$.kwicks = function(el, options){
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;

// Access to jQuery and DOM versions of element
base.$el = $(el).addClass('kwicks');
base.el = el;

// Add a reverse reference to the DOM object
base.$el.data('kwicks', base);

base.init = function(){
base.options = $.extend({},$.kwicks.defaultOptions, options);

base.$kwicks = base.$el.children('li');
base.$el.addClass( (base.options.isVertical) ? 'vertical' : 'horizontal' );
base.size = base.$kwicks.size();

// Variables for events
base.lastActive = base.options.defaultKwick;
base.active = base.options.defaultKwick;
base.$active = base.$kwicks.eq(base.active);

// Default variables
base.WoH = (base.options.isVertical) ? 'height' : 'width'; // WoH = Width or Height
base.LoT = (base.options.isVertical) ? 'top' : 'left'; // LoT = Left or Top
base.normWoH = parseInt( base.$kwicks.eq(0).css(base.WoH), 10); // normWoH = Normal Width or Height
base.preCalcLoTs = []; // preCalcLoTs = pre-calculated Left or Top's
base.aniObj = {};

if (!base.options.max) {
base.options.max = (base.normWoH * base.size) - (base.options.min * (base.size - 1));
} else {
base.options.min = ((base.normWoH * base.size) - base.options.max) / (base.size - 1);
}

// set width of container ul
if (base.options.isVertical) {
base.$el.css({
width : base.$kwicks.eq(0).css('width'),
height : (base.normWoH * base.size) + (base.options.spacing * (base.size - 1))
});
} else {
base.$el.css({
width : (base.normWoH * base.size) + (base.options.spacing * (base.size - 1)),
height : base.$kwicks.eq(0).css('height')
});
}

// pre calculate left or top values for all kwicks but the first and last
// i = index of currently hovered kwick, j = index of kwick we're calculating
for(var i = 0; i < base.size; i++) {
base.preCalcLoTs[i] = [];
// don't need to calculate values for first or last kwick
for(var j = 1; j < base.size - 1; j++) {
if(i == j) {
base.preCalcLoTs[i][j] = base.options.isVertical ? j * base.options.min + (j * base.options.spacing) : j * base.options.min + (j * base.options.spacing);
} else {
base.preCalcLoTs[i][j] = (j <= i ? (j * base.options.min) : (j-1) * base.options.min + base.options.max) + (j * base.options.spacing);
}
}
}

// loop through all kwick elements
base.$kwicks.each(function(i) {
var kwick = $(this);

// add class to each kwick
kwick.addClass('kwick-panel kwick' + (i+1));
// set initial width or height and left or top values
// set first kwick
if (i === 0) {
kwick.css(base.LoT, 0);
}
// set last kwick
else if (i == base.size - 1) {
kwick.css(base.options.isVertical ? 'bottom' : 'right', 0);
}
// set all other kwicks
else {
if (base.options.sticky) {
kwick.css(base.LoT, Math.ceil(base.preCalcLoTs[base.options.defaultKwick][i]));
} else {
kwick.css(base.LoT, Math.ceil((i * base.normWoH) + (i * base.options.spacing)));
}
}
// correct size in sticky mode
if (base.options.sticky) {
if(base.options.defaultKwick == i) {
kwick.css(base.WoH, base.options.max);
kwick.addClass(base.options.activeClass);
} else {
kwick.css(base.WoH, base.options.min);
}
}
kwick.css({
margin: 0,
position: 'absolute'
});

kwick.bind(base.options.event, function(e) {
// ignore if already active
if (kwick.is('.' + base.options.activeClass)) { return; }

// update active variables & trigger event
base.$kwicks.stop().removeClass(base.options.activeClass);
base.lastActive = base.active;
base.$active = kwick;
base.active = base.$kwicks.index(kwick);
kwick.addClass(base.options.activeClass);
base.triggerEvent('init');
base.lastEvent = e.timeStamp;

// calculate previous width or heights and left or top values
var prevWoHs = [], // prevWoHs = previous Widths or Heights
prevLoTs = []; // prevLoTs = previous Left or Tops
for(var j = 0; j < base.size; j++) {
prevWoHs[j] = parseInt( base.$kwicks.eq(j).css(base.WoH), 10);
prevLoTs[j] = parseInt( base.$kwicks.eq(j).css(base.LoT), 10);
}
base.aniObj[base.WoH] = base.options.max;
var maxDif = base.options.max - prevWoHs[i],
prevWoHsMaxDifRatio = prevWoHs[i]/maxDif;

base.triggerEvent('expanding');
kwick.animate(base.aniObj, {
step: function(now) {
// calculate animation completeness as percentage
var percentage = maxDif !== 0 ? now/maxDif - prevWoHsMaxDifRatio : 1;
// adjsut other elements based on percentage
base.$kwicks.each(function(j) {
if (j != i) {
base.$kwicks.eq(j).css(base.WoH, Math.ceil(prevWoHs[j] - ((prevWoHs[j] - base.options.min) * percentage)));
}
if (j > 0 && j < base.size - 1) { // if not the first or last kwick
base.$kwicks.eq(j).css(base.LoT, Math.ceil(prevLoTs[j] - ((prevLoTs[j] - base.preCalcLoTs[i][j]) * percentage)));
}
});
},
duration: base.options.duration,
easing: base.options.easing,
complete: function(){ base.triggerEvent('completed'); }
});
});
});

// Reset (collapse) kwicks panel
if (!base.options.sticky) {
base.$el.bind(base.options.eventClose, function(e) {
// check timestamp - in case both 'event' and 'eventClose' are 'click'
if (e.timeStamp - base.lastEvent < 200) { return; }
base.lastEvent = e.timeStamp;
base.triggerEvent('init');
base.closeKwick();
});
}
};

base.openKwick = function(num){
// I tried pulling the expansion animation out, but the event bindings are
// inside an each loop - which doesn't look right to me, but I'm being lazy.
// So, instead of completely rewritting it, I made this shortcut.
if (/\d/.test(num) && !isNaN(num)) {
var n = parseInt($.trim(num),10); // accepts " 2 "
if (n < 0 || n > base.size - 1) { return; }
base.$kwicks.eq(n).trigger(base.options.event);
}
};

base.closeKwick = function(){
if (base.options.sticky) { return; }
var prevWoHs = [],
prevLoTs = [];
for(var i = 0; i < base.size; i++) {
prevWoHs[i] = parseInt( base.$kwicks.eq(i).css(base.WoH), 10);
prevLoTs[i] = parseInt( base.$kwicks.eq(i).css(base.LoT), 10);
}
base.aniObj[base.WoH] = base.normWoH;
var normDif = base.normWoH - prevWoHs[0];

base.triggerEvent('collapsing');

base.$kwicks
.stop()
.removeClass(base.options.activeClass)
.eq(0).animate(base.aniObj, {
step: function(now) {
var percentage = normDif !== 0 ? (now - prevWoHs[0])/normDif : 1;
for(var i = 1; i < base.size; i++) {
base.$kwicks.eq(i).css(base.WoH, Math.ceil(prevWoHs[i] - ((prevWoHs[i] - base.normWoH) * percentage)));
if(i < base.size - 1) {
base.$kwicks.eq(i).css(base.LoT, Math.ceil(prevLoTs[i] - ((prevLoTs[i] - ((i * base.normWoH) + (i * base.options.spacing))) * percentage)));
}
}
},
duration: base.options.duration,
easing: base.options.easing,
complete: function(){ base.triggerEvent('completed'); }
});
};

// Trigger Kwick events
base.triggerEvent = function(cb){
base.$el.trigger('kwicks-' + cb, base);
if ($.isFunction(base.options[cb])) { base.options[cb](base); }
};

// Methods
base.isActive = function(){
return base.$kwicks.is('.active');
};
base.getActive = function(){
return (base.isActive()) ? base.active : -1;
};

// Run initializer
base.init();
};

$.kwicks.defaultOptions = {
isVertical : false, // Kwicks will align vertically if true
sticky : false, // One kwick will always be expanded if true
defaultKwick : 0, // The initially expanded kwick (if and only if sticky is true). zero based
activeClass : 'active', //
event : 'mouseenter', // The event that triggers the expand effect
eventClose : 'mouseleave', // The event that triggers the collapse effect
spacing : 0, // The width (in pixels) separating each kwick element
duration : 500, // The number of milliseconds required for each animation to complete
easing : 'swing', // Custom animation easing (requires easing plugin if anything other than 'swing' or 'linear')
max : null, // The width or height of a fully expanded kwick element
min : null, // The width or height of a fully collapsed kwick element

init : null, // event called when the event occurs (click or mouseover)
expanding : null, // event called before kwicks expanding animation begins
collapsing : null, // event called before kwicks collapsing animation begins
completed : null // event called when animation completes
};

$.fn.kwicks = function(options){
return this.each(function(){
// don't allow multiple instances
if ($(this).data('kwicks')) { return; }
(new $.kwicks(this, options));
});
};

$.fn.getkwicks = function(){
this.data('kwicks');
};

})(jQuery);


