sweetalert2
Version:
A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes, supported fork of sweetalert
1,358 lines (1,208 loc) • 41.9 kB
JavaScript
import defaultParams, { deprecatedParams } from './utils/params.js'
import { swalClasses, iconTypes } from './utils/classes.js'
import { formatInputOptions, warn, error, warnOnce, callIfFunction } from './utils/utils.js'
import * as dom from './utils/dom.js'
let popupParams = Object.assign({}, defaultParams)
let queue = []
let previousWindowKeyDown, windowOnkeydownOverridden
/**
* Show relevant warnings for given params
*
* @param params
*/
const showWarningsForParams = (params) => {
for (const param in params) {
if (!sweetAlert.isValidParameter(param)) {
warn(`Unknown parameter "${param}"`)
}
if (sweetAlert.isDeprecatedParameter(param)) {
warnOnce(`The parameter "${param}" is deprecated and will be removed in the next major release.`)
}
}
}
/**
* Set type, text and actions on popup
*
* @param params
* @returns {boolean}
*/
const setParameters = (params) => {
// Determine if the custom target element is valid
if (
!params.target ||
(typeof params.target === 'string' && !document.querySelector(params.target)) ||
(typeof params.target !== 'string' && !params.target.appendChild)
) {
warn('Target parameter is not valid, defaulting to "body"')
params.target = 'body'
}
let popup
const oldPopup = dom.getPopup()
let targetElement = typeof params.target === 'string' ? document.querySelector(params.target) : params.target
// If the model target has changed, refresh the popup
if (oldPopup && targetElement && oldPopup.parentNode !== targetElement.parentNode) {
popup = dom.init(params)
} else {
popup = oldPopup || dom.init(params)
}
// Set popup width
if (params.width) {
popup.style.width = (typeof params.width === 'number') ? params.width + 'px' : params.width
}
// Set popup padding
if (params.padding) {
popup.style.padding = (typeof params.padding === 'number') ? params.padding + 'px' : params.padding
}
// Set popup background
if (params.background) {
popup.style.background = params.background
}
const popupBackgroundColor = window.getComputedStyle(popup).getPropertyValue('background-color')
const successIconParts = popup.querySelectorAll('[class^=swal2-success-circular-line], .swal2-success-fix')
for (let i = 0; i < successIconParts.length; i++) {
successIconParts[i].style.backgroundColor = popupBackgroundColor
}
const container = dom.getContainer()
const title = dom.getTitle()
const content = dom.getContent().querySelector('#' + swalClasses.content)
const actions = dom.getActions()
const confirmButton = dom.getConfirmButton()
const cancelButton = dom.getCancelButton()
const closeButton = dom.getCloseButton()
const footer = dom.getFooter()
// Title
if (params.titleText) {
title.innerText = params.titleText
} else if (params.title) {
title.innerHTML = params.title.split('\n').join('<br />')
}
if (typeof params.backdrop === 'string') {
dom.getContainer().style.background = params.backdrop
} else if (!params.backdrop) {
dom.addClass([document.documentElement, document.body], swalClasses['no-backdrop'])
}
// Content as HTML
if (params.html) {
dom.parseHtmlToContainer(params.html, content)
// Content as plain text
} else if (params.text) {
content.textContent = params.text
dom.show(content)
} else {
dom.hide(content)
}
// Position
if (params.position in swalClasses) {
dom.addClass(container, swalClasses[params.position])
} else {
warn('The "position" parameter is not valid, defaulting to "center"')
dom.addClass(container, swalClasses.center)
}
// Grow
if (params.grow && typeof params.grow === 'string') {
let growClass = 'grow-' + params.grow
if (growClass in swalClasses) {
dom.addClass(container, swalClasses[growClass])
}
}
// Animation
if (typeof params.animation === 'function') {
params.animation = params.animation.call()
}
// Close button
if (params.showCloseButton) {
closeButton.setAttribute('aria-label', params.closeButtonAriaLabel)
dom.show(closeButton)
} else {
dom.hide(closeButton)
}
// Default Class
popup.className = swalClasses.popup
if (params.toast) {
dom.addClass([document.documentElement, document.body], swalClasses['toast-shown'])
dom.addClass(popup, swalClasses.toast)
} else {
dom.addClass(popup, swalClasses.modal)
}
// Custom Class
if (params.customClass) {
dom.addClass(popup, params.customClass)
}
// Progress steps
let progressStepsContainer = dom.getProgressSteps()
let currentProgressStep = parseInt(params.currentProgressStep === null ? sweetAlert.getQueueStep() : params.currentProgressStep, 10)
if (params.progressSteps && params.progressSteps.length) {
dom.show(progressStepsContainer)
dom.empty(progressStepsContainer)
if (currentProgressStep >= params.progressSteps.length) {
warn(
'Invalid currentProgressStep parameter, it should be less than progressSteps.length ' +
'(currentProgressStep like JS arrays starts from 0)'
)
}
params.progressSteps.forEach((step, index) => {
let circle = document.createElement('li')
dom.addClass(circle, swalClasses.progresscircle)
circle.innerHTML = step
if (index === currentProgressStep) {
dom.addClass(circle, swalClasses.activeprogressstep)
}
progressStepsContainer.appendChild(circle)
if (index !== params.progressSteps.length - 1) {
let line = document.createElement('li')
dom.addClass(line, swalClasses.progressline)
if (params.progressStepsDistance) {
line.style.width = params.progressStepsDistance
}
progressStepsContainer.appendChild(line)
}
})
} else {
dom.hide(progressStepsContainer)
}
// Icon
const icons = dom.getIcons()
for (let i = 0; i < icons.length; i++) {
dom.hide(icons[i])
}
if (params.type) {
let validType = false
for (let iconType in iconTypes) {
if (params.type === iconType) {
validType = true
break
}
}
if (!validType) {
error(`Unknown alert type: ${params.type}`)
return false
}
const icon = popup.querySelector(`.${swalClasses.icon}.${iconTypes[params.type]}`)
dom.show(icon)
// Animate icon
if (params.animation) {
dom.addClass(icon, `swal2-animate-${params.type}-icon`)
}
}
// Custom image
const image = dom.getImage()
if (params.imageUrl) {
image.setAttribute('src', params.imageUrl)
image.setAttribute('alt', params.imageAlt)
dom.show(image)
if (params.imageWidth) {
image.setAttribute('width', params.imageWidth)
} else {
image.removeAttribute('width')
}
if (params.imageHeight) {
image.setAttribute('height', params.imageHeight)
} else {
image.removeAttribute('height')
}
image.className = swalClasses.image
if (params.imageClass) {
dom.addClass(image, params.imageClass)
}
} else {
dom.hide(image)
}
// Cancel button
if (params.showCancelButton) {
cancelButton.style.display = 'inline-block'
} else {
dom.hide(cancelButton)
}
// Confirm button
if (params.showConfirmButton) {
dom.removeStyleProperty(confirmButton, 'display')
} else {
dom.hide(confirmButton)
}
// Actions (buttons) wrapper
if (!params.showConfirmButton && !params.showCancelButton) {
dom.hide(actions)
} else {
dom.show(actions)
}
// Edit text on confirm and cancel buttons
confirmButton.innerHTML = params.confirmButtonText
cancelButton.innerHTML = params.cancelButtonText
// ARIA labels for confirm and cancel buttons
confirmButton.setAttribute('aria-label', params.confirmButtonAriaLabel)
cancelButton.setAttribute('aria-label', params.cancelButtonAriaLabel)
// Add buttons custom classes
confirmButton.className = swalClasses.confirm
dom.addClass(confirmButton, params.confirmButtonClass)
cancelButton.className = swalClasses.cancel
dom.addClass(cancelButton, params.cancelButtonClass)
// Buttons styling
if (params.buttonsStyling) {
dom.addClass([confirmButton, cancelButton], swalClasses.styled)
// Buttons background colors
if (params.confirmButtonColor) {
confirmButton.style.backgroundColor = params.confirmButtonColor
}
if (params.cancelButtonColor) {
cancelButton.style.backgroundColor = params.cancelButtonColor
}
// Loading state
const confirmButtonBackgroundColor = window.getComputedStyle(confirmButton).getPropertyValue('background-color')
confirmButton.style.borderLeftColor = confirmButtonBackgroundColor
confirmButton.style.borderRightColor = confirmButtonBackgroundColor
} else {
dom.removeClass([confirmButton, cancelButton], swalClasses.styled)
confirmButton.style.backgroundColor = confirmButton.style.borderLeftColor = confirmButton.style.borderRightColor = ''
cancelButton.style.backgroundColor = cancelButton.style.borderLeftColor = cancelButton.style.borderRightColor = ''
}
// Footer
dom.parseHtmlToContainer(params.footer, footer)
// CSS animation
if (params.animation === true) {
dom.removeClass(popup, swalClasses.noanimation)
} else {
dom.addClass(popup, swalClasses.noanimation)
}
// showLoaderOnConfirm && preConfirm
if (params.showLoaderOnConfirm && !params.preConfirm) {
warn(
'showLoaderOnConfirm is set to true, but preConfirm is not defined.\n' +
'showLoaderOnConfirm should be used together with preConfirm, see usage example:\n' +
'https://sweetalert2.github.io/#ajax-request'
)
}
}
/**
* Animations
*
* @param animation
* @param onBeforeOpen
* @param onComplete
*/
const openPopup = (animation, onBeforeOpen, onComplete) => {
const container = dom.getContainer()
const popup = dom.getPopup()
if (onBeforeOpen !== null && typeof onBeforeOpen === 'function') {
onBeforeOpen(popup)
}
if (animation) {
dom.addClass(popup, swalClasses.show)
dom.addClass(container, swalClasses.fade)
dom.removeClass(popup, swalClasses.hide)
} else {
dom.removeClass(popup, swalClasses.fade)
}
dom.show(popup)
// scrolling is 'hidden' until animation is done, after that 'auto'
container.style.overflowY = 'hidden'
if (dom.animationEndEvent && !dom.hasClass(popup, swalClasses.noanimation)) {
popup.addEventListener(dom.animationEndEvent, function swalCloseEventFinished () {
popup.removeEventListener(dom.animationEndEvent, swalCloseEventFinished)
container.style.overflowY = 'auto'
})
} else {
container.style.overflowY = 'auto'
}
dom.addClass([document.documentElement, document.body, container], swalClasses.shown)
if (dom.isModal()) {
fixScrollbar()
iOSfix()
}
dom.states.previousActiveElement = document.activeElement
if (onComplete !== null && typeof onComplete === 'function') {
setTimeout(() => {
onComplete(popup)
})
}
}
const fixScrollbar = () => {
// for queues, do not do this more than once
if (dom.states.previousBodyPadding !== null) {
return
}
// if the body has overflow
if (document.body.scrollHeight > window.innerHeight) {
// add padding so the content doesn't shift after removal of scrollbar
dom.states.previousBodyPadding = document.body.style.paddingRight
document.body.style.paddingRight = dom.measureScrollbar() + 'px'
}
}
const undoScrollbar = () => {
if (dom.states.previousBodyPadding !== null) {
document.body.style.paddingRight = dom.states.previousBodyPadding
dom.states.previousBodyPadding = null
}
}
// Fix iOS scrolling http://stackoverflow.com/q/39626302/1331425
const iOSfix = () => {
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
if (iOS && !dom.hasClass(document.body, swalClasses.iosfix)) {
const offset = document.body.scrollTop
document.body.style.top = (offset * -1) + 'px'
dom.addClass(document.body, swalClasses.iosfix)
}
}
const undoIOSfix = () => {
if (dom.hasClass(document.body, swalClasses.iosfix)) {
const offset = parseInt(document.body.style.top, 10)
dom.removeClass(document.body, swalClasses.iosfix)
document.body.style.top = ''
document.body.scrollTop = (offset * -1)
}
}
// SweetAlert entry point
const sweetAlert = (...args) => {
// Prevent run in Node env
if (typeof window === 'undefined') {
return
}
// Check for the existence of Promise
if (typeof Promise === 'undefined') {
error('This package requires a Promise library, please include a shim to enable it in this browser (See: https://github.com/sweetalert2/sweetalert2/wiki/Migration-from-SweetAlert-to-SweetAlert2#1-ie-support)')
}
if (typeof args[0] === 'undefined') {
error('SweetAlert2 expects at least 1 attribute!')
return false
}
let params = Object.assign({}, popupParams)
switch (typeof args[0]) {
case 'string':
[params.title, params.html, params.type] = args
break
case 'object':
showWarningsForParams(args[0])
Object.assign(params, args[0])
params.extraParams = args[0].extraParams
if (params.input === 'email' && params.inputValidator === null) {
const inputValidator = (email) => {
return new Promise((resolve, reject) => {
const emailRegex = /^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,24}$/
if (emailRegex.test(email)) {
resolve()
} else {
reject('Invalid email address')
}
})
}
params.inputValidator = params.expectRejections ? inputValidator : sweetAlert.adaptInputValidator(inputValidator)
}
if (params.input === 'url' && params.inputValidator === null) {
const inputValidator = (url) => {
return new Promise((resolve, reject) => {
// taken from https://stackoverflow.com/a/3809435/1331425
const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)$/
if (urlRegex.test(url)) {
resolve()
} else {
reject('Invalid URL')
}
})
}
params.inputValidator = params.expectRejections ? inputValidator : sweetAlert.adaptInputValidator(inputValidator)
}
break
default:
error('Unexpected type of argument! Expected "string" or "object", got ' + typeof args[0])
return false
}
setParameters(params)
const container = dom.getContainer()
const popup = dom.getPopup()
return new Promise((resolve, reject) => {
// functions to handle all resolving/rejecting/settling
const succeedWith = (value) => {
sweetAlert.closePopup(params.onClose)
if (params.useRejections) {
resolve(value)
} else {
resolve({value})
}
}
const dismissWith = (dismiss) => {
sweetAlert.closePopup(params.onClose)
if (params.useRejections) {
reject(dismiss)
} else {
resolve({dismiss})
}
}
const errorWith = (error) => {
sweetAlert.closePopup(params.onClose)
reject(error)
}
// Close on timer
if (params.timer) {
popup.timeout = setTimeout(() => dismissWith('timer'), params.timer)
}
// Get input element by specified type or, if type isn't specified, by params.input
const getInput = (inputType) => {
inputType = inputType || params.input
if (!inputType) {
return null
}
switch (inputType) {
case 'select':
case 'textarea':
case 'file':
return dom.getChildByClass(content, swalClasses[inputType])
case 'checkbox':
return popup.querySelector(`.${swalClasses.checkbox} input`)
case 'radio':
return popup.querySelector(`.${swalClasses.radio} input:checked`) ||
popup.querySelector(`.${swalClasses.radio} input:first-child`)
case 'range':
return popup.querySelector(`.${swalClasses.range} input`)
default:
return dom.getChildByClass(content, swalClasses.input)
}
}
// Get the value of the popup input
const getInputValue = () => {
const input = getInput()
if (!input) {
return null
}
switch (params.input) {
case 'checkbox':
return input.checked ? 1 : 0
case 'radio':
return input.checked ? input.value : null
case 'file':
return input.files.length ? input.files[0] : null
default:
return params.inputAutoTrim ? input.value.trim() : input.value
}
}
// input autofocus
if (params.input) {
setTimeout(() => {
const input = getInput()
if (input) {
dom.focusInput(input)
}
}, 0)
}
const confirm = (value) => {
if (params.showLoaderOnConfirm) {
sweetAlert.showLoading()
}
if (params.preConfirm) {
sweetAlert.resetValidationError()
const preConfirmPromise = Promise.resolve().then(() => params.preConfirm(value, params.extraParams))
if (params.expectRejections) {
preConfirmPromise.then(
(preConfirmValue) => succeedWith(preConfirmValue || value),
(validationError) => {
sweetAlert.hideLoading()
if (validationError) {
sweetAlert.showValidationError(validationError)
}
}
)
} else {
preConfirmPromise.then(
(preConfirmValue) => {
if (dom.isVisible(dom.getValidationError()) || preConfirmValue === false) {
sweetAlert.hideLoading()
} else {
succeedWith(preConfirmValue || value)
}
},
(error) => errorWith(error)
)
}
} else {
succeedWith(value)
}
}
// Mouse interactions
const onButtonEvent = (event) => {
const e = event || window.event
const target = e.target || e.srcElement
const confirmButton = dom.getConfirmButton()
const cancelButton = dom.getCancelButton()
const targetedConfirm = confirmButton && (confirmButton === target || confirmButton.contains(target))
const targetedCancel = cancelButton && (cancelButton === target || cancelButton.contains(target))
switch (e.type) {
case 'click':
// Clicked 'confirm'
if (targetedConfirm && sweetAlert.isVisible()) {
sweetAlert.disableButtons()
if (params.input) {
const inputValue = getInputValue()
if (params.inputValidator) {
sweetAlert.disableInput()
const validationPromise = Promise.resolve().then(() => params.inputValidator(inputValue, params.extraParams))
if (params.expectRejections) {
validationPromise.then(
() => {
sweetAlert.enableButtons()
sweetAlert.enableInput()
confirm(inputValue)
},
(validationError) => {
sweetAlert.enableButtons()
sweetAlert.enableInput()
if (validationError) {
sweetAlert.showValidationError(validationError)
}
}
)
} else {
validationPromise.then(
(validationError) => {
sweetAlert.enableButtons()
sweetAlert.enableInput()
if (validationError) {
sweetAlert.showValidationError(validationError)
} else {
confirm(inputValue)
}
},
error => errorWith(error)
)
}
} else {
confirm(inputValue)
}
} else {
confirm(true)
}
// Clicked 'cancel'
} else if (targetedCancel && sweetAlert.isVisible()) {
sweetAlert.disableButtons()
dismissWith(sweetAlert.DismissReason.cancel)
}
break
default:
}
}
const buttons = popup.querySelectorAll('button')
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = onButtonEvent
buttons[i].onmouseover = onButtonEvent
buttons[i].onmouseout = onButtonEvent
buttons[i].onmousedown = onButtonEvent
}
// Closing popup by close button
dom.getCloseButton().onclick = () => {
dismissWith(sweetAlert.DismissReason.close)
}
if (params.toast) {
// Closing popup by backdrop click
popup.onclick = (e) => {
if (e.target !== popup || (params.showConfirmButton || params.showCancelButton)) {
return
}
if (params.allowOutsideClick) {
sweetAlert.closePopup(params.onClose)
dismissWith(sweetAlert.DismissReason.backdrop)
}
}
} else {
let ignoreOutsideClick = false
// Ignore click events that had mousedown on the popup but mouseup on the container
// This can happen when the user drags a slider
popup.onmousedown = () => {
container.onmouseup = function (e) {
container.onmouseup = undefined
// We only check if the mouseup target is the container because usually it doesn't
// have any other direct children aside of the popup
if (e.target === container) {
ignoreOutsideClick = true
}
}
}
// Ignore click events that had mousedown on the container but mouseup on the popup
container.onmousedown = () => {
popup.onmouseup = function (e) {
popup.onmouseup = undefined
// We also need to check if the mouseup target is a child of the popup
if (e.target === popup || popup.contains(e.target)) {
ignoreOutsideClick = true
}
}
}
container.onclick = (e) => {
if (ignoreOutsideClick) {
ignoreOutsideClick = false
return
}
if (e.target !== container) {
return
}
if (callIfFunction(params.allowOutsideClick)) {
dismissWith(sweetAlert.DismissReason.backdrop)
}
}
}
const content = dom.getContent()
const actions = dom.getActions()
const confirmButton = dom.getConfirmButton()
const cancelButton = dom.getCancelButton()
// Reverse buttons (Confirm on the right side)
if (params.reverseButtons) {
confirmButton.parentNode.insertBefore(cancelButton, confirmButton)
} else {
confirmButton.parentNode.insertBefore(confirmButton, cancelButton)
}
// Focus handling
const setFocus = (index, increment) => {
const focusableElements = dom.getFocusableElements(params.focusCancel)
// search for visible elements and select the next possible match
for (let i = 0; i < focusableElements.length; i++) {
index = index + increment
// rollover to first item
if (index === focusableElements.length) {
index = 0
// go to last item
} else if (index === -1) {
index = focusableElements.length - 1
}
// determine if element is visible
const el = focusableElements[index]
if (dom.isVisible(el)) {
return el.focus()
}
}
}
const handleKeyDown = (event) => {
const e = event || window.event
const arrowKeys = [
'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown',
'Left', 'Right', 'Up', 'Down' // IE11
]
if (e.key === 'Enter' && !e.isComposing) {
if (e.target === getInput()) {
if (['textarea', 'file'].includes(params.input)) {
return // do not submit
}
sweetAlert.clickConfirm()
e.preventDefault()
}
// TAB
} else if (e.key === 'Tab') {
const targetElement = e.target || e.srcElement
const focusableElements = dom.getFocusableElements(params.focusCancel)
let btnIndex = -1 // Find the button - note, this is a nodelist, not an array.
for (let i = 0; i < focusableElements.length; i++) {
if (targetElement === focusableElements[i]) {
btnIndex = i
break
}
}
if (!e.shiftKey) {
// Cycle to the next button
setFocus(btnIndex, 1)
} else {
// Cycle to the prev button
setFocus(btnIndex, -1)
}
e.stopPropagation()
e.preventDefault()
// ARROWS - switch focus between buttons
} else if (arrowKeys.includes(e.key)) {
// focus Cancel button if Confirm button is currently focused
if (document.activeElement === confirmButton && dom.isVisible(cancelButton)) {
cancelButton.focus()
// and vice versa
} else if (document.activeElement === cancelButton && dom.isVisible(confirmButton)) {
confirmButton.focus()
}
// ESC
} else if ((e.key === 'Escape' || e.key === 'Esc') && callIfFunction(params.allowEscapeKey) === true) {
dismissWith(sweetAlert.DismissReason.esc)
}
}
if (params.toast && windowOnkeydownOverridden) {
window.onkeydown = previousWindowKeyDown
windowOnkeydownOverridden = false
}
if (!params.toast && !windowOnkeydownOverridden) {
previousWindowKeyDown = window.onkeydown
windowOnkeydownOverridden = true
window.onkeydown = handleKeyDown
}
/**
* Show spinner instead of Confirm button and disable Cancel button
*/
sweetAlert.hideLoading = sweetAlert.disableLoading = () => {
if (!params.showConfirmButton) {
dom.hide(confirmButton)
if (!params.showCancelButton) {
dom.hide(dom.getActions())
}
}
dom.removeClass([popup, actions], swalClasses.loading)
popup.removeAttribute('aria-busy')
popup.removeAttribute('data-loading')
confirmButton.disabled = false
cancelButton.disabled = false
}
sweetAlert.getTitle = () => dom.getTitle()
sweetAlert.getContent = () => dom.getContent()
sweetAlert.getInput = () => getInput()
sweetAlert.getImage = () => dom.getImage()
sweetAlert.getButtonsWrapper = () => dom.getButtonsWrapper()
sweetAlert.getActions = () => dom.getActions()
sweetAlert.getConfirmButton = () => dom.getConfirmButton()
sweetAlert.getCancelButton = () => dom.getCancelButton()
sweetAlert.getFooter = () => dom.getFooter()
sweetAlert.isLoading = () => dom.isLoading()
sweetAlert.enableButtons = () => {
confirmButton.disabled = false
cancelButton.disabled = false
}
sweetAlert.disableButtons = () => {
confirmButton.disabled = true
cancelButton.disabled = true
}
sweetAlert.enableConfirmButton = () => {
confirmButton.disabled = false
}
sweetAlert.disableConfirmButton = () => {
confirmButton.disabled = true
}
sweetAlert.enableInput = () => {
const input = getInput()
if (!input) {
return false
}
if (input.type === 'radio') {
const radiosContainer = input.parentNode.parentNode
const radios = radiosContainer.querySelectorAll('input')
for (let i = 0; i < radios.length; i++) {
radios[i].disabled = false
}
} else {
input.disabled = false
}
}
sweetAlert.disableInput = () => {
const input = getInput()
if (!input) {
return false
}
if (input && input.type === 'radio') {
const radiosContainer = input.parentNode.parentNode
const radios = radiosContainer.querySelectorAll('input')
for (let i = 0; i < radios.length; i++) {
radios[i].disabled = true
}
} else {
input.disabled = true
}
}
// Show block with validation error
sweetAlert.showValidationError = (error) => {
const validationError = dom.getValidationError()
validationError.innerHTML = error
const popupComputedStyle = window.getComputedStyle(popup)
validationError.style.marginLeft = `-${popupComputedStyle.getPropertyValue('padding-left')}`
validationError.style.marginRight = `-${popupComputedStyle.getPropertyValue('padding-right')}`
dom.show(validationError)
const input = getInput()
if (input) {
input.setAttribute('aria-invalid', true)
input.setAttribute('aria-describedBy', swalClasses.validationerror)
dom.focusInput(input)
dom.addClass(input, swalClasses.inputerror)
}
}
// Hide block with validation error
sweetAlert.resetValidationError = () => {
const validationError = dom.getValidationError()
dom.hide(validationError)
const input = getInput()
if (input) {
input.removeAttribute('aria-invalid')
input.removeAttribute('aria-describedBy')
dom.removeClass(input, swalClasses.inputerror)
}
}
sweetAlert.getProgressSteps = () => {
return params.progressSteps
}
sweetAlert.setProgressSteps = (progressSteps) => {
params.progressSteps = progressSteps
setParameters(params)
}
sweetAlert.showProgressSteps = () => {
dom.show(dom.getProgressSteps())
}
sweetAlert.hideProgressSteps = () => {
dom.hide(dom.getProgressSteps())
}
sweetAlert.enableButtons()
sweetAlert.hideLoading()
sweetAlert.resetValidationError()
if (params.input) {
dom.addClass(document.body, swalClasses['has-input'])
}
// inputs
const inputTypes = ['input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea']
let input
for (let i = 0; i < inputTypes.length; i++) {
const inputClass = swalClasses[inputTypes[i]]
const inputContainer = dom.getChildByClass(content, inputClass)
input = getInput(inputTypes[i])
// set attributes
if (input) {
for (let j in input.attributes) {
if (input.attributes.hasOwnProperty(j)) {
const attrName = input.attributes[j].name
if (attrName !== 'type' && attrName !== 'value') {
input.removeAttribute(attrName)
}
}
}
for (let attr in params.inputAttributes) {
input.setAttribute(attr, params.inputAttributes[attr])
}
}
// set class
inputContainer.className = inputClass
if (params.inputClass) {
dom.addClass(inputContainer, params.inputClass)
}
dom.hide(inputContainer)
}
let populateInputOptions
switch (params.input) {
case 'text':
case 'email':
case 'password':
case 'number':
case 'tel':
case 'url':
input = dom.getChildByClass(content, swalClasses.input)
input.value = params.inputValue
input.placeholder = params.inputPlaceholder
input.type = params.input
dom.show(input)
break
case 'file':
input = dom.getChildByClass(content, swalClasses.file)
input.placeholder = params.inputPlaceholder
input.type = params.input
dom.show(input)
break
case 'range':
const range = dom.getChildByClass(content, swalClasses.range)
const rangeInput = range.querySelector('input')
const rangeOutput = range.querySelector('output')
rangeInput.value = params.inputValue
rangeInput.type = params.input
rangeOutput.value = params.inputValue
dom.show(range)
break
case 'select':
const select = dom.getChildByClass(content, swalClasses.select)
select.innerHTML = ''
if (params.inputPlaceholder) {
const placeholder = document.createElement('option')
placeholder.innerHTML = params.inputPlaceholder
placeholder.value = ''
placeholder.disabled = true
placeholder.selected = true
select.appendChild(placeholder)
}
populateInputOptions = (inputOptions) => {
inputOptions.forEach(([optionValue, optionLabel]) => {
const option = document.createElement('option')
option.value = optionValue
option.innerHTML = optionLabel
if (params.inputValue.toString() === optionValue.toString()) {
option.selected = true
}
select.appendChild(option)
})
dom.show(select)
select.focus()
}
break
case 'radio':
const radio = dom.getChildByClass(content, swalClasses.radio)
radio.innerHTML = ''
populateInputOptions = (inputOptions) => {
inputOptions.forEach(([radioValue, radioLabel]) => {
const radioInput = document.createElement('input')
const radioLabelElement = document.createElement('label')
radioInput.type = 'radio'
radioInput.name = swalClasses.radio
radioInput.value = radioValue
if (params.inputValue.toString() === radioValue.toString()) {
radioInput.checked = true
}
radioLabelElement.innerHTML = radioLabel
radioLabelElement.insertBefore(radioInput, radioLabelElement.firstChild)
radio.appendChild(radioLabelElement)
})
dom.show(radio)
const radios = radio.querySelectorAll('input')
if (radios.length) {
radios[0].focus()
}
}
break
case 'checkbox':
const checkbox = dom.getChildByClass(content, swalClasses.checkbox)
const checkboxInput = getInput('checkbox')
checkboxInput.type = 'checkbox'
checkboxInput.value = 1
checkboxInput.id = swalClasses.checkbox
checkboxInput.checked = Boolean(params.inputValue)
let label = checkbox.getElementsByTagName('span')
if (label.length) {
checkbox.removeChild(label[0])
}
label = document.createElement('span')
label.innerHTML = params.inputPlaceholder
checkbox.appendChild(label)
dom.show(checkbox)
break
case 'textarea':
const textarea = dom.getChildByClass(content, swalClasses.textarea)
textarea.value = params.inputValue
textarea.placeholder = params.inputPlaceholder
dom.show(textarea)
break
case null:
break
default:
error(`Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "${params.input}"`)
break
}
if (params.input === 'select' || params.input === 'radio') {
const processInputOptions = inputOptions => populateInputOptions(formatInputOptions(inputOptions))
if (params.inputOptions instanceof Promise) {
sweetAlert.showLoading()
params.inputOptions.then((inputOptions) => {
sweetAlert.hideLoading()
processInputOptions(inputOptions)
})
} else if (typeof params.inputOptions === 'object') {
processInputOptions(params.inputOptions)
} else {
error('Unexpected type of inputOptions! Expected object, Map or Promise, got ' + typeof params.inputOptions)
}
}
openPopup(params.animation, params.onBeforeOpen, params.onOpen)
if (!params.toast) {
if (!callIfFunction(params.allowEnterKey)) {
if (document.activeElement) {
document.activeElement.blur()
}
} else if (params.focusCancel && dom.isVisible(cancelButton)) {
cancelButton.focus()
} else if (params.focusConfirm && dom.isVisible(confirmButton)) {
confirmButton.focus()
} else {
setFocus(-1, 1)
}
}
// fix scroll
dom.getContainer().scrollTop = 0
})
}
/*
* Global function to determine if swal2 popup is shown
*/
sweetAlert.isVisible = () => {
return !!dom.getPopup()
}
/*
* Global function for chaining sweetAlert popups
*/
sweetAlert.queue = (steps) => {
queue = steps
const resetQueue = () => {
queue = []
document.body.removeAttribute('data-swal2-queue-step')
}
let queueResult = []
return new Promise((resolve, reject) => {
(function step (i, callback) {
if (i < queue.length) {
document.body.setAttribute('data-swal2-queue-step', i)
sweetAlert(queue[i]).then((result) => {
if (typeof result.value !== 'undefined') {
queueResult.push(result.value)
step(i + 1, callback)
} else {
resetQueue()
resolve({dismiss: result.dismiss})
}
})
} else {
resetQueue()
resolve({value: queueResult})
}
})(0)
})
}
/*
* Global function for getting the index of current popup in queue
*/
sweetAlert.getQueueStep = () => document.body.getAttribute('data-swal2-queue-step')
/*
* Global function for inserting a popup to the queue
*/
sweetAlert.insertQueueStep = (step, index) => {
if (index && index < queue.length) {
return queue.splice(index, 0, step)
}
return queue.push(step)
}
/*
* Global function for deleting a popup from the queue
*/
sweetAlert.deleteQueueStep = (index) => {
if (typeof queue[index] !== 'undefined') {
queue.splice(index, 1)
}
}
/*
* Global function to close sweetAlert
*/
sweetAlert.close = sweetAlert.closePopup = sweetAlert.closeModal = sweetAlert.closeToast = (onComplete) => {
const container = dom.getContainer()
const popup = dom.getPopup()
if (!popup) {
return
}
dom.removeClass(popup, swalClasses.show)
dom.addClass(popup, swalClasses.hide)
clearTimeout(popup.timeout)
if (!dom.isToast()) {
dom.resetPrevState()
window.onkeydown = previousWindowKeyDown
windowOnkeydownOverridden = false
}
const removePopupAndResetState = () => {
if (container.parentNode) {
container.parentNode.removeChild(container)
}
dom.removeClass(
[document.documentElement, document.body],
[
swalClasses.shown,
swalClasses['no-backdrop'],
swalClasses['has-input'],
swalClasses['toast-shown']
]
)
if (dom.isModal()) {
undoScrollbar()
undoIOSfix()
}
}
// If animation is supported, animate
if (dom.animationEndEvent && !dom.hasClass(popup, swalClasses.noanimation)) {
popup.addEventListener(dom.animationEndEvent, function swalCloseEventFinished () {
popup.removeEventListener(dom.animationEndEvent, swalCloseEventFinished)
if (dom.hasClass(popup, swalClasses.hide)) {
removePopupAndResetState()
}
})
} else {
// Otherwise, remove immediately
removePopupAndResetState()
}
if (onComplete !== null && typeof onComplete === 'function') {
setTimeout(() => {
onComplete(popup)
})
}
}
/*
* Global function to click 'Confirm' button
*/
sweetAlert.clickConfirm = () => dom.getConfirmButton().click()
/*
* Global function to click 'Cancel' button
*/
sweetAlert.clickCancel = () => dom.getCancelButton().click()
/**
* Show spinner instead of Confirm button and disable Cancel button
*/
sweetAlert.showLoading = sweetAlert.enableLoading = () => {
let popup = dom.getPopup()
if (!popup) {
sweetAlert('')
}
popup = dom.getPopup()
const actions = dom.getActions()
const confirmButton = dom.getConfirmButton()
const cancelButton = dom.getCancelButton()
dom.show(actions)
dom.show(confirmButton)
dom.addClass([popup, actions], swalClasses.loading)
confirmButton.disabled = true
cancelButton.disabled = true
popup.setAttribute('data-loading', true)
popup.setAttribute('aria-busy', true)
popup.focus()
}
/**
* Is valid parameter
* @param {String} paramName
*/
sweetAlert.isValidParameter = (paramName) => {
return defaultParams.hasOwnProperty(paramName) || paramName === 'extraParams'
}
/**
* Is deprecated parameter
* @param {String} paramName
*/
sweetAlert.isDeprecatedParameter = (paramName) => {
return deprecatedParams.includes(paramName)
}
/**
* Set default params for each popup
* @param {Object} userParams
*/
sweetAlert.setDefaults = (userParams) => {
if (!userParams || typeof userParams !== 'object') {
return error('the argument for setDefaults() is required and has to be a object')
}
showWarningsForParams(userParams)
// assign valid params from userParams to popupParams
for (const param in userParams) {
if (sweetAlert.isValidParameter(param)) {
popupParams[param] = userParams[param]
}
}
}
/**
* Reset default params for each popup
*/
sweetAlert.resetDefaults = () => {
popupParams = Object.assign({}, defaultParams)
}
/**
* Adapt a legacy inputValidator for use with expectRejections=false
*/
sweetAlert.adaptInputValidator = (legacyValidator) => {
return function adaptedInputValidator (inputValue, extraParams) {
return legacyValidator.call(this, inputValue, extraParams)
.then(() => undefined, validationError => validationError)
}
}
sweetAlert.DismissReason = Object.freeze({
cancel: 'cancel',
backdrop: 'overlay',
close: 'close',
esc: 'esc',
timer: 'timer'
})
sweetAlert.noop = () => { }
sweetAlert.version = ''
sweetAlert.default = sweetAlert
/**
* Set default params if `window._swalDefaults` is an object
*/
if (typeof window !== 'undefined' && typeof window._swalDefaults === 'object') {
sweetAlert.setDefaults(window._swalDefaults)
}
export default sweetAlert