Skip to content

Commit 5e396c0

Browse files
committed
DIsplay error in access management forms
Related to #38
1 parent 0601fda commit 5e396c0

File tree

16 files changed

+254
-62
lines changed

16 files changed

+254
-62
lines changed

resources/javascript/admin/resources.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import getResource from "./api/get";
1313
import deleteResource from "./api/delete";
1414
import { load as loadResourceInModal } from "./resources/modal";
1515
import { Resource } from "../model/Resource";
16-
import { submit as submitForm } from "./resources/form";
16+
import { submit as submitForm, resetWarning } from "./resources/form";
1717
import { load as loadPermInModal } from "./resources/access/modal";
1818
import { submit as submitPermForm } from "./resources/access/form";
19+
import { FormError, display as displayFormError } from "../global/FormError";
1920

2021
((): void => {
2122
let currentResourceId: number = null;
@@ -86,10 +87,10 @@ import { submit as submitPermForm } from "./resources/access/form";
8687
});
8788
}
8889

89-
/**
90-
* Submit form
91-
*/
9290
if (document.querySelector("#modal-resource form") !== null) {
91+
/**
92+
* Submit form
93+
*/
9394
document
9495
.querySelector("#modal-resource form")
9596
.addEventListener("submit", (event: Event) => {
@@ -101,8 +102,28 @@ import { submit as submitPermForm } from "./resources/access/form";
101102
currentResourceId = null;
102103

103104
document.location.reload();
105+
}).catch((error: FormError|Error) => {
106+
if (error.name === "FormError") {
107+
displayFormError(target, error as FormError);
108+
} else {
109+
const alert = target.querySelector(".alert") as HTMLDivElement;
110+
111+
alert.innerText = error.message;
112+
alert.hidden = false;
113+
}
104114
});
105115
});
116+
117+
/**
118+
* Reset form
119+
*/
120+
document
121+
.querySelector("#modal-resource form")
122+
.addEventListener("reset", (event: Event) => {
123+
const target = event.currentTarget as HTMLFormElement;
124+
125+
resetWarning(target);
126+
});
106127
}
107128

108129
/**

resources/javascript/admin/resources/form.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ import { Resource } from "../../model/Resource";
55
import createResource from "../api/post";
66
import updateResource from "../api/put";
77

8-
export async function submit(
9-
form: HTMLFormElement,
10-
id: number
11-
): Promise<Resource> {
8+
export function resetWarning(form: HTMLFormElement): void {
9+
(form.querySelector(".alert") as HTMLDivElement).hidden = true;
10+
11+
form.querySelectorAll(".form-control.is-invalid").forEach((element: HTMLInputElement|HTMLSelectElement) => element.classList.remove("is-invalid"));
12+
form.querySelectorAll(".invalid-feedback").forEach((element: HTMLDivElement) => element.remove());
13+
}
14+
15+
export async function submit(form: HTMLFormElement, id: number): Promise<Resource> {
16+
resetWarning(form);
17+
1218
const data = new FormData(form);
1319

1420
const resource = new Resource();

resources/javascript/admin/roles.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import getRole from "./api/get";
1414
import deleteRole from "./api/delete";
1515
import { load as loadRoleInModal } from "./roles/modal";
1616
import { Role } from "../model/Role";
17-
import { submit as submitForm } from "./roles/form";
17+
import { resetWarning, submit as submitForm } from "./roles/form";
1818
import rangeOnChange from "./roles/range";
1919
import { load as loadPermInModal } from "./roles/access/modal";
2020
import { submit as submitPermForm } from "./roles/access/form";
21+
import { FormError, display as displayFormError } from "../global/FormError";
2122

2223
((): void => {
2324
let currentRoleId: number = null;
@@ -111,10 +112,10 @@ import { submit as submitPermForm } from "./roles/access/form";
111112
});
112113
}
113114

114-
/**
115-
* Submit form
116-
*/
117115
if (document.querySelector("#modal-role form") !== null) {
116+
/**
117+
* Submit form
118+
*/
118119
document
119120
.querySelector("#modal-role form")
120121
.addEventListener("submit", (event: Event) => {
@@ -126,8 +127,28 @@ import { submit as submitPermForm } from "./roles/access/form";
126127
currentRoleId = null;
127128

128129
document.location.reload();
130+
}).catch((error: FormError|Error) => {
131+
if (error.name === "FormError") {
132+
displayFormError(target, error as FormError);
133+
} else {
134+
const alert = target.querySelector(".alert") as HTMLDivElement;
135+
136+
alert.innerText = error.message;
137+
alert.hidden = false;
138+
}
129139
});
130140
});
141+
142+
/**
143+
* Reset form
144+
*/
145+
document
146+
.querySelector("#modal-role form")
147+
.addEventListener("reset", (event: Event) => {
148+
const target = event.currentTarget as HTMLFormElement;
149+
150+
resetWarning(target);
151+
});
131152
}
132153

133154
/** Show access */

resources/javascript/admin/roles/form.ts

+9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ import { Role } from "../../model/Role";
55
import createRole from "../api/post";
66
import updateRole from "../api/put";
77

8+
export function resetWarning(form: HTMLFormElement): void {
9+
(form.querySelector(".alert") as HTMLDivElement).hidden = true;
10+
11+
form.querySelectorAll(".form-control.is-invalid").forEach((element: HTMLInputElement|HTMLSelectElement) => element.classList.remove("is-invalid"));
12+
form.querySelectorAll(".invalid-feedback").forEach((element: HTMLDivElement) => element.remove());
13+
}
14+
815
export async function submit(form: HTMLFormElement, id: number): Promise<Role> {
16+
resetWarning(form);
17+
918
const data = new FormData(form);
1019

1120
const role = new Role();

resources/javascript/admin/users.ts

+10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import deleteUser from "./api/delete";
1515
import { init as initModal, load as loadUserInModal } from "./users/modal";
1616
import { User } from "../model/User";
1717
import { reset as resetForm, submit as submitForm } from "./users/form";
18+
import { FormError, display as displayFormError } from "../global/FormError";
1819

1920
((): void => {
2021
let currentUserId: number = null;
@@ -113,6 +114,15 @@ import { reset as resetForm, submit as submitForm } from "./users/form";
113114
currentUserId = null;
114115

115116
document.location.reload();
117+
}).catch((error: FormError|Error) => {
118+
if (error.name === "FormError") {
119+
displayFormError(target, error as FormError);
120+
} else {
121+
const alert = target.querySelector(".alert") as HTMLDivElement;
122+
123+
alert.innerText = error.message;
124+
alert.hidden = false;
125+
}
116126
});
117127
});
118128

resources/javascript/admin/users/form.ts

+11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ import { User } from "../../model/User";
55
import createUser from "./api/post";
66
import updateUser from "./api/put";
77

8+
function resetWarning(form: HTMLFormElement): void {
9+
(form.querySelector(".alert") as HTMLDivElement).hidden = true;
10+
11+
form.querySelectorAll(".form-control.is-invalid").forEach((element: HTMLInputElement|HTMLSelectElement) => element.classList.remove("is-invalid"));
12+
form.querySelectorAll(".invalid-feedback").forEach((element: HTMLDivElement) => element.remove());
13+
}
14+
815
export function reset(form: HTMLFormElement): void {
16+
resetWarning(form);
17+
918
form
1019
.querySelectorAll("input[name='roles[]']")
1120
.forEach((input: HTMLInputElement) => {
@@ -14,6 +23,8 @@ export function reset(form: HTMLFormElement): void {
1423
}
1524

1625
export async function submit(form: HTMLFormElement, id: number): Promise<User> {
26+
resetWarning(form);
27+
1728
const data = new FormData(form);
1829

1930
const user = new User();
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use strict";
2+
3+
export class FormError extends Error {
4+
public readonly field: string;
5+
6+
public constructor(field: string, ...params) {
7+
super(...params);
8+
9+
if (Error.captureStackTrace) {
10+
Error.captureStackTrace(this, FormError);
11+
}
12+
13+
this.name = "FormError";
14+
this.field = field;
15+
}
16+
}
17+
18+
export function display(form: HTMLFormElement, error: FormError): void {
19+
const inputElement = form.querySelector(`input[name="${error.field}"], select[name="${error.field}"]`) as HTMLInputElement|HTMLSelectElement;
20+
21+
const feedbackElement = document.createElement("div");
22+
feedbackElement.className = "invalid-feedback";
23+
feedbackElement.innerText = error.message;
24+
25+
inputElement.parentElement.insertBefore(feedbackElement, inputElement.nextSibling);
26+
27+
inputElement.classList.add("is-invalid");
28+
}

resources/javascript/global/fetch.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import { User } from "../model/User";
44
import { Role } from "../model/Role";
5+
import { Resource } from "../model/Resource";
6+
import { FormError } from "./FormError";
57

68
export default async function(
79
method: string,
@@ -20,10 +22,15 @@ export default async function(
2022
}
2123

2224
const response = await fetch(url, init);
25+
const json = await response.json();
2326

2427
if (!response.ok) {
25-
throw new Error(response.statusText);
28+
if (typeof json.field !== "undefined") {
29+
throw new FormError(json.field, json.error || response.statusText);
30+
} else {
31+
throw new Error(json.error || response.statusText);
32+
}
2633
}
2734

28-
return response.json();
35+
return json;
2936
}

src/App/Handler/API/DefaultHandler.php

+59-46
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
namespace App\Handler\API;
66

7+
use App\Handler\Exception\FormException;
78
use App\Middleware\DbMiddleware;
9+
use Exception;
810
use Laminas\Db\Adapter\Adapter;
911
use Laminas\Db\RowGateway\RowGateway;
1012
use Laminas\Db\Sql\TableIdentifier;
@@ -65,56 +67,67 @@ public function handle(ServerRequestInterface $request): ResponseInterface
6567
$id = $request->getAttribute('id');
6668
$data = $request->getParsedBody();
6769

68-
$objects = $this->getObjects($adapter);
70+
try {
71+
$objects = $this->getObjects($adapter);
6972

70-
if (!is_null($id)) {
71-
$filter = array_filter($objects, function ($object) use ($id) {
72-
return $object->id === intval($id);
73-
});
73+
if (!is_null($id)) {
74+
$filter = array_filter($objects, function ($object) use ($id) {
75+
return $object->id === intval($id);
76+
});
7477

75-
if (count($filter) === 1) {
76-
$object = current($filter);
77-
}
78-
}
79-
80-
switch ($request->getMethod()) {
81-
case 'GET':
82-
if (is_null($id)) {
83-
return new JsonResponse($objects);
84-
} elseif (isset($object)) {
85-
return new JsonResponse($object);
86-
} else {
87-
return new JsonResponse(new stdClass(), 404);
88-
}
89-
break;
90-
case 'POST':
91-
if (!is_null($data)) {
92-
$object = $this->insert($adapter, $data);
93-
94-
return new JsonResponse($object);
95-
} else {
96-
return new JsonResponse(new stdClass(), 404);
97-
}
98-
break;
99-
case 'PUT':
100-
if (isset($object) && !is_null($data)) {
101-
$object = $this->update($adapter, $object, $data);
102-
103-
return new JsonResponse($object);
104-
} else {
105-
return new JsonResponse(new stdClass(), 404);
106-
}
107-
break;
108-
case 'DELETE':
109-
if (isset($object)) {
110-
$object = $this->delete($adapter, $object);
111-
112-
return new JsonResponse($object);
113-
} else {
114-
return new JsonResponse(new stdClass(), 404);
78+
if (count($filter) === 1) {
79+
$object = current($filter);
11580
}
81+
}
11682

117-
break;
83+
switch ($request->getMethod()) {
84+
case 'GET':
85+
if (is_null($id)) {
86+
return new JsonResponse($objects);
87+
} elseif (isset($object)) {
88+
return new JsonResponse($object);
89+
} else {
90+
return new JsonResponse(new stdClass(), 404);
91+
}
92+
break;
93+
case 'POST':
94+
if (!is_null($data)) {
95+
$object = $this->insert($adapter, $data);
96+
97+
return new JsonResponse($object);
98+
} else {
99+
return new JsonResponse(new stdClass(), 404);
100+
}
101+
break;
102+
case 'PUT':
103+
if (isset($object) && !is_null($data)) {
104+
$object = $this->update($adapter, $object, $data);
105+
106+
return new JsonResponse($object);
107+
} else {
108+
return new JsonResponse(new stdClass(), 404);
109+
}
110+
break;
111+
case 'DELETE':
112+
if (isset($object)) {
113+
$object = $this->delete($adapter, $object);
114+
115+
return new JsonResponse($object);
116+
} else {
117+
return new JsonResponse(new stdClass(), 404);
118+
}
119+
120+
break;
121+
}
122+
} catch (FormException $e) {
123+
return new JsonResponse([
124+
'error' => $e->getMessage(),
125+
'field' => $e->getField(),
126+
], 500);
127+
} catch (Exception $e) {
128+
return new JsonResponse([
129+
'error' => $e->getMessage(),
130+
], 500);
118131
}
119132
}
120133

0 commit comments

Comments
 (0)