PageSourceSearch

https://illerich.de/wp-content/plugins/Lime-popups-toolkit/library/jBox.js?ver=7.1.2

js illerich.de collected 2026-10-02 16:58:11 UTC 63,948 bytes, 1,509 lines download raw bytes

1/*
2---
3description: jBox is a powerful and flexible jQuery plugin, taking care of all your modal windows, tooltips, notices and more.
4
5authors: Stephan Wagner (http://stephanwagner.me)
6
7license: MIT (http://www.opensource.org/licenses/mit-license.php)
8
9requires: jQuery 1.11.0 (http://code.jquery.com/jquery-1.11.0.min.js)
10          jQuery 2.1.0 (http://code.jquery.com/jquery-2.1.0.min.js)
11
12documentation: http://stephanwagner.me/jBox/documentation
13
14demos: http://stephanwagner.me/jBox/demos
15...
16*/
17
18function jBox(type, options) {
19	
20	this.options = {
21		
22		// jBox ID
23		id: null,					// Choose a unique id, otherwise jBox will set one for you (jBoxID1, jBoxID2, ...)
24		
25		// Dimensions
26		width: 'auto',				// Width of content area (e.g. 'auto', 100)
27		height: 'auto',				// Height of content area
28		minWidth: null,				// Minimum width
29		maxHeight: null,			// Minimum height
30		minWidth: null,				// Maximum width
31		maxHeight: null,			// Minimum height
32		
33		// Attach
34		attach: null,				// Attach jBox to elements (if no target element is provided, jBox will use the attached element as target)
35		trigger: 'click',			// The event to open or close your jBoxes, use 'click' or 'mouseenter'
36		preventDefault: false,		// Prevent default event when opening jBox (e.g. don't follow the href in a link when clicking on it)
37		
38		// Content
39		title: null,				// Adds a title to your jBox
40		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)
41		getTitle: null,				// Get the title from an attribute when jBox opens
42		getContent: null,			// Get the content from an attribute when jBox opens
43		
44		// AJAX request
45		ajax: {						// Setting an url will make an AJAX call when jBox opens
46			url: null,				// URL to send the AJAX request to
47			data: '',				// Data to send with your AJAX call (e.g. 'id=82&limit=10')
48									// Optional you can add any jQuery AJAX option (http://api.jquery.com/jquery.ajax/)
49			reload: false,			// Resend the ajax call every time jBox opens
50			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"
51			setContent: true,		// Automatically set the response as new content when the AJAX call is finished
52			spinner: true			// Hides the current content and adds a spinner while loading
53		},
54		
55		// Position
56		target: null,				// The target element where jBox will be opened
57		position: {
58			x: 'center',			// Horizontal Position (Use a number, 'left', 'right' or 'center')
59			y: 'center'				// Vertical Position (Use a number, 'top', 'bottom' or 'center')
60		},
61		outside: null,				// Use 'x', 'y', or 'xy' to move your jBox outside of the target element
62		offset: 0,					// Offset to final position, you can set different values for x and y with an object e.g. {x: 15, y: 0}
63		
64		attributes: {				// Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20}
65			x: 'left',				// Horizontal position, use 'left' or 'right'
66			y: 'top'				// Vertical position, use 'top' or 'bottom'
67		},
68		adjustPosition: false,		// Adjusts the position when there is not enough space (use true, 'flip' or 'move')
69		adjustTracker: false,		// By default jBox adjusts the position when opening, to adjust when scrolling or resizing, use 'scroll', 'resize' or 'true' (both events)
70		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}
71		fixed: false,				// Your jBox will stay on position when scrolling
72		reposition: false,			// Calculates new position when the window-size changes
73		
74		// Pointer
75		pointer: false,				// Your pointer will always point towards the target element, so the option outside should be 'x' or 'y'
76		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')
77		
78		// Animations
79		fade: 180,					// Fade duration in ms, set to 0 or false to disable
80		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)
81		
82		// Appearance
83		theme: 'Default',			// Set a jBox theme class
84		addClass: '',				// Adds classes to the wrapper
85		overlay: false,				// Adds an overlay when jBox opens (set color and opacity with CSS)
86		zIndex: 10000,				// Use a high zIndex (your overlay will have the lowest zIndex of all your jBoxes (with overlays) minus one)
87		
88		// Delays
89		delayOpen: 0,				// Delay opening in ms (Note that the delay will be ignored if your jBox didn't finish closing)
90		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)
91		
92		// Closing events
93		closeOnEsc: false,			// Close jBox when pressing [esc] key
94		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)
95		closeOnMouseleave: false,	// Close jBox when the mouse leaves the jBox area or the area of the attached element
96		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)
97		
98		// Other options
99		constructOnInit: false,		// Construct jBox when it's being initialized
100		blockScroll: false,			// When jBox is open, block scrolling
101		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)
102		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/)
103		dragOver: true,				// When you have multiple draggable jBoxes, the one you select will always move over the other ones
104		
105		// Events						// Note: You can use 'this' in the event functions, it refers to your jBox object (e.g. onInit: function() { this.open(); })
106		onInit: function() {},			// Triggered when jBox is initialized, just before it's being created
107		onCreated: function() {},		// Triggered when jBox is created and is availible in DOM
108		onOpen: function() {},			// Triggered when jBox is opened
109		onClose: function() {},			// Triggered when jBox is closed
110		onCloseComplete: function() {},	// Triggered when jBox is completely closed (when fading is finished, useful if you want to destroy the jBox when it is closed)
111		
112		// Only for type "Confirm"
113		confirmButton: 'Submit',	// Text for the submit button
114		cancelButton: 'Cancel',		// Text for the cancel button
115		
116		// Only for type "Notice"
117		autoClose: 7000,			// Time when jBox should close automatically
118		color: null,				// Makes your notices colorful, use 'black', 'red', 'green', 'blue', 'yellow'
119		stack: true,				// Set to false to disable notice-stacking
120		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
121		volume: 100,				// Percent of volume for audio files
122		
123		// Only for type "Image"
124		src: 'href',				// The attribute where jBox gets the image source from, e.g. href="/path_to_image/image.jpg"
125		gallery: 'data-jbox-image',	// The attribute where you define the image gallery, e.g. data-jbox-image="gallery1"
126		imageLabel: 'title',		// The attribute where jBox gets the image label from, e.g. title="My label"
127		imageFade: 600,				// The fade duration for images
128		imageSize: 'cover'			// How to display the images: Use CSS background-position values, e.g. 'cover', 'contain', 'auto', 'initial', '50% 50%'
129	};
130	
131	// Default type options
132	this.defaultOptions = {
133		// Default options for tooltips
134		'Tooltip': {
135			getContent: 'title',
136			trigger: 'mouseenter',
137			position: {x: 'center', y: 'top'},
138			outside: 'y',
139			pointer: true,
140			adjustPosition: true,
141			reposition: true
142		},
143		// Default options for mouse tooltips
144		'Mouse': {
145			target: 'mouse',
146			position: {x: 'right', y: 'bottom'},
147			offset: 15,
148			trigger: 'mouseenter',
149			adjustPosition: 'flip'
150		},
151		// Default options for modal windows
152		'Modal': {
153			target: jQuery(window),
154			fixed: true,
155			blockScroll: true,
156			closeOnEsc: true,
157			closeOnClick: 'overlay',
158			closeButton: true,
159			overlay: true,
160			animation: 'zoomOut'
161		},
162		// Default options for modal confirm windows
163		'Confirm': {
164			target: jQuery(window),
165			fixed: true,
166			attach: jQuery('[data-confirm]'),
167			getContent: 'data-confirm',
168			content: 'Do you really want to do this?',
169			minWidth: 320,
170			maxWidth: 460,
171			blockScroll: true,
172			closeOnEsc: true,
173			closeOnClick: 'overlay',
174			closeButton: true,
175			overlay: true,
176			animation: 'zoomOut',
177			preventDefault: true,
178			_onAttach: function(el) {
179				// Extract the href or the onclick event
180				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') + '";') : '');
181				el.prop('onclick', null).data('jBox-Confirm-submit', submit);
182			},
183			_onCreated: function() {
184				// Add a footer to the jBox container
185				this.footer = jQuery('<div class="jBox-Confirm-footer"/>');
186				jQuery('<div class="jBox-Confirm-button jBox-Confirm-button-cancel"/>').html(this.options.cancelButton).click(function() { this.close(); }.bind(this)).appendTo(this.footer);
187				this.submitButton = jQuery('<div class="jBox-Confirm-button jBox-Confirm-button-submit"/>').html(this.options.confirmButton).appendTo(this.footer);
188				this.footer.appendTo(this.container);
189			},
190			_onOpen: function() {
191				// Set the new action for the submit button
192				this.submitButton.off('click.jBox-Confirm' + this.id).on('click.jBox-Confirm' + this.id, function() { eval(this.source.data('jBox-Confirm-submit')); this.close(); }.bind(this));
193			}
194		},
195		// Default options for notices
196		'Notice': {
197			target: jQuery(window),
198			fixed: true,
199			position: {x: 20, y: 20},
200			attributes: {x: 'right', y: 'top'},
201			animation: 'zoomIn',
202			closeOnClick: 'box',
203			_onInit: function () {
204				this.open();
205				this.options.delayClose = this.options.autoClose;
206				this.options.delayClose && this.close();
207			},
208			_onCreated: function() {
209				this.options.color && this.wrapper.addClass('jBox-Notice-color jBox-Notice-' + this.options.color);
210				this.wrapper.data('jBox-Notice-position', this.options.attributes.x + '-' + this.options.attributes.y);
211			}, 
212			_onOpen: function() {
213				// Loop through notices at same window corner and either move or destroy them
214				jQuery.each(jQuery('.jBox-Notice'), function(index, el) {
215					el = jQuery(el);
216					
217					if (el.attr('id') == this.id || el.data('jBox-Notice-position') != this.options.attributes.x + '-' + this.options.attributes.y) return;
218					if (!this.options.stack) {
219						el.data('jBox').close({ignoreDelay: true});
220						return;
221					}
222					el.css('margin-' + this.options.attributes.y, parseInt(el.css('margin-' + this.options.attributes.y)) + this.wrapper.outerHeight() + 10);
223				}.bind(this));
224				
225				// Play audio file, IE8 doesn't support audio
226				this.options.audio && this.audio({url: this.options.audio, valume: this.options.volume});
227			},
228			// Remove notice from DOM when closing finishes
229			_onCloseComplete: function() {
230				this.destroy();
231			}
232		},
233		// Default options for images
234		'Image': {
235			target: jQuery(window),
236			fixed: true,
237			blockScroll: true,
238			closeOnEsc: true,
239			closeOnClick: 'overlay',
240			closeButton: true,
241			overlay: true,
242			animation: 'zoomOut',
243			width: 800,
244			height: 533,
245			attach: jQuery('[data-jbox-image]'),
246			preventDefault: true,
247			
248			// TODO: What if the image is not found?
249			// 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
250			
251			_onInit: function() {
252				this.images = this.currentImage = {};
253				this.imageZIndex = 1;
254				
255				// Loop through images, sort and save in global variable
256				jQuery.each(this.attachedElements, function (index, item) {
257					item = jQuery(item);
258					if (item.data('jBox-image-gallery')) return;
259					var gallery = item.attr(this.options.gallery) || 'default';
260					!this.images[gallery] && (this.images[gallery] = []);
261					this.images[gallery].push({src: item.attr(this.options.src), label: (item.attr(this.options.imageLabel) || '')});
262					
263					// Remove the title attribute so it won't show the browsers tooltip
264					this.options.imageLabel == 'title' && item.removeAttr('title');
265					
266					// Store data in source element for easy access
267					item.data('jBox-image-gallery', gallery);
268					item.data('jBox-image-id', (this.images[gallery].length - 1));
269				}.bind(this));
270				
271				// Helper to inject the image into content area
272				var appendImage = function(gallery, id, preload, open) {
273					if (jQuery('#jBox-image-' + gallery + '-' + id).length) return;
274					
275					var image = jQuery('<div/>', {
276						id: 'jBox-image-' + gallery + '-' + id,
277						'class': 'jBox-image-container'
278					}).css({
279						backgroundImage: 'url(' + this.images[gallery][id].src + ')',
280						backgroundSize: this.options.imageSize,
281						opacity: (open ? 1 : 0),
282						zIndex: (preload ? 0 : this.imageZIndex++)
283					}).appendTo(this.content);
284					
285					var text = jQuery('<div/>', {
286						id: 'jBox-image-label-' + gallery + '-' + id,
287						'class': 'jBox-image-label' + (open ? ' active' : '')
288					}).html(this.images[gallery][id].label).appendTo(this.imageLabel);
289					
290					!open && !preload && image.animate({opacity: 1}, this.options.imageFade);
291				}.bind(this);
292				
293				// Helper to show new Image label
294				var showLabel = function(gallery, id) {
295					jQuery('.jBox-image-label.active').removeClass('active');
296					jQuery('#jBox-image-label-' + gallery + '-' + id).addClass('active');
297				};
298				
299				// Show images when they are loaded or load them if not
300				this.showImage = function(img) {
301					// Get the gallery and the image id from the next or the previous image
302					if (img != 'open') {
303						var gallery = this.currentImage.gallery;
304						var id = this.currentImage.id + (1 * (img == 'prev') ? -1 : 1);
305						id = id > (this.images[gallery].length - 1) ? 0 : (id < 0 ? (this.images[gallery].length - 1) : id);
306					// Or get image data from source element
307					} else {
308						var gallery = this.source.data('jBox-image-gallery');
309						var id = this.source.data('jBox-image-id');
310						
311						// Remove or show the next and prev buttons
312						jQuery('.jBox-image-pointer-prev, .jBox-image-pointer-next').css({display: (this.images[gallery].length > 1 ? 'block' : 'none')});
313					}
314					
315					// Set new current image
316					this.currentImage = {gallery: gallery, id: id};
317					
318					// Show image if it already exists
319					if (jQuery('#jBox-image-' + gallery + '-' + id).length) {
320						jQuery('#jBox-image-' + gallery + '-' + id).css({zIndex: this.imageZIndex++, opacity: 0}).animate({opacity: 1}, (img == 'open') ? 0 : this.options.imageFade);
321						showLabel(gallery, id);
322						
323					// Load image if not found
324					} else {
325						// TODO loading not working properly anymore
326						this.wrapper.addClass('jBox-loading');
327						var image = jQuery('<img src="' + this.images[gallery][id].src + '">').load(function() {
328							appendImage(gallery, id, false);
329							showLabel(gallery, id);
330							this.wrapper.removeClass('jBox-loading');
331						}.bind(this));
332					}
333					
334					// Preload next image
335					var next_id = id + 1;
336					next_id = next_id > (this.images[gallery].length - 1) ? 0 : (next_id < 0 ? (this.images[gallery].length - 1) : next_id);
337					
338					(!jQuery('#jBox-image-' + gallery + '-' + next_id).length) && jQuery('<img src="' + this.images[gallery][next_id].src + '">').load(function() {
339						appendImage(gallery, next_id, true);
340					});
341				};
342			},
343			_onCreated: function() {
344				this.imageLabel = jQuery('<div/>', {'id': 'jBox-image-label'}).appendTo(this.wrapper);
345				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)}));
346			},
347			_onOpen: function() {
348				// Add a class to body so you can control the appearance of the overlay, fo rimages a darker one is better
349				jQuery('body').addClass('jBox-image-open');
350				
351				// Add key events
352				jQuery(document).on('keyup.jBox-' + this.id, function(ev) {
353					(ev.keyCode == 37) && this.showImage('prev');
354					(ev.keyCode == 39) && this.showImage('next');
355				}.bind(this));
356				
357				// Load the image from the attached element
358				this.showImage('open');
359			},
360			_onClose: function() {
361				jQuery('body').removeClass('jBox-image-open');
362				
363				// Remove key events
364				jQuery(document).off('keyup.jBox-' + this.id);
365			},
366			_onCloseComplete: function() {
367				// Hide all images
368				this.wrapper.find('.jBox-image-container').css('opacity', 0);
369			}
370		}
371	};
372	
373	// Set default options for jBox types
374	if (jQuery.type(type) == 'string') {
375		this.type = type;
376		type = this.defaultOptions[type];
377	}
378	
379	// Merge options
380	this.options = jQuery.extend(true, this.options, type, options);
381	
382	// Get unique ID
383	if (this.options.id === null) {
384		this.options.id = 'jBoxID' + jBox._getUniqueID();
385	}
386	this.id = this.options.id;
387	
388	// Correct impossible options
389	((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = false);
390	(!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false);
391	
392	// Correct multiple choice options
393	jQuery.type(this.options.offset) != 'object' && (this.options.offset = {x: this.options.offset, y: this.options.offset});
394	this.options.offset.x || (this.options.offset.x = 0);
395	this.options.offset.y || (this.options.offset.y = 0);
396	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});
397	
398	// Save where the jBox is aligned to
399	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));
400	
401	// Save default outside position
402	this.options.outside && this.options.outside != 'xy' && (this.outside = this.options.position[this.options.outside]);
403	
404	// I know browser detection is bad practice, but for now it seems the only option to get jBox working in IE8
405	var userAgent = navigator.userAgent.toLowerCase();
406	this.IE8 = userAgent.indexOf('msie') != -1 && parseInt(userAgent.
406split('msie')[1]) == 8;
407	
408	// Save global var for webkit prefix
409	this.prefix = userAgent.indexOf('webkit') != -1 ? '-webkit-' : '';
410	
411	// Internal functions, used to easily get values
412	this._getOpp = function(opp) { return {left: 'right', right: 'left', top: 'bottom', bottom: 'top', x: 'y', y: 'x'}[opp]; };
413	this._getXY = function(xy) { return {left: 'x', right: 'x', top: 'y', bottom: 'y', center: 'x'}[xy]; };
414	this._getTL = function(tl) { return {left: 'left', right: 'left', top: 'top', bottom: 'top', center: 'left', x: 'left', y: 'top'}[tl]; };
415	
416	// Create jBox
417	this._create = function() {
418		if (this.wrapper) return;
419		
420		// Create wrapper
421		this.wrapper = jQuery('<div/>', {
422			id: this.id,
423			'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' : '')
424		}).css({
425			position: (this.options.fixed ? 'fixed' : 'absolute'),
426			display: 'none',
427			opacity: 0,
428			zIndex: this.options.zIndex
429			
430		// Save the jBox instance in the wrapper, so you can get access to your jBox when you only have the element
431		}).data('jBox', this);
432		
433		// Add mouseleave event (.parents('*') might be a performance nightmare! Maybe there is a better)
434		this.options.closeOnMouseleave && this.wrapper.mouseleave(function(ev) {
435			// Only close when the new target is not the source element
436			!this.source || !(ev.relatedTarget == this.source[0] || jQuery.inArray(this.source[0], jQuery(ev.relatedTarget).parents('*')) !== -1) && this.close();
437		}.bind(this));
438		
439		// Add closeOnClick: 'box' events
440		(this.options.closeOnClick == 'box') && this.wrapper.on('touchend click', function() { this.close({ignoreDelay: true}); }.bind(this));
441		
442		// Create container
443		this.container = jQuery('<div/>', {'class': 'jBox-container'}).appendTo(this.wrapper);
444		
445		// Create content
446		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);
447		
448		// Create close button
449		if (this.options.closeButton) {
450			this.closeButton = jQuery('<div/>', {'class': 'jBox-closeButton jBox-noDrag'}).on('touchend click', function(ev) { this.isOpen && this.close({ignoreDelay: true}); }.bind(this));
451			var closeButtonSVG = this._createSVG('svg', [['viewBox', '0 0 24 24']]);
452			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);
453			this.closeButton.append(closeButtonSVG);
454			
455			// Add close button to jBox container
456			if (this.options.closeButton == 'box' || (this.options.closeButton === true && !this.options.overlay && !this.options.title)) {
457				this.wrapper.addClass('jBox-closeButton-box');
458				this.closeButton.appendTo(this.container);
459			}
460		}
461		
462		// Append jBox to DOM
463		this.wrapper.appendTo(this.options.appendTo);
464		
465		// Create pointer
466		if (this.options.pointer) {
467			
468			// Get pointer vars and save globally
469			this.pointer = {
470				position: (this.options.pointTo != 'target') ? this.options.pointTo : this._getOpp(this.outside),
471				xy: (this.options.pointTo != 'target') ? this._getXY(this.options.pointTo) : this._getXY(this.outside),
472				align: 'center',
473				offset: 0
474			};
475			
476			this.pointer.element = jQuery('<div/>', {'class': 'jBox-pointer jBox-pointer-' + this.pointer.position}).appendTo(this.wrapper);
477			this.pointer.dimensions = {
478				x: this.pointer.element.outerWidth(),
479				y: this.pointer.element.outerHeight()
480			};
481			
482			if (jQuery.type(this.options.pointer) == 'string') {
483				var split = this.options.pointer.split(':');
484				split[0] && (this.pointer.align = split[0]);
485				split[1] && (this.pointer.offset = parseInt(split[1]));
486			}
487			this.pointer.alignAttribute = (this.pointer.xy == 'x' ? (this.pointer.align == 'bottom' ? 'bottom' : 'top') : (this.pointer.align == 'right' ? 'right' : 'left'));
488			
489			// Set wrapper CSS
490			this.wrapper.css('padding-' + this.pointer.position, this.pointer.dimensions[this.pointer.xy]);
491			
492			// Set pointer CSS
493			this.pointer.element.css(this.pointer.alignAttribute, (this.pointer.align == 'center' ? '50%' : 0)).css('margin-' + this.pointer.alignAttribute, this.pointer.offset);
494			this.pointer.margin = {}; this.pointer.margin['margin-' + this.pointer.alignAttribute] = this.pointer.offset;
495			
496			// Add a transform to fix centered position
497			(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) + ')');
498			
499			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')));
500			
501			// Add class to wrapper for CSS access
502			this.wrapper.addClass('jBox-pointerPosition-' + this.pointer.position);
503		}
504		
505		// Set title and content
506		this.setContent(this.options.content, true);
507		this.setTitle(this.options.title, true);
508		
509		// Make jBox draggable
510		if (this.options.draggable) {
511			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));
512			handle.addClass('jBox-draggable').on('mousedown', function(ev) {
513				if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return;
514				
515				if (this.options.dragOver && this.wrapper.css('zIndex') <= jBox.zIndexMax) {
516					jBox.zIndexMax += 1;
517					this.wrapper.css('zIndex', jBox.zIndexMax);
518				}
519				
520				var drg_h = this.wrapper.outerHeight(),
521					drg_w = this.wrapper.outerWidth(),
522					pos_y = this.wrapper.offset().top + drg_h - ev.pageY,
523					pos_x = this.wrapper.offset().left + drg_w - ev.pageX;
524				jQuery(document).on('mousemove.jBox-draggable-' + this.id, function(ev) {
525					this.wrapper.offset({
526						top: ev.pageY + pos_y - drg_h,
527						left: ev.pageX + pos_x - drg_w
528					});
529				}.bind(this));
530				ev.preventDefault();
531			}.bind(this)).on('mouseup', function() { jQuery(document).off('mousemove.jBox-draggable-' + this.id); }.bind(this));
532			
533			// Add z-index
534			jBox.zIndexMax = !jBox.zIndexMax ? this.options.zIndex : Math.max(jBox.zIndexMax, this.options.zIndex);
535		}
536
537		// Fire onCreated event
538		(this.options.onCreated.bind(this))();
539		this.options._onCreated && (this.options._onCreated.bind(this))();
540	};
541	
542	// Create jBox onInit
543	this.options.constructOnInit && this._create();
544	
545	// Attach jBox
546	this.options.attach && this.attach();
547	
548	// Position jBox on mouse
549	this._positionMouse = function(ev) {
550			
551		// Calculate positions
552		this.pos = {
553			left: ev.pageX,
554			top: ev.pageY
555		};
556		var setPosition = function(a, p) {
557			// Set centered position
558			if (this.options.position[p] == 'center') {
559				this.pos[a] -= Math.ceil(this.dimensions[p] / 2);
560				return;
561			}
562			// Move to left or top
563			this.pos[a] += (a == this.options.position[p]) ? ((this.dimensions[p] * -1) - this.options.offset[p]) : this.options.offset[p];
564			
565			return this.pos[a];
566		}.bind(this);
567		
568		// Set position to wrapper
569		this.wrapper.css({
570			left: setPosition('left', 'x'),
571			top: setPosition('top', 'y')
572		});
573		
574		// Adjust mouse position
575		this.targetDimensions = {x: 0, y: 0, left: ev.pageX, top: ev.pageY};
576		this._adjustPosition();
577	};
578	
579	// Attach document and window events
580	this._attachEvents = function() {
581		
582		// Closing event: closeOnEsc
583		this.options.closeOnEsc && jQuery(document).on('keyup.jBox-' + this.id, function(ev) { if (ev.keyCode == 27) { this.close({ignoreDelay: true}); }}.bind(this));
584		
585		// Closing event: closeOnClick
586		if (this.options.closeOnClick === true || this.options.closeOnClick == 'body') {
587			jQuery(document).on('touchend.jBox-' + this.id + ' click.jBox-' + this.id, function(ev) {
588				if (this.blockBodyClick || (this.options.closeOnClick == 'body' && (ev.target == this.wrapper[0] || this.wrapper.has(ev.target).length))) 
589					return;
590				this.close({ignoreDelay: true});
591			}.bind(this));
592		}
593		
594		// Positioning events
595		if (((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) && !this.fixed && this.outside) {
596		
597			var scrollTimer,
598				scrollTimerTriggered = 0,
599				scrollTriggerDelay = 150;	// Trigger scroll and resize events every 150 ms (set a higher value to improve performance)
600			
601			// Function to delay positioning event
602			var positionDelay = function () {
603				var now = new Date().getTime();
604				if (!scrollTimer) {
605					if (now - scrollTimerTriggered > scrollTriggerDelay) {
606						this.options.reposition && this.position();
607						this.options.adjustTracker && this._adjustPosition();
608						scrollTimerTriggered = now;
609					}
610					scrollTimer = setTimeout(function() {
611						scrollTimer = null;
612						scrollTimerTriggered = new Date().getTime();
613						this.options.reposition && this.position();
614						this.options.adjustTracker && this._adjustPosition();
615					}.bind(this), scrollTriggerDelay);
616				}
617			}.bind(this);
618			
619			// Trigger position events when scrolling
620			(this.options.adjustTracker && this.options.adjustTracker != 'resize') && jQuery(window).on('scroll.jBox-' + this.id, function(ev) { positionDelay(); }.bind(this));
621			
622			// Trigger position events when resizing
623			((this.options.adjustTracker && this.options.adjustTracker != 'scroll') || this.options.reposition) && jQuery(window).on('resize.jBox-' + this.id, function(ev) { positionDelay(); }.bind(this));
624		}
625		
626		// Mousemove events
627		this.options.target == 'mouse' && jQuery('body').on('mousemove.jBox-' + this.id, function(ev) { this._positionMouse(ev); }.bind(this));
628	};
629	
630	// Detach document and window events
631	this._detachEvents = function() {
632		
633		// Closing event: closeOnEsc
634		this.options.closeOnEsc && jQuery(document).off('keyup.jBox-' + this.id);
635		
636		// Closing event: closeOnClick
637		(this.options.closeOnClick === true || this.options.closeOnClick == 'body') && jQuery(document).off('touchend.jBox-' + this.id + ' click.jBox-' + this.id);
638		
639		// Positioning events
640		if ((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) {
641			jQuery(window).off('scroll.jBox-' + this.id);
642			jQuery(window).off('resize.jBox-' + this.id);
643		}
644		
645		// Mousemove events
646		this.options.target == 'mouse' && jQuery('body').off('mousemove.jBox-' + this.id);
647	};
648	
649	// Add overlay
650	this._addOverlay = function() {
651		
652		// If the overlay isn't cached, set overlay or create it
653		if (!this.overlay) {
654			// Get the overlay and adjust z-Index
655			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')));
656			
657			// Add close button to overlay
658			(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));
659			
660			// Add closeOnClick: 'overlay' events
661			(this.options.closeOnClick == 'overlay') && this.overlay.on('touchend click', function() { this.isOpen && this.close({ignoreDelay: true}); }.bind(this));
662		}
663		
664		// Add jBox to data
665		var overlay_data = this.overlay.data('jBox') || {};
666		overlay_data['jBox-' + this.id] = true;
667		this.overlay.data('jBox', overlay_data);
668		
669		// Abort if overlay is shown already
670		if (this.overlay.css('display') == 'block') return;
671		
672		// Show overlay
673		this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 1}, {
674			queue: false,
675			duration: this.options.fade,
676			start: function() { this.overlay.css({display: 'block'}); }.bind(this)
677		})) : this.overlay.css({display: 'block', opacity: 1});
678	};
679	
680	// Remove overlay
681	this._removeOverlay = function() {
682		
683		// Abort if no overlay found
684		if (!this.overlay) return;
685		
686		// Remove jBox from data
687		var overlay_data = this.overlay.data('jBox');
688		delete overlay_data['jBox-' + this.id];
689		this.overlay.data('jBox', overlay_data);
690		
691		// Hide overlay if no other jBox needs it
692		if (jQuery.isEmptyObject(overlay_data)) {
693			this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 0}, {
694				queue: false,
695				duration: this.options.fade,
696				complete: function() { this.overlay.css({display: 'none'}); }.bind(this)
697			})) : this.overlay.css({display: 'none', opacity: 0});
698		}
699	};
700	
701	// Create a svg element
702	this._createSVG = function(type, options) {
703		var svg = document.createElementNS('http://www.w3.org/2000/svg', type);
704		jQuery.each(options, function (index, item) {
705			svg.setAttribute(item[0], (item[1] || ''));
706		});
707		return svg;
708	};
709	
710	// Append a svg element to a svg container
711	this._appendSVG = function(source, target) {
712		return target.appendChild(source);
713	};
714		
715	// Generate CSS for animations and append to header
716	this._generateCSS = function() {
717		if (this.IE8) return;
718		
719		// Get open and close animations if none provided
720		(jQuery.type(this.options.animation) != 'object') && (this.options.animation = {
721			pulse: {open: 'pulse', close: 'zoomOut'},
722			zoomIn: {open: 'zoomIn', close: 'zoomIn'},
723			zoomOut: {open: 'zoomOut', close: 'zoomOut'},
724			move: {open: 'move', close: 'move'},
725			slide: {open: 'slide', close: 'slide'},
726			flip: {open: 'flip', close: 'flip'},
727			tada: {open: 'tada', close: 'zoomOut'}
728		}[this.options.animation]);
729		
730		// Get direction var
731		this.options.animation.open && (this.options.animation.open = this.options.animation.open.split(':'));
732		this.options.animation.close && (this.options.animation.close = this.options.animation.close.split(':'));
733		this.options.animation.openDirection = this.options.animation.open ? this.options.animation.open[1] : null;
734		this.options.animation.closeDirection = this.options.animation.close ? this.options.animation.close[1] : null;
735		this.options.animation.open && (this.options.animation.open = this.options.animation.open[0]);
736		this.options.animation.close && (this.options.animation.close = this.options.animation.close[0]);
737		
738		// Add 'Open' and 'Close' to animation names
739		this.options.animation.open && (this.options.animation.open += 'Open');
740		this.options.animation.close && (this.options.animation.close += 'Close');
741		
742		// All animations
743		var animations = {
744			pulse: {
745				duration: 350,
746				css: [['0%', 'scale(1)'], ['50%', 'scale(1.1)'], ['100%', 'scale(1)']]
747			},
748			zoomInOpen: {
749				duration: (this.options.fade || 180),
750				css: [['0%', 'scale(0.9)'], ['100%', 'scale(1)']]
751			},
752			zoomInClose: {
753				duration: (this.options.fade || 180),
754				css: [['0%', 'scale(1)'], ['100%', 'scale(0.9)']]
755			},
756			zoomOutOpen: {
757				duration: (this.options.fade || 180),
758				css: [['0%', 'scale(1.1)'], ['100%', 'scale(1)']]
759			},
760			zoomOutClose: {
761				duration: (this.options.fade || 180),
762				css: [['0%', 'scale(1)'], ['100%', 'scale(1.1)']]
763			},
764			moveOpen: {
765				duration: (this.options.fade || 180),
766				positions: {top: {'0%': -12}, right: {'0%': 12}, bottom: {'0%': 12}, left: {'0%': -12}},
767				css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
768			},
769			moveClose: {
770				duration: (this.options.fade || 180),
771				timing: 'ease-in',
772				positions: {top: {'100%': -12}, right: {'100%': 12}, bottom: {'100%': 12}, left: {'100%': -12}},
773				css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
774			},
775			slideOpen: {
776				duration: 400,
777				positions: {top: {'0%': -400}, right: {'0%': 400}, bottom: {'0%': 400}, left: {'0%': -400}},
778				css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
779			},
780			slideClose: {
781				duration: 400,
782				timing: 'ease-in',
783				positions: {top: {'100%': -400}, right: {'100%': 400}, bottom: {'100%': 400}, left: {'100%': -400}},
784				css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
785			},
786			flipOpen: {
787				duration: 600,
788				css: [['0%', 'perspective(400px) rotateX(90deg)'], ['40%', 'perspective(400px) rotateX(-15deg)'], ['70%', 'perspective(400px) rotateX(15deg)'], ['100%', 'perspective(400px) rotateX(0deg)']]
789			},
790			flipClose: {
791				duration: (this.options.fade || 300),
792				css: [['0%', 'perspective(400px) rotateX(0deg)'], ['100%', 'perspective(400px) rotateX(90deg)']]
793			},
794			tada: {
795				duration: 800,
796				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)']]
797			}
798		};
799		
800		// Set Open and Close names for standalone animations
801		jQuery.each(['pulse', 'tada'], function(index, item) { animations[item + 'Open'] = animations[item + 'Close'] = animations[item]; });
802		
803		// Function to generate the CSS for the keyframes
804		var generateKeyframeCSS = function(ev, position) {
805			
806			// Generate keyframes CSS
807			keyframe_css = '@' + this.prefix + 'keyframes jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
808			jQuery.each(animations[this.options.animation[ev]].css, function(index, item) {
809				var translate = position ? item[1].replace('%XY', this._getXY(position).toUpperCase()) : item[1];
810				animations[this.options.animation[ev]].positions && (translate = translate.replace('%V', animations[this.options.animation[ev]].positions[position][item[0]]));
811				keyframe_css += item[0] + ' {' + this.prefix + 'transform:' + translate + ';}';
812			}.bind(this));
813			keyframe_css += '}';
814			
815			// Generate class CSS
816			keyframe_css += '.jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
817			keyframe_css += this.prefix + 'animation-duration: ' + animations[this.options.animation[ev]].duration + 'ms;';
818			keyframe_css += this.prefix + 'animation-name: jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ';';
819			keyframe_css += animations[this.options.animation[ev]].timing ? (this.prefix + 'animation-timing-function: ' + animations[this.options.animation[ev]].timing + ';') : '';
820			keyframe_css += '}';
821			
822			return keyframe_css;
823		}.bind(this);
824		
825		// Generate css for each event and positions
826		var css = '';
827		jQuery.each(['open', 'close'], function(index, ev) {
828			// No CSS needed for closing with no fade
829			if (!this.options.animation[ev] || !animations[this.options.animation[ev]] || (ev == 'close' && !this.options.fade)) return '';
830			
831			// Generate CSS
832			animations[this.options.animation[ev]].positions ?
833				jQuery.each(['top', 'right', 'bottom', 'left'], function(index2, position) { css += generateKeyframeCSS(ev, position); }) :
834				css += generateKeyframeCSS(ev);
835		}.bind(this));
836		
837		jQuery('<style/>').append(css).appendTo(jQuery('head'));
838	};
839	
840	// Block body clicks for 10ms to prevent extra event triggering
841	this._blockBodyClick = function() {
842		this.blockBodyClick = true;
843		setTimeout(function() { this.blockBodyClick = false; }.bind(this), 10);
844	};
845	
846	// Add css for animations
847	this.options.animation && this._generateCSS();
848	
849	// Animations
850	this._animate = function(ev) {
851		if (this.IE8) return;
852		ev || (ev = this.isOpen ? 'open' : 'close');
853		
854		// Don't animate when closing with no fade duration
855		if (!this.options.fade && ev == 'close') return null;
856		
857		// Get the current position, use opposite if jBox is flipped
858		var animationDirection = (this.options.animation[ev + 'Direction'] || ((this.align != 'center') ? this.align : this.options.attributes.x));
859		this.flipped && this._getXY(animationDirection) == (this._getXY(this.align)) && (animationDirection = this._getOpp(animationDirection));
860		
861		// Add event and position classes
862		var classnames = 'jBox-animation-' + this.options.animation[ev] + '-' + ev + ' jBox-animation-' + this.options.animation[ev] + '-' + ev + '-' + animationDirection;
863		this.wrapper.addClass(classnames);
864		
865		// Get duration of animation
866		var animationDuration = parseFloat(this.wrapper.css(this.prefix + 'animation-duration')) * 1000;
867		ev == 'close' && (animationDuration = Math.min(animationDuration, this.options.fade));
868		
869		// Remove animation classes when animation is finished
870		setTimeout(function() { this.wrapper.removeClass(classnames); }.bind(this), animationDuration);
871	};
872	
873	// Abort animation
874	this._abortAnimation = function() {
875		if (this.IE8) return;
876		
877		// Remove all animation classes
878		var prefix = 'jBox-animation';
879		var classes = this.wrapper.attr('class').split(' ').filter(function(c) {
880			return c.lastIndexOf(prefix, 0) !== 0;
881		});
882		this.wrapper.attr('class', classes.join(' '));
883	};
884	
885	// Adjust position
886	this._adjustPosition = function() {
887		if (!this.options.adjustPosition) return null;
888		
889		// Reset cached pointer position
890		if (this.positionAdjusted) {
891			this.wrapper.css(this.pos);
892			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);
893			this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this._getOpp(this.outside)).css(this.pointer.margin);
894			this.positionAdjusted = false;
895			this.flipped = false;
896		}
897		
898		// Get the window dimensions
899		var win = jQuery(window);
900		var windowDimensions = {
901			x: win.width(),
902			y: win.height(),
903			top: win.scrollTop(),
904			left: win.scrollLeft()
905		};
906		windowDimensions.bottom = windowDimensions.top + windowDimensions.y;
907		windowDimensions.right = windowDimensions.left + windowDimensions.x;
908		
909		// Find out where the jBox is out of view area
910		var outYT = (windowDimensions.top > this.pos.top - (this.options.adjustDistance.top || 0)),
911			outXR = (windowDimensions.right < this.pos.left + this.dimensions.x + (this.options.adjustDistance.right || 0)),
912			outYB = (windowDimensions.bottom < this.pos.top + this.dimensions.y + (this.options.adjustDistance.bottom || 0)),
913			outXL = (windowDimensions.left > this.pos.left - (this.options.adjustDistance.left || 0)),
914			outX = outXL ? 'left' : (outXR ? 'right' : null),
915			outY = outYT ? 'top' : (outYB ? 'bottom' : null),
916			out = outX || outY;
917		
918		// Stop here if jBox is not out of view area
919		if (!out) return;
920			
921		// Flip jBox
922		if (this.options.adjustPosition != 'move' && (outX == this.outside || outY == this.outside)) {
923			
924			this.target == 'mouse' && (this.outside = 'right');
925			
926			// Check if enough space is availible on opposite position
927			if (((this.outside == 'top' || this.outside == 'left') ? 
928				(windowDimensions[this._getXY(this.outside)] - (this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)]) - this.targetDimensions[this._getXY(this.outside)]) :
929				(this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)])) > this.dimensions[this._getXY(this.outside)] + this.options.adjustDistance[this._getOpp(this.outside)]) {
930				
931				// Adjust wrapper and pointer
932				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.targetDimensions[this._getXY(this.outside)]) * (this.outside == 'top' || this.outside == 'left' ? 1 : -1)));
933				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)]);
934				this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this.outside);
935				this.positionAdjusted = true;
936				this.flipped = true;
937			}
938		}
939		
940		// Move jBox (only possible with pointer)
941		var outMove = (this._getXY(this.outside) == 'x') ? outY : outX;
942		
943		if (this.pointer && this.options.adjustPosition != 'flip' && this._getXY(outMove) == this._getOpp(this._getXY(this.outside))) {
944			
945			// Get the maximum space we have availible to adjust
946			if (this.pointer.align == 'center') {
947				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));
948			} else {
949				var spaceAvail = (outMove == this.pointer.alignAttribute) ?
950					parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) :
951					this.dimensions[this._getXY(outMove)] - parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - this.pointer.dimensions[this._getXY(outMove)];
952			}
953			
954			// Get the overlapping space
955			spaceDiff = (outMove == this._getTL(outMove)) ?
956				windowDimensions[this._getTL(outMove)] - this.pos[this._getTL(outMove)] + this.options.adjustDistance[outMove] :
957				(windowDimensions[this._getOpp(this._getTL(outMove))] - this.pos[this._getTL(outMove)] - this.options.adjustDistance[outMove] - this.dimensions[this._getXY(outMove)]) * -1;
958			
959			// Add overlapping space on left or top window edge
960			if (outMove == this._getOpp(this._getTL(outMove)) && this.pos[this._getTL(outMove)] - spaceDiff < windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)]) {
961				spaceDiff -= windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)] - (this.pos[this._getTL(outMove)] - spaceDiff);
962			}
963			
964			// Only adjust the maximum availible
965			spaceDiff = Math.min(spaceDiff, spaceAvail);
966			
967			// Move jBox
968			if (spaceDiff <= spaceAvail && spaceDiff > 0) {
969				this.pointer.element.css('margin-' + this.pointer.alignAttribute, parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - (spaceDiff * (outMove != this.pointer.alignAttribute ? -1 : 1)));
970				this.wrapper.css(this._getTL(outMove), this.pos[this._getTL(outMove)] + (spaceDiff * (outMove != this._getTL(outMove) ? -1 : 1)));
971				this.positionAdjusted = true;
972			}
973		}
974	};
975	
976	// Fire onInit event
977	(this.options.onInit.bind(this))();
978	this.options._onInit && (this.options._onInit.bind(this))();
979	
980	return this;
981};
982
983// Attach jBox to elements
984jBox.prototype.attach = function(elements, trigger) {
985	elements || (elements = jQuery(this.options.attach.selector || this.options.attach));
986	trigger || (trigger = this.options.trigger);
987	
988	elements && elements.length && jQuery.each(elements, function(index, el) {
989		el = jQuery(el);
990		if (!el.data('jBox-attached-' + this.id)) {
991			
992			// Remove title attribute and store content on element
993			(this.options.getContent == 'title' && el.attr('title') != undefined) && el.data('jBox-getContent', el.attr('title')).removeAttr('title');
994			
995			// Add Element to collection
996			this.attachedElements || (this.attachedElements = []);
997			this.attachedElements.push(el[0]);
998			
999			// Add click or mouseenter event, click events can prevent default as well
1000			el.on(trigger + '.jBox-attach-' + this.id, function(ev) {
1001				// Clear timer
1002				this.timer && clearTimeout(this.timer);
1003				
1004				// Block opening when jbox is open and the source element is triggering
1005				if (trigger == 'mouseenter' && this.isOpen && this.source[0] == el[0])
1006					return;
1007				
1008				// Only close jBox if you click the current target element, otherwise open at new target
1009				if (this.isOpen && this.source && this.source[0] != el[0]) var forceOpen = true;
1010				
1011				// Set new source element
1012				this.source = el;
1013				
1014				// Set new target
1015				!this.options.target && (this.target = el);
1016				
1017				// Prevent default action on click
1018				trigger == 'click' && this.options.preventDefault && ev.preventDefault();
1019				
1020				// Toggle or open jBox
1021				this[trigger == 'click' && !forceOpen ? 'toggle' : 'open']();
1022			}.bind(this));
1023			
1024			// Add close event for trigger event mouseenter
1025			(this.options.trigger == 'mouseenter') && el.on('mouseleave', function(ev) {
1026				// If we have set closeOnMouseleave, do not close jBox when leaving attached element and mouse is over jBox
1027				if(!this.options.closeOnMouseleave || !(ev.relatedTarget == this.wrapper[0] || jQuery(ev.relatedTarget).parents('#' + this.id).length)) this.close();
1028			}.bind(this));
1029			
1030			el.data('jBox-attached-' + this.id, trigger);
1031			
1032			// TODO // TODO TOO CLOSE
1033			// Fire onAttach event
1034			this.options._onAttach && (this.options._onAttach.bind(this))(el);
1035		}
1036	}.bind(this));
1037	
1038	return this;
1039};
1040
1041// Detach jBox from elements
1042jBox.prototype.detach = function(elements) {
1043	elements || (elements = this.attachedElements || []);
1044	
1045	elements && elements.length && jQuery.each(elements, function(index, el) {
1046		el = jQuery(el);
1047		// Remove events
1048		if (el.data('jBox-attached-' + this.id)) {
1049			el.off(el.data('jBox-attached-' + this.id) + '.jBox-attach-' + this.id);
1050			el.data('jBox-attached-' + this.id, null);
1051		}
1052		// Remove element from collection
1053		this.attachedElements = jQuery.grep(this.attachedElements, function(value) {
1054			return value != el[0];
1055		});
1056	}.bind(this));
1057	
1058	return this;
1059};
1060
1061// Set title
1062jBox.prototype.setTitle = function(title, ignore_positioning) {
1063	var wrapperHeight = this.wrapper.height(), wrapperWidth = this.wrapper.width();
1064	if (title == null || title == undefined) return this;
1065	!this.wrapper && this._create();
1066	if (!this.title) {
1067		this.titleContainer = jQuery('<div/>', {'class': 'jBox-title'});
1068		this.title = jQuery('<div/>').appendTo(this.titleContainer);
1069		this.wrapper.addClass('jBox-hasTitle');
1070		if (this.options.closeButton == 'title' || (this.options.closeButton === true && !this.options.overlay)) {
1071			this.wrapper.addClass('jBox-closeButton-title');
1072			this.closeButton.appendTo(this.titleContainer);
1073		}
1074		this.titleContainer.insertBefore(this.content);
1075	}
1076	this.title.html(title);
1077	
1078	// Reposition if dimensions changed
1079	!ignore_positioning && (wrapperHeight != this.wrapper.height() || wrapperWidth != this.wrapper.width()) && this.position();
1080	
1081	return this;
1082};
1083
1084// Set content
1085jBox.prototype.setContent = function(content, ignore_positioning) {
1086	if (content == null) return this;
1087	
1088	// Create jBox if no wrapper found
1089	!this.wrapper && this._create();
1090	
1091	// Get the width and height of wrapper, only if they change we need to reposition
1092	var wrapperHeight = this.wrapper.height(), wrapperWidth = this.wrapper.width();
1093	
1094	//  Get the width and height of body, if they change with new content, adjust accordingly (happens when a hidden scrollbar changes body dimensions)
1095	var bodyHeight = jQuery('body').height(), bodyWidth = jQuery('body').width();
1096	
1097	// Set the new content
1098	switch (jQuery.type(content)) {
1099		case 'string': this.content.html(content); break;
1100		case 'object': this.content.children().css({display: 'none'}); this.options.content.appendTo(this.content).css({display: 'block'}); break;
1101	}
1102	
1103	// Calculate the difference to before the content was set
1104	var adjustOffset = {
1105		x: bodyWidth - jQuery('body').width(),
1106		y: bodyHeight - jQuery('body').height()
1107	};
1108	
1109	// Reposition if dimensions changed
1110	!ignore_positioning && (wrapperHeight != this.wrapper.height() || wrapperWidth != this.wrapper.width()) && this.position({adjustOffset: adjustOffset});
1111	
1112	return this;
1113};
1114
1115// Position jBox
1116jBox.prototype.position = function(options) {
1117	options || (options = {});
1118	
1119	// Get target
1120	this.target = options.target || this.target || this.options.target || jQuery(window);
1121	
1122	// Cache total current dimensions of jBox
1123	this.dimensions = {
1124		x: this.wrapper.outerWidth(),
1125		y: this.wrapper.outerHeight()
1126	};
1127	
1128	// Mousemouve can't be positioned
1129	if (this.target == 'mouse') return;
1130	
1131	// Set percent and margin for centered inside
1132	if (this.options.position.x == 'center' && this.options.position.y == 'center') {
1133		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)});
1134		return this;
1135	}
1136	
1137	// Add fixed data to target
1138	!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');
1139	
1140	// Total current dimensions of target element
1141	var targetOffset = this.target[this.target.data('jBox-fixed') == 'fixed' ? 'position' : 'offset']();
1142	this.targetDimensions = {
1143		x: this.target.outerWidth(),
1144		y: this.target.outerHeight(),
1145		top: (targetOffset ? targetOffset.top : 0),
1146		left: (targetOffset ? targetOffset.left : 0)
1147	};
1148	this.pos = {};
1149	
1150	// Calculate positions
1151	var setPosition = function(p) {
1152		
1153		// Set number positions
1154		if (jQuery.inArray(this.options.position[p], ['top', 'right', 'bottom', 'left', 'center']) == -1) {
1155			this.pos[this.options.attributes[p]] = this.options.position[p];
1156			return;
1157		}
1158		
1159		// We have a target, so use 'left' or 'top' as attributes
1160		var a = this.options.attributes[p] = (p == 'x' ? 'left' : 'top');
1161		
1162		// Start at target position
1163		this.pos[a] = this.targetDimensions[a];
1164		
1165		// Set centered position
1166		if (this.options.position[p] == 'center') {
1167			this.pos[a] += Math.ceil((this.targetDimensions[p] - this.dimensions[p]) / 2);
1168			return;
1169		}
1170		
1171		// Move inside
1172		(a != this.options.position[p]) && (this.pos[a] += this.targetDimensions[p] - this.dimensions[p]);
1173		
1174		
1175		// Move outside
1176		(this.options.outside == p || this.options.outside == 'xy') && (this.pos[a] += this.dimensions[p] * (a != this.options.position[p] ? 1 : -1));
1177		
1178	}.bind(this);
1179	
1180	// Set position including offset
1181	setPosition('x');
1182	setPosition('y');
1183	
1184	// Adjust position depending on pointer align
1185	if (this.options.pointer && jQuery.type(this.options.position.x) != 'number' && jQuery.type(this.options.position.y) != 'number') {
1186	
1187		var adjustWrapper = 0;
1188		
1189		// Where is the pointer aligned? Add or substract accordingly
1190		switch (this.pointer.align) {
1191			case 'center':
1192			if (this.options.position[this._getOpp(this.options.outside)] != 'center') {
1193				adjustWrapper += (this.dimensions[this._getOpp(this.options.outside)] / 2);
1194			}
1195			break;
1196			default:
1197			switch (this.options.position[this._getOpp(this.options.outside)]) {
1198				case 'center':
1199				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);
1200				break;
1201				default:
1202				adjustWrapper += (this.pointer.align != this.options.position[this._getOpp(this.options.outside)]) ?
1203					this.dimensions[this._getOpp(this.options.outside)] - (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2) :
1204					(this.pointer.dimensions[this._getOpp(this.options.outside)] / 2);
1205				break;
1206			}
1207			break;
1208		}
1209		adjustWrapper *= (this.options.position[this._getOpp(this.options.outside)] == this.pointer.alignAttribute ? -1 : 1);
1210		adjustWrapper += this.pointer.offset * (this.pointer.align == this._getOpp(this._getTL(this.pointer.align)) ? 1 : -1);
1211		
1212		this.pos[this._getTL(this._getOpp(this.pointer.xy))] += adjustWrapper;
1213	}
1214	
1215	// Add adjustments
1216	options.adjustOffset && options.adjustOffset.x && (this.pos[this.options.attributes.x] += parseInt(options.adjustOffset.x) * (this.options.attributes.x == 'left' ? 1 : -1));
1217	options.adjustOffset && options.adjustOffset.y && (this.pos[this.options.attributes.y] += parseInt(options.adjustOffset.y) * (this.options.attributes.y == 'top' ? 1 : -1));
1218	
1219	// Add final offset
1220	this.pos[this.options.attributes.x] += this.options.offset.x;
1221	this.pos[this.options.attributes.y] += this.options.offset.y;
1222	
1223	// Set CSS
1224	this.wrapper.css(this.pos);
1225	
1226	// Adjust position
1227	this._adjustPosition();
1228	
1229	return this;
1230};
1231
1232// Open jBox
1233jBox.prototype.open = function(options) {
1234	options || (options = {});
1235	
1236	// Construct jBox if not already constructed
1237	!this.wrapper && this._create();
1238	
1239	// Abort any opening or closing timer
1240	this.timer && clearTimeout(this.timer);
1241	
1242	// Block body click for 10ms, so jBox can open on attached elements while closeOnClick = 'body'
1243	this._blockBodyClick();
1244	
1245	// Block opening
1246	if (this.isDisabled) return this;
1247	
1248	// Opening function
1249	var open = function() {
1250		
1251		// Set title from source element
1252		this.source && this.options.getTitle && (this.source.attr(this.options.getTitle) && this.setTitle(this.source.attr(this.options.getTitle)));
1253		
1254		// Set content from source element
1255		this.source && this.options.getContent && (this.source.data('jBox-getContent') ? this.setContent(this.source.data('jBox-getContent')) : (this.source.attr(this.options.getContent) ? this.setContent(this.source.attr(this.options.getContent)) : null));
1256		
1257		// Fire onOpen event
1258		(this.options.onOpen.bind(this))();
1259		this.options._onOpen && (this.options._onOpen.bind(this))();
1260		
1261		// Get content from ajax
1262		this.options.ajax && this.options.ajax.url && (!this.ajaxLoaded || this.options.ajax.reload) && this.ajax();
1263		
1264		// Set position
1265		this.position({target: options.target});
1266		
1267		// Abort closing
1268		this.isClosing && this._abortAnimation();
1269		
1270		// Open functions to call when jBox is closed
1271		if (!this.isOpen) {
1272		
1273			// jBox is open now
1274			this.isOpen = true;
1275			
1276			// Attach events
1277			this._attachEvents();
1278			
1279			// Block scrolling
1280			this.options.blockScroll && jQuery('body').addClass('jBox-blockScroll-' + this.id);
1281			
1282			// Add overlay
1283			this.options.overlay && this._addOverlay();
1284			
1285			// Only animate if jBox is compleately closed
1286			this.options.animation && !this.isClosing && this._animate('open');
1287			
1288			// Fading animation or show immediately
1289			if (this.options.fade) {
1290				this.wrapper.stop().animate({opacity: 1}, {
1291					queue: false,
1292					duration: this.options.fade,
1293					start: function() {
1294						this.isOpening = true;
1295						this.wrapper.css({display: 'block'});
1296					}.bind(this),
1297					always: function() {
1298						this.isOpening = false;
1299					}.bind(this)
1300				});
1301			} else {
1302				this.wrapper.css({display: 'block', opacity: 1});
1303			}
1304		}
1305	}.bind(this);
1306	
1307	// Open jBox
1308	this.options.delayOpen && !this.isOpen && !this.isClosing && !options.ignoreDelay ? (this.timer = setTimeout(open, this.options.delayOpen)) : open();
1309	
1310	return this;
1311};
1312
1313// Close jBox
1314jBox.prototype.close = function(options) {
1315	options || (options = {});
1316	
1317	// Abort opening
1318	this.timer && clearTimeout(this.timer);
1319	
1320	// Block body click for 10ms, so jBox can open on attached elements while closeOnClock = 'body' is true
1321	this._blockBodyClick();
1322	
1323	// Block closing
1324	if (this.isDisabled) return this;
1325	
1326	// Close function
1327	var close = function() {
1328		
1329		// Fire onClose event
1330		(this.options.onClose.bind(this))();
1331		this.options._onClose && (this.options._onClose.bind(this))();
1332		
1333		// Only close if jBox is open
1334		if (this.isOpen) {
1335		
1336			// jBox is not open anymore
1337			this.isOpen = false;
1338			
1339			// Detach events
1340			this._detachEvents();
1341			
1342			// Unblock scrolling
1343			this.options.blockScroll && jQuery('body').removeClass('jBox-blockScroll-' + this.id);
1344			
1345			// Remove overlay
1346			this.options.overlay && this._removeOverlay();
1347			
1348			// Only animate if jBox is compleately closed
1349			this.options.animation && !this.isOpening && this._animate('close');
1350			
1351			// Fading animation or show immediately
1352			if (this.options.fade) {
1353				this.wrapper.stop().animate({opacity: 0}, {
1354					queue: false,
1355					duration: this.options.fade,
1356					start: function() {
1357						this.isClosing = true;
1358					}.bind(this),
1359					complete: function() {
1360						this.wrapper.css({display: 'none'});
1361						this.options.onCloseComplete && (this.options.onCloseComplete.bind(this))();
1362						this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))();
1363					}.bind(this),
1364					always: function() {
1365						this.isClosing = false;
1366					}.bind(this)
1367				});
1368			} else {
1369				this.wrapper.css({display: 'none', opacity: 0});
1370				this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))();
1371			}
1372		}
1373	}.bind(this);
1374	
1375	// Close jBox
1376	options.ignoreDelay ? close() : (this.timer = setTimeout(close, Math.max(this.options.delayClose, 10)));
1377	
1378	return this;
1379};
1380
1381// Open or close jBox
1382jBox.prototype.toggle = function(options) {
1383	this[this.isOpen ? 'close' : 'open'](options);
1384	return this;
1385};
1386
1387// Block opening and closing
1388jBox.prototype.disable = function() {
1389	this.isDisabled = true;
1390	return this;
1391};
1392
1393// Unblock opening and closing
1394jBox.prototype.enable = function() {
1395	this.isDisabled = false;
1396	return this;
1397};
1398
1399// Get content from ajax
1400jBox.prototype.ajax = function(options) {
1401	options || (options = {});
1402	
1403	// Add data from source element if none set in options
1404	(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) || '');
1405	
1406	// Clone the system options
1407	var sysOptions = jQuery.extend(true, {}, this.options.ajax);
1408	
1409	// Abort running ajax call
1410	this.ajaxRequest && this.ajaxRequest.abort();
1411    
1412    // Extract events
1413    var beforeSend = options.beforeSend || sysOptions.beforeSend || function () {};
1414    var complete = options.complete || sysOptions.complete || function () {};
1415	
1416    // Merge options
1417    var userOptions = jQuery.extend(true, sysOptions, options);
1418	
1419	// Set new beforeSend event
1420    userOptions.beforeSend = function () {
1421		
1422		// Add loading spinner
1423		userOptions.spinner && this.wrapper.addClass('jBox-loading');
1424
1425        (beforeSend.bind(this))();
1426    }.bind(this);
1427
1428    // Set new complete event 
1429    userOptions.complete = function (response) {
1430		
1431		// Remove spinner, set content and reposition jBox
1432		this.wrapper.removeClass('jBox-loading');
1433		
1434		// Set new content
1435		userOptions.setContent && this.setContent(response.responseText);
1436		
1437		this.ajaxLoaded = true;
1438		
1439        (complete.bind(this))(response);
1440    }.bind(this);
1441	
1442    // Send new ajax request
1443    this.ajaxRequest = jQuery.ajax(userOptions);
1444	
1445	return this;
1446};
1447
1448// Play an audio file
1449jBox.prototype.audio = function(options) {
1450	options || (options = {});
1451	jBox._audio || (jBox._audio = {});
1452	
1453	// URL required, no IE8 support
1454	if (!options.url || this.IE8) return this;
1455	
1456	// Create audio if it doesn't exist
1457	if (!jBox._audio[options.url]) {
1458		var audio = jQuery('<audio/>');
1459		jQuery('<source/>', {src: options.url + '.mp3'}).appendTo(audio);
1460		jQuery('<source/>', {src: options.url + '.ogg'}).appendTo(audio);
1461		jBox._audio[options.url] = audio[0];
1462	}
1463	
1464	// Set volume and play audio
1465	jBox._audio[options.url].volume = Math.min((options.volume != undefined ? options.volume : (this.options.volume != undefined ? this.options.volume : 100) / 100), 1);
1466	jBox._audio[options.url].pause();
1467	try { jBox._audio[options.url].currentTime = 0; } catch (e) {}
1468	jBox._audio[options.url].play();	
1469	
1470	return this;
1471};
1472
1473// Destroy jBox and remove it from DOM
1474jBox.prototype.destroy = function() {
1475	this.close({ignoreDelay: true});
1476	this.wrapper.remove();
1477	return this;
1478};
1479
1480// TODO
1481// Find an option to preload audio files
1482
1483// Get a unique ID for jBoxes
1484jBox._getUniqueID = (function () {
1485	var i = 1;
1486	return function () {
1487		return i++;
1488	};
1489}());
1490
1491// Make jBox usable with jQuery selectors
1492jQuery.fn.jBox = function(type, options) {
1493	type || (type = {});
1494	options || (options = {});
1495	return new jBox(type, jQuery.extend(options, {attach: this}));
1496};
1497
1498// Add the .bind() function for IE 8 support
1499if (!Function.prototype.bind) {
1500	Function.prototype.bind = function (oThis) {
1501		var aArgs = Array.prototype.slice.call(arguments, 1), 
1502			fToBind = this, 
1503			fNOP = function () {},
1504			fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); };
1505		fNOP.prototype = this.prototype;
1506		fBound.prototype = new fNOP();
1507		return fBound;
1508	};
1509}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.