mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-07 21:43:23 +09:00
refactor: replace jquery.are-you-sure with first-party code (#39233)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -6,6 +6,5 @@
|
||||
/templates/swagger/*.generated.json linguist-generated
|
||||
/options/fileicon/** linguist-generated
|
||||
/vendor/** -text -eol linguist-vendored
|
||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||
Dockerfile.* linguist-language=Dockerfile
|
||||
Makefile.* linguist-language=Makefile
|
||||
|
||||
@@ -32,7 +32,6 @@ const restrictedProperties = [
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores([
|
||||
'web_src/js/vendor',
|
||||
'web_src/fomantic',
|
||||
'public/assets/js',
|
||||
]),
|
||||
|
||||
@@ -24,3 +24,15 @@ test('comment on and close an issue', async ({page, request}) => {
|
||||
await page.getByRole('button', {name: 'Close Issue'}).click();
|
||||
await expect(page.getByRole('button', {name: 'Reopen Issue'})).toBeVisible();
|
||||
});
|
||||
|
||||
test('unsaved issue description prompts before leaving', async ({page, request}) => {
|
||||
const repoName = `e2e-are-you-sure-${randomString(8)}`;
|
||||
await Promise.all([apiCreateRepo(request, {name: repoName, autoInit: false}), login(page)]);
|
||||
await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/issues/new`);
|
||||
await page.getByPlaceholder('Leave a comment').press('a');
|
||||
const dialogPromise = page.waitForEvent('dialog');
|
||||
page.once('dialog', (dialog) => dialog.dismiss());
|
||||
await page.getByRole('link', {name: 'Dashboard'}).click();
|
||||
expect((await dialogPromise).type()).toBe('beforeunload');
|
||||
await expect(page).toHaveURL(/\/issues\/new$/);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import SvgIcon from './SvgIcon.vue';
|
||||
import {isPlainClick} from '../utils/dom.ts';
|
||||
import {shouldTriggerAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {shouldTriggerAreYouSure} from '../modules/are-you-sure.ts';
|
||||
import {shallowRef} from 'vue';
|
||||
import type {createViewFileTreeStore, FileTreeItem} from './ViewFileTreeStore.ts';
|
||||
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts';
|
||||
import {queryElems} from '../utils/dom.ts';
|
||||
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||
|
||||
export function initGlobalFormDirtyLeaveConfirm() {
|
||||
initAreYouSure(window.jQuery);
|
||||
// Warn users that try to leave a page after entering data into a form.
|
||||
// Except on sign-in pages, and for forms marked as 'ignore-dirty'.
|
||||
if (!document.querySelector('.page-content.user.signin')) {
|
||||
applyAreYouSure('form:not(.ignore-dirty)');
|
||||
}
|
||||
}
|
||||
|
||||
export function initGlobalEnterQuickSubmit() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.isComposing) return;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {hideElem, queryElems, showElem, createElementFromHTML, onInputDebounce}
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {initDropzone} from './dropzone.ts';
|
||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||
import {applyAreYouSure, ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {applyAreYouSure, ignoreAreYouSure} from '../modules/are-you-sure.ts';
|
||||
import {submitFormFetchAction} from '../modules/fetch-action.ts';
|
||||
import {dirname} from '../utils.ts';
|
||||
import {pathEscapeSegments} from '../utils/url.ts';
|
||||
@@ -181,24 +181,10 @@ export function initRepoEditor() {
|
||||
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
||||
if (!editArea) return;
|
||||
|
||||
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
||||
// to enable or disable the commit button
|
||||
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button')!;
|
||||
const dirtyFileClass = 'dirty-file';
|
||||
|
||||
const syncCommitButtonState = () => {
|
||||
const dirty = elForm.classList.contains(dirtyFileClass);
|
||||
commitButton.disabled = !dirty;
|
||||
};
|
||||
// Registering a custom listener for the file path and the file content
|
||||
// FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
|
||||
applyAreYouSure(elForm, {
|
||||
silent: true,
|
||||
dirtyClass: dirtyFileClass,
|
||||
fieldSelector: ':input:not(.commit-form-wrapper :input)',
|
||||
change: syncCommitButtonState,
|
||||
});
|
||||
syncCommitButtonState(); // disable the "commit" button when no content changes
|
||||
commitButton.disabled = true;
|
||||
elForm.querySelector('.commit-form-wrapper')!.classList.add('ays-ignore'); // commit form fields don't count
|
||||
applyAreYouSure(elForm, (dirty) => commitButton.disabled = !dirty);
|
||||
|
||||
initEditPreviewTab(elForm);
|
||||
|
||||
@@ -207,7 +193,7 @@ export function initRepoEditor() {
|
||||
filenameInput.addEventListener('input', onInputDebounce(() => editor.updateFilename(filenameInput.value)));
|
||||
|
||||
// Update the editor from query params, if available,
|
||||
// only after the dirtyFileClass initialization
|
||||
// only after the areYouSure initialization
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const value = params.get('value');
|
||||
if (value) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
|
||||
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
|
||||
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {applyAreYouSure} from '../modules/are-you-sure.ts';
|
||||
|
||||
async function tryOnEditContent(e: Event) {
|
||||
const clickTarget = (e.target as HTMLElement).closest('.edit-content');
|
||||
@@ -52,7 +52,7 @@ async function tryOnEditContent(e: Event) {
|
||||
return;
|
||||
}
|
||||
|
||||
reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty
|
||||
applyAreYouSure(editContentZone.querySelector('form')!); // the form is no longer dirty
|
||||
editContentZone.setAttribute('data-content-version', data.contentVersion);
|
||||
|
||||
// replace the render content with new one, to trigger re-initialization of all features
|
||||
|
||||
@@ -16,7 +16,7 @@ import {showErrorToast} from '../modules/toast.ts';
|
||||
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {showFomanticModal} from '../modules/fomantic/modal.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {ignoreAreYouSure} from '../modules/are-you-sure.ts';
|
||||
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
||||
import type {FomanticApiResponse} from '../types.ts';
|
||||
|
||||
|
||||
+2
-1
@@ -58,7 +58,8 @@ import {initOAuth2SettingsDisableCheckbox} from './features/oauth2-settings.ts';
|
||||
import {initGlobalFetchAction} from './modules/fetch-action.ts';
|
||||
import {initCommmPageComponents, initGlobalComponent, initGlobalDropdown, initGlobalInput} from './features/common-page.ts';
|
||||
import {initGlobalButtonClickOnEnter, initGlobalButtons} from './features/common-button.ts';
|
||||
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
||||
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit} from './features/common-form.ts';
|
||||
import {initGlobalFormDirtyLeaveConfirm} from './modules/are-you-sure.ts';
|
||||
import {callInitFunctions} from './modules/init.ts';
|
||||
import {initRepoViewFileTree} from './features/repo-view-file-tree.ts';
|
||||
import {initActionsPermissionsForm} from './features/common-actions-permissions.ts';
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import {applyAreYouSure, ignoreAreYouSure, initGlobalFormDirtyLeaveConfirm, shouldTriggerAreYouSure} from './are-you-sure.ts';
|
||||
|
||||
function createForm(fieldsHtml: string, wrapperClass = ''): HTMLFormElement {
|
||||
document.body.insertAdjacentHTML('beforeend', `<div class="${wrapperClass}"><form>${fieldsHtml}</form></div>`);
|
||||
return document.body.lastElementChild!.querySelector('form')!;
|
||||
}
|
||||
|
||||
const field = (form: HTMLFormElement, name: string) => form.elements.namedItem(name) as HTMLInputElement;
|
||||
|
||||
function set(field: HTMLInputElement, props: Partial<HTMLInputElement>, eventType = 'change') {
|
||||
Object.assign(field, props);
|
||||
field.dispatchEvent(new Event(eventType, {bubbles: true}));
|
||||
}
|
||||
|
||||
const isBeforeUnloadPrevented = () => !window.dispatchEvent(new Event('beforeunload', {cancelable: true}));
|
||||
|
||||
afterEach(() => document.body.replaceChildren());
|
||||
|
||||
test('tracks field changes, clears on submit and reset', () => {
|
||||
const form = createForm(`
|
||||
<input name="title" value="a">
|
||||
<input type="checkbox" name="flag">
|
||||
<select name="pick"><option value="a" selected>a</option><option value="b">b</option></select>
|
||||
`);
|
||||
const onDirtyChange = vi.fn();
|
||||
applyAreYouSure(form, onDirtyChange);
|
||||
set(field(form, 'title'), {value: 'b'}, 'input');
|
||||
set(field(form, 'title'), {value: 'c'}, 'input');
|
||||
set(field(form, 'title'), {value: 'a'}, 'keyup');
|
||||
set(field(form, 'flag'), {checked: true});
|
||||
set(field(form, 'flag'), {checked: false});
|
||||
set(field(form, 'pick'), {value: 'b'});
|
||||
expect(onDirtyChange.mock.calls).toEqual([[true], [false], [true], [false], [true]]);
|
||||
expect(shouldTriggerAreYouSure()).toBe(true);
|
||||
form.addEventListener('submit', (e) => e.preventDefault());
|
||||
form.dispatchEvent(new SubmitEvent('submit', {cancelable: true}));
|
||||
expect(shouldTriggerAreYouSure()).toBe(false);
|
||||
set(field(form, 'title'), {value: 'b'});
|
||||
form.reset();
|
||||
expect(shouldTriggerAreYouSure()).toBe(false);
|
||||
});
|
||||
|
||||
test('ignores untracked fields', () => {
|
||||
const form = createForm(`
|
||||
<input value="a">
|
||||
<input name="dummy" class="ays-ignore" value="a">
|
||||
<div class="ays-ignore"><input name="summary" value="a"></div>
|
||||
<input type="submit" name="send" value="a">
|
||||
<input type="button" name="act" value="a">
|
||||
`);
|
||||
applyAreYouSure(form);
|
||||
form.insertAdjacentHTML('beforeend', '<input name="later" value="a">');
|
||||
for (const input of form.querySelectorAll('input')) set(input, {value: 'b'});
|
||||
expect(shouldTriggerAreYouSure()).toBe(false);
|
||||
});
|
||||
|
||||
test('applying again resets the baseline', () => {
|
||||
const form = createForm(`
|
||||
<input name="title" value="a">
|
||||
<input name="extra" value="a">
|
||||
<div class="wrapper"><input name="summary" value="a"></div>
|
||||
`);
|
||||
const oldOnDirtyChange = vi.fn();
|
||||
const onDirtyChange = vi.fn();
|
||||
applyAreYouSure(form, oldOnDirtyChange);
|
||||
set(field(form, 'title'), {value: 'b'});
|
||||
form.querySelector('.wrapper')!.classList.add('ays-ignore');
|
||||
applyAreYouSure(form, onDirtyChange);
|
||||
set(field(form, 'summary'), {value: 'b'});
|
||||
set(field(form, 'extra'), {value: 'b'});
|
||||
field(form, 'extra').remove();
|
||||
set(field(form, 'title'), {value: 'b'});
|
||||
set(field(form, 'title'), {value: 'a'});
|
||||
expect(oldOnDirtyChange.mock.calls).toEqual([[true]]);
|
||||
expect(onDirtyChange.mock.calls).toEqual([[true], [false], [true]]);
|
||||
});
|
||||
|
||||
test('initGlobalFormDirtyLeaveConfirm guards beforeunload', () => {
|
||||
const signin = createForm('<input name="user_name" value="a">', 'page-content user signin');
|
||||
initGlobalFormDirtyLeaveConfirm();
|
||||
set(field(signin, 'user_name'), {value: 'b'});
|
||||
expect(shouldTriggerAreYouSure()).toBe(false);
|
||||
signin.parentElement!.remove();
|
||||
const form = createForm('<input name="title" value="a">');
|
||||
const ignored = createForm('<input name="q" value="a">');
|
||||
ignored.classList.add('ignore-dirty');
|
||||
initGlobalFormDirtyLeaveConfirm();
|
||||
set(field(ignored, 'q'), {value: 'b'});
|
||||
ignored.classList.remove('ignore-dirty');
|
||||
expect(isBeforeUnloadPrevented()).toBe(false);
|
||||
set(field(form, 'title'), {value: 'b'});
|
||||
expect(isBeforeUnloadPrevented()).toBe(true);
|
||||
form.parentElement!.classList.add('tw-hidden');
|
||||
expect(isBeforeUnloadPrevented()).toBe(false);
|
||||
form.parentElement!.classList.remove('tw-hidden');
|
||||
ignoreAreYouSure(form);
|
||||
expect(isBeforeUnloadPrevented()).toBe(false);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
type FormField = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
||||
type AreYouSureState = {onDirtyChange?: (dirty: boolean) => void, dirty: boolean};
|
||||
|
||||
const areYouSureStates = new WeakMap<HTMLFormElement, AreYouSureState>();
|
||||
const originalValues = new WeakMap<FormField, string>();
|
||||
|
||||
function fieldValue(field: FormField): string {
|
||||
if (field instanceof HTMLSelectElement) {
|
||||
return JSON.stringify(Array.from(field.selectedOptions, (option) => option.value));
|
||||
}
|
||||
if (field instanceof HTMLInputElement && ['checkbox', 'radio'].includes(field.type)) return String(field.checked);
|
||||
return field.value;
|
||||
}
|
||||
|
||||
function trackedFields(form: HTMLFormElement): FormField[] {
|
||||
return Array.from(form.querySelectorAll<FormField>('input:not([type=submit], [type=button]), select, textarea')).filter((field) => {
|
||||
return field.name && !field.closest('.ays-ignore');
|
||||
});
|
||||
}
|
||||
|
||||
function setDirty(form: HTMLFormElement, dirty: boolean) {
|
||||
const state = areYouSureStates.get(form)!;
|
||||
if (state.dirty === dirty) return;
|
||||
state.dirty = dirty;
|
||||
state.onDirtyChange?.(dirty);
|
||||
}
|
||||
|
||||
export function applyAreYouSure(form: HTMLFormElement, onDirtyChange?: (dirty: boolean) => void) {
|
||||
if (!areYouSureStates.has(form)) {
|
||||
const isFieldDirty = (field: FormField) => originalValues.has(field) && originalValues.get(field) !== fieldValue(field);
|
||||
const checkDirty = () => setDirty(form, trackedFields(form).some(isFieldDirty));
|
||||
// keyup: some keydown handlers set values without an input event
|
||||
for (const eventType of ['input', 'change', 'keyup']) form.addEventListener(eventType, checkDirty);
|
||||
for (const eventType of ['submit', 'reset']) form.addEventListener(eventType, () => setDirty(form, false));
|
||||
}
|
||||
areYouSureStates.set(form, {onDirtyChange, dirty: false});
|
||||
for (const field of trackedFields(form)) originalValues.set(field, fieldValue(field));
|
||||
}
|
||||
|
||||
export function ignoreAreYouSure(el: Element) {
|
||||
el.classList.add('ignore-dirty');
|
||||
}
|
||||
|
||||
export function shouldTriggerAreYouSure(): boolean {
|
||||
return Array.from(document.querySelectorAll<HTMLFormElement>('form:not(.ignore-dirty)')).some((form) => {
|
||||
return areYouSureStates.get(form)?.dirty && !form.closest('.tw-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
export function initGlobalFormDirtyLeaveConfirm() {
|
||||
// TODO: refactor the "signin" forms to use "ignore-dirty" class directly, then decouple this module
|
||||
if (document.querySelector('.page-content.user.signin')) return;
|
||||
for (const form of document.querySelectorAll<HTMLFormElement>('form:not(.ignore-dirty)')) applyAreYouSure(form);
|
||||
window.addEventListener('beforeunload', (e) => {
|
||||
if (shouldTriggerAreYouSure()) e.preventDefault();
|
||||
});
|
||||
}
|
||||
@@ -262,7 +262,7 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn
|
||||
cm.view.EditorView.updateListener.of((update: ViewUpdate) => {
|
||||
if (update.docChanged) {
|
||||
textarea.value = update.state.doc.toString();
|
||||
textarea.dispatchEvent(new Event('change')); // needed for jquery-are-you-sure
|
||||
textarea.dispatchEvent(new Event('change', {bubbles: true})); // for the areYouSure dirty tracking
|
||||
}
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@ import {hideToastsAll, showErrorToast} from './toast.ts';
|
||||
import {activePageTimerRefresh, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
|
||||
import {errorMessage, errorName} from './errors.ts';
|
||||
import {confirmModal, createConfirmModal} from '../features/comp/ConfirmModal.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {ignoreAreYouSure} from './are-you-sure.ts';
|
||||
import {registerGlobalSelectorFunc} from './observer.ts';
|
||||
import {Idiomorph} from 'idiomorph';
|
||||
import {parseDom} from '../utils.ts';
|
||||
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// Fork of the upstream module. The only changes are:
|
||||
// * use export to make it work with ES6 modules.
|
||||
// * the addition of `const` to make it strict mode compatible.
|
||||
// * ignore forms with "ignore-dirty" class, ignore hidden forms (closest('.tw-hidden'))
|
||||
// * extract the dirty check logic into a separate function
|
||||
|
||||
/*!
|
||||
* jQuery Plugin: Are-You-Sure (Dirty Form Detection)
|
||||
* https://github.com/codedance/jquery.AreYouSure/
|
||||
*
|
||||
* Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Author: chris.dance@papercut.com
|
||||
* Version: 1.9.0
|
||||
* Date: 13th August 2014
|
||||
*/
|
||||
|
||||
const dataKeyAysSettings = 'ays-settings';
|
||||
|
||||
export function initAreYouSure($) {
|
||||
|
||||
$.fn.areYouSure = function(options) {
|
||||
|
||||
var settings = $.extend(
|
||||
{
|
||||
'message' : 'You have unsaved changes!',
|
||||
'dirtyClass' : 'dirty',
|
||||
'change' : null,
|
||||
'silent' : false,
|
||||
'addRemoveFieldsMarksDirty' : false,
|
||||
'fieldEvents' : 'change keyup propertychange input',
|
||||
'fieldSelector': ":input:not(input[type=submit]):not(input[type=button])"
|
||||
}, options);
|
||||
|
||||
var getValue = function($field) {
|
||||
if ($field.hasClass('ays-ignore')
|
||||
|| $field.hasClass('aysIgnore')
|
||||
|| $field.attr('data-ays-ignore')
|
||||
|| $field.attr('name') === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($field.is(':disabled')) {
|
||||
return 'ays-disabled';
|
||||
}
|
||||
|
||||
var val;
|
||||
var type = $field.attr('type');
|
||||
if ($field.is('select')) {
|
||||
type = 'select';
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'checkbox':
|
||||
case 'radio':
|
||||
val = $field.is(':checked');
|
||||
break;
|
||||
case 'select':
|
||||
val = '';
|
||||
$field.find('option').each(function(o) {
|
||||
var $option = $(this);
|
||||
if ($option.is(':selected')) {
|
||||
val += $option.val();
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
val = $field.val();
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
var storeOrigValue = function($field) {
|
||||
$field.data('ays-orig', getValue($field));
|
||||
};
|
||||
|
||||
var checkForm = function(evt) {
|
||||
|
||||
var isFieldDirty = function($field) {
|
||||
var origValue = $field.data('ays-orig');
|
||||
if (undefined === origValue) {
|
||||
return false;
|
||||
}
|
||||
return (getValue($field) != origValue);
|
||||
};
|
||||
|
||||
var $form = ($(this).is('form'))
|
||||
? $(this)
|
||||
: $(this).parents('form');
|
||||
|
||||
// Test on the target first as it's the most likely to be dirty
|
||||
if (isFieldDirty($(evt.target))) {
|
||||
setDirtyStatus($form, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const $fields = $form.find(settings.fieldSelector);
|
||||
|
||||
if (settings.addRemoveFieldsMarksDirty) {
|
||||
// Check if field count has changed
|
||||
var origCount = $form.data("ays-orig-field-count");
|
||||
if (origCount != $fields.length) {
|
||||
setDirtyStatus($form, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Brute force - check each field
|
||||
var isDirty = false;
|
||||
$fields.each(function() {
|
||||
var $field = $(this);
|
||||
if (isFieldDirty($field)) {
|
||||
isDirty = true;
|
||||
return false; // break
|
||||
}
|
||||
});
|
||||
|
||||
setDirtyStatus($form, isDirty);
|
||||
};
|
||||
|
||||
var initForm = function($form) {
|
||||
var fields = $form.find(settings.fieldSelector);
|
||||
$(fields).each(function() { storeOrigValue($(this)); });
|
||||
$(fields).unbind(settings.fieldEvents, checkForm);
|
||||
$(fields).bind(settings.fieldEvents, checkForm);
|
||||
$form.data("ays-orig-field-count", $(fields).length);
|
||||
$form.data(dataKeyAysSettings, settings);
|
||||
setDirtyStatus($form, false);
|
||||
};
|
||||
|
||||
var setDirtyStatus = function($form, isDirty) {
|
||||
var changed = isDirty != $form.hasClass(settings.dirtyClass);
|
||||
$form.toggleClass(settings.dirtyClass, isDirty);
|
||||
|
||||
// Fire change event if required
|
||||
if (changed) {
|
||||
if (settings.change) settings.change.call($form, $form);
|
||||
|
||||
if (isDirty) $form.trigger('dirty.areYouSure', [$form]);
|
||||
if (!isDirty) $form.trigger('clean.areYouSure', [$form]);
|
||||
$form.trigger('change.areYouSure', [$form]);
|
||||
}
|
||||
};
|
||||
|
||||
var rescan = function() {
|
||||
var $form = $(this);
|
||||
var fields = $form.find(settings.fieldSelector);
|
||||
$(fields).each(function() {
|
||||
var $field = $(this);
|
||||
if (!$field.data('ays-orig')) {
|
||||
storeOrigValue($field);
|
||||
$field.bind(settings.fieldEvents, checkForm);
|
||||
}
|
||||
});
|
||||
// Check for changes while we're here
|
||||
$form.trigger('checkform.areYouSure');
|
||||
};
|
||||
|
||||
var reinitialize = function() {
|
||||
initForm($(this));
|
||||
}
|
||||
|
||||
if (!settings.silent && !window.aysUnloadSet) {
|
||||
window.aysUnloadSet = true;
|
||||
$(window).bind('beforeunload', function() {
|
||||
if (!shouldTriggerAreYouSure(settings)) return;
|
||||
|
||||
// Prevent multiple prompts - seen on Chrome and IE
|
||||
if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) {
|
||||
if (window.aysHasPrompted) {
|
||||
return;
|
||||
}
|
||||
window.aysHasPrompted = true;
|
||||
window.setTimeout(function() {window.aysHasPrompted = false;}, 900);
|
||||
}
|
||||
return settings.message;
|
||||
});
|
||||
}
|
||||
|
||||
return this.each(function(elem) {
|
||||
if (!$(this).is('form')) {
|
||||
return;
|
||||
}
|
||||
var $form = $(this);
|
||||
|
||||
$form.submit(function() {
|
||||
$form.removeClass(settings.dirtyClass);
|
||||
});
|
||||
$form.bind('reset', function() { setDirtyStatus($form, false); });
|
||||
// Add a custom events
|
||||
$form.bind('rescan.areYouSure', rescan);
|
||||
$form.bind('reinitialize.areYouSure', reinitialize);
|
||||
$form.bind('checkform.areYouSure', checkForm);
|
||||
initForm($form);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function applyAreYouSure(selectorOrEl: string|Element|$, opts = {}) {
|
||||
$(selectorOrEl).areYouSure(opts);
|
||||
}
|
||||
|
||||
export function reinitializeAreYouSure(selectorOrEl: string|Element|$) {
|
||||
$(selectorOrEl).trigger('reinitialize.areYouSure');
|
||||
}
|
||||
|
||||
export function ignoreAreYouSure(selectorOrEl: string|Element|$) {
|
||||
// here we should only add "ignore-dirty" but not remove "dirty".
|
||||
// because when using "enter" to submit a form, the "dirty" class will appear again before reloading.
|
||||
$(selectorOrEl).addClass('ignore-dirty');
|
||||
}
|
||||
|
||||
export function shouldTriggerAreYouSure(): boolean {
|
||||
const forms = document.querySelectorAll('form:not(.ignore-dirty)');
|
||||
for (const form of forms) {
|
||||
const settings = $(form).data(dataKeyAysSettings);
|
||||
if (!settings) continue;
|
||||
if (!form.matches('.' + settings.dirtyClass)) continue;
|
||||
if (form.closest('.tw-hidden')) continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user