Skip to content

Commit 087758a

Browse files
authored
Merge pull request #858 from maykinmedia/feature/1817-show-errors-for-selected-documents
💄 [#1817] Show errors for selected documents
2 parents 12086d1 + f9cba5d commit 087758a

File tree

11 files changed

+167
-36
lines changed

11 files changed

+167
-36
lines changed

src/open_inwoner/components/templates/components/Form/FileInput.html

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
{% load i18n button_tags card_tags form_tags icon_tags %}
1+
{% load i18n solo_tags button_tags card_tags form_tags icon_tags %}
22

3-
<div class="form__control file-input">
3+
{% get_solo 'openzaak.OpenZaakConfig' as openzaak_config %}
4+
5+
<div class="form__control file-input" aria-live="polite">
46
{% render_card direction="vertical" %}
57
{% icon icon="upload" icon_position="before" outlined=True %}
6-
<input class="file-input__input" id="{{ field.auto_id }}" name="file" type="file"{% if field.field.required %} required{% endif %}{% if multiple %} multiple{% endif %}>
7-
<label class="button button--primary" for="{{ field.auto_id }}">
8+
<input class="file-input__input" id="{{ field.auto_id }}" name="file" type="file"{% if field.field.required %} required{% endif %}{% if multiple %} multiple{% endif %} data-max-size="{{ openzaak_config.max_upload_size }}">
9+
<label class="button button--primary file-input__label-empty" for="{{ field.auto_id }}">
810
{% if multiple %}{% trans 'Sleep of selecteer bestanden' %}{% else %}{% trans 'Sleep of selecteer bestand' %}{% endif %}
911
</label>
12+
<label class="button button--borderless file-input__label-selected" for="{{ field.auto_id }}">
13+
{% icon icon="add_circle_outlined" icon_position="before" outlined=True %}
14+
{% if multiple %}{% trans 'Selecteer meer bestanden' %}{% else %}{% trans 'Selecteer ander bestand' %}{% endif %}
15+
</label>
1016
{% endrender_card %}
1117

12-
{% if field.help_text %}<p class="p p--muted p--small">{{ field.help_text }}</p>{% endif %}
18+
{% if field.help_text %}<p class="p p--small p--centered">{{ field.help_text }}</p>{% endif %}
1319
{% if field.errors %}{% errors errors=field.errors %}{% endif %}
1420

1521
<div class="file-list" aria-live="polite">

src/open_inwoner/js/components/form/FileInput.js

+79-19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ export class FileInput extends Component {
77
/** @type {string} Use this as selector to instantiate the file input. */
88
static selector = '.file-input'
99

10+
/**
11+
* Get configured maximum filesize from 'data-max-size' and use in node.
12+
* @returns {number} Maximum file size.
13+
*/
14+
getLimit() {
15+
return parseInt(this.getInput().dataset.maxSize)
16+
}
17+
1018
/**
1119
* Returns the card (drop zone) associated with the file input.
1220
* @return {HTMLDivElement}
@@ -23,6 +31,22 @@ export class FileInput extends Component {
2331
return this.node.querySelector(`${FileInput.selector}__input`)
2432
}
2533

34+
/**
35+
* Return the label when zero files are selected.
36+
* @return HTMLInputElement
37+
*/
38+
getLabelEmpty() {
39+
return this.node.querySelector(`${FileInput.selector}__label-empty`)
40+
}
41+
42+
/**
43+
* Return the label if more than 0 files are selected.
44+
* @return HTMLInputElement
45+
*/
46+
getLabelSelected() {
47+
return this.node.querySelector(`${FileInput.selector}__label-selected`)
48+
}
49+
2650
/**
2751
* Return the element associated with the files section.
2852
* @return {HTMLDivElement}
@@ -39,6 +63,14 @@ export class FileInput extends Component {
3963
return this.node.querySelector(`${FileInput.selector} .file-list__list`)
4064
}
4165

66+
/**
67+
* Returns the element outside of this component, which prompts the user to delete file-items that exceed the limit.
68+
* @return {HTMLDivElement}
69+
*/
70+
getFormNonFieldError() {
71+
return document.querySelector('.form__non-field-error')
72+
}
73+
4274
/**
4375
* Binds events to callbacks.
4476
* Callbacks should trigger `setState` which in turn triggers `render`.
@@ -90,13 +122,15 @@ export class FileInput extends Component {
90122

91123
/**
92124
* Gets called when dragging starts on the card (drop zone).
125+
* @param {Event} e
93126
*/
94-
onDragEnter(e) {
127+
onDragEnter() {
95128
this.node.classList.add('file-input--drag-active')
96129
}
97130

98131
/**
99132
* Gets called when dragging ends on the card (drop zone).
133+
* @param {Event} e
100134
*/
101135
onDragLeave(e) {
102136
if (e.target !== this.getCard()) {
@@ -106,8 +140,7 @@ export class FileInput extends Component {
106140
}
107141

108142
/**
109-
* Gets called when click event is received on the files list, it originates from a delete button, handle the deletion
110-
* accordingly.
143+
* Gets called when click event is received on the files list, it originates from a delete button, handle the deletion accordingly.
111144
* @param {PointerEvent} e
112145
*/
113146
onClick(e) {
@@ -164,10 +197,14 @@ export class FileInput extends Component {
164197
render() {
165198
const { files } = this.getInput()
166199
const filesSection = this.getFilesSection()
200+
const additionalLabel = this.getLabelSelected()
201+
const emptyLabel = this.getLabelEmpty()
167202

168-
// Only show files section when files are selected.
169-
filesSection.setAttribute('hidden', true)
170-
files.length && filesSection.removeAttribute('hidden')
203+
// Only show these sections when files are selected.
204+
filesSection.toggleAttribute('hidden', !files.length)
205+
additionalLabel.toggleAttribute('hidden', !files.length)
206+
// Hide label when no files are selected
207+
emptyLabel.toggleAttribute('hidden', files.length > 0)
171208

172209
// Populate the file list.
173210
const html = [...files].map((file) => this.renderFileHTML(file)).join('')
@@ -184,27 +221,50 @@ export class FileInput extends Component {
184221
const ext = name.split('.').pop().toUpperCase()
185222
const sizeMB = (size / (1024 * 1024)).toFixed(2)
186223
const labelDelete = this.getFilesList().dataset.labelDelete || 'Delete'
224+
const getFormNonFieldError = this.getFormNonFieldError()
225+
226+
// Only show errors notification if data-max-file-size is exceeded + add error class to file-list
227+
const maxMegabytes = this.getLimit()
187228

188-
return `
229+
const htmlStart = `
189230
<li class="file-list__list-item">
190231
<aside class="file">
191-
<div class="file__container">
192-
<div class="file__file">
193-
<p class="file__symbol">
232+
<div class="file__container">
233+
${
234+
sizeMB > maxMegabytes
235+
? '<div class="file__file error">'
236+
: '<div class="file__file">'
237+
}
238+
<p class="file__symbol">
194239
<span aria-hidden="true" class="material-icons-outlined">${
195240
type.match('image') ? 'image' : 'description'
196241
}</span>
197-
</p>
198-
<p class="p file__data">
199-
<span class="file__name">${name} (${ext}, ${sizeMB}MB)</span>
200-
</p>
201-
<a class="link link--primary" href="#" role="button" aria-label="${labelDelete}">
202-
<span aria-hidden="true" class="material-icons-outlined">delete</span>
203-
</a>
242+
</p>
243+
<p class="p file__data">
244+
<span class="file__name">${name} (${ext}, ${sizeMB}MB)</span>
245+
</p>
246+
<a class="link link--primary" href="#" role="button" aria-label="${labelDelete}">
247+
<span aria-hidden="true" class="material-icons-outlined">delete</span>
248+
</a>
204249
</div>
205250
</div>
206251
</aside>
207-
</li>
208-
`
252+
</li>`
253+
254+
if (sizeMB > maxMegabytes) {
255+
getFormNonFieldError.removeAttribute('hidden')
256+
257+
return (
258+
htmlStart +
259+
`<p class="p p--small p--centered error">
260+
<span aria-hidden="true" class="material-icons-outlined">warning_amber</span>
261+
Dit bestand is te groot
262+
</p>`
263+
)
264+
} else {
265+
getFormNonFieldError.setAttribute('hidden', 'hidden')
266+
}
267+
268+
return htmlStart
209269
}
210270
}

src/open_inwoner/scss/components/Button/Button.scss

+9
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
}
6464
}
6565

66+
&--fullwidth {
67+
display: block;
68+
width: 100%;
69+
}
70+
6671
&#{&}--primary {
6772
background-color: var(--color-primary);
6873
border: 1px solid var(--color-primary);
@@ -169,6 +174,10 @@
169174
> .link__text {
170175
width: 100%;
171176
}
177+
178+
&[hidden] {
179+
display: none;
180+
}
172181
}
173182

174183
.h2 + .button,

src/open_inwoner/scss/components/Cases/CaseDetail.scss

+2-9
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
}
1616

1717
#document-upload {
18+
color: var(--color-gray-dark);
1819
gap: 0;
19-
padding: 0 0 var(--row-height-giant) 0;
20+
padding: 0 0 var(--row-height) 0;
2021

2122
&:invalid [type='submit'] {
2223
@extend .button--disabled;
@@ -28,20 +29,12 @@
2829
justify-content: center;
2930
pointer-events: none;
3031
width: 100%;
31-
32-
@media (min-width: 768px) {
33-
width: initial;
34-
}
3532
}
3633

3734
.file-list,
3835
.form__actions {
3936
display: block;
4037
margin: 0;
41-
42-
@media (min-width: 768px) {
43-
display: grid;
44-
}
4538
}
4639

4740
/// Make required asterisk visible for this component

src/open_inwoner/scss/components/File/FileList.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
}
1313

1414
&__list-item {
15-
margin-bottom: var(--spacing-large);
15+
margin-bottom: var(--spacing-medium);
1616

1717
&:last-child {
1818
margin-bottom: 0;

src/open_inwoner/scss/components/Form/FileInput.scss

+49
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@
1919
.card__body {
2020
box-sizing: border-box;
2121
padding: var(--row-height);
22+
23+
.button--borderless {
24+
display: flex;
25+
gap: var(--spacing-medium);
26+
color: var(--color-primary);
27+
font-weight: bold;
28+
29+
[class*='icon'] {
30+
color: var(--color-primary);
31+
font-size: var(--font-size-body);
32+
position: static !important;
33+
transform: none !important;
34+
height: var(--font-size-body);
35+
}
36+
37+
&[hidden] {
38+
display: none;
39+
}
40+
}
2241
}
2342

2443
[class*='icon'] {
@@ -56,5 +75,35 @@
5675

5776
& .file-list {
5877
margin-bottom: var(--spacing-large) !important;
78+
79+
// upload-error styles
80+
.file__file.error {
81+
border-color: var(--color-red-notification);
82+
83+
.p {
84+
color: var(--color-gray-90);
85+
}
86+
87+
[class*='icon'] {
88+
color: var(--color-gray-light);
89+
}
90+
91+
.link [class*='icon'] {
92+
color: var(--color-red-notification);
93+
}
94+
}
95+
96+
.p--centered.error {
97+
color: var(--color-red-notification);
98+
display: flex;
99+
align-items: normal;
100+
gap: var(--spacing-small);
101+
justify-content: flex-start;
102+
103+
[class*='icon'] {
104+
color: var(--color-red-notification);
105+
font-size: var(--font-size-body-large);
106+
}
107+
}
59108
}
60109
}

src/open_inwoner/scss/components/Form/FileInputDefault.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ input[type='file'] {
1515
@media (min-width: 768px) {
1616
display: flex;
1717
flex-direction: column;
18-
min-width: var(--form-width);
18+
min-width: var(--mobile-xs-width);
1919
}
2020
}
2121

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.form {
2+
&__non-field-error {
3+
color: var(--color-gray-dark);
4+
margin-top: var(--spacing-medium);
5+
}
6+
}

src/open_inwoner/scss/components/Typography/P.scss

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
color: var(--color-mute);
2121
}
2222

23+
&--centered {
24+
text-align: center;
25+
}
26+
2327
&:empty {
2428
margin: 0 !important;
2529
}

src/open_inwoner/scss/components/_index.scss

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
@import './Form/Input.scss';
3838
@import './Form/Label.scss';
3939
@import './Form/MultiCheckbox.scss';
40+
@import './Form/NonFieldError.scss';
4041
@import './Form/Radio.scss';
4142
@import './Form/Search.scss';
4243
@import './Form/Select.scss';

src/open_inwoner/templates/pages/cases/document_form.html

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
{% csrf_token %}
55
{% input form.type no_label=True no_help=True class="label input" id="id_type" extra_classes="file-type__select" %}
66
{% file_input form.files %}
7-
{% form_actions primary_text=_("Upload documenten") primary_icon="arrow_forward" enctype="multipart/form-data" %}
7+
{% form_actions primary_text=_("Upload documenten") enctype="multipart/form-data" %}
8+
<div class="form__non-field-error" hidden>
9+
<p class="p p--small p--centered">{% trans "Verwijder eerst bestanden die niet voldoen aan de voorwaarden" %}</p>
10+
</div>
811
{% endrender_form %}

0 commit comments

Comments
 (0)