@dile/crud 2.8.7 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ajax/ajax.spec.js +35 -0
- package/components/ajax/src/DileAjax.js +28 -17
- package/components/file-uploader/file-uploader.spec.js +32 -0
- package/components/file-uploader/src/DileFileUploader.js +10 -1
- package/components/file-uploader/src/DileFileUploaderForm.js +5 -3
- package/components/ui/crud-sort-form.spec.js +2 -2
- package/lib/i18n/en.js +3 -0
- package/lib/i18n/es.js +3 -0
- package/package.json +3 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import './ajax.js';
|
|
3
|
+
|
|
4
|
+
describe('dile-ajax', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('appends arrays of files as multiple "key[]" entries when sending data as FormData', async () => {
|
|
10
|
+
const el = document.createElement('dile-ajax');
|
|
11
|
+
document.body.appendChild(el);
|
|
12
|
+
await el.updateComplete;
|
|
13
|
+
|
|
14
|
+
const files = [new File(['a'], 'a.txt'), new File(['b'], 'b.txt')];
|
|
15
|
+
el.data = { title: 'Docs', files, empty: null };
|
|
16
|
+
el._prepareFormData();
|
|
17
|
+
|
|
18
|
+
expect(el.formData.get('title')).toBe('Docs');
|
|
19
|
+
expect(el.formData.getAll('files[]').map(file => file.name)).toEqual(['a.txt', 'b.txt']);
|
|
20
|
+
expect(el.formData.has('files')).toBe(false);
|
|
21
|
+
expect(el.formData.has('empty')).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('keeps sending other arrays as a single entry', async () => {
|
|
25
|
+
const el = document.createElement('dile-ajax');
|
|
26
|
+
document.body.appendChild(el);
|
|
27
|
+
await el.updateComplete;
|
|
28
|
+
|
|
29
|
+
el.data = { tags: [1, 2, 3] };
|
|
30
|
+
el._prepareFormData();
|
|
31
|
+
|
|
32
|
+
expect(el.formData.get('tags')).toBe('1,2,3');
|
|
33
|
+
expect(el.formData.has('tags[]')).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -80,7 +80,7 @@ export class DileAjax extends DileAxios(DileI18nMixin(LitElement)) {
|
|
|
80
80
|
}
|
|
81
81
|
this.dispatchEvent(new CustomEvent('ajax-success', { detail }));
|
|
82
82
|
} else {
|
|
83
|
-
this.dispatchError(this.translations.http_unhandled_success, response.data);
|
|
83
|
+
this.dispatchError(this.translations.http_unhandled_success, response.data, [], response.status);
|
|
84
84
|
}
|
|
85
85
|
})
|
|
86
86
|
.catch(async err => {
|
|
@@ -112,50 +112,51 @@ export class DileAjax extends DileAxios(DileI18nMixin(LitElement)) {
|
|
|
112
112
|
const status = res.status;
|
|
113
113
|
switch (status) {
|
|
114
114
|
case 422:
|
|
115
|
-
this.dispatchError(res.data.message || this.translations.http_422, res.data, this.getValidationErrors(res.data));
|
|
115
|
+
this.dispatchError(res.data.message || this.translations.http_422, res.data, this.getValidationErrors(res.data), status);
|
|
116
116
|
break;
|
|
117
117
|
case 400:
|
|
118
|
-
this.dispatchError(res.data.message || this.translations.http_400, res.data, this.getValidationErrors(res.data));
|
|
118
|
+
this.dispatchError(res.data.message || this.translations.http_400, res.data, this.getValidationErrors(res.data), status);
|
|
119
119
|
break;
|
|
120
120
|
case 404:
|
|
121
|
-
this.dispatchError(res.data.message || this.translations.http_404, res.data);
|
|
121
|
+
this.dispatchError(res.data.message || this.translations.http_404, res.data, [], status);
|
|
122
122
|
break;
|
|
123
123
|
case 401:
|
|
124
|
-
this.dispatchError(res.data.message || this.translations.http_401, res.data);
|
|
124
|
+
this.dispatchError(res.data.message || this.translations.http_401, res.data, [], status);
|
|
125
125
|
break;
|
|
126
126
|
case 403:
|
|
127
|
-
this.dispatchError(res.data.message || this.translations.http_403, res.data);
|
|
127
|
+
this.dispatchError(res.data.message || this.translations.http_403, res.data, [], status);
|
|
128
128
|
break;
|
|
129
129
|
case 405:
|
|
130
|
-
this.dispatchError(res.data.message || this.translations.http_405, res.data);
|
|
130
|
+
this.dispatchError(res.data.message || this.translations.http_405, res.data, [], status);
|
|
131
131
|
break;
|
|
132
132
|
case 413:
|
|
133
|
-
this.dispatchError(res.data.message || this.translations.http_413, res.data);
|
|
133
|
+
this.dispatchError(res.data.message || this.translations.http_413, res.data, [], status);
|
|
134
134
|
break;
|
|
135
135
|
case 419:
|
|
136
|
-
this.dispatchError(res.data.message || this.translations.http_419, res.data);
|
|
136
|
+
this.dispatchError(res.data.message || this.translations.http_419, res.data, [], status);
|
|
137
137
|
break;
|
|
138
138
|
case 502:
|
|
139
|
-
this.dispatchError(res.data.message || this.translations.http_502, res.data);
|
|
139
|
+
this.dispatchError(res.data.message || this.translations.http_502, res.data, [], status);
|
|
140
140
|
break;
|
|
141
141
|
case 504:
|
|
142
|
-
this.dispatchError(res.data.message || this.translations.http_504, res.data);
|
|
142
|
+
this.dispatchError(res.data.message || this.translations.http_504, res.data, [], status);
|
|
143
143
|
break;
|
|
144
144
|
default:
|
|
145
|
-
this.dispatchError(res.data.message || this.translations.http_other_error, res.data);
|
|
145
|
+
this.dispatchError(res.data.message || this.translations.http_other_error, res.data, [], status);
|
|
146
146
|
}
|
|
147
147
|
} else {
|
|
148
148
|
this.dispatchRawError(err, null);
|
|
149
|
-
this.dispatchError(this.translations.http_no_response, {});
|
|
149
|
+
this.dispatchError(this.translations.http_no_response, {}, [], null);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
dispatchError(message, data, errors = []) {
|
|
153
|
+
dispatchError(message, data, errors = [], status = null) {
|
|
154
154
|
this.dispatchEvent(new CustomEvent('ajax-error', {
|
|
155
155
|
detail: {
|
|
156
156
|
message,
|
|
157
157
|
data,
|
|
158
|
-
errors
|
|
158
|
+
errors,
|
|
159
|
+
status
|
|
159
160
|
}
|
|
160
161
|
}));
|
|
161
162
|
}
|
|
@@ -191,12 +192,22 @@ export class DileAjax extends DileAxios(DileI18nMixin(LitElement)) {
|
|
|
191
192
|
this.formData = new FormData();
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
_isFileArray(value) {
|
|
196
|
+
return Array.isArray(value) && value.some(item => item instanceof Blob);
|
|
197
|
+
}
|
|
198
|
+
|
|
194
199
|
_addObjectToFormData(data) {
|
|
195
200
|
if (!this.formData) {
|
|
196
|
-
this.
|
|
201
|
+
this._createFormData();
|
|
197
202
|
}
|
|
198
203
|
for (const key in data) {
|
|
199
|
-
if (data[key]
|
|
204
|
+
if (this._isFileArray(data[key])) {
|
|
205
|
+
data[key].forEach(item => {
|
|
206
|
+
if (item !== null && item !== undefined) {
|
|
207
|
+
this.formData.append(`${key}[]`, item);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
} else if (data[key] !== null && data[key] !== undefined) {
|
|
200
211
|
this.formData.append(key, data[key]);
|
|
201
212
|
}
|
|
202
213
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import './file-uploader.js';
|
|
3
|
+
|
|
4
|
+
describe('dile-file-uploader', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
async function renderUploader(html) {
|
|
10
|
+
document.body.innerHTML = html;
|
|
11
|
+
const el = document.body.querySelector('dile-file-uploader');
|
|
12
|
+
await el.updateComplete;
|
|
13
|
+
const form = el.shadowRoot.querySelector('dile-file-uploader-form');
|
|
14
|
+
await form.updateComplete;
|
|
15
|
+
const dropFile = form.shadowRoot.querySelector('dile-drop-file');
|
|
16
|
+
await dropFile.updateComplete;
|
|
17
|
+
return { el, form, dropFile };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
it('uses a single file field named "file" by default', async () => {
|
|
21
|
+
const { dropFile } = await renderUploader('<dile-file-uploader endpoint="/upload"></dile-file-uploader>');
|
|
22
|
+
expect(dropFile.getAttribute('name')).toBe('file');
|
|
23
|
+
expect(dropFile.multiple).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('uses a multiple field named "files" when multiple is set', async () => {
|
|
27
|
+
const { form, dropFile } = await renderUploader('<dile-file-uploader endpoint="/upload" multiple></dile-file-uploader>');
|
|
28
|
+
expect(dropFile.getAttribute('name')).toBe('files');
|
|
29
|
+
expect(dropFile.multiple).toBe(true);
|
|
30
|
+
expect(form.getData()).toEqual({ files: [] });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -20,6 +20,7 @@ export class DileFileUploader extends DileI18nMixin(LitElement) {
|
|
|
20
20
|
saveLabel: { type: String },
|
|
21
21
|
allowedExtensions: { type: Array },
|
|
22
22
|
responseAdapter: { type: Object },
|
|
23
|
+
multiple: { type: Boolean },
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -27,6 +28,7 @@ export class DileFileUploader extends DileI18nMixin(LitElement) {
|
|
|
27
28
|
super();
|
|
28
29
|
this.responseAdapter = new ResponseApiAdapter();
|
|
29
30
|
this.allowedExtensions = ['*'];
|
|
31
|
+
this.multiple = false;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
render() {
|
|
@@ -45,6 +47,7 @@ export class DileFileUploader extends DileI18nMixin(LitElement) {
|
|
|
45
47
|
id="form"
|
|
46
48
|
label="${this.selectLabelComputed(this.selectFileLabel, this.translations)}"
|
|
47
49
|
.allowedExtensions=${this.allowedExtensions}
|
|
50
|
+
?multiple=${this.multiple}
|
|
48
51
|
.translations=${this.translations}
|
|
49
52
|
></dile-file-uploader-form>
|
|
50
53
|
</dile-ajax-form>
|
|
@@ -52,7 +55,13 @@ export class DileFileUploader extends DileI18nMixin(LitElement) {
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
selectLabelComputed(label, translations) {
|
|
55
|
-
|
|
58
|
+
if (label) {
|
|
59
|
+
return label;
|
|
60
|
+
}
|
|
61
|
+
if (this.multiple) {
|
|
62
|
+
return translations?.select_files_label ? translations.select_files_label : 'Select files';
|
|
63
|
+
}
|
|
64
|
+
return translations?.select_file_label ? translations.select_file_label : 'Select a file';
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
saveLabelComputed(label, translations) {
|
|
@@ -19,18 +19,20 @@ export class DileFileUploaderForm extends DileForm(LitElement) {
|
|
|
19
19
|
label: { type: String },
|
|
20
20
|
allowedExtensions: { type: Array },
|
|
21
21
|
translations: { type: Object },
|
|
22
|
+
multiple: { type: Boolean },
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
render() {
|
|
26
27
|
return html`
|
|
27
28
|
<dile-drop-file
|
|
28
|
-
name="file"
|
|
29
|
+
name="${this.multiple ? 'files' : 'file'}"
|
|
29
30
|
label="${this.label}"
|
|
30
31
|
id="dropfile"
|
|
32
|
+
?multiple=${this.multiple}
|
|
31
33
|
.allowedExtensions=${this.allowedExtensions}
|
|
32
|
-
dropLabel="${this.translations.file_drop}"
|
|
33
|
-
buttonLabel="${this.translations.select_file}"
|
|
34
|
+
dropLabel="${this.multiple ? this.translations.files_drop : this.translations.file_drop}"
|
|
35
|
+
buttonLabel="${this.multiple ? this.translations.select_files : this.translations.select_file}"
|
|
34
36
|
selectedFileLabel="${this.translations.selected_file}"
|
|
35
37
|
extensionErrorMessage="${this.translations.extension_allowed}"
|
|
36
38
|
></dile-drop-file>
|
|
@@ -96,7 +96,7 @@ describe('dile-crud-sort-form', () => {
|
|
|
96
96
|
let receivedDetail = null;
|
|
97
97
|
el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
|
|
98
98
|
|
|
99
|
-
el.shadowRoot.querySelector('dile-order-switch[name="year"]').shadowRoot.querySelector('
|
|
99
|
+
el.shadowRoot.querySelector('dile-order-switch[name="year"]').shadowRoot.querySelector('a').click();
|
|
100
100
|
await el.updateComplete;
|
|
101
101
|
|
|
102
102
|
expect(el.sortField).toBe('year');
|
|
@@ -117,7 +117,7 @@ describe('dile-crud-sort-form', () => {
|
|
|
117
117
|
let receivedDetail = null;
|
|
118
118
|
el.addEventListener('sort-changed', (e) => { receivedDetail = e.detail; });
|
|
119
119
|
|
|
120
|
-
const label = () => el.shadowRoot.querySelector('dile-order-switch[name="name"]').shadowRoot.querySelector('
|
|
120
|
+
const label = () => el.shadowRoot.querySelector('dile-order-switch[name="name"]').shadowRoot.querySelector('a');
|
|
121
121
|
|
|
122
122
|
label().click();
|
|
123
123
|
await el.updateComplete;
|
package/lib/i18n/en.js
CHANGED
|
@@ -51,6 +51,9 @@ export const translations = {
|
|
|
51
51
|
file_drop: "Drop here your file",
|
|
52
52
|
select_file: "Select file",
|
|
53
53
|
selected_file: "Selected file",
|
|
54
|
+
select_files_label: "Select files",
|
|
55
|
+
files_drop: "Drop here your files",
|
|
56
|
+
select_files: "Select files",
|
|
54
57
|
extension_allowed: "Only this file extensions are allowed: ",
|
|
55
58
|
image_empty_file: "The selected file is empty",
|
|
56
59
|
image_invalid_file: "The selected file is not a valid image or it's corrupted",
|
package/lib/i18n/es.js
CHANGED
|
@@ -51,6 +51,9 @@ export const translations = {
|
|
|
51
51
|
file_drop: "Suelta aquí el archivo",
|
|
52
52
|
select_file: "Seleccionar archivo",
|
|
53
53
|
selected_file: "Archivo seleccionado",
|
|
54
|
+
select_files_label: "Selecciona archivos",
|
|
55
|
+
files_drop: "Suelta aquí los archivos",
|
|
56
|
+
select_files: "Seleccionar archivos",
|
|
54
57
|
extension_allowed: "Solo se permiten estas extensiones: ",
|
|
55
58
|
image_empty_file: "El archivo seleccionado está vacío",
|
|
56
59
|
image_invalid_file: "El archivo seleccionado no es una imagen válida o está corrupto",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/crud",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "Components to create a generic crud system based on Web Components and Lit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://dile-components.com/",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@dile/ui": "^3.
|
|
28
|
+
"@dile/ui": "^3.16.1",
|
|
29
29
|
"axios": "^1.19.0",
|
|
30
30
|
"lit": "^2.7.0 || ^3.0.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "bb98f6a98cd4fc1034d9fb05fe35cf6379cda54f"
|
|
36
36
|
}
|