Browse code

Progress

Benjamin Roth authored on21/02/2023 19:42:19
Showing1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2290 @@
1
+/**
2
+ * jBox is a jQuery plugin that makes it easy to create customizable tooltips, modal windows, image galleries and more.
3
+ *
4
+ * Author: Stephan Wagner <stephanwagner.me@gmail.com> (https://stephanwagner.me)
5
+ *
6
+ * License: MIT (https://opensource.org/licenses/MIT)
7
+ *
8
+ * Requires: jQuery 3.5.0 (https://code.jquery.com/jquery-3.5.0.min.js)
9
+ *
10
+ * Documentation: https://stephanwagner.me/jBox/documentation
11
+ *
12
+ * Demos: https://stephanwagner.me/jBox/demos
13
+ */
14
+
15
+function jBoxWrapper(jQuery) {
16
+
17
+
18
+  var jBox = function jBox(type, options) {
19
+
20
+
21
+    // Options (https://stephanwagner.me/jBox/options)
22
+
23
+    this.options = {
24
+
25
+      // jBox ID
26
+      id: null,                    // Choose a unique id, otherwise jBox will set one for you (jBox1, jBox2, ...)
27
+
28
+      // Dimensions
29
+      width: 'auto',               // The width of the content area, e.g. 'auto', 200, '80%'
30
+      height: 'auto',              // The height of the content area
31
+      minWidth: null,              // Minimal width
32
+      minHeight: null,             // Minimal height
33
+      maxWidth: null,              // Maximal width
34
+      maxHeight: null,             // Maximal height
35
+
36
+      // Responsive dimensions
37
+      responsiveWidth: true,       // Adjusts the width to fit the viewport
38
+      responsiveHeight: true,      // Adjusts the height to fit the viewport
39
+      responsiveMinWidth: 100,     // Don't adjust width below this value (in pixel)
40
+      responsiveMinHeight: 100,    // Don't adjust height below this value (in pixel)
41
+
42
+      // Attach
43
+      attach: null,                // A jQuery selector to elements that will open and close your jBox, e.g. '.tooltip'
44
+      trigger: 'click',            // The event to open or close your jBox, use 'click', 'touchclick' or 'mouseenter'
45
+      preventDefault: false,       // Prevent the default event when opening jBox, e.g. don't follow the href in a link
46
+
47
+      // Content
48
+      content: null,               // You can use HTML or a jQuery element, e.g. jQuery('#jBox-content'). The elements will be appended to the content element and then made visible, so hide them with style="display: none" beforehand
49
+      getContent: null,            // Get the content from an attribute when jBox opens, e.g. getContent: 'data-content'. Use 'html' to get the attached elements HTML as content
50
+      title: null,                 // Adds a title to your jBox
51
+      getTitle: null,              // Get the title from an attribute when jBox opens, e.g. getTitle: 'data-title'
52
+      footer: null,                // Adds a footer to your jBox
53
+      isolateScroll: true,         // Isolates scrolling to the content container
54
+
55
+      // AJAX
56
+      ajax: {                      // Setting an URL will make an AJAX request when jBox opens. Optional you can add any jQuery AJAX option (http://api.jquery.com/jquery.ajax/)
57
+        url: null,                 // The URL to send the AJAX request to
58
+        data: '',                  // Data to send with your AJAX request, e.g. {id: 82, limit: 10}
59
+        reload: false,             // Resend the AJAX request when jBox opens. Use true to send the AJAX request only once for every attached element or 'strict' to resend every time jBox opens
60
+        getURL: 'data-url',        // The attribute in the source element where the AJAX request will look for the URL, e.g. data-url="https://reqres.in/api/users"
61
+        getData: 'data-ajax',      // The attribute in the source element where the AJAX request will look for the data, e.g. data-ajax="id=82&limit=10"
62
+        setContent: true,          // Automatically set the response as new content when the AJAX request is finished
63
+        loadingClass: true,        // Add a class to the wrapper when jBox is loading, set to class name or true to use the default class name 'jBox-loading'
64
+        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>'
65
+        spinnerDelay: 300,         // Milliseconds to wait until spinner appears
66
+        spinnerReposition: true    // Repositions jBox when the spinner is added or removed
67
+      },
68
+      cancelAjaxOnClose: true,     // Cancels the ajax call when jBox closes and it hasn't finished loading yet
69
+
70
+      // Position
71
+      target: null,                // The jQuery selector to the target element where jBox will be opened. If no element is found, jBox will use the attached element as target
72
+      position: {
73
+        x: 'center',               // Horizontal position, use a number, 'left', 'right' or 'center'
74
+        y: 'center'                // Vertical position, use a number, 'top', 'bottom' or 'center'
75
+      },
76
+      outside: null,               // Use 'x', 'y', or 'xy' to move your jBox outside of the target element
77
+      offset: 0,                   // Offset to final position, you can set different values for x and y with an object, e.g. {x: 20, y: 10}
78
+      attributes: {                // Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20}
79
+        x: 'left',                 // Horizontal position, use 'left' or 'right'
80
+        y: 'top'                   // Vertical position, use 'top' or 'bottom'
81
+      },
82
+      fixed: false,                // Your jBox will stay on position when scrolling
83
+      adjustPosition: true,        // Adjusts your jBoxes position if there is not enough space, use 'flip', 'move' or true for both. This option overrides the reposition options
84
+      adjustTracker: false,        // By default jBox adjusts its position when it opens or when the window size changes, set to true to also adjust when scrolling
85
+      adjustDistance: 5,           // The minimal distance to the viewport edge while adjusting. Use an object to set different values, e.g. {top: 50, right: 5, bottom: 20, left: 5}
86
+      reposition: true,            // Calculates new position when the window-size changes
87
+      repositionOnOpen: true,      // Calculates new position each time jBox opens (rather than only when it opens the first time)
88
+      repositionOnContent: true,   // Calculates new position when the content changes with .setContent() or .setTitle()
89
+      holdPosition: true,          // Keeps current position if space permits. Applies only to 'Modal' type.
90
+
91
+      // Pointer
92
+      pointer: false,              // Your pointer will always point towards the target element, so the option outside needs to be 'x' or 'y'. By default the pointer is centered, set a position to move it to any side. You can also add an offset, e.g. 'left:30' or 'center:-20'
93
+      pointTo: 'target',           // Setting something else than 'target' will add a pointer even if there is no target element set or found. Use 'top', 'right', 'bottom' or 'left'
94
+
95
+      // Animations
96
+      fade: 180,                   // Fade duration in ms, set to 0 or false to disable
97
+      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)
98
+
99
+      // Appearance
100
+      theme: 'Default',            // Set a jBox theme class
101
+      addClass: null,              // Adds classes to the wrapper
102
+      overlay: false,              // Adds an overlay to hide page content when jBox opens (adjust color and opacity with CSS)
103
+      overlayClass: null,          // Add a class name to the overlay
104
+      zIndex: 10000,               // Use a high z-index, or set to 'auto' to bring to front on open
105
+
106
+      // Delays
107
+      delayOpen: 0,                // Delay opening in ms. Note that the delay will be ignored if your jBox didn't finish closing
108
+      delayClose: 0,               // Delay closing in ms. Nnote that there is always a closing delay of at least 10ms to ensure jBox won't be closed when opening right away
109
+
110
+      // Closing
111
+      closeOnEsc: false,           // Close jBox when pressing [esc] key
112
+      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)
113
+      closeOnMouseleave: false,    // Close jBox when the mouse leaves the jBox area or the area of the attached element
114
+      closeButton: false,          // Adds a close button to your jBox. Use 'title', 'box', 'overlay' or true (true will add the button to the overlay, title or the jBox itself, in that order if any of those elements can be found)
115
+
116
+      // Other options
117
+      appendTo: jQuery('body'),    // The element your jBox will be appended to. Any other element than jQuery('body') is only useful for fixed positions or when position values are numbers
118
+      createOnInit: false,         // Creates jBox and makes it available in DOM when it's being initialized, otherwise it will be created when it opens for the first time
119
+      blockScroll: false,          // Blocks scrolling when jBox is open
120
+      blockScrollAdjust: true,     // Adjust page elements to avoid content jumps when scrolling is blocked. See more here: https://github.com/StephanWagner/unscroll
121
+      draggable: false,            // 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/)
122
+      dragOver: true,              // When you have multiple draggable jBoxes, the one you select will always move over the other ones
123
+      autoClose: false,            // Time in ms when jBox will close automatically after it was opened
124
+      delayOnHover: false,         // Delay auto-closing while mouse is hovered
125
+      showCountdown: false,        // Display a nice progress-indicator when autoClose is enabled
126
+
127
+      // Audio                     // You can use the integrated audio function whenever you'd like to play an audio file, e.g. onInit: function () { this.audio('url_to_audio_file_without_file_extension', 75); }
128
+      preloadAudio: true,          // Preloads the audio files set in option audio. You can also preload other audio files, e.g. ['src_to_file.mp3', 'src_to_file.ogg']
129
+      audio: null,                 // The URL to an audio file to play when jBox opens. Set the URL without file extension, jBox will look for an .mp3 and .ogg file. To play audio when jBox closes, use an object, e.g. {open: 'src_to_audio1', close: 'src_to_audio2'}
130
+      volume: 100,                 // The volume in percent. To have different volumes for opening and closeing, use an object, e.g. {open: 75, close: 100}
131
+
132
+      // Events                    // Note that you can use 'this' in all event functions, it refers to your jBox object (e.g. onInit: function () { this.open(); })
133
+      onInit: null,                // Fired when jBox is initialized
134
+      onAttach: null,              // Fired when jBox attached itself to elements, the attached element will be passed as a parameter, e.g. onAttach: function (element) { element.css({color: 'red'}); }
135
+      onPosition: null,            // Fired when jBox is positioned
136
+      onCreated: null,             // Fired when jBox is created and availible in DOM
137
+      onOpen: null,                // Fired when jBox opens
138
+      onClose: null,               // Fired when jBox closes
139
+      onCloseComplete: null,       // Fired when jBox is completely closed (when fading is finished)
140
+      onDragStart: null,           // Fired when dragging starts
141
+      onDragEnd: null              // Fired when dragging finished
142
+    };
143
+
144
+
145
+    // Default plugin options
146
+
147
+    this._pluginOptions = {
148
+
149
+      // Default options for tooltips
150
+      'Tooltip': {
151
+        getContent: 'title',
152
+        trigger: 'mouseenter',
153
+        position: {
154
+          x: 'center',
155
+          y: 'top'
156
+        },
157
+        outside: 'y',
158
+        pointer: true
159
+      },
160
+
161
+      // Default options for mouse tooltips
162
+      'Mouse': {
163
+        responsiveWidth: false,
164
+        responsiveHeight: false,
165
+        adjustPosition: 'flip',
166
+        target: 'mouse',
167
+        trigger: 'mouseenter',
168
+        position: {
169
+          x: 'right',
170
+          y: 'bottom'
171
+        },
172
+        outside: 'xy',
173
+        offset: 5
174
+      },
175
+
176
+      // Default options for modal windows
177
+      'Modal': {
178
+        target: jQuery(window),
179
+        fixed: true,
180
+        blockScroll: true,
181
+        closeOnEsc: true,
182
+        closeOnClick: 'overlay',
183
+        closeButton: true,
184
+        overlay: true,
185
+        animation: 'zoomIn'
186
+      },
187
+    };
188
+
189
+
190
+    // Merge options
191
+
192
+    this.options = jQuery.extend(true, this.options, this._pluginOptions[type] ? this._pluginOptions[type] : jBox._pluginOptions[type], options);
193
+
194
+
195
+    // Set the jBox type
196
+
197
+    jQuery.type(type) == 'string' && (this.type = type);
198
+
199
+
200
+    // Checks if the user is on a touch device, borrowed from https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
201
+
202
+    this.isTouchDevice = (function () {
203
+      var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
204
+      var mq = function (query) {
205
+        return window.matchMedia(query).matches;
206
+      }
207
+
208
+      if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
209
+        return true;
210
+      }
211
+
212
+      var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
213
+      return mq(query);
214
+    })();
215
+
216
+
217
+    // Add close event for body click when we are on touch device and jBox triggers on mouseenter
218
+
219
+    if (this.isTouchDevice && this.options.trigger === 'mouseenter' && this.options.closeOnClick === false) {
220
+      this.options.closeOnClick = 'body';
221
+    }
222
+
223
+
224
+    // Local function to fire events
225
+
226
+    this._fireEvent = function (event, pass)
227
+    {
228
+      this.options['_' + event] && (this.options['_' + event].bind(this))(pass);
229
+      this.options[event] && (this.options[event].bind(this))(pass);
230
+    };
231
+
232
+
233
+    // Get a unique jBox ID
234
+
235
+    this.options.id === null && (this.options.id = 'jBox' + jBox._getUniqueID());
236
+    this.id = this.options.id;
237
+
238
+
239
+    // Correct impossible options
240
+
241
+    ((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = null);
242
+    this.options.pointTo == 'target' && (!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false);
243
+
244
+
245
+    // Correct multiple choice options
246
+
247
+    jQuery.type(this.options.offset) != 'object' ? (this.options.offset = {x: this.options.offset, y: this.options.offset}) : (this.options.offset = jQuery.extend({x: 0, y: 0}, this.options.offset));
248
+    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));
249
+
250
+
251
+    // Save default outside position
252
+
253
+    this.outside = this.options.outside && this.options.outside != 'xy' ? this.options.position[this.options.outside] : false;
254
+
255
+
256
+    // Save where the jBox is aligned to
257
+
258
+    this.align = this.outside ? this.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));
259
+
260
+
261
+    // Adjust option zIndex
262
+
263
+    jBox.zIndexMax = Math.max(jBox.zIndexMax || 0, this.options.zIndex === 'auto' ? 10000 : this.options.zIndex);
264
+    if (this.options.zIndex === 'auto') {
265
+      this.adjustZIndexOnOpen = true;
266
+      jBox.zIndexMax += 2;
267
+      this.options.zIndex = jBox.zIndexMax;
268
+      this.trueModal = this.options.overlay;
269
+    }
270
+
271
+    // Internal positioning functions
272
+
273
+    this._getOpp = function (opp) { return {left: 'right', right: 'left', top: 'bottom', bottom: 'top', x: 'y', y: 'x'}[opp]; };
274
+    this._getXY = function (xy) { return {left: 'x', right: 'x', top: 'y', bottom: 'y', center: 'x'}[xy]; };
275
+    this._getTL = function (tl) { return {left: 'left', right: 'left', top: 'top', bottom: 'top', center: 'left', x: 'left', y: 'top'}[tl]; };
276
+
277
+
278
+    // Get a dimension value in integer pixel dependent on appended element
279
+
280
+    this._getInt = function (value, dimension) {
281
+      if (value == 'auto') return 'auto';
282
+      if (value && jQuery.type(value) == 'string' && value.slice(-1) == '%') {
283
+        return jQuery(window)[dimension == 'height' ? 'innerHeight' : 'innerWidth']() * parseInt(value.replace('%', '')) / 100;
284
+      }
285
+      return value;
286
+    };
287
+
288
+
289
+    // Create an svg element
290
+
291
+    this._createSVG = function (type, options)
292
+    {
293
+      var svg = document.createElementNS('http://www.w3.org/2000/svg', type);
294
+      jQuery.each(options, function (index, item) {
295
+        svg.setAttribute(item[0], (item[1] || ''));
296
+      });
297
+      return svg;
298
+    };
299
+
300
+
301
+    // Isolate scrolling in a container
302
+
303
+    this._isolateScroll = function (el)
304
+    {
305
+      // Abort if element not found
306
+      if (!el || !el.length) return;
307
+
308
+      el.on('DOMMouseScroll.jBoxIsolateScroll mousewheel.jBoxIsolateScroll', function (ev) {
309
+        var delta = ev.wheelDelta || (ev.originalEvent && ev.originalEvent.wheelDelta) || -ev.detail;
310
+        var overflowBottom = this.scrollTop + el.outerHeight() - this.scrollHeight >= 0;
311
+        var overflowTop = this.scrollTop <= 0;
312
+        ((delta < 0 && overflowBottom) || (delta > 0 && overflowTop)) && ev.preventDefault();
313
+      });
314
+    };
315
+
316
+
317
+    // Set the title width to content width
318
+
319
+    this._setTitleWidth = function ()
320
+    {
321
+      // Abort if there is no title or width of content is auto
322
+      if (!this.titleContainer || (this.content[0].style.width == 'auto' && !this.content[0].style.maxWidth)) return null;
323
+
324
+      // Expose wrapper to get actual width
325
+      if (this.wrapper.css('display') == 'none') {
326
+        this.wrapper.css('display', 'block');
327
+        var contentWidth = this.content.outerWidth();
328
+        this.wrapper.css('display', 'none');
329
+      } else {
330
+        var contentWidth = this.content.outerWidth();
331
+      }
332
+
333
+      // Set max-width only
334
+      this.titleContainer.css({maxWidth: (Math.max(contentWidth, parseInt(this.content[0].style.maxWidth)) || null)});
335
+    }
336
+
337
+
338
+    // Make jBox draggable
339
+
340
+    this._draggable = function ()
341
+    {
342
+      // Abort if jBox is not draggable
343
+      if (!this.options.draggable) return false;
344
+
345
+      // Get the handle where jBox will be dragged with
346
+      var handle = this.options.draggable == 'title' ? this.titleContainer : (this.options.draggable instanceof jQuery ? this.options.draggable : (jQuery.type(this.options.draggable) == 'string' ? jQuery(this.options.draggable) : this.wrapper));
347
+
348
+      // Abort if no handle or if draggable was set already
349
+      if (!handle || !(handle instanceof jQuery) || !handle.length || handle.data('jBox-draggable')) return false;
350
+
351
+      // Add mouse events
352
+      handle.addClass('jBox-draggable').data('jBox-draggable', true).on('touchstart mousedown', function (ev)
353
+      {
354
+        if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return;
355
+
356
+        // Store current mouse position
357
+        this.draggingStartX = ev.pageX;
358
+        this.draggingStartY = ev.pageY;
359
+
360
+        // Adjust z-index when dragging jBox over another draggable jBox
361
+        if (this.options.dragOver && !this.trueModal && parseInt(this.wrapper.css('zIndex'), 10) <= jBox.zIndexMaxDragover) {
362
+          jBox.zIndexMaxDragover += 1;
363
+          this.wrapper.css('zIndex', jBox.zIndexMaxDragover);
364
+        }
365
+
366
+        var drg_h = this.wrapper.outerHeight();
367
+        var drg_w = this.wrapper.outerWidth();
368
+        var pos_y = this.wrapper.offset().top + drg_h - ev.pageY;
369
+        var pos_x = this.wrapper.offset().left + drg_w - ev.pageX;
370
+
371
+        jQuery(document).on('touchmove.jBox-draggable-' + this.id + ' mousemove.jBox-draggable-' + this.id, function (ev) {
372
+          // Fire onDragStart event when jBox moves
373
+          if (!this.dragging && this.draggingStartX != ev.pageX && this.draggingStartY != ev.pageY) {
374
+            this._fireEvent('onDragStart');
375
+            this.dragging = true;
376
+          }
377
+
378
+          // Adjust position
379
+          this.wrapper.offset({
380
+            top: ev.pageY + pos_y - drg_h,
381
+            left: ev.pageX + pos_x - drg_w
382
+          });
383
+        }.bind(this));
384
+        ev.preventDefault();
385
+
386
+      }.bind(this)).on('touchend mouseup', function () {
387
+        // Remove drag event
388
+        jQuery(document).off('touchmove.jBox-draggable-' + this.id + ' mousemove.jBox-draggable-' + this.id);
389
+
390
+        // Fire onDragEnd event
391
+        this.dragging && this._fireEvent('onDragEnd');
392
+
393
+        // Reset dragging reference
394
+        this.dragging = false;
395
+
396
+        if ((this.type == 'Modal' || this.type == 'Confirm') && this.options.holdPosition) {
397
+          // Drag end captures new position
398
+          var jBoxOffset = jQuery('#' + this.id).offset(),
399
+            pos = {
400
+              x: jBoxOffset.left - jQuery(document).scrollLeft(),
401
+              y: jBoxOffset.top - jQuery(document).scrollTop()
402
+            };
403
+          this.position({position: pos, offset: {x: 0, y: 0}});
404
+        }
405
+      }.bind(this));
406
+
407
+      // Get highest z-index
408
+      if (!this.trueModal) {
409
+        jBox.zIndexMaxDragover = !jBox.zIndexMaxDragover ? this.options.zIndex : Math.max(jBox.zIndexMaxDragover, this.options.zIndex);
410
+      }
411
+
412
+      return this;
413
+    };
414
+
415
+    // Create jBox
416
+
417
+    this._create = function ()
418
+    {
419
+      // Abort if jBox was created already
420
+      if (this.wrapper) return;
421
+
422
+      // Create wrapper
423
+      this.wrapper = jQuery('<div/>', {
424
+        id: this.id,
425
+        'class': 'jBox-wrapper' + (this.type ? ' jBox-' + this.type : '') + (this.options.theme ? ' jBox-' + this.options.theme : '') + (this.options.addClass ? ' ' + this.options.addClass : '')
426
+      }).css({
427
+        position: (this.options.fixed ? 'fixed' : 'absolute'),
428
+        display: 'none',
429
+        opacity: 0,
430
+        zIndex: this.options.zIndex
431
+
432
+        // Save the jBox instance in the wrapper, so you can get access to your jBox when you only have the element
433
+      }).data('jBox', this);
434
+
435
+      // Add mouseleave event, only close jBox when the new target is not the source element
436
+      this.options.closeOnMouseleave && this.wrapper.on('mouseleave', function (ev) {
437
+        !this.source || !(ev.relatedTarget == this.source[0] || jQuery.inArray(this.source[0], jQuery(ev.relatedTarget).parents('*')) !== -1) && this.close();
438
+      }.bind(this));
439
+
440
+      // Add closeOnClick: 'box' events
441
+      (this.options.closeOnClick == 'box') && this.wrapper.on('click tap', function () { this.close({ignoreDelay: true}); }.bind(this));
442
+
443
+      // Create container
444
+      this.container = jQuery('<div class="jBox-container"/>').appendTo(this.wrapper);
445
+
446
+      // Create content
447
+      this.content = jQuery('<div class="jBox-content"/>').appendTo(this.container);
448
+
449
+      // Create footer
450
+      this.options.footer && (this.footer = jQuery('<div class="jBox-footer"/>').append(this.options.footer).appendTo(this.container));
451
+
452
+      // Isolate scrolling
453
+      this.options.isolateScroll && this._isolateScroll(this.content);
454
+
455
+      // Create close button
456
+      if (this.options.closeButton) {
457
+        var closeButtonSVG = this._createSVG('svg', [['viewBox', '0 0 24 24']]);
458
+        closeButtonSVG.appendChild(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']]));
459
+        this.closeButton = jQuery('<div class="jBox-closeButton jBox-noDrag"/>').on('click tap', function (ev) { this.close({ignoreDelay: true}); }.bind(this)).append(closeButtonSVG);
460
+
461
+        // Add close button to jBox container
462
+        if (this.options.closeButton == 'box' || (this.options.closeButton === true && !this.options.overlay && !this.options.title && !this.options.getTitle)) {
463
+          this.wrapper.addClass('jBox-closeButton-box');
464
+          this.closeButton.appendTo(this.container);
465
+        }
466
+      }
467
+
468
+      // Append jBox to DOM
469
+      this.wrapper.appendTo(this.options.appendTo);
470
+
471
+      // Fix adjustDistance if there is a close button in the box
472
+      this.wrapper.find('.jBox-closeButton').length &&  jQuery.each(['top', 'right', 'bottom', 'left'], function (index, pos) {
473
+        this.wrapper.find('.jBox-closeButton').css(pos) && this.wrapper.find('.jBox-closeButton').css(pos) != 'auto' && (this.options.adjustDistance[pos] = Math.max(this.options.adjustDistance[pos], this.options.adjustDistance[pos] + (((parseInt(this.wrapper.find('.jBox-closeButton').css(pos)) || 0) + (parseInt(this.container.css('border-' + pos + '-width')) || 0)) * -1)));
474
+      }.bind(this));
475
+
476
+      // Create pointer
477
+      if (this.options.pointer) {
478
+
479
+        // Get pointer vars and save globally
480
+        this.pointer = {
481
+          position: (this.options.pointTo != 'target') ? this.options.pointTo : this._getOpp(this.outside),
482
+          xy: (this.options.pointTo != 'target') ? this._getXY(this.options.pointTo) : this._getXY(this.outside),
483
+          align: 'center',
484
+          offset: 0
485
+        };
486
+
487
+        this.pointer.element = jQuery('<div class="jBox-pointer jBox-pointer-' + this.pointer.position + '"/>').appendTo(this.wrapper);
488
+        this.pointer.dimensions = {
489
+          x: this.pointer.element.outerWidth(),
490
+          y: this.pointer.element.outerHeight()
491
+        };
492
+
493
+        if (jQuery.type(this.options.pointer) == 'string') {
494
+          var split = this.options.pointer.split(':');
495
+          split[0] && (this.pointer.align = split[0]);
496
+          split[1] && (this.pointer.offset = parseInt(split[1]));
497
+        }
498
+        this.pointer.alignAttribute = (this.pointer.xy == 'x' ? (this.pointer.align == 'bottom' ? 'bottom' : 'top') : (this.pointer.align == 'right' ? 'right' : 'left'));
499
+
500
+        // Set wrapper CSS
501
+        this.wrapper.css('padding-' + this.pointer.position, this.pointer.dimensions[this.pointer.xy]);
502
+
503
+        // Set pointer CSS
504
+        this.pointer.element.css(this.pointer.alignAttribute, (this.pointer.align == 'center' ? '50%' : 0)).css('margin-' + this.pointer.alignAttribute, this.pointer.offset);
505
+        this.pointer.margin = {};
506
+        this.pointer.margin['margin-' + this.pointer.alignAttribute] = this.pointer.offset;
507
+
508
+        // Add a transform to fix centered position
509
+        (this.pointer.align == 'center') && this.pointer.element.css('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) + ')');
510
+
511
+        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')));
512
+
513
+        // Add class to wrapper for CSS access
514
+        this.wrapper.addClass('jBox-pointerPosition-' + this.pointer.position);
515
+      }
516
+
517
+      // Set title and content
518
+      this.setContent(this.options.content, true);
519
+      this.setTitle(this.options.title, true);
520
+
521
+      this.options.draggable && this._draggable();
522
+
523
+      // Fire onCreated event
524
+      this._fireEvent('onCreated');
525
+    };
526
+
527
+
528
+    // Create jBox onInit
529
+
530
+    this.options.createOnInit && this._create();
531
+
532
+
533
+    // Attach jBox
534
+
535
+    this.options.attach && this.attach();
536
+
537
+
538
+    // Attach document and window events
539
+
540
+    this._attachEvents = function ()
541
+    {
542
+      // Cancel countdown on mouseenter if delayOnHover
543
+      this.options.delayOnHover && jQuery('#' + this.id).on('mouseenter', function (ev) { this.isHovered = true; }.bind(this));
544
+
545
+      // Resume countdown on mouseleave if delayOnHover
546
+      this.options.delayOnHover && jQuery('#' + this.id).on('mouseleave', function (ev) { this.isHovered = false; }.bind(this));
547
+
548
+      // Positioning events
549
+      if ((this.options.adjustPosition || this.options.reposition) && !this.fixed && this.outside) {
550
+
551
+        // Trigger position events when scrolling
552
+        this.options.adjustTracker && jQuery(window).on('scroll.jBox-' + this.id, function (ev) { this.position(); }.bind(this));
553
+
554
+        // Trigger position events when resizing
555
+        (this.options.adjustPosition || this.options.reposition) && jQuery(window).on('resize.jBox-' + this.id, function (ev) { this.position(); }.bind(this));
556
+      }
557
+
558
+      // Mousemove events
559
+      this.options.target == 'mouse' && jQuery('body').on('mousemove.jBox-' + this.id, function (ev) { this.position({mouseTarget: {top: ev.pageY, left: ev.pageX}}); }.bind(this));
560
+    };
561
+
562
+
563
+    // Detach document and window events
564
+
565
+    this._detachEvents = function ()
566
+    {
567
+      // Closing event: closeOnEsc
568
+      this.options.closeOnEsc && jQuery(document).off('keyup.jBox-' + this.id);
569
+
570
+      // Closing event: closeOnClick
571
+      (this.options.closeOnClick === true || this.options.closeOnClick == 'body') && jQuery(document).off('click.jBox-' + this.id + ' tap.jBox-' + this.id);
572
+
573
+      // Positioning events
574
+      this.options.adjustTracker && jQuery(window).off('scroll.jBox-' + this.id);
575
+      (this.options.adjustPosition || this.options.reposition) && jQuery(window).off('resize.jBox-' + this.id);
576
+
577
+      // Mousemove events
578
+      this.options.target == 'mouse' && jQuery('body').off('mousemove.jBox-' + this.id);
579
+    };
580
+
581
+
582
+    // Show overlay
583
+
584
+    this._showOverlay = function ()
585
+    {
586
+      // Create the overlay if wasn't created already
587
+      if (!this.overlay) {
588
+
589
+        // Create element and append to the element where jBox is appended to
590
+        this.overlay = jQuery('<div id="' + this.id + '-overlay"/>').addClass('jBox-overlay' + (this.type ? ' jBox-overlay-' + this.type : '')).css({
591
+          display: 'none',
592
+          opacity: 0,
593
+          zIndex: this.options.zIndex - 1
594
+        }).appendTo(this.options.appendTo);
595
+
596
+        // Add a class name to the overlay
597
+        this.options.overlayClass && this.overlay.addClass(this.options.overlayClass);
598
+
599
+        // Add close button to overlay
600
+        (this.options.closeButton == 'overlay' || this.options.closeButton === true) && this.overlay.append(this.closeButton);
601
+
602
+        // Add closeOnClick: 'overlay' events
603
+        this.options.closeOnClick == 'overlay' && this.overlay.on('click tap', function () { this.close({ignoreDelay: true}); }.bind(this));
604
+
605
+        // Adjust option adjustDistance if there is a close button in the overlay
606
+        jQuery('#' + this.id + '-overlay .jBox-closeButton').length && (this.options.adjustDistance.top = Math.max(jQuery('#' + this.id + '-overlay .jBox-closeButton').outerHeight(), this.options.adjustDistance.top));
607
+      }
608
+
609
+      // Adjust zIndex
610
+      if (this.adjustZIndexOnOpen === true) {
611
+        this.overlay.css('zIndex', parseInt(this.wrapper.css('zIndex'), 10) - 1);
612
+      }
613
+
614
+      // Abort if overlay is already visible
615
+      if (this.overlay.css('display') == 'block') return;
616
+
617
+      // Show overlay
618
+      this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 1}, {
619
+        queue: false,
620
+        duration: this.options.fade,
621
+        start: function () { this.overlay.css({display: 'block'}); }.bind(this)
622
+      })) : this.overlay.css({display: 'block', opacity: 1});
623
+    };
624
+
625
+
626
+    // Hide overlay
627
+
628
+    this._hideOverlay = function ()
629
+    {
630
+      // Abort if the overlay wasn't created yet
631
+      if (!this.overlay) return;
632
+
633
+      // Hide overlay if no other jBox needs it
634
+      this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 0}, {
635
+        queue: false,
636
+        duration: this.options.fade,
637
+        complete: function () { this.overlay.css({display: 'none'}); }.bind(this)
638
+      })) : this.overlay.css({display: 'none', opacity: 0});
639
+    };
640
+
641
+
642
+    // Get the correct jBox dimensions by moving jBox out of viewport
643
+
644
+    this._exposeDimensions = function ()
645
+    {
646
+      // Move wrapper out of viewport
647
+      this.wrapper.css({
648
+        top: -10000,
649
+        left: -10000,
650
+        right: 'auto',
651
+        bottom: 'auto'
652
+      });
653
+
654
+      // Get jBox dimensions
655
+      var jBoxDimensions = {
656
+        x: this.wrapper.outerWidth(),
657
+        y: this.wrapper.outerHeight()
658
+      };
659
+
660
+      // Reset position to viewport
661
+      this.wrapper.css({
662
+        top: 'auto',
663
+        left: 'auto'
664
+      });
665
+
666
+      return jBoxDimensions;
667
+    };
668
+
669
+
670
+    // Generate CSS for animations and append to header
671
+
672
+    this._generateAnimationCSS = function ()
673
+    {
674
+      // Get open and close animations if none provided
675
+      (jQuery.type(this.options.animation) != 'object') && (this.options.animation = {
676
+        pulse: {open: 'pulse', close: 'zoomOut'},
677
+        zoomIn: {open: 'zoomIn', close: 'zoomIn'},
678
+        zoomOut: {open: 'zoomOut', close: 'zoomOut'},
679
+        move: {open: 'move', close: 'move'},
680
+        slide: {open: 'slide', close: 'slide'},
681
+        flip: {open: 'flip', close: 'flip'},
682
+        tada: {open: 'tada', close: 'zoomOut'}
683
+      }[this.options.animation]);
684
+
685
+      // Abort if animation not found
686
+      if (!this.options.animation) return null;
687
+
688
+      // Get direction var
689
+      this.options.animation.open && (this.options.animation.open = this.options.animation.open.split(':'));
690
+      this.options.animation.close && (this.options.animation.close = this.options.animation.close.split(':'));
691
+      this.options.animation.openDirection = this.options.animation.open[1] ? this.options.animation.open[1] : null;
692
+      this.options.animation.closeDirection = this.options.animation.close[1] ? this.options.animation.close[1] : null;
693
+      this.options.animation.open && (this.options.animation.open = this.options.animation.open[0]);
694
+      this.options.animation.close && (this.options.animation.close = this.options.animation.close[0]);
695
+
696
+      // Add 'Open' and 'Close' to animation names
697
+      this.options.animation.open && (this.options.animation.open += 'Open');
698
+      this.options.animation.close && (this.options.animation.close += 'Close');
699
+
700
+      // All animations
701
+      var animations = {
702
+        pulse: {
703
+          duration: 350,
704
+          css: [['0%', 'scale(1)'], ['50%', 'scale(1.1)'], ['100%', 'scale(1)']]
705
+        },
706
+        zoomInOpen: {
707
+          duration: (this.options.fade || 180),
708
+          css: [['0%', 'scale(0.9)'], ['100%', 'scale(1)']]
709
+        },
710
+        zoomInClose: {
711
+          duration: (this.options.fade || 180),
712
+          css: [['0%', 'scale(1)'], ['100%', 'scale(0.9)']]
713
+        },
714
+        zoomOutOpen: {
715
+          duration: (this.options.fade || 180),
716
+          css: [['0%', 'scale(1.1)'], ['100%', 'scale(1)']]
717
+        },
718
+        zoomOutClose: {
719
+          duration: (this.options.fade || 180),
720
+          css: [['0%', 'scale(1)'], ['100%', 'scale(1.1)']]
721
+        },
722
+        moveOpen: {
723
+          duration: (this.options.fade || 180),
724
+          positions: {top: {'0%': -12}, right: {'0%': 12}, bottom: {'0%': 12}, left: {'0%': -12}},
725
+          css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
726
+        },
727
+        moveClose: {
728
+          duration: (this.options.fade || 180),
729
+          timing: 'ease-in',
730
+          positions: {top: {'100%': -12}, right: {'100%': 12}, bottom: {'100%': 12}, left: {'100%': -12}},
731
+          css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
732
+        },
733
+        slideOpen: {
734
+          duration: 400,
735
+          positions: {top: {'0%': -400}, right: {'0%': 400}, bottom: {'0%': 400}, left: {'0%': -400}},
736
+          css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
737
+        },
738
+        slideClose: {
739
+          duration: 400,
740
+          timing: 'ease-in',
741
+          positions: {top: {'100%': -400}, right: {'100%': 400}, bottom: {'100%': 400}, left: {'100%': -400}},
742
+          css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
743
+        },
744
+        flipOpen: {
745
+          duration: 600,
746
+          css: [['0%', 'perspective(400px) rotateX(90deg)'], ['40%', 'perspective(400px) rotateX(-15deg)'], ['70%', 'perspective(400px) rotateX(15deg)'], ['100%', 'perspective(400px) rotateX(0deg)']]
747
+        },
748
+        flipClose: {
749
+          duration: (this.options.fade || 300),
750
+          css: [['0%', 'perspective(400px) rotateX(0deg)'], ['100%', 'perspective(400px) rotateX(90deg)']]
751
+        },
752
+        tada: {
753
+          duration: 800,
754
+          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)']]
755
+        }
756
+      };
757
+
758
+      // Set Open and Close names for standalone animations
759
+      jQuery.each(['pulse', 'tada'], function (index, item) { animations[item + 'Open'] = animations[item + 'Close'] = animations[item]; });
760
+
761
+      // Function to generate the CSS for the keyframes
762
+      var generateKeyframeCSS = function (ev, position)
763
+      {
764
+        // Generate keyframes CSS
765
+        var keyframe_css = '@keyframes jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
766
+        jQuery.each(animations[this.options.animation[ev]].css, function (index, item) {
767
+          var translate = position ? item[1].replace('%XY', this._getXY(position).toUpperCase()) : item[1];
768
+          animations[this.options.animation[ev]].positions && (translate = translate.replace('%V', animations[this.options.animation[ev]].positions[position][item[0]]));
769
+          keyframe_css += item[0] + ' {transform:' + translate + ';}';
770
+        }.bind(this));
771
+        keyframe_css += '}';
772
+
773
+        // Generate class CSS
774
+        keyframe_css += '.jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
775
+        keyframe_css += 'animation-duration: ' + animations[this.options.animation[ev]].duration + 'ms;';
776
+        keyframe_css += 'animation-name: jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ';';
777
+        keyframe_css += animations[this.options.animation[ev]].timing ? ('animation-timing-function: ' + animations[this.options.animation[ev]].timing + ';') : '';
778
+        keyframe_css += '}';
779
+
780
+        return keyframe_css;
781
+      }.bind(this);
782
+
783
+      // Generate css for each event and positions
784
+      this._animationCSS = '';
785
+      jQuery.each(['open', 'close'], function (index, ev)
786
+      {
787
+        // No CSS needed for closing with no fade
788
+        if (!this.options.animation[ev] || !animations[this.options.animation[ev]] || (ev == 'close' && !this.options.fade)) return '';
789
+
790
+        // Generate CSS
791
+        animations[this.options.animation[ev]].positions ?
792
+          jQuery.each(['top', 'right', 'bottom', 'left'], function (index2, position) { this._animationCSS += generateKeyframeCSS(ev, position); }.bind(this)) :
793
+          this._animationCSS += generateKeyframeCSS(ev);
794
+      }.bind(this));
795
+
796
+    };
797
+
798
+
799
+    // Add css for animations
800
+
801
+    this.options.animation && this._generateAnimationCSS();
802
+
803
+
804
+    // Block body clicks for 10ms to prevent extra event triggering
805
+
806
+    this._blockBodyClick = function ()
807
+    {
808
+      this.blockBodyClick = true;
809
+      setTimeout(function () { this.blockBodyClick = false; }.bind(this), 10);
810
+    };
811
+
812
+
813
+    // Animations
814
+
815
+    this._animate = function (ev)
816
+    {
817
+      // The event which triggers the animation
818
+      !ev && (ev = this.isOpen ? 'open' : 'close');
819
+
820
+      // Don't animate when closing with no fade duration
821
+      if (!this.options.fade && ev == 'close') return null;
822
+
823
+      // Get the current position, use opposite if jBox is flipped
824
+      var animationDirection = (this.options.animation[ev + 'Direction'] || ((this.align != 'center') ? this.align : this.options.attributes.x));
825
+      this.flipped && this._getXY(animationDirection) == (this._getXY(this.align)) && (animationDirection = this._getOpp(animationDirection));
826
+
827
+      // Add event and position classes
828
+      var classnames = 'jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + ' jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + '-' + animationDirection;
829
+      this.wrapper.addClass(classnames);
830
+
831
+      // Get duration of animation
832
+      var animationDuration = parseFloat(this.wrapper.css('animation-duration')) * 1000;
833
+      ev == 'close' && (animationDuration = Math.min(animationDuration, this.options.fade));
834
+
835
+      // Remove animation classes when animation is finished
836
+      setTimeout(function () { this.wrapper.removeClass(classnames); }.bind(this), animationDuration);
837
+    };
838
+
839
+
840
+    // Abort an animation
841
+
842
+    this._abortAnimation = function ()
843
+    {
844
+      // Remove all animation classes
845
+      var classes = this.wrapper.attr('class').split(' ').filter(function (c) {
846
+        return c.lastIndexOf('jBox-' + this.id + '-animation', 0) !== 0;
847
+      }.bind(this));
848
+      this.wrapper.attr('class', classes.join(' '));
849
+    };
850
+
851
+
852
+    // Adjust dimensions when browser is resized
853
+
854
+    if (this.options.responsiveWidth || this.options.responsiveHeight)
855
+    {
856
+      // Responsive positioning overrides options adjustPosition and reposition
857
+      // TODO: Only add this resize event when the other one from adjustPosition and reposition was not set
858
+      jQuery(window).on('resize.responsivejBox-' + this.id, function (ev) { if (this.isOpen) { this.position(); } }.bind(this));
859
+    }
860
+
861
+
862
+    // Fix audio options
863
+
864
+    jQuery.type(this.options.preloadAudio) === 'string' && (this.options.preloadAudio = [this.options.preloadAudio]);
865
+    jQuery.type(this.options.audio) === 'string' && (this.options.audio = {open: this.options.audio});
866
+    jQuery.type(this.options.volume) === 'number' && (this.options.volume = {open: this.options.volume, close: this.options.volume});
867
+
868
+    if (this.options.preloadAudio === true && this.options.audio) {
869
+      this.options.preloadAudio = [];
870
+      jQuery.each(this.options.audio, function (index, url) {
871
+        this.options.preloadAudio.push(url + '.mp3');
872
+        this.options.preloadAudio.push(url + '.ogg');
873
+      }.bind(this));
874
+    }
875
+
876
+
877
+    // Preload audio files
878
+
879
+    this.options.preloadAudio.length && jQuery.each(this.options.preloadAudio, function (index, url) {
880
+      var audio = new Audio();
881
+      audio.src = url;
882
+      audio.preload = 'auto';
883
+    });
884
+
885
+
886
+    // Fire onInit event
887
+
888
+    this._fireEvent('onInit');
889
+
890
+
891
+    return this;
892
+  };
893
+
894
+
895
+  // Attach jBox to elements
896
+
897
+  jBox.prototype.attach = function (elements, trigger)
898
+  {
899
+    // Get elements from options if none passed
900
+    !elements && (elements = this.options.attach);
901
+
902
+    // Convert selectors to jQuery objects
903
+    jQuery.type(elements) == 'string' && (elements = jQuery(elements));
904
+
905
+    // Get trigger event from options if not passed
906
+    !trigger && (trigger = this.options.trigger);
907
+
908
+    // Loop through elements and attach jBox
909
+    elements && elements.length && jQuery.each(elements, function (index, el) {
910
+      el = jQuery(el);
911
+
912
+      // Only attach if the element wasn't attached to this jBox already
913
+      if (!el.data('jBox-attached-' + this.id)) {
914
+
915
+        // Remove title attribute and store content on element
916
+        (this.options.getContent == 'title' && el.attr('title') != undefined) && el.data('jBox-getContent', el.attr('title')).removeAttr('title');
917
+
918
+        // Add Element to collection
919
+        this.attachedElements || (this.attachedElements = []);
920
+        this.attachedElements.push(el[0]);
921
+
922
+        // Add click or mouseenter event, click events can prevent default as well
923
+        el.on(trigger + '.jBox-attach-' + this.id, function (ev)
924
+        {
925
+          // Clear timer
926
+          this.timer && clearTimeout(this.timer);
927
+
928
+          // Block opening when jbox is open and the source element is triggering
929
+          if (trigger == 'mouseenter' && this.isOpen && this.source[0] == el[0]) return;
930
+
931
+          // Only close jBox if you click the current target element, otherwise open at new target
932
+          if (this.isOpen && this.source && this.source[0] != el[0]) var forceOpen = true;
933
+
934
+          // Set new source element
935
+          this.source = el;
936
+
937
+          // Set new target
938
+          !this.options.target && (this.target = el);
939
+
940
+          // Prevent default action on click
941
+          trigger == 'click' && this.options.preventDefault && ev.preventDefault();
942
+
943
+          // Toggle or open jBox
944
+          this[trigger == 'click' && !forceOpen ? 'toggle' : 'open']();
945
+
946
+        }.bind(this));
947
+
948
+        // Add close event for trigger event mouseenter
949
+        (this.options.trigger == 'mouseenter') && el.on('mouseleave', function (ev)
950
+        {
951
+          // Abort if jBox wasn't created yet
952
+          if (!this.wrapper) return null;
953
+
954
+          // If we have set closeOnMouseleave, do not close jBox when leaving attached element and mouse is over jBox
955
+          if (!this.options.closeOnMouseleave || !(ev.relatedTarget == this.wrapper[0] || jQuery(ev.relatedTarget).parents('#' + this.id).length)) this.close();
956
+        }.bind(this));
957
+
958
+        // Store
959
+        el.data('jBox-attached-' + this.id, trigger);
960
+
961
+        // Fire onAttach event
962
+        this._fireEvent('onAttach', el);
963
+      }
964
+
965
+    }.bind(this));
966
+
967
+    return this;
968
+  };
969
+
970
+
971
+  // Detach jBox from elements
972
+
973
+  jBox.prototype.detach = function (elements)
974
+  {
975
+    // Get elements from stores elements if none passed
976
+    !elements && (elements = this.attachedElements || []);
977
+
978
+    elements && elements.length && jQuery.each(elements, function (index, el) {
979
+      el = jQuery(el);
980
+
981
+      // Remove events
982
+      if (el.data('jBox-attached-' + this.id)) {
983
+        el.off(el.data('jBox-attached-' + this.id) + '.jBox-attach-' + this.id);
984
+        el.data('jBox-attached-' + this.id, null);
985
+      }
986
+      // Remove element from collection
987
+      this.attachedElements = jQuery.grep(this.attachedElements, function (value) {
988
+        return value != el[0];
989
+      });
990
+    }.bind(this));
991
+
992
+    return this;
993
+  };
994
+
995
+
996
+  // Set title
997
+
998
+  jBox.prototype.setTitle = function (title, ignore_positioning)
999
+  {
1000
+    // Abort if title to set
1001
+    if (title == null || title == undefined) return this;
1002
+
1003
+    // Create jBox if it wasn't created already
1004
+    !this.wrapper && this._create();
1005
+
1006
+    // Get the width and height of wrapper, only if they change we need to reposition
1007
+    var wrapperHeight = this.wrapper.outerHeight();
1008
+    var wrapperWidth = this.wrapper.outerWidth();
1009
+
1010
+    // Create title elements if they weren't created already
1011
+    if (!this.title) {
1012
+      this.titleContainer = jQuery('<div class="jBox-title"/>');
1013
+      this.title = jQuery('<div/>').appendTo(this.titleContainer);
1014
+      if (this.options.closeButton == 'title' || (this.options.closeButton === true && !this.options.overlay)) {
1015
+        this.wrapper.addClass('jBox-closeButton-title');
1016
+        this.closeButton.appendTo(this.titleContainer);
1017
+      }
1018
+      this.titleContainer.insertBefore(this.content);
1019
+      this._setTitleWidth();
1020
+    }
1021
+
1022
+    // Add or remove wrapper class
1023
+    this.wrapper[title ? 'addClass' : 'removeClass']('jBox-hasTitle');
1024
+
1025
+    // Set title html
1026
+    this.title.html(title);
1027
+
1028
+    // Adjust width of title
1029
+    wrapperWidth != this.wrapper.outerWidth() && this._setTitleWidth();
1030
+
1031
+    // Make jBox draggable
1032
+    this.options.draggable && this._draggable();
1033
+
1034
+    // Reposition if dimensions changed
1035
+    !ignore_positioning && this.options.repositionOnContent && (wrapperHeight != this.wrapper.outerHeight() || wrapperWidth != this.wrapper.outerWidth()) && this.position();
1036
+
1037
+    return this;
1038
+  };
1039
+
1040
+
1041
+  // Set content
1042
+
1043
+  jBox.prototype.setContent = function (content, ignore_positioning)
1044
+  {
1045
+    // Abort if no content to set
1046
+    if (content == null || content == undefined) return this;
1047
+
1048
+    // Create jBox if it wasn't created already
1049
+    !this.wrapper && this._create();
1050
+
1051
+    // Get the width and height of wrapper, only if they change we need to reposition
1052
+    var wrapperHeight = this.wrapper.outerHeight();
1053
+    var wrapperWidth = this.wrapper.outerWidth();
1054
+
1055
+    // Move all appended containers to body
1056
+    this.content.children('[data-jbox-content-appended]').appendTo('body').css({display: 'none'});
1057
+
1058
+    // Set the new content
1059
+    switch (jQuery.type(content)) {
1060
+      case 'string':
1061
+        this.content.html(content);
1062
+        break;
1063
+      case 'object':
1064
+        if (content && (content instanceof jQuery || content.constructor.prototype.jquery)) {
1065
+          this.content.html('');
1066
+          content.attr('data-jbox-content-appended', 1).appendTo(this.content).css({display: 'block'});
1067
+        } else {
1068
+          this.content.html(JSON.stringify(content));
1069
+        }
1070
+        break;
1071
+     }
1072
+
1073
+    // Adjust title width
1074
+    wrapperWidth != this.wrapper.outerWidth() && this._setTitleWidth();
1075
+
1076
+    // Make jBox draggable
1077
+    this.options.draggable && this._draggable();
1078
+
1079
+    // Reposition if dimensions changed
1080
+    !ignore_positioning && this.options.repositionOnContent && (wrapperHeight != this.wrapper.outerHeight() || wrapperWidth != this.wrapper.outerWidth()) && this.position();
1081
+
1082
+    return this;
1083
+  };
1084
+
1085
+
1086
+  // Set jBox dimensions
1087
+
1088
+  jBox.prototype.setDimensions = function (type, value, pos)
1089
+  {
1090
+    // Create jBox if it wasn't created already
1091
+    !this.wrapper && this._create();
1092
+
1093
+    // Default value is 'auto'
1094
+    value == undefined && (value = 'auto');
1095
+
1096
+    // Set CSS of content and title
1097
+    this.content.css(type, this._getInt(value));
1098
+
1099
+    // Adjust title width
1100
+    type == 'width' && this._setTitleWidth();
1101
+
1102
+    // Update options
1103
+    this.options[type] = value;
1104
+
1105
+    // Reposition by default
1106
+    (pos == undefined || pos) && this.position();
1107
+  };
1108
+
1109
+
1110
+  // Set jBox width or height
1111
+
1112
+  jBox.prototype.setWidth = function (value, pos) { this.setDimensions('width', value, pos); };
1113
+  jBox.prototype.setHeight = function (value, pos) { this.setDimensions('height', value, pos); };
1114
+
1115
+
1116
+  // Position jBox
1117
+
1118
+  jBox.prototype.position = function (options)
1119
+  {
1120
+    // Options are required
1121
+    !options && (options = {});
1122
+
1123
+    // Combine passed options with jBox options
1124
+    options = jQuery.extend(true, this.options, options);
1125
+
1126
+    // Get the target
1127
+    this.target = options.target || this.target || jQuery(window);
1128
+
1129
+    // Make sure target is a jQuery element
1130
+    !(this.target instanceof jQuery || this.target == 'mouse') && (this.target = jQuery(this.target));
1131
+
1132
+    // Abort if target is missing
1133
+    if (!this.target.length) return this;
1134
+
1135
+    // Reset content css to get original dimensions
1136
+    this.content.css({
1137
+      width: this._getInt(options.width, 'width'),
1138
+      height: this._getInt(options.height, 'height'),
1139
+      minWidth: this._getInt(options.minWidth, 'width'),
1140
+      minHeight: this._getInt(options.minHeight, 'height'),
1141
+      maxWidth: this._getInt(options.maxWidth, 'width'),
1142
+      maxHeight: this._getInt(options.maxHeight, 'height'),
1143
+    });
1144
+
1145
+    // Reset width of title
1146
+    this._setTitleWidth();
1147
+
1148
+    // Get jBox dimensions
1149
+    var jBoxDimensions = this._exposeDimensions();
1150
+
1151
+    // Check if target has fixed position, store in elements data
1152
+    this.target != 'mouse' && !this.target.data('jBox-' + this.id + '-fixed') && this.target.data('jBox-' + this.id + '-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');
1153
+
1154
+    // Get the window dimensions
1155
+    var windowDimensions = {
1156
+      x: jQuery(window).outerWidth(),
1157
+      y: jQuery(window).outerHeight(),
1158
+      top: (options.fixed && this.target.data('jBox-' + this.id + '-fixed') ? 0 : jQuery(window).scrollTop()),
1159
+      left: (options.fixed && this.target.data('jBox-' + this.id + '-fixed') ? 0 : jQuery(window).scrollLeft())
1160
+    };
1161
+    windowDimensions.bottom = windowDimensions.top + windowDimensions.y;
1162
+    windowDimensions.right = windowDimensions.left + windowDimensions.x;
1163
+
1164
+    // Get target offset
1165
+    try { var targetOffset = this.target.offset(); } catch (e) { var targetOffset = {top: 0, left: 0}; };
1166
+
1167
+    // When the target is fixed and jBox is fixed, remove scroll offset
1168
+    if (this.target != 'mouse' && this.target.data('jBox-' + this.id + '-fixed') == 'fixed' && options.fixed) {
1169
+      targetOffset.top = targetOffset.top - jQuery(window).scrollTop();
1170
+      targetOffset.left = targetOffset.left - jQuery(window).scrollLeft();
1171
+    }
1172
+
1173
+    // Get target dimensions
1174
+    var targetDimensions = {
1175
+      x: this.target == 'mouse' ? 12 : this.target.outerWidth(),
1176
+      y: this.target == 'mouse' ? 20 : this.target.outerHeight(),
1177
+      top: this.target == 'mouse' && options.mouseTarget ? options.mouseTarget.top : (targetOffset ? targetOffset.top : 0),
1178
+      left: this.target == 'mouse' && options.mouseTarget ? options.mouseTarget.left : (targetOffset ? targetOffset.left : 0)
1179
+    };
1180
+
1181
+    // Check if jBox is outside
1182
+    var outside = options.outside && !(options.position.x == 'center' && options.position.y == 'center');
1183
+
1184
+    // Get the available space on all sides
1185
+    var availableSpace = {
1186
+      x: windowDimensions.x - options.adjustDistance.left - options.adjustDistance.right, // TODO: substract position.x when they are numbers
1187
+      y: windowDimensions.y - options.adjustDistance.top - options.adjustDistance.bottom, // TODO: substract position.x when they are numbers
1188
+      left: !outside ? 0 : (targetDimensions.left - jQuery(window).scrollLeft() - options.adjustDistance.left),
1189
+      right: !outside ? 0 : (windowDimensions.x - targetDimensions.left + jQuery(window).scrollLeft() - targetDimensions.x - options.adjustDistance.right),
1190
+      top: !outside ? 0 : (targetDimensions.top - jQuery(window).scrollTop() - this.options.adjustDistance.top),
1191
+      bottom: !outside ? 0 : (windowDimensions.y - targetDimensions.top + jQuery(window).scrollTop() - targetDimensions.y - options.adjustDistance.bottom),
1192
+    };
1193
+
1194
+    // Get the default outside position, check if box will be flipped
1195
+    var jBoxOutsidePosition = {
1196
+      x: (options.outside == 'x' || options.outside == 'xy') && jQuery.type(options.position.x) != 'number' ? options.position.x : null,
1197
+      y: (options.outside == 'y' || options.outside == 'xy') && jQuery.type(options.position.y) != 'number' ? options.position.y : null
1198
+    };
1199
+    var flip = {x: false, y: false};
1200
+    (jBoxOutsidePosition.x && jBoxDimensions.x > availableSpace[jBoxOutsidePosition.x] && availableSpace[this._getOpp(jBoxOutsidePosition.x)] > availableSpace[jBoxOutsidePosition.x]) && (jBoxOutsidePosition.x = this._getOpp(jBoxOutsidePosition.x)) && (flip.x = true);
1201
+    (jBoxOutsidePosition.y && jBoxDimensions.y > availableSpace[jBoxOutsidePosition.y] && availableSpace[this._getOpp(jBoxOutsidePosition.y)] > availableSpace[jBoxOutsidePosition.y]) && (jBoxOutsidePosition.y = this._getOpp(jBoxOutsidePosition.y)) && (flip.y = true);
1202
+
1203
+    // Adjust responsive dimensions
1204
+    if (options.responsiveWidth || options.responsiveHeight) {
1205
+
1206
+      // Adjust width and height according to default outside position
1207
+      var adjustResponsiveWidth = function ()
1208
+      {
1209
+        if (options.responsiveWidth && jBoxDimensions.x > availableSpace[jBoxOutsidePosition.x || 'x']) {
1210
+          var contentWidth = availableSpace[jBoxOutsidePosition.x || 'x'] - (this.pointer && outside && options.outside == 'x' ? this.pointer.dimensions.x : 0) - parseInt(this.container.css('border-left-width')) - parseInt(this.container.css('border-right-width'));
1211
+          this.content.css({
1212
+            width: contentWidth > this.options.responsiveMinWidth ? contentWidth : null,
1213
+            minWidth: contentWidth < parseInt(this.content.css('minWidth')) ? 0 : null
1214
+          });
1215
+          this._setTitleWidth();
1216
+        }
1217
+        jBoxDimensions = this._exposeDimensions();
1218
+
1219
+      }.bind(this);
1220
+      options.responsiveWidth && adjustResponsiveWidth();
1221
+
1222
+      // After adjusting width, check if jBox will be flipped for y
1223
+      options.responsiveWidth && !flip.y && (jBoxOutsidePosition.y && jBoxDimensions.y > availableSpace[jBoxOutsidePosition.y] && availableSpace[this._getOpp(jBoxOutsidePosition.y)] > availableSpace[jBoxOutsidePosition.y]) && (jBoxOutsidePosition.y = this._getOpp(jBoxOutsidePosition.y)) && (flip.y = true);
1224
+
1225
+      // Adjust width and height according to default outside position
1226
+      var adjustResponsiveHeight = function ()
1227
+      {
1228
+        if (options.responsiveHeight && jBoxDimensions.y > availableSpace[jBoxOutsidePosition.y || 'y']) {
1229
+
1230
+          // Expose wrapper to get correct title height
1231
+          var exposeTitleFooterHeight = function () {
1232
+            if (!this.titleContainer && !this.footer) return 0;
1233
+            if (this.wrapper.css('display') == 'none') {
1234
+              this.wrapper.css('display', 'block');
1235
+              var height = (this.titleContainer ? this.titleContainer.outerHeight() : 0) + (this.footer ? this.footer.outerHeight() : 0);
1236
+              this.wrapper.css('display', 'none');
1237
+            } else {
1238
+              var height = (this.titleContainer ? this.titleContainer.outerHeight() : 0) + (this.footer ? this.footer.outerHeight() : 0);
1239
+            }
1240
+            return height || 0;
1241
+          }.bind(this);
1242
+
1243
+          var contentHeight = availableSpace[jBoxOutsidePosition.y || 'y'] - (this.pointer && outside && options.outside == 'y' ? this.pointer.dimensions.y : 0) - exposeTitleFooterHeight() - parseInt(this.container.css('border-top-width')) - parseInt(this.container.css('border-bottom-width'));
1244
+          this.content.css({height: contentHeight > this.options.responsiveMinHeight ? contentHeight : null});
1245
+          this._setTitleWidth();
1246
+        }
1247
+        jBoxDimensions = this._exposeDimensions();
1248
+
1249
+      }.bind(this);
1250
+      options.responsiveHeight && adjustResponsiveHeight();
1251
+
1252
+      // After adjusting height, check if jBox will be flipped for x
1253
+      options.responsiveHeight && !flip.x && (jBoxOutsidePosition.x && jBoxDimensions.x > availableSpace[jBoxOutsidePosition.x] && availableSpace[this._getOpp(jBoxOutsidePosition.x)] > availableSpace[jBoxOutsidePosition.x]) && (jBoxOutsidePosition.x = this._getOpp(jBoxOutsidePosition.x)) && (flip.x = true);
1254
+
1255
+      // Adjust width and height if jBox will be flipped
1256
+      if (options.adjustPosition && options.adjustPosition != 'move') {
1257
+        flip.x && adjustResponsiveWidth();
1258
+        flip.y && adjustResponsiveHeight();
1259
+      }
1260
+    }
1261
+
1262
+    // Store new positioning vars in local var
1263
+    var pos = {};
1264
+
1265
+    // Calculate positions
1266
+    var setPosition = function (p)
1267
+    {
1268
+      // Set number positions
1269
+      if (jQuery.type(options.position[p]) == 'number') {
1270
+        pos[options.attributes[p]] = options.position[p];
1271
+        return;
1272
+      }
1273
+
1274
+      // We have a target, so use 'left' or 'top' as attributes
1275
+      var a = options.attributes[p] = (p == 'x' ? 'left' : 'top');
1276
+
1277
+      // Start at target position
1278
+      pos[a] = targetDimensions[a];
1279
+
1280
+      // Set centered position
1281
+      if (options.position[p] == 'center') {
1282
+        pos[a] += Math.ceil((targetDimensions[p] - jBoxDimensions[p]) / 2);
1283
+
1284
+        // If the target is the window, adjust centered position depending on adjustDistance
1285
+        (this.target != 'mouse' && this.target[0] && this.target[0] == jQuery(window)[0]) && (pos[a] += (options.adjustDistance[a] - options.adjustDistance[this._getOpp(a)]) * 0.5);
1286
+        return;
1287
+      }
1288
+
1289
+      // Move inside
1290
+      (a != options.position[p]) && (pos[a] += targetDimensions[p] - jBoxDimensions[p]);
1291
+
1292
+      // Move outside
1293
+      (options.outside == p || options.outside == 'xy') && (pos[a] += jBoxDimensions[p] * (a != options.position[p] ? 1 : -1));
1294
+
1295
+    }.bind(this);
1296
+
1297
+    // Set position including offset
1298
+    setPosition('x');
1299
+    setPosition('y');
1300
+
1301
+    // Adjust position depending on pointer align
1302
+    if (this.pointer && options.pointTo == 'target' && jQuery.type(options.position.x) != 'number' && jQuery.type(options.position.y) != 'number') {
1303
+
1304
+      var adjustWrapper = 0;
1305
+
1306
+      // Where is the pointer aligned? Add or substract accordingly
1307
+      switch (this.pointer.align) {
1308
+        case 'center':
1309
+        if (options.position[this._getOpp(options.outside)] != 'center') {
1310
+          adjustWrapper += (jBoxDimensions[this._getOpp(options.outside)] / 2);
1311
+        }
1312
+        break;
1313
+        default:
1314
+        switch (options.position[this._getOpp(options.outside)]) {
1315
+          case 'center':
1316
+            adjustWrapper += ((jBoxDimensions[this._getOpp(options.outside)] / 2) - (this.pointer.dimensions[this._getOpp(options.outside)] / 2)) * (this.pointer.align == this._getTL(this.pointer.align) ? 1 : -1);
1317
+          break;
1318
+          default:
1319
+            adjustWrapper += (this.pointer.align != options.position[this._getOpp(options.outside)]) ?
1320
+
1321
+            // If pointer align is different to position align
1322
+            (this.dimensions[this._getOpp(options.outside)] * (jQuery.inArray(this.pointer.align, ['top', 'left']) !== -1 ? 1 : -1)) + ((this.pointer.dimensions[this._getOpp(options.outside)] / 2) * (jQuery.inArray(this.pointer.align, ['top', 'left']) !== -1 ? -1 : 1)) :
1323
+
1324
+            // If pointer align is same as position align
1325
+            (this.pointer.dimensions[this._getOpp(options.outside)] / 2) * (jQuery.inArray(this.pointer.align, ['top', 'left']) !== -1 ? 1 : -1);
1326
+          break;
1327
+        }
1328
+        break;
1329
+      }
1330
+
1331
+      adjustWrapper *= (options.position[this._getOpp(options.outside)] == this.pointer.alignAttribute ? -1 : 1);
1332
+      adjustWrapper += this.pointer.offset * (this.pointer.align == this._getOpp(this._getTL(this.pointer.align)) ? 1 : -1);
1333
+
1334
+      pos[this._getTL(this._getOpp(this.pointer.xy))] += adjustWrapper;
1335
+    }
1336
+
1337
+    // Add final offset
1338
+    pos[options.attributes.x] += options.offset.x;
1339
+    pos[options.attributes.y] += options.offset.y;
1340
+
1341
+    // Set CSS
1342
+    this.wrapper.css(pos);
1343
+
1344
+    // Adjust position
1345
+    if (options.adjustPosition) {
1346
+
1347
+      // Reset cached pointer position
1348
+      if (this.positionAdjusted) {
1349
+        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);
1350
+        this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this._getOpp(this.outside)).css(this.pointer.margin);
1351
+        this.positionAdjusted = false;
1352
+        this.flipped = false;
1353
+      }
1354
+
1355
+      // Find out where the jBox is out of view area
1356
+      var outYT = (windowDimensions.top > pos.top - (options.adjustDistance.top || 0)),
1357
+        outXR = (windowDimensions.right < pos.left + jBoxDimensions.x + (options.adjustDistance.right || 0)),
1358
+        outYB = (windowDimensions.bottom < pos.top + jBoxDimensions.y + (options.adjustDistance.bottom || 0)),
1359
+        outXL = (windowDimensions.left > pos.left - (options.adjustDistance.left || 0)),
1360
+        outX = outXL ? 'left' : (outXR ? 'right' : null),
1361
+        outY = outYT ? 'top' : (outYB ? 'bottom' : null),
1362
+        out = outX || outY;
1363
+
1364
+      // Only continue if jBox is out of view area
1365
+      if (out) {
1366
+
1367
+        if ((this.type == 'Modal' || this.type == 'Confirm')
1368
+          && jQuery.type(this.options.position.x) == 'number'
1369
+          && jQuery.type(this.options.position.y) == 'number'
1370
+        ) {
1371
+          var diffX = 0, diffY = 0;
1372
+          if (this.options.holdPosition) {
1373
+
1374
+            // Adjust left or right
1375
+            if (outXL) {
1376
+              diffX = windowDimensions.left - (pos.left - (options.adjustDistance.left || 0));
1377
+            } else if (outXR) {
1378
+              diffX = windowDimensions.right - (pos.left + jBoxDimensions.x + (options.adjustDistance.right || 0));
1379
+            }
1380
+
1381
+            // Adjust top or bottom
1382
+            if (outYT) {
1383
+              diffY = windowDimensions.top - (pos.top - (options.adjustDistance.top || 0));
1384
+            } else if (outYB) {
1385
+              diffY = windowDimensions.bottom - (pos.top + jBoxDimensions.y + (options.adjustDistance.bottom || 0));
1386
+            }
1387
+
1388
+            this.options.position.x = Math.max(windowDimensions.top, this.options.position.x + diffX);
1389
+            this.options.position.y = Math.max(windowDimensions.left, this.options.position.y + diffY);
1390
+
1391
+            setPosition('x');
1392
+            setPosition('y');
1393
+            this.wrapper.css(pos);
1394
+          }
1395
+          // Fire onPosition event
1396
+          this._fireEvent('onPosition');
1397
+
1398
+          return this;
1399
+        }
1400
+
1401
+        // Function to flip position
1402
+        if (options.adjustPosition === true || options.adjustPosition === 'flip') {
1403
+          var flipJBox = function (xy) {
1404
+            this.wrapper.css(this._getTL(xy), pos[this._getTL(xy)] + ((jBoxDimensions[this._getXY(xy)] + (options.offset[this._getXY(xy)] * (xy == 'top' || xy == 'left' ? -2 : 2)) + targetDimensions[this._getXY(xy)]) * (xy == 'top' || xy == 'left' ? 1 : -1)));
1405
+            this.pointer && this.wrapper.removeClass('jBox-pointerPosition-' + this.pointer.position).addClass('jBox-pointerPosition-' + this._getOpp(this.pointer.position)).css('padding', 0).css('padding-' + xy, this.pointer.dimensions[this._getXY(xy)]);
1406
+            this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + xy);
1407
+            this.positionAdjusted = true;
1408
+            this.flipped = true;
1409
+          }.bind(this);
1410
+
1411
+          // Flip jBox
1412
+          flip.x && flipJBox(this.options.position.x);
1413
+          flip.y && flipJBox(this.options.position.y);
1414
+        }
1415
+
1416
+        // Move jBox (only possible with pointer)
1417
+        var outMove = (this._getXY(this.outside) == 'x') ? outY : outX;
1418
+
1419
+        if (this.pointer && options.pointTo == 'target' && options.adjustPosition != 'flip' && this._getXY(outMove) == this._getOpp(this._getXY(this.outside))) {
1420
+
1421
+          // Get the maximum space we have availible to adjust
1422
+          if (this.pointer.align == 'center') {
1423
+            var spaceAvail = (jBoxDimensions[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));
1424
+          } else {
1425
+            var spaceAvail = (outMove == this.pointer.alignAttribute) ?
1426
+              parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) :
1427
+              jBoxDimensions[this._getXY(outMove)] - parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - this.pointer.dimensions[this._getXY(outMove)];
1428
+          }
1429
+
1430
+          // Get the overlapping space
1431
+          var spaceDiff = (outMove == this._getTL(outMove)) ?
1432
+            windowDimensions[this._getTL(outMove)] - pos[this._getTL(outMove)] + options.adjustDistance[outMove] :
1433
+            (windowDimensions[this._getOpp(this._getTL(outMove))] - pos[this._getTL(outMove)] - options.adjustDistance[outMove] - jBoxDimensions[this._getXY(outMove)]) * -1;
1434
+
1435
+          // Add overlapping space on left or top window edge
1436
+          if (outMove == this._getOpp(this._getTL(outMove)) && pos[this._getTL(outMove)] - spaceDiff < windowDimensions[this._getTL(outMove)] + options.adjustDistance[this._getTL(outMove)]) {
1437
+            spaceDiff -= windowDimensions[this._getTL(outMove)] + options.adjustDistance[this._getTL(outMove)] - (pos[this._getTL(outMove)] - spaceDiff);
1438
+          }
1439
+
1440
+          // Only adjust the maximum availible
1441
+          spaceDiff = Math.min(spaceDiff, spaceAvail);
1442
+
1443
+          // Move jBox
1444
+          if (spaceDiff <= spaceAvail && spaceDiff > 0) {
1445
+            this.pointer.element.css('margin-' + this.pointer.alignAttribute, parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - (spaceDiff * (outMove != this.pointer.alignAttribute ? -1 : 1)));
1446
+            this.wrapper.css(this._getTL(outMove), pos[this._getTL(outMove)] + (spaceDiff * (outMove != this._getTL(outMove) ? -1 : 1)));
1447
+            this.positionAdjusted = true;
1448
+          }
1449
+        }
1450
+      }
1451
+    }
1452
+
1453
+    // Fire onPosition event
1454
+    this._fireEvent('onPosition');
1455
+
1456
+    return this;
1457
+  };
1458
+
1459
+
1460
+  // Block scrolling
1461
+  // Borrowed from https://github.com/StephanWagner/unscroll
1462
+
1463
+  jBox.prototype.unscroll = function (elements) {
1464
+
1465
+    // Store reusable vars
1466
+    this.set = function (id, value) {
1467
+      if (!window.unscrollStore) {
1468
+        window.unscrollStore = {};
1469
+      }
1470
+      window.unscrollStore[id] = value;
1471
+    };
1472
+
1473
+    // Get reusable vars
1474
+    this.get = function (id) {
1475
+      return window.unscrollStore ? window.unscrollStore[id] : null;
1476
+    };
1477
+
1478
+    // Get the width of the scroll bar in pixel
1479
+    this.getScrollbarWidth = function () {
1480
+      if (this.get('scrollbarWidth')) {
1481
+        return this.get('scrollbarWidth') + 'px';
1482
+      }
1483
+      var scrollElement = document.createElement('div');
1484
+      scrollElement.style.width = '100px';
1485
+      scrollElement.style.height = '100px';
1486
+      scrollElement.style.overflow = 'scroll';
1487
+      scrollElement.style.position = 'absolute';
1488
+      scrollElement.style.top = '-10000';
1489
+
1490
+      document.body.appendChild(scrollElement);
1491
+      var scrollbarWidth = scrollElement.offsetWidth - scrollElement.clientWidth;
1492
+      document.body.removeChild(scrollElement);
1493
+
1494
+      this.set('scrollbarWidth', scrollbarWidth);
1495
+      return scrollbarWidth + 'px';
1496
+    }
1497
+
1498
+    // Add unscroll class to head
1499
+    function addUnscrollClassName() {
1500
+      if (document.getElementById('unscroll-class-name')) {
1501
+        return;
1502
+      }
1503
+      var css = '.unscrollable { overflow: hidden !important; }',
1504
+        head = document.head || document.getElementsByTagName('head')[0],
1505
+        style = document.createElement('style');
1506
+      style.type = 'text/css';
1507
+      style.setAttribute('id', 'unscroll-class-name');
1508
+      style.appendChild(document.createTextNode(css));
1509
+      head.appendChild(style);
1510
+    }
1511
+
1512
+    // Get the elements to adjust, force body element
1513
+    this.getElementsToAdjust = function (elements) {
1514
+      !elements && (elements = []);
1515
+
1516
+      if (typeof elements === 'string') {
1517
+        elements = [
1518
+          [elements, 'padding-right']
1519
+        ];
1520
+      }
1521
+
1522
+      elements.forEach(function (element, index) {
1523
+        if (typeof element === 'string') {
1524
+          elements[index] = [element, 'padding-right'];
1525
+        }
1526
+      });
1527
+
1528
+      var bodyFound = false;
1529
+      for (var i = 0; i < elements.length; i++) {
1530
+        if (elements[i][0].indexOf('body') !== -1) {
1531
+          bodyFound = true;
1532
+        }
1533
+      };
1534
+
1535
+      if (bodyFound === false) {
1536
+        elements.push(['body', 'padding-right']);
1537
+      }
1538
+
1539
+      return elements;
1540
+    }
1541
+
1542
+    this.pageHasScrollbar = function () {
1543
+      return this.getScrollbarWidth() && document.body.offsetHeight > window.innerHeight;
1544
+    }
1545
+
1546
+    // Clean up elements
1547
+    if (this.pageHasScrollbar()) {
1548
+      elements = this.getElementsToAdjust(elements);
1549
+
1550
+      // Loop through elements and adjust accordingly
1551
+      for (var i = 0; i < elements.length; i++) {
1552
+        var elementsDOM = document.querySelectorAll(elements[i][0]);
1553
+        for (var j = 0; j < elementsDOM.length; j++) {
1554
+          if (elementsDOM[j].getAttribute('data-unscroll')) {
1555
+            return;
1556
+          }
1557
+          var attribute = elements[i][1];
1558
+          var computedStyles = window.getComputedStyle(elementsDOM[j]);
1559
+          var computedStyle = computedStyles.getPropertyValue(attribute);
1560
+          elementsDOM[j].setAttribute('data-unscroll', attribute);
1561
+          if (!computedStyle) {
1562
+            computedStyle = '0px';
1563
+          }
1564
+          var operator = attribute == 'padding-right' || attribute == 'right' ? '+' : '-';
1565
+          elementsDOM[j].style[attribute] = 'calc(' + computedStyle + ' ' + operator + ' ' + this.getScrollbarWidth() + ')';
1566
+        }
1567
+      }
1568
+    }
1569
+
1570
+    // Make the page unscrollable
1571
+    addUnscrollClassName();
1572
+    document.body.classList.add('unscrollable');
1573
+  }
1574
+
1575
+  jBox.prototype.unscroll.reset = function () {
1576
+    var elements = document.querySelectorAll('[data-unscroll]');
1577
+
1578
+    for (var i = 0; i < elements.length; i++) {
1579
+      var attribute = elements[i].getAttribute('data-unscroll');
1580
+      elements[i].style[attribute] = null;
1581
+      elements[i].removeAttribute('data-unscroll');
1582
+    }
1583
+    document.body.classList.remove('unscrollable');
1584
+  }
1585
+
1586
+
1587
+  // Open jBox
1588
+
1589
+  jBox.prototype.open = function (options)
1590
+  {
1591
+    // Create blank options if none passed
1592
+    !options && (options = {});
1593
+
1594
+    // Abort if jBox was destroyed
1595
+    if (this.isDestroyed) return this;
1596
+
1597
+    // Construct jBox if not already constructed
1598
+    !this.wrapper && this._create();
1599
+
1600
+    // Add css to header if not added already
1601
+    !this._styles && (this._styles = jQuery('<style/>').append(this._animationCSS).appendTo(jQuery('head')));
1602
+
1603
+    // Abort any opening or closing timer
1604
+    this.timer && clearTimeout(this.timer);
1605
+
1606
+    // Block body click for 10ms, so jBox can open on attached elements while closeOnClick = 'body'
1607
+    this._blockBodyClick();
1608
+
1609
+    // Block opening
1610
+    if (this.isDisabled) return this;
1611
+
1612
+    // Closing event: closeOnEsc
1613
+    this.options.closeOnEsc && jQuery(document).on('keyup.jBox-' + this.id, function (ev) { if (ev.keyCode == 27) { this.close({ignoreDelay: true}); }}.bind(this));
1614
+
1615
+    // Closing event: closeOnClick
1616
+    if (this.options.closeOnClick === true || this.options.closeOnClick === 'body') {
1617
+      jQuery('body').on('click.jBox-' + this.id + ' tap.jBox-' + this.id, function (ev) {
1618
+        if (this.blockBodyClick || (this.options.closeOnClick == 'body' && (ev.target == this.wrapper[0] || this.wrapper.has(ev.target).length))) return;
1619
+        this.close({ignoreDelay: true});
1620
+      }.bind(this));
1621
+
1622
+      // Fix for iOS event bubbling issue
1623
+      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
1624
+      this.isTouchDevice && jQuery('body > *').on('click.jBox-' + this.id + ' tap.jBox-' + this.id, function () {
1625
+        return true;
1626
+      });
1627
+    }
1628
+
1629
+    // Opening function
1630
+    var open = function () {
1631
+
1632
+      // Adjust zIndex
1633
+      if (this.adjustZIndexOnOpen === true) {
1634
+        jBox.zIndexMax = Math.max(
1635
+          parseInt(this.wrapper.css('zIndex'), 10),
1636
+          this.options.zIndex,
1637
+          jBox.zIndexMax || 0,
1638
+          jBox.zIndexMaxDragover || 0
1639
+        ) + 2;
1640
+        this.wrapper.css('zIndex', jBox.zIndexMax);
1641
+        this.options.zIndex = jBox.zIndexMax;
1642
+      }
1643
+
1644
+      // Set title from source element
1645
+      this.source && this.options.getTitle && (this.source.attr(this.options.getTitle) && this.setTitle(this.source.attr(this.options.getTitle), true));
1646
+
1647
+      // Set content from source element
1648
+      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) : (this.options.getContent == 'html' ? this.setContent(this.source.html(), true) : null)));
1649
+
1650
+      // Fire onOpen event
1651
+      this._fireEvent('onOpen');
1652
+
1653
+      // Get content from ajax
1654
+      if ((this.options.ajax && (this.options.ajax.url || (this.source && this.source.attr(this.options.ajax.getURL))) && (!this.ajaxLoaded || this.options.ajax.reload)) || (options.ajax && (options.ajax.url || options.ajax.data))) {
1655
+        // Send the content from stored data if there is any, otherwise load new data
1656
+        (this.options.ajax.reload != 'strict' && this.source && this.source.data('jBox-ajax-data') && !(options.ajax && (options.ajax.url || options.ajax.data))) ? this.setContent(this.source.data('jBox-ajax-data')) : this.ajax((options.ajax || null), true);
1657
+      }
1658
+
1659
+      // Set position
1660
+      (!this.positionedOnOpen || this.options.repositionOnOpen) && this.position(options) && (this.positionedOnOpen = true);
1661
+
1662
+      // Abort closing
1663
+      this.isClosing && this._abortAnimation();
1664
+
1665
+      // Open functions to call when jBox is closed
1666
+      if (!this.isOpen) {
1667
+
1668
+        // jBox is open now
1669
+        this.isOpen = true;
1670
+
1671
+        // Automatically close jBox after some time
1672
+        this.options.autoClose && (this.options.delayClose = this.options.autoClose) && this.close();
1673
+
1674
+        // Attach events
1675
+        this._attachEvents();
1676
+
1677
+        // Block scrolling
1678
+        if (this.options.blockScroll) {
1679
+          if (this.options.blockScrollAdjust) {
1680
+            if (jBox.blockScrollScopes) {
1681
+              jBox.blockScrollScopes++;
1682
+            } else {
1683
+              jBox.blockScrollScopes = 1;
1684
+              this.unscroll(Array.isArray(this.options.blockScrollAdjust) || typeof this.options.blockScrollAdjust === 'string' ? this.options.blockScrollAdjust : null);
1685
+            }
1686
+          } else {
1687
+            jQuery('body').addClass('jBox-blockScroll-' + this.id);
1688
+          }
1689
+        }
1690
+
1691
+        // Show overlay
1692
+        if (this.options.overlay) {
1693
+          this._showOverlay();
1694
+
1695
+          // TODO Optimize: We have to position here again, because if the overlay has a close button, the upper adjustDistance will be wrong
1696
+          this.position();
1697
+        }
1698
+
1699
+        // Only animate if jBox is completely closed
1700
+        this.options.animation && !this.isClosing && this._animate('open');
1701
+
1702
+        // Play audio file
1703
+        this.options.audio && this.options.audio.open && this.audio(this.options.audio.open, this.options.volume.open);
1704
+
1705
+        // Fading animation or show immediately
1706
+        if (this.options.fade) {
1707
+          this.wrapper.stop().animate({opacity: 1}, {
1708
+            queue: false,
1709
+            duration: this.options.fade,
1710
+            start: function () {
1711
+              this.isOpening = true;
1712
+              this.wrapper.css({display: 'block'});
1713
+            }.bind(this),
1714
+            always: function () {
1715
+              this.isOpening = false;
1716
+
1717
+              // Delay positioning for ajax to prevent positioning during animation
1718
+              setTimeout(function () { this.positionOnFadeComplete && this.position() && (this.positionOnFadeComplete = false); }.bind(this), 10);
1719
+            }.bind(this)
1720
+          });
1721
+        } else {
1722
+          this.wrapper.css({display: 'block', opacity: 1});
1723
+          this.positionOnFadeComplete && this.position() && (this.positionOnFadeComplete = false);
1724
+        }
1725
+      }
1726
+    }.bind(this);
1727
+
1728
+    // Open jBox
1729
+    this.options.delayOpen && !this.isOpen && !this.isClosing && !options.ignoreDelay ? (this.timer = setTimeout(open, this.options.delayOpen)) : open();
1730
+
1731
+    return this;
1732
+  };
1733
+
1734
+
1735
+  // Close jBox
1736
+
1737
+  jBox.prototype.close = function (options)
1738
+  {
1739
+    // Create blank options if none passed
1740
+    options || (options = {});
1741
+
1742
+    // Remove close events
1743
+    jQuery('body').off('click.jBox-' + this.id + ' tap.jBox-' + this.id);
1744
+    this.isTouchDevice && jQuery('body > *').off('click.jBox-' + this.id + ' tap.jBox-' + this.id);
1745
+
1746
+    // Abort if jBox was destroyed or is currently closing
1747
+    if (this.isDestroyed || this.isClosing) return this;
1748
+
1749
+    // Abort opening
1750
+    this.timer && clearTimeout(this.timer);
1751
+
1752
+    // Block body click for 10ms, so jBox can open on attached elements while closeOnClick = 'body' is true
1753
+    this._blockBodyClick();
1754
+
1755
+    // Block closing
1756
+    if (this.isDisabled) return this;
1757
+
1758
+    // Close function
1759
+    var close = function () {
1760
+
1761
+      // Fire onClose event
1762
+      this._fireEvent('onClose');
1763
+
1764
+      // Cancel the ajax call
1765
+      if (this.options.cancelAjaxOnClose) {
1766
+        this.cancelAjax();
1767
+      }
1768
+
1769
+      // Only close if jBox is open
1770
+      if (this.isOpen) {
1771
+
1772
+        // jBox is not open anymore
1773
+        this.isOpen = false;
1774
+
1775
+        // Detach events
1776
+        this._detachEvents();
1777
+
1778
+        // Unblock scrolling
1779
+        if (this.options.blockScroll) {
1780
+          if (this.options.blockScrollAdjust) {
1781
+            jBox.blockScrollScopes = jBox.blockScrollScopes ? --jBox.blockScrollScopes : 0;
1782
+            !jBox.blockScrollScopes && this.unscroll.reset();
1783
+          } else {
1784
+            jQuery('body').removeClass('jBox-blockScroll-' + this.id);
1785
+          }
1786
+        }
1787
+
1788
+        // Hide overlay
1789
+        this.options.overlay && this._hideOverlay();
1790
+
1791
+        // Only animate if jBox is compleately closed
1792
+        this.options.animation && !this.isOpening && this._animate('close');
1793
+
1794
+        // Play audio file
1795
+        this.options.audio && this.options.audio.close && this.audio(this.options.audio.close, this.options.volume.close);
1796
+
1797
+        // Get fade duration
1798
+        var fadeDuration = this.isTouchDevice && this.options.target == 'mouse' ? 0 : this.options.fade;
1799
+
1800
+        // Fading animation or show immediately
1801
+        if (fadeDuration) {
1802
+          this.wrapper.stop().animate({opacity: 0}, {
1803
+            queue: false,
1804
+            duration: fadeDuration,
1805
+            start: function () {
1806
+              this.isClosing = true;
1807
+            }.bind(this),
1808
+            complete: function () {
1809
+              this.wrapper.css({display: 'none'});
1810
+              this._fireEvent('onCloseComplete');
1811
+            }.bind(this),
1812
+            always: function () {
1813
+              this.isClosing = false;
1814
+            }.bind(this)
1815
+          });
1816
+        } else {
1817
+          this.wrapper.css({display: 'none', opacity: 0});
1818
+          this._fireEvent('onCloseComplete');
1819
+        }
1820
+      }
1821
+    }.bind(this);
1822
+
1823
+    // Close jBox
1824
+    if (options.ignoreDelay || (this.isTouchDevice && this.options.target == 'mouse')) {
1825
+      close();
1826
+    } else if ((this.options.delayOnHover || this.options.showCountdown) && this.options.delayClose > 10) {
1827
+      var self = this;
1828
+      var remaining = this.options.delayClose;
1829
+      var prevFrame = Date.now();
1830
+      if (this.options.showCountdown && !this.inner) {
1831
+        var outer = jQuery('<div class="jBox-countdown" />');
1832
+        this.inner = jQuery('<div class="jBox-countdown-inner" />');
1833
+        outer.prepend(this.inner);
1834
+        jQuery('#' + this.id).append(outer);
1835
+      }
1836
+      this.countdown = function(){
1837
+        var dateNow = Date.now();
1838
+        if (!self.isHovered) {
1839
+          remaining -= dateNow - prevFrame;
1840
+        }
1841
+        prevFrame = dateNow;
1842
+        if (remaining > 0) {
1843
+          if (self.options.showCountdown) {
1844
+            self.inner.css('width', (remaining * 100 / self.options.delayClose) + '%');
1845
+          }
1846
+          window.requestAnimationFrame(self.countdown);
1847
+        } else {
1848
+          close();
1849
+        }
1850
+      };
1851
+      window.requestAnimationFrame(this.countdown);
1852
+    } else {
1853
+      this.timer = setTimeout(close, Math.max(this.options.delayClose, 10));
1854
+    }
1855
+
1856
+    return this;
1857
+  };
1858
+
1859
+
1860
+  // Open or close jBox
1861
+
1862
+  jBox.prototype.toggle = function (options)
1863
+  {
1864
+    this[this.isOpen ? 'close' : 'open'](options);
1865
+    return this;
1866
+  };
1867
+
1868
+
1869
+  // Block opening and closing
1870
+
1871
+  jBox.prototype.disable = function ()
1872
+  {
1873
+    this.isDisabled = true;
1874
+    return this;
1875
+  };
1876
+
1877
+
1878
+  // Unblock opening and closing
1879
+
1880
+  jBox.prototype.enable = function ()
1881
+  {
1882
+    this.isDisabled = false;
1883
+    return this;
1884
+  };
1885
+
1886
+
1887
+  // Hide jBox
1888
+
1889
+  jBox.prototype.hide = function ()
1890
+  {
1891
+    this.disable();
1892
+    if (this.wrapper) {
1893
+      this.cacheWrapperDisplay = this.wrapper.css('display');
1894
+      this.wrapper.css({display: 'none'});
1895
+    }
1896
+    if (this.overlay) {
1897
+      this.cacheOverlayDisplay = this.overlay.css('display');
1898
+      this.overlay.css({display: 'none'});
1899
+    }
1900
+    return this;
1901
+  };
1902
+
1903
+
1904
+  // Show jBox
1905
+
1906
+  jBox.prototype.show = function ()
1907
+  {
1908
+    this.enable();
1909
+    if (this.wrapper && this.cacheWrapperDisplay) {
1910
+      this.wrapper.css({display: this.cacheWrapperDisplay});
1911
+      this.cacheWrapperDisplay = null;
1912
+    }
1913
+    if (this.overlay && this.cacheOverlayDisplay) {
1914
+      this.overlay.css({display: this.cacheOverlayDisplay});
1915
+      this.cacheOverlayDisplay = null;
1916
+    }
1917
+    return this;
1918
+  };
1919
+
1920
+
1921
+  // Get content from ajax
1922
+
1923
+  jBox.prototype.ajax = function (options, opening)
1924
+  {
1925
+    options || (options = {});
1926
+
1927
+    // Add data or url from source element if none set in options
1928
+    jQuery.each([['getData', 'data'], ['getURL', 'url']], function (index, item) {
1929
+      (this.options.ajax[item[0]] && !options[item[1]] && this.source && this.source.attr(this.options.ajax[item[0]]) != undefined) && (options[item[1]] = this.source.attr(this.options.ajax[item[0]]) || '');
1930
+    }.bind(this));
1931
+
1932
+    // Clone the system options
1933
+    var sysOptions = jQuery.extend(true, {}, this.options.ajax);
1934
+
1935
+    // Abort running ajax call
1936
+    this.cancelAjax();
1937
+
1938
+    // Extract events
1939
+    var beforeSend = options.beforeSend || sysOptions.beforeSend || function () {};
1940
+    var complete = options.complete || sysOptions.complete || function () {};
1941
+    var success = options.success || sysOptions.success || function () {};
1942
+    var error = options.error || sysOptions.error || function () {};
1943
+
1944
+    // Merge options
1945
+    var userOptions = jQuery.extend(true, sysOptions, options);
1946
+
1947
+    // Set new beforeSend event
1948
+    userOptions.beforeSend = function (xhr)
1949
+    {
1950
+      // jBox is loading
1951
+      userOptions.loadingClass && this.wrapper.addClass(userOptions.loadingClass === true ? 'jBox-loading' : userOptions.loadingClass);
1952
+
1953
+      // Add loading spinner
1954
+      userOptions.spinner && (this.spinnerDelay = setTimeout(function ()
1955
+      {
1956
+        // Add class for loading spinner
1957
+        this.wrapper.addClass('jBox-loading-spinner');
1958
+
1959
+        // Reposition jBox
1960
+        // TODO: Only reposition if dimensions change
1961
+        userOptions.spinnerReposition && (opening ? (this.positionOnFadeComplete = true) : this.position());
1962
+
1963
+        // Add spinner to container
1964
+        this.spinner = jQuery(userOptions.spinner !== true ? userOptions.spinner : '<div class="jBox-spinner"></div>').appendTo(this.container);
1965
+
1966
+        // Fix spinners position if there is a title
1967
+        this.titleContainer && this.spinner.css('position') == 'absolute' && this.spinner.css({transform: 'translateY(' + (this.titleContainer.outerHeight() * 0.5) + 'px)'});
1968
+
1969
+      }.bind(this), (this.content.html() == '' ? 0 : (userOptions.spinnerDelay || 0))));
1970
+
1971
+      // Fire users beforeSend event
1972
+      (beforeSend.bind(this))(xhr);
1973
+
1974
+    }.bind(this);
1975
+
1976
+    // Set up new complete event
1977
+    userOptions.complete = function (response)
1978
+    {
1979
+      // Abort spinner timeout
1980
+      this.spinnerDelay && clearTimeout(this.spinnerDelay);
1981
+
1982
+      // jBox finished loading
1983
+      this.wrapper.removeClass('jBox-loading jBox-loading-spinner jBox-loading-spinner-delay');
1984
+
1985
+      // Remove spinner
1986
+      this.spinner && this.spinner.length && this.spinner.remove() && userOptions.spinnerReposition && (opening ? (this.positionOnFadeComplete = true) : this.position());
1987
+
1988
+      // Store that ajax loading finished
1989
+      this.ajaxLoaded = true;
1990
+
1991
+      // Fire users complete event
1992
+      (complete.bind(this))(response);
1993
+
1994
+    }.bind(this);
1995
+
1996
+    // Set up new success event
1997
+    userOptions.success = function (response)
1998
+    {
1999
+      // Set content
2000
+      userOptions.setContent && this.setContent(response, true) && (opening ? (this.positionOnFadeComplete = true) : this.position());
2001
+
2002
+      // Store content in source element
2003
+      userOptions.setContent && this.source && this.source.data('jBox-ajax-data', response);
2004
+
2005
+      // Fire users success event
2006
+      (success.bind(this))(response);
2007
+
2008
+    }.bind(this);
2009
+
2010
+    // Add error event
2011
+    userOptions.error = function (response) { (error.bind(this))(response); }.bind(this);
2012
+
2013
+    // Send new ajax request
2014
+    this.ajaxRequest = jQuery.ajax(userOptions);
2015
+
2016
+    return this;
2017
+  };
2018
+
2019
+
2020
+  // Abort an ajax call
2021
+
2022
+  jBox.prototype.cancelAjax = function () {
2023
+    if (this.ajaxRequest) {
2024
+      this.ajaxRequest.abort();
2025
+      this.ajaxLoaded = false;
2026
+    }
2027
+  };
2028
+
2029
+
2030
+  // Play an audio file
2031
+
2032
+  jBox.prototype.audio = function (url, volume)
2033
+  {
2034
+    // URL is required
2035
+    if (!url) return this;
2036
+
2037
+    // Create intern audio object if it wasn't created already
2038
+    !jBox._audio && (jBox._audio = {});
2039
+
2040
+    // Create an audio element specific to this audio file if it doesn't exist already
2041
+    if (!jBox._audio[url]) {
2042
+      var audio = jQuery('<audio/>');
2043
+      jQuery('<source/>', {src: url + '.mp3'}).appendTo(audio);
2044
+      jQuery('<source/>', {src: url + '.ogg'}).appendTo(audio);
2045
+      jBox._audio[url] = audio[0];
2046
+    }
2047
+
2048
+    // Set volume
2049
+    jBox._audio[url].volume = Math.min(((volume != undefined ? volume : 100) / 100), 1);
2050
+
2051
+    // Try to pause current audio
2052
+    try {
2053
+      jBox._audio[url].pause();
2054
+      jBox._audio[url].currentTime = 0;
2055
+    } catch (e) {}
2056
+
2057
+    // Play audio
2058
+    jBox._audio[url].play();
2059
+
2060
+    return this;
2061
+  };
2062
+
2063
+
2064
+  // Apply custom animations to jBox
2065
+
2066
+  jBox._animationSpeeds = {
2067
+    'tada': 1000,
2068
+    'tadaSmall': 1000,
2069
+    'flash': 500,
2070
+    'shake': 400,
2071
+    'pulseUp': 250,
2072
+    'pulseDown': 250,
2073
+    'popIn': 250,
2074
+    'popOut': 250,
2075
+    'fadeIn': 200,
2076
+    'fadeOut': 200,
2077
+    'slideUp': 400,
2078
+    'slideRight': 400,
2079
+    'slideLeft': 400,
2080
+    'slideDown': 400
2081
+  };
2082
+
2083
+  jBox.prototype.animate = function (animation, options)
2084
+  {
2085
+    // Options are required
2086
+    !options && (options = {});
2087
+
2088
+    // Timout needs to be an object
2089
+    !this.animationTimeout && (this.animationTimeout = {});
2090
+
2091
+    // Use jBox wrapper by default
2092
+    !options.element && (options.element = this.wrapper);
2093
+
2094
+    // Give the element an unique id
2095
+    !options.element.data('jBox-animating-id') && options.element.data('jBox-animating-id', jBox._getUniqueElementID());
2096
+
2097
+    // Abort if element is animating
2098
+    if (options.element.data('jBox-animating')) {
2099
+      options.element.removeClass(options.element.data('jBox-animating')).data('jBox-animating', null);
2100
+      this.animationTimeout[options.element.data('jBox-animating-id')] && clearTimeout(this.animationTimeout[options.element.data('jBox-animating-id')]);
2101
+    }
2102
+
2103
+    // Animate the element
2104
+    options.element.addClass('jBox-animated-' + animation).data('jBox-animating', 'jBox-animated-' + animation);
2105
+    this.animationTimeout[options.element.data('jBox-animating-id')] = setTimeout((function() { options.element.removeClass(options.element.data('jBox-animating')).data('jBox-animating', null); options.complete && options.complete(); }), jBox._animationSpeeds[animation]);
2106
+  };
2107
+
2108
+  // https://gist.github.com/AlexEmashev/ee8302b5036b01362f63dab35948401f
2109
+  jBox.prototype.swipeDetector = function (swipeTarget, options) {
2110
+    // States: 0 - no swipe, 1 - swipe started, 2 - swipe released
2111
+    var swipeState = 0;
2112
+    // Coordinates when swipe started
2113
+    var startX = 0;
2114
+    var startY = 0;
2115
+    // Distance of swipe
2116
+    var pixelOffsetX = 0;
2117
+    var pixelOffsetY = 0;
2118
+
2119
+    var defaultSettings = {
2120
+      // Amount of pixels, when swipe don't count.
2121
+      swipeThreshold: 70,
2122
+      // Flag that indicates that plugin should react only on touch events.
2123
+      // Not on mouse events too.
2124
+      useOnlyTouch: false
2125
+    };
2126
+
2127
+    // Initializer
2128
+    (function init() {
2129
+      options = jQuery.extend(defaultSettings, options);
2130
+      // Support touch and mouse as well.
2131
+      swipeTarget.on("mousedown touchstart", swipeStart);
2132
+      $("html").on("mouseup touchend", swipeEnd);
2133
+      $("html").on("mousemove touchmove", swiping);
2134
+    })();
2135
+
2136
+    function swipeStart(event) {
2137
+      if (options.useOnlyTouch && !event.originalEvent.touches) {
2138
+        return;
2139
+      }
2140
+
2141
+      if (event.originalEvent.touches) {
2142
+        event = event.originalEvent.touches[0];
2143
+      }
2144
+
2145
+      if (swipeState === 0) {
2146
+        swipeState = 1;
2147
+        startX = event.clientX;
2148
+        startY = event.clientY;
2149
+      }
2150
+    }
2151
+
2152
+    function swipeEnd(event) {
2153
+      if (swipeState === 2) {
2154
+        swipeState = 0;
2155
+
2156
+        if (
2157
+          Math.abs(pixelOffsetX) > Math.abs(pixelOffsetY) &&
2158
+          Math.abs(pixelOffsetX) > options.swipeThreshold
2159
+        ) {
2160
+          // Horizontal Swipe
2161
+          if (pixelOffsetX < 0) {
2162
+            swipeTarget.trigger($.Event("swipeLeft.sd"));
2163
+          } else {
2164
+            swipeTarget.trigger($.Event("swipeRight.sd"));
2165
+          }
2166
+        } else if (Math.abs(pixelOffsetY) > options.swipeThreshold) {
2167
+          // Vertical swipe
2168
+          if (pixelOffsetY < 0) {
2169
+            swipeTarget.trigger($.Event("swipeUp.sd"));
2170
+          } else {
2171
+            swipeTarget.trigger($.Event("swipeDown.sd"));
2172
+          }
2173
+        }
2174
+      }
2175
+    }
2176
+
2177
+    function swiping(event) {
2178
+      // If swipe don't occuring, do nothing.
2179
+      if (swipeState !== 1) return;
2180
+
2181
+      if (event.originalEvent.touches) {
2182
+        event = event.originalEvent.touches[0];
2183
+      }
2184
+
2185
+      var swipeOffsetX = event.clientX - startX;
2186
+      var swipeOffsetY = event.clientY - startY;
2187
+
2188
+      if (
2189
+        Math.abs(swipeOffsetX) > options.swipeThreshold ||
2190
+        Math.abs(swipeOffsetY) > options.swipeThreshold
2191
+      ) {
2192
+        swipeState = 2;
2193
+        pixelOffsetX = swipeOffsetX;
2194
+        pixelOffsetY = swipeOffsetY;
2195
+      }
2196
+    }
2197
+
2198
+    return swipeTarget; // Return element available for chaining.
2199
+  }
2200
+
2201
+
2202
+  // Destroy jBox and remove it from DOM
2203
+
2204
+  jBox.prototype.destroy = function ()
2205
+  {
2206
+    // Detach from attached elements
2207
+    this.detach();
2208
+
2209
+    // If jBox is open, close without delay
2210
+    this.isOpen && this.close({ignoreDelay: true});
2211
+
2212
+    // Remove wrapper
2213
+    this.wrapper && this.wrapper.remove();
2214
+
2215
+    // Remove overlay
2216
+    this.overlay && this.overlay.remove();
2217
+
2218
+    // Remove styles
2219
+    this._styles && this._styles.remove();
2220
+
2221
+    // Tell the jBox instance it is destroyed
2222
+    this.isDestroyed = true;
2223
+
2224
+    return this;
2225
+  };
2226
+
2227
+
2228
+  // Get a unique ID for jBoxes
2229
+
2230
+  jBox._getUniqueID = (function ()
2231
+  {
2232
+    var i = 1;
2233
+    return function () { return i++; };
2234
+  }());
2235
+
2236
+
2237
+  // Get a unique ID for animating elements
2238
+
2239
+  jBox._getUniqueElementID = (function ()
2240
+  {
2241
+    var i = 1;
2242
+    return function () { return i++; };
2243
+  }());
2244
+
2245
+
2246
+  // Function to create jBox plugins
2247
+
2248
+  jBox._pluginOptions = {};
2249
+  jBox.plugin = function (type, options)
2250
+  {
2251
+    jBox._pluginOptions[type] = options;
2252
+  };
2253
+
2254
+
2255
+  // Make jBox usable with jQuery selectors
2256
+
2257
+  jQuery.fn.jBox = function (type, options) {
2258
+    // Variables type and object are required
2259
+    !type && (type = {});
2260
+    !options && (options = {});
2261
+
2262
+    // Return a new instance of jBox with the selector as attached element
2263
+    return new jBox(type, jQuery.extend(options, {
2264
+      attach: this
2265
+    }));
2266
+  };
2267
+
2268
+  return jBox;
2269
+
2270
+};
2271
+
2272
+(function (root, factory) {
2273
+  if (typeof define === 'function' && define.amd) {
2274
+    define(['jquery'], function (jQuery) {
2275
+      return (root.jBox = factory(jQuery));
2276
+    });
2277
+  } else if (typeof module === 'object' && module.exports) {
2278
+    module.exports = (root.jBox = factory(require('jquery')));
2279
+  } else {
2280
+    root.jBox = factory(root.jQuery);
2281
+  }
2282
+}(this, function (jQuery) {
2283
+  var jBox = jBoxWrapper(jQuery);
2284
+  try { typeof jBoxConfirmWrapper !== 'undefined' && jBoxConfirmWrapper && jBoxConfirmWrapper(jBox, jQuery); } catch(e) { console.error(e); }
2285
+  try { typeof jBoxImageWrapper !== 'undefined' && jBoxImageWrapper && jBoxImageWrapper(jBox, jQuery); } catch(e) { console.error(e); }
2286
+  try { typeof jBoxNoticeWrapper !== 'undefined' && jBoxNoticeWrapper && jBoxNoticeWrapper(jBox, jQuery); } catch(e) { console.error(e); }
2287
+  return jBox;
2288
+}));
2289
+
2290
+//# sourceMappingURL=jBox.js.map