@osimatic/helpers-js 1.5.3 → 1.5.5
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/details_sub_array.js +45 -41
- package/form_helper.js +283 -232
- package/google_charts.js +154 -144
- package/google_maps.js +1 -1
- package/import_from_csv.js +166 -157
- package/multi_files_input.js +42 -34
- package/multiple_action_in_table.js +116 -105
- package/package.json +1 -1
- package/paging.js +103 -84
- package/select_all.js +65 -70
- package/sortable_list.js +12 -13
- package/tests/details_sub_array.test.js +211 -239
- package/tests/form_helper.test.js +553 -673
- package/tests/google_charts.test.js +338 -339
- package/tests/google_maps.test.js +3 -15
- package/tests/import_from_csv.test.js +391 -652
- package/tests/multi_files_input.test.js +292 -722
- package/tests/multiple_action_in_table.test.js +458 -417
- package/tests/paging.test.js +344 -475
- package/tests/select_all.test.js +232 -318
- package/tests/sortable_list.test.js +176 -500
- package/tests/user.test.js +163 -54
- package/user.js +35 -38
package/import_from_csv.js
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
39
|
-
formUpload.
|
|
40
|
-
formUpload.
|
|
44
|
+
formMatching.classList.add('hide');
|
|
45
|
+
formUpload.classList.remove('hide');
|
|
46
|
+
formUpload.querySelector('div.errors')?.classList.add('hide');
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
if (
|
|
44
|
-
div.
|
|
49
|
+
if (specificDescDiv != null) {
|
|
50
|
+
div.querySelector('.specific_desc')?.append(specificDescDiv);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
if (
|
|
48
|
-
div.
|
|
53
|
+
if (additionalFormField != null) {
|
|
54
|
+
div.querySelector('.import_matching_select_content')?.insertAdjacentHTML('afterend', additionalFormField);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
formUpload.
|
|
57
|
+
const submitUploadBtn = formUpload.querySelector('button[type="submit"]');
|
|
58
|
+
submitUploadBtn.addEventListener('click', function(event) {
|
|
52
59
|
event.preventDefault();
|
|
53
|
-
FormHelper.buttonLoader(
|
|
54
|
-
formUpload.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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 (
|
|
96
|
-
|
|
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
|
-
|
|
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.
|
|
108
|
+
const submitMatchingBtn = formMatching.querySelector('button[type="submit"]');
|
|
109
|
+
submitMatchingBtn.addEventListener('click', function(event) {
|
|
105
110
|
event.preventDefault();
|
|
106
|
-
FormHelper.buttonLoader(
|
|
107
|
-
formMatching.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
FormHelper.buttonLoader(
|
|
116
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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.
|
|
138
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
154
|
-
|
|
157
|
+
const lineData = { line: (index + 1) };
|
|
158
|
+
Object.entries(tabLink).forEach(([key, listeImportIndex]) => {
|
|
155
159
|
if (listeImportIndex != -1) {
|
|
156
|
-
|
|
157
|
-
if (td
|
|
158
|
-
lineData[key] = td.
|
|
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.
|
|
170
|
-
if (table
|
|
171
|
-
divResult.
|
|
172
|
-
table = divResult.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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.
|
|
203
|
+
table.innerHTML = tableContent;
|
|
201
204
|
|
|
202
|
-
table.
|
|
203
|
-
ImportFromCsv.initEditLink(formMatching,
|
|
205
|
+
table.querySelectorAll('td.edit_line_button').forEach(el => {
|
|
206
|
+
ImportFromCsv.initEditLink(formMatching, el);
|
|
204
207
|
});
|
|
205
208
|
|
|
206
|
-
divResult.
|
|
209
|
+
divResult.classList.remove('hide');
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
static initValidateLine(formMatching, td) {
|
|
210
|
-
td.
|
|
211
|
-
td.
|
|
212
|
-
|
|
213
|
-
tr.
|
|
214
|
-
|
|
215
|
-
if (
|
|
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
|
-
|
|
221
|
+
const input = cell.querySelector('input');
|
|
222
|
+
cell.innerHTML = input ? input.value : '';
|
|
219
223
|
});
|
|
220
224
|
|
|
221
|
-
if (!td.closest('table').
|
|
222
|
-
formMatching.
|
|
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.
|
|
231
|
-
td.
|
|
232
|
-
|
|
233
|
-
tr.
|
|
234
|
-
|
|
235
|
-
if (
|
|
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
|
-
|
|
239
|
-
|
|
241
|
+
cell.dataset.original_value = cell.innerHTML;
|
|
242
|
+
cell.innerHTML = '<input type="text" class="form-control" value="' + cell.innerHTML.replace(/"/g, '"') + '" />';
|
|
240
243
|
});
|
|
241
|
-
formMatching.
|
|
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
|
-
|
|
254
|
+
json.forEach((errorData) => {
|
|
253
255
|
console.error(errorData);
|
|
254
256
|
if (null != divResult) {
|
|
255
|
-
divResult.
|
|
257
|
+
divResult.querySelector('table tr[data-line="' + errorData.line + '"]')?.classList.add('danger');
|
|
256
258
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
285
|
-
formMatching.
|
|
286
|
-
|
|
285
|
+
const tabLink = {};
|
|
286
|
+
formMatching.querySelectorAll('select').forEach((select) => {
|
|
287
|
+
const listeImportIndex = select.value;
|
|
287
288
|
if (listeImportIndex != -1) {
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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.
|
|
311
|
-
|
|
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.
|
|
315
|
-
formMatching.
|
|
323
|
+
formMatching.querySelector('div.errors')?.classList.add('hide');
|
|
324
|
+
formMatching.classList.remove('hide');
|
|
316
325
|
}
|
|
317
326
|
|
|
318
327
|
}
|
package/multi_files_input.js
CHANGED
|
@@ -6,8 +6,8 @@ class MultiFilesInput {
|
|
|
6
6
|
let filesList = [];
|
|
7
7
|
const formGroup = fileInput.closest('.form-group');
|
|
8
8
|
|
|
9
|
-
if (formGroup.
|
|
10
|
-
fileInput.
|
|
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.
|
|
19
|
-
formGroup.
|
|
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.
|
|
25
|
-
const filesPreview = fileInput.
|
|
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.
|
|
28
|
+
fileInput.classList.add('hide');
|
|
28
29
|
|
|
29
30
|
// Dropzone interactions
|
|
30
|
-
dropzone.
|
|
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.
|
|
38
|
+
fileInput.click();
|
|
34
39
|
});
|
|
35
|
-
|
|
40
|
+
activeDropzone.addEventListener('dragover', (e) => {
|
|
36
41
|
e.preventDefault();
|
|
37
42
|
e.stopPropagation();
|
|
38
|
-
|
|
43
|
+
activeDropzone.classList.add('border-primary');
|
|
39
44
|
});
|
|
40
|
-
|
|
45
|
+
activeDropzone.addEventListener('dragleave', (e) => {
|
|
41
46
|
e.preventDefault();
|
|
42
47
|
e.stopPropagation();
|
|
43
|
-
|
|
48
|
+
activeDropzone.classList.remove('border-primary');
|
|
44
49
|
});
|
|
45
|
-
|
|
50
|
+
activeDropzone.addEventListener('drop', (e) => {
|
|
46
51
|
e.preventDefault();
|
|
47
52
|
e.stopPropagation();
|
|
48
|
-
|
|
49
|
-
const dtFiles = (e.
|
|
53
|
+
activeDropzone.classList.remove('border-primary');
|
|
54
|
+
const dtFiles = (e.dataTransfer || {}).files || [];
|
|
50
55
|
handleFiles(Array.from(dtFiles));
|
|
51
56
|
});
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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.
|
|
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.
|
|
103
|
+
wrap.querySelector('.preview-thumb').innerHTML = `<i class="fas fa-file fa-2x text-muted"></i>`;
|
|
94
104
|
}
|
|
95
105
|
|
|
96
|
-
wrap.
|
|
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
|
-
|
|
110
|
+
wrap.remove();
|
|
103
111
|
|
|
104
112
|
if (filesList.length === 0) {
|
|
105
|
-
filesPreview.
|
|
113
|
+
filesPreview.classList.add('hide');
|
|
106
114
|
}
|
|
107
115
|
});
|
|
108
116
|
}
|