@liedekef/ftable 1.1.23 → 1.1.24
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/ftable.esm.js +225 -136
- package/ftable.js +225 -136
- package/ftable.min.js +2 -2
- package/ftable.umd.js +225 -136
- package/package.json +1 -1
- package/themes/basic/ftable_basic.css +1 -1
- package/themes/basic/ftable_basic.min.css +1 -1
- package/themes/ftable_theme_base.less +1 -1
- package/themes/lightcolor/blue/ftable.css +1 -1
- package/themes/lightcolor/blue/ftable.min.css +1 -1
- package/themes/lightcolor/gray/ftable.css +1 -1
- package/themes/lightcolor/gray/ftable.min.css +1 -1
- package/themes/lightcolor/green/ftable.css +1 -1
- package/themes/lightcolor/green/ftable.min.css +1 -1
- package/themes/lightcolor/orange/ftable.css +1 -1
- package/themes/lightcolor/orange/ftable.min.css +1 -1
- package/themes/lightcolor/red/ftable.css +1 -1
- package/themes/lightcolor/red/ftable.min.css +1 -1
- package/themes/metro/blue/ftable.css +1 -1
- package/themes/metro/blue/ftable.min.css +1 -1
- package/themes/metro/brown/ftable.css +1 -1
- package/themes/metro/brown/ftable.min.css +1 -1
- package/themes/metro/crimson/ftable.css +1 -1
- package/themes/metro/crimson/ftable.min.css +1 -1
- package/themes/metro/darkgray/ftable.css +1 -1
- package/themes/metro/darkgray/ftable.min.css +1 -1
- package/themes/metro/darkorange/ftable.css +1 -1
- package/themes/metro/darkorange/ftable.min.css +1 -1
- package/themes/metro/green/ftable.css +1 -1
- package/themes/metro/green/ftable.min.css +1 -1
- package/themes/metro/lightgray/ftable.css +1 -1
- package/themes/metro/lightgray/ftable.min.css +1 -1
- package/themes/metro/pink/ftable.css +1 -1
- package/themes/metro/pink/ftable.min.css +1 -1
- package/themes/metro/purple/ftable.css +1 -1
- package/themes/metro/purple/ftable.min.css +1 -1
- package/themes/metro/red/ftable.css +1 -1
- package/themes/metro/red/ftable.min.css +1 -1
package/ftable.esm.js
CHANGED
|
@@ -545,17 +545,86 @@ class FTableFormBuilder {
|
|
|
545
545
|
this.dependencies = new Map(); // Track field dependencies
|
|
546
546
|
this.optionsCache = new FTableOptionsCache();
|
|
547
547
|
this.originalFieldOptions = new Map(); // Store original field.options
|
|
548
|
+
this.resolvedFieldOptions = new Map(); // Store resolved options per context
|
|
549
|
+
|
|
550
|
+
// Initialize with empty cache objects
|
|
551
|
+
Object.keys(this.options.fields || {}).forEach(fieldName => {
|
|
552
|
+
this.resolvedFieldOptions.set(fieldName, {});
|
|
553
|
+
});
|
|
554
|
+
Object.entries(this.options.fields).forEach(([fieldName, field]) => {
|
|
555
|
+
this.originalFieldOptions.set(fieldName, field.options);
|
|
556
|
+
});
|
|
548
557
|
}
|
|
549
558
|
|
|
550
|
-
//
|
|
551
|
-
|
|
552
|
-
|
|
559
|
+
// Get options for specific context
|
|
560
|
+
async getFieldOptions(fieldName, context = 'table', params = {}) {
|
|
561
|
+
const field = this.options.fields[fieldName];
|
|
562
|
+
const originalOptions = this.originalFieldOptions.get(fieldName);
|
|
563
|
+
|
|
564
|
+
// If no options or already resolved for this context with same params, return cached
|
|
565
|
+
if (!originalOptions) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Determine if we should skip caching for this specific context
|
|
570
|
+
const shouldSkipCache = this.shouldSkipCachingForContext(field, context, params);
|
|
571
|
+
const cacheKey = this.generateOptionsCacheKey(context, params);
|
|
572
|
+
// Skip cache if configured or forceRefresh requested
|
|
573
|
+
if (!shouldSkipCache && !params.forceRefresh) {
|
|
574
|
+
const cached = this.resolvedFieldOptions.get(fieldName)[cacheKey];
|
|
575
|
+
if (cached) return cached;
|
|
576
|
+
}
|
|
553
577
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
578
|
+
try {
|
|
579
|
+
// Create temp field with original options for resolution
|
|
580
|
+
const tempField = { ...field, options: originalOptions };
|
|
581
|
+
const resolved = await this.resolveOptions(tempField, {
|
|
582
|
+
...params
|
|
583
|
+
}, context, shouldSkipCache);
|
|
584
|
+
|
|
585
|
+
// Only cache if noCache is not enabled
|
|
586
|
+
if (!shouldSkipCache) {
|
|
587
|
+
this.resolvedFieldOptions.get(fieldName)[cacheKey] = resolved;
|
|
557
588
|
}
|
|
558
|
-
|
|
589
|
+
return resolved;
|
|
590
|
+
} catch (err) {
|
|
591
|
+
console.error(`Failed to resolve options for ${fieldName} (${context}):`, err);
|
|
592
|
+
return originalOptions;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Helper method to determine caching behavior
|
|
597
|
+
shouldSkipCachingForContext(field, context, params) {
|
|
598
|
+
if (!field.noCache) return false;
|
|
599
|
+
|
|
600
|
+
if (typeof field.noCache === 'boolean') {
|
|
601
|
+
return field.noCache; // true = skip all contexts
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (typeof field.noCache === 'function') {
|
|
605
|
+
return field.noCache({ context, ...params });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (typeof field.noCache === 'object') {
|
|
609
|
+
// Check if this specific context should skip cache
|
|
610
|
+
return field.noCache[context] === true;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return false; // Default to caching
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
generateOptionsCacheKey(context, params) {
|
|
617
|
+
// Create a unique key based on context and dependency values
|
|
618
|
+
const keyParts = [context];
|
|
619
|
+
|
|
620
|
+
if (params.dependedValues) {
|
|
621
|
+
// Include relevant dependency values in the cache key
|
|
622
|
+
Object.keys(params.dependedValues).sort().forEach(key => {
|
|
623
|
+
keyParts.push(`${key}=${params.dependedValues[key]}`);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return keyParts.join('|');
|
|
559
628
|
}
|
|
560
629
|
|
|
561
630
|
shouldIncludeField(field, formType) {
|
|
@@ -568,6 +637,7 @@ class FTableFormBuilder {
|
|
|
568
637
|
}
|
|
569
638
|
|
|
570
639
|
createFieldContainer(fieldName, field, record, formType) {
|
|
640
|
+
// in this function, field.options already contains the resolved values
|
|
571
641
|
const container = FTableDOMHelper.create('div', {
|
|
572
642
|
className: 'ftable-input-field-container',
|
|
573
643
|
attributes: {
|
|
@@ -589,66 +659,12 @@ class FTableFormBuilder {
|
|
|
589
659
|
return container;
|
|
590
660
|
}
|
|
591
661
|
|
|
592
|
-
/*async resolveAllFieldOptions(fieldValues) {
|
|
593
|
-
// Store original options before first resolution
|
|
594
|
-
this.storeOriginalFieldOptions();
|
|
595
|
-
|
|
596
|
-
const promises = Object.entries(this.options.fields).map(async ([fieldName, field]) => {
|
|
597
|
-
// Use original options if we have them, otherwise use current field.options
|
|
598
|
-
const originalOptions = this.originalFieldOptions.get(fieldName) || field.options;
|
|
599
|
-
|
|
600
|
-
if (originalOptions && (typeof originalOptions === 'function' || typeof originalOptions === 'string')) {
|
|
601
|
-
try {
|
|
602
|
-
// Pass fieldValues as dependedValues for dependency resolution
|
|
603
|
-
const params = { dependedValues: fieldValues };
|
|
604
|
-
|
|
605
|
-
// Resolve using original options, not the possibly already-resolved ones
|
|
606
|
-
const tempField = { ...field, options: originalOptions };
|
|
607
|
-
const resolved = await this.resolveOptions(tempField, params);
|
|
608
|
-
field.options = resolved; // Replace with resolved data
|
|
609
|
-
} catch (err) {
|
|
610
|
-
console.error(`Failed to resolve options for ${fieldName}:`, err);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
await Promise.all(promises);
|
|
615
|
-
}*/
|
|
616
|
-
|
|
617
|
-
async resolveNonDependantFieldOptions(fieldValues, formType) {
|
|
618
|
-
// Store original options before first resolution
|
|
619
|
-
this.storeOriginalFieldOptions();
|
|
620
|
-
|
|
621
|
-
const promises = Object.entries(this.options.fields).map(async ([fieldName, field]) => {
|
|
622
|
-
// Use original options if we have them, otherwise use current field.options
|
|
623
|
-
if (field.dependsOn) {
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
const originalOptions = this.originalFieldOptions.get(fieldName) || field.options;
|
|
627
|
-
|
|
628
|
-
if (originalOptions && (typeof originalOptions === 'function' || typeof originalOptions === 'string')) {
|
|
629
|
-
try {
|
|
630
|
-
// Pass fieldValues as dependedValues for dependency resolution (but record contains everything too ...)
|
|
631
|
-
const params = { dependedValues: fieldValues, source: formType, record: fieldValues };
|
|
632
|
-
|
|
633
|
-
// Resolve using original options, not the possibly already-resolved ones
|
|
634
|
-
const tempField = { ...field, options: originalOptions };
|
|
635
|
-
const resolved = await this.resolveOptions(tempField, params);
|
|
636
|
-
field.options = resolved; // Replace with resolved data
|
|
637
|
-
} catch (err) {
|
|
638
|
-
console.error(`Failed to resolve options for ${fieldName}:`, err);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
await Promise.all(promises);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
662
|
async createForm(formType = 'create', record = {}) {
|
|
646
663
|
|
|
647
664
|
this.currentFormRecord = record;
|
|
648
665
|
|
|
649
666
|
// Pre-resolve all options for fields depending on nothing, the others are handled down the road when dependancies are calculated
|
|
650
|
-
|
|
651
|
-
await this.resolveNonDependantFieldOptions(record, formType);
|
|
667
|
+
await this.resolveFormFieldOptions(record, formType);
|
|
652
668
|
|
|
653
669
|
const form = FTableDOMHelper.create('form', {
|
|
654
670
|
className: `ftable-dialog-form ftable-${formType}-form`
|
|
@@ -657,12 +673,27 @@ class FTableFormBuilder {
|
|
|
657
673
|
// Build dependency map first
|
|
658
674
|
this.buildDependencyMap();
|
|
659
675
|
|
|
660
|
-
|
|
676
|
+
// Create form fields using for...of instead of forEach, this allows the await to work
|
|
677
|
+
for (const [fieldName, field] of Object.entries(this.options.fields)) {
|
|
661
678
|
if (this.shouldIncludeField(field, formType)) {
|
|
662
|
-
|
|
679
|
+
let fieldWithOptions = { ...field };
|
|
680
|
+
if (!field.dependsOn) {
|
|
681
|
+
const contextOptions = await this.getFieldOptions(fieldName, formType, {
|
|
682
|
+
record,
|
|
683
|
+
source: formType
|
|
684
|
+
});
|
|
685
|
+
fieldWithOptions.options = contextOptions;
|
|
686
|
+
} else {
|
|
687
|
+
// For dependent fields, use placeholder or original options
|
|
688
|
+
// They will be resolved when dependencies change
|
|
689
|
+
fieldWithOptions.options = field.options;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
const fieldContainer = this.createFieldContainer(fieldName, fieldWithOptions, record, formType);
|
|
663
694
|
form.appendChild(fieldContainer);
|
|
664
695
|
}
|
|
665
|
-
}
|
|
696
|
+
}
|
|
666
697
|
|
|
667
698
|
// Set up dependency listeners after all fields are created
|
|
668
699
|
this.setupDependencyListeners(form);
|
|
@@ -670,6 +701,35 @@ class FTableFormBuilder {
|
|
|
670
701
|
return form;
|
|
671
702
|
}
|
|
672
703
|
|
|
704
|
+
async resolveFormFieldOptions(record, formType) {
|
|
705
|
+
const promises = Object.entries(this.options.fields).map(async ([fieldName, field]) => {
|
|
706
|
+
if (field.dependsOn) {
|
|
707
|
+
// Dependent fields will be resolved when dependencies change
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (this.shouldResolveOptions(field.options)) {
|
|
712
|
+
try {
|
|
713
|
+
await this.getFieldOptions(fieldName, formType, {
|
|
714
|
+
record,
|
|
715
|
+
source: formType
|
|
716
|
+
});
|
|
717
|
+
} catch (err) {
|
|
718
|
+
console.error(`Failed to resolve form options for ${fieldName}:`, err);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
await Promise.all(promises);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
shouldResolveOptions(options) {
|
|
727
|
+
return options &&
|
|
728
|
+
(typeof options === 'function' || typeof options === 'string') &&
|
|
729
|
+
!Array.isArray(options) &&
|
|
730
|
+
!(typeof options === 'object' && !Array.isArray(options) && Object.keys(options).length > 0);
|
|
731
|
+
}
|
|
732
|
+
|
|
673
733
|
buildDependencyMap() {
|
|
674
734
|
this.dependencies.clear();
|
|
675
735
|
|
|
@@ -717,7 +777,7 @@ class FTableFormBuilder {
|
|
|
717
777
|
this.handleDependencyChange(form);
|
|
718
778
|
}
|
|
719
779
|
|
|
720
|
-
async resolveOptions(field, params = {}) {
|
|
780
|
+
async resolveOptions(field, params = {}, source = '', noCache = false) {
|
|
721
781
|
if (!field.options) return [];
|
|
722
782
|
|
|
723
783
|
// Case 1: Direct options (array or object)
|
|
@@ -726,13 +786,16 @@ class FTableFormBuilder {
|
|
|
726
786
|
}
|
|
727
787
|
|
|
728
788
|
let result;
|
|
729
|
-
// Create a mutable flag for cache clearing
|
|
730
|
-
let noCache = false;
|
|
731
789
|
|
|
732
790
|
// Enhance params with clearCache() method
|
|
733
791
|
const enhancedParams = {
|
|
734
792
|
...params,
|
|
735
|
-
|
|
793
|
+
source: source,
|
|
794
|
+
clearCache: () => {
|
|
795
|
+
noCache = true;
|
|
796
|
+
// Also update the field's noCache setting for future calls
|
|
797
|
+
this.updateFieldCacheSetting(field, source, true);
|
|
798
|
+
}
|
|
736
799
|
};
|
|
737
800
|
|
|
738
801
|
if (typeof field.options === 'function') {
|
|
@@ -775,41 +838,81 @@ class FTableFormBuilder {
|
|
|
775
838
|
}
|
|
776
839
|
}
|
|
777
840
|
|
|
841
|
+
updateFieldCacheSetting(field, context, skipCache) {
|
|
842
|
+
if (!field.noCache) {
|
|
843
|
+
// Initialize noCache as object for this context
|
|
844
|
+
field.noCache = { [context]: skipCache };
|
|
845
|
+
} else if (typeof field.noCache === 'boolean') {
|
|
846
|
+
// Convert boolean to object, preserving existing behavior for other contexts
|
|
847
|
+
field.noCache = {
|
|
848
|
+
'table': field.noCache,
|
|
849
|
+
'create': field.noCache,
|
|
850
|
+
'edit': field.noCache,
|
|
851
|
+
[context]: skipCache // Override for this context
|
|
852
|
+
};
|
|
853
|
+
} else if (typeof field.noCache === 'object') {
|
|
854
|
+
// Update specific context
|
|
855
|
+
field.noCache[context] = skipCache;
|
|
856
|
+
}
|
|
857
|
+
// Function-based noCache remains unchanged (runtime decision)
|
|
858
|
+
}
|
|
859
|
+
|
|
778
860
|
clearOptionsCache(url = null, params = null) {
|
|
779
861
|
this.optionsCache.clear(url, params);
|
|
780
862
|
}
|
|
781
863
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const dependedValues = {};
|
|
864
|
+
getFormValues(form) {
|
|
865
|
+
const values = {};
|
|
785
866
|
|
|
786
|
-
// Get all
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
867
|
+
// Get all form elements
|
|
868
|
+
const elements = form.elements;
|
|
869
|
+
|
|
870
|
+
for (let i = 0; i < elements.length; i++) {
|
|
871
|
+
const element = elements[i];
|
|
872
|
+
const name = element.name;
|
|
873
|
+
|
|
874
|
+
if (!name || element.disabled) continue;
|
|
875
|
+
|
|
876
|
+
switch (element.type) {
|
|
877
|
+
case 'checkbox':
|
|
878
|
+
values[name] = element.checked ? element.value || '1' : '0';
|
|
879
|
+
break;
|
|
880
|
+
|
|
881
|
+
case 'radio':
|
|
882
|
+
if (element.checked) {
|
|
883
|
+
values[name] = element.value;
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
|
|
887
|
+
case 'select-multiple':
|
|
888
|
+
values[name] = Array.from(element.selectedOptions).map(option => option.value);
|
|
889
|
+
break;
|
|
890
|
+
|
|
891
|
+
default:
|
|
892
|
+
values[name] = element.value;
|
|
893
|
+
break;
|
|
795
894
|
}
|
|
796
895
|
}
|
|
797
896
|
|
|
798
|
-
|
|
897
|
+
return values;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async handleDependencyChange(form, changedFieldname = '') {
|
|
901
|
+
// Build dependedValues: { field1: value1, field2: value2 }
|
|
902
|
+
const dependedValues = this.getFormValues(form);
|
|
799
903
|
const formType = form.classList.contains('ftable-create-form') ? 'create' : 'edit';
|
|
800
904
|
const record = this.currentFormRecord || {};
|
|
801
905
|
|
|
802
|
-
// Prepare base params for options function
|
|
803
906
|
const baseParams = {
|
|
804
907
|
record,
|
|
805
908
|
source: formType,
|
|
806
|
-
form,
|
|
909
|
+
form,
|
|
807
910
|
dependedValues
|
|
808
911
|
};
|
|
809
912
|
|
|
810
|
-
// Update each dependent field
|
|
811
913
|
for (const [fieldName, field] of Object.entries(this.options.fields)) {
|
|
812
914
|
if (!field.dependsOn) continue;
|
|
915
|
+
|
|
813
916
|
if (changedFieldname !== '') {
|
|
814
917
|
let dependsOnFields = field.dependsOn
|
|
815
918
|
.split(',')
|
|
@@ -832,31 +935,22 @@ class FTableFormBuilder {
|
|
|
832
935
|
if (datalist) datalist.innerHTML = '';
|
|
833
936
|
}
|
|
834
937
|
|
|
835
|
-
//
|
|
938
|
+
// Resolve options with current context
|
|
836
939
|
const params = {
|
|
837
940
|
...baseParams,
|
|
838
|
-
// Specific for this field
|
|
839
941
|
dependsOnField: field.dependsOn,
|
|
840
942
|
dependsOnValue: dependedValues[field.dependsOn]
|
|
841
943
|
};
|
|
842
944
|
|
|
843
|
-
|
|
844
|
-
const originalOptions = this.originalFieldOptions.get(fieldName) || field.options;
|
|
845
|
-
const tempField = { ...field, options: originalOptions };
|
|
846
|
-
|
|
847
|
-
// Resolve options with full context using original options
|
|
848
|
-
const newOptions = await this.resolveOptions(tempField, params);
|
|
945
|
+
const newOptions = await this.getFieldOptions(fieldName, formType, params);
|
|
849
946
|
|
|
850
|
-
// Populate
|
|
947
|
+
// Populate the input
|
|
851
948
|
if (input.tagName === 'SELECT') {
|
|
852
949
|
this.populateSelectOptions(input, newOptions, '');
|
|
853
950
|
} else if (input.tagName === 'INPUT' && input.list) {
|
|
854
951
|
this.populateDatalistOptions(input.list, newOptions);
|
|
855
952
|
}
|
|
856
953
|
|
|
857
|
-
// at the end of the event chain: trigger change so other depending fields are notified too
|
|
858
|
-
// we don't do this without setTimeout so it triggers after the current loop is finished
|
|
859
|
-
// otherwise the change might trigger too soon
|
|
860
954
|
setTimeout(() => {
|
|
861
955
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
862
956
|
}, 0);
|
|
@@ -1176,7 +1270,7 @@ class FTableFormBuilder {
|
|
|
1176
1270
|
const select = FTableDOMHelper.create('select', { attributes });
|
|
1177
1271
|
|
|
1178
1272
|
if (field.options) {
|
|
1179
|
-
//
|
|
1273
|
+
// the field options are already the resolved ones
|
|
1180
1274
|
this.populateSelectOptions(select, field.options, value);
|
|
1181
1275
|
}
|
|
1182
1276
|
|
|
@@ -1736,53 +1830,45 @@ class FTable extends FTableEventEmitter {
|
|
|
1736
1830
|
}
|
|
1737
1831
|
|
|
1738
1832
|
async resolveAsyncFieldOptions() {
|
|
1739
|
-
|
|
1740
|
-
this.formBuilder.storeOriginalFieldOptions();
|
|
1741
|
-
|
|
1742
|
-
for (const fieldName of this.columnList) {
|
|
1833
|
+
const promises = this.columnList.map(async (fieldName) => {
|
|
1743
1834
|
const field = this.options.fields[fieldName];
|
|
1835
|
+
const originalOptions = this.formBuilder.originalFieldOptions.get(fieldName);
|
|
1744
1836
|
|
|
1745
|
-
|
|
1746
|
-
const originalOptions = this.formBuilder.originalFieldOptions.get(fieldName) || field.options;
|
|
1747
|
-
|
|
1748
|
-
if (originalOptions &&
|
|
1749
|
-
(typeof originalOptions === 'function' || typeof originalOptions === 'string') &&
|
|
1750
|
-
!Array.isArray(originalOptions) &&
|
|
1751
|
-
!(typeof originalOptions === 'object' && !Array.isArray(originalOptions) && Object.keys(originalOptions).length > 0)
|
|
1752
|
-
) {
|
|
1837
|
+
if (this.formBuilder.shouldResolveOptions(originalOptions)) {
|
|
1753
1838
|
try {
|
|
1754
|
-
//
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1839
|
+
// Check if already resolved to avoid duplicate work
|
|
1840
|
+
const cacheKey = this.formBuilder.generateOptionsCacheKey('table', {});
|
|
1841
|
+
if (!this.formBuilder.resolvedFieldOptions.get(fieldName)?.[cacheKey]) {
|
|
1842
|
+
await this.formBuilder.getFieldOptions(fieldName, 'table');
|
|
1843
|
+
}
|
|
1758
1844
|
} catch (err) {
|
|
1759
|
-
console.error(`Failed to resolve options for ${fieldName}:`, err);
|
|
1845
|
+
console.error(`Failed to resolve table options for ${fieldName}:`, err);
|
|
1760
1846
|
}
|
|
1761
1847
|
}
|
|
1762
|
-
}
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
await Promise.all(promises);
|
|
1851
|
+
// DON'T call refreshDisplayValues() here - let renderTableData do it
|
|
1763
1852
|
}
|
|
1764
1853
|
|
|
1765
|
-
refreshDisplayValues() {
|
|
1854
|
+
async refreshDisplayValues() {
|
|
1766
1855
|
const rows = this.elements.tableBody.querySelectorAll('.ftable-data-row');
|
|
1767
1856
|
if (rows.length === 0) return;
|
|
1768
1857
|
|
|
1769
|
-
|
|
1770
|
-
this.columnList
|
|
1858
|
+
for (const row of rows) {
|
|
1859
|
+
for (const fieldName of this.columnList) {
|
|
1771
1860
|
const field = this.options.fields[fieldName];
|
|
1772
|
-
if (!field.options)
|
|
1773
|
-
|
|
1774
|
-
// Check if options are now resolved (was a function/string before)
|
|
1775
|
-
if (typeof field.options === 'function' || typeof field.options === 'string') {
|
|
1776
|
-
return; // Still unresolved
|
|
1777
|
-
}
|
|
1861
|
+
if (!field.options) continue;
|
|
1778
1862
|
|
|
1779
1863
|
const cell = row.querySelector(`td[data-field-name="${fieldName}"]`);
|
|
1780
|
-
if (!cell)
|
|
1864
|
+
if (!cell) continue;
|
|
1781
1865
|
|
|
1782
|
-
|
|
1866
|
+
// Get table-specific options
|
|
1867
|
+
const options = await this.formBuilder.getFieldOptions(fieldName, 'table');
|
|
1868
|
+
const value = this.getDisplayText(row.recordData, fieldName, options);
|
|
1783
1869
|
cell.innerHTML = field.listEscapeHTML ? FTableDOMHelper.escapeHtml(value) : value;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1786
1872
|
}
|
|
1787
1873
|
|
|
1788
1874
|
createMainStructure() {
|
|
@@ -1985,7 +2071,7 @@ class FTable extends FTableEventEmitter {
|
|
|
1985
2071
|
case 'datetime-local':
|
|
1986
2072
|
if (typeof FDatepicker !== 'undefined') {
|
|
1987
2073
|
const dateFormat = field.dateFormat || this.options.defaultDateFormat;
|
|
1988
|
-
|
|
2074
|
+
const containerDiv = document.createElement('div');
|
|
1989
2075
|
// Create hidden input
|
|
1990
2076
|
const hiddenInput = FTableDOMHelper.create('input', {
|
|
1991
2077
|
className: 'ftable-toolbarsearch-extra',
|
|
@@ -2006,8 +2092,8 @@ class FTable extends FTableEventEmitter {
|
|
|
2006
2092
|
}
|
|
2007
2093
|
});
|
|
2008
2094
|
// Append both inputs
|
|
2009
|
-
|
|
2010
|
-
|
|
2095
|
+
containerDiv.appendChild(hiddenInput);
|
|
2096
|
+
containerDiv.appendChild(visibleInput);
|
|
2011
2097
|
|
|
2012
2098
|
// Apply FDatepicker
|
|
2013
2099
|
const picker = new FDatepicker(visibleInput, {
|
|
@@ -2016,6 +2102,8 @@ class FTable extends FTableEventEmitter {
|
|
|
2016
2102
|
altFormat: 'Y-m-d'
|
|
2017
2103
|
});
|
|
2018
2104
|
|
|
2105
|
+
input = containerDiv;
|
|
2106
|
+
|
|
2019
2107
|
} else {
|
|
2020
2108
|
input = FTableDOMHelper.create('input', {
|
|
2021
2109
|
className: 'ftable-toolbarsearch',
|
|
@@ -2136,7 +2224,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2136
2224
|
DisplayText: displayText
|
|
2137
2225
|
}));
|
|
2138
2226
|
} else if (field.options) {
|
|
2139
|
-
optionsSource = await this.formBuilder.resolveOptions(field);
|
|
2227
|
+
optionsSource = await this.formBuilder.resolveOptions(field, {}, 'search');
|
|
2140
2228
|
}
|
|
2141
2229
|
|
|
2142
2230
|
// Add empty option only if first option is not already empty
|
|
@@ -3175,9 +3263,10 @@ class FTable extends FTableEventEmitter {
|
|
|
3175
3263
|
});
|
|
3176
3264
|
}
|
|
3177
3265
|
|
|
3178
|
-
getDisplayText(record, fieldName) {
|
|
3266
|
+
getDisplayText(record, fieldName, customOptions = null) {
|
|
3179
3267
|
const field = this.options.fields[fieldName];
|
|
3180
3268
|
const value = record[fieldName];
|
|
3269
|
+
const options = customOptions || field.options;
|
|
3181
3270
|
|
|
3182
3271
|
if (field.display && typeof field.display === 'function') {
|
|
3183
3272
|
return field.display({ record, value });
|
|
@@ -3203,8 +3292,8 @@ class FTable extends FTableEventEmitter {
|
|
|
3203
3292
|
return this.getCheckboxText(fieldName, value);
|
|
3204
3293
|
}
|
|
3205
3294
|
|
|
3206
|
-
if (
|
|
3207
|
-
const option = this.findOptionByValue(
|
|
3295
|
+
if (options) {
|
|
3296
|
+
const option = this.findOptionByValue(options, value);
|
|
3208
3297
|
return option ? option.DisplayText || option.text || option : value;
|
|
3209
3298
|
}
|
|
3210
3299
|
|