// SweetAlert // 2014 (c) - Tristan Edwards // github.com/t4t5/sweetalert ;(function(window, document, undefined) { var modalClass = '.sweet-alert', overlayClass = '.sweet-overlay', alertTypes = ['error', 'warning', 'info', 'success'], defaultParams = { title: '', text: '', type: null, allowOutsideClick: false, showCancelButton: false, closeOnConfirm: true, closeOnCancel: true, confirmButtonText: 'OK', confirmButtonColor: '#AEDEF4', cancelButtonText: 'Cancel', imageUrl: null, imageSize: null, timer: null, customClass: '', html: false, animation: true, allowEscapeKey: true }; /* * Manipulate DOM */ var getModal = function() { var $modal = document.querySelector(modalClass); if (!$modal) { sweetAlertInitialize(); $modal = getModal(); } return $modal; }, getOverlay = function() { return document.querySelector(overlayClass); }, hasClass = function(elem, className) { return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' '); }, addClass = function(elem, className) { if (!hasClass(elem, className)) { elem.className += ' ' + className; } }, removeClass = function(elem, className) { var newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' '; if (hasClass(elem, className)) { while (newClass.indexOf(' ' + className + ' ') >= 0) { newClass = newClass.replace(' ' + className + ' ', ' '); } elem.className = newClass.replace(/^\s+|\s+$/g, ''); } }, escapeHtml = function(str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; }, _show = function(elem) { elem.style.opacity = ''; elem.style.display = 'block'; }, show = function(elems) { if (elems && !elems.length) { return _show(elems); } for (var i = 0; i < elems.length; ++i) { _show(elems[i]); } }, _hide = function(elem) { elem.style.opacity = ''; elem.style.display = 'none'; }, hide = function(elems) { if (elems && !elems.length) { return _hide(elems); } for (var i = 0; i < elems.length; ++i) { _hide(elems[i]); } }, isDescendant = function(parent, child) { var node = child.parentNode; while (node !== null) { if (node === parent) { return true; } node = node.parentNode; } return false; }, getTopMargin = function(elem) { elem.style.left = '-9999px'; elem.style.display = 'block'; var height = elem.clientHeight, padding; if (typeof getComputedStyle !== "undefined") { /* IE 8 */ padding = parseInt(getComputedStyle(elem).getPropertyValue('padding'), 10); } else { padding = parseInt(elem.currentStyle.padding); } elem.style.left = ''; elem.style.display = 'none'; return ('-' + parseInt(height / 2 + padding) + 'px'); }, fadeIn = function(elem, interval) { if (+elem.style.opacity < 1) { interval = interval || 16; elem.style.opacity = 0; elem.style.display = 'block'; var last = +new Date(); var tick = function() { elem.style.opacity = +elem.style.opacity + (new Date() - last) / 100; last = +new Date(); if (+elem.style.opacity < 1) { setTimeout(tick, interval); } }; tick(); } elem.style.display = 'block'; //fallback IE8 }, fadeOut = function(elem, interval) { interval = interval || 16; elem.style.opacity = 1; var last = +new Date(); var tick = function() { elem.style.opacity = +elem.style.opacity - (new Date() - last) / 100; last = +new Date(); if (+elem.style.opacity > 0) { setTimeout(tick, interval); } else { elem.style.display = 'none'; } }; tick(); }, fireClick = function(node) { // Taken from http://www.nonobtrusive.com/2011/11/29/programatically-fire-crossbrowser-click-event-with-javascript/ // Then fixed for today's Chrome browser. if (typeof MouseEvent === 'function') { // Up-to-date approach var mevt = new MouseEvent('click', { view: window, bubbles: false, cancelable: true }); node.dispatchEvent(mevt); } else if ( document.createEvent ) { // Fallback var evt = document.createEvent('MouseEvents'); evt.initEvent('click', false, false); node.dispatchEvent(evt); } else if( document.createEventObject ) { node.fireEvent('onclick') ; } else if (typeof node.onclick === 'function' ) { node.onclick(); } }, stopEventPropagation = function(e) { // In particular, make sure the space bar doesn't scroll the main window. if (typeof e.stopPropagation === 'function') { e.stopPropagation(); e.preventDefault(); } else if (window.event && window.event.hasOwnProperty('cancelBubble')) { window.event.cancelBubble = true; } }; // Remember state in cases where opening and handling a modal will fiddle with it. var previousActiveElement, previousDocumentClick, previousWindowKeyDown, lastFocusedButton; /* * Add modal + overlay to DOM */ window.sweetAlertInitialize = function() { var sweetHTML = '

Title

Text

', sweetWrap = document.createElement('div'); sweetWrap.innerHTML = sweetHTML; // Append elements to body while (sweetWrap.firstChild) { document.body.appendChild(sweetWrap.firstChild); } }; /* * Global sweetAlert function */ window.sweetAlert = window.swal = function() { // Copy arguments to the local args variable var args = arguments; if (getModal() !== null) { // If getModal returns values then continue modalDependant.apply(this, args); } else { // If getModal returns null i.e. no matches, then set up a interval event to check the return value until it is not null var modalCheckInterval = setInterval(function() { if (getModal() !== null) { clearInterval(modalCheckInterval); modalDependant.apply(this, args); } }, 100); } }; function modalDependant() { var customizations = arguments[0]; /* * Use argument if defined or default value from params object otherwise. * Supports the case where a default value is boolean true and should be * overridden by a corresponding explicit argument which is boolean false. */ function argumentOrDefault(key) { var args = customizations; if (typeof args[key] !== 'undefined') { return args[key]; } else { return defaultParams[key]; } } if (arguments[0] === undefined) { logStr('SweetAlert expects at least 1 attribute!'); return false; } var params = extend({}, defaultParams); switch (typeof arguments[0]) { // Ex: swal("Hello", "Just testing", "info"); case 'string': params.title = arguments[0]; params.text = arguments[1] || ''; params.type = arguments[2] || ''; break; // Ex: swal({title:"Hello", text: "Just testing", type: "info"}); case 'object': if (arguments[0].title === undefined) { logStr('Missing "title" argument!'); return false; } params.title = arguments[0].title; var availableCustoms = [ 'text', 'type', 'customClass', 'allowOutsideClick', 'showCancelButton', 'closeOnConfirm', 'closeOnCancel', 'timer', 'confirmButtonColor', 'cancelButtonText', 'imageUrl', 'imageSize', 'html', 'animation', 'allowEscapeKey']; // It would be nice to just use .forEach here, but IE8... :( var numCustoms = availableCustoms.length; for (var customIndex = 0; customIndex < numCustoms; customIndex++) { var customName = availableCustoms[customIndex]; params[customName] = argumentOrDefault(customName); } // Show "Confirm" instead of "OK" if cancel button is visible params.confirmButtonText = (params.showCancelButton) ? 'Confirm' : defaultParams.confirmButtonText; params.confirmButtonText = argumentOrDefault('confirmButtonText'); // Function to call when clicking on cancel/OK params.doneFunction = arguments[1] || null; break; default: logStr('Unexpected type of argument! Expected "string" or "object", got ' + typeof arguments[0]); return false; } setParameters(params); fixVerticalPosition(); openModal(); // Modal interactions var modal = getModal(); // Mouse interactions var onButtonEvent = function(event) { var e = event || window.event; var target = e.target || e.srcElement, targetedConfirm = (target.className.indexOf("confirm") !== -1), modalIsVisible = hasClass(modal, 'visible'), doneFunctionExists = (params.doneFunction && modal.getAttribute('data-has-done-function') === 'true'); switch (e.type) { case ("mouseover"): if (targetedConfirm) { target.style.backgroundColor = colorLuminance(params.confirmButtonColor, -0.04); } break; case ("mouseout"): if (targetedConfirm) { target.style.backgroundColor = params.confirmButtonColor; } break; case ("mousedown"): if (targetedConfirm) { target.style.backgroundColor = colorLuminance(params.confirmButtonColor, -0.14); } break; case ("mouseup"): if (targetedConfirm) { target.style.backgroundColor = colorLuminance(params.confirmButtonColor, -0.04); } break; case ("focus"): var $confirmButton = modal.querySelector('button.confirm'), $cancelButton = modal.querySelector('button.cancel'); if (targetedConfirm) { $cancelButton.style.boxShadow = 'none'; } else { $confirmButton.style.boxShadow = 'none'; } break; case ("click"): if (targetedConfirm && doneFunctionExists && modalIsVisible) { // Clicked "confirm" params.doneFunction(true); if (params.closeOnConfirm) { window.sweetAlert.close(); } } else if (doneFunctionExists && modalIsVisible) { // Clicked "cancel" // Check if callback function expects a parameter (to track cancel actions) var functionAsStr = String(params.doneFunction).replace(/\s/g, ''); var functionHandlesCancel = functionAsStr.substring(0, 9) === "function(" && functionAsStr.substring(9, 10) !== ")"; if (functionHandlesCancel) { params.doneFunction(false); } if (params.closeOnCancel) { window.sweetAlert.close(); } } else { window.sweetAlert.close(); } break; } }; var $buttons = modal.querySelectorAll('button'); for (var i = 0; i < $buttons.length; i++) { $buttons[i].onclick = onButtonEvent; $buttons[i].onmouseover = onButtonEvent; $buttons[i].onmouseout = onButtonEvent; $buttons[i].onmousedown = onButtonEvent; //$buttons[i].onmouseup = onButtonEvent; $buttons[i].onfocus = onButtonEvent; } // Remember the current document.onclick event. previousDocumentClick = document.onclick; document.onclick = function(event) { var e = event || window.event; var target = e.target || e.srcElement; var clickedOnModal = (modal === target), clickedOnModalChild = isDescendant(modal, target), modalIsVisible = hasClass(modal, 'visible'), outsideClickIsAllowed = modal.getAttribute('data-allow-ouside-click') === 'true'; if (!clickedOnModal && !clickedOnModalChild && modalIsVisible && outsideClickIsAllowed) { window.sweetAlert.close(); } }; // Keyboard interactions var $okButton = modal.querySelector('button.confirm'), $cancelButton = modal.querySelector('button.cancel'), $modalButtons = modal.querySelectorAll('button[tabindex]'); function handleKeyDown(event) { var e = event || window.event; var keyCode = e.keyCode || e.which; if ([9,13,32,27].indexOf(keyCode) === -1) { // Don't do work on keys we don't care about. return; } var $targetElement = e.target || e.srcElement; var btnIndex = -1; // Find the button - note, this is a nodelist, not an array. for (var i = 0; i < $modalButtons.length; i++) { if ($targetElement === $modalButtons[i]) { btnIndex = i; break; } } if (keyCode === 9) { // TAB if (btnIndex === -1) { // No button focused. Jump to the confirm button. $targetElement = $okButton; } else { // Cycle to the next button if (btnIndex === $modalButtons.length - 1) { $targetElement = $modalButtons[0]; } else { $targetElement = $modalButtons[btnIndex + 1]; } } stopEventPropagation(e); $targetElement.focus(); setFocusStyle($targetElement, params.confirmButtonColor); // TODO } else { if (keyCode === 13 || keyCode === 32) { if (btnIndex === -1) { // ENTER/SPACE clicked outside of a button. $targetElement = $okButton; } else { // Do nothing - let the browser handle it. $targetElement = undefined; } } else if (keyCode === 27 && params.allowEscapeKey === true) { $targetElement = $cancelButton; } else { // Fallback - let the browser handle it. $targetElement = undefined; } if ($targetElement !== undefined) { fireClick($targetElement, e); } } } previousWindowKeyDown = window.onkeydown; window.onkeydown = handleKeyDown; function handleOnBlur(event) { var e = event || window.event; var $targetElement = e.target || e.srcElement, $focusElement = e.relatedTarget, modalIsVisible = hasClass(modal, 'visible'); if (modalIsVisible) { var btnIndex = -1; // Find the button - note, this is a nodelist, not an array. if ($focusElement !== null) { // If we picked something in the DOM to focus to, let's see if it was a button. for (var i = 0; i < $modalButtons.length; i++) { if ($focusElement === $modalButtons[i]) { btnIndex = i; break; } } if (btnIndex === -1) { // Something in the dom, but not a visible button. Focus back on the button. $targetElement.focus(); } } else { // Exiting the DOM (e.g. clicked in the URL bar); lastFocusedButton = $targetElement; } } } $okButton.onblur = handleOnBlur; $cancelButton.onblur = handleOnBlur; window.onfocus = function() { // When the user has focused away and focused back from the whole window. window.setTimeout(function() { // Put in a timeout to jump out of the event sequence. Calling focus() in the event // sequence confuses things. if (lastFocusedButton !== undefined) { lastFocusedButton.focus(); lastFocusedButton = undefined; } }, 0); }; } /* * Set default params for each popup * @param {Object} userParams */ window.sweetAlert.setDefaults = window.swal.setDefaults = function(userParams) { if (!userParams) { throw new Error('userParams is required'); } if (typeof userParams !== 'object') { throw new Error('userParams has to be a object'); } extend(defaultParams, userParams); }; /* * Set type, text and actions on modal */ function setParameters(params) { var modal = getModal(); var $title = modal.querySelector('h2'), $text = modal.querySelector('p'), $cancelBtn = modal.querySelector('button.cancel'), $confirmBtn = modal.querySelector('button.confirm'); // Title $title.innerHTML = (params.html) ? params.title : escapeHtml(params.title).split("\n").join("
"); // Text $text.innerHTML = (params.html) ? params.text : escapeHtml(params.text || '').split("\n").join("
"); if (params.text) { show($text); } //Custom Class if (params.customClass) { addClass(modal, params.customClass); modal.setAttribute('data-custom-class', params.customClass); } else { // Find previously set classes and remove them var customClass = modal.getAttribute('data-custom-class'); removeClass(modal, customClass); modal.setAttribute('data-custom-class', ""); } // Icon hide(modal.querySelectorAll('.icon')); if (params.type && !isIE8()) { var validType = false; for (var i = 0; i < alertTypes.length; i++) { if (params.type === alertTypes[i]) { validType = true; break; } } if (!validType) { logStr('Unknown alert type: ' + params.type); return false; } var $icon = modal.querySelector('.icon.' + params.type); show($icon); // Animate icon switch (params.type) { case "success": addClass($icon, 'animate'); addClass($icon.querySelector('.tip'), 'animateSuccessTip'); addClass($icon.querySelector('.long'), 'animateSuccessLong'); break; case "error": addClass($icon, 'animateErrorIcon'); addClass($icon.querySelector('.x-mark'), 'animateXMark'); break; case "warning": addClass($icon, 'pulseWarning'); addClass($icon.querySelector('.body'), 'pulseWarningIns'); addClass($icon.querySelector('.dot'), 'pulseWarningIns'); break; } } // Custom image if (params.imageUrl) { var $customIcon = modal.querySelector('.icon.custom'); $customIcon.style.backgroundImage = 'url(' + params.imageUrl + ')'; show($customIcon); var _imgWidth = 80, _imgHeight = 80; if (params.imageSize) { var dimensions = params.imageSize.toString().split('x'); var imgWidth = dimensions[0]; var imgHeight = dimensions[1]; if (!imgWidth || !imgHeight) { logStr("Parameter imageSize expects value with format WIDTHxHEIGHT, got " + params.imageSize); } else { _imgWidth = imgWidth; _imgHeight = imgHeight; } } $customIcon.setAttribute('style', $customIcon.getAttribute('style') + 'width:' + _imgWidth + 'px; height:' + _imgHeight + 'px'); } // Cancel button modal.setAttribute('data-has-cancel-button', params.showCancelButton); if (params.showCancelButton) { $cancelBtn.style.display = 'inline-block'; } else { hide($cancelBtn); } // Edit text on cancel and confirm buttons if (params.cancelButtonText) { $cancelBtn.innerHTML = escapeHtml(params.cancelButtonText); } if (params.confirmButtonText) { $confirmBtn.innerHTML = escapeHtml(params.confirmButtonText); } // Set confirm button to selected background color $confirmBtn.style.backgroundColor = params.confirmButtonColor; // Set box-shadow to default focused button setFocusStyle($confirmBtn, params.confirmButtonColor); // Allow outside click? modal.setAttribute('data-allow-ouside-click', params.allowOutsideClick); // Done-function var hasDoneFunction = (params.doneFunction) ? true : false; modal.setAttribute('data-has-done-function', hasDoneFunction); // Prevent modal from animating if (!params.animation){ modal.setAttribute('data-animation', 'none'); } else{ modal.setAttribute('data-animation', 'pop'); } // Close timer modal.setAttribute('data-timer', params.timer); } /* * Set hover, active and focus-states for buttons (source: http://www.sitepoint.com/javascript-generate-lighter-darker-color) */ function colorLuminance(hex, lum) { // Validate hex string hex = String(hex).replace(/[^0-9a-f]/gi, ''); if (hex.length < 6) { hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; } lum = lum || 0; // Convert to decimal and change luminosity var rgb = "#", c, i; for (i = 0; i < 3; i++) { c = parseInt(hex.substr(i*2,2), 16); c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); rgb += ("00"+c).substr(c.length); } return rgb; } function extend(a, b){ for (var key in b) { if (b.hasOwnProperty(key)) { a[key] = b[key]; } } return a; } function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) : null; } // Add box-shadow style to button (depending on its chosen bg-color) function setFocusStyle($button, bgColor) { var rgbColor = hexToRgb(bgColor); $button.style.boxShadow = '0 0 2px rgba(' + rgbColor +', 0.8), inset 0 0 0 1px rgba(0, 0, 0, 0.05)'; } // Animation when opening modal function openModal() { var modal = getModal(); fadeIn(getOverlay(), 10); show(modal); addClass(modal, 'showSweetAlert'); removeClass(modal, 'hideSweetAlert'); previousActiveElement = document.activeElement; var $okButton = modal.querySelector('button.confirm'); $okButton.focus(); setTimeout(function() { addClass(modal, 'visible'); }, 500); var timer = modal.getAttribute('data-timer'); if (timer !== "null" && timer !== "") { modal.timeout = setTimeout(function() { window.sweetAlert.close(); }, timer); } } // Aninmation when closing modal window.sweetAlert.close = window.swal.close = function() { var modal = getModal(); fadeOut(getOverlay(), 5); fadeOut(modal, 5); removeClass(modal, 'showSweetAlert'); addClass(modal, 'hideSweetAlert'); removeClass(modal, 'visible'); // Reset icon animations var $successIcon = modal.querySelector('.icon.success'); removeClass($successIcon, 'animate'); removeClass($successIcon.querySelector('.tip'), 'animateSuccessTip'); removeClass($successIcon.querySelector('.long'), 'animateSuccessLong'); var $errorIcon = modal.querySelector('.icon.error'); removeClass($errorIcon, 'animateErrorIcon'); removeClass($errorIcon.querySelector('.x-mark'), 'animateXMark'); var $warningIcon = modal.querySelector('.icon.warning'); removeClass($warningIcon, 'pulseWarning'); removeClass($warningIcon.querySelector('.body'), 'pulseWarningIns'); removeClass($warningIcon.querySelector('.dot'), 'pulseWarningIns'); // Reset the page to its previous state window.onkeydown = previousWindowKeyDown; document.onclick = previousDocumentClick; if (previousActiveElement) { previousActiveElement.focus(); } lastFocusedButton = undefined; clearTimeout(modal.timeout); }; /* * Set "margin-top"-property on modal based on its computed height */ function fixVerticalPosition() { var modal = getModal(); modal.style.marginTop = getTopMargin(getModal()); } // If browser is Internet Explorer 8 function isIE8() { if (window.attachEvent && !window.addEventListener) { return true; } else { return false; } } // Error messages for developers function logStr(string) { if (window.console) { // IE... window.console.log("SweetAlert: " + string); } } })(window, document);