UNPKG

qtip2

Version:

Introducing... qTip2. The second generation of the advanced qTip plugin for the ever popular jQuery framework.

339 lines (274 loc) 9.2 kB
var MODAL, OVERLAY, MODALCLASS = 'qtip-modal', MODALSELECTOR = '.'+MODALCLASS; OVERLAY = function() { var self = this, focusableElems = {}, current, prevState, elem; // Modified code from jQuery UI 1.10.0 source // http://code.jquery.com/ui/1.10.0/jquery-ui.js function focusable(element) { // Use the defined focusable checker when possible if($.expr[':'].focusable) { return $.expr[':'].focusable; } var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')), nodeName = element.nodeName && element.nodeName.toLowerCase(), map, mapName, img; if('area' === nodeName) { map = element.parentNode; mapName = map.name; if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') { return false; } img = $('img[usemap=#' + mapName + ']')[0]; return !!img && img.is(':visible'); } return /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : 'a' === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN ; } // Focus inputs using cached focusable elements (see update()) function focusInputs(blurElems) { // Blurring body element in IE causes window.open windows to unfocus! if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); } // Focus the inputs else { focusableElems.first().focus(); } } // Steal focus from elements outside tooltip function stealFocus(event) { if(!elem.is(':visible')) { return; } var target = $(event.target), tooltip = current.tooltip, container = target.closest(SELECTOR), targetOnTop; // Determine if input container target is above this targetOnTop = container.length < 1 ? FALSE : parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10); // If we're showing a modal, but focus has landed on an input below // this modal, divert focus to the first visible input in this modal // or if we can't find one... the tooltip itself if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) { focusInputs(target); } } $.extend(self, { init: function() { // Create document overlay elem = self.elem = $('<div />', { id: 'qtip-overlay', html: '<div></div>', mousedown: function() { return FALSE; } }) .hide(); // Make sure we can't focus anything outside the tooltip $(document.body).bind('focusin'+MODALSELECTOR, stealFocus); // Apply keyboard "Escape key" close handler $(document).bind('keydown'+MODALSELECTOR, function(event) { if(current && current.options.show.modal.escape && event.keyCode === 27) { current.hide(event); } }); // Apply click handler for blur option elem.bind('click'+MODALSELECTOR, function(event) { if(current && current.options.show.modal.blur) { current.hide(event); } }); return self; }, update: function(api) { // Update current API reference current = api; // Update focusable elements if enabled if(api.options.show.modal.stealfocus !== FALSE) { focusableElems = api.tooltip.find('*').filter(function() { return focusable(this); }); } else { focusableElems = []; } }, toggle: function(api, state, duration) { var tooltip = api.tooltip, options = api.options.show.modal, effect = options.effect, type = state ? 'show': 'hide', visible = elem.is(':visible'), visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip); // Set active tooltip API reference self.update(api); // If the modal can steal the focus... // Blur the current item and focus anything in the modal we an if(state && options.stealfocus !== FALSE) { focusInputs( $(':focus') ); } // Toggle backdrop cursor style on show elem.toggleClass('blurs', options.blur); // Append to body on show if(state) { elem.appendTo(document.body); } // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible if(elem.is(':animated') && visible === state && prevState !== FALSE || !state && visibleModals.length) { return self; } // Stop all animations elem.stop(TRUE, FALSE); // Use custom function if provided if($.isFunction(effect)) { effect.call(elem, state); } // If no effect type is supplied, use a simple toggle else if(effect === FALSE) { elem[ type ](); } // Use basic fade function else { elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() { if(!state) { elem.hide(); } }); } // Reset position and detach from body on hide if(!state) { elem.queue(function(next) { elem.css({ left: '', top: '' }); if(!$(MODALSELECTOR).length) { elem.detach(); } next(); }); } // Cache the state prevState = state; // If the tooltip is destroyed, set reference to null if(current.destroyed) { current = NULL; } return self; } }); self.init(); }; OVERLAY = new OVERLAY(); function Modal(api, options) { this.options = options; this._ns = '-modal'; this.qtip = api; this.init(api); } $.extend(Modal.prototype, { init: function(qtip) { var tooltip = qtip.tooltip; // If modal is disabled... return if(!this.options.on) { return this; } // Set overlay reference qtip.elements.overlay = OVERLAY.elem; // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length); // Apply our show/hide/focus modal events qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) { var oEvent = event.originalEvent; // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop if(event.target === tooltip[0]) { if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) { /* eslint-disable no-empty */ try { event.preventDefault(); } catch(e) {} /* eslint-enable no-empty */ } else if(!oEvent || oEvent && oEvent.type !== 'tooltipsolo') { this.toggle(event, event.type === 'tooltipshow', duration); } } }, this._ns, this); // Adjust modal z-index on tooltip focus qtip._bind(tooltip, 'tooltipfocus', function(event, api) { // If focus was cancelled before it reached us, don't do anything if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; } var qtips = $(MODALSELECTOR), // Keep the modal's lower than other, regular qtips newIndex = QTIP.modal_zindex + qtips.length, curIndex = parseInt(tooltip[0].style.zIndex, 10); // Set overlay z-index OVERLAY.elem[0].style.zIndex = newIndex - 1; // Reduce modal z-index's and keep them properly ordered qtips.each(function() { if(this.style.zIndex > curIndex) { this.style.zIndex -= 1; } }); // Fire blur event for focused tooltip qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent); // Set the new z-index tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex; // Set current OVERLAY.update(api); // Prevent default handling /* eslint-disable no-empty */ try { event.preventDefault(); } catch(e) {} /* eslint-enable no-empty */ }, this._ns, this); // Focus any other visible modals when this one hides qtip._bind(tooltip, 'tooltiphide', function(event) { if(event.target === tooltip[0]) { $(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event); } }, this._ns, this); }, toggle: function(event, state, duration) { // Make sure default event hasn't been prevented if(event && event.isDefaultPrevented()) { return this; } // Toggle it OVERLAY.toggle(this.qtip, !!state, duration); }, destroy: function() { // Remove modal class this.qtip.tooltip.removeClass(MODALCLASS); // Remove bound events this.qtip._unbind(this.qtip.tooltip, this._ns); // Delete element reference OVERLAY.toggle(this.qtip, FALSE); delete this.qtip.elements.overlay; } }); MODAL = PLUGINS.modal = function(api) { return new Modal(api, api.options.show.modal); }; // Setup sanitiztion rules MODAL.sanitize = function(opts) { if(opts.show) { if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; } else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; } } }; // Base z-index for all modal tooltips (use qTip core z-index as a base) /* eslint-disable camelcase */ QTIP.modal_zindex = QTIP.zindex - 200; /* eslint-enable camelcase */ // Plugin needs to be initialized on render MODAL.initialize = 'render'; // Setup option set checks CHECKS.modal = { '^show.modal.(on|blur)$': function() { // Initialise this.destroy(); this.init(); // Show the modal if not visible already and tooltip is visible this.qtip.elems.overlay.toggle( this.qtip.tooltip[0].offsetWidth > 0 ); } }; // Extend original api defaults $.extend(TRUE, QTIP.defaults, { show: { modal: { on: FALSE, effect: TRUE, blur: TRUE, stealfocus: TRUE, escape: TRUE } } });