/**
 * Boxy 0.1.4 - Facebook-style dialog, with frills
 *
 * (c) 2008 Jason Frame
 * Licensed under the MIT License (LICENSE)
 */

/*
 * jQuery plugin
 *
 * Options:
 * message: confirmation message for form submit hook (default: "Please confirm:")
 *
 * Any other options - e.g. 'clone' - will be passed onto the boxy constructor (or
 * Boxy.load for AJAX operations)
 */
jQuery.fn.boxy = function(options) {
 options = options || {};
 return this.each(function() {
 var node = this.nodeName.toLowerCase(), self = this;
 if (node == 'a') {
 jQuery(this).click(function() {
 var active = Boxy.linkedTo(this),
 href = this.getAttribute('href'),
 localOptions = jQuery.extend({actuator: this, title: this.title}, options);

 if (active) {
 active.show();
 } else if (href.indexOf('#') >= 0) {
 var content = jQuery(href.substr(href.indexOf('#'))),
 newContent = content.clone(true);
 content.remove();
 localOptions.unloadOnHide = false;
 new Boxy(newContent, localOptions);
 } else { // fall back to AJAX; could do with a same-origin check
 if (!localOptions.cache) localOptions.unloadOnHide = true;
 Boxy.load(this.href, localOptions);
 }

 return false;
 });
 } else if (node == 'form') {
 jQuery(this).bind('submit.boxy', function() {
 Boxy.confirm(options.message || 'Prosím potvrdiť.', function() {
 jQuery(self).unbind('submit.boxy').submit();
 });
 return false;
 });
 }
 });
};

//
// Boxy Class

function Boxy(element, options) {

 this.boxy = jQuery(Boxy.WRAPPER);
 jQuery.data(this.boxy[0], 'boxy', this);

 this.visible = false;
 this.options = jQuery.extend({}, Boxy.DEFAULTS, options || {});

 if (this.options.modal) {
 this.options = jQuery.extend(this.options, {center: true, draggable: false});
 }

 // options.actuator == DOM element that opened this boxy
 // association will be automatically deleted when this boxy is remove()d
 if (this.options.actuator) {
 jQuery.data(this.options.actuator, 'active.boxy', this);
 }

 this.setContent(element || "<div></div>");
 this._setupTitleBar();

 this.boxy.css('display', 'none').appendTo(document.body);
 this.toTop();

 if (this.options.fixed) {
 if (jQuery.browser.msie && jQuery.browser.version < 7) {
 this.options.fixed = false; // IE6 doesn't support fixed positioning
 } else {
 this.boxy.addClass('fixed');
 }
 }

 if (this.options.center && Boxy._u(this.options.x, this.options.y)) {
 this.center();
 } else {
 this.moveTo(
 Boxy._u(this.options.x) ? this.options.x : Boxy.DEFAULT_X,
 Boxy._u(this.options.y) ? this.options.y : Boxy.DEFAULT_Y
 );
 }

 if (this.options.show) this.show();

};

Boxy.EF = function() {};

jQuery.extend(Boxy, {

 WRAPPER: "<table cellspacing='0' cellpadding='0' border='0' class='boxy-wrapper'>" +
 "<tr><td class='top-left'></td><td class='top'></td><td class='top-right'></td></tr>" +
 "<tr><td class='left'></td><td class='boxy-inner'></td><td class='right'></td></tr>" +
 "<tr><td class='bottom-left'></td><td class='bottom'></td><td class='bottom-right'></td></tr>" +
 "</table>",

 DEFAULTS: {
 title: null, // titlebar text. titlebar will not be visible if not set.
 closeable: true, // display close link in titlebar?
 draggable: true, // can this dialog be dragged?
 clone: false, // clone content prior to insertion into dialog?
 actuator: null, // element which opened this dialog
 center: true, // center dialog in viewport?
 show: true, // show dialog immediately?
 modal: false, // make dialog modal?
 fixed: true, // use fixed positioning, if supported? absolute positioning used otherwise
 closeText: 'bezár', // text to use for default close link
 unloadOnHide: false, // should this dialog be removed from the DOM after being hidden?
 clickToFront: false, // bring dialog to foreground on any click (not just titlebar)?
 behaviours: Boxy.EF, // function used to apply behaviours to all content embedded in dialog.
 afterDrop: Boxy.EF, // callback fired after dialog is dropped. executes in context of Boxy instance.
 afterShow: Boxy.EF, // callback fired after dialog becomes visible. executes in context of Boxy instance.
 afterHide: Boxy.EF, // callback fired after dialog is hidden. executed in context of Boxy instance.
 beforeUnload: Boxy.EF // callback fired after dialog is unloaded. executed in context of Boxy instance.
 },

 DEFAULT_X: 50,
 DEFAULT_Y: 50,
 zIndex: 1337,
 dragConfigured: false, // only set up one drag handler for all boxys
 resizeConfigured: false,
 dragging: null,

 // load a URL and display in boxy
 // url - url to load
 // options keys (any not listed below are passed to boxy constructor)
 // type: HTTP method, default: GET
 // cache: cache retrieved content? default: false
 // filter: jQuery selector used to filter remote content
 load: function(url, options) {

 options = options || {};

 var ajax = {
 url: url, type: 'GET', dataType: 'html', cache: false, success: function(html) {
 html = jQuery(html);
 if (options.filter) html = jQuery(options.filter, html);
 new Boxy(html, options);
 }
 };

 jQuery.each(['type', 'cache'], function() {
 if (this in options) {
 ajax[this] = options[this];
 delete options[this];
 }
 });

 jQuery.ajax(ajax);

 },

 // allows you to get a handle to the containing boxy instance of any element
 // e.g. <a href='#' onclick='alert(Boxy.get(this));'>inspect!</a>.
 // this returns the actual instance of the boxy 'class', not just a DOM element.
 // Boxy.get(this).hide() would be valid, for instance.
 get: function(ele) {
 var p = jQuery(ele).parents('.boxy-wrapper');
 return p.length ? jQuery.data(p[0], 'boxy') : null;
 },

 // returns the boxy instance which has been linked to a given element via the
 // 'actuator' constructor option.
 linkedTo: function(ele) {
 return jQuery.data(ele, 'active.boxy');
 },

 // displays an alert box with a given message, calling optional callback
 // after dismissal.
 alert: function(message, callback, options) {
 return Boxy.ask(message, ['OK'], callback, options);
 },

 // displays an alert box with a given message, calling after callback iff
 // user selects OK.
 confirm: function(message, after, options) {
 return Boxy.ask(message, ['OK', 'Cancel'], function(response) {
 if (response == 'OK') after();
 }, options);
 },

 // asks a question with multiple responses presented as buttons
 // selected item is returned to a callback method.
 // answers may be either an array or a hash. if it's an array, the
 // the callback will received the selected value. if it's a hash,
 // you'll get the corresponding key.
 ask: function(question, answers, callback, options) {

 options = jQuery.extend({modal: true, closeable: false},
 options || {},
 {show: true, unloadOnHide: true});

 var body = jQuery('<div></div>').append(jQuery('<div class="question"></div>').html(question));

 // ick
 var map = {}, answerStrings = [];
 if (answers instanceof Array) {
 for (var i = 0; i < answers.length; i++) {
 map[answers[i]] = answers[i];
 answerStrings.push(answers[i]);
 }
 } else {
 for (var k in answers) {
 map[answers[k]] = k;
 answerStrings.push(answers[k]);
 }
 }

 var buttons = jQuery('<form class="answers"></form>');
 buttons.html(jQuery.map(answerStrings, function(v) {
 return "<input type='button' value='" + v + "' />";
 }).join(' '));

 jQuery('input[type=button]', buttons).click(function() {
 var clicked = this;
 Boxy.get(this).hide(function() {
 if (callback) callback(map[clicked.value]);
 });
 });

 body.append(buttons);

 new Boxy(body, options);

 },

 // returns true if a modal boxy is visible, false otherwise
 isModalVisible: function() {
 return jQuery('.boxy-modal-blackout').length > 0;
 },

 _u: function() {
 for (var i = 0; i < arguments.length; i++)
 if (typeof arguments[i] != 'undefined') return false;
 return true;
 },

 _handleResize: function(evt) {
 var d = jQuery(document);
 jQuery('.boxy-modal-blackout').css('display', 'none').css({
 width: d.width(), height: d.height()
 }).css('display', 'block');
 },

 _handleDrag: function(evt) {
 var d;
 if (d = Boxy.dragging) {
 d[0].boxy.css({left: evt.pageX - d[1], top: evt.pageY - d[2]});
 }
 },

 _nextZ: function() {
 return Boxy.zIndex++;
 },

 _viewport: function() {
 var d = document.documentElement, b = document.body, w = window;
 return jQuery.extend(
 jQuery.browser.msie ?
 { left: b.scrollLeft || d.scrollLeft, top: b.scrollTop || d.scrollTop } :
 { left: w.pageXOffset, top: w.pageYOffset },
 !Boxy._u(w.innerWidth) ?
 { width: w.innerWidth, height: w.innerHeight } :
 (!Boxy._u(d) && !Boxy._u(d.clientWidth) && d.clientWidth != 0 ?
 { width: d.clientWidth, height: d.clientHeight } :
 { width: b.clientWidth, height: b.clientHeight }) );
 }

});

Boxy.prototype = {

 // Returns the size of this boxy instance without displaying it.
 // Do not use this method if boxy is already visible, use getSize() instead.
 estimateSize: function() {
 this.boxy.css({visibility: 'hidden', display: 'block'});
 var dims = this.getSize();
 this.boxy.css('display', 'none').css('visibility', 'visible');
 return dims;
 },

 // Returns the dimensions of the entire boxy dialog as [width,height]
 getSize: function() {
 return [this.boxy.width(), this.boxy.height()];
 },

 // Returns the dimensions of the content region as [width,height]
 getContentSize: function() {
 var c = this.getContent();
 return [c.width(), c.height()];
 },

 // Returns the position of this dialog as [x,y]
 getPosition: function() {
 var b = this.boxy[0];
 return [b.offsetLeft, b.offsetTop];
 },

 // Returns the center point of this dialog as [x,y]
 getCenter: function() {
 var p = this.getPosition();
 var s = this.getSize();
 return [Math.floor(p[0] + s[0] / 2), Math.floor(p[1] + s[1] / 2)];
 },

 // Returns a jQuery object wrapping the inner boxy region.
 // Not much reason to use this, you're probably more interested in getContent()
 getInner: function() {
 return jQuery('.boxy-inner', this.boxy);
 },

 // Returns a jQuery object wrapping the boxy content region.
 // This is the user-editable content area (i.e. excludes titlebar)
 getContent: function() {
 return jQuery('.boxy-content', this.boxy);
 },

 // Replace dialog content
 setContent: function(newContent) {
 newContent = jQuery(newContent).css({display: 'block'}).addClass('boxy-content');
 if (this.options.clone) newContent = newContent.clone(true);
 this.getContent().remove();
 this.getInner().append(newContent);
 this._setupDefaultBehaviours(newContent);
 this.options.behaviours.call(this, newContent);
 return this;
 },

 // Move this dialog to some position, funnily enough
 moveTo: function(x, y) {
 this.moveToX(x).moveToY(y);
 return this;
 },

 // Move this dialog (x-coord only)
 moveToX: function(x) {
 if (typeof x == 'number') this.boxy.css({left: x});
 else this.centerX();
 return this;
 },

 // Move this dialog (y-coord only)
 moveToY: function(y) {
 if (typeof y == 'number') this.boxy.css({top: y});
 else this.centerY();
 return this;
 },

 // Move this dialog so that it is centered at (x,y)
 centerAt: function(x, y) {
 var s = this[this.visible ? 'getSize' : 'estimateSize']();
 if (typeof x == 'number') this.moveToX(x - s[0] / 2);
 if (typeof y == 'number') this.moveToY(y - s[1] / 2);
 return this;
 },

 centerAtX: function(x) {
 return this.centerAt(x, null);
 },

 centerAtY: function(y) {
 return this.centerAt(null, y);
 },

 // Center this dialog in the viewport
 // axis is optional, can be 'x', 'y'.
 center: function(axis) {
 var v = Boxy._viewport();
 var o = this.options.fixed ? [0, 0] : [v.left, v.top];
 if (!axis || axis == 'x') this.centerAt(o[0] + v.width / 2, null);
 if (!axis || axis == 'y') this.centerAt(null, o[1] + v.height / 2);
 return this;
 },

 // Center this dialog in the viewport (x-coord only)
 centerX: function() {
 return this.center('x');
 },

 // Center this dialog in the viewport (y-coord only)
 centerY: function() {
 return this.center('y');
 },

 // Resize the content region to a specific size
 resize: function(width, height, after) {
 if (!this.visible) return;
 var bounds = this._getBoundsForResize(width, height);
 this.boxy.css({left: bounds[0], top: bounds[1]});
 this.getContent().css({width: bounds[2], height: bounds[3]});
 if (after) after(this);
 return this;
 },

 // Tween the content region to a specific size
 tween: function(width, height, after) {
 if (!this.visible) return;
 var bounds = this._getBoundsForResize(width, height);
 var self = this;
 this.boxy.stop().animate({left: bounds[0], top: bounds[1]});
 this.getContent().stop().animate({width: bounds[2], height: bounds[3]}, function() {
 if (after) after(self);
 });
 return this;
 },

 // Returns true if this dialog is visible, false otherwise
 isVisible: function() {
 return this.visible;
 },

 // Make this boxy instance visible
 show: function() {
 if (this.visible) return;
 if (this.options.modal) {
 var self = this;
 if (!Boxy.resizeConfigured) {
 Boxy.resizeConfigured = true;
 jQuery(window).resize(function() { Boxy._handleResize(); });
 }
 this.modalBlackout = jQuery('<div class="boxy-modal-blackout"></div>')
 .css({zIndex: Boxy._nextZ(),
 opacity: 0.7,
 width: jQuery(document).width(),
 height: jQuery(document).height()})
 .appendTo(document.body);
 this.toTop();
 if (this.options.closeable) {
 jQuery(document.body).bind('keypress.boxy', function(evt) {
 var key = evt.which || evt.keyCode;
 if (key == 27) {
 self.hide();
 jQuery(document.body).unbind('keypress.boxy');
 }
 });
 }
 }
 this.boxy.stop().css({opacity: 1}).show();
 this.visible = true;
 this._fire('afterShow');
 return this;
 },

 // Hide this boxy instance
 hide: function(after) {
 if (!this.visible) return;
 var self = this;
 if (this.options.modal) {
 jQuery(document.body).unbind('keypress.boxy');
 this.modalBlackout.animate({opacity: 0}, function() {
 jQuery(this).remove();
 });
 }
 this.boxy.stop().animate({opacity: 0}, 300, function() {
 self.boxy.css({display: 'none'});
 self.visible = false;
 self._fire('afterHide');
 if (after) after(self);
 if (self.options.unloadOnHide) self.unload();
 });
 return this;
 },

 toggle: function() {
 this[this.visible ? 'hide' : 'show']();
 return this;
 },

 hideAndUnload: function(after) {
 this.options.unloadOnHide = true;
 this.hide(after);
 return this;
 },

 unload: function() {
 this._fire('beforeUnload');
 this.boxy.remove();
 if (this.options.actuator) {
 jQuery.data(this.options.actuator, 'active.boxy', false);
 }
 },

 // Move this dialog box above all other boxy instances
 toTop: function() {
 this.boxy.css({zIndex: Boxy._nextZ()});
 return this;
 },

 // Returns the title of this dialog
 getTitle: function() {
 return jQuery('> .title-bar h2', this.getInner()).html();
 },

 // Sets the title of this dialog
 setTitle: function(t) {
 jQuery('> .title-bar h2', this.getInner()).html(t);
 return this;
 },

 //
 // Don't touch these privates

 _getBoundsForResize: function(width, height) {
 var csize = this.getContentSize();
 var delta = [width - csize[0], height - csize[1]];
 var p = this.getPosition();
 return [Math.max(p[0] - delta[0] / 2, 0),
 Math.max(p[1] - delta[1] / 2, 0), width, height];
 },

 _setupTitleBar: function() {
 if (this.options.title) {
 var self = this;
 var tb = jQuery("<div class='title-bar'></div>").html("<h2>" + this.options.title + "</h2>");
 if (this.options.closeable) {
 tb.append(jQuery("<span><a href='#' class='close' id='close'>bezár</a></span>"));
 }
 if (this.options.draggable) {
 tb[0].onselectstart = function() { return false; }
 tb[0].unselectable = 'on';
 tb[0].style.MozUserSelect = 'none';
 if (!Boxy.dragConfigured) {
 jQuery(document).mousemove(Boxy._handleDrag);
 Boxy.dragConfigured = true;
 }
 tb.mousedown(function(evt) {
 self.toTop();
 Boxy.dragging = [self, evt.pageX - self.boxy[0].offsetLeft, evt.pageY - self.boxy[0].offsetTop];
 jQuery(this).addClass('dragging');
 }).mouseup(function() {
 jQuery(this).removeClass('dragging');
 Boxy.dragging = null;
 self._fire('afterDrop');
 });
 }
 this.getInner().prepend(tb);
 this._setupDefaultBehaviours(tb);
 }
 },

 _setupDefaultBehaviours: function(root) {
 var self = this;
 if (this.options.clickToFront) {
 root.click(function() { self.toTop(); });
 }
 jQuery('.close', root).click(function() {
 self.hide();
 return false;
 }).mousedown(function(evt) { evt.stopPropagation(); });
 },

 _fire: function(event) {
 this.options[event].call(this);
 }

};


