/** * jBox is a jQuery plugin that makes it easy to create customizable tooltips, modal windows, image galleries and more. * * Author: Stephan Wagner (https://stephanwagner.me) * * License: MIT (https://opensource.org/licenses/MIT) * * Requires: jQuery 3.4.1 (https://code.jquery.com/jquery-3.4.1.min.js) * * Documentation: https://stephanwagner.me/jBox/documentation * * Demos: https://stephanwagner.me/jBox/demos */ function jBoxWrapper(jQuery) { var jBox = function jBox(type, options) { // Options (https://stephanwagner.me/jBox/options) this.options = { // jBox ID id: null, // Choose a unique id, otherwise jBox will set one for you (jBox1, jBox2, ...) // Dimensions width: 'auto', // The width of the content area, e.g. 'auto', 200, '80%' height: 'auto', // The height of the content area minWidth: null, // Minimal width minHeight: null, // Minimal height maxWidth: null, // Maximal width maxHeight: null, // Maximal height // Responsive dimensions responsiveWidth: true, // Adjusts the width to fit the viewport responsiveHeight: true, // Adjusts the height to fit the viewport responsiveMinWidth: 100, // Don't adjust width below this value (in pixel) responsiveMinHeight: 100, // Don't adjust height below this value (in pixel) // Attach attach: null, // A jQuery selector to elements that will open and close your jBox, e.g. '.tooltip' trigger: 'click', // The event to open or close your jBox, use 'click', 'touchclick' or 'mouseenter' preventDefault: false, // Prevent the default event when opening jBox, e.g. don't follow the href in a link // Content 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 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 title: null, // Adds a title to your jBox getTitle: null, // Get the title from an attribute when jBox opens, e.g. getTitle: 'data-title' footer: null, // Adds a footer to your jBox isolateScroll: true, // Isolates scrolling to the content container // AJAX 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/) url: null, // The URL to send the AJAX request to data: '', // Data to send with your AJAX request, e.g. {id: 82, limit: 10} 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 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" 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" setContent: true, // Automatically set the response as new content when the AJAX request is finished 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' 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: '
' spinnerDelay: 300, // Milliseconds to wait until spinner appears spinnerReposition: true // Repositions jBox when the spinner is added or removed }, cancelAjaxOnClose: true, // Cancels the ajax call when jBox closes and it hasn't finished loading yet // Position 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 position: { x: 'center', // Horizontal position, use a number, 'left', 'right' or 'center' y: 'center' // Vertical position, use a number, 'top', 'bottom' or 'center' }, outside: null, // Use 'x', 'y', or 'xy' to move your jBox outside of the target element offset: 0, // Offset to final position, you can set different values for x and y with an object, e.g. {x: 20, y: 10} attributes: { // Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20} x: 'left', // Horizontal position, use 'left' or 'right' y: 'top' // Vertical position, use 'top' or 'bottom' }, fixed: false, // Your jBox will stay on position when scrolling 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 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 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} reposition: true, // Calculates new position when the window-size changes repositionOnOpen: true, // Calculates new position each time jBox opens (rather than only when it opens the first time) repositionOnContent: true, // Calculates new position when the content changes with .setContent() or .setTitle() holdPosition: true, // Keeps current position if space permits. Applies only to 'Modal' type. // Pointer 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' 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' // Animations fade: 180, // Fade duration in ms, set to 0 or false to disable animation: null, // Animation when opening or closing, use 'pulse', 'zoomIn', 'zoomOut', 'move', 'slide', 'flip', 'tada' (CSS inspired from Daniel Edens Animate.css: http://daneden.me/animate) // Appearance theme: 'Default', // Set a jBox theme class addClass: null, // Adds classes to the wrapper overlay: false, // Adds an overlay to hide page content when jBox opens (adjust color and opacity with CSS) overlayClass: null, // Add a class name to the overlay zIndex: 10000, // Use a high z-index, or set to 'auto' to bring to front on open // Delays delayOpen: 0, // Delay opening in ms. Note that the delay will be ignored if your jBox didn't finish closing delayClose: 0, // Delay closing in ms. Nnote that there is always a closing delay of at least 10ms to ensure jBox won't be closed when opening right away // Closing closeOnEsc: false, // Close jBox when pressing [esc] key closeOnClick: false, // Close jBox with mouseclick. Use true (click anywhere), 'box' (click on jBox itself), 'overlay' (click on the overlay), 'body' (click anywhere but jBox) closeOnMouseleave: false, // Close jBox when the mouse leaves the jBox area or the area of the attached element closeButton: false, // Adds a close button to your jBox. Use 'title', '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) // Other options 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 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 blockScroll: false, // Blocks scrolling when jBox is open blockScrollAdjust: true, // Adjust page elements to avoid content jumps when scrolling is blocked. See more here: https://github.com/StephanWagner/unscroll 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/) dragOver: true, // When you have multiple draggable jBoxes, the one you select will always move over the other ones autoClose: false, // Time in ms when jBox will close automatically after it was opened delayOnHover: false, // Delay auto-closing while mouse is hovered showCountdown: false, // Display a nice progress-indicator when autoClose is enabled // 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); } 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'] 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'} volume: 100, // The volume in percent. To have different volumes for opening and closeing, use an object, e.g. {open: 75, close: 100} // Events // Note that you can use 'this' in all event functions, it refers to your jBox object (e.g. onInit: function () { this.open(); }) onInit: null, // Fired when jBox is initialized 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'}); } onPosition: null, // Fired when jBox is positioned onCreated: null, // Fired when jBox is created and availible in DOM onOpen: null, // Fired when jBox opens onClose: null, // Fired when jBox closes onCloseComplete: null, // Fired when jBox is completely closed (when fading is finished) onDragStart: null, // Fired when dragging starts onDragEnd: null // Fired when dragging finished }; // Default plugin options this._pluginOptions = { // Default options for tooltips 'Tooltip': { getContent: 'title', trigger: 'mouseenter', position: { x: 'center', y: 'top' }, outside: 'y', pointer: true }, // Default options for mouse tooltips 'Mouse': { responsiveWidth: false, responsiveHeight: false, adjustPosition: 'flip', target: 'mouse', trigger: 'mouseenter', position: { x: 'right', y: 'bottom' }, outside: 'xy', offset: 5 }, // Default options for modal windows 'Modal': { target: jQuery(window), fixed: true, blockScroll: true, closeOnEsc: true, closeOnClick: 'overlay', closeButton: true, overlay: true, animation: 'zoomIn' }, }; // Merge options this.options = jQuery.extend(true, this.options, this._pluginOptions[type] ? this._pluginOptions[type] : jBox._pluginOptions[type], options); // Set the jBox type jQuery.type(type) == 'string' && (this.type = type); // Checks if the user is on a touch device, borrowed from https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js this.isTouchDevice = (function () { var prefixes = ' -webkit- -moz- -o- -ms- '.split(' '); var mq = function (query) { return window.matchMedia(query).matches; } if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { return true; } var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join(''); return mq(query); })(); // Add close event for body click when we are on touch device and jBox triggers on mouseenter if (this.isTouchDevice && this.options.trigger === 'mouseenter' && this.options.closeOnClick === false) { this.options.closeOnClick = 'body'; } // Local function to fire events this._fireEvent = function (event, pass) { this.options['_' + event] && (this.options['_' + event].bind(this))(pass); this.options[event] && (this.options[event].bind(this))(pass); }; // Get a unique jBox ID this.options.id === null && (this.options.id = 'jBox' + jBox._getUniqueID()); this.id = this.options.id; // Correct impossible options ((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = null); this.options.pointTo == 'target' && (!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false); // Correct multiple choice options jQuery.type(this.options.offset) != 'object' ? (this.options.offset = {x: this.options.offset, y: this.options.offset}) : (this.options.offset = jQuery.extend({x: 0, y: 0}, this.options.offset)); jQuery.type(this.options.adjustDistance) != 'object' ? (this.options.adjustDistance = {top: this.options.adjustDistance, right: this.options.adjustDistance, bottom: this.options.adjustDistance, left: this.options.adjustDistance}) : (this.options.adjustDistance = jQuery.extend({top: 5, left: 5, right: 5, bottom: 5}, this.options.adjustDistance)); // Save default outside position this.outside = this.options.outside && this.options.outside != 'xy' ? this.options.position[this.options.outside] : false; // Save where the jBox is aligned to 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)); // Adjust option zIndex jBox.zIndexMax = Math.max(jBox.zIndexMax || 0, this.options.zIndex === 'auto' ? 10000 : this.options.zIndex); if (this.options.zIndex === 'auto') { this.adjustZIndexOnOpen = true; jBox.zIndexMax += 2; this.options.zIndex = jBox.zIndexMax; this.trueModal = this.options.overlay; } // Internal positioning functions this._getOpp = function (opp) { return {left: 'right', right: 'left', top: 'bottom', bottom: 'top', x: 'y', y: 'x'}[opp]; }; this._getXY = function (xy) { return {left: 'x', right: 'x', top: 'y', bottom: 'y', center: 'x'}[xy]; }; this._getTL = function (tl) { return {left: 'left', right: 'left', top: 'top', bottom: 'top', center: 'left', x: 'left', y: 'top'}[tl]; }; // Get a dimension value in integer pixel dependent on appended element this._getInt = function (value, dimension) { if (value == 'auto') return 'auto'; if (value && jQuery.type(value) == 'string' && value.slice(-1) == '%') { return jQuery(window)[dimension == 'height' ? 'innerHeight' : 'innerWidth']() * parseInt(value.replace('%', '')) / 100; } return value; }; // Create an svg element this._createSVG = function (type, options) { var svg = document.createElementNS('http://www.w3.org/2000/svg', type); jQuery.each(options, function (index, item) { svg.setAttribute(item[0], (item[1] || '')); }); return svg; }; // Isolate scrolling in a container this._isolateScroll = function (el) { // Abort if element not found if (!el || !el.length) return; el.on('DOMMouseScroll.jBoxIsolateScroll mousewheel.jBoxIsolateScroll', function (ev) { var delta = ev.wheelDelta || (ev.originalEvent && ev.originalEvent.wheelDelta) || -ev.detail; var overflowBottom = this.scrollTop + el.outerHeight() - this.scrollHeight >= 0; var overflowTop = this.scrollTop <= 0; ((delta < 0 && overflowBottom) || (delta > 0 && overflowTop)) && ev.preventDefault(); }); }; // Set the title width to content width this._setTitleWidth = function () { // Abort if there is no title or width of content is auto if (!this.titleContainer || (this.content[0].style.width == 'auto' && !this.content[0].style.maxWidth)) return null; // Expose wrapper to get actual width if (this.wrapper.css('display') == 'none') { this.wrapper.css('display', 'block'); var contentWidth = this.content.outerWidth(); this.wrapper.css('display', 'none'); } else { var contentWidth = this.content.outerWidth(); } // Set max-width only this.titleContainer.css({maxWidth: (Math.max(contentWidth, parseInt(this.content[0].style.maxWidth)) || null)}); } // Make jBox draggable this._draggable = function () { // Abort if jBox is not draggable if (!this.options.draggable) return false; // Get the handle where jBox will be dragged with 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)); // Abort if no handle or if draggable was set already if (!handle || !(handle instanceof jQuery) || !handle.length || handle.data('jBox-draggable')) return false; // Add mouse events handle.addClass('jBox-draggable').data('jBox-draggable', true).on('touchstart mousedown', function (ev) { if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return; // Store current mouse position this.draggingStartX = ev.pageX; this.draggingStartY = ev.pageY; // Adjust z-index when dragging jBox over another draggable jBox if (this.options.dragOver && !this.trueModal && parseInt(this.wrapper.css('zIndex'), 10) <= jBox.zIndexMaxDragover) { jBox.zIndexMaxDragover += 1; this.wrapper.css('zIndex', jBox.zIndexMaxDragover); } var drg_h = this.wrapper.outerHeight(); var drg_w = this.wrapper.outerWidth(); var pos_y = this.wrapper.offset().top + drg_h - ev.pageY; var pos_x = this.wrapper.offset().left + drg_w - ev.pageX; jQuery(document).on('touchmove.jBox-draggable-' + this.id + ' mousemove.jBox-draggable-' + this.id, function (ev) { // Fire onDragStart event when jBox moves if (!this.dragging && this.draggingStartX != ev.pageX && this.draggingStartY != ev.pageY) { this._fireEvent('onDragStart'); this.dragging = true; } // Adjust position this.wrapper.offset({ top: ev.pageY + pos_y - drg_h, left: ev.pageX + pos_x - drg_w }); }.bind(this)); ev.preventDefault(); }.bind(this)).on('touchend mouseup', function () { // Remove drag event jQuery(document).off('touchmove.jBox-draggable-' + this.id + ' mousemove.jBox-draggable-' + this.id); // Fire onDragEnd event this.dragging && this._fireEvent('onDragEnd'); // Reset dragging reference this.dragging = false; if ((this.type == 'Modal' || this.type == 'Confirm') && this.options.holdPosition) { // Drag end captures new position var jBoxOffset = jQuery('#' + this.id).offset(), pos = { x: jBoxOffset.left - jQuery(document).scrollLeft(), y: jBoxOffset.top - jQuery(document).scrollTop() }; this.position({position: pos, offset: {x: 0, y: 0}}); } }.bind(this)); // Get highest z-index if (!this.trueModal) { jBox.zIndexMaxDragover = !jBox.zIndexMaxDragover ? this.options.zIndex : Math.max(jBox.zIndexMaxDragover, this.options.zIndex); } return this; }; // Create jBox this._create = function () { // Abort if jBox was created already if (this.wrapper) return; // Create wrapper this.wrapper = jQuery('
', { id: this.id, 'class': 'jBox-wrapper' + (this.type ? ' jBox-' + this.type : '') + (this.options.theme ? ' jBox-' + this.options.theme : '') + (this.options.addClass ? ' ' + this.options.addClass : '') }).css({ position: (this.options.fixed ? 'fixed' : 'absolute'), display: 'none', opacity: 0, zIndex: this.options.zIndex // Save the jBox instance in the wrapper, so you can get access to your jBox when you only have the element }).data('jBox', this); // Add mouseleave event, only close jBox when the new target is not the source element this.options.closeOnMouseleave && this.wrapper.on('mouseleave', function (ev) { !this.source || !(ev.relatedTarget == this.source[0] || jQuery.inArray(this.source[0], jQuery(ev.relatedTarget).parents('*')) !== -1) && this.close(); }.bind(this)); // Add closeOnClick: 'box' events (this.options.closeOnClick == 'box') && this.wrapper.on('click', function () { this.close({ignoreDelay: true}); }.bind(this)); // Create container this.container = jQuery('
').appendTo(this.wrapper); // Create content this.content = jQuery('
').appendTo(this.container); // Create footer this.options.footer && (this.footer = jQuery('