Playwright Tests / test (push) Canceled after 0s
serializeObject() returns null for an invalid form, but most callers sent
the result anyway. Add $.fn.rejectInvalid (stop the spinner, let the
browser report the offending field) and use it at every call site.
Bugs fixed:
- Password change: on a mismatch the spinner kept running, and the custom
validity on "npw" was never cleared, so the browser blocked every later
submit even after the passwords matched. It is now cleared as soon as
either password field is edited.
- sendForm: options.complete referenced an undefined jqXHR (ReferenceError)
and an invalid form was posted with partial data.
- login.send: an invalid form threw a TypeError on qs.loginaccount.
- postXT({ form }): an invalid form was posted with empty data.
- Form dialogs without required fields had no "Bitte überprüfen Sie Ihre
Eingaben" note, so a rejected submit showed nothing.
Hardening: null guards for the user, user-group, re-login, SMS password,
admin specialties and report-parameter submits.
Add e2e_tests/form-validation.cjs covering each path against the real
scripts; it fails on the previous code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
208 lines
10 KiB
JavaScript
208 lines
10 KiB
JavaScript
let $usr = {
|
|
init2: function () {
|
|
// No external libraries needed — go straight to render.
|
|
$ocms.getScript([], function () { $usr.init3(); });
|
|
},
|
|
init3: function () {
|
|
$fis.cf(true);
|
|
$fis.lf(true);
|
|
$('#activemodule').text($uct.mdl);
|
|
$usr.eM();
|
|
$usr.renderLst();
|
|
},
|
|
/* Default ribbon (nothing selected). */
|
|
eM: function (r) {
|
|
let m = [{ lbl: $uct.newuser_btn, fnc: $usr.createDlg }];
|
|
if (bool(r, false) === true) { m.push({ lbl: $uct.rel, fnc: $usr.renderLst }); }
|
|
return $('#topbar').ocmsmenu(m);
|
|
},
|
|
/* Per-row ribbon shown while a user row is selected (mirrors the Banking module's iMn/eM swap). */
|
|
iMn: function (rw) {
|
|
return $('#topbar').ocmsmenu([
|
|
{ lbl: $uct.pwdreset, fnc: function () { $usr.pwdResetDlg(rw); } },
|
|
{ lbl: $uct.email, fnc: function () { $usr.emailDlg(rw); } },
|
|
{ lbl: $uct.groups, fnc: function () { $usr.groupsDlg(rw); } },
|
|
{ lbl: $uct.delete, fnc: function () { $usr.deleteUser(rw); } }
|
|
]);
|
|
},
|
|
/* initials for the avatar badge, e.g. "Nadine Beckmann" -> "NB" */
|
|
initials: function (firstname, name) {
|
|
let f = (firstname || '').trim().charAt(0), n = (name || '').trim().charAt(0);
|
|
return (f + n).toUpperCase() || '?';
|
|
},
|
|
/* Builds one (detached) grid row for a user record; shared by renderLst and refreshRow. */
|
|
buildRow: function (rw) {
|
|
let disabled = bool(rw.disabled, false) === true;
|
|
let row = $$.dc('usr_row', { id: 'usr' + rw.useraccount_id });
|
|
$$.dc('usr_avatar', row).text($usr.initials(rw.firstname, rw.name));
|
|
$$.dc('usr_col_name', row).text(`${rw.firstname || ''} ${rw.name || ''}`.trim());
|
|
$$.dc('usr_col_email', row).text(rw.email || '');
|
|
$$.dc('usr_col_groups', row).text(rw.groups || '').attr('title', rw.groups || '');
|
|
$$.dc('usr_col_status', row).append(
|
|
$$.dc(disabled ? 'usr_pill usr_pill_off' : 'usr_pill usr_pill_on').text(disabled ? $uct.disabled : $uct.active)
|
|
);
|
|
row.click(function () {
|
|
row.toggleClass('selected').siblings().rC('selected');
|
|
if (row.is('.selected') === true) { $usr.iMn(rw); } else { $usr.eM(true); }
|
|
});
|
|
return row;
|
|
},
|
|
/* Re-fetches a single user and swaps its grid row in place, keeping selection + ribbon
|
|
(used after changes that only affect that one row: email, group membership). Falls back
|
|
to a full reload when the row is no longer in the grid. Looks up the current row node by
|
|
id inside the response handler (not before the request) so two overlapping calls — e.g.
|
|
two quick checkbox toggles in groupsDlg — each replace whatever row is live at the time
|
|
their own response arrives, instead of the second call operating on a node the first
|
|
already detached via replaceWith. */
|
|
refreshRow: function (rw) {
|
|
if ($('#usr' + rw.useraccount_id).length === 0) { $usr.renderLst(); return; }
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/list'), data: { useraccount_id: rw.useraccount_id }, success: function (r) {
|
|
let fresh = (r.users || [])[0];
|
|
let old = $('#usr' + rw.useraccount_id);
|
|
if (!fresh || old.length === 0) { $usr.renderLst(); return; }
|
|
let selected = old.is('.selected');
|
|
let row = $usr.buildRow(fresh).toggleClass('selected', selected);
|
|
old.replaceWith(row);
|
|
if (selected) { $usr.iMn(fresh); }
|
|
}, error: function () { $usr.renderLst(); }
|
|
});
|
|
},
|
|
renderLst: function () {
|
|
let usrlst = $fis.frm_list(true, true).ldng(1), usrfrm = $$.dc('usrfrm', usrlst);
|
|
$usr.eM(true);
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/list'), success: function (r) {
|
|
usrfrm.empty();
|
|
let grid = $$.dc('usr_grid', usrfrm);
|
|
let hdr = $$.dc('usr_row usr_row_hd', grid);
|
|
$$.dc('usr_col_avatar', hdr);
|
|
$$.dc('usr_col_name', hdr).text($uct.col_name);
|
|
$$.dc('usr_col_email', hdr).text($uct.col_email);
|
|
$$.dc('usr_col_groups', hdr).text($uct.col_groups);
|
|
$$.dc('usr_col_status', hdr).text($uct.col_status);
|
|
if ((r.users || []).length === 0) {
|
|
$$.dc('usr_empty', grid).text($uct.empty);
|
|
return;
|
|
}
|
|
$.each(r.users || [], function (ri, rw) { grid.append($usr.buildRow(rw)); });
|
|
}, error: function () {
|
|
usrfrm.empty();
|
|
$$.dc('usr_err', usrfrm).text($uct.loadfail);
|
|
}, complete: function () { usrlst.ldng(0); }
|
|
});
|
|
},
|
|
createDlg: function () {
|
|
$ocms.dlgform([
|
|
{ name: 'firstname', label: $uct.firstname_field, type: 'string', required: true },
|
|
{ name: 'name', label: $uct.name_field, type: 'string', required: true },
|
|
{ name: 'email', label: $uct.email_field, type: 'email', required: true },
|
|
{ name: 'title', label: $uct.title_field, type: 'string' },
|
|
{ name: 'gender', label: $uct.gender_field, type: 'select', url: [['', ''], ['m', $uct.gender_m], ['f', $uct.gender_f]] },
|
|
{ name: 'mobile', label: $uct.mobile_field, type: 'string' }
|
|
], {
|
|
title: $uct.newuser,
|
|
button: $uct.newuser_btn,
|
|
submit: function (e) {
|
|
let c = $(this).ldng(1);
|
|
let qs = c.serializeObject(true, { typedvalues: true });
|
|
if (qs === null) { c.rejectInvalid(); return; }
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/create'), data: qs, success: function (response) {
|
|
alert(response.emailed === true ? $uct.create_sent : $uct.create_noemail);
|
|
c.trigger('modal_close');
|
|
}, error: function (jqXHR) {
|
|
let code = ((jqXHR || {}).responseJSON || {}).error;
|
|
alert($uct.err[code] || $uct.err_generic);
|
|
}, complete: function () { c.ldng(0); $usr.renderLst(); }
|
|
});
|
|
}
|
|
});
|
|
},
|
|
deleteUser: function (rw) {
|
|
let displayName = `${rw.firstname || ''} ${rw.name || ''}`.trim();
|
|
if (confirm(string($uct.delete_confirm, [displayName]))) {
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/delete'), data: { useraccount_id: rw.useraccount_id }, success: function () {
|
|
$usr.eM(true);
|
|
}, error: function (jqXHR) {
|
|
let code = ((jqXHR || {}).responseJSON || {}).error;
|
|
alert($uct.err[code] || $uct.err_generic);
|
|
}, complete: function () { $usr.renderLst(); }
|
|
});
|
|
}
|
|
},
|
|
pwdResetDlg: function (rw) {
|
|
$ocms.dlgform([
|
|
{ name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id },
|
|
{ name: 'newpassword', label: $uct.pwd_field, type: 'string', note: $uct.pwd_hint }
|
|
], {
|
|
title: $uct.pwdreset,
|
|
button: $uct.pwdreset_btn,
|
|
submit: function (e) {
|
|
let c = $(this).ldng(1);
|
|
let qs = c.serializeObject(true, { typedvalues: true });
|
|
if (qs === null) { c.rejectInvalid(); return; }
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/pwdreset'), data: qs, success: function (response) {
|
|
alert(response.emailed === true ? $uct.pwdreset_sent : $uct.pwdreset_noemail);
|
|
c.trigger('modal_close');
|
|
}, error: function (jqXHR) {
|
|
let code = ((jqXHR || {}).responseJSON || {}).error;
|
|
alert($uct.err[code] || $uct.err_generic);
|
|
}, complete: function () { c.ldng(0); $usr.renderLst(); }
|
|
});
|
|
}
|
|
});
|
|
},
|
|
emailDlg: function (rw) {
|
|
$ocms.dlgform([
|
|
{ name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id },
|
|
{ name: 'email', label: $uct.email_field, type: 'email', required: true, value: rw.email }
|
|
], {
|
|
title: $uct.email,
|
|
submit: function (e) {
|
|
let c = $(this).ldng(1);
|
|
let qs = c.serializeObject(true, { typedvalues: true });
|
|
if (qs === null) { c.rejectInvalid(); return; }
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/email'), data: qs, success: function () {
|
|
c.trigger('modal_close');
|
|
}, error: function (jqXHR) {
|
|
let code = ((jqXHR || {}).responseJSON || {}).error;
|
|
alert($uct.err[code] || $uct.err_generic);
|
|
}, complete: function () { c.ldng(0); $usr.refreshRow(rw); }
|
|
});
|
|
}
|
|
});
|
|
},
|
|
groupsDlg: function (rw) {
|
|
let f = $$.dc('usr_groups_dlg');
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/groups'), data: { useraccount_id: rw.useraccount_id }, success: function (r) {
|
|
$.each(r.groups || [], function (gi, g) {
|
|
let row = $$.dc('usr_group_row', f);
|
|
let cid = 'usrg' + g.usergroup_id;
|
|
let cb = $$.i({ type: 'checkbox', id: cid }).prop('checked', bool(g.member, false)).appendTo(row);
|
|
$$.lbl(g.name, { for: cid }).appendTo(row);
|
|
cb.change(function () {
|
|
let checked = cb.prop('checked');
|
|
$ocms.postXT({
|
|
url: $ocms.url('usr/groupset'),
|
|
data: { useraccount_id: rw.useraccount_id, usergroup_id: g.usergroup_id, member: checked },
|
|
success: function () { $usr.refreshRow(rw); }, /* keep the groups csv in the grid current per toggle */
|
|
error: function () {
|
|
cb.prop('checked', !checked);
|
|
alert($uct.err_generic);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
$ocms.dlg(f, { title: string($uct.groups_title, [`${rw.firstname || ''} ${rw.name || ''}`.trim()]), size: [420, 380] });
|
|
}
|
|
};
|
|
let $$usr = { init2: $usr.init2, auth: {} };
|
|
export default $$usr;
|