[damned-lies] Helper JS, CSS and image to create overlays (bug #666968)



commit 88dd48f1c7a113dca89aec93cdcfc0e9f6090646
Author: Gil Forcada <gforcada gnome org>
Date:   Thu Dec 29 00:42:07 2011 +0100

    Helper JS, CSS and image to create overlays (bug #666968)

 media/css/overlays.css       |   57 ++
 media/img/close.png          |  Bin 0 -> 1996 bytes
 media/js/jquery.tools.js     | 1672 ++++++++++++++++++++++++++++++++++++++++++
 media/js/jquery.tools.min.js |   11 +
 media/js/overlayhelpers.js   |  595 +++++++++++++++
 5 files changed, 2335 insertions(+), 0 deletions(-)
---
diff --git a/media/css/overlays.css b/media/css/overlays.css
new file mode 100644
index 0000000..8f9b43e
--- /dev/null
+++ b/media/css/overlays.css
@@ -0,0 +1,57 @@
+div.overlay {
+    width: auto;
+    height: auto;
+    /* initially overlay is hidden */
+    display: none;
+    /* some padding to layout nested elements nicely  */
+    margin: 1em;
+    color: black;
+    font-weight: normal;
+}
+
+div.overlay-ajax {
+    width: 60%;
+    min-height: 100px;
+    z-index: 99; /* overlays should be above everything */
+}
+
+
+/* default close button positioned on upper-left corner */
+div.overlaybg div.close,
+div.overlay div.close {
+    background-image: url(/media/img/close.png);
+    position: absolute;
+    left: -16px;
+    top: -16px;
+    cursor: pointer;
+    height: 36px;
+    width: 36px;
+}
+
+.pb-ajax {
+    overflow-y: auto;
+}
+
+.pb-ajax, .pb-image {
+    background-color: #fff;
+    border: 1px solid #999;
+    white-space: normal;
+    box-shadow: 0 0 3em 0.5em #666;
+    -moz-box-shadow: 0 0 3em 0.5em #666;
+    -webkit-box-shadow: 0 0 3em #666;
+    padding-left: 30px;
+}
+
+.pb-ajax > div {
+    width: 92%;
+    padding: 0em;
+}
+
+div.overlay div.close span {
+    display: block;
+    height: 1px;
+    margin: -1px 0 0 -1px;
+    overflow: hidden;
+    padding: 0;
+    width: 1px;
+}
diff --git a/media/img/close.png b/media/img/close.png
new file mode 100644
index 0000000..d247e09
Binary files /dev/null and b/media/img/close.png differ
diff --git a/media/js/jquery.tools.js b/media/js/jquery.tools.js
new file mode 100644
index 0000000..5896b1a
--- /dev/null
+++ b/media/js/jquery.tools.js
@@ -0,0 +1,1672 @@
+/**
+ * @license
+ * jQuery Tools v1.2.6 Overlay - Overlay base. Extend it.
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/overlay/
+ *
+ * Since: March 2008
+ * Date: 2011-10-26 11:02
+ */
+(function($) {
+
+	// static constructs
+	$.tools = $.tools || {version: 'v1.2.6'};
+
+	$.tools.overlay = {
+
+		addEffect: function(name, loadFn, closeFn) {
+			effects[name] = [loadFn, closeFn];
+		},
+
+		conf: {
+			close: null,
+			closeOnClick: true,
+			closeOnEsc: true,
+			closeSpeed: 'fast',
+			effect: 'default',
+
+			// since 1.2. fixed positioning not supported by IE6
+			fixed: !$.browser.msie || $.browser.version > 6,
+
+			left: 'center',
+			load: false, // 1.2
+			mask: null,
+			oneInstance: true,
+			speed: 'normal',
+			target: null, // target element to be overlayed. by default taken from [rel]
+			top: '10%'
+		}
+	};
+
+
+	var instances = [], effects = {};
+
+	// the default effect. nice and easy!
+	$.tools.overlay.addEffect('default',
+
+		/*
+			onLoad/onClose functions must be called otherwise none of the
+			user supplied callback methods won't be called
+		*/
+		function(pos, onLoad) {
+
+			var conf = this.getConf(),
+				 w = $(window);
+
+			if (!conf.fixed)  {
+				pos.top += w.scrollTop();
+				pos.left += w.scrollLeft();
+			}
+
+			pos.position = conf.fixed ? 'fixed' : 'absolute';
+			this.getOverlay().css(pos).fadeIn(conf.speed, onLoad);
+
+		}, function(onClose) {
+			this.getOverlay().fadeOut(this.getConf().closeSpeed, onClose);
+		}
+	);
+
+
+	function Overlay(trigger, conf) {
+
+		// private variables
+		var self = this,
+			 fire = trigger.add(self),
+			 w = $(window),
+			 closers,
+			 overlay,
+			 opened,
+			 maskConf = $.tools.expose && (conf.mask || conf.expose),
+			 uid = Math.random().toString().slice(10);
+
+
+		// mask configuration
+		if (maskConf) {
+			if (typeof maskConf == 'string') { maskConf = {color: maskConf}; }
+			maskConf.closeOnClick = maskConf.closeOnEsc = false;
+		}
+
+		// get overlay and trigger
+		var jq = conf.target || trigger.attr("rel");
+		overlay = jq ? $(jq) : null || trigger;
+
+		// overlay not found. cannot continue
+		if (!overlay.length) { throw "Could not find Overlay: " + jq; }
+
+		// trigger's click event
+		if (trigger && trigger.index(overlay) == -1) {
+			trigger.click(function(e) {
+				self.load(e);
+				return e.preventDefault();
+			});
+		}
+
+		// API methods
+		$.extend(self, {
+
+			load: function(e) {
+
+				// can be opened only once
+				if (self.isOpened()) { return self; }
+
+				// find the effect
+		 		var eff = effects[conf.effect];
+		 		if (!eff) { throw "Overlay: cannot find effect : \"" + conf.effect + "\""; }
+
+				// close other instances?
+				if (conf.oneInstance) {
+					$.each(instances, function() {
+						this.close(e);
+					});
+				}
+
+				// onBeforeLoad
+				e = e || $.Event();
+				e.type = "onBeforeLoad";
+				fire.trigger(e);
+				if (e.isDefaultPrevented()) { return self; }
+
+				// opened
+				opened = true;
+
+				// possible mask effect
+				if (maskConf) { $(overlay).expose(maskConf); }
+
+				// position & dimensions
+				var top = conf.top,
+					 left = conf.left,
+					 oWidth = overlay.outerWidth({margin:true}),
+					 oHeight = overlay.outerHeight({margin:true});
+
+				if (typeof top == 'string')  {
+					top = top == 'center' ? Math.max((w.height() - oHeight) / 2, 0) :
+						parseInt(top, 10) / 100 * w.height();
+				}
+
+				if (left == 'center') { left = Math.max((w.width() - oWidth) / 2, 0); }
+
+
+		 		// load effect
+				eff[0].call(self, {top: top, left: left}, function() {
+					if (opened) {
+						e.type = "onLoad";
+						fire.trigger(e);
+					}
+				});
+
+				// mask.click closes overlay
+				if (maskConf && conf.closeOnClick) {
+					$.mask.getMask().one("click", self.close);
+				}
+
+				// when window is clicked outside overlay, we close
+				if (conf.closeOnClick) {
+					$(document).bind("click." + uid, function(e) {
+						if (!$(e.target).parents(overlay).length) {
+							self.close(e);
+						}
+					});
+				}
+
+				// keyboard::escape
+				if (conf.closeOnEsc) {
+
+					// one callback is enough if multiple instances are loaded simultaneously
+					$(document).bind("keydown." + uid, function(e) {
+						if (e.keyCode == 27) {
+							self.close(e);
+						}
+					});
+				}
+
+
+				return self;
+			},
+
+			close: function(e) {
+
+				if (!self.isOpened()) { return self; }
+
+				e = e || $.Event();
+				e.type = "onBeforeClose";
+				fire.trigger(e);
+				if (e.isDefaultPrevented()) { return; }
+
+				opened = false;
+
+				// close effect
+				effects[conf.effect][1].call(self, function() {
+					e.type = "onClose";
+					fire.trigger(e);
+				});
+
+				// unbind the keyboard / clicking actions
+				$(document).unbind("click." + uid).unbind("keydown." + uid);
+
+				if (maskConf) {
+					$.mask.close();
+				}
+
+				return self;
+			},
+
+			getOverlay: function() {
+				return overlay;
+			},
+
+			getTrigger: function() {
+				return trigger;
+			},
+
+			getClosers: function() {
+				return closers;
+			},
+
+			isOpened: function()  {
+				return opened;
+			},
+
+			// manipulate start, finish and speeds
+			getConf: function() {
+				return conf;
+			}
+
+		});
+
+		// callbacks
+		$.each("onBeforeLoad,onStart,onLoad,onBeforeClose,onClose".split(","), function(i, name) {
+
+			// configuration
+			if ($.isFunction(conf[name])) {
+				$(self).bind(name, conf[name]);
+			}
+
+			// API
+			self[name] = function(fn) {
+				if (fn) { $(self).bind(name, fn); }
+				return self;
+			};
+		});
+
+		// close button
+		closers = overlay.find(conf.close || ".close");
+
+		if (!closers.length && !conf.close) {
+			closers = $('<a class="close"></a>');
+			overlay.prepend(closers);
+		}
+
+		closers.click(function(e) {
+			self.close(e);
+		});
+
+		// autoload
+		if (conf.load) { self.load(); }
+
+	}
+
+	// jQuery plugin initialization
+	$.fn.overlay = function(conf) {
+
+		// already constructed --> return API
+		var el = this.data("overlay");
+		if (el) { return el; }
+
+		if ($.isFunction(conf)) {
+			conf = {onBeforeLoad: conf};
+		}
+
+		conf = $.extend(true, {}, $.tools.overlay.conf, conf);
+
+		this.each(function() {
+			el = new Overlay($(this), conf);
+			instances.push(el);
+			$(this).data("overlay", el);
+		});
+
+		return conf.api ? el: this;
+	};
+
+})(jQuery);
+
+
+/**
+ * @license
+ * jQuery Tools v1.2.6 Scrollable - New wave UI design
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/scrollable.html
+ *
+ * Since: March 2008
+ * Date: 2011-10-26 11:02
+ */
+(function($) {
+
+	// static constructs
+	$.tools = $.tools || {version: 'v1.2.6'};
+
+	$.tools.scrollable = {
+
+		conf: {
+			activeClass: 'active',
+			circular: false,
+			clonedClass: 'cloned',
+			disabledClass: 'disabled',
+			easing: 'swing',
+			initialIndex: 0,
+			item: '> *',
+			items: '.items',
+			keyboard: true,
+			mousewheel: false,
+			next: '.next',
+			prev: '.prev',
+			size: 1,
+			speed: 400,
+			vertical: false,
+			touch: true,
+			wheelSpeed: 0
+		}
+	};
+
+	// get hidden element's width or height even though it's hidden
+	function dim(el, key) {
+		var v = parseInt(el.css(key), 10);
+		if (v) { return v; }
+		var s = el[0].currentStyle;
+		return s && s.width && parseInt(s.width, 10);
+	}
+
+	function find(root, query) {
+		var el = $(query);
+		return el.length < 2 ? el : root.parent().find(query);
+	}
+
+	var current;
+
+	// constructor
+	function Scrollable(root, conf) {
+
+		// current instance
+		var self = this,
+			 fire = root.add(self),
+			 itemWrap = root.children(),
+			 index = 0,
+			 vertical = conf.vertical;
+
+		if (!current) { current = self; }
+		if (itemWrap.length > 1) { itemWrap = $(conf.items, root); }
+
+
+		// in this version circular not supported when size > 1
+		if (conf.size > 1) { conf.circular = false; }
+
+		// methods
+		$.extend(self, {
+
+			getConf: function() {
+				return conf;
+			},
+
+			getIndex: function() {
+				return index;
+			},
+
+			getSize: function() {
+				return self.getItems().size();
+			},
+
+			getNaviButtons: function() {
+				return prev.add(next);
+			},
+
+			getRoot: function() {
+				return root;
+			},
+
+			getItemWrap: function() {
+				return itemWrap;
+			},
+
+			getItems: function() {
+				return itemWrap.find(conf.item).not("." + conf.clonedClass);
+			},
+
+			move: function(offset, time) {
+				return self.seekTo(index + offset, time);
+			},
+
+			next: function(time) {
+				return self.move(conf.size, time);
+			},
+
+			prev: function(time) {
+				return self.move(-conf.size, time);
+			},
+
+			begin: function(time) {
+				return self.seekTo(0, time);
+			},
+
+			end: function(time) {
+				return self.seekTo(self.getSize() -1, time);
+			},
+
+			focus: function() {
+				current = self;
+				return self;
+			},
+
+			addItem: function(item) {
+				item = $(item);
+
+				if (!conf.circular)  {
+					itemWrap.append(item);
+					next.removeClass("disabled");
+
+				} else {
+					itemWrap.children().last().before(item);
+					itemWrap.children().first().replaceWith(item.clone().addClass(conf.clonedClass));
+				}
+
+				fire.trigger("onAddItem", [item]);
+				return self;
+			},
+
+
+			/* all seeking functions depend on this */
+			seekTo: function(i, time, fn) {
+
+				// ensure numeric index
+				if (!i.jquery) { i *= 1; }
+
+				// avoid seeking from end clone to the beginning
+				if (conf.circular && i === 0 && index == -1 && time !== 0) { return self; }
+
+				// check that index is sane
+				if (!conf.circular && i < 0 || i > self.getSize() || i < -1) { return self; }
+
+				var item = i;
+
+				if (i.jquery) {
+					i = self.getItems().index(i);
+
+				} else {
+					item = self.getItems().eq(i);
+				}
+
+				// onBeforeSeek
+				var e = $.Event("onBeforeSeek");
+				if (!fn) {
+					fire.trigger(e, [i, time]);
+					if (e.isDefaultPrevented() || !item.length) { return self; }
+				}
+
+				var props = vertical ? {top: -item.position().top} : {left: -item.position().left};
+
+				index = i;
+				current = self;
+				if (time === undefined) { time = conf.speed; }
+
+				itemWrap.animate(props, time, conf.easing, fn || function() {
+					fire.trigger("onSeek", [i]);
+				});
+
+				return self;
+			}
+
+		});
+
+		// callbacks
+		$.each(['onBeforeSeek', 'onSeek', 'onAddItem'], function(i, name) {
+
+			// configuration
+			if ($.isFunction(conf[name])) {
+				$(self).bind(name, conf[name]);
+			}
+
+			self[name] = function(fn) {
+				if (fn) { $(self).bind(name, fn); }
+				return self;
+			};
+		});
+
+		// circular loop
+		if (conf.circular) {
+
+			var cloned1 = self.getItems().slice(-1).clone().prependTo(itemWrap),
+				 cloned2 = self.getItems().eq(1).clone().appendTo(itemWrap);
+
+			cloned1.add(cloned2).addClass(conf.clonedClass);
+
+			self.onBeforeSeek(function(e, i, time) {
+
+				if (e.isDefaultPrevented()) { return; }
+
+				/*
+					1. animate to the clone without event triggering
+					2. seek to correct position with 0 speed
+				*/
+				if (i == -1) {
+					self.seekTo(cloned1, time, function()  {
+						self.end(0);
+					});
+					return e.preventDefault();
+
+				} else if (i == self.getSize()) {
+					self.seekTo(cloned2, time, function()  {
+						self.begin(0);
+					});
+				}
+
+			});
+
+			// seek over the cloned item
+
+			// if the scrollable is hidden the calculations for seekTo position
+			// will be incorrect (eg, if the scrollable is inside an overlay).
+			// ensure the elements are shown, calculate the correct position,
+			// then re-hide the elements. This must be done synchronously to
+			// prevent the hidden elements being shown to the user.
+
+			// See: https://github.com/jquerytools/jquerytools/issues#issue/87
+
+			var hidden_parents = root.parents().add(root).filter(function () {
+				if ($(this).css('display') === 'none') {
+					return true;
+				}
+			});
+			if (hidden_parents.length) {
+				hidden_parents.show();
+				self.seekTo(0, 0, function() {});
+				hidden_parents.hide();
+			}
+			else {
+				self.seekTo(0, 0, function() {});
+			}
+
+		}
+
+		// next/prev buttons
+		var prev = find(root, conf.prev).click(function(e) { e.stopPropagation(); self.prev(); }),
+			 next = find(root, conf.next).click(function(e) { e.stopPropagation(); self.next(); });
+
+		if (!conf.circular) {
+			self.onBeforeSeek(function(e, i) {
+				setTimeout(function() {
+					if (!e.isDefaultPrevented()) {
+						prev.toggleClass(conf.disabledClass, i <= 0);
+						next.toggleClass(conf.disabledClass, i >= self.getSize() -1);
+					}
+				}, 1);
+			});
+
+			if (!conf.initialIndex) {
+				prev.addClass(conf.disabledClass);
+			}
+		}
+
+		if (self.getSize() < 2) {
+			prev.add(next).addClass(conf.disabledClass);
+		}
+
+		// mousewheel support
+		if (conf.mousewheel && $.fn.mousewheel) {
+			root.mousewheel(function(e, delta)  {
+				if (conf.mousewheel) {
+					self.move(delta < 0 ? 1 : -1, conf.wheelSpeed || 50);
+					return false;
+				}
+			});
+		}
+
+		// touch event
+		if (conf.touch) {
+			var touch = {};
+
+			itemWrap[0].ontouchstart = function(e) {
+				var t = e.touches[0];
+				touch.x = t.clientX;
+				touch.y = t.clientY;
+			};
+
+			itemWrap[0].ontouchmove = function(e) {
+
+				// only deal with one finger
+				if (e.touches.length == 1 && !itemWrap.is(":animated")) {
+					var t = e.touches[0],
+						 deltaX = touch.x - t.clientX,
+						 deltaY = touch.y - t.clientY;
+
+					self[vertical && deltaY > 0 || !vertical && deltaX > 0 ? 'next' : 'prev']();
+					e.preventDefault();
+				}
+			};
+		}
+
+		if (conf.keyboard)  {
+
+			$(document).bind("keydown.scrollable", function(evt) {
+
+				// skip certain conditions
+				if (!conf.keyboard || evt.altKey || evt.ctrlKey || evt.metaKey || $(evt.target).is(":input")) {
+					return;
+				}
+
+				// does this instance have focus?
+				if (conf.keyboard != 'static' && current != self) { return; }
+
+				var key = evt.keyCode;
+
+				if (vertical && (key == 38 || key == 40)) {
+					self.move(key == 38 ? -1 : 1);
+					return evt.preventDefault();
+				}
+
+				if (!vertical && (key == 37 || key == 39)) {
+					self.move(key == 37 ? -1 : 1);
+					return evt.preventDefault();
+				}
+
+			});
+		}
+
+		// initial index
+		if (conf.initialIndex) {
+			self.seekTo(conf.initialIndex, 0, function() {});
+		}
+	}
+
+
+	// jQuery plugin implementation
+	$.fn.scrollable = function(conf) {
+
+		// already constructed --> return API
+		var el = this.data("scrollable");
+		if (el) { return el; }
+
+		conf = $.extend({}, $.tools.scrollable.conf, conf);
+
+		this.each(function() {
+			el = new Scrollable($(this), conf);
+			$(this).data("scrollable", el);
+		});
+
+		return conf.api ? el: this;
+
+	};
+
+
+})(jQuery);
+
+/**
+ * @license
+ * jQuery Tools v1.2.6 Tabs- The basics of UI design.
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/tabs/
+ *
+ * Since: November 2008
+ * Date: 2011-10-26 11:02
+ */
+(function($) {
+
+	// static constructs
+	$.tools = $.tools || {version: 'v1.2.6'};
+
+	$.tools.tabs = {
+
+		conf: {
+			tabs: 'a',
+			current: 'current',
+			onBeforeClick: null,
+			onClick: null,
+			effect: 'default',
+			initialIndex: 0,
+			event: 'click',
+			rotate: false,
+
+      // slide effect
+      slideUpSpeed: 400,
+      slideDownSpeed: 400,
+
+			// 1.2
+			history: false
+		},
+
+		addEffect: function(name, fn) {
+			effects[name] = fn;
+		}
+
+	};
+
+	var effects = {
+
+		// simple "toggle" effect
+		'default': function(i, done) {
+			this.getPanes().hide().eq(i).show();
+			done.call();
+		},
+
+		/*
+			configuration:
+				- fadeOutSpeed (positive value does "crossfading")
+				- fadeInSpeed
+		*/
+		fade: function(i, done) {
+
+			var conf = this.getConf(),
+				 speed = conf.fadeOutSpeed,
+				 panes = this.getPanes();
+
+			if (speed) {
+				panes.fadeOut(speed);
+			} else {
+				panes.hide();
+			}
+
+			panes.eq(i).fadeIn(conf.fadeInSpeed, done);
+		},
+
+		// for basic accordions
+		slide: function(i, done) {
+		  var conf = this.getConf();
+
+			this.getPanes().slideUp(conf.slideUpSpeed);
+			this.getPanes().eq(i).slideDown(conf.slideDownSpeed, done);
+		},
+
+		/**
+		 * AJAX effect
+		 */
+		ajax: function(i, done)  {
+			this.getPanes().eq(0).load(this.getTabs().eq(i).attr("href"), done);
+		}
+	};
+
+	/**
+	 * Horizontal accordion
+	 *
+	 * @deprecated will be replaced with a more robust implementation
+	*/
+
+	var
+	  /**
+	  *   @type {Boolean}
+	  *
+	  *   Mutex to control horizontal animation
+	  *   Disables clicking of tabs while animating
+	  *   They mess up otherwise as currentPane gets set *after* animation is done
+	  */
+	  animating,
+	  /**
+	  *   @type {Number}
+	  *
+	  *   Initial width of tab panes
+	  */
+	  w;
+
+	$.tools.tabs.addEffect("horizontal", function(i, done) {
+	  if (animating) return;    // don't allow other animations
+
+	  var nextPane = this.getPanes().eq(i),
+	      currentPane = this.getCurrentPane();
+
+		// store original width of a pane into memory
+		w || ( w = this.getPanes().eq(0).width() );
+		animating = true;
+
+		nextPane.show(); // hidden by default
+
+		// animate current pane's width to zero
+    // animate next pane's width at the same time for smooth animation
+    currentPane.animate({width: 0}, {
+      step: function(now){
+        nextPane.css("width", w-now);
+      },
+      complete: function(){
+        $(this).hide();
+        done.call();
+        animating = false;
+     }
+    });
+    // Dirty hack...  onLoad, currentPant will be empty and nextPane will be the first pane
+    // If this is the case, manually run callback since the animation never occured, and reset animating
+    if (!currentPane.length){
+      done.call();
+      animating = false;
+    }
+	});
+
+
+	function Tabs(root, paneSelector, conf) {
+
+		var self = this,
+			 trigger = root.add(this),
+			 tabs = root.find(conf.tabs),
+			 panes = paneSelector.jquery ? paneSelector : root.children(paneSelector),
+			 current;
+
+
+		// make sure tabs and panes are found
+		if (!tabs.length)  { tabs = root.children(); }
+		if (!panes.length) { panes = root.parent().find(paneSelector); }
+		if (!panes.length) { panes = $(paneSelector); }
+
+
+		// public methods
+		$.extend(this, {
+			click: function(i, e) {
+
+				var tab = tabs.eq(i);
+
+				if (typeof i == 'string' && i.replace("#", "")) {
+					tab = tabs.filter("[href*=" + i.replace("#", "") + "]");
+					i = Math.max(tabs.index(tab), 0);
+				}
+
+				if (conf.rotate) {
+					var last = tabs.length -1;
+					if (i < 0) { return self.click(last, e); }
+					if (i > last) { return self.click(0, e); }
+				}
+
+				if (!tab.length) {
+					if (current >= 0) { return self; }
+					i = conf.initialIndex;
+					tab = tabs.eq(i);
+				}
+
+				// current tab is being clicked
+				if (i === current) { return self; }
+
+				// possibility to cancel click action
+				e = e || $.Event();
+				e.type = "onBeforeClick";
+				trigger.trigger(e, [i]);
+				if (e.isDefaultPrevented()) { return; }
+
+				// call the effect
+				effects[conf.effect].call(self, i, function() {
+					current = i;
+					// onClick callback
+					e.type = "onClick";
+					trigger.trigger(e, [i]);
+				});
+
+				// default behaviour
+				tabs.removeClass(conf.current);
+				tab.addClass(conf.current);
+
+				return self;
+			},
+
+			getConf: function() {
+				return conf;
+			},
+
+			getTabs: function() {
+				return tabs;
+			},
+
+			getPanes: function() {
+				return panes;
+			},
+
+			getCurrentPane: function() {
+				return panes.eq(current);
+			},
+
+			getCurrentTab: function() {
+				return tabs.eq(current);
+			},
+
+			getIndex: function() {
+				return current;
+			},
+
+			next: function() {
+				return self.click(current + 1);
+			},
+
+			prev: function() {
+				return self.click(current - 1);
+			},
+
+			destroy: function() {
+				tabs.unbind(conf.event).removeClass(conf.current);
+				panes.find("a[href^=#]").unbind("click.T");
+				return self;
+			}
+
+		});
+
+		// callbacks
+		$.each("onBeforeClick,onClick".split(","), function(i, name) {
+
+			// configuration
+			if ($.isFunction(conf[name])) {
+				$(self).bind(name, conf[name]);
+			}
+
+			// API
+			self[name] = function(fn) {
+				if (fn) { $(self).bind(name, fn); }
+				return self;
+			};
+		});
+
+
+		if (conf.history && $.fn.history) {
+			$.tools.history.init(tabs);
+			conf.event = 'history';
+		}
+
+		// setup click actions for each tab
+		tabs.each(function(i) {
+			$(this).bind(conf.event, function(e) {
+				self.click(i, e);
+				return e.preventDefault();
+			});
+		});
+
+		// cross tab anchor link
+		panes.find("a[href^=#]").bind("click.T", function(e) {
+			self.click($(this).attr("href"), e);
+		});
+
+		// open initial tab
+		if (location.hash && conf.tabs == "a" && root.find("[href=" +location.hash+ "]").length) {
+			self.click(location.hash);
+
+		} else {
+			if (conf.initialIndex === 0 || conf.initialIndex > 0) {
+				self.click(conf.initialIndex);
+			}
+		}
+
+	}
+
+
+	// jQuery plugin implementation
+	$.fn.tabs = function(paneSelector, conf) {
+
+		// return existing instance
+		var el = this.data("tabs");
+		if (el) {
+			el.destroy();
+			this.removeData("tabs");
+		}
+
+		if ($.isFunction(conf)) {
+			conf = {onBeforeClick: conf};
+		}
+
+		// setup conf
+		conf = $.extend({}, $.tools.tabs.conf, conf);
+
+
+		this.each(function() {
+			el = new Tabs($(this), paneSelector, conf);
+			$(this).data("tabs", el);
+		});
+
+		return conf.api ? el: this;
+	};
+
+}) (jQuery);
+
+
+
+/**
+ * @license
+ * jQuery Tools v1.2.6 / Expose - Dim the lights
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/toolbox/expose.html
+ *
+ * Since: Mar 2010
+ * Date: 2011-10-26 11:02
+ */
+(function($) {
+
+	// static constructs
+	$.tools = $.tools || {version: 'v1.2.6'};
+
+	var tool;
+
+	tool = $.tools.expose = {
+
+		conf: {
+			maskId: 'exposeMask',
+			loadSpeed: 'slow',
+			closeSpeed: 'fast',
+			closeOnClick: true,
+			closeOnEsc: true,
+
+			// css settings
+			zIndex: 9998,
+			opacity: 0.8,
+			startOpacity: 0,
+			color: '#fff',
+
+			// callbacks
+			onLoad: null,
+			onClose: null
+		}
+	};
+
+	/* one of the greatest headaches in the tool. finally made it */
+	function viewport() {
+
+		// the horror case
+		if ($.browser.msie) {
+
+			// if there are no scrollbars then use window.height
+			var d = $(document).height(), w = $(window).height();
+
+			return [
+				window.innerWidth || 							// ie7+
+				document.documentElement.clientWidth || 	// ie6
+				document.body.clientWidth, 					// ie6 quirks mode
+				d - w < 20 ? w : d
+			];
+		}
+
+		// other well behaving browsers
+		return [$(document).width(), $(document).height()];
+	}
+
+	function call(fn) {
+		if (fn) { return fn.call($.mask); }
+	}
+
+	var mask, exposed, loaded, config, overlayIndex;
+
+
+	$.mask = {
+
+		load: function(conf, els) {
+
+			// already loaded ?
+			if (loaded) { return this; }
+
+			// configuration
+			if (typeof conf == 'string') {
+				conf = {color: conf};
+			}
+
+			// use latest config
+			conf = conf || config;
+
+			config = conf = $.extend($.extend({}, tool.conf), conf);
+
+			// get the mask
+			mask = $("#" + conf.maskId);
+
+			// or create it
+			if (!mask.length) {
+				mask = $('<div/>').attr("id", conf.maskId);
+				$("body").append(mask);
+			}
+
+			// set position and dimensions
+			var size = viewport();
+
+			mask.css({
+				position:'absolute',
+				top: 0,
+				left: 0,
+				width: size[0],
+				height: size[1],
+				display: 'none',
+				opacity: conf.startOpacity,
+				zIndex: conf.zIndex
+			});
+
+			if (conf.color) {
+				mask.css("backgroundColor", conf.color);
+			}
+
+			// onBeforeLoad
+			if (call(conf.onBeforeLoad) === false) {
+				return this;
+			}
+
+			// esc button
+			if (conf.closeOnEsc) {
+				$(document).bind("keydown.mask", function(e) {
+					if (e.keyCode == 27) {
+						$.mask.close(e);
+					}
+				});
+			}
+
+			// mask click closes
+			if (conf.closeOnClick) {
+				mask.bind("click.mask", function(e)  {
+					$.mask.close(e);
+				});
+			}
+
+			// resize mask when window is resized
+			$(window).bind("resize.mask", function() {
+				$.mask.fit();
+			});
+
+			// exposed elements
+			if (els && els.length) {
+
+				overlayIndex = els.eq(0).css("zIndex");
+
+				// make sure element is positioned absolutely or relatively
+				$.each(els, function() {
+					var el = $(this);
+					if (!/relative|absolute|fixed/i.test(el.css("position"))) {
+						el.css("position", "relative");
+					}
+				});
+
+				// make elements sit on top of the mask
+				exposed = els.css({ zIndex: Math.max(conf.zIndex + 1, overlayIndex == 'auto' ? 0 : overlayIndex)});
+			}
+
+			// reveal mask
+			mask.css({display: 'block'}).fadeTo(conf.loadSpeed, conf.opacity, function() {
+				$.mask.fit();
+				call(conf.onLoad);
+				loaded = "full";
+			});
+
+			loaded = true;
+			return this;
+		},
+
+		close: function() {
+			if (loaded) {
+
+				// onBeforeClose
+				if (call(config.onBeforeClose) === false) { return this; }
+
+				mask.fadeOut(config.closeSpeed, function()  {
+					call(config.onClose);
+					if (exposed) {
+						exposed.css({zIndex: overlayIndex});
+					}
+					loaded = false;
+				});
+
+				// unbind various event listeners
+				$(document).unbind("keydown.mask");
+				mask.unbind("click.mask");
+				$(window).unbind("resize.mask");
+			}
+
+			return this;
+		},
+
+		fit: function() {
+			if (loaded) {
+				var size = viewport();
+				mask.css({width: size[0], height: size[1]});
+			}
+		},
+
+		getMask: function() {
+			return mask;
+		},
+
+		isLoaded: function(fully) {
+			return fully ? loaded == 'full' : loaded;
+		},
+
+		getConf: function() {
+			return config;
+		},
+
+		getExposed: function() {
+			return exposed;
+		}
+	};
+
+	$.fn.mask = function(conf) {
+		$.mask.load(conf);
+		return this;
+	};
+
+	$.fn.expose = function(conf) {
+		$.mask.load(conf, this);
+		return this;
+	};
+
+
+})(jQuery);
+
+/**
+ * @license
+ * jQuery Tools v1.2.6 History "Back button for AJAX apps"
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/toolbox/history.html
+ *
+ * Since: Mar 2010
+ * Date: 2011-10-26 11:02
+ */
+(function($) {
+
+	var hash, iframe, links, inited;
+
+	$.tools = $.tools || {version: 'v1.2.6'};
+
+	$.tools.history = {
+
+		init: function(els) {
+
+			if (inited) { return; }
+
+			// IE
+			if ($.browser.msie && $.browser.version < '8') {
+
+				// create iframe that is constantly checked for hash changes
+				if (!iframe) {
+					iframe = $("<iframe/>").attr("src", "javascript:false;").hide().get(0);
+					$("body").prepend(iframe);
+
+					setInterval(function() {
+						var idoc = iframe.contentWindow.document,
+							 h = idoc.location.hash;
+
+						if (hash !== h) {
+							$(window).trigger("hash", h);
+						}
+					}, 100);
+
+					setIframeLocation(location.hash || '#');
+				}
+
+
+			// other browsers scans for location.hash changes directly without iframe hack
+			} else {
+				setInterval(function() {
+					var h = location.hash;
+					if (h !== hash) {
+						$(window).trigger("hash", h);
+					}
+				}, 100);
+			}
+
+			links = !links ? els : links.add(els);
+
+			els.click(function(e) {
+				var href = $(this).attr("href");
+				if (iframe) { setIframeLocation(href); }
+
+				// handle non-anchor links
+				if (href.slice(0, 1) != "#") {
+					location.href = "#" + href;
+					return e.preventDefault();
+				}
+
+			});
+
+			inited = true;
+		}
+	};
+
+
+	function setIframeLocation(h) {
+		if (h) {
+			var doc = iframe.contentWindow.document;
+			doc.open().close();
+			doc.location.hash = h;
+		}
+	}
+
+	// global histroy change listener
+	$(window).bind("hash", function(e, h)  {
+		if (h) {
+			links.filter(function() {
+			  var href = $(this).attr("href");
+			  return href == h || href == h.replace("#", "");
+			}).trigger("history", [h]);
+		} else {
+			links.eq(0).trigger("history", [h]);
+		}
+
+		hash = h;
+
+	});
+
+
+	// jQuery plugin implementation
+	$.fn.history = function(fn) {
+
+		$.tools.history.init(this);
+
+		// return jQuery
+		return this.bind("history", fn);
+	};
+
+})(jQuery);
+
+
+/**
+ * @license
+ * jQuery Tools v1.2.6 Tooltip - UI essentials
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/tooltip/
+ *
+ * Since: November 2008
+ * Date: 2011-10-26 11:02
+ */
+(function($) {
+	// static constructs
+	$.tools = $.tools || {version: 'v1.2.6'};
+
+	$.tools.tooltip = {
+
+		conf: {
+
+			// default effect variables
+			effect: 'toggle',
+			fadeOutSpeed: "fast",
+			predelay: 0,
+			delay: 30,
+			opacity: 1,
+			tip: 0,
+            fadeIE: false, // enables fade effect in IE
+
+			// 'top', 'bottom', 'right', 'left', 'center'
+			position: ['top', 'center'],
+			offset: [0, 0],
+			relative: false,
+			cancelDefault: true,
+
+			// type to event mapping
+			events: {
+				def: 			"mouseenter,mouseleave",
+				input: 		"focus,blur",
+				widget:		"focus mouseenter,blur mouseleave",
+				tooltip:		"mouseenter,mouseleave"
+			},
+
+			// 1.2
+			layout: '<div/>',
+			tipClass: 'tooltip'
+		},
+
+		addEffect: function(name, loadFn, hideFn) {
+			effects[name] = [loadFn, hideFn];
+		}
+	};
+
+
+	var effects = {
+		toggle: [
+			function(done) {
+				var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
+				if (o < 1) { tip.css({opacity: o}); }
+				tip.show();
+				done.call();
+			},
+
+			function(done) {
+				this.getTip().hide();
+				done.call();
+			}
+		],
+
+		fade: [
+			function(done) {
+				var conf = this.getConf();
+				if (!$.browser.msie || conf.fadeIE) {
+					this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
+				}
+				else {
+					this.getTip().show();
+					done();
+				}
+			},
+			function(done) {
+				var conf = this.getConf();
+				if (!$.browser.msie || conf.fadeIE) {
+					this.getTip().fadeOut(conf.fadeOutSpeed, done);
+				}
+				else {
+					this.getTip().hide();
+					done();
+				}
+			}
+		]
+	};
+
+
+	/* calculate tip position relative to the trigger */
+	function getPosition(trigger, tip, conf) {
+
+
+		// get origin top/left position
+		var top = conf.relative ? trigger.position().top : trigger.offset().top,
+			 left = conf.relative ? trigger.position().left : trigger.offset().left,
+			 pos = conf.position[0];
+
+		top  -= tip.outerHeight() - conf.offset[0];
+		left += trigger.outerWidth() + conf.offset[1];
+
+		// iPad position fix
+		if (/iPad/i.test(navigator.userAgent)) {
+			top -= $(window).scrollTop();
+		}
+
+		// adjust Y
+		var height = tip.outerHeight() + trigger.outerHeight();
+		if (pos == 'center') 	{ top += height / 2; }
+		if (pos == 'bottom') 	{ top += height; }
+
+
+		// adjust X
+		pos = conf.position[1];
+		var width = tip.outerWidth() + trigger.outerWidth();
+		if (pos == 'center') 	{ left -= width / 2; }
+		if (pos == 'left')   	{ left -= width; }
+
+		return {top: top, left: left};
+	}
+
+
+
+	function Tooltip(trigger, conf) {
+
+		var self = this,
+			 fire = trigger.add(self),
+			 tip,
+			 timer = 0,
+			 pretimer = 0,
+			 title = trigger.attr("title"),
+			 tipAttr = trigger.attr("data-tooltip"),
+			 effect = effects[conf.effect],
+			 shown,
+
+			 // get show/hide configuration
+			 isInput = trigger.is(":input"),
+			 isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),
+			 type = trigger.attr("type"),
+			 evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def'];
+
+
+		// check that configuration is sane
+		if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }
+
+		evt = evt.split(/,\s*/);
+		if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; }
+
+
+		// trigger --> show
+		trigger.bind(evt[0], function(e) {
+
+			clearTimeout(timer);
+			if (conf.predelay) {
+				pretimer = setTimeout(function() { self.show(e); }, conf.predelay);
+
+			} else {
+				self.show(e);
+			}
+
+		// trigger --> hide
+		}).bind(evt[1], function(e)  {
+			clearTimeout(pretimer);
+			if (conf.delay)  {
+				timer = setTimeout(function() { self.hide(e); }, conf.delay);
+
+			} else {
+				self.hide(e);
+			}
+
+		});
+
+
+		// remove default title
+		if (title && conf.cancelDefault) {
+			trigger.removeAttr("title");
+			trigger.data("title", title);
+		}
+
+		$.extend(self, {
+
+			show: function(e) {
+
+				// tip not initialized yet
+				if (!tip) {
+
+					// data-tooltip
+					if (tipAttr) {
+						tip = $(tipAttr);
+
+					// single tip element for all
+					} else if (conf.tip) {
+						tip = $(conf.tip).eq(0);
+
+					// autogenerated tooltip
+					} else if (title) {
+						tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
+							.hide().append(title);
+
+					// manual tooltip
+					} else {
+						tip = trigger.next();
+						if (!tip.length) { tip = trigger.parent().next(); }
+					}
+
+					if (!tip.length) { throw "Cannot find tooltip for " + trigger;	}
+				}
+
+			 	if (self.isShown()) { return self; }
+
+			 	// stop previous animation
+			 	tip.stop(true, true);
+
+				// get position
+				var pos = getPosition(trigger, tip, conf);
+
+				// restore title for single tooltip element
+				if (conf.tip) {
+					tip.html(trigger.data("title"));
+				}
+
+				// onBeforeShow
+				e = $.Event();
+				e.type = "onBeforeShow";
+				fire.trigger(e, [pos]);
+				if (e.isDefaultPrevented()) { return self; }
+
+
+				// onBeforeShow may have altered the configuration
+				pos = getPosition(trigger, tip, conf);
+
+				// set position
+				tip.css({position:'absolute', top: pos.top, left: pos.left});
+
+				shown = true;
+
+				// invoke effect
+				effect[0].call(self, function() {
+					e.type = "onShow";
+					shown = 'full';
+					fire.trigger(e);
+				});
+
+
+				// tooltip events
+				var event = conf.events.tooltip.split(/,\s*/);
+
+				if (!tip.data("__set")) {
+
+					tip.unbind(event[0]).bind(event[0], function() {
+						clearTimeout(timer);
+						clearTimeout(pretimer);
+					});
+
+					if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) {
+						tip.unbind(event[1]).bind(event[1], function(e) {
+
+							// being moved to the trigger element
+							if (e.relatedTarget != trigger[0]) {
+								trigger.trigger(evt[1].split(" ")[0]);
+							}
+						});
+					}
+
+					// bind agein for if same tip element
+					if (!conf.tip) tip.data("__set", true);
+				}
+
+				return self;
+			},
+
+			hide: function(e) {
+
+				if (!tip || !self.isShown()) { return self; }
+
+				// onBeforeHide
+				e = $.Event();
+				e.type = "onBeforeHide";
+				fire.trigger(e);
+				if (e.isDefaultPrevented()) { return; }
+
+				shown = false;
+
+				effects[conf.effect][1].call(self, function() {
+					e.type = "onHide";
+					fire.trigger(e);
+				});
+
+				return self;
+			},
+
+			isShown: function(fully) {
+				return fully ? shown == 'full' : shown;
+			},
+
+			getConf: function() {
+				return conf;
+			},
+
+			getTip: function() {
+				return tip;
+			},
+
+			getTrigger: function() {
+				return trigger;
+			}
+
+		});
+
+		// callbacks
+		$.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {
+
+			// configuration
+			if ($.isFunction(conf[name])) {
+				$(self).bind(name, conf[name]);
+			}
+
+			// API
+			self[name] = function(fn) {
+				if (fn) { $(self).bind(name, fn); }
+				return self;
+			};
+		});
+
+	}
+
+
+	// jQuery plugin implementation
+	$.fn.tooltip = function(conf) {
+
+		// return existing instance
+		var api = this.data("tooltip");
+		if (api) { return api; }
+
+		conf = $.extend(true, {}, $.tools.tooltip.conf, conf);
+
+		// position can also be given as string
+		if (typeof conf.position == 'string') {
+			conf.position = conf.position.split(/,?\s/);
+		}
+
+		// install tooltip for each entry in jQuery object
+		this.each(function() {
+			api = new Tooltip($(this), conf);
+			$(this).data("tooltip", api);
+		});
+
+		return conf.api ? api: this;
+	};
+
+}) (jQuery);
diff --git a/media/js/jquery.tools.min.js b/media/js/jquery.tools.min.js
new file mode 100644
index 0000000..5e047b1
--- /dev/null
+++ b/media/js/jquery.tools.min.js
@@ -0,0 +1,11 @@
+/*!
+ * jQuery Tools v1.2.6 - The missing UI library for the Web
+ *
+ * overlay/overlay.js
+ *
+ * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+ *
+ * http://flowplayer.org/tools/
+ *
+ */
+(function(a){a.tools=a.tools||{version:"v1.2.6"},a.tools.overlay={addEffect:function(a,b,d){c[a]=[b,d]},conf:{close:null,closeOnClick:!0,closeOnEsc:!0,closeSpeed:"fast",effect:"default",fixed:!a.browser.msie||a.browser.version>6,left:"center",load:!1,mask:null,oneInstance:!0,speed:"normal",target:null,top:"10%"}};var b=[],c={};a.tools.overlay.addEffect("default",function(b,c){var d=this.getConf(),e=a(window);d.fixed||(b.top+=e.scrollTop(),b.left+=e.scrollLeft()),b.position=d.fixed?"fixed":"absolute",this.getOverlay().css(b).fadeIn(d.speed,c)},function(a){this.getOverlay().fadeOut(this.getConf().closeSpeed,a)});function d(d,e){var f=this,g=d.add(f),h=a(window),i,j,k,l=a.tools.expose&&(e.mask||e.expose),m=Math.random().toString().slice(10);l&&(typeof l=="string"&&(l={color:l}),l.closeOnClick=l.closeOnEsc=!1);var n=e.target||d.attr("rel");j=n?a(n):null||d;if(!j.length)throw"Could not find Overlay: "+n;d&&d.index(j)==-1&&d.click(function(a){f.load(a);return a.preventDefault()}),
 a.extend(f,{load:function(d){if(f.isOpened())return f;var i=c[e.effect];if(!i)throw"Overlay: cannot find effect : \""+e.effect+"\"";e.oneInstance&&a.each(b,function(){this.close(d)}),d=d||a.Event(),d.type="onBeforeLoad",g.trigger(d);if(d.isDefaultPrevented())return f;k=!0,l&&a(j).expose(l);var n=e.top,o=e.left,p=j.outerWidth({margin:!0}),q=j.outerHeight({margin:!0});typeof n=="string"&&(n=n=="center"?Math.max((h.height()-q)/2,0):parseInt(n,10)/100*h.height()),o=="center"&&(o=Math.max((h.width()-p)/2,0)),i[0].call(f,{top:n,left:o},function(){k&&(d.type="onLoad",g.trigger(d))}),l&&e.closeOnClick&&a.mask.getMask().one("click",f.close),e.closeOnClick&&a(document).bind("click."+m,function(b){a(b.target).parents(j).length||f.close(b)}),e.closeOnEsc&&a(document).bind("keydown."+m,function(a){a.keyCode==27&&f.close(a)});return f},close:function(b){if(!f.isOpened())return f;b=b||a.Event(),b.type="onBeforeClose",g.trigger(b);if(!b.isDefaultPrevented()){k=!1,c[e.effect][1].call(f,funct
 ion(){b.type="onClose",g.trigger(b)}),a(document).unbind("click."+m).unbind("keydown."+m),l&&a.mask.close();return f}},getOverlay:function(){return j},getTrigger:function(){return d},getClosers:function(){return i},isOpened:function(){return k},getConf:function(){return e}}),a.each("onBeforeLoad,onStart,onLoad,onBeforeClose,onClose".split(","),function(b,c){a.isFunction(e[c])&&a(f).bind(c,e[c]),f[c]=function(b){b&&a(f).bind(c,b);return f}}),i=j.find(e.close||".close"),!i.length&&!e.close&&(i=a("<a class=\"close\"></a>"),j.prepend(i)),i.click(function(a){f.close(a)}),e.load&&f.load()}a.fn.overlay=function(c){var e=this.data("overlay");if(e)return e;a.isFunction(c)&&(c={onBeforeLoad:c}),c=a.extend(!0,{},a.tools.overlay.conf,c),this.each(function(){e=new d(a(this),c),b.push(e),a(this).data("overlay",e)});return c.api?e:this}})(jQuery);
diff --git a/media/js/overlayhelpers.js b/media/js/overlayhelpers.js
new file mode 100644
index 0000000..0137d5f
--- /dev/null
+++ b/media/js/overlayhelpers.js
@@ -0,0 +1,595 @@
+/*****************
+
+   jQuery Tools overlay helpers.
+
+   Copyright  2010, The Plone Foundation
+   Licensed under the GPL, see LICENSE.txt for details.
+
+*****************/
+
+/*jslint browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, newcap: true, immed: true, regexp: false, white:true */
+/*global jQuery, ajax_noresponse_message, window */
+
+// Name space object for pipbox
+var pb = {spinner: {}, overlay_counter: 1};
+
+jQuery.tools.overlay.conf.oneInstance = false;
+
+(function($) {
+    // override jqt default effect to take account of position
+    // of parent elements
+    jQuery.tools.overlay.addEffect('default',
+        function(pos, onLoad) {
+            var conf = this.getConf(),
+                 w = $(window),
+                 ovl = this.getOverlay(),
+                 op = ovl.parent().offsetParent().offset();
+
+            if (!conf.fixed)  {
+                pos.top += w.scrollTop() - op.top;
+                pos.left += w.scrollLeft() - op.left;
+            }
+
+            pos.position = conf.fixed ? 'fixed' : 'absolute';
+            ovl.css(pos).fadeIn(conf.speed, onLoad);
+
+        }, function(onClose) {
+            this.getOverlay().fadeOut(this.getConf().closeSpeed, onClose);
+        }
+    );
+
+    pb.spinner.show = function () {
+        $('body').css('cursor', 'wait');
+    };
+    pb.spinner.hide = function () {
+        $('body').css('cursor', '');
+    };
+}) (jQuery);
+
+
+
+jQuery(function ($) {
+
+    /******
+        $.fn.prepOverlay jQuery plugin to inject overlay target into DOM and
+        annotate it with the data we'll need in order to display it.
+    ******/
+    $.fn.prepOverlay = function (pba) {
+        return this.each(function () {
+            var o, pbo, config, onBeforeLoad, onLoad, src, parts;
+
+            o = $(this);
+
+            // copy options so that it's not just a reference
+            // to the parameter.
+            pbo = $.extend(true, {'width':'60%'}, pba);
+
+            // set overlay configuration
+            config = pbo.config || {};
+
+            // set onBeforeLoad handler
+            onBeforeLoad = pb[pbo.subtype];
+            if (onBeforeLoad) {
+                config.onBeforeLoad = onBeforeLoad;
+            }
+            onLoad = config.onLoad;
+            config.onLoad = function () {
+                if (onLoad) {
+                    onLoad.apply(this, arguments);
+                }
+                pb.fi_focus(this.getOverlay());
+            };
+
+            // be promiscuous, pick up the url from
+            // href, src or action attributes
+            src = o.attr('href') || o.attr('src') || o.attr('action');
+
+            // translate url with config specifications
+            if (pbo.urlmatch) {
+                src = src.replace(new RegExp(pbo.urlmatch), pbo.urlreplace);
+            }
+
+            if (pbo.subtype === 'inline') {
+                // we're going to let tools' overlay do all the real
+                // work. Just get the markers in place.
+                src = src.replace(/^.+#/, '#');
+                $("[id='" + src.replace('#', '') + "']")
+                    .addClass('overlay');
+                o.removeAttr('href').attr('rel', src);
+                // use overlay on the source (clickable) element
+                o.overlay();
+            } else {
+                // save various bits of information from the pbo options,
+                // and enable the overlay.
+
+                // this is not inline, so in one fashion or another
+                // we'll be loading it via the beforeLoad callback.
+                // create a unique id for a target element
+                pbo.nt = 'pb_' + pb.overlay_counter;
+                pb.overlay_counter += 1;
+
+                pbo.selector = pbo.filter || pbo.selector;
+                if (!pbo.selector) {
+                    // see if one's been supplied in the src
+                    parts = src.split(' ');
+                    src = parts.shift();
+                    pbo.selector = parts.join(' ');
+                }
+
+                pbo.src = src;
+                pbo.config = config;
+                pbo.source = o;
+
+                // remove any existing overlay and overlay handler
+                pb.remove_overlay(o);
+
+                // save options on trigger element
+                o.data('pbo', pbo);
+
+                // mark the source with a rel attribute so we can find
+                // the overlay, and a special class for styling
+                o.attr('rel', '#' + pbo.nt);
+                o.addClass('link-overlay');
+
+                // for some subtypes, we're setting click handlers
+                // and attaching overlay to the target element. That's
+                // so we'll know the dimensions early.
+                // Others, like iframe, just use overlay.
+                switch (pbo.subtype) {
+                case 'image':
+                    o.click(pb.image_click);
+                    break;
+                case 'ajax':
+                    o.click(pb.ajax_click);
+                    break;
+                case 'iframe':
+                    pb.create_content_div(pbo);
+                    o.overlay(config);
+                    break;
+                default:
+                    throw "Unsupported overlay type";
+                }
+
+                // in case the click source wasn't
+                // already a link.
+                o.css('cursor', 'pointer');
+            }
+        });
+    };
+
+
+    /******
+        pb.remove_overlay
+        Remove the overlay and handler associated with a jquery wrapped
+        trigger object
+    ******/
+    pb.remove_overlay = function (o) {
+        var old_data = o.data('pbo');
+        if (old_data) {
+            switch (old_data.subtype) {
+            case 'image':
+                o.unbind('click', pb.image_click);
+                break;
+            case 'ajax':
+                o.unbind('click', pb.ajax_click);
+                break;
+            default:
+                // it's probably the jqt overlay click handler,
+                // but we don't know the handler and are forced
+                // to do a generic unbind of click handlers.
+                o.unbind('click');
+            }
+            if (old_data.nt) {
+                $('#' + old_data.nt).remove();
+            }
+        }
+    };
+
+
+    /******
+        pb.create_content_div
+        create a div to act as an overlay; append it to
+        the body; return it
+    ******/
+    pb.create_content_div = function (pbo, trigger) {
+        var content,
+            top,
+            pbw = pbo.width;
+
+        content = $(
+            '<div id="' + pbo.nt +
+            '" class="overlay overlay-' + pbo.subtype +
+            ' ' + (pbo.cssclass || '') +
+            '"><div class="close"><span>Close</span></div></div>'
+        );
+
+        content.data('pbo', pbo);
+
+        // if a width option is specified, set it on the overlay div,
+        // computing against the window width if a % was specified.
+        if (pbw) {
+            if (pbw.indexOf('%') > 0) {
+                content.width(parseInt(pbw, 10) / 100 * $(window).width());
+            } else {
+                content.width(pbw);
+            }
+        }
+
+        // add the target element at the end of the body.
+        if (trigger) {
+            trigger.after(content);
+        } else {
+            content.appendTo($("body"));
+        }
+
+        return content;
+    };
+
+
+    /******
+        pb.image_click
+        click handler for image loads
+    ******/
+    pb.image_click = function (event) {
+        var ethis, content, api, img, el, pbo;
+
+        ethis = $(this);
+        pbo = ethis.data('pbo');
+
+        // find target container
+        content = $(ethis.attr('rel'));
+        if (!content.length) {
+            content = pb.create_content_div(pbo);
+            content.overlay(pbo.config);
+        }
+        api = content.overlay();
+
+        // is the image loaded yet?
+        if (content.find('img').length === 0) {
+            // load the image.
+            if (pbo.src) {
+                pb.spinner.show();
+
+                // create the image and stuff it
+                // into our target
+                img = new Image();
+                img.src = pbo.src;
+                el = $(img);
+                content.append(el.addClass('pb-image'));
+
+                // Now, we'll cause the overlay to
+                // load when the image is loaded.
+                el.load(function () {
+                    pb.spinner.hide();
+                    api.load();
+                });
+
+            }
+        } else {
+            api.load();
+        }
+
+        return false;
+    };
+
+
+    /******
+        pb.fi_focus
+        First-input focus inside $ selection.
+    ******/
+    pb.fi_focus = function (jqo) {
+        if (! jqo.find("form div.error :input:first").focus().length) {
+            jqo.find("form :input:visible:first").focus();
+        }
+    };
+
+
+    /******
+        pb.ajax_error_recover
+        jQuery's ajax load function does not load error responses.
+        This routine returns the cooked error response.
+    ******/
+    pb.ajax_error_recover = function (responseText, selector) {
+        var tcontent = $('<div/>')
+            .append(responseText.replace(/<script(.|\s)*?\/script>/gi, ""));
+        return selector ? tcontent.find(selector) : tcontent;
+    };
+
+
+    /******
+        pb.add_ajax_load
+        Adds a hidden ajax_load input to form
+    ******/
+    pb.add_ajax_load = function (form) {
+        if (form.find('input[name=ajax_load]').length === 0) {
+            form.prepend($('<input type="hidden" name="ajax_load" value="' +
+                (new Date().getTime()) +
+                '" />'));
+        }
+    };
+
+    /******
+        pb.prep_ajax_form
+        Set up form with ajaxForm, including success and error handlers.
+    ******/
+    pb.prep_ajax_form = function (form) {
+        var ajax_parent = form.closest('.pb-ajax'),
+            data_parent = ajax_parent.closest('.overlay-ajax'),
+            pbo = data_parent.data('pbo'),
+            formtarget = pbo.formselector,
+            closeselector = pbo.closeselector,
+            beforepost = pbo.beforepost,
+            afterpost = pbo.afterpost,
+            noform = pbo.noform,
+            api = data_parent.overlay(),
+            selector = pbo.selector,
+            options = {};
+
+        options.beforeSerialize = function () {
+            pb.spinner.show();
+        };
+
+        if (beforepost) {
+            options.beforeSubmit = function (arr, form, options) {
+                return beforepost(form, arr, options);
+            };
+        }
+        options.success = function (responseText, statusText, xhr, form) {
+            $(document).trigger('formOverlayStart', [this, responseText, statusText, xhr, form]);
+            // success comes in many forms, some of which are errors;
+            //
+
+            var el, myform, success, target;
+
+            success = statusText === "success" || statusText === "notmodified";
+
+            if (! success) {
+                // The responseText parameter is actually xhr
+                responseText = responseText.responseText;
+            }
+            // strip inline script tags
+            responseText = responseText.replace(/<script(.|\s)*?\/script>/gi, "");
+
+            // create a div containing the optionally filtered response
+            el = $('<div />').append(
+                selector ?
+                    // a lesson learned from the jQuery source: $(responseText)
+                    // will not work well unless responseText is well-formed;
+                    // appending to a div is more robust, and automagically
+                    // removes the html/head/body outer tagging.
+                    $('<div />').append(responseText).find(selector)
+                    :
+                    responseText
+                );
+
+            // afterpost callback
+            if (success && afterpost) {
+                afterpost(el, data_parent);
+            }
+
+            myform = el.find(formtarget);
+            if (success && myform.length) {
+                ajax_parent.empty().append(el);
+                pb.fi_focus(ajax_parent);
+
+                pb.add_ajax_load(myform);
+                // attach submit handler with the same options
+                myform.ajaxForm(options);
+
+                // attach close to element id'd by closeselector
+                if (closeselector) {
+                    el.find(closeselector).click(function (event) {
+                        api.close();
+                        return false;
+                    });
+                }
+                $(document).trigger('formOverlayLoadSuccess', [this, myform, api, pb, ajax_parent]);
+            } else {
+                // there's no form in our new content or there's been an error
+                if (success) {
+                    if (typeof(noform) === "function") {
+                        // get action from callback
+                        noform = noform(this);
+                    }
+                } else {
+                    noform = statusText;
+                }
+
+
+                switch (noform) {
+                case 'close':
+                    api.close();
+                    break;
+                case 'reload':
+                    api.close();
+                    pb.spinner.show();
+                    // location.reload results in a repost
+                    // dialog in some browsers; very unlikely to
+                    // be what we want.
+                    location.replace(location.href);
+                    break;
+                case 'redirect':
+                    api.close();
+                    pb.spinner.show();
+                    target = pbo.redirect;
+                    if (typeof(target) === "function") {
+                        // get target from callback
+                        target = target(this, responseText);
+                    }
+                    location.replace(target);
+                    break;
+                default:
+                    if (el.children()) {
+                        // show what we've got
+                        ajax_parent.empty().append(el);
+                    } else {
+                        api.close();
+                    }
+                }
+                $(document).trigger('formOverlayLoadFailure', [this, myform, api, pb, ajax_parent, noform]);
+            }
+            pb.spinner.hide();
+        };
+        // error and success callbacks are the same
+        options.error = options.success;
+
+        pb.add_ajax_load(form);
+        form.ajaxForm(options);
+    };
+
+
+    /******
+        pb.ajax_click
+        Click handler for ajax sources. The job of this routine
+        is to do the ajax load of the overlay element, then
+        call the JQT overlay loader.
+    ******/
+    pb.ajax_click = function (event) {
+        var ethis = $(this),
+            pbo,
+            content,
+            api,
+            src,
+            el,
+            selector,
+            formtarget,
+            closeselector,
+            sep;
+
+        e = $.Event();
+    	e.type = "beforeAjaxClickHandled";
+        $(document).trigger(e, [this, event]);
+        if (e.isDefaultPrevented()) { return; }
+
+        pbo = ethis.data('pbo');
+
+        content = pb.create_content_div(pbo, ethis);
+        // pbo.config.top = $(window).height() * 0.1 - ethis.offsetParent().offset().top;
+        content.overlay(pbo.config, ethis);
+        api = content.overlay();
+        src = pbo.src;
+        selector = pbo.selector;
+        formtarget = pbo.formselector;
+        closeselector = pbo.closeselector;
+
+        pb.spinner.show();
+
+        // prevent double click warning for this form
+        $(this).find("input.submitting").removeClass('submitting');
+
+        el = $('<div class="pb-ajax" />');
+        if (api.getConf().fixed) {
+            // don't let it be over 75% of the viewport's height
+            el.css('max-height', Math.floor($(window).height() * 0.75));
+        }
+        content.append(el);
+
+        // affix a random query argument to prevent
+        // loading from browser cache
+        sep = (src.indexOf('?') >= 0) ? '&': '?';
+        src += sep + "ajax_load=" + (new Date().getTime());
+
+        // add selector, if any
+        if (selector) {
+            src += ' ' + selector;
+        }
+
+        // set up callback to be used whenever new contents are loaded
+        // into the overlay, to prepare links and forms to stay within
+        // the overlay
+        el[0].handle_load_inside_overlay = function(responseText, errorText) {
+            var el = $(this);
+
+            if (errorText === 'error') {
+                el.append(pb.ajax_error_recover(responseText, selector));
+            } else if (!responseText.length) {
+                el.append(ajax_noresponse_message || 'No response from server.');
+            }
+
+            // a non-semantic div here will make sure we can
+            // do enough formatting.
+            el.wrapInner('<div />');
+
+            // add the submit handler if we've a formtarget
+            if (formtarget) {
+                var target = el.find(formtarget);
+                if (target.length > 0) {
+                    pb.prep_ajax_form(target);
+                }
+            }
+
+            // if a closeselector has been specified, tie it to the overlay's
+            // close method via closure
+            if (closeselector) {
+                el.find(closeselector).click(function (event) {
+                    api.close();
+                    return false;
+                });
+            }
+
+            // This may be a complex form.
+            if ($.fn.ploneTabInit) {
+                el.ploneTabInit();
+            }
+
+            // remove element on close so that it doesn't congest the DOM
+            api.onClose = function () {
+                content.remove();
+            };
+            $(document).trigger('loadInsideOverlay', [this, responseText, errorText, api]);
+        }
+
+        // and load the div
+        el.load(src, null, function (responseText, errorText) {
+            // post-process the overlay contents
+            el[0].handle_load_inside_overlay.apply(this, [responseText, errorText]);
+
+            // Now, it's all ready to display; hide the
+            // spinner and call JQT overlay load.
+            pb.spinner.hide();
+            api.load();
+
+            return true;
+        });
+
+        // don't do the default action
+        return false;
+    };
+
+
+    /******
+        pb.iframe
+        onBeforeLoad handler for iframe overlays.
+
+        Note that the spinner is handled a little differently
+        so that we can keep it displayed while the iframe's
+        content is loading.
+    ******/
+    pb.iframe = function () {
+        var content, pbo;
+
+        pb.spinner.show();
+
+        content = this.getOverlay();
+        pbo = this.getTrigger().data('pbo');
+
+        if (content.find('iframe').length === 0 && pbo.src) {
+            content.append(
+                '<iframe src="' + pbo.src + '" width="' +
+                 content.width() + '" height="' + content.height() +
+                 '" onload="pb.spinner.hide()"/>'
+            );
+        } else {
+            pb.spinner.hide();
+        }
+        return true;
+    };
+
+    // $('.newsImageContainer a')
+    //     .prepOverlay({
+    //          subtype: 'image',
+    //          urlmatch: '/image_view_fullscreen$',
+    //          urlreplace: '_preview'
+    //         });
+
+});



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]