@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.
@@ -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.createFormData();
201
+ this._createFormData();
197
202
  }
198
203
  for (const key in data) {
199
- if (data[key] !== null && data[key] !== undefined) {
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
- return label ? label : translations?.select_file_label ? translations.select_file_label : 'Select a file';
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('span').click();
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('span');
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.8.7",
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.14.0",
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": "2dbcfcfc126715e1f21c28806714f5f59b89d93b"
35
+ "gitHead": "bb98f6a98cd4fc1034d9fb05fe35cf6379cda54f"
36
36
  }