mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-11 17:08:45 +00:00
refactor: form binding validation (#38832)
Make "form-fetch-action" highlight the invalid field as "error"
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import {execPseudoSelectorCommands, handleFetchActionSuccessJson} from './common-fetch-action.ts';
|
||||
import {execPseudoSelectorCommands, handleFetchActionErrorFields, handleFetchActionSuccessJson} from './common-fetch-action.ts';
|
||||
import {createElementFromHTML} from '../utils/dom.ts';
|
||||
import {normalizeTestHtml} from '../utils/testhelper.ts';
|
||||
|
||||
test('execPseudoSelectorCommands', () => {
|
||||
window.document.body.innerHTML = `
|
||||
@@ -57,3 +59,15 @@ test('handleFetchActionSuccessJson', async () => {
|
||||
expect(spyReload).toHaveBeenCalledTimes(1);
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
test('handleFetchActionErrorFields', () => {
|
||||
const elForm = createElementFromHTML<HTMLElement>(`<form>
|
||||
<div class="error field"></div>
|
||||
<div class="field"><input name="Foo_Bar[]"></div>
|
||||
</form>`);
|
||||
handleFetchActionErrorFields(elForm, ['foo-BAR', 'other']);
|
||||
expect(normalizeTestHtml(elForm.outerHTML)).toEqual(normalizeTestHtml(`<form>
|
||||
<div class="field"></div>
|
||||
<div class="field error"><input name="Foo_Bar[]"></div>
|
||||
</form>`));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {GET, request} from '../modules/fetch.ts';
|
||||
import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
|
||||
import {activePageTimerRefresh, addDelegatedEventListener, createElementFromHTML} from '../utils/dom.ts';
|
||||
import {activePageTimerRefresh, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
|
||||
import {errorMessage, errorName} from '../modules/errors.ts';
|
||||
import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
@@ -92,7 +92,34 @@ async function handleFetchActionSuccess(el: HTMLElement, opt: FetchActionOpts, r
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetchActionError(resp: Response) {
|
||||
function resetFormErrorFields(elForm: HTMLFormElement) {
|
||||
queryElems(elForm, '.field.error', (el) => el.classList.remove('error'));
|
||||
}
|
||||
|
||||
export function handleFetchActionErrorFields(el: HTMLElement, errorFields: string[]) {
|
||||
// The "error field" only works in a form.
|
||||
// And non-form requests do not need such "error field" because the requests are just from a button or a link in such cases.
|
||||
if (el.nodeName !== 'FORM') return;
|
||||
const elForm = el as HTMLFormElement;
|
||||
resetFormErrorFields(elForm);
|
||||
const fieldNameElemMap : Record<string, HTMLElement> = {};
|
||||
const normalizeFieldName = (name: string) => name.replace(/[^A-Za-z0-9]/g, '').toLowerCase();
|
||||
queryElems(elForm, '[name]', (el) => {
|
||||
const name = el.getAttribute('name');
|
||||
if (!name) return;
|
||||
fieldNameElemMap[normalizeFieldName(name)] = el;
|
||||
});
|
||||
for (const errorField of errorFields) {
|
||||
const name = normalizeFieldName(errorField);
|
||||
const elInput = fieldNameElemMap[name];
|
||||
if (!elInput) continue;
|
||||
const elField = elInput.closest('.field');
|
||||
if (!elField) continue;
|
||||
elField.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetchActionError(el: HTMLElement, resp: Response) {
|
||||
const isRespJson = resp.headers.get('content-type')?.includes('application/json');
|
||||
const respText = await resp.text();
|
||||
const respJson = isRespJson ? JSON.parse(respText) : null;
|
||||
@@ -100,6 +127,9 @@ async function handleFetchActionError(resp: Response) {
|
||||
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
||||
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
||||
showErrorToast(respJson.errorMessage, {useHtmlBody: respJson.renderFormat === 'html'});
|
||||
if (respJson?.errorFields?.length) {
|
||||
handleFetchActionErrorFields(el, respJson?.errorFields);
|
||||
}
|
||||
} else {
|
||||
showErrorToast(`Error ${resp.status} ${resp.statusText}. Response: ${respText.substring(0, 200)}`);
|
||||
}
|
||||
@@ -135,7 +165,7 @@ export async function performFetchActionRequest(el: HTMLElement, opt: FetchActio
|
||||
headers.set('X-Gitea-Fetch-Action', '1');
|
||||
const resp = await request(url, {method: opt.method, data: opt.data, headers});
|
||||
if (resp.ok) return resp;
|
||||
await handleFetchActionError(resp);
|
||||
await handleFetchActionError(el, resp);
|
||||
} catch (err) {
|
||||
if (errorName(err) !== 'AbortError') {
|
||||
console.error(`Fetch action request error:`, err);
|
||||
@@ -194,9 +224,10 @@ function prepareFormFetchActionOpts(formEl: HTMLFormElement, opts: SubmitFormFet
|
||||
};
|
||||
}
|
||||
|
||||
export async function submitFormFetchAction(formEl: HTMLFormElement, opts: SubmitFormFetchActionOpts = {}) {
|
||||
export async function submitFormFetchAction(elForm: HTMLFormElement, opts: SubmitFormFetchActionOpts = {}) {
|
||||
hideToastsAll();
|
||||
await performFetchAction(formEl, prepareFormFetchActionOpts(formEl, opts));
|
||||
resetFormErrorFields(elForm);
|
||||
await performFetchAction(elForm, prepareFormFetchActionOpts(elForm, opts));
|
||||
}
|
||||
|
||||
async function confirmFetchAction(el: HTMLElement) {
|
||||
|
||||
Reference in New Issue
Block a user