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