text: ﻿/*
  ---
  description: jBox is a powerful and flexible jQuery plugin, taking care of all your modal windows, tooltips, notices and more.
  
  authors: Stephan Wagner (http://stephanwagner.me)
  
  license: MIT (http://opensource.org/licenses/MIT)
  
  requires: jQuery 1.11.1 (http://code.jquery.com/jquery-1.11.1.min.js)
            jQuery 2.1.1 (http://code.jquery.com/jquery-2.1.1.min.js)
  
  documentation: http://stephanwagner.me/jBox/documentation
  
  demos: http://stephanwagner.me/jBox/demos
  ...
  */
  
  function jBox(type, options) {
  
  	this.options = {
  
  		// jBox ID
  		id: null, // Choose a unique id, otherwise jBox will set one for you (jBoxID1, jBoxID2, ...)
  
  		// Dimensions
  		width: 'auto', // Width of content area (e.g. 'auto', 100)
  		height: 'auto', // Height of content area
  		minWidth: null, // Minimum width
  		maxHeight: null, // Minimum height
  		minWidth: null, // Maximum width
  		maxHeight: null, // Minimum height
  
  		// Attach
  		attach: null, // Attach jBox to elements (if no target element is provided, jBox will use the attached element as target)
  		trigger: 'click', // The event to open or close your jBoxes, use 'click' or 'mouseenter'
  		preventDefault: false, // Prevent default event when opening jBox (e.g. don't follow the href in a link when clicking on it)
  
  		// Content
  		title: null, // Adds a title to your jBox
  		content: null, // You can use a string to set text or HTML as content, or an element selector (e.g. jQuery('#jBox-content')) to append one or several elements (elements appended will get style display: 'block', so hide them with CSS style display: 'none' beforehand)
  		getTitle: null, // Get the title from an attribute when jBox opens
  		getContent: null, // Get the content from an attribute when jBox opens
  		isolateScroll: true, // Isolates scrolling to content container
  
  		// AJAX request
  		ajax: { // Setting an url will make an AJAX call when jBox opens
  			url: null, // URL to send the AJAX request to
  			data: '', // Data to send with your AJAX call (e.g. 'id=82&limit=10')
  			// Optional you can add any jQuery AJAX option (http://api.jquery.com/jquery.ajax/)
  			reload: false, // Resend the ajax call every time jBox opens
  			getData: 'data-ajax', // The attribute in the source element where the AJAX will look for the data to send with, e.g. data-ajax="id=82&limit=10"
  			setContent: true, // Automatically set the response as new content when the AJAX call is finished
  			spinner: true // Hides the current content and adds a spinner while loading, you can pass html content to add your own spinner, e.g. spinner: '<div class="mySpinner"></div>'
  		},
  
  		// Position
  		target: null, // The target element where jBox will be opened
  		position: {
  			x: 'center', // Horizontal Position (Use a number, 'left', 'right' or 'center')
  			y: 'center' // Vertical Position (Use a number, 'top', 'bottom' or 'center')
  		},
  		outside: null, // Use 'x', 'y', or 'xy' to move your jBox outside of the target element
  		offset: 0, // Offset to final position, you can set different values for x and y with an object e.g. {x: 15, y: 0}
  
  		attributes: { // Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20}
  			x: 'left', // Horizontal position, use 'left' or 'right'
  			y: 'top' // Vertical position, use 'top' or 'bottom'
  		},
  		adjustPosition: false, // Adjusts the position when there is not enough space (use true, 'flip' or 'move')
  		adjustTracker: false, // By default jBox adjusts the position when opening, to adjust when scrolling or resizing, use 'scroll', 'resize' or 'true' (both events)
  		adjustDistance: 5, // How far from the window edge we start adjusting, use an object to set different values: {bottom: 5, top: 50, left: 5, right: 20}
  		fixed: false, // Your jBox will stay on position when scrolling
  		reposition: false, // Calculates new position when the window-size changes
  		repositionOnOpen: true, // Calculates new position each time jBox opens (rather than only when it opens the first time)
  		repositionOnContent: true, // Calculates new position when the content changes with .setContent() or .setTitle()
  
  		// Pointer
  		pointer: false, // Your pointer will always point towards the target element, so the option outside should be 'x' or 'y'
  		pointTo: 'target', // Setting something else than 'target' will add a pointer even if there is no target element set or found (Use 'top', 'bottom', 'left' or 'right')
  
  		// Animations
  		fade: 180, // Fade duration in ms, set to 0 or false to disable
  		animation: null, // Animation when opening or closing (use 'pulse', 'zoomIn', 'zoomOut', 'move', 'slide', 'flip', 'tada') (CSS inspired from Daniel Edens Animate.css: http://daneden.me/animate)
  
  		// Appearance
  		theme: 'Default', // Set a jBox theme class
  		addClass: '', // Adds classes to the wrapper
  		overlay: false, // Adds an overlay when jBox opens (set color and opacity with CSS)
  		zIndex: 10000, // Use a high zIndex (your overlay will have the lowest zIndex of all your jBoxes (with overlays) minus one)
  
  		// Delays
  		delayOpen: 0, // Delay opening in ms (Note that the delay will be ignored if your jBox didn't finish closing)
  		delayClose: 0, // Delay closing in ms (Note that there is always a closing delay of at least 10ms to ensure jBox won't be closed when opening right away)
  
  		// Closing events
  		closeOnEsc: false, // Close jBox when pressing [esc] key
  		closeOnClick: false, // Close jBox with mouseclick, use 'true' (click anywhere), 'box' (click on jBox itself), 'overlay' (click on the overlay), 'body' (click anywhere but jBox)
  		closeOnMouseleave: false, // Close jBox when the mouse leaves the jBox area or the area of the attached element
  		closeButton: false, // Adds a close button to your jBox, use 'title', 'overlay', 'box' or true (true will add the button to overlay, title or box, in that order if any of those elements can be found)
  
  		// Other options
  		constructOnInit: false, // Construct jBox when it's being initialized
  		blockScroll: false, // When jBox is open, block scrolling
  		appendTo: jQuery('body'), // Provide an element if you want the jBox to be positioned inside a specific element (only useful for fixed positions or when position values are numbers)
  		draggable: null, // Make your jBox draggable (use 'true', 'title' or provide an element as handle) (inspired from Chris Coyiers CSS-Tricks http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/)
  		dragOver: true, // When you have multiple draggable jBoxes, the one you select will always move over the other ones
  
  		// Events					// Note: You can use 'this' in the event functions, it refers to your jBox object (e.g. onInit: function() { this.open(); })
  		onInit: null, // Triggered when jBox is initialized
  		onBeforeInit: null, // Triggered when jBox starts initializing, useful to add your own internal functions
  		onAttach: null, // Triggered when jBox attached itself to elements
  		// TODO onPosition
  		onCreated: null, // Triggered when jBox is created and is availible in DOM
  		onOpen: null, // Triggered when jBox opens
  		onClose: null, // Triggered when jBox closes
  		onCloseComplete: null, // Triggered when jBox is completely closed (when fading is finished, useful if you want to destroy the jBox when it is closed)
  
  		// Only for type "Confirm"
  		confirmButton: 'Submit', // Text for the submit button
  		cancelButton: 'Cancel', // Text for the cancel button
  		confirm: null, // Function to execute when clicking the submit button. By default jBox will use firstly the onclick and secondly the href attribute
  		cancel: null, // Function to execute when clicking the cancel button
  
  		// Only for type "Notice"
  		autoClose: 7000, // Time when jBox should close automatically
  		color: null, // Makes your notices colorful, use 'black', 'red', 'green', 'blue', 'yellow'
  		stack: true, // Set to false to disable notice-stacking
  		audio: false, // Set the url to an audio file without extention, e.g. '/url/filename'. jBox will look for an .mp3 and an .ogg file
  		volume: 100, // Percent of volume for audio files
  
  		// Only for type "Image"
  		src: 'href', // The attribute where jBox gets the image source from, e.g. href="/path_to_image/image.jpg"
  		gallery: 'data-jbox-image', // The attribute where you define the image gallery, e.g. data-jbox-image="gallery1"
  		imageLabel: 'title', // The attribute where jBox gets the image label from, e.g. title="My label"
  		imageFade: 600, // The fade duration for images
  		imageSize: 'contain' // How to display the images: Use CSS background-position values, e.g. 'cover', 'contain', 'auto', 'initial', '50% 50%'
  	};
  
  	// Default type options
  	this.defaultOptions = {
  		// Default options for tooltips
  		'Tooltip': {
  			getContent: 'title',
  			trigger: 'mouseenter',
  			position: {
  				x: 'center',
  				y: 'top'
  			},
  			outside: 'y',
  			pointer: true,
  			adjustPosition: true,
  			reposition: true
  		},
  		// Default options for mouse tooltips
  		'Mouse': {
  			target: 'mouse',
  			position: {
  				x: 'right',
  				y: 'bottom'
  			},
  			offset: 15,
  			trigger: 'mouseenter',
  			adjustPosition: 'flip'
  		},
  		// Default options for modal windows
  		'Modal': {
  			target: jQuery(window),
  			fixed: true,
  			blockScroll: true,
  			closeOnEsc: true,
  			closeOnClick: 'overlay',
  			closeButton: true,
  			overlay: true,
  			animation: 'zoomOut'
  		},
  		// Default options for modal confirm windows
  		'Confirm': {
  			target: jQuery(window),
  			fixed: true,
  			attach: jQuery('[data-confirm]'),
  			getContent: 'data-confirm',
  			content: 'Do you really want to do this?',
  			minWidth: 320,
  			maxWidth: 460,
  			blockScroll: true,
  			closeOnEsc: true,
  			closeOnClick: 'overlay',
  			closeButton: true,
  			overlay: true,
  			animation: 'zoomOut',
  			preventDefault: true,
  			_onAttach: function (el) {
  				// Extract the href or the onclick event if no submit event is passed
  				if (!this.options.confirm) {
  					var submit = el.attr('onclick') ? el.attr('onclick') : (el.attr('href') ? (el.attr('target') ? 'window.open("' + el.attr('href') + '", "' + el.attr('target') + '");' : 'window.location.href = "' + el.attr('href') + '";') : '');
  					el.prop('onclick', null).data('jBox-Confirm-submit', submit);
  				}
  			},
  			_onCreated: function () {
  				// Add a footer to the jBox container
  				this.footer = jQuery('<div class="jBox-Confirm-footer"/>');
  				jQuery('<div class="jBox-Confirm-button jBox-Confirm-button-cancel"/>').html(this.options.cancelButton).click(function () {
  					this.options.cancel && this.options.cancel();
  					this.close();
  				}.bind(this)).appendTo(this.footer);
  				this.submitButton = jQuery('<div class="jBox-Confirm-button jBox-Confirm-button-submit"/>').html(this.options.confirmButton).appendTo(this.footer);
  				this.footer.appendTo(this.container);
  			},
  			_onOpen: function () {
  				// Set the new action for the submit button
  				this.submitButton.off('click.jBox-Confirm' + this.id).on('click.jBox-Confirm' + this.id, function () {
  					this.options.confirm ? this.options.confirm() : eval(this.source.data('jBox-Confirm-submit'));
  					this.close();
  				}.bind(this));
  			}
  		},
  		// Default options for notices
  		'Notice': {
  			target: jQuery(window),
  			fixed: true,
  			position: {
  				x: 20,
  				y: 20
  			},
  			attributes: {
  				x: 'right',
  				y: 'top'
  			},
  			animation: 'zoomIn',
  			closeOnClick: 'box',
  			_onInit: function () {
  				this.open();
  				this.options.delayClose = this.options.autoClose;
  				this.options.delayClose && this.close();
  			},
  			_onCreated: function () {
  				this.options.color && this.wrapper.addClass('jBox-Notice-color jBox-Notice-' + this.options.color);
  				this.wrapper.data('jBox-Notice-position', this.options.attributes.x + '-' + this.options.attributes.y);
  			},
  			_onOpen: function () {
  				// Loop through notices at same window corner and either move or destroy them
  				jQuery.each(jQuery('.jBox-Notice'), function (index, el) {
  					el = jQuery(el);
  
  					if (el.attr('id') == this.id || el.data('jBox-Notice-position') != this.options.attributes.x + '-' + this.options.attributes.y) return;
  					if (!this.options.stack) {
  						el.data('jBox').close({
  							ignoreDelay: true
  						});
  						return;
  					}
  					el.css('margin-' + this.options.attributes.y, parseInt(el.css('margin-' + this.options.attributes.y)) + this.wrapper.outerHeight() + 10);
  				}.bind(this));
  
  				// Play audio file, IE8 doesn't support audio
  				this.options.audio && this.audio({
  					url: this.options.audio,
  					valume: this.options.volume
  				});
  			},
  			// Remove notice from DOM when closing finishes
  			_onCloseComplete: function () {
  				this.destroy();
  			}
  		},
  		// Default options for images
  		'Image': {
  			target: jQuery(window),
  			fixed: true,
  			blockScroll: true,
  			closeOnEsc: true,
  			closeOnClick: 'overlay',
  			closeButton: true,
  			overlay: true,
  			animation: 'zoomOut',
  			width: 800,
  			height: 533,
  			attach: jQuery('[data-jbox-image]'),
  			preventDefault: true,
  
  			// TODO: What if the image is not found?
  			// TODO: What if the first image of a gallery needs some time to load, but other images are in content. Maybe add a blank black container
  
  			_onInit: function () {
  				this.images = this.currentImage = {};
  				this.imageZIndex = 1;
  
  				// Loop through images, sort and save in global variable
  				this.attachedElements && jQuery.each(this.attachedElements, function (index, item) {
  					item = jQuery(item);
  					if (item.data('jBox-image-gallery')) return;
  					var gallery = item.attr(this.options.gallery) || 'default';
  					!this.images[gallery] && (this.images[gallery] = []);
  					this.images[gallery].push({
  						src: item.attr(this.options.src),
  						label: (item.attr(this.options.imageLabel) || '')
  					});
  
  					// Remove the title attribute so it won't show the browsers tooltip
  					this.options.imageLabel == 'title' && item.removeAttr('title');
  
  					// Store data in source element for easy access
  					item.data('jBox-image-gallery', gallery);
  					item.data('jBox-image-id', (this.images[gallery].length - 1));
  				}.bind(this));
  
  				// Helper to inject the image into content area
  				var appendImage = function (gallery, id, preload, open) {
  					if (jQuery('#jBox-image-' + gallery + '-' + id).length) return;
  
  					var image = jQuery('<div/>', {
  						id: 'jBox-image-' + gallery + '-' + id,
  						'class': 'jBox-image-container'
  					}).css({
  						backgroundImage: 'url(' + this.images[gallery][id].src + ')',
  						backgroundSize: this.options.imageSize,
  						opacity: (open ? 1 : 0),
  						zIndex: (preload ? 0 : this.imageZIndex++)
  					}).appendTo(this.content);
  
  					var text = jQuery('<div/>', {
  						id: 'jBox-image-label-' + gallery + '-' + id,
  						'class': 'jBox-image-label' + (open ? ' active' : '')
  					}).html(this.images[gallery][id].label).appendTo(this.imageLabel);
  
  					!open && !preload && image.animate({
  						opacity: 1
  					}, this.options.imageFade);
  				}.bind(this);
  
  				// Helper to show new image label
  				var showLabel = function (gallery, id) {
  					jQuery('.jBox-image-label.active').removeClass('active');
  					jQuery('#jBox-image-label-' + gallery + '-' + id).addClass('active');
  				};
  
  				// Show images when they are loaded or load them if not
  				this.showImage = function (img) {
  					// Get the gallery and the image id from the next or the previous image
  					if (img != 'open') {
  						var gallery = this.currentImage.gallery;
  						var id = this.currentImage.id + (1 * (img == 'prev') ? -1 : 1);
  						id = id > (this.images[gallery].length - 1) ? 0 : (id < 0 ? (this.images[gallery].length - 1) : id);
  						// Or get image data from source element
  					} else {
  						var gallery = this.source.data('jBox-image-gallery');
  						var id = this.source.data('jBox-image-id');
  
  						// Remove or show the next and prev buttons
  						jQuery('.jBox-image-pointer-prev, .jBox-image-pointer-next').css({
  							display: (this.images[gallery].length > 1 ? 'block' : 'none')
  						});
  					}
  
  					// Set new current image
  					this.currentImage = {
  						gallery: gallery,
  						id: id
  					};
  
  					// Show image if it already exists
  					if (jQuery('#jBox-image-' + gallery + '-' + id).length) {
  						jQuery('#jBox-image-' + gallery + '-' + id).css({
  							zIndex: this.imageZIndex++,
  							opacity: 0
  						}).animate({
  							opacity: 1
  						}, (img == 'open') ? 0 : this.options.imageFade);
  						showLabel(gallery, id);
  
  						// Load image if not found
  					} else {
  						// TODO loading not working properly anymore
  						this.wrapper.addClass('jBox-loading');
  						var image = jQuery('<img src="' + this.images[gallery][id].src + '">').load(function () {
  							appendImage(gallery, id, false);
  							showLabel(gallery, id);
  							this.wrapper.removeClass('jBox-loading');
  						}.bind(this));
  					}
  
  					// Preload next image
  					var next_id = id + 1;
  					next_id = next_id > (this.images[gallery].length - 1) ? 0 : (next_id < 0 ? (this.images[gallery].length - 1) : next_id);
  
  					(!jQuery('#jBox-image-' + gallery + '-' + next_id).length) && jQuery('<img src="' + this.images[gallery][next_id].src + '">').load(function () {
  						appendImage(gallery, next_id, true);
  					});
  				};
  			},
  			_onCreated: function () {
  
  				// TODO: NO ID!!!
  
  				this.imageLabel = jQuery('<div/>', {
  					'id': 'jBox-image-label'
  				}).appendTo(this.wrapper);
  				this.wrapper.append(jQuery('<div/>', {
  					'class': 'jBox-image-pointer-prev',
  					click: function () {
  						this.showImage('prev');
  					}.bind(this)
  				})).append(jQuery('<div/>', {
  					'class': 'jBox-image-pointer-next',
  					click: function () {
  						this.showImage('next');
  					}.bind(this)
  				}));
  			},
  			_onOpen: function () {
  				// Add a class to body so you can control the appearance of the overlay, for images a darker one is better
  				jQuery('body').addClass('jBox-image-open');
  
  				// Add key events
  				jQuery(document).on('keyup.jBox-' + this.id, function (ev) {
  					(ev.keyCode == 37) && this.showImage('prev');
  					(ev.keyCode == 39) && this.showImage('next');
  				}.bind(this));
  
  				// Load the image from the attached element
  				this.showImage('open');
  			},
  			_onClose: function () {
  				jQuery('body').removeClass('jBox-image-open');
  
  				// Remove key events
  				jQuery(document).off('keyup.jBox-' + this.id);
  			},
  			_onCloseComplete: function () {
  				// Hide all images
  				this.wrapper.find('.jBox-image-container').css('opacity', 0);
  			}
  		}
  	};
  
  	// Set default options for jBox types
  	if (jQuery.type(type) == 'string') {
  		this.type = type;
  		type = this.defaultOptions[type] ? this.defaultOptions[type] : window['jBox' + type + 'Options'];
  	}
  
  	// Merge options
  	this.options = jQuery.extend(true, this.options, type, options);
  
  	// Function to fire events
  	this._fireEvent = function (event, pass) {
  		this.options['_' + event] && (this.options['_' + event].bind(this))(pass);
  		this.options[event] && (this.options[event].bind(this))(pass);
  	};
  
  	// Fire onBeforeInit event
  	this._fireEvent('onBeforeInit');
  
  	// Get unique ID
  	if (this.options.id === null) {
  		this.options.id = 'jBoxID' + jBox._getUniqueID();
  	}
  	this.id = this.options.id;
  
  	// Correct impossible options
  	((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = false);
  	(!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false);
  
  	// Correct multiple choice options
  	jQuery.type(this.options.offset) != 'object' && (this.options.offset = {
  		x: this.options.offset,
  		y: this.options.offset
  	});
  	this.options.offset.x || (this.options.offset.x = 0);
  	this.options.offset.y || (this.options.offset.y = 0);
  	jQuery.type(this.options.adjustDistance) != 'object' ? (this.options.adjustDistance = {
  		top: this.options.adjustDistance,
  		right: this.options.adjustDistance,
  		bottom: this.options.adjustDistance,
  		left: this.options.adjustDistance
  	}) : (this.options.adjustDistance = jQuery.extend({
  		top: 5,
  		left: 5,
  		right: 5,
  		bottom: 5
  	}, this.options.adjustDistance));
  
  	// Save where the jBox is aligned to
  	this.align = (this.options.outside && this.options.outside != 'xy') ? this.options.position[this.options.outside] : (this.options.position.y != 'center' && jQuery.type(this.options.position.y) != 'number' ? this.options.position.x : (this.options.position.x != 'center' && jQuery.type(this.options.position.x) != 'number' ? this.options.position.y : this.options.attributes.x));
  
  	// Save default outside position
  	this.options.outside && this.options.outside != 'xy' && (this.outside = this.options.position[this.options.outside]);
  
  	// I know browser detection is bad practice, but for now it seems the only option to get jBox working in IE8
  	var userAgent = navigator.userAgent.toLowerCase();
  	this.IE8 = userAgent.indexOf('msie') != -1 && parseInt(userAgent.split('msie')[1]) == 8;
  
  	// Save global var for webkit prefix
  	this.prefix = userAgent.indexOf('webkit') != -1 ? '-webkit-' : '';
  
  	// Internal functions, used to easily get values
  	this._getOpp = function (opp) {
  		return {
  			left: 'right',
  			right: 'left',
  			top: 'bottom',
  			bottom: 'top',
  			x: 'y',
  			y: 'x'
  		}[opp];
  	};
  	this._getXY = function (xy) {
  		return {
  			left: 'x',
  			right: 'x',
  			top: 'y',
  			bottom: 'y',
  			center: 'x'
  		}[xy];
  	};
  	this._getTL = function (tl) {
  		return {
  			left: 'left',
  			right: 'left',
  			top: 'top',
  			bottom: 'top',
  			center: 'left',
  			x: 'left',
  			y: 'top'
  		}[tl];
  	};
  
  	// Check for SVG support
  	this._supportsSVG = function () {
  		return document.createElement('svg').getAttributeNS;
  	}
  
  	// Create an svg element
  	this._createSVG = function (type, options) {
  		var svg = document.createElementNS('http://www.w3.org/2000/svg', type);
  		jQuery.each(options, function (index, item) {
  			svg.setAttribute(item[0], (item[1] || ''));
  		});
  		return svg;
  	};
  
  	// Append a svg element to a svg container
  	this._appendSVG = function (source, target) {
  		return target.appendChild(source);
  	};
  
  	// Isolate scrolling in a container
  	this._isolateScroll = function (el) {
  		if (!el || !jQuery(el).length) return;
  		el.on('DOMMouseScroll.jBoxIsolatedScroll mousewheel.jBoxIsolatedScroll', function (ev) {
  			var delta = ev.wheelDelta || (ev.originalEvent && ev.originalEvent.wheelDelta) || -ev.detail,
  				overflowBottom = this.scrollTop + el.outerHeight() - this.scrollHeight >= 0,
  				overflowTop = this.scrollTop <= 0;
  			if ((delta < 0 && overflowBottom) || (delta > 0 && overflowTop)) ev.preventDefault();
  		});
  	};
  
  	// Create jBox
  	this._create = function () {
  		if (this.wrapper) return;
  
  		// Create wrapper
  		this.wrapper = jQuery('<div/>', {
  			id: this.id,
  			'class': 'jBox-wrapper' + (this.type ? ' jBox-' + this.type : '') + (this.options.theme ? ' jBox-' + this.options.theme : '') + (this.options.addClass ? ' ' + this.options.addClass : '') + (this.IE8 ? ' jBox-IE8' : '')
  		}).css({
  			position: (this.options.fixed ? 'fixed' : 'absolute'),
  			display: 'none',
  			opacity: 0,
  			zIndex: this.options.zIndex
  
  			// Save the jBox instance in the wrapper, so you can get access to your jBox when you only have the element
  		}).data('jBox', this);
  
  		// Add mouseleave event (.parents('*') might be a performance nightmare! Maybe there is a better way)
  		this.options.closeOnMouseleave && this.wrapper.mouseleave(function (ev) {
  			// Only close when the new target is not the source element
  			!this.source || !(ev.relatedTarget == this.source[0] || jQuery.inArray(this.source[0], jQuery(ev.relatedTarget).parents('*')) !== -1) && this.close();
  		}.bind(this));
  
  		// Add closeOnClick: 'box' events
  		(this.options.closeOnClick == 'box') && this.wrapper.on('touchend click', function () {
  			this.close({
  				ignoreDelay: true
  			});
  		}.bind(this));
  
  		// Create container
  		this.container = jQuery('<div/>', {
  			'class': 'jBox-container'
  		}).appendTo(this.wrapper);
  
  		// Create content
  		this.content = jQuery('<div/>', {
  			'class': 'jBox-content'
  		}).css({
  			width: this.options.width,
  			height: this.options.height,
  			minWidth: this.options.minWidth,
  			minHeight: this.options.minHeight,
  			maxWidth: this.options.maxWidth,
  			maxHeight: this.options.maxHeight
  		}).appendTo(this.container);
  
  		// Isolate scrolling
  		this.options.isolateScroll && this._isolateScroll(this.content);
  
  		// Create close button
  		if (this.options.closeButton) {
  			this.closeButton = jQuery('<div/>', {
  				'class': 'jBox-closeButton jBox-noDrag'
  			}).on('touchend click', function (ev) {
  				this.isOpen && this.close({
  					ignoreDelay: true
  				});
  			}.bind(this));
  
  			if (this._supportsSVG()) {
  				var closeButtonSVG = this._createSVG('svg', [
  					['viewBox', '0 0 24 24']
  				]);
  				this._appendSVG(this._createSVG('path', [
  					['d', 'M22.2,4c0,0,0.5,0.6,0,1.1l-6.8,6.8l6.9,6.9c0.5,0.5,0,1.1,0,1.1L20,22.3c0,0-0.6,0.5-1.1,0L12,15.4l-6.9,6.9c-0.5,0.5-1.1,0-1.1,0L1.7,20c0,0-0.5-0.6,0-1.1L8.6,12L1.7,5.1C1.2,4.6,1.7,4,1.7,4L4,1.7c0,0,0.6-0.5,1.1,0L12,8.5l6.8-6.8c0.5-0.5,1.1,0,1.1,0L22.2,4z']
  				]), closeButtonSVG);
  				this.closeButton.append(closeButtonSVG);
  			} else {
  				this.wrapper.addClass('jBox-nosvg');
  			}
  
  			// Add close button to jBox container
  			if (this.options.closeButton == 'box' || (this.options.closeButton === true && !this.options.overlay && !this.options.title)) {
  				this.wrapper.addClass('jBox-closeButton-box');
  				this.closeButton.appendTo(this.container);
  			}
  		}
  
  		// Append jBox to DOM
  		this.wrapper.appendTo(this.options.appendTo);
  
  		// Create pointer
  		if (this.options.pointer) {
  
  			// Get pointer vars and save globally
  			this.pointer = {
  				position: (this.options.pointTo != 'target') ? this.options.pointTo : this._getOpp(this.outside),
  				xy: (this.options.pointTo != 'target') ? this._getXY(this.options.pointTo) : this._getXY(this.outside),
  				align: 'center',
  				offset: 0
  			};
  
  			this.pointer.element = jQuery('<div/>', {
  				'class': 'jBox-pointer jBox-pointer-' + this.pointer.position
  			}).appendTo(this.wrapper);
  			this.pointer.dimensions = {
  				x: this.pointer.element.outerWidth(),
  				y: this.pointer.element.outerHeight()
  			};
  
  			if (jQuery.type(this.options.pointer) == 'string') {
  				var split = this.options.pointer.split(':');
  				split[0] && (this.pointer.align = split[0]);
  				split[1] && (this.pointer.offset = parseInt(split[1]));
  			}
  			this.pointer.alignAttribute = (this.pointer.xy == 'x' ? (this.pointer.align == 'bottom' ? 'bottom' : 'top') : (this.pointer.align == 'right' ? 'right' : 'left'));
  
  			// Set wrapper CSS
  			this.wrapper.css('padding-' + this.pointer.position, this.pointer.dimensions[this.pointer.xy]);
  
  			// Set pointer CSS
  			this.pointer.element.css(this.pointer.alignAttribute, (this.pointer.align == 'center' ? '50%' : 0)).css('margin-' + this.pointer.alignAttribute, this.pointer.offset);
  			this.pointer.margin = {};
  			this.pointer.margin['margin-' + this.pointer.alignAttribute] = this.pointer.offset;
  
  			// Add a transform to fix centered position
  			(this.pointer.align == 'center') && this.pointer.element.css(this.prefix + 'transform', 'translate(' + (this.pointer.xy == 'y' ? (this.pointer.dimensions.x * -0.5 + 'px') : 0) + ', ' + (this.pointer.xy == 'x' ? (this.pointer.dimensions.y * -0.5 + 'px') : 0) + ')');
  
  			this.pointer.element.css((this.pointer.xy == 'x' ? 'width' : 'height'), parseInt(this.pointer.dimensions[this.pointer.xy]) + parseInt(this.container.css('border-' + this.pointer.alignAttribute + '-width')));
  
  			// Add class to wrapper for CSS access
  			this.wrapper.addClass('jBox-pointerPosition-' + this.pointer.position);
  		}
  
  		// Set title and content
  		this.setContent(this.options.content, true);
  		this.setTitle(this.options.title, true);
  
  		// Make jBox draggable
  		if (this.options.draggable) {
  			var handle = (this.options.draggable == 'title') ? this.titleContainer : (this.options.draggable.length > 0 ? this.options.draggable : (this.options.draggable.selector ? jQuery(this.options.draggable.selector) : this.wrapper));
  			handle.addClass('jBox-draggable').on('mousedown', function (ev) {
  				if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return;
  
  				if (this.options.dragOver && this.wrapper.css('zIndex') <= jBox.zIndexMax) {
  					jBox.zIndexMax += 1;
  					this.wrapper.css('zIndex', jBox.zIndexMax);
  				}
  
  				var drg_h = this.wrapper.outerHeight(),
  					drg_w = this.wrapper.outerWidth(),
  					pos_y = this.wrapper.offset().top + drg_h - ev.pageY,
  					pos_x = this.wrapper.offset().left + drg_w - ev.pageX;
  				jQuery(document).on('mousemove.jBox-draggable-' + this.id, function (ev) {
  					this.wrapper.offset({
  						top: ev.pageY + pos_y - drg_h,
  						left: ev.pageX + pos_x - drg_w
  					});
  				}.bind(this));
  				ev.preventDefault();
  			}.bind(this)).on('mouseup', function () {
  				jQuery(document).off('mousemove.jBox-draggable-' + this.id);
  			}.bind(this));
  
  			// Add z-index
  			jBox.zIndexMax = !jBox.zIndexMax ? this.options.zIndex : Math.max(jBox.zIndexMax, this.options.zIndex);
  		}
  
  		// Fire onCreated event
  		this._fireEvent('onCreated');
  	};
  
  	// Create jBox onInit
  	this.options.constructOnInit && this._create();
  
  	// Attach jBox
  	this.options.attach && this.attach();
  
  	// Position jBox on mouse
  	this._positionMouse = function (ev) {
  
  		// Calculate positions
  		this.pos = {
  			left: ev.pageX,
  			top: ev.pageY
  		};
  		var setPosition = function (a, p) {
  			// Set centered position
  			if (this.options.position[p] == 'center') {
  				this.pos[a] -= Math.ceil(this.dimensions[p] / 2);
  				return;
  			}
  			// Move to left or top
  			this.pos[a] += (a == this.options.position[p]) ? ((this.dimensions[p] * -1) - this.options.offset[p]) : this.options.offset[p];
  
  			return this.pos[a];
  		}.bind(this);
  
  		// Set position to wrapper
  		this.wrapper.css({
  			left: setPosition('left', 'x'),
  			top: setPosition('top', 'y')
  		});
  
  		// Adjust mouse position
  		this.targetDimensions = {
  			x: 0,
  			y: 0,
  			left: ev.pageX,
  			top: ev.pageY
  		};
  		this._adjustPosition();
  	};
  
  	// Attach document and window events
  	this._attachEvents = function () {
  
  		// Closing event: closeOnEsc
  		this.options.closeOnEsc && jQuery(document).on('keyup.jBox-' + this.id, function (ev) {
  			if (ev.keyCode == 27) {
  				this.close({
  					ignoreDelay: true
  				});
  			}
  		}.bind(this));
  
  		// Closing event: closeOnClick
  		if (this.options.closeOnClick === true || this.options.closeOnClick == 'body') {
  			jQuery(document).on('touchend.jBox-' + this.id + ' click.jBox-' + this.id, function (ev) {
  				if (this.blockBodyClick || (this.options.closeOnClick == 'body' && (ev.target == this.wrapper[0] || this.wrapper.has(ev.target).length)))
  					return;
  				this.close({
  					ignoreDelay: true
  				});
  			}.bind(this));
  		}
  
  		// Positioning events
  		if (((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) && !this.fixed && this.outside) {
  
  			var scrollTimer,
  				scrollTimerTriggered = 0,
  				scrollTriggerDelay = 150; // Trigger scroll and resize events every 150 ms (set a higher value to improve performance)
  
  			// Function to delay positioning event
  			var positionDelay = function () {
  				var now = new Date().getTime();
  				if (!scrollTimer) {
  					if (now - scrollTimerTriggered > scrollTriggerDelay) {
  						this.options.reposition && this.position();
  						this.options.adjustTracker && this._adjustPosition();
  						scrollTimerTriggered = now;
  					}
  					scrollTimer = setTimeout(function () {
  						scrollTimer = null;
  						scrollTimerTriggered = new Date().getTime();
  						this.options.reposition && this.position();
  						this.options.adjustTracker && this._adjustPosition();
  					}.bind(this), scrollTriggerDelay);
  				}
  			}.bind(this);
  
  			// Trigger position events when scrolling
  			(this.options.adjustTracker && this.options.adjustTracker != 'resize') && jQuery(window).on('scroll.jBox-' + this.id, function (ev) {
  				positionDelay();
  			}.bind(this));
  
  			// Trigger position events when resizing
  			((this.options.adjustTracker && this.options.adjustTracker != 'scroll') || this.options.reposition) && jQuery(window).on('resize.jBox-' + this.id, function (ev) {
  				positionDelay();
  			}.bind(this));
  		}
  
  		// Mousemove events
  		this.options.target == 'mouse' && jQuery('body').on('mousemove.jBox-' + this.id, function (ev) {
  			this._positionMouse(ev);
  		}.bind(this));
  	};
  
  	// Detach document and window events
  	this._detachEvents = function () {
  
  		// Closing event: closeOnEsc
  		this.options.closeOnEsc && jQuery(document).off('keyup.jBox-' + this.id);
  
  		// Closing event: closeOnClick
  		(this.options.closeOnClick === true || this.options.closeOnClick == 'body') && jQuery(document).off('touchend.jBox-' + this.id + ' click.jBox-' + this.id);
  
  		// Positioning events
  		if ((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) {
  			jQuery(window).off('scroll.jBox-' + this.id);
  			jQuery(window).off('resize.jBox-' + this.id);
  		}
  
  		// Mousemove events
  		this.options.target == 'mouse' && jQuery('body').off('mousemove.jBox-' + this.id);
  	};
  
  	// Add overlay
  	this._addOverlay = function () {
  
  		// If the overlay isn't cached, set overlay or create it
  		if (!this.overlay) {
  			// Get the overlay and adjust z-Index
  			this.overlay = jQuery('#jBox-overlay').length ? jQuery('#jBox-overlay').css({
  				zIndex: Math.min(jQuery('#jBox-overlay').css('z-index'), (this.options.zIndex - 1))
  			}) : (jQuery('<div/>', {
  				id: 'jBox-overlay'
  			}).css({
  				display: 'none',
  				opacity: 0,
  				zIndex: (this.options.zIndex - 1)
  			}).appendTo(jQuery('body')));
  
  			// Add close button to overlay
  			(this.options.closeButton == 'overlay' || this.options.closeButton === true) && ((jQuery('#jBox-overlay .jBox-closeButton').length > 0) ? jQuery('#jBox-overlay .jBox-closeButton').on('touchend click', function () {
  				this.isOpen && this.close({
  					ignoreDelay: true
  				});
  			}.bind(this)) : this.overlay.append(this.closeButton));
  
  			// Add closeOnClick: 'overlay' events
  			(this.options.closeOnClick == 'overlay') && this.overlay.on('touchend click', function () {
  				this.isOpen && this.close({
  					ignoreDelay: true
  				});
  			}.bind(this));
  		}
  
  		// Add jBox to overlay data
  		var overlay_data = this.overlay.data('jBox') || {};
  		overlay_data['jBox-' + this.id] = true;
  		this.overlay.data('jBox', overlay_data);
  
  		// Abort if overlay is shown already
  		if (this.overlay.css('display') == 'block') return;
  
  		// Show overlay
  		this.options.fade ? (this.overlay.stop() && this.overlay.animate({
  			opacity: 1
  		}, {
  			queue: false,
  			duration: this.options.fade,
  			start: function () {
  				this.overlay.css({
  					display: 'block'
  				});
  			}.bind(this)
  		})) : this.overlay.css({
  			display: 'block',
  			opacity: 1
  		});
  	};
  
  	// Remove overlay
  	this._removeOverlay = function () {
  
  		// Abort if no overlay found
  		if (!this.overlay) return;
  
  		// Remove jBox from data
  		var overlay_data = this.overlay.data('jBox');
  		delete overlay_data['jBox-' + this.id];
  		this.overlay.data('jBox', overlay_data);
  
  		// Hide overlay if no other jBox needs it
  		if (jQuery.isEmptyObject(overlay_data)) {
  			this.options.fade ? (this.overlay.stop() && this.overlay.animate({
  				opacity: 0
  			}, {
  				queue: false,
  				duration: this.options.fade,
  				complete: function () {
  					this.overlay.css({
  						display: 'none'
  					});
  				}.bind(this)
  			})) : this.overlay.css({
  				display: 'none',
  				opacity: 0
  			});
  		}
  	};
  
  	// Generate CSS for animations and append to header
  	this._generateCSS = function () {
  		if (this.IE8) return;
  
  		// Get open and close animations if none provided
  		(jQuery.type(this.options.animation) != 'object') && (this.options.animation = {
  			pulse: {
  				open: 'pulse',
  				close: 'zoomOut'
  			},
  			zoomIn: {
  				open: 'zoomIn',
  				close: 'zoomIn'
  			},
  			zoomOut: {
  				open: 'zoomOut',
  				close: 'zoomOut'
  			},
  			move: {
  				open: 'move',
  				close: 'move'
  			},
  			slide: {
  				open: 'slide',
  				close: 'slide'
  			},
  			flip: {
  				open: 'flip',
  				close: 'flip'
  			},
  			tada: {
  				open: 'tada',
  				close: 'zoomOut'
  			}
  		}[this.options.animation]);
  
  		// Get direction var
  		this.options.animation.open && (this.options.animation.open = this.options.animation.open.split(':'));
  		this.options.animation.close && (this.options.animation.close = this.options.animation.close.split(':'));
  		this.options.animation.openDirection = this.options.animation.open ? this.options.animation.open[1] : null;
  		this.options.animation.closeDirection = this.options.animation.close ? this.options.animation.close[1] : null;
  		this.options.animation.open && (this.options.animation.open = this.options.animation.open[0]);
  		this.options.animation.close && (this.options.animation.close = this.options.animation.close[0]);
  
  		// Add 'Open' and 'Close' to animation names
  		this.options.animation.open && (this.options.animation.open += 'Open');
  		this.options.animation.close && (this.options.animation.close += 'Close');
  
  		// All animations
  		var animations = {
  			pulse: {
  				duration: 350,
  				css: [
  					['0%', 'scale(1)'],
  					['50%', 'scale(1.1)'],
  					['100%', 'scale(1)']
  				]
  			},
  			zoomInOpen: {
  				duration: (this.options.fade || 180),
  				css: [
  					['0%', 'scale(0.9)'],
  					['100%', 'scale(1)']
  				]
  			},
  			zoomInClose: {
  				duration: (this.options.fade || 180),
  				css: [
  					['0%', 'scale(1)'],
  					['100%', 'scale(0.9)']
  				]
  			},
  			zoomOutOpen: {
  				duration: (this.options.fade || 180),
  				css: [
  					['0%', 'scale(1.1)'],
  					['100%', 'scale(1)']
  				]
  			},
  			zoomOutClose: {
  				duration: (this.options.fade || 180),
  				css: [
  					['0%', 'scale(1)'],
  					['100%', 'scale(1.1)']
  				]
  			},
  			moveOpen: {
  				duration: (this.options.fade || 180),
  				positions: {
  					top: {
  						'0%': -12
  					},
  					right: {
  						'0%': 12
  					},
  					bottom: {
  						'0%': 12
  					},
  					left: {
  						'0%': -12
  					}
  				},
  				css: [
  					['0%', 'translate%XY(%Vpx)'],
  					['100%', 'translate%XY(0px)']
  				]
  			},
  			moveClose: {
  				duration: (this.options.fade || 180),
  				timing: 'ease-in',
  				positions: {
  					top: {
  						'100%': -12
  					},
  					right: {
  						'100%': 12
  					},
  					bottom: {
  						'100%': 12
  					},
  					left: {
  						'100%': -12
  					}
  				},
  				css: [
  					['0%', 'translate%XY(0px)'],
  					['100%', 'translate%XY(%Vpx)']
  				]
  			},
  			slideOpen: {
  				duration: 400,
  				positions: {
  					top: {
  						'0%': -400
  					},
  					right: {
  						'0%': 400
  					},
  					bottom: {
  						'0%': 400
  					},
  					left: {
  						'0%': -400
  					}
  				},
  				css: [
  					['0%', 'translate%XY(%Vpx)'],
  					['100%', 'translate%XY(0px)']
  				]
  			},
  			slideClose: {
  				duration: 400,
  				timing: 'ease-in',
  				positions: {
  					top: {
  						'100%': -400
  					},
  					right: {
  						'100%': 400
  					},
  					bottom: {
  						'100%': 400
  					},
  					left: {
  						'100%': -400
  					}
  				},
  				css: [
  					['0%', 'translate%XY(0px)'],
  					['100%', 'translate%XY(%Vpx)']
  				]
  			},
  			flipOpen: {
  				duration: 600,
  				css: [
  					['0%', 'perspective(400px) rotateX(90deg)'],
  					['40%', 'perspective(400px) rotateX(-15deg)'],
  					['70%', 'perspective(400px) rotateX(15deg)'],
  					['100%', 'perspective(400px) rotateX(0deg)']
  				]
  			},
  			flipClose: {
  				duration: (this.options.fade || 300),
  				css: [
  					['0%', 'perspective(400px) rotateX(0deg)'],
  					['100%', 'perspective(400px) rotateX(90deg)']
  				]
  			},
  			tada: {
  				duration: 800,
  				css: [
  					['0%', 'scale(1)'],
  					['10%, 20%', 'scale(0.9) rotate(-3deg)'],
  					['30%, 50%, 70%, 90%', 'scale(1.1) rotate(3deg)'],
  					['40%, 60%, 80%', 'scale(1.1) rotate(-3deg)'],
  					['100%', 'scale(1) rotate(0)']
  				]
  			}
  		};
  
  		// Set Open and Close names for standalone animations
  		jQuery.each(['pulse', 'tada'], function (index, item) {
  			animations[item + 'Open'] = animations[item + 'Close'] = animations[item];
  		});
  
  		// Function to generate the CSS for the keyframes
  		var generateKeyframeCSS = function (ev, position) {
  
  			// Generate keyframes CSS
  			keyframe_css = '@' + this.prefix + 'keyframes jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
  			jQuery.each(animations[this.options.animation[ev]].css, function (index, item) {
  				var translate = position ? item[1].replace('%XY', this._getXY(position).toUpperCase()) : item[1];
  				animations[this.options.animation[ev]].positions && (translate = translate.replace('%V', animations[this.options.animation[ev]].positions[position][item[0]]));
  				keyframe_css += item[0] + ' {' + this.prefix + 'transform:' + translate + ';}';
  			}.bind(this));
  			keyframe_css += '}';
  
  			// Generate class CSS
  			keyframe_css += '.jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
  			keyframe_css += this.prefix + 'animation-duration: ' + animations[this.options.animation[ev]].duration + 'ms;';
  			keyframe_css += this.prefix + 'animation-name: jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ';';
  			keyframe_css += animations[this.options.animation[ev]].timing ? (this.prefix + 'animation-timing-function: ' + animations[this.options.animation[ev]].timing + ';') : '';
  			keyframe_css += '}';
  
  			return keyframe_css;
  		}.bind(this);
  
  		// Generate css for each event and positions
  		var css = '';
  		jQuery.each(['open', 'close'], function (index, ev) {
  			// No CSS needed for closing with no fade
  			if (!this.options.animation[ev] || !animations[this.options.animation[ev]] || (ev == 'close' && !this.options.fade)) return '';
  
  			// Generate CSS
  			animations[this.options.animation[ev]].positions ?
  				jQuery.each(['top', 'right', 'bottom', 'left'], function (index2, position) {
  					css += generateKeyframeCSS(ev, position);
  				}) :
  				css += generateKeyframeCSS(ev);
  		}.bind(this));
  
  		var style = document.createElement('style');
  
  		style.type = "text/css";
  		if (style.styleSheet) { //ie下  
  			style.styleSheet.cssText = css;
  		} else {
  			style.innerHTML = css;
  		}
  
  		document.getElementsByTagName('head')[0].appendChild(style);
  	};
  
  	// Block body clicks for 10ms to prevent extra event triggering
  	this._blockBodyClick = function () {
  		this.blockBodyClick = true;
  		setTimeout(function () {
  			this.blockBodyClick = false;
  		}.bind(this), 10);
  	};
  
  	// Add css for animations
  	this.options.animation && this._generateCSS();
  
  	// Animations
  	this._animate = function (ev) {
  		if (this.IE8) return;
  		ev || (ev = this.isOpen ? 'open' : 'close');
  
  		// Don't animate when closing with no fade duration
  		if (!this.options.fade && ev == 'close') return null;
  
  		// Get the current position, use opposite if jBox is flipped
  		var animationDirection = (this.options.animation[ev + 'Direction'] || ((this.align != 'center') ? this.align : this.options.attributes.x));
  		this.flipped && this._getXY(animationDirection) == (this._getXY(this.align)) && (animationDirection = this._getOpp(animationDirection));
  
  		// Add event and position classes
  		var classnames = 'jBox-animation-' + this.options.animation[ev] + '-' + ev + ' jBox-animation-' + this.options.animation[ev] + '-' + ev + '-' + animationDirection;
  		this.wrapper.addClass(classnames);
  
  		// Get duration of animation
  		var animationDuration = parseFloat(this.wrapper.css(this.prefix + 'animation-duration')) * 1000;
  		ev == 'close' && (animationDuration = Math.min(animationDuration, this.options.fade));
  
  		// Remove animation classes when animation is finished
  		setTimeout(function () {
  			this.wrapper.removeClass(classnames);
  		}.bind(this), animationDuration);
  	};
  
  	// Abort animation
  	this._abortAnimation = function () {
  		if (this.IE8) return;
  
  		// Remove all animation classes
  		var prefix = 'jBox-animation';
  		var classes = this.wrapper.attr('class').split(' ').filter(function (c) {
  			return c.lastIndexOf(prefix, 0) !== 0;
  		});
  		this.wrapper.attr('class', classes.join(' '));
  	};
  
  	// Adjust position
  	this._adjustPosition = function () {
  		if (!this.options.adjustPosition) return null;
  
  		// Reset cached pointer position
  		if (this.positionAdjusted) {
  			this.wrapper.css(this.pos);
  			this.pointer && this.wrapper.css('padding', 0).css('padding-' + this._getOpp(this.outside), this.pointer.dimensions[this._getXY(this.outside)]).removeClass('jBox-pointerPosition-' + this._getOpp(this.pointer.position)).addClass('jBox-pointerPosition-' + this.pointer.position);
  			this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this._getOpp(this.outside)).css(this.pointer.margin);
  			this.positionAdjusted = false;
  			this.flipped = false;
  		}
  
  		// Get the window dimensions
  		var win = jQuery(window);
  		var windowDimensions = {
  			x: win.width(),
  			y: win.height(),
  			top: (this.options.fixed && this.target.data('jBox-fixed') ? 0 : win.scrollTop()),
  			left: (this.options.fixed && this.target.data('jBox-fixed') ? 0 : win.scrollLeft())
  		};
  		windowDimensions.bottom = windowDimensions.top + windowDimensions.y;
  		windowDimensions.right = windowDimensions.left + windowDimensions.x;
  
  		// Find out where the jBox is out of view area
  		var outYT = (windowDimensions.top > this.pos.top - (this.options.adjustDistance.top || 0)),
  			outXR = (windowDimensions.right < this.pos.left + this.dimensions.x + (this.options.adjustDistance.right || 0)),
  			outYB = (windowDimensions.bottom < this.pos.top + this.dimensions.y + (this.options.adjustDistance.bottom || 0)),
  			outXL = (windowDimensions.left > this.pos.left - (this.options.adjustDistance.left || 0)),
  			outX = outXL ? 'left' : (outXR ? 'right' : null),
  			outY = outYT ? 'top' : (outYB ? 'bottom' : null),
  			out = outX || outY;
  
  		// Stop here if jBox is not out of view area
  		if (!out) return;
  
  		// Flip jBox
  		if (this.options.adjustPosition != 'move' && (outX == this.outside || outY == this.outside)) {
  
  			this.target == 'mouse' && (this.outside = 'right');
  
  			// Check if enough space is availible on opposite position
  			if (((this.outside == 'top' || this.outside == 'left') ?
  					(windowDimensions[this._getXY(this.outside)] - (this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)]) - this.targetDimensions[this._getXY(this.outside)]) + this.options.offset[this._getXY(this.outside)] :
  					(this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)]) - this.options.offset[this._getXY(this.outside)]
  				) > this.dimensions[this._getXY(this.outside)] + parseInt(this.options.adjustDistance[this._getOpp(this.outside)])) {
  
  				// Adjust wrapper and pointer
  				this.wrapper.css(this._getTL(this.outside), this.pos[this._getTL(this.outside)] + ((this.dimensions[this._getXY(this.outside)] + (this.options.offset[this._getXY(this.outside)] * (this.outside == 'top' || this.outside == 'left' ? -2 : 2)) + this.targetDimensions[this._getXY(this.outside)]) * (this.outside == 'top' || this.outside == 'left' ? 1 : -1)));
  				this.pointer && this.wrapper.removeClass('jBox-pointerPosition-' + this.pointer.position).addClass('jBox-pointerPosition-' + this._getOpp(this.pointer.position)).css('padding', 0).css('padding-' + this.outside, this.pointer.dimensions[this._getXY(this.outside)]);
  				this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this.outside);
  				this.positionAdjusted = true;
  				this.flipped = true;
  			}
  		}
  
  		// Move jBox (only possible with pointer)
  		var outMove = (this._getXY(this.outside) == 'x') ? outY : outX;
  
  		if (this.pointer && this.options.adjustPosition != 'flip' && this._getXY(outMove) == this._getOpp(this._getXY(this.outside))) {
  
  			// Get the maximum space we have availible to adjust
  			if (this.pointer.align == 'center') {
  				var spaceAvail = (this.dimensions[this._getXY(outMove)] / 2) - (this.pointer.dimensions[this._getOpp(this.pointer.xy)] / 2) - (parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) * (outMove != this._getTL(outMove) ? -1 : 1));
  			} else {
  				var spaceAvail = (outMove == this.pointer.alignAttribute) ?
  					parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) :
  					this.dimensions[this._getXY(outMove)] - parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - this.pointer.dimensions[this._getXY(outMove)];
  			}
  
  			// Get the overlapping space
  			spaceDiff = (outMove == this._getTL(outMove)) ?
  				windowDimensions[this._getTL(outMove)] - this.pos[this._getTL(outMove)] + this.options.adjustDistance[outMove] :
  				(windowDimensions[this._getOpp(this._getTL(outMove))] - this.pos[this._getTL(outMove)] - this.options.adjustDistance[outMove] - this.dimensions[this._getXY(outMove)]) * -1;
  
  			// Add overlapping space on left or top window edge
  			if (outMove == this._getOpp(this._getTL(outMove)) && this.pos[this._getTL(outMove)] - spaceDiff < windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)]) {
  				spaceDiff -= windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)] - (this.pos[this._getTL(outMove)] - spaceDiff);
  			}
  
  			// Only adjust the maximum availible
  			spaceDiff = Math.min(spaceDiff, spaceAvail);
  
  			// Move jBox
  			if (spaceDiff <= spaceAvail && spaceDiff > 0) {
  				this.pointer.element.css('margin-' + this.pointer.alignAttribute, parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - (spaceDiff * (outMove != this.pointer.alignAttribute ? -1 : 1)));
  				this.wrapper.css(this._getTL(outMove), this.pos[this._getTL(outMove)] + (spaceDiff * (outMove != this._getTL(outMove) ? -1 : 1)));
  				this.positionAdjusted = true;
  			}
  		}
  	};
  
  	// Fire onInit event
  	this._fireEvent('onInit');
  
  	return this;
  };
  
  // Attach jBox to elements
  jBox.prototype.attach = function (elements, trigger) {
  	elements || (elements = jQuery(this.options.attach.selector || this.options.attach));
  	trigger || (trigger = this.options.trigger);
  
  	elements && elements.length && jQuery.each(elements, function (index, el) {
  		el = jQuery(el);
  		if (!el.data('jBox-attached-' + this.id)) {
  
  			// Remove title attribute and store content on element
  			(this.options.getContent == 'title' && el.attr('title') != undefined) && el.data('jBox-getContent', el.attr('title')).removeAttr('title');
  
  			// Add Element to collection
  			this.attachedElements || (this.attachedElements = []);
  			this.attachedElements.push(el[0]);
  
  			// Add click or mouseenter event, click events can prevent default as well
  			el.on(trigger + '.jBox-attach-' + this.id, function (ev) {
  				// Clear timer
  				this.timer && clearTimeout(this.timer);
  
  				// Block opening when jbox is open and the source element is triggering
  				if (trigger == 'mouseenter' && this.isOpen && this.source[0] == el[0])
  					return;
  
  				// Only close jBox if you click the current target element, otherwise open at new target
  				if (this.isOpen && this.source && this.source[0] != el[0]) var forceOpen = true;
  
  				// Set new source element
  				this.source = el;
  
  				// Set new target
  				!this.options.target && (this.target = el);
  
  				// Prevent default action on click
  				trigger == 'click' && this.options.preventDefault && ev.preventDefault();
  
  				// Toggle or open jBox
  				this[trigger == 'click' && !forceOpen ? 'toggle' : 'open']();
  			}.bind(this));
  
  			// Add close event for trigger event mouseenter
  			(this.options.trigger == 'mouseenter') && el.on('mouseleave', function (ev) {
  				// If we have set closeOnMouseleave, do not close jBox when leaving attached element and mouse is over jBox
  				if (!this.options.closeOnMouseleave || !(ev.relatedTarget == this.wrapper[0] || jQuery(ev.relatedTarget).parents('#' + this.id).length)) this.close();
  			}.bind(this));
  
  			el.data('jBox-attached-' + this.id, trigger);
  
  			// Fire onAttach event
  			this._fireEvent('onAttach', el);
  		}
  	}.bind(this));
  
  	return this;
  };
  
  // Detach jBox from elements
  jBox.prototype.detach = function (elements) {
  	elements || (elements = this.attachedElements || []);
  
  	elements && elements.length && jQuery.each(elements, function (index, el) {
  		el = jQuery(el);
  		// Remove events
  		if (el.data('jBox-attached-' + this.id)) {
  			el.off(el.data('jBox-attached-' + this.id) + '.jBox-attach-' + this.id);
  			el.data('jBox-attached-' + this.id, null);
  		}
  		// Remove element from collection
  		this.attachedElements = jQuery.grep(this.attachedElements, function (value) {
  			return value != el[0];
  		});
  	}.bind(this));
  
  	return this;
  };
  
  // Set title
  jBox.prototype.setTitle = function (title, ignore_positioning) {
  	!this.wrapper && this._create();
  
  	var wrapperHeight = this.wrapper.height(),
  		wrapperWidth = this.wrapper.width();
  	if (title == null || title == undefined) return this;
  
  	if (!this.title) {
  		this.titleContainer = jQuery('<div/>', {
  			'class': 'jBox-title'
  		});
  		this.title = jQuery('<div/>').appendTo(this.titleContainer);
  		this.wrapper.addClass('jBox-hasTitle');
  		if (this.options.closeButton == 'title' || (this.options.closeButton === true && !this.options.overlay)) {
  			this.wrapper.addClass('jBox-closeButton-title');
  			this.closeButton.appendTo(this.titleContainer);
  		}
  		this.titleContainer.insertBefore(this.content);
  	}
  	this.title.html(title);
  
  	// Reposition if dimensions changed
  	!ignore_positioning && this.options.repositionOnContent && (wrapperHeight != this.wrapper.height() || wrapperWidth != this.wrapper.width()) && this.position();
  
  	return this;
  };
  
  // Set content
  jBox.prototype.setContent = function (content, ignore_positioning) {
  	if (content == null) return this;
  
  	// Create jBox if no wrapper found
  	!this.wrapper && this._create();
  
  	// Get the width and height of wrapper, only if they change we need to reposition
  	var wrapperHeight = this.wrapper.height(),
  		wrapperWidth = this.wrapper.width();
  
  	// Get the width and height of body, if they change with new content, adjust accordingly (happens when a hidden scrollbar changes body dimensions)
  	var bodyHeight = jQuery('body').height(),
  		bodyWidth = jQuery('body').width();
  
  	// Extract all appended containers to body
  	this.content.children('[data-jbox-content-appended]').appendTo('body').css({
  		display: 'none'
  	});
  
  	// Set the new content
  	switch (jQuery.type(content)) {
  	case 'string':
  		this.content.html(content);
  		break;
  	case 'object':
  		this.content.html('');
  		content.attr('data-jbox-content-appended', 1).appendTo(this.content).css({
  			display: 'block'
  		});
  		break;
  	}
  
  	// Calculate the difference to before the content was set
  	var adjustOffset = {
  		x: bodyWidth - jQuery('body').width(),
  		y: bodyHeight - jQuery('body').height()
  	};
  
  	// Reposition if dimensions changed
  	!ignore_positioning && this.options.repositionOnContent && (wrapperHeight != this.wrapper.height() || wrapperWidth != this.wrapper.width()) && this.position({
  		adjustOffset: adjustOffset
  	});
  
  	return this;
  };
  
  // Set new dimensions
  jBox.prototype.setDimensions = function (type, val, pos) {
  
  	// Create jBox if no wrapper found
  	!this.wrapper && this._create();
  
  	// Default value is 'auto'
  	val == undefined && (val == 'auto');
  
  	// Set CSS of content
  	this.content.css(type, val);
  
  	// Reposition by default
  	(pos == undefined || pos) && this.position();
  };
  
  // Set width or height
  jBox.prototype.setWidth = function (val, pos) {
  	this.setDimensions('width', val, pos);
  };
  jBox.prototype.setHeight = function (val, pos) {
  	this.setDimensions('height', val, pos);
  };
  
  // Position jBox
  jBox.prototype.position = function (options) {
  	options || (options = {});
  
  	// Get target
  	this.target = options.target || this.target || this.options.target || jQuery(window);
  
  	// Cache total current dimensions of jBox
  	this.dimensions = {
  		x: this.wrapper.outerWidth(),
  		y: this.wrapper.outerHeight()
  	};
  
  	// Mousemove can't be positioned
  	if (this.target == 'mouse') return;
  
  	// Set percent and margin for centered inside
  	if (this.options.position.x == 'center' && this.options.position.y == 'center') {
  		this.wrapper.css({
  			left: '50%',
  			top: '50%',
  			marginLeft: (this.dimensions.x * -0.5 + this.options.offset.x),
  			marginTop: (this.dimensions.y * -0.5 + this.options.offset.y)
  		});
  		return this;
  	}
  
  	// Total current dimensions of target element
  	var targetOffset = this.target.offset();
  
  	// Add fixed data to target
  	!this.target.data('jBox-fixed') && this.target.data('jBox-fixed', (this.target[0] != jQuery(window)[0] && (this.target.css('position') == 'fixed' || this.target.parents().filter(function () {
  		return jQuery(this).css('position') == 'fixed';
  	}).length > 0)) ? 'fixed' : 'static');
  
  	// When the target is fixed and jBox is fixed, remove scroll offset
  	if (this.target.data('jBox-fixed') == 'fixed' && this.options.fixed) {
  		targetOffset.top = targetOffset.top - jQuery(window).scrollTop();
  		targetOffset.left = targetOffset.left - jQuery(window).scrollLeft();
  	}
  
  	// Store target dimensions
  	this.targetDimensions = {
  		x: this.target.outerWidth(),
  		y: this.target.outerHeight(),
  		top: (targetOffset ? targetOffset.top : 0),
  		left: (targetOffset ? targetOffset.left : 0)
  	};
  	this.pos = {};
  
  	// Calculate positions
  	var setPosition = function (p) {
  
  		// Set number positions
  		if (jQuery.inArray(this.options.position[p], ['top', 'right', 'bottom', 'left', 'center']) == -1) {
  			this.pos[this.options.attributes[p]] = this.options.position[p];
  			return;
  		}
  
  		// We have a target, so use 'left' or 'top' as attributes
  		var a = this.options.attributes[p] = (p == 'x' ? 'left' : 'top');
  
  		// Start at target position
  		this.pos[a] = this.targetDimensions[a];
  
  		// Set centered position
  		if (this.options.position[p] == 'center') {
  			this.pos[a] += Math.ceil((this.targetDimensions[p] - this.dimensions[p]) / 2);
  			return;
  		}
  
  		// Move inside
  		(a != this.options.position[p]) && (this.pos[a] += this.targetDimensions[p] - this.dimensions[p]);
  
  		// Move outside
  		(this.options.outside == p || this.options.outside == 'xy') && (this.pos[a] += this.dimensions[p] * (a != this.options.position[p] ? 1 : -1));
  
  	}.bind(this);
  
  	// Set position including offset
  	setPosition('x');
  	setPosition('y');
  
  	// Adjust position depending on pointer align
  	if (this.options.pointer && jQuery.type(this.options.position.x) != 'number' && jQuery.type(this.options.position.y) != 'number') {
  
  		var adjustWrapper = 0;
  
  		// Where is the pointer aligned? Add or substract accordingly
  		switch (this.pointer.align) {
  		case 'center':
  			if (this.options.position[this._getOpp(this.options.outside)] != 'center') {
  				adjustWrapper += (this.dimensions[this._getOpp(this.options.outside)] / 2);
  			}
  			break;
  		default:
  			switch (this.options.position[this._getOpp(this.options.outside)]) {
  			case 'center':
  				adjustWrapper += ((this.dimensions[this._getOpp(this.options.outside)] / 2) - (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2)) * (this.pointer.align == this._getTL(this.pointer.align) ? 1 : -1);
  				break;
  			default:
  				adjustWrapper += (this.pointer.align != this.options.position[this._getOpp(this.options.outside)]) ?
  
  					// If pointer align is different to position align
  					(this.dimensions[this._getOpp(this.options.outside)] * (jQuery.inArray(this.pointer.align, ['top', 'left']) !== -1 ? 1 : -1)) + ((this.pointer.dimensions[this._getOpp(this.options.outside)] / 2) * (jQuery.inArray(this.pointer.align, ['top', 'left']) !== -1 ? -1 : 1)) :
  
  					// If pointer align is same as position align
  					(this.pointer.dimensions[this._getOpp(this.options.outside)] / 2) * (jQuery.inArray(this.pointer.align, ['top', 'left']) !== -1 ? 1 : -1);
  				break;
  			}
  			break;
  		}
  		adjustWrapper *= (this.options.position[this._getOpp(this.options.outside)] == this.pointer.alignAttribute ? -1 : 1);
  		adjustWrapper += this.pointer.offset * (this.pointer.align == this._getOpp(this._getTL(this.pointer.align)) ? 1 : -1);
  
  		this.pos[this._getTL(this._getOpp(this.pointer.xy))] += adjustWrapper;
  	}
  
  	// Add adjustments
  	options.adjustOffset && options.adjustOffset.x && (this.pos[this.options.attributes.x] += parseInt(options.adjustOffset.x) * (this.options.attributes.x == 'left' ? 1 : -1));
  	options.adjustOffset && options.adjustOffset.y && (this.pos[this.options.attributes.y] += parseInt(options.adjustOffset.y) * (this.options.attributes.y == 'top' ? 1 : -1));
  
  	// Add final offset
  	this.pos[this.options.attributes.x] += this.options.offset.x;
  	this.pos[this.options.attributes.y] += this.options.offset.y;
  
  	// Set CSS
  	this.wrapper.css(this.pos);
  
  	// Adjust position
  	this._adjustPosition();
  
  	return this;
  };
  
  // Open jBox
  jBox.prototype.open = function (options) {
  	options || (options = {});
  
  	// Abort if jBox was destroyed
  	if (this.isDestroyed) return false;
  
  	// Construct jBox if not already constructed
  	!this.wrapper && this._create();
  
  	// Abort any opening or closing timer
  	this.timer && clearTimeout(this.timer);
  
  	// Block body click for 10ms, so jBox can open on attached elements while closeOnClick = 'body'
  	this._blockBodyClick();
  
  	// Block opening
  	if (this.isDisabled) return this;
  
  	// Opening function
  	var open = function () {
  
  		// Set title from source element
  		this.source && this.options.getTitle && (this.source.attr(this.options.getTitle) && this.setTitle(this.source.attr(this.options.getTitle)), true);
  
  		// Set content from source element
  		this.source && this.options.getContent && (this.source.data('jBox-getContent') ? this.setContent(this.source.data('jBox-getContent'), true) : (this.source.attr(this.options.getContent) ? this.setContent(this.source.attr(this.options.getContent), true) : null));
  
  		// Fire onOpen event
  		this._fireEvent('onOpen');
  
  		// Get content from ajax
  		((this.options.ajax && this.options.ajax.url && (!this.ajaxLoaded || this.options.ajax.reload)) || (options.ajax && options.ajax.url)) && this.ajax(options.ajax || null);
  
  		// Set position
  		(!this.positionedOnOpen || this.options.repositionOnOpen) && this.position({
  			target: options.target
  		}) && (this.positionedOnOpen = true);
  
  		// Abort closing
  		this.isClosing && this._abortAnimation();
  
  		// Open functions to call when jBox is closed
  		if (!this.isOpen) {
  
  			// jBox is open now
  			this.isOpen = true;
  
  			// Attach events
  			this._attachEvents();
  
  			// Block scrolling
  			this.options.blockScroll && jQuery('body').addClass('jBox-blockScroll-' + this.id);
  
  			// Add overlay
  			this.options.overlay && this._addOverlay();
  
  			// Only animate if jBox is compleately closed
  			this.options.animation && !this.isClosing && this._animate('open');
  
  			// Fading animation or show immediately
  			if (this.options.fade) {
  				this.wrapper.stop().animate({
  					opacity: 1
  				}, {
  					queue: false,
  					duration: this.options.fade,
  					start: function () {
  						this.isOpening = true;
  						this.wrapper.css({
  							display: 'block'
  						});
  					}.bind(this),
  					always: function () {
  						this.isOpening = false;
  					}.bind(this)
  				});
  			} else {
  				this.wrapper.css({
  					display: 'block',
  					opacity: 1
  				});
  			}
  		}
  	}.bind(this);
  
  	// Open jBox
  	this.options.delayOpen && !this.isOpen && !this.isClosing && !options.ignoreDelay ? (this.timer = setTimeout(open, this.options.delayOpen)) : open();
  
  	return this;
  };
  
  // Close jBox
  jBox.prototype.close = function (options) {
  	options || (options = {});
  
  	// Abort if jBox was destroyed
  	if (this.isDestroyed) return false;
  
  	// Abort opening
  	this.timer && clearTimeout(this.timer);
  
  	// Block body click for 10ms, so jBox can open on attached elements while closeOnClock = 'body' is true
  	this._blockBodyClick();
  
  	// Block closing
  	if (this.isDisabled) return this;
  
  	// Close function
  	var close = function () {
  
  		// Fire onClose event
  		this._fireEvent('onClose');
  
  		// Only close if jBox is open
  		if (this.isOpen) {
  
  			// jBox is not open anymore
  			this.isOpen = false;
  
  			// Detach events
  			this._detachEvents();
  
  			// Unblock scrolling
  			this.options.blockScroll && jQuery('body').removeClass('jBox-blockScroll-' + this.id);
  
  			// Remove overlay
  			this.options.overlay && this._removeOverlay();
  
  			// Only animate if jBox is compleately closed
  			this.options.animation && !this.isOpening && this._animate('close');
  
  			// Fading animation or show immediately
  			if (this.options.fade) {
  				this.wrapper.stop().animate({
  					opacity: 0
  				}, {
  					queue: false,
  					duration: this.options.fade,
  					start: function () {
  						this.isClosing = true;
  					}.bind(this),
  					complete: function () {
  						this.wrapper.css({
  							display: 'none'
  						});
  						this.options.onCloseComplete && (this.options.onCloseComplete.bind(this))();
  						this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))();
  					}.bind(this),
  					always: function () {
  						this.isClosing = false;
  					}.bind(this)
  				});
  			} else {
  				this.wrapper.css({
  					display: 'none',
  					opacity: 0
  				});
  				this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))();
  			}
  		}
  	}.bind(this);
  
  	// Close jBox
  	options.ignoreDelay ? close() : (this.timer = setTimeout(close, Math.max(this.options.delayClose, 10)));
  
  	return this;
  };
  
  // Open or close jBox
  jBox.prototype.toggle = function (options) {
  	this[this.isOpen ? 'close' : 'open'](options);
  	return this;
  };
  
  // Block opening and closing
  jBox.prototype.disable = function () {
  	this.isDisabled = true;
  	return this;
  };
  
  // Unblock opening and closing
  jBox.prototype.enable = function () {
  	this.isDisabled = false;
  	return this;
  };
  
  // Get content from ajax
  jBox.prototype.ajax = function (options) {
  	options || (options = {});
  
  	// Add data from source element if none set in options
  	(this.options.ajax.getData && !options.data && this.source && this.source.attr(this.options.ajax.getData) != undefined) && (options.data = this.source.attr(this.options.ajax.getData) || '');
  
  	// Clone the system options
  	var sysOptions = jQuery.extend(true, {}, this.options.ajax);
  
  	// Abort running ajax call
  	this.ajaxRequest && this.ajaxRequest.abort();
  
  	// Extract events
  	var beforeSend = options.beforeSend || sysOptions.beforeSend || function () {};
  	var complete = options.complete || sysOptions.complete || function () {};
  
  	// Merge options
  	var userOptions = jQuery.extend(true, sysOptions, options);
  
  	// Set new beforeSend event
  	userOptions.beforeSend = function () {
  
  		// Add loading spinner
  		if (userOptions.spinner) {
  			this.wrapper.addClass('jBox-loading');
  			this.spinner = jQuery(userOptions.spinner !== true ? userOptions.spinner : '<div class="jBox-spinner"></div>').appendTo(this.container);
  		}
  
  		(beforeSend.bind(this))();
  	}.bind(this);
  
  	// Set new complete event
  	userOptions.complete = function (response) {
  
  		// Remove spinner
  		this.wrapper.removeClass('jBox-loading');
  		this.spinner && this.spinner.remove();
  
  		// Set new content
  		userOptions.setContent && this.setContent(response.responseText);
  
  		this.ajaxLoaded = true;
  
  		(complete.bind(this))(response);
  	}.bind(this);
  
  	// Send new ajax request
  	this.ajaxRequest = jQuery.ajax(userOptions);
  
  	return this;
  };
  
  // Play an audio file
  jBox.prototype.audio = function (options) {
  	options || (options = {});
  	jBox._audio || (jBox._audio = {});
  
  	// URL required, no IE8 support
  	if (!options.url || this.IE8) return this;
  
  	// Create audio if it doesn't exist
  	if (!jBox._audio[options.url]) {
  		var audio = jQuery('<audio/>');
  		jQuery('<source/>', {
  			src: options.url + '.mp3'
  		}).appendTo(audio);
  		jQuery('<source/>', {
  			src: options.url + '.ogg'
  		}).appendTo(audio);
  		jBox._audio[options.url] = audio[0];
  	}
  
  	// Set volume and play audio
  	jBox._audio[options.url].volume = Math.min((options.volume != undefined ? options.volume : (this.options.volume != undefined ? this.options.volume : 100) / 100), 1);
  	jBox._audio[options.url].pause();
  	try {
  		jBox._audio[options.url].currentTime = 0;
  	} catch (e) {}
  	jBox._audio[options.url].play();
  
  	return this;
  };
  
  // Destroy jBox and remove it from DOM
  // TODO: If no other jBox needs an overlay remove it as well
  jBox.prototype.destroy = function () {
  	this.detach().close({
  		ignoreDelay: true
  	});
  	this.wrapper && this.wrapper.remove();
  	this.isDestroyed = true;
  	return this;
  };
  
  // TODO: Find an option to preload audio files
  
  // Get a unique ID for jBoxes
  jBox._getUniqueID = (function () {
  	var i = 1;
  	return function () {
  		return i++;
  	};
  }());
  
  // Make jBox usable with jQuery selectors
  jQuery.fn.jBox = function (type, options) {
  	type || (type = {});
  	options || (options = {});
  	return new jBox(type, jQuery.extend(options, {
  		attach: this
  	}));
  };
  
  // Add the .bind() function for IE 8 support
  if (!Function.prototype.bind) {
  	Function.prototype.bind = function (oThis) {
  		var aArgs = Array.prototype.slice.call(arguments, 1),
  			fToBind = this,
  			fNOP = function () {},
  			fBound = function () {
  				return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
  			};
  		fNOP.prototype = this.prototype;
  		fBound.prototype = new fNOP();
  		return fBound;
  	};
  }