@liedekef/ftable 1.1.45 → 1.1.47

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.
Files changed (40) hide show
  1. package/ftable.esm.js +253 -215
  2. package/ftable.js +253 -215
  3. package/ftable.min.js +2 -2
  4. package/ftable.umd.js +253 -215
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +1 -1
  7. package/themes/basic/ftable_basic.min.css +1 -1
  8. package/themes/ftable_theme_base.less +1 -2
  9. package/themes/lightcolor/blue/ftable.css +2 -3
  10. package/themes/lightcolor/blue/ftable.min.css +1 -1
  11. package/themes/lightcolor/ftable_lightcolor_base.less +1 -2
  12. package/themes/lightcolor/gray/ftable.css +2 -3
  13. package/themes/lightcolor/gray/ftable.min.css +1 -1
  14. package/themes/lightcolor/green/ftable.css +2 -3
  15. package/themes/lightcolor/green/ftable.min.css +1 -1
  16. package/themes/lightcolor/orange/ftable.css +2 -3
  17. package/themes/lightcolor/orange/ftable.min.css +1 -1
  18. package/themes/lightcolor/red/ftable.css +2 -3
  19. package/themes/lightcolor/red/ftable.min.css +1 -1
  20. package/themes/metro/blue/ftable.css +1 -1
  21. package/themes/metro/blue/ftable.min.css +1 -1
  22. package/themes/metro/brown/ftable.css +1 -1
  23. package/themes/metro/brown/ftable.min.css +1 -1
  24. package/themes/metro/crimson/ftable.css +1 -1
  25. package/themes/metro/crimson/ftable.min.css +1 -1
  26. package/themes/metro/darkgray/ftable.css +1 -1
  27. package/themes/metro/darkgray/ftable.min.css +1 -1
  28. package/themes/metro/darkorange/ftable.css +1 -1
  29. package/themes/metro/darkorange/ftable.min.css +1 -1
  30. package/themes/metro/green/ftable.css +1 -1
  31. package/themes/metro/green/ftable.min.css +1 -1
  32. package/themes/metro/lightgray/ftable.css +1 -1
  33. package/themes/metro/lightgray/ftable.min.css +1 -1
  34. package/themes/metro/pink/ftable.css +1 -1
  35. package/themes/metro/pink/ftable.min.css +1 -1
  36. package/themes/metro/purple/ftable.css +1 -1
  37. package/themes/metro/purple/ftable.min.css +1 -1
  38. package/themes/metro/red/ftable.css +1 -1
  39. package/themes/metro/red/ftable.min.css +1 -1
  40. package/themes/lightcolor/bg-thead.png +0 -0
package/ftable.js CHANGED
@@ -30,7 +30,7 @@
30
30
  sortingInfoNone: 'No sorting applied',
31
31
  resetSorting: 'Reset sorting',
32
32
  csvExport: 'CSV',
33
- printTable: '🖨️ Print',
33
+ printTable: '🖨️ Print',
34
34
  cloneRecord: 'Clone Record',
35
35
  resetTable: 'Reset table',
36
36
  resetTableConfirm: 'This will reset all columns, pagesize, sorting to their defaults. Do you want to continue?',
@@ -187,36 +187,45 @@ class FTableLogger {
187
187
  }
188
188
 
189
189
  class FTableDOMHelper {
190
+ static PROPERTY_ATTRIBUTES = new Set([
191
+ 'value', 'checked', 'selected', 'disabled', 'readOnly',
192
+ 'name', 'id', 'type', 'placeholder', 'min', 'max',
193
+ 'step', 'required', 'multiple', 'accept', 'className',
194
+ 'textContent', 'innerHTML', 'title'
195
+ ]);
196
+
190
197
  static create(tag, options = {}) {
191
198
  const element = document.createElement(tag);
192
199
 
193
- if (options.className) {
194
- element.className = options.className;
200
+ // Handle special cases first
201
+ if (options.style !== undefined) {
202
+ element.style.cssText = options.style;
195
203
  }
204
+
205
+ FTableDOMHelper.PROPERTY_ATTRIBUTES.forEach(prop => {
206
+ if (prop in options && options[prop] !== null) {
207
+ element[prop] = options[prop];
208
+ }
209
+ });
196
210
 
197
- if (options.style) {
198
- element.style.cssText = options.style;
211
+ if (options.parent !== undefined) {
212
+ options.parent.appendChild(element);
199
213
  }
200
214
 
215
+ // the attributes last, so we can override stuff if needed
201
216
  if (options.attributes) {
202
217
  Object.entries(options.attributes).forEach(([key, value]) => {
203
- if (value !== null)
204
- element.setAttribute(key, value);
218
+ if (value !== null) {
219
+ // Use property if it exists on the element, otherwise use setAttribute
220
+ if (FTableDOMHelper.PROPERTY_ATTRIBUTES.has(key)) {
221
+ element[key] = value;
222
+ } else {
223
+ element.setAttribute(key, value);
224
+ }
225
+ }
205
226
  });
206
227
  }
207
228
 
208
- if (options.text) {
209
- element.textContent = options.text;
210
- }
211
-
212
- if (options.html) {
213
- element.innerHTML = options.html;
214
- }
215
-
216
- if (options.parent) {
217
- options.parent.appendChild(element);
218
- }
219
-
220
229
  return element;
221
230
  }
222
231
 
@@ -473,14 +482,14 @@ class FtableModal {
473
482
  // Header
474
483
  const header = FTableDOMHelper.create('h2', {
475
484
  className: 'ftable-modal-header',
476
- text: this.options.title,
485
+ textContent: this.options.title,
477
486
  parent: this.modal
478
487
  });
479
488
 
480
489
  // Close button
481
490
  const closeBtn = FTableDOMHelper.create('span', {
482
491
  className: 'ftable-modal-close',
483
- html: '×',
492
+ innerHTML: '×',
484
493
  parent: this.modal
485
494
  });
486
495
 
@@ -508,7 +517,7 @@ class FtableModal {
508
517
  this.options.buttons.forEach(button => {
509
518
  const btn = FTableDOMHelper.create('button', {
510
519
  className: `ftable-dialog-button ${button.className || ''}`,
511
- html: `<span>${button.text}</span>`,
520
+ innerHTML: `<span>${button.text}</span>`,
512
521
  parent: footer
513
522
  });
514
523
 
@@ -743,7 +752,7 @@ class FTableFormBuilder {
743
752
  // Label
744
753
  const label = FTableDOMHelper.create('div', {
745
754
  className: 'ftable-input-label',
746
- text: field.inputTitle || field.title,
755
+ textContent: field.inputTitle || field.title,
747
756
  parent: container
748
757
  });
749
758
  }
@@ -1065,10 +1074,10 @@ class FTableFormBuilder {
1065
1074
 
1066
1075
  let input;
1067
1076
 
1068
- if (value == undefined) {
1077
+ if (value === undefined) {
1069
1078
  value = null;
1070
1079
  }
1071
- if (value == null && field.defaultValue ) {
1080
+ if (value === null && field.defaultValue ) {
1072
1081
  value = field.defaultValue;
1073
1082
  }
1074
1083
  // Auto-detect select type if options are provided
@@ -1146,7 +1155,7 @@ class FTableFormBuilder {
1146
1155
  if (field.explain) {
1147
1156
  const explain = FTableDOMHelper.create('div', {
1148
1157
  className: 'ftable-field-explain',
1149
- html: `<small>${field.explain}</small>`,
1158
+ innerHTML: `<small>${field.explain}</small>`,
1150
1159
  parent: container
1151
1160
  });
1152
1161
  }
@@ -1162,21 +1171,15 @@ class FTableFormBuilder {
1162
1171
  const container = document.createElement('div');
1163
1172
  // Create hidden input
1164
1173
  const hiddenInput = FTableDOMHelper.create('input', {
1165
- attributes: {
1166
- id: 'real-' + fieldName,
1167
- type: 'hidden',
1168
- value: value,
1169
- name: fieldName
1170
- }
1174
+ id: 'real-' + fieldName,
1175
+ type: 'hidden',
1176
+ value: value,
1177
+ name: fieldName
1171
1178
  });
1172
1179
 
1173
1180
  // Create visible input
1174
1181
  const attributes = {
1175
- id: `Edit-${fieldName}`,
1176
- type: 'text',
1177
- 'data-date': value,
1178
- placeholder: field.placeholder || null,
1179
- readOnly: true
1182
+ 'data-date': value
1180
1183
  };
1181
1184
  // Set any additional attributes
1182
1185
  if (field.inputAttributes) {
@@ -1184,9 +1187,13 @@ class FTableFormBuilder {
1184
1187
  Object.assign(attributes, parsed);
1185
1188
  }
1186
1189
 
1187
- const visibleInput = FTableDOMHelper.create('input', {
1190
+ const visibleInput = FTableDOMHelper.create('input', {
1191
+ attributes: attributes,
1192
+ id: `Edit-${fieldName}`,
1193
+ type: 'text',
1194
+ placeholder: field.placeholder || null,
1188
1195
  className: field.inputClass || 'datepicker-input',
1189
- attributes: attributes
1196
+ readOnly: true
1190
1197
  });
1191
1198
 
1192
1199
  // Append both inputs
@@ -1226,12 +1233,7 @@ class FTableFormBuilder {
1226
1233
 
1227
1234
  createTypedInput(fieldName, field, value) {
1228
1235
  const inputType = field.type || 'text';
1229
- const attributes = {
1230
- type: inputType,
1231
- id: `Edit-${fieldName}`,
1232
- placeholder: field.placeholder || null,
1233
- value: value
1234
- };
1236
+ const attributes = { };
1235
1237
 
1236
1238
  // extra check for name and multiple
1237
1239
  let name = fieldName;
@@ -1248,11 +1250,15 @@ class FTableFormBuilder {
1248
1250
  name = `${fieldName}[]`;
1249
1251
  }
1250
1252
  }
1251
- attributes.name = name;
1252
1253
 
1253
- const input = FTableDOMHelper.create('input', {
1254
+ const input = FTableDOMHelper.create('input', {
1255
+ attributes: attributes,
1256
+ type: inputType,
1257
+ id: `Edit-${fieldName}`,
1254
1258
  className: field.inputClass || null,
1255
- attributes: attributes
1259
+ placeholder: field.placeholder || null,
1260
+ value: value,
1261
+ name: name
1256
1262
  });
1257
1263
 
1258
1264
  // Prevent form submit on Enter, trigger change instead
@@ -1270,11 +1276,6 @@ class FTableFormBuilder {
1270
1276
 
1271
1277
  createDatalistInput(fieldName, field, value) {
1272
1278
  const attributes = {
1273
- type: 'text',
1274
- name: fieldName,
1275
- id: `Edit-${fieldName}`,
1276
- placeholder: field.placeholder || null,
1277
- value: value,
1278
1279
  list: `${fieldName}-datalist`
1279
1280
  };
1280
1281
 
@@ -1285,15 +1286,18 @@ class FTableFormBuilder {
1285
1286
  }
1286
1287
 
1287
1288
  const input = FTableDOMHelper.create('input', {
1289
+ attributes: attributes,
1290
+ type: 'search',
1291
+ name: fieldName,
1292
+ id: `Edit-${fieldName}`,
1288
1293
  className: field.inputClass || null,
1289
- attributes: attributes
1294
+ placeholder: field.placeholder || null,
1295
+ value: value
1290
1296
  });
1291
1297
 
1292
1298
  // Create the datalist element
1293
1299
  const datalist = FTableDOMHelper.create('datalist', {
1294
- attributes: {
1295
- id: `${fieldName}-datalist`
1296
- }
1300
+ id: `${fieldName}-datalist`
1297
1301
  });
1298
1302
 
1299
1303
  // Populate datalist options
@@ -1313,18 +1317,16 @@ class FTableFormBuilder {
1313
1317
  if (Array.isArray(options)) {
1314
1318
  options.forEach(option => {
1315
1319
  FTableDOMHelper.create('option', {
1316
- attributes: {
1317
- value: option.Value || option.value || option
1318
- },
1319
- text: option.DisplayText || option.text || option,
1320
+ value: option.Value || option.value || option,
1321
+ textContent: option.DisplayText || option.text || option,
1320
1322
  parent: datalist
1321
1323
  });
1322
1324
  });
1323
1325
  } else if (typeof options === 'object') {
1324
1326
  Object.entries(options).forEach(([key, text]) => {
1325
1327
  FTableDOMHelper.create('option', {
1326
- attributes: { value: key },
1327
- text: text,
1328
+ value: key,
1329
+ textContent: text,
1328
1330
  parent: datalist
1329
1331
  });
1330
1332
  });
@@ -1332,12 +1334,7 @@ class FTableFormBuilder {
1332
1334
  }
1333
1335
 
1334
1336
  createHiddenInput(fieldName, field, value) {
1335
- const attributes = {
1336
- type: 'hidden',
1337
- name: fieldName,
1338
- id: `Edit-${fieldName}`,
1339
- value: value
1340
- };
1337
+ const attributes = { };
1341
1338
 
1342
1339
  // Apply inputAttributes
1343
1340
  if (field.inputAttributes) {
@@ -1345,15 +1342,17 @@ class FTableFormBuilder {
1345
1342
  Object.assign(attributes, parsed);
1346
1343
  }
1347
1344
 
1348
- return FTableDOMHelper.create('input', { attributes });
1345
+ return FTableDOMHelper.create('input', {
1346
+ attributes: attributes,
1347
+ type: 'hidden',
1348
+ name: fieldName,
1349
+ id: `Edit-${fieldName}`,
1350
+ value: value
1351
+ });
1349
1352
  }
1350
1353
 
1351
1354
  createTextarea(fieldName, field, value) {
1352
- const attributes = {
1353
- name: fieldName,
1354
- id: `Edit-${fieldName}`,
1355
- placeholder: field.placeholder || null
1356
- };
1355
+ const attributes = { };
1357
1356
 
1358
1357
  // Apply inputAttributes
1359
1358
  if (field.inputAttributes) {
@@ -1361,19 +1360,18 @@ class FTableFormBuilder {
1361
1360
  Object.assign(attributes, parsed);
1362
1361
  }
1363
1362
 
1364
- const textarea = FTableDOMHelper.create('textarea', {
1365
- className: field.inputClass || null,
1363
+ return FTableDOMHelper.create('textarea', {
1366
1364
  attributes: attributes,
1367
- value: value
1365
+ name: fieldName,
1366
+ id: `Edit-${fieldName}`,
1367
+ className: field.inputClass || null,
1368
+ placeholder: field.placeholder || null,
1369
+ value = value
1368
1370
  });
1369
- return textarea;
1370
1371
  }
1371
1372
 
1372
1373
  createSelect(fieldName, field, value) {
1373
- const attributes = {
1374
- name: fieldName,
1375
- id: `Edit-${fieldName}`,
1376
- };
1374
+ const attributes = { };
1377
1375
 
1378
1376
  // extra check for name and multiple
1379
1377
  let name = fieldName;
@@ -1393,8 +1391,10 @@ class FTableFormBuilder {
1393
1391
  attributes.name = name;
1394
1392
 
1395
1393
  const select = FTableDOMHelper.create('select', {
1396
- className: field.inputClass || null,
1397
- attributes: attributes
1394
+ attributes: attributes,
1395
+ name: fieldName,
1396
+ id: `Edit-${fieldName}`,
1397
+ className: field.inputClass || null
1398
1398
  });
1399
1399
 
1400
1400
  if (field.options) {
@@ -1420,15 +1420,7 @@ class FTableFormBuilder {
1420
1420
  });
1421
1421
 
1422
1422
  const radioId = `${fieldName}_${index}`;
1423
- const radioAttributes = {
1424
- type: 'radio',
1425
- name: fieldName,
1426
- id: radioId,
1427
- value: option.Value || option.value || option
1428
- };
1429
-
1430
- if (field.required && index === 0) radioAttributes.required = 'required';
1431
- if (field.disabled) radioAttributes.disabled = 'disabled';
1423
+ const radioAttributes = { };
1432
1424
 
1433
1425
  // Apply inputAttributes
1434
1426
  if (field.inputAttributes) {
@@ -1436,19 +1428,24 @@ class FTableFormBuilder {
1436
1428
  Object.assign(radioAttributes, parsed);
1437
1429
  }
1438
1430
 
1431
+ const fieldValue = option.Value !== undefined ? option.Value :
1432
+ option.value !== undefined ? option.value :
1433
+ option; // fallback for string
1434
+
1439
1435
  const radio = FTableDOMHelper.create('input', {
1440
1436
  attributes: radioAttributes,
1437
+ type: 'radio',
1438
+ name: fieldName,
1439
+ id: radioId,
1440
+ value: fieldValue,
1441
1441
  className: field.inputClass || null,
1442
+ checked: fieldValue == value,
1442
1443
  parent: radioWrapper
1443
1444
  });
1444
1445
 
1445
- if (radioAttributes.value === value) {
1446
- radio.checked = true;
1447
- }
1448
-
1449
1446
  const label = FTableDOMHelper.create('label', {
1450
1447
  attributes: { for: radioId },
1451
- text: option.DisplayText || option.text || option,
1448
+ textContent: option.DisplayText || option.text || option,
1452
1449
  parent: radioWrapper
1453
1450
  });
1454
1451
  });
@@ -1476,12 +1473,10 @@ class FTableFormBuilder {
1476
1473
  // Create the checkbox
1477
1474
  const checkbox = FTableDOMHelper.create('input', {
1478
1475
  className: ['ftable-yesno-check-input', field.inputClass || ''].filter(Boolean).join(' '),
1479
- attributes: {
1480
- type: 'checkbox',
1481
- name: fieldName,
1482
- id: `Edit-${fieldName}`,
1483
- value: '1'
1484
- },
1476
+ type: 'checkbox',
1477
+ name: fieldName,
1478
+ id: `Edit-${fieldName}`,
1479
+ value: '1',
1485
1480
  parent: wrapper
1486
1481
  });
1487
1482
  checkbox.checked = isChecked;
@@ -1493,7 +1488,7 @@ class FTableFormBuilder {
1493
1488
  attributes: {
1494
1489
  for: `Edit-${fieldName}`,
1495
1490
  },
1496
- text: field.label,
1491
+ textContent: field.label,
1497
1492
  parent: wrapper
1498
1493
  });
1499
1494
  } else {
@@ -1520,8 +1515,9 @@ class FTableFormBuilder {
1520
1515
  option.value !== undefined ? option.value :
1521
1516
  option; // fallback for string
1522
1517
  const optionElement = FTableDOMHelper.create('option', {
1523
- attributes: { value: value },
1524
- text: option.DisplayText || option.text || option,
1518
+ value: value,
1519
+ textContent: option.DisplayText || option.text || option,
1520
+ selected: value == selectedValue,
1525
1521
  parent: select
1526
1522
  });
1527
1523
 
@@ -1531,30 +1527,21 @@ class FTableFormBuilder {
1531
1527
  });
1532
1528
  }
1533
1529
 
1534
- if (optionElement.value == selectedValue) {
1535
- optionElement.selected = true;
1536
- }
1537
1530
  });
1538
1531
  } else if (typeof options === 'object') {
1539
1532
  Object.entries(options).forEach(([key, text]) => {
1540
1533
  const optionElement = FTableDOMHelper.create('option', {
1541
- attributes: { value: key },
1542
- text: text,
1534
+ value: key,
1535
+ textContent: text,
1536
+ selected: key == selectedValue,
1543
1537
  parent: select
1544
1538
  });
1545
-
1546
- if (key == selectedValue) {
1547
- optionElement.selected = true;
1548
- }
1549
1539
  });
1550
1540
  }
1551
1541
  }
1552
1542
 
1553
1543
  createFileInput(fieldName, field, value) {
1554
- const attributes = {
1555
- type: 'file',
1556
- id: `Edit-${fieldName}`,
1557
- };
1544
+ const attributes = { };
1558
1545
 
1559
1546
  // extra check for name and multiple
1560
1547
  let name = fieldName;
@@ -1570,9 +1557,11 @@ class FTableFormBuilder {
1570
1557
  name = `${fieldName}[]`;
1571
1558
  }
1572
1559
  }
1573
- attributes.name = name;
1574
1560
 
1575
1561
  return FTableDOMHelper.create('input', {
1562
+ type: 'file',
1563
+ id: `Edit-${fieldName}`,
1564
+ name: name,
1576
1565
  className: field.inputClass || null,
1577
1566
  attributes: attributes
1578
1567
  });
@@ -1898,7 +1887,7 @@ class FTable extends FTableEventEmitter {
1898
1887
  });
1899
1888
 
1900
1889
  FTableDOMHelper.create('span', {
1901
- text: this.options.messages.pageSizeChangeLabel,
1890
+ textContent: this.options.messages.pageSizeChangeLabel,
1902
1891
  parent: container
1903
1892
  });
1904
1893
 
@@ -1911,7 +1900,7 @@ class FTable extends FTableEventEmitter {
1911
1900
  pageSizes.forEach(size => {
1912
1901
  const option = FTableDOMHelper.create('option', {
1913
1902
  attributes: { value: size },
1914
- text: size.toString(),
1903
+ textContent: size.toString(),
1915
1904
  parent: select
1916
1905
  });
1917
1906
 
@@ -2022,7 +2011,7 @@ class FTable extends FTableEventEmitter {
2022
2011
 
2023
2012
  FTableDOMHelper.create('div', {
2024
2013
  className: 'ftable-title-text',
2025
- html: this.options.title,
2014
+ innerHTML: this.options.title,
2026
2015
  parent: this.elements.titleDiv
2027
2016
  });
2028
2017
  }
@@ -2108,7 +2097,7 @@ class FTable extends FTableEventEmitter {
2108
2097
 
2109
2098
  const textHeader = FTableDOMHelper.create('span', {
2110
2099
  className: 'ftable-column-header-text',
2111
- html: field.title || fieldName,
2100
+ innerHTML: field.title || fieldName,
2112
2101
  parent: container
2113
2102
  });
2114
2103
 
@@ -2217,21 +2206,19 @@ class FTable extends FTableEventEmitter {
2217
2206
  // Create hidden input
2218
2207
  const hiddenInput = FTableDOMHelper.create('input', {
2219
2208
  className: 'ftable-toolbarsearch-extra',
2209
+ type: 'hidden',
2210
+ id: 'ftable-toolbarsearch-extra-' + fieldName,
2220
2211
  attributes: {
2221
- type: 'hidden',
2222
2212
  'data-field-name': fieldName,
2223
- id: 'ftable-toolbarsearch-extra-' + fieldName,
2224
2213
  }
2225
2214
  });
2226
2215
  // Create visible input
2227
2216
  const visibleInput = FTableDOMHelper.create('input', {
2228
2217
  className: 'ftable-toolbarsearch',
2229
- attributes: {
2230
- id: 'ftable-toolbarsearch-' + fieldName,
2231
- type: 'text',
2232
- placeholder: field.searchPlaceholder || field.placeholder || '',
2233
- readOnly: true
2234
- }
2218
+ id: 'ftable-toolbarsearch-' + fieldName,
2219
+ type: 'text',
2220
+ placeholder: field.searchPlaceholder || field.placeholder || '',
2221
+ readOnly: true
2235
2222
  });
2236
2223
  // Append both inputs
2237
2224
  containerDiv.appendChild(hiddenInput);
@@ -2246,7 +2233,8 @@ class FTable extends FTableEventEmitter {
2246
2233
  const picker = new FDatepicker(visibleInput, {
2247
2234
  format: dateFormat,
2248
2235
  altField: 'ftable-toolbarsearch-extra-' + fieldName,
2249
- altFormat: 'Y-m-d'
2236
+ altFormat: 'Y-m-d',
2237
+ autoClose: true
2250
2238
  });
2251
2239
  }, 0);
2252
2240
  break;
@@ -2267,10 +2255,10 @@ class FTable extends FTableEventEmitter {
2267
2255
  } else {
2268
2256
  input = FTableDOMHelper.create('input', {
2269
2257
  className: 'ftable-toolbarsearch',
2258
+ type: searchType,
2259
+ id: fieldSearchName,
2270
2260
  attributes: {
2271
- type: 'date',
2272
2261
  'data-field-name': fieldName,
2273
- id: fieldSearchName,
2274
2262
  }
2275
2263
  });
2276
2264
  }
@@ -2289,30 +2277,37 @@ class FTable extends FTableEventEmitter {
2289
2277
  } else {
2290
2278
  input = FTableDOMHelper.create('input', {
2291
2279
  className: 'ftable-toolbarsearch',
2280
+ type: 'text',
2281
+ id: fieldSearchName,
2282
+ placeholder: field.searchPlaceholder || field.placeholder || 'Search...',
2292
2283
  attributes: {
2293
- type: 'text',
2294
- 'data-field-name': fieldName,
2295
- id: fieldSearchName,
2296
- placeholder: field.searchPlaceholder || field.placeholder || 'Search...'
2284
+ 'data-field-name': fieldName
2297
2285
  }
2298
2286
  });
2299
2287
  }
2300
2288
  break;
2301
2289
 
2290
+ case 'datalist':
2291
+ input = await this.createDatalistForSearch(fieldName, field);
2292
+ break;
2293
+
2302
2294
  default:
2303
2295
  input = FTableDOMHelper.create('input', {
2304
2296
  className: 'ftable-toolbarsearch',
2297
+ type: 'text',
2298
+ id: fieldSearchName,
2299
+ placeholder: field.searchPlaceholder || field.placeholder || 'Search...',
2305
2300
  attributes: {
2306
- type: 'text',
2307
- 'data-field-name': fieldName,
2308
- id: fieldSearchName,
2309
- placeholder: field.searchPlaceholder || field.placeholder || 'Search...'
2301
+ 'data-field-name': fieldName
2310
2302
  }
2311
2303
  });
2312
2304
  }
2313
2305
 
2314
2306
  if (input) {
2315
2307
  container.appendChild(input);
2308
+ if (input.datalistElement && input.datalistElement instanceof Node) {
2309
+ container.appendChild(input.datalistElement);
2310
+ }
2316
2311
 
2317
2312
  if (input.tagName === 'SELECT') {
2318
2313
  input.addEventListener('change', (e) => {
@@ -2351,7 +2346,7 @@ class FTable extends FTableEventEmitter {
2351
2346
 
2352
2347
  const resetButton = FTableDOMHelper.create('button', {
2353
2348
  className: 'ftable-toolbarsearch-reset-button',
2354
- text: this.options.messages.resetSearch,
2349
+ textContent: this.options.messages.resetSearch,
2355
2350
  attributes : {
2356
2351
  id: 'ftable-toolbarsearch-reset-button'
2357
2352
  },
@@ -2364,7 +2359,6 @@ class FTable extends FTableEventEmitter {
2364
2359
  async createSelectForSearch(fieldName, field, isCheckboxValues) {
2365
2360
  const fieldSearchName = 'ftable-toolbarsearch-' + fieldName;
2366
2361
  const attributes = {
2367
- id: fieldSearchName,
2368
2362
  };
2369
2363
 
2370
2364
  // extra check for name and multiple
@@ -2387,6 +2381,7 @@ class FTable extends FTableEventEmitter {
2387
2381
 
2388
2382
  const select = FTableDOMHelper.create('select', {
2389
2383
  attributes: attributes,
2384
+ id: fieldSearchName,
2390
2385
  className: 'ftable-toolbarsearch'
2391
2386
  });
2392
2387
 
@@ -2409,8 +2404,8 @@ class FTable extends FTableEventEmitter {
2409
2404
 
2410
2405
  if (!hasEmptyFirst) {
2411
2406
  FTableDOMHelper.create('option', {
2412
- attributes: { value: '' },
2413
- text: '',
2407
+ value: '',
2408
+ innerHTML: '&nbsp;',
2414
2409
  parent: select
2415
2410
  });
2416
2411
  }
@@ -2418,18 +2413,16 @@ class FTable extends FTableEventEmitter {
2418
2413
  if (optionsSource && Array.isArray(optionsSource)) {
2419
2414
  optionsSource.forEach(option => {
2420
2415
  const optionElement = FTableDOMHelper.create('option', {
2421
- attributes: {
2422
- value: option.Value !== undefined ? option.Value : option.value !== undefined ? option.value : option
2423
- },
2424
- text: option.DisplayText || option.text || option,
2416
+ value: option.Value !== undefined ? option.Value : option.value !== undefined ? option.value : option,
2417
+ textContent: option.DisplayText || option.text || option,
2425
2418
  parent: select
2426
2419
  });
2427
2420
  });
2428
2421
  } else if (optionsSource && typeof optionsSource === 'object') {
2429
2422
  Object.entries(optionsSource).forEach(([key, text]) => {
2430
2423
  FTableDOMHelper.create('option', {
2431
- attributes: { value: key },
2432
- text: text,
2424
+ value: key,
2425
+ textContent: text,
2433
2426
  parent: select
2434
2427
  });
2435
2428
  });
@@ -2438,6 +2431,48 @@ class FTable extends FTableEventEmitter {
2438
2431
  return select;
2439
2432
  }
2440
2433
 
2434
+ async createDatalistForSearch(fieldName, field) {
2435
+ const fieldSearchName = 'ftable-toolbarsearch-' + fieldName;
2436
+
2437
+ // Create the datalist element first
2438
+ const datalistId = `${fieldSearchName}-datalist`;
2439
+ const datalist = FTableDOMHelper.create('datalist', {
2440
+ attributes: { id: datalistId }
2441
+ });
2442
+
2443
+ // Create the input that uses the datalist
2444
+ const input = FTableDOMHelper.create('input', {
2445
+ className: 'ftable-toolbarsearch',
2446
+ type: 'search',
2447
+ id: fieldSearchName,
2448
+ placeholder: field.searchPlaceholder || field.placeholder || 'Type or select...',
2449
+ attributes: {
2450
+ 'data-field-name': fieldName,
2451
+ list: datalistId
2452
+ }
2453
+ });
2454
+
2455
+ // Store reference to datalist on the input element
2456
+ input.datalistElement = datalist;
2457
+
2458
+ // Load options for the datalist
2459
+ let optionsSource;
2460
+
2461
+ // Use search-specific options if available
2462
+ if (field.searchOptions) {
2463
+ optionsSource = field.searchOptions;
2464
+ } else if (field.options) {
2465
+ optionsSource = await this.formBuilder.getFieldOptions(fieldName, 'table');
2466
+ }
2467
+
2468
+ // Populate datalist with options
2469
+ if (optionsSource) {
2470
+ this.formBuilder.populateDatalistOptions(datalist, optionsSource);
2471
+ }
2472
+
2473
+ return input;
2474
+ }
2475
+
2441
2476
  handleSearchInputChange(event) {
2442
2477
  const input = event.target;
2443
2478
  const fieldName = input.getAttribute('data-field-name');
@@ -2689,7 +2724,7 @@ class FTable extends FTableEventEmitter {
2689
2724
  const colCount = this.elements.table.querySelector('thead tr').children.length;
2690
2725
  FTableDOMHelper.create('td', {
2691
2726
  attributes: { colspan: colCount },
2692
- text: this.options.messages.noDataAvailable,
2727
+ textContent: this.options.messages.noDataAvailable,
2693
2728
  parent: row
2694
2729
  });
2695
2730
  }
@@ -2984,7 +3019,7 @@ class FTable extends FTableEventEmitter {
2984
3019
  }
2985
3020
 
2986
3021
  const labelText = FTableDOMHelper.create('span', {
2987
- text: field.title || fieldName,
3022
+ textContent: field.title || fieldName,
2988
3023
  style: isSeparator ? 'font-weight: bold;' : null,
2989
3024
  parent: label
2990
3025
  });
@@ -2993,7 +3028,7 @@ class FTable extends FTableEventEmitter {
2993
3028
  if (isSorted) {
2994
3029
  const sortIndicator = FTableDOMHelper.create('span', {
2995
3030
  className: 'ftable-sort-indicator',
2996
- text: ' (sorted)',
3031
+ textContent: ' (sorted)',
2997
3032
  parent: labelText
2998
3033
  });
2999
3034
  sortIndicator.style.fontSize = '0.8em';
@@ -3079,25 +3114,61 @@ class FTable extends FTableEventEmitter {
3079
3114
  }
3080
3115
 
3081
3116
  addToolbarButton(options) {
3082
- const button = FTableDOMHelper.create('span', {
3117
+ const button = FTableDOMHelper.create('button', {
3083
3118
  className: `ftable-toolbar-item ${options.className || ''}`,
3119
+ id: options.id || null,
3120
+ title: options.title || null,
3121
+ textContent: options.text || null,
3122
+ type: 'button', // Prevent accidental form submission
3084
3123
  parent: this.elements.toolbarDiv
3085
3124
  });
3125
+
3086
3126
  if (options.addIconSpan) {
3087
3127
  // just the span, the rest is CSS here
3088
- const buttonText = FTableDOMHelper.create('span', {
3128
+ const iconSpan = FTableDOMHelper.create('span', {
3089
3129
  className: `ftable-toolbar-item-icon ${options.className || ''}`,
3090
3130
  parent: button
3091
3131
  });
3132
+ // If we want icon before text, we need to insert it
3133
+ // Since textContent replaces everything, we need to append text node
3134
+ if (options.text) {
3135
+ // Remove the textContent we set earlier
3136
+ button.textContent = '';
3137
+ button.append(iconSpan, options.text || '');
3138
+ }
3139
+ }
3140
+
3141
+ // Add icon if provided
3142
+ if (options.icon) {
3143
+ const img = FTableDOMHelper.create('img', {
3144
+ attributes: {
3145
+ src: options.icon,
3146
+ alt: '',
3147
+ width: 16,
3148
+ height: 16,
3149
+ style: 'margin-right: 6px; vertical-align: middle;'
3150
+ },
3151
+ parent: button
3152
+ });
3153
+ // If we want icon before text, we need to insert it
3154
+ // Since textContent replaces everything, we need to append text node
3155
+ if (options.text) {
3156
+ // Remove the textContent we set earlier
3157
+ button.textContent = '';
3158
+ button.append(img, options.text || '');
3159
+ }
3092
3160
  }
3093
- const buttonText = FTableDOMHelper.create('span', {
3094
- className: `ftable-toolbar-item-text ${options.className || ''}`,
3095
- text: options.text,
3096
- parent: button
3097
- });
3098
3161
 
3099
3162
  if (options.onClick) {
3100
- button.addEventListener('click', options.onClick);
3163
+ button.addEventListener('click', (e) => {
3164
+ e.preventDefault();
3165
+ e.stopPropagation();
3166
+ options.onClick(e);
3167
+ });
3168
+ }
3169
+
3170
+ if (options.disabled) {
3171
+ button.disabled = true;
3101
3172
  }
3102
3173
 
3103
3174
  return button;
@@ -3106,48 +3177,15 @@ class FTable extends FTableEventEmitter {
3106
3177
  createCustomToolbarItems() {
3107
3178
  if (!this.options.toolbar || !this.options.toolbar.items) return;
3108
3179
 
3109
- this.options.toolbar.items.forEach(item => {
3110
- const button = FTableDOMHelper.create('span', {
3111
- className: `ftable-toolbar-item ftable-toolbar-item-custom ${item.buttonClass || ''}`,
3112
- parent: this.elements.toolbarDiv
3180
+ this.options.toolbar.items.forEach((item, index) => {
3181
+ this.addToolbarButton({
3182
+ text: item.text || '',
3183
+ className: `ftable-toolbar-item-custom ${item.buttonClass || ''}`,
3184
+ id: item.buttonId || `ftable-toolbar-item-custom-id-${index}`,
3185
+ title: item.tooltip || '',
3186
+ icon: item.icon || null,
3187
+ onClick: typeof item.click === 'function' ? item.click : null
3113
3188
  });
3114
-
3115
- // Add title/tooltip if provided
3116
- if (item.tooltip) {
3117
- button.setAttribute('title', item.tooltip);
3118
- }
3119
-
3120
- // Add icon if provided
3121
- if (item.icon) {
3122
- const img = FTableDOMHelper.create('img', {
3123
- attributes: {
3124
- src: item.icon,
3125
- alt: '',
3126
- width: 16,
3127
- height: 16,
3128
- style: 'margin-right: 6px; vertical-align: middle;'
3129
- },
3130
- parent: button
3131
- });
3132
- }
3133
-
3134
- // Add text
3135
- if (item.text) {
3136
- FTableDOMHelper.create('span', {
3137
- text: item.text,
3138
- className: `ftable-toolbar-item-text ftable-toolbar-item-custom-text ${item.buttonTextClass || ''}`,
3139
- parent: button
3140
- });
3141
- }
3142
-
3143
- // Attach click handler
3144
- if (typeof item.click === 'function') {
3145
- button.addEventListener('click', (e) => {
3146
- e.preventDefault();
3147
- e.stopPropagation();
3148
- item.click(e);
3149
- });
3150
- }
3151
3189
  });
3152
3190
  }
3153
3191
 
@@ -3401,7 +3439,7 @@ class FTable extends FTableEventEmitter {
3401
3439
 
3402
3440
  const cell = FTableDOMHelper.create('td', {
3403
3441
  className: `${field.listClass || ''} ${field.listClassEntry || ''}`,
3404
- html: field.listEscapeHTML ? FTableDOMHelper.escapeHtml(value) : value,
3442
+ innerHTML: field.listEscapeHTML ? FTableDOMHelper.escapeHtml(value) : value,
3405
3443
  attributes: { 'data-field-name': fieldName },
3406
3444
  parent: row
3407
3445
  });
@@ -3422,7 +3460,7 @@ class FTable extends FTableEventEmitter {
3422
3460
  const button = FTableDOMHelper.create('button', {
3423
3461
  className: 'ftable-command-button ftable-edit-command-button',
3424
3462
  attributes: { title: this.options.messages.editRecord },
3425
- html: `<span>${this.options.messages.editRecord}</span>`,
3463
+ innerHTML: `<span>${this.options.messages.editRecord}</span>`,
3426
3464
  parent: cell
3427
3465
  });
3428
3466
 
@@ -3441,7 +3479,7 @@ class FTable extends FTableEventEmitter {
3441
3479
  const button = FTableDOMHelper.create('button', {
3442
3480
  className: 'ftable-command-button ftable-clone-command-button',
3443
3481
  attributes: { title: this.options.messages.cloneRecord || 'Clone' },
3444
- html: `<span>${this.options.messages.cloneRecord || 'Clone'}</span>`,
3482
+ innerHTML: `<span>${this.options.messages.cloneRecord || 'Clone'}</span>`,
3445
3483
  parent: cell
3446
3484
  });
3447
3485
  button.addEventListener('click', (e) => {
@@ -3460,7 +3498,7 @@ class FTable extends FTableEventEmitter {
3460
3498
  const button = FTableDOMHelper.create('button', {
3461
3499
  className: 'ftable-command-button ftable-delete-command-button',
3462
3500
  attributes: { title: this.options.messages.deleteText },
3463
- html: `<span>${this.options.messages.deleteText}</span>`,
3501
+ innerHTML: `<span>${this.options.messages.deleteText}</span>`,
3464
3502
  parent: cell
3465
3503
  });
3466
3504
 
@@ -4128,7 +4166,7 @@ class FTable extends FTableEventEmitter {
4128
4166
  if (pageNum - lastNumber > 1) {
4129
4167
  FTableDOMHelper.create('span', {
4130
4168
  className: 'ftable-page-number-space',
4131
- text: '...',
4169
+ textContent: '...',
4132
4170
  parent: this.elements.pagingListArea
4133
4171
  });
4134
4172
  }
@@ -4168,7 +4206,7 @@ class FTable extends FTableEventEmitter {
4168
4206
 
4169
4207
  // Label
4170
4208
  const label = FTableDOMHelper.create('span', {
4171
- text: this.options.messages.gotoPageLabel + ': ',
4209
+ textContent: this.options.messages.gotoPageLabel + ': ',
4172
4210
  parent: this.elements.pagingGotoArea
4173
4211
  });
4174
4212
 
@@ -4185,7 +4223,7 @@ class FTable extends FTableEventEmitter {
4185
4223
  for (let i = 1; i <= totalPages; i++) {
4186
4224
  FTableDOMHelper.create('option', {
4187
4225
  attributes: { value: i },
4188
- text: i,
4226
+ textContent: i,
4189
4227
  parent: this.elements.gotoPageSelect
4190
4228
  });
4191
4229
  }
@@ -4229,7 +4267,7 @@ class FTable extends FTableEventEmitter {
4229
4267
  createPageButton(text, pageNumber, disabled, className) {
4230
4268
  const button = FTableDOMHelper.create('span', {
4231
4269
  className: className + (disabled ? ' ftable-page-number-disabled' : ''),
4232
- html: text,
4270
+ innerHTML: text,
4233
4271
  parent: this.elements.pagingListArea
4234
4272
  });
4235
4273