@liedekef/ftable 1.1.6 → 1.1.8

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 (39) hide show
  1. package/ftable.esm.js +66 -193
  2. package/ftable.js +67 -194
  3. package/ftable.min.js +3 -3
  4. package/ftable.umd.js +67 -194
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +35 -25
  7. package/themes/basic/ftable_basic.min.css +1 -1
  8. package/themes/ftable_theme_base.less +49 -35
  9. package/themes/lightcolor/blue/ftable.css +43 -33
  10. package/themes/lightcolor/blue/ftable.min.css +1 -1
  11. package/themes/lightcolor/ftable_lightcolor_base.less +6 -5
  12. package/themes/lightcolor/gray/ftable.css +43 -33
  13. package/themes/lightcolor/gray/ftable.min.css +1 -1
  14. package/themes/lightcolor/green/ftable.css +43 -33
  15. package/themes/lightcolor/green/ftable.min.css +1 -1
  16. package/themes/lightcolor/orange/ftable.css +43 -33
  17. package/themes/lightcolor/orange/ftable.min.css +1 -1
  18. package/themes/lightcolor/red/ftable.css +43 -33
  19. package/themes/lightcolor/red/ftable.min.css +1 -1
  20. package/themes/metro/blue/ftable.css +35 -25
  21. package/themes/metro/blue/ftable.min.css +1 -1
  22. package/themes/metro/brown/ftable.css +35 -25
  23. package/themes/metro/brown/ftable.min.css +1 -1
  24. package/themes/metro/crimson/ftable.css +35 -25
  25. package/themes/metro/crimson/ftable.min.css +1 -1
  26. package/themes/metro/darkgray/ftable.css +35 -25
  27. package/themes/metro/darkgray/ftable.min.css +1 -1
  28. package/themes/metro/darkorange/ftable.css +35 -25
  29. package/themes/metro/darkorange/ftable.min.css +1 -1
  30. package/themes/metro/green/ftable.css +35 -25
  31. package/themes/metro/green/ftable.min.css +1 -1
  32. package/themes/metro/lightgray/ftable.css +35 -25
  33. package/themes/metro/lightgray/ftable.min.css +1 -1
  34. package/themes/metro/pink/ftable.css +35 -25
  35. package/themes/metro/pink/ftable.min.css +1 -1
  36. package/themes/metro/purple/ftable.css +35 -25
  37. package/themes/metro/purple/ftable.min.css +1 -1
  38. package/themes/metro/red/ftable.css +35 -25
  39. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.js CHANGED
@@ -4,9 +4,7 @@
4
4
  typeof define === 'function' && define.amd ? define(factory) :
5
5
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.FTable = factory());
6
6
  }(this, (function () {
7
- // Modern fTable - Vanilla JS Refactor
8
-
9
- const FTABLE_DEFAULT_MESSAGES = {
7
+ const FTABLE_DEFAULT_MESSAGES = {
10
8
  serverCommunicationError: 'An error occurred while communicating to the server.',
11
9
  loadingMessage: 'Loading records...',
12
10
  noDataAvailable: 'No data available!',
@@ -753,7 +751,9 @@ class FTableFormBuilder {
753
751
  }
754
752
 
755
753
  try {
756
- const response = await FTableHttpClient.get(url);
754
+ const response = this.options.forcePost
755
+ ? await FTableHttpClient.post(url)
756
+ : await FTableHttpClient.get(url);
757
757
  const options = response.Options || response.options || response || [];
758
758
 
759
759
  // Only cache if noCache is false
@@ -1223,64 +1223,55 @@ class FTableFormBuilder {
1223
1223
  }
1224
1224
 
1225
1225
  createCheckbox(fieldName, field, value) {
1226
- function getCheckboxText(field, value) {
1227
- if (value == undefined ) {
1228
- value = 0;
1229
- }
1230
- if (field.values && field.values[value] !== undefined) {
1231
- return field.values[value];
1232
- }
1233
- return value ? 'Yes' : 'No';
1234
- }
1235
1226
  const wrapper = FTableDOMHelper.create('div', {
1236
- className: 'ftable-checkbox-wrapper'
1227
+ className: 'ftable-yesno-check-wrapper'
1237
1228
  });
1238
1229
 
1239
1230
  const isChecked = [1, '1', true, 'true'].includes(value);
1240
1231
 
1241
- const displayValue = getCheckboxText(field, value); // Uses field.values if defined
1232
+ // Determine "Yes" and "No" labels
1233
+ let dataNo = 'No';
1234
+ let dataYes = 'Yes';
1242
1235
 
1236
+ if (field.values && typeof field.values === 'object') {
1237
+ if (field.values['0'] !== undefined) dataNo = field.values['0'];
1238
+ if (field.values['1'] !== undefined) dataYes = field.values['1'];
1239
+ }
1240
+
1241
+ // Create the checkbox
1243
1242
  const checkbox = FTableDOMHelper.create('input', {
1243
+ className: ['ftable-yesno-check-input', field.inputClass || ''].filter(Boolean).join(' '),
1244
1244
  attributes: {
1245
1245
  type: 'checkbox',
1246
1246
  name: fieldName,
1247
1247
  id: `Edit-${fieldName}`,
1248
- class: field.inputClass || '',
1249
1248
  value: '1'
1250
1249
  },
1250
+ properties: {
1251
+ checked: isChecked
1252
+ },
1251
1253
  parent: wrapper
1252
1254
  });
1253
- checkbox.checked = isChecked;
1254
1255
 
1256
+ // Create the label with data attributes
1255
1257
  const label = FTableDOMHelper.create('label', {
1256
- attributes: { for: `Edit-${fieldName}` },
1258
+ className: 'ftable-yesno-check-text',
1259
+ attributes: {
1260
+ for: `Edit-${fieldName}`,
1261
+ 'data-yes': dataYes,
1262
+ 'data-no': dataNo
1263
+ },
1257
1264
  parent: wrapper
1258
1265
  });
1259
1266
 
1260
- // Add the static formText (e.g., "Is Active?")
1267
+ // Optional: Add a static form label (e.g., "Is Active?")
1261
1268
  if (field.formText) {
1262
- FTableDOMHelper.create('span', {
1269
+ const formSpan = FTableDOMHelper.create('span', {
1263
1270
  text: field.formText,
1264
- parent: label
1271
+ parent: wrapper
1265
1272
  });
1273
+ formSpan.style.marginLeft = '8px';
1266
1274
  }
1267
-
1268
- // Only add dynamic value span if field.values is defined
1269
- if (field.values) {
1270
- const valueSpan = FTableDOMHelper.create('span', {
1271
- className: 'ftable-checkbox-dynamic-value',
1272
- text: ` ${displayValue}`,
1273
- parent: label
1274
- });
1275
-
1276
- // Update on change
1277
- checkbox.addEventListener('change', () => {
1278
- const newValue = checkbox.checked ? '1' : '0';
1279
- const newText = getCheckboxText(field, newValue);
1280
- valueSpan.textContent = ` ${newText}`;
1281
- });
1282
- }
1283
-
1284
1275
  return wrapper;
1285
1276
  }
1286
1277
 
@@ -1359,10 +1350,14 @@ class FTable extends FTableEventEmitter {
1359
1350
  this.element = typeof element === 'string' ?
1360
1351
  document.querySelector(element) : element;
1361
1352
 
1353
+ if (!this.element) {
1354
+ return;
1355
+ }
1356
+
1362
1357
  // Prevent double initialization
1363
- if (element.ftableInstance) {
1358
+ if (this.element.ftableInstance) {
1364
1359
  //console.warn('FTable is already initialized on this element. Using that.');
1365
- return element.ftableInstance;
1360
+ return this.element.ftableInstance;
1366
1361
  }
1367
1362
 
1368
1363
  this.options = this.mergeOptions(options);
@@ -1399,6 +1394,7 @@ class FTable extends FTableEventEmitter {
1399
1394
  logLevel: FTableLogger.LOG_LEVELS.WARN,
1400
1395
  actions: {},
1401
1396
  fields: {},
1397
+ forcePost: true,
1402
1398
  animationsEnabled: true,
1403
1399
  loadingAnimationDelay: 1000,
1404
1400
  defaultDateLocale: '',
@@ -2525,13 +2521,15 @@ class FTable extends FTableEventEmitter {
2525
2521
  // Create overlay to capture clicks outside menu
2526
2522
  this.elements.columnSelectionOverlay = FTableDOMHelper.create('div', {
2527
2523
  className: 'ftable-contextmenu-overlay',
2528
- parent: this.elements.mainContainer
2524
+ //parent: this.elements.mainContainer
2525
+ parent: document.body
2529
2526
  });
2530
2527
 
2531
2528
  // Create the menu
2532
2529
  this.elements.columnSelectionMenu = FTableDOMHelper.create('div', {
2533
2530
  className: 'ftable-column-selection-container',
2534
- parent: this.elements.columnSelectionOverlay
2531
+ //parent: this.elements.columnSelectionOverlay
2532
+ parent: document.body
2535
2533
  });
2536
2534
 
2537
2535
  // Populate menu with column options
@@ -2617,29 +2615,36 @@ class FTable extends FTableEventEmitter {
2617
2615
  }
2618
2616
 
2619
2617
  positionColumnSelectionMenu(e) {
2620
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2621
- const menuWidth = 200; // Approximate menu width
2622
- const menuHeight = this.columnList.length * 30 + 20; // Approximate height
2618
+ const self = this;
2623
2619
 
2624
- let left = e.clientX - containerRect.left;
2625
- let top = e.clientY - containerRect.top;
2620
+ // menu is bounded to the body for absolute positioning above other content, so safest is to use pageX/Y
2621
+ let left = e.pageX;
2622
+ let top = e.pageY;
2626
2623
 
2627
- // Adjust position to keep menu within container bounds
2628
- if (left + menuWidth > containerRect.width) {
2629
- left = Math.max(0, containerRect.width - menuWidth);
2630
- }
2624
+ // Define minimum width
2625
+ const minWidth = 100;
2631
2626
 
2632
- if (top + menuHeight > containerRect.height) {
2633
- top = Math.max(0, containerRect.height - menuHeight);
2634
- }
2627
+ // Position the menu
2628
+ self.elements.columnSelectionMenu.style.position = 'absolute';
2629
+ self.elements.columnSelectionMenu.style.left = `${left}px`;
2630
+ self.elements.columnSelectionMenu.style.top = `${top}px`;
2631
+ self.elements.columnSelectionMenu.style.minWidth = `${minWidth}px`;
2632
+ self.elements.columnSelectionMenu.style.boxSizing = 'border-box';
2633
+
2634
+ // Optional: Adjust if menu would overflow right edge
2635
+ const menuWidth = self.elements.columnSelectionMenu.offsetWidth;
2636
+ const windowWidth = window.innerWidth;
2635
2637
 
2636
- this.elements.columnSelectionMenu.style.left = left + 'px';
2637
- this.elements.columnSelectionMenu.style.top = top + 'px';
2638
+ if (left + menuWidth > windowWidth) {
2639
+ left = Math.max(10, windowWidth - menuWidth - 10); // 10px margin
2640
+ self.elements.columnSelectionMenu.style.left = `${left}px`;
2641
+ }
2638
2642
  }
2639
2643
 
2640
2644
  hideColumnSelectionMenu() {
2641
2645
  if (this.elements.columnSelectionOverlay) {
2642
2646
  this.elements.columnSelectionOverlay.remove();
2647
+ this.elements.columnSelectionMenu.remove();
2643
2648
  this.elements.columnSelectionOverlay = null;
2644
2649
  this.elements.columnSelectionMenu = null;
2645
2650
  }
@@ -2882,7 +2887,9 @@ class FTable extends FTableEventEmitter {
2882
2887
  if (typeof listAction === 'function') {
2883
2888
  data = await listAction(params);
2884
2889
  } else if (typeof listAction === 'string') {
2885
- data = await FTableHttpClient.get(listAction, params);
2890
+ data = this.options.forcePost
2891
+ ? await FTableHttpClient.post(listAction, params)
2892
+ : await FTableHttpClient.get(listAction, params);
2886
2893
  } else {
2887
2894
  throw new Error('No valid listAction provided');
2888
2895
  }
@@ -4416,7 +4423,9 @@ class FTable extends FTableEventEmitter {
4416
4423
  ...params
4417
4424
  };
4418
4425
 
4419
- const response = await FTableHttpClient.get(url, fullParams);
4426
+ const response = this.options.forcePost
4427
+ ? await FTableHttpClient.post(url, fullParams)
4428
+ : await FTableHttpClient.get(url, fullParams);
4420
4429
 
4421
4430
  if (!response || !response.Record) {
4422
4431
  throw new Error('Invalid response or missing Record');
@@ -4718,141 +4727,5 @@ class FTable extends FTableEventEmitter {
4718
4727
  }
4719
4728
  }
4720
4729
 
4721
- // Export for use
4722
- //window.FTable = FTable;
4723
-
4724
- // Usage example:
4725
- /*
4726
- const table = new FTable('#myTable', {
4727
- title: 'My Data Table',
4728
- paging: true,
4729
- pageSize: 25,
4730
- sorting: true,
4731
- selecting: true,
4732
- actions: {
4733
- listAction: '/api/users',
4734
- createAction: '/api/users',
4735
- updateAction: '/api/users',
4736
- deleteAction: '/api/users'
4737
- },
4738
- fields: {
4739
- id: { key: true, list: false },
4740
- name: {
4741
- title: 'Name',
4742
- type: 'text',
4743
- inputAttributes: "maxlength=100 required"
4744
- },
4745
- email: {
4746
- title: 'Email',
4747
- type: 'email',
4748
- width: '40%',
4749
- inputAttributes: {
4750
- pattern: '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$'
4751
- }
4752
- },
4753
- created: { title: 'Created', type: 'date', width: '30%' }
4754
- },
4755
- toolbarsearch: true,
4756
- childTable: {
4757
- title: 'Child Records',
4758
- actions: {
4759
- listAction: '/api/users/{id}/orders', // {id} will be replaced with parent record id
4760
- createAction: '/api/users/{id}/orders',
4761
- updateAction: '/api/orders',
4762
- deleteAction: '/api/orders'
4763
- },
4764
- fields: {
4765
- orderId: { key: true, list: false },
4766
- orderDate: { title: 'Date', type: 'date' },
4767
- amount: { title: 'Amount', type: 'number' }
4768
- }
4769
- },
4770
- childTableColumnsVisible: true,
4771
-
4772
- });
4773
-
4774
- // Or dynamic child table
4775
- childTable: async function(parentRecord) {
4776
- return {
4777
- title: `Orders for ${parentRecord.name}`,
4778
- actions: {
4779
- listAction: `/api/users/${parentRecord.id}/orders`
4780
- },
4781
- fields: {
4782
- // Dynamic fields based on parent
4783
- }
4784
- };
4785
- }
4786
-
4787
- // function for select options
4788
- fields: {
4789
- assignee: {
4790
- title: 'Assigned To',
4791
- type: 'select',
4792
- options: async function(params) {
4793
- // params contains dependsOnValue, dependsOnField, etc.
4794
- const department = params.dependsOnValue;
4795
- const response = await fetch(`/api/users?department=${department}`);
4796
- return response.json();
4797
- },
4798
- dependsOn: 'department'
4799
- }
4800
- }
4801
-
4802
- // child table:
4803
- phoneNumbers: {
4804
- title: 'Phones',
4805
- display: (data) => {
4806
- const img = document.createElement('img');
4807
- img.className = 'child-opener-image';
4808
- img.src = '/Content/images/Misc/phone.png';
4809
- img.title = 'Edit phone numbers';
4810
- img.style.cursor = 'pointer';
4811
-
4812
- parentRow = img.closest('tr');
4813
- img.addEventListener('click', () => {
4814
- e.stopPropagation();
4815
- if (parentRow.childRow) {
4816
- myTable.closeChildTable(parentRow);
4817
- } else {
4818
- myTable.openChildTable( parentRow, {
4819
- title: `${data.record.Name} - Phone numbers`,
4820
- actions: {
4821
- listAction: `/PagingPerson/PhoneList?PersonId=${data.record.PersonId}`,
4822
- deleteAction: '/PagingPerson/DeletePhone',
4823
- updateAction: '/PagingPerson/UpdatePhone',
4824
- createAction: `/PagingPerson/CreatePhone?PersonId=${data.record.PersonId}`
4825
- },
4826
- fields: {
4827
- PhoneId: { key: true },
4828
- Number: { title: 'Number', type: 'text' },
4829
- Type: { title: 'Type', options: { 0: 'Home', 1: 'Work', 2: 'Mobile' } }
4830
- }
4831
- }, (childTable) => {
4832
- console.log('Child table created');
4833
- };
4834
- }
4835
- });
4836
- img.addEventListener('click', (e) => {
4837
- e.stopPropagation();
4838
- if (parentRow.childRow) {
4839
- myTable.closeChildTable(parentRow);
4840
- } else {
4841
- myTable.openChildTable(parentRow, childOptions);
4842
- }
4843
- });
4844
-
4845
- return img;
4846
- }
4847
- }
4848
-
4849
- // Clear specific options cache
4850
- table.clearOptionsCache('/api/countries');
4851
-
4852
- table.load();
4853
- */
4854
-
4855
- window.FTable = FTable;
4856
-
4857
4730
  return FTable;
4858
4731
  })));