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