!(function($) { 'use strict'; /** * Name of the plugin * @private * @type {String} */ var pluginName = 'remodal'; /** * Namespace for CSS and events * @private * @type {String} */ var namespace = window.remodalGlobals && window.remodalGlobals.namespace || pluginName; /** * Default settings * @private * @type {Object} */ var defaults = $.extend({ hashTracking: true, closeOnConfirm: true, closeOnCancel: true, closeOnEscape: true, closeOnAnyClick: true }, window.remodalGlobals && window.remodalGlobals.defaults); /** * Current modal * @private * @type {Remodal} */ var current; /** * Scrollbar position * @private * @type {Number} */ var scrollTop; /** * Get a transition duration in ms * @private * @param {jQuery} $elem * @return {Number} */ function getTransitionDuration($elem) { var duration = $elem.css('transition-duration') || $elem.css('-webkit-transition-duration') || $elem.css('-moz-transition-duration') || $elem.css('-o-transition-duration') || $elem.css('-ms-transition-duration') || '0s'; var delay = $elem.css('transition-delay') || $elem.css('-webkit-transition-delay') || $elem.css('-moz-transition-delay') || $elem.css('-o-transition-delay') || $elem.css('-ms-transition-delay') || '0s'; var max; var len; var num; var i; duration = duration.split(', '); delay = delay.split(', '); // The duration length is the same as the delay length for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) { num = parseFloat(duration[i]) + parseFloat(delay[i]); if (num > max) { max = num; } } return num * 1000; } /** * Get a scrollbar width * @private * @return {Number} */ function getScrollbarWidth() { if ($(document.body).height() <= $(window).height()) { return 0; } var outer = document.createElement('div'); var inner = document.createElement('div'); var widthNoScroll; var widthWithScroll; outer.style.visibility = 'hidden'; outer.style.width = '100px'; document.body.appendChild(outer); widthNoScroll = outer.offsetWidth; // Force scrollbars outer.style.overflow = 'scroll'; // Add inner div inner.style.width = '100%'; outer.appendChild(inner); widthWithScroll = inner.offsetWidth; // Remove divs outer.parentNode.removeChild(outer); return widthNoScroll - widthWithScroll; } /** * Lock the screen * @private */ function lockScreen() { var $html = $('html'); var lockedClass = namespace + '-is-locked'; var paddingRight; var $body; if (!$html.hasClass(lockedClass)) { $body = $(document.body); // Zepto does not support '-=', '+=' in the `css` method paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth(); $body.css('padding-right', paddingRight + 'px'); $html.addClass(lockedClass); } } /** * Unlock the screen * @private */ function unlockScreen() { var $html = $('html'); var lockedClass = namespace + '-is-locked'; var paddingRight; var $body; if ($html.hasClass(lockedClass)) { $body = $(document.body); // Zepto does not support '-=', '+=' in the `css` method paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth(); $body.css('padding-right', paddingRight + 'px'); $html.removeClass(lockedClass); } } /** * Parse a string with options * @private * @param str * @returns {Object} */ function parseOptions(str) { var obj = {}; var arr; var len; var val; var i; // Remove spaces before and after delimiters str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ','); // Parse a string arr = str.split(','); for (i = 0, len = arr.length; i < len; i++) { arr[i] = arr[i].split(':'); val = arr[i][1]; // Convert a string value if it is like a boolean if (typeof val === 'string' || val instanceof String) { val = val === 'true' || (val === 'false' ? false : val); } // Convert a string value if it is like a number if (typeof val === 'string' || val instanceof String) { val = !isNaN(val) ? +val : val; } obj[arr[i][0]] = val; } return obj; } /** * Remodal constructor * @param {jQuery} $modal * @param {Object} options * @constructor */ function Remodal($modal, options) { var remodal = this; var tdOverlay; var tdModal; var tdBg; remodal.settings = $.extend({}, defaults, options); // Build DOM remodal.$body = $(document.body); remodal.$overlay = $('.' + namespace + '-overlay'); if (!remodal.$overlay.length) { remodal.$overlay = $('
').addClass(namespace + '-overlay'); remodal.$body.append(remodal.$overlay); } remodal.$bg = $('.' + namespace + '-bg'); remodal.$closeButton = $('').addClass(namespace + '-close'); remodal.$wrapper = $('
').addClass(namespace + '-wrapper'); remodal.$modal = $modal; remodal.$modal.addClass(namespace); remodal.$modal.css('visibility', 'visible'); remodal.$modal.append(remodal.$closeButton); remodal.$wrapper.append(remodal.$modal); remodal.$body.append(remodal.$wrapper); remodal.$confirmButton = remodal.$modal.find('.' + namespace + '-confirm'); remodal.$cancelButton = remodal.$modal.find('.' + namespace + '-cancel'); // Calculate timeouts tdOverlay = getTransitionDuration(remodal.$overlay); tdModal = getTransitionDuration(remodal.$modal); tdBg = getTransitionDuration(remodal.$bg); remodal.td = tdModal > tdOverlay ? tdModal : tdOverlay; remodal.td = tdBg > remodal.td ? tdBg : remodal.td; // Add the close button event listener remodal.$wrapper.on('click.' + namespace, '.' + namespace + '-close', function(e) { e.preventDefault(); remodal.close(); }); // Add the cancel button event listener remodal.$wrapper.on('click.' + namespace, '.' + namespace + '-cancel', function(e) { e.preventDefault(); remodal.$modal.trigger('cancel'); if (remodal.settings.closeOnCancel) { remodal.close('cancellation'); } }); // Add the confirm button event listener remodal.$wrapper.on('click.' + namespace, '.' + namespace + '-confirm', function(e) { e.preventDefault(); remodal.$modal.trigger('confirm'); if (remodal.settings.closeOnConfirm) { remodal.close('confirmation'); } }); // Add the keyboard event listener $(document).on('keyup.' + namespace, function(e) { if (e.keyCode === 27 && remodal.settings.closeOnEscape) { remodal.close(); } }); // Add the overlay event listener remodal.$wrapper.on('click.' + namespace, function(e) { var $target = $(e.target); if (!$target.hasClass(namespace + '-wrapper')) { return; } if (remodal.settings.closeOnAnyClick) { remodal.close(); } }); remodal.index = $[pluginName].lookup.push(remodal) - 1; remodal.busy = false; } /** * Open a modal window * @public */ Remodal.prototype.open = function() { // Check if the animation was completed if (this.busy) { return; } var remodal = this; var id; remodal.busy = true; remodal.$modal.trigger('open'); id = remodal.$modal.attr('data-' + pluginName + '-id'); if (id && remodal.settings.hashTracking) { scrollTop = $(window).scrollTop(); location.hash = id; } if (current && current !== remodal) { current.$overlay.hide(); current.$wrapper.hide(); current.$body.removeClass(namespace + '-is-active'); } current = remodal; lockScreen(); remodal.$overlay.show(); remodal.$wrapper.show(); setTimeout(function() { remodal.$body.addClass(namespace + '-is-active'); setTimeout(function() { remodal.busy = false; remodal.$modal.trigger('opened'); }, remodal.td + 50); }, 25); }; /** * Close a modal window * @public * @param {String|undefined} reason A reason to close */ Remodal.prototype.close = function(reason) { // Check if the animation was completed if (this.busy) { return; } var remodal = this; remodal.busy = true; remodal.$modal.trigger({ type: 'close', reason: reason }); if (remodal.settings.hashTracking && remodal.$modal.attr('data-' + pluginName + '-id') === location.hash.substr(1)) { location.hash = ''; $(window).scrollTop(scrollTop); } remodal.$body.removeClass(namespace + '-is-active'); setTimeout(function() { remodal.$overlay.hide(); remodal.$wrapper.hide(); unlockScreen(); remodal.busy = false; remodal.$modal.trigger({ type: 'closed', reason: reason }); }, remodal.td + 50); }; /** * Special plugin object for instances. * @public * @type {Object} */ $[pluginName] = { lookup: [] }; /** * Plugin constructor * @param {Object} options * @returns {JQuery} * @constructor */ $.fn[pluginName] = function(opts) { var instance; var $elem; this.each(function(index, elem) { $elem = $(elem); if ($elem.data(pluginName) == null) { instance = new Remodal($elem, opts); $elem.data(pluginName, instance.index); if (instance.settings.hashTracking && $elem.attr('data-' + pluginName + '-id') === location.hash.substr(1)) { instance.open(); } } else { instance = $[pluginName].lookup[$elem.data(pluginName)]; } }); return instance; }; $(document).ready(function() { // data-remodal-target opens a modal window with the special Id. $(document).on('click', '[data-' + pluginName + '-target]', function(e) { e.preventDefault(); var elem = e.currentTarget; var id = elem.getAttribute('data-' + pluginName + '-target'); var $target = $('[data-' + pluginName + '-id=' + id + ']'); $[pluginName].lookup[$target.data(pluginName)].open(); }); // Auto initialization of modal windows. // They should have the 'remodal' class attribute. // Also you can write `data-remodal-options` attribute to pass params into the modal. $(document).find('.' + namespace).each(function(i, container) { var $container = $(container); var options = $container.data(pluginName + '-options'); if (!options) { options = {}; } else if (typeof options === 'string' || options instanceof String) { options = parseOptions(options); } $container[pluginName](options); }); }); /** * Hashchange handler * @private * @param {Event} e * @param {Boolean} [closeOnEmptyHash=true] */ function hashHandler(e, closeOnEmptyHash) { var id = location.hash.replace('#', ''); var instance; var $elem; if (typeof closeOnEmptyHash === 'undefined') { closeOnEmptyHash = true; } if (!id) { if (closeOnEmptyHash) { // Check if we have currently opened modal and animation was completed if (current && !current.busy && current.settings.hashTracking) { current.close(); } } } else { // Catch syntax error if your hash is bad try { $elem = $( '[data-' + pluginName + '-id=' + id.replace(new RegExp('/', 'g'), '\\/') + ']' ); } catch (err) {} if ($elem && $elem.length) { instance = $[pluginName].lookup[$elem.data(pluginName)]; if (instance && instance.settings.hashTracking) { instance.open(); } } } } $(window).bind('hashchange.' + namespace, hashHandler); })(window.jQuery || window.Zepto);