@osimatic/helpers-js 1.5.2 → 1.5.4

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.
@@ -1,239 +1,268 @@
1
+ require('./string');
1
2
 
2
3
  class ImportFromCsv {
3
4
 
4
- static initForm(div, importColumns, requestImportData, specificDescDiv, additionalFormField) {
5
- div.empty().append($('.import_form_base').clone().removeClass('import_form_base hide'));
5
+ static _defaults = {
6
+ errorMessageFileNotValid: 'Le fichier sélectionné n\'est pas un fichier CSV valide.',
7
+ errorMessageFileEmpty: 'Veuillez indiquer le fichier CSV à importer.',
8
+ errorMessageImportSelectColumns: 'Veuillez sélectionner les colonnes à importer.',
9
+ selectDefaultOptionLabel: 'Sélectionnez la colonne\u2026',
10
+ lineLabel: 'Ligne {0} :',
11
+ errorMessageImportFailed: 'L\'importation a échouée :',
12
+ };
13
+
14
+ static setDefault(options) {
15
+ ImportFromCsv._defaults = { ...ImportFromCsv._defaults, ...options };
16
+ }
6
17
 
7
- let formUpload = div.find('.form_upload');
8
- let formMatching = div.find('.form_matching');
9
- let divResult = div.find('.csv_result');
18
+ static initForm(div, options = {}) {
19
+ const {
20
+ importColumns,
21
+ requestImportData,
22
+ specificDescDiv,
23
+ additionalFormField,
24
+ errorMessageFileNotValid,
25
+ errorMessageFileEmpty,
26
+ errorMessageImportSelectColumns,
27
+ selectDefaultOptionLabel,
28
+ lineLabel,
29
+ errorMessageImportFailed,
30
+ } = { ...ImportFromCsv._defaults, ...options };
31
+
32
+ const template = document.querySelector('.import_form_base');
33
+ if (!template) return;
34
+ const clone = template.cloneNode(true);
35
+ clone.classList.remove('import_form_base', 'hide');
36
+ div.innerHTML = '';
37
+ div.appendChild(clone);
38
+
39
+ const formUpload = div.querySelector('.form_upload');
40
+ const formMatching = div.querySelector('.form_matching');
41
+ const divResult = div.querySelector('.csv_result');
10
42
 
11
43
  function resetUi() {
12
- formMatching.addClass('hide');
13
- formUpload.removeClass('hide');
14
- formUpload.find('div.errors').addClass('hide');
44
+ formMatching.classList.add('hide');
45
+ formUpload.classList.remove('hide');
46
+ formUpload.querySelector('div.errors')?.classList.add('hide');
15
47
  }
16
48
 
17
- if (typeof specificDescDiv != 'undefined' && specificDescDiv != null) {
18
- div.find('.specific_desc').append(specificDescDiv);
49
+ if (specificDescDiv != null) {
50
+ div.querySelector('.specific_desc')?.append(specificDescDiv);
19
51
  }
20
52
 
21
- if (typeof additionalFormField != 'undefined' && additionalFormField != null) {
22
- div.find('.import_matching_select_content').after(additionalFormField);
53
+ if (additionalFormField != null) {
54
+ div.querySelector('.import_matching_select_content')?.insertAdjacentHTML('afterend', additionalFormField);
23
55
  }
24
56
 
25
- formUpload.find('button[type="submit"]').click(function(event) {
57
+ const submitUploadBtn = formUpload.querySelector('button[type="submit"]');
58
+ submitUploadBtn.addEventListener('click', function(event) {
26
59
  event.preventDefault();
27
- FormHelper.buttonLoader($(this), 'loading');
28
- formUpload.find('div.errors').addClass('hide');
29
-
30
- let isFileParsed = false;
31
- let hasHeader = formUpload.find('input[name="header"][value="1"]:checked').length;
32
- let encoding = formUpload.find('select[name="encoding"]').val();
33
-
34
- formUpload.find('input[type="file"]').parse({
35
- config: {
36
- header: hasHeader,
37
- encoding: encoding,
38
- dynamicTyping: false,
39
- skipEmptyLines: true,
40
- beforeFirstChunk: function(chunk) {
41
- return chunk.trim();
42
- },
43
- complete: function(results, file) {
44
- isFileParsed = true;
45
- //console.log(file, results);
46
-
47
- if (false === CSV.checkFile(file.name, file.type)) {
48
- $('#form_import_upload div.errors').html(errorMessageFileNotValid).removeClass('hide');
49
- return;
50
- }
51
-
52
- let parsedImportList = results.data;
53
- let header = hasHeader?results.meta.fields:results.data[0];
54
-
55
- ImportFromCsv.displayData(divResult, parsedImportList, (hasHeader?header:null), formMatching);
56
- ImportFromCsv.displayFormMatching(formMatching, importColumns, header, hasHeader);
57
-
58
- formUpload.addClass('hide');
59
- }
60
- },
61
- before: function(file, inputElem) {
62
- },
63
- error: function(err, file, inputElem, reason) {
64
- isFileParsed = true;
65
- formUpload.find('div.errors').html(errorMessageFileNotValid).removeClass('hide');
66
- console.error(err, file, reason);
60
+ FormHelper.buttonLoader(this, 'loading');
61
+ formUpload.querySelector('div.errors')?.classList.add('hide');
62
+
63
+ const fileInput = formUpload.querySelector('input[type="file"]');
64
+ if (!fileInput.files || !fileInput.files.length) {
65
+ const errDiv = formUpload.querySelector('div.errors');
66
+ if (errDiv) { errDiv.innerHTML = errorMessageFileEmpty; errDiv.classList.remove('hide'); }
67
+ FormHelper.buttonLoader(submitUploadBtn, 'reset');
68
+ return;
69
+ }
70
+
71
+ const hasHeader = formUpload.querySelectorAll('input[name="header"][value="1"]:checked').length;
72
+ const encoding = formUpload.querySelector('select[name="encoding"]').value;
73
+
74
+ Papa.parse(fileInput.files[0], {
75
+ header: hasHeader,
76
+ encoding: encoding,
77
+ dynamicTyping: false,
78
+ skipEmptyLines: true,
79
+ beforeFirstChunk: function(chunk) {
80
+ return chunk.trim();
67
81
  },
68
- complete: function() {
69
- if (!isFileParsed) {
70
- formUpload.find('div.errors').html(errorMessageFileEmpty).removeClass('hide');
82
+ complete: function(results, file) {
83
+ if (false === CSV.checkFile(file.name, file.type)) {
84
+ const errDiv = document.querySelector('#form_import_upload div.errors');
85
+ if (errDiv) { errDiv.innerHTML = errorMessageFileNotValid; errDiv.classList.remove('hide'); }
86
+ FormHelper.buttonLoader(submitUploadBtn, 'reset');
87
+ return;
71
88
  }
72
- FormHelper.buttonLoader(formUpload.find('button[type="submit"]'), 'reset');
89
+
90
+ const parsedImportList = results.data;
91
+ const header = hasHeader ? results.meta.fields : results.data[0];
92
+
93
+ ImportFromCsv.displayData(divResult, parsedImportList, (hasHeader ? header : null), formMatching);
94
+ ImportFromCsv.displayFormMatching(formMatching, importColumns, header, hasHeader, selectDefaultOptionLabel);
95
+
96
+ formUpload.classList.add('hide');
97
+ FormHelper.buttonLoader(submitUploadBtn, 'reset');
98
+ },
99
+ error: function(err, file) {
100
+ const errDiv = formUpload.querySelector('div.errors');
101
+ if (errDiv) { errDiv.innerHTML = errorMessageFileNotValid; errDiv.classList.remove('hide'); }
102
+ console.error(err, file);
103
+ FormHelper.buttonLoader(submitUploadBtn, 'reset');
73
104
  }
74
105
  });
75
- event.preventDefault();
76
106
  });
77
107
 
78
- formMatching.find('button[type="submit"]').click(function (event) {
108
+ const submitMatchingBtn = formMatching.querySelector('button[type="submit"]');
109
+ submitMatchingBtn.addEventListener('click', function(event) {
79
110
  event.preventDefault();
80
- FormHelper.buttonLoader($(this), 'loading');
81
- formMatching.find('div.errors').addClass('hide').empty();
82
- divResult.find('table tr').removeClass('danger');
83
-
84
- let tabLink = ImportFromCsv.getTabLink(formMatching);
85
- //console.log('tabLink', tabLink);
86
-
87
- if ($.isEmptyObject(tabLink)) {
88
- formMatching.find('div.errors').html(errorMessageImportSelectColumns).removeClass('hide');
89
- FormHelper.buttonLoader($(this), 'reset');
90
- return false;
111
+ FormHelper.buttonLoader(this, 'loading');
112
+ const errDiv = formMatching.querySelector('div.errors');
113
+ if (errDiv) { errDiv.classList.add('hide'); errDiv.innerHTML = ''; }
114
+ divResult.querySelectorAll('table tr').forEach(tr => tr.classList.remove('danger'));
115
+
116
+ const tabLink = ImportFromCsv.getTabLink(formMatching);
117
+
118
+ if (Object.keys(tabLink).length === 0) {
119
+ if (errDiv) { errDiv.innerHTML = errorMessageImportSelectColumns; errDiv.classList.remove('hide'); }
120
+ FormHelper.buttonLoader(this, 'reset');
121
+ return;
91
122
  }
92
123
 
93
- let dataToImport = ImportFromCsv.getDataToImport(divResult, tabLink);
94
- //console.log('dataToImport', dataToImport);
124
+ const dataToImport = ImportFromCsv.getDataToImport(divResult, tabLink);
95
125
 
96
126
  requestImportData(dataToImport,
97
- // fonction callback en cas d'erreur de formulaire
98
127
  (json) => {
99
- //console.log(json);
100
- if (typeof json['import_list'] !== 'undefined') {
101
- formMatching.find('div.errors').html(json['import_list']).removeClass('hide');
128
+ if (errDiv) {
129
+ errDiv.innerHTML = typeof json['import_list'] !== 'undefined'
130
+ ? json['import_list']
131
+ : ImportFromCsv.getErrorsHtmlOfImportData(json, divResult, errorMessageImportFailed, lineLabel);
132
+ errDiv.classList.remove('hide');
102
133
  }
103
- else {
104
- formMatching.find('div.errors').html(ImportFromCsv.getErrorsHtmlOfImportData(json, divResult)).removeClass('hide');
105
- }
106
- FormHelper.buttonLoader(formMatching.find('button[type="submit"]'), 'reset');
134
+ FormHelper.buttonLoader(formMatching.querySelector('button[type="submit"]'), 'reset');
107
135
  }
108
136
  );
109
137
  });
110
138
 
111
- formMatching.find('a.cancel_link').click(function (event) {
112
- resetUi();
113
- });
139
+ const cancelLink = formMatching.querySelector('a.cancel_link');
140
+ if (cancelLink) {
141
+ cancelLink.addEventListener('click', (event) => {
142
+ event.preventDefault();
143
+ resetUi();
144
+ });
145
+ }
114
146
 
115
147
  resetUi();
116
148
  }
117
149
 
118
150
  static getDataToImport(divResult, tabLink) {
119
- let importListWithFieldNames = [];
120
- $.each(divResult.find('table tbody tr'), function(index, line) {
121
- // console.log('line', line);
122
- if (!$(line).find('input.import_line_checkbox:checked').length) {
123
- // if (!divResult.find('table tr[data-line="'+(index+1)+'"] input.import_line_checkbox:checked').length) {
151
+ const importListWithFieldNames = [];
152
+ divResult.querySelectorAll('table tbody tr').forEach((line, index) => {
153
+ if (!line.querySelectorAll('input.import_line_checkbox:checked').length) {
124
154
  return;
125
155
  }
126
156
 
127
- let lineData = {line: (index+1)};
128
- $.each(tabLink, function(key, listeImportIndex) {
157
+ const lineData = { line: (index + 1) };
158
+ Object.entries(tabLink).forEach(([key, listeImportIndex]) => {
129
159
  if (listeImportIndex != -1) {
130
- var td = $(line).find('td[data-key="'+listeImportIndex+'"]');
131
- if (td.length) {
132
- lineData[key] = td.text();
160
+ const td = line.querySelector('td[data-key="' + listeImportIndex + '"]');
161
+ if (td) {
162
+ lineData[key] = td.textContent;
133
163
  }
134
164
  }
135
165
  });
136
- //console.log('lineData', lineData);
137
166
  importListWithFieldNames.push(lineData);
138
167
  });
139
168
  return importListWithFieldNames;
140
169
  }
141
170
 
142
171
  static displayData(divResult, data, header, formMatching) {
143
- let table = divResult.find('table');
144
- if (table.length === 0) {
145
- divResult.append('<table class="table table-sm table-bordered"></table>');
146
- table = divResult.find('table');
172
+ let table = divResult.querySelector('table');
173
+ if (!table) {
174
+ divResult.insertAdjacentHTML('beforeend', '<table class="table table-sm table-bordered"></table>');
175
+ table = divResult.querySelector('table');
147
176
  }
148
- table.empty();
177
+ table.innerHTML = '';
149
178
 
150
179
  let tableContent = '';
151
180
  if (null !== header) {
152
181
  tableContent += '<thead><tr>';
153
- //tableContent += '<th><input type="checkbox" class="import_line_select_all" /></th>';
154
182
  tableContent += '<th></th>';
155
- $.each(header, function (index, value) {
156
- tableContent += '<th>'+value+'</th>';
183
+ header.forEach((value) => {
184
+ tableContent += '<th>' + value + '</th>';
157
185
  });
158
186
  tableContent += '<th></th>';
159
187
  tableContent += '</tr></thead>';
160
188
  }
161
189
 
162
190
  tableContent += '<tbody>';
163
- $.each(data, function (index, line) {
164
- tableContent += '<tr data-line="'+(index+1)+'">';
165
- tableContent += '<td class="text-bold text-end select_line_checkbox"><input type="checkbox" class="import_line_checkbox pull-left" checked="checked" /> '+(index+1)+'.</td>';
166
- $.each(line, function (key, value) {
167
- tableContent += '<td data-key="'+key+'">'+(value!==null?value:'')+'</td>';
191
+ data.forEach((line, index) => {
192
+ tableContent += '<tr data-line="' + (index + 1) + '">';
193
+ tableContent += '<td class="text-bold text-end select_line_checkbox"><input type="checkbox" class="import_line_checkbox pull-left" checked="checked" /> ' + (index + 1) + '.</td>';
194
+ const entries = Array.isArray(line) ? line.map((v, i) => [i, v]) : Object.entries(line);
195
+ entries.forEach(([key, value]) => {
196
+ tableContent += '<td data-key="' + key + '">' + (value !== null ? value : '') + '</td>';
168
197
  });
169
198
  tableContent += '<td class="text-center edit_line_button"></td>';
170
- tableContent +='</tr>';
199
+ tableContent += '</tr>';
171
200
  });
172
201
  tableContent += '</tbody>';
173
202
 
174
- table.html(tableContent);
203
+ table.innerHTML = tableContent;
175
204
 
176
- table.find('td.edit_line_button').each(function(idx, el) {
177
- ImportFromCsv.initEditLink(formMatching, $(el));
205
+ table.querySelectorAll('td.edit_line_button').forEach(el => {
206
+ ImportFromCsv.initEditLink(formMatching, el);
178
207
  });
179
208
 
180
- divResult.removeClass('hide');
209
+ divResult.classList.remove('hide');
181
210
  }
182
211
 
183
212
  static initValidateLine(formMatching, td) {
184
- td.html($('<a href="#" class="import_validate_line text-success"><i class="fas fa-check"></i></a>'));
185
- td.find('a.import_validate_line').click(function () {
186
- let tr = $(this).parent().parent();
187
- tr.find('td').each(function(key, el) {
188
- let td = $(el);
189
- if (td.hasClass('select_line_checkbox') || td.hasClass('edit_line_button')) {
213
+ td.innerHTML = '<a href="#" class="import_validate_line text-success"><i class="fas fa-check"></i></a>';
214
+ td.querySelector('a.import_validate_line').addEventListener('click', function(e) {
215
+ e.preventDefault();
216
+ const tr = this.closest('tr');
217
+ tr.querySelectorAll('td').forEach((cell) => {
218
+ if (cell.classList.contains('select_line_checkbox') || cell.classList.contains('edit_line_button')) {
190
219
  return;
191
220
  }
192
- td.html(td.find('input').val());
221
+ const input = cell.querySelector('input');
222
+ cell.innerHTML = input ? input.value : '';
193
223
  });
194
224
 
195
- if (!td.closest('table').find('td input[type="text"]').length) {
196
- formMatching.find('button[type="submit"]').prop('disabled', false);
225
+ if (!td.closest('table').querySelectorAll('td input[type="text"]').length) {
226
+ formMatching.querySelector('button[type="submit"]').disabled = false;
197
227
  }
198
228
  ImportFromCsv.initEditLink(formMatching, td);
199
- return false;
200
229
  });
201
230
  }
202
231
 
203
232
  static initEditLink(formMatching, td) {
204
- td.html($('<a href="#" class="import_edit_line text-danger"><i class="fas fa-pencil-alt"></i></a>'));
205
- td.find('a.import_edit_line').click(function () {
206
- let tr = $(this).parent().parent();
207
- tr.find('td').each(function(key, el) {
208
- let td = $(el);
209
- if (td.hasClass('select_line_checkbox') || td.hasClass('edit_line_button')) {
233
+ td.innerHTML = '<a href="#" class="import_edit_line text-danger"><i class="fas fa-pencil-alt"></i></a>';
234
+ td.querySelector('a.import_edit_line').addEventListener('click', function(e) {
235
+ e.preventDefault();
236
+ const tr = this.closest('tr');
237
+ tr.querySelectorAll('td').forEach((cell) => {
238
+ if (cell.classList.contains('select_line_checkbox') || cell.classList.contains('edit_line_button')) {
210
239
  return;
211
240
  }
212
- td.data('original_value', td.html());
213
- td.html($('<input type="text" class="form-control" value="'+td.html()+'" />'));
241
+ cell.dataset.original_value = cell.innerHTML;
242
+ cell.innerHTML = '<input type="text" class="form-control" value="' + cell.innerHTML.replace(/"/g, '&quot;') + '" />';
214
243
  });
215
- formMatching.find('button[type="submit"]').prop('disabled', true);
244
+ formMatching.querySelector('button[type="submit"]').disabled = true;
216
245
  ImportFromCsv.initValidateLine(formMatching, td);
217
- return false;
218
246
  });
219
247
  }
220
248
 
221
- static getErrorsHtmlOfImportData(json, divResult=null) {
249
+ static getErrorsHtmlOfImportData(json, divResult = null, errorMessageImportFailed = null, lineLabel = null) {
250
+ errorMessageImportFailed = errorMessageImportFailed ?? ImportFromCsv._defaults.errorMessageImportFailed;
251
+ lineLabel = lineLabel ?? ImportFromCsv._defaults.lineLabel;
222
252
  let resultError = errorMessageImportFailed;
223
253
  resultError += '<ul>';
224
- $.each(json, function(idx, errorData) {
254
+ json.forEach((errorData) => {
225
255
  console.error(errorData);
226
256
  if (null != divResult) {
227
- divResult.find('table tr[data-line="'+errorData.line+'"]').addClass('danger');
257
+ divResult.querySelector('table tr[data-line="' + errorData.line + '"]')?.classList.add('danger');
228
258
  }
229
-
230
- resultError += '<li>'+lineLabel.format(errorData.line)+'<ul>';
231
- $.each(errorData.errors, function(index, error) {
232
- resultError += '<li>'+error+'</li>';
259
+ resultError += '<li>' + lineLabel.format(errorData.line) + '<ul>';
260
+ errorData.errors.forEach((error) => {
261
+ resultError += '<li>' + error + '</li>';
233
262
  });
234
263
  resultError += '</ul></li>';
235
264
  });
236
- resultError +='</ul>';
265
+ resultError += '</ul>';
237
266
  return resultError;
238
267
  }
239
268
 
@@ -253,37 +282,46 @@ class ImportFromCsv {
253
282
  }
254
283
 
255
284
  static getTabLink(formMatching) {
256
- let tabLink = {};
257
- formMatching.find('select').each(function(idx, select) {
258
- var listeImportIndex = $(select).val();
285
+ const tabLink = {};
286
+ formMatching.querySelectorAll('select').forEach((select) => {
287
+ const listeImportIndex = select.value;
259
288
  if (listeImportIndex != -1) {
260
- let key = $(select).prop('name');
261
- tabLink[key] = listeImportIndex;
289
+ tabLink[select.name] = listeImportIndex;
262
290
  }
263
291
  });
264
292
  return tabLink;
265
293
  }
266
294
 
267
- static displayFormMatching(formMatching, importColumns, header, hasHeader) {
268
- let options = '<option value="-1">'+selectDefaultOptionLabel+'</option>';
269
- $.each(header, function (index, value) {
270
- options += '<option value="'+(hasHeader?value:index)+'">' + value + '</option>';
295
+ static displayFormMatching(formMatching, importColumns, header, hasHeader, selectDefaultOptionLabel = null) {
296
+ selectDefaultOptionLabel = selectDefaultOptionLabel ?? ImportFromCsv._defaults.selectDefaultOptionLabel;
297
+ let options = '<option value="-1">' + selectDefaultOptionLabel + '</option>';
298
+ header.forEach((value, index) => {
299
+ options += '<option value="' + (hasHeader ? value : index) + '">' + value + '</option>';
271
300
  });
272
301
 
273
- let selectContent = formMatching.find('.import_matching_select_content').addClass('row').empty();
274
- $.each(importColumns, function (key, label) {
275
- let selectFormGroup = $(
302
+ const selectContent = formMatching.querySelector('.import_matching_select_content');
303
+ selectContent.classList.add('row');
304
+ selectContent.innerHTML = '';
305
+
306
+ Object.entries(importColumns).forEach(([key, label]) => {
307
+ const tempDiv = document.createElement('div');
308
+ tempDiv.innerHTML =
276
309
  '<div class="form-group col-md-3">' +
277
- '<label for="form_import_'+key+'">'+label+'</label>' +
278
- '<select class="form-control" name="'+key+'" id="form_import_'+key+'">'+options+'</select>' +
279
- '</div>'
280
- );
281
- selectFormGroup.find('select option:contains('+label+')').prop('selected', true);
282
- selectContent.append(selectFormGroup);
310
+ '<label for="form_import_' + key + '">' + label + '</label>' +
311
+ '<select class="form-control" name="' + key + '" id="form_import_' + key + '">' + options + '</select>' +
312
+ '</div>';
313
+ const selectFormGroup = tempDiv.firstElementChild;
314
+ for (const option of selectFormGroup.querySelector('select').options) {
315
+ if (option.textContent.trim() === label) {
316
+ option.selected = true;
317
+ break;
318
+ }
319
+ }
320
+ selectContent.appendChild(selectFormGroup);
283
321
  });
284
322
 
285
- formMatching.find('div.errors').addClass('hide');
286
- formMatching.removeClass('hide');
323
+ formMatching.querySelector('div.errors')?.classList.add('hide');
324
+ formMatching.classList.remove('hide');
287
325
  }
288
326
 
289
327
  }
@@ -1,12 +1,13 @@
1
- const { Str } = require('./string');
1
+ require('./string');
2
+ const { FlashMessage } = require('./flash_message');
2
3
 
3
4
  class MultiFilesInput {
4
5
  static init(fileInput, setFilesList, nbMaxFiles, maxFileSize) {
5
6
  let filesList = [];
6
7
  const formGroup = fileInput.closest('.form-group');
7
8
 
8
- if (formGroup.find('.multi_files_input_dropzone').length === 0) {
9
- fileInput.after(`
9
+ if (!formGroup.querySelector('.multi_files_input_dropzone')) {
10
+ fileInput.insertAdjacentHTML('afterend', `
10
11
  <div class="multi_files_input_dropzone border rounded p-3 text-center" style="background:#fafafa; cursor: pointer;">
11
12
  <i class="fas fa-cloud-upload-alt fa-2x text-muted mb-1"></i>
12
13
  <div class="small text-muted">Glissez-déposez vos fichiers ici ou cliquez pour sélectionner</div>
@@ -14,43 +15,51 @@ class MultiFilesInput {
14
15
  `);
15
16
  }
16
17
 
17
- if (formGroup.find('.multi_files_input_files_preview').length === 0) {
18
- formGroup.append(`
18
+ if (!formGroup.querySelector('.multi_files_input_files_preview')) {
19
+ formGroup.insertAdjacentHTML('beforeend', `
19
20
  <div class="multi_files_input_files_preview mt-2 d-flex flex-wrap gap-2 hide"></div>
20
21
  `);
21
22
  }
22
23
 
23
- const dropzone = fileInput.parent().find('.multi_files_input_dropzone');
24
- const filesPreview = fileInput.parent().find('.multi_files_input_files_preview').empty();
24
+ const dropzone = fileInput.parentElement.querySelector('.multi_files_input_dropzone');
25
+ const filesPreview = fileInput.parentElement.querySelector('.multi_files_input_files_preview');
26
+ filesPreview.innerHTML = '';
25
27
 
26
- fileInput.addClass('hide');
28
+ fileInput.classList.add('hide');
27
29
 
28
30
  // Dropzone interactions
29
- dropzone.off('click').on('click', function (e) {
31
+ const dropzoneClone = dropzone.cloneNode(true);
32
+ dropzone.parentElement.replaceChild(dropzoneClone, dropzone);
33
+ const activeDropzone = dropzoneClone;
34
+
35
+ activeDropzone.addEventListener('click', (e) => {
30
36
  e.preventDefault();
31
37
  e.stopPropagation();
32
- fileInput.trigger('click');
38
+ fileInput.click();
33
39
  });
34
- dropzone.off('dragover').on('dragover', function (e) {
40
+ activeDropzone.addEventListener('dragover', (e) => {
35
41
  e.preventDefault();
36
42
  e.stopPropagation();
37
- $(this).addClass('border-primary');
43
+ activeDropzone.classList.add('border-primary');
38
44
  });
39
- dropzone.off('dragleave').on('dragleave', function (e) {
45
+ activeDropzone.addEventListener('dragleave', (e) => {
40
46
  e.preventDefault();
41
47
  e.stopPropagation();
42
- $(this).removeClass('border-primary');
48
+ activeDropzone.classList.remove('border-primary');
43
49
  });
44
- dropzone.off('drop').on('drop', function (e) {
50
+ activeDropzone.addEventListener('drop', (e) => {
45
51
  e.preventDefault();
46
52
  e.stopPropagation();
47
- $(this).removeClass('border-primary');
48
- const dtFiles = (e.originalEvent.dataTransfer || {}).files || [];
53
+ activeDropzone.classList.remove('border-primary');
54
+ const dtFiles = (e.dataTransfer || {}).files || [];
49
55
  handleFiles(Array.from(dtFiles));
50
56
  });
51
- fileInput.off('change').on('change', (e) => {
57
+
58
+ const fileInputClone = fileInput.cloneNode(true);
59
+ fileInput.parentElement.replaceChild(fileInputClone, fileInput);
60
+ fileInputClone.addEventListener('change', (e) => {
52
61
  handleFiles(Array.from(e.target.files));
53
- fileInput.val('');
62
+ fileInputClone.value = '';
54
63
  });
55
64
 
56
65
  function handleFiles(selected) {
@@ -71,37 +80,37 @@ class MultiFilesInput {
71
80
 
72
81
  function renderPreview(file) {
73
82
  const id = 'f_' + Math.random().toString(36).slice(2, 9);
74
- const wrap = $(`
75
- <div class="border rounded p-2 d-inline-flex align-items-center" data-file-id="${id}" style="background:white;">
76
- <div class="me-2 preview-thumb" style="width:64px; height:48px; display:flex; align-items:center; justify-content:center; overflow:hidden;"></div>
77
- <div class="small text-truncate" style="max-width:160px;">${(file.name || '').escapeHtml()}</div>
78
- <button type="button" class="btn-close btn-close-small ms-2" aria-label="Supprimer" style="margin-left:8px;"></button>
79
- </div>
80
- `);
81
- filesPreview.append(wrap);
82
- filesPreview.removeClass('hide');
83
+ const wrap = document.createElement('div');
84
+ wrap.className = 'border rounded p-2 d-inline-flex align-items-center';
85
+ wrap.dataset.fileId = id;
86
+ wrap.style.background = 'white';
87
+ wrap.innerHTML = `
88
+ <div class="me-2 preview-thumb" style="width:64px; height:48px; display:flex; align-items:center; justify-content:center; overflow:hidden;"></div>
89
+ <div class="small text-truncate" style="max-width:160px;">${(file.name || '').escapeHtml()}</div>
90
+ <button type="button" class="btn-close btn-close-small ms-2" aria-label="Supprimer" style="margin-left:8px;"></button>
91
+ `;
92
+ filesPreview.appendChild(wrap);
93
+ filesPreview.classList.remove('hide');
83
94
 
84
95
  // thumbnail for images
85
96
  if (file.type.startsWith('image/')) {
86
97
  const reader = new FileReader();
87
98
  reader.onload = function (e) {
88
- wrap.find('.preview-thumb').html(`<img src="${e.target.result}" alt="" style="max-width:100%; max-height:100%;" />`);
99
+ wrap.querySelector('.preview-thumb').innerHTML = `<img src="${e.target.result}" alt="" style="max-width:100%; max-height:100%;" />`;
89
100
  };
90
101
  reader.readAsDataURL(file);
91
102
  } else {
92
- wrap.find('.preview-thumb').html(`<i class="fas fa-file fa-2x text-muted"></i>`);
103
+ wrap.querySelector('.preview-thumb').innerHTML = `<i class="fas fa-file fa-2x text-muted"></i>`;
93
104
  }
94
105
 
95
- wrap.find('.btn-close').off('click').on('click', function () {
96
- const idx = $(this).closest('[data-file-id]').index();
97
- // remove by reference: find corresponding file by name+size (best-effort)
106
+ wrap.querySelector('.btn-close').addEventListener('click', () => {
98
107
  const name = file.name, size = file.size;
99
108
  filesList = filesList.filter(f => !(f.name === name && f.size === size));
100
109
  setFilesList(filesList);
101
- $(this).closest('[data-file-id]').remove();
110
+ wrap.remove();
102
111
 
103
112
  if (filesList.length === 0) {
104
- filesPreview.addClass('hide');
113
+ filesPreview.classList.add('hide');
105
114
  }
106
115
  });
107
116
  }