|
| 1 | +/** |
| 2 | + * ConfirmDialog - Promise-based confirmation dialog |
| 3 | + * |
| 4 | + * Uses Bootstrap Modal when available, falls back to native confirm(). |
| 5 | + * Auto-binds to elements with [data-ms3-confirm] attribute. |
| 6 | + * |
| 7 | + * @example |
| 8 | + * const confirmed = await ms3Confirm('Delete this item?') |
| 9 | + * |
| 10 | + * @example |
| 11 | + * await ms3Confirm('Cancel order?', { |
| 12 | + * confirmText: 'Yes, cancel', |
| 13 | + * confirmClass: 'btn-danger' |
| 14 | + * }) |
| 15 | + * |
| 16 | + * @example HTML auto-bind |
| 17 | + * <a href="/logout" data-ms3-confirm="Are you sure?">Logout</a> |
| 18 | + */ |
| 19 | +const ms3Confirm = (function () { |
| 20 | + let modalElement = null |
| 21 | + let pendingResolve = null |
| 22 | + |
| 23 | + function getOrCreateModal () { |
| 24 | + if (modalElement) return modalElement |
| 25 | + |
| 26 | + modalElement = document.createElement('div') |
| 27 | + modalElement.className = 'modal fade' |
| 28 | + modalElement.setAttribute('tabindex', '-1') |
| 29 | + modalElement.innerHTML = ` |
| 30 | + <div class="modal-dialog modal-dialog-centered modal-sm"> |
| 31 | + <div class="modal-content"> |
| 32 | + <div class="modal-body text-center py-4"> |
| 33 | + <p class="mb-0 ms3-confirm-message"></p> |
| 34 | + </div> |
| 35 | + <div class="modal-footer justify-content-center border-top-0 pt-0"> |
| 36 | + <button type="button" class="btn btn-secondary ms3-confirm-cancel" data-bs-dismiss="modal"></button> |
| 37 | + <button type="button" class="btn ms3-confirm-ok"></button> |
| 38 | + </div> |
| 39 | + </div> |
| 40 | + </div> |
| 41 | + ` |
| 42 | + document.body.appendChild(modalElement) |
| 43 | + return modalElement |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * Show confirmation dialog |
| 48 | + * |
| 49 | + * @param {string} message - Confirmation message |
| 50 | + * @param {Object} [options] - Options |
| 51 | + * @param {string} [options.confirmText] - Confirm button text |
| 52 | + * @param {string} [options.cancelText] - Cancel button text |
| 53 | + * @param {string} [options.confirmClass] - Confirm button CSS class |
| 54 | + * @returns {Promise<boolean>} - true if confirmed, false if cancelled |
| 55 | + */ |
| 56 | + async function confirm (message, options = {}) { |
| 57 | + // Fallback to native confirm if Bootstrap is not available |
| 58 | + if (typeof bootstrap === 'undefined' || !bootstrap.Modal) { |
| 59 | + return window.confirm(message) |
| 60 | + } |
| 61 | + |
| 62 | + // Dismiss previous dialog if still open |
| 63 | + if (pendingResolve) { |
| 64 | + pendingResolve(false) |
| 65 | + pendingResolve = null |
| 66 | + } |
| 67 | + |
| 68 | + const lexicon = (typeof window !== 'undefined' && window.ms3Lexicon) || {} |
| 69 | + const lang = (document.documentElement.lang || 'en').slice(0, 2) |
| 70 | + const i18n = { ru: { ok: 'Подтвердить', cancel: 'Отмена' }, en: { ok: 'Confirm', cancel: 'Cancel' } } |
| 71 | + const t = i18n[lang] || i18n.en |
| 72 | + const opts = { |
| 73 | + confirmText: options.confirmText || lexicon.ms3_confirm_ok || t.ok, |
| 74 | + cancelText: options.cancelText || lexicon.ms3_confirm_cancel || t.cancel, |
| 75 | + confirmClass: options.confirmClass || 'btn-primary' |
| 76 | + } |
| 77 | + |
| 78 | + const el = getOrCreateModal() |
| 79 | + const modalInstance = bootstrap.Modal.getOrCreateInstance(el) |
| 80 | + |
| 81 | + el.querySelector('.ms3-confirm-message').textContent = message |
| 82 | + |
| 83 | + const okBtn = el.querySelector('.ms3-confirm-ok') |
| 84 | + okBtn.textContent = opts.confirmText |
| 85 | + okBtn.className = `btn ${opts.confirmClass} ms3-confirm-ok` |
| 86 | + |
| 87 | + el.querySelector('.ms3-confirm-cancel').textContent = opts.cancelText |
| 88 | + |
| 89 | + return new Promise((resolve) => { |
| 90 | + let resolved = false |
| 91 | + pendingResolve = resolve |
| 92 | + |
| 93 | + function cleanup () { |
| 94 | + okBtn.removeEventListener('click', handleConfirm) |
| 95 | + el.removeEventListener('hidden.bs.modal', handleHidden) |
| 96 | + if (pendingResolve === resolve) { |
| 97 | + pendingResolve = null |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + function handleConfirm () { |
| 102 | + if (resolved) return |
| 103 | + resolved = true |
| 104 | + cleanup() |
| 105 | + modalInstance.hide() |
| 106 | + resolve(true) |
| 107 | + } |
| 108 | + |
| 109 | + function handleHidden () { |
| 110 | + if (resolved) return |
| 111 | + resolved = true |
| 112 | + cleanup() |
| 113 | + resolve(false) |
| 114 | + } |
| 115 | + |
| 116 | + okBtn.addEventListener('click', handleConfirm) |
| 117 | + el.addEventListener('hidden.bs.modal', handleHidden) |
| 118 | + |
| 119 | + modalInstance.show() |
| 120 | + }) |
| 121 | + } |
| 122 | + |
| 123 | + // Auto-bind: elements with [data-ms3-confirm] get a confirm dialog on click |
| 124 | + document.addEventListener('click', async (e) => { |
| 125 | + const el = e.target.closest('[data-ms3-confirm]') |
| 126 | + if (!el) return |
| 127 | + |
| 128 | + e.preventDefault() |
| 129 | + |
| 130 | + const message = el.dataset.ms3Confirm |
| 131 | + const confirmed = await confirm(message) |
| 132 | + |
| 133 | + if (confirmed) { |
| 134 | + if (el.tagName === 'A' && el.href) { |
| 135 | + window.location.href = el.href |
| 136 | + } |
| 137 | + } |
| 138 | + }) |
| 139 | + |
| 140 | + return confirm |
| 141 | +})() |
| 142 | + |
| 143 | +window.ms3Confirm = ms3Confirm |
0 commit comments