@aemforms/af-core 0.22.74 → 0.22.76
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/esm/afb-runtime.js +685 -638
- package/esm/types/src/BaseNode.d.ts +4 -2
- package/esm/types/src/Checkbox.d.ts +2 -1
- package/esm/types/src/Container.d.ts +4 -2
- package/esm/types/src/Field.d.ts +3 -1
- package/esm/types/src/Form.d.ts +4 -2
- package/esm/types/src/types/Json.d.ts +1 -0
- package/esm/types/src/types/Model.d.ts +1 -0
- package/esm/types/src/utils/ValidationUtils.d.ts +2 -1
- package/lib/BaseNode.d.ts +4 -2
- package/lib/BaseNode.js +9 -1
- package/lib/Checkbox.d.ts +2 -1
- package/lib/Container.d.ts +4 -2
- package/lib/Field.d.ts +3 -1
- package/lib/Field.js +6 -1
- package/lib/Form.d.ts +4 -2
- package/lib/rules/FunctionRuntime.js +39 -5
- package/lib/types/Json.d.ts +1 -0
- package/lib/types/Model.d.ts +1 -0
- package/lib/utils/ValidationUtils.d.ts +2 -1
- package/lib/utils/ValidationUtils.js +3 -1
- package/package.json +2 -2
package/esm/afb-runtime.js
CHANGED
|
@@ -578,6 +578,529 @@ const resolveData = (data, input, create) => {
|
|
|
578
578
|
return result;
|
|
579
579
|
};
|
|
580
580
|
|
|
581
|
+
class FileObject {
|
|
582
|
+
data;
|
|
583
|
+
mediaType = 'application/octet-stream';
|
|
584
|
+
name = 'unknown';
|
|
585
|
+
size = 0;
|
|
586
|
+
constructor(init) {
|
|
587
|
+
Object.assign(this, init);
|
|
588
|
+
}
|
|
589
|
+
get type() {
|
|
590
|
+
return this.mediaType;
|
|
591
|
+
}
|
|
592
|
+
toJSON() {
|
|
593
|
+
return {
|
|
594
|
+
'name': this.name,
|
|
595
|
+
'size': this.size,
|
|
596
|
+
'mediaType': this.mediaType,
|
|
597
|
+
'data': this.data.toString()
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
equals(obj) {
|
|
601
|
+
return (this.data === obj.data &&
|
|
602
|
+
this.mediaType === obj.mediaType &&
|
|
603
|
+
this.name === obj.name &&
|
|
604
|
+
this.size === obj.size);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'.split('');
|
|
609
|
+
const fileSizeRegex = /^(\d*\.?\d+)(\\?(?=[KMGT])([KMGT])(?:i?B)?|B?)$/i;
|
|
610
|
+
const randomWord = (l) => {
|
|
611
|
+
const ret = [];
|
|
612
|
+
for (let i = 0; i <= l; i++) {
|
|
613
|
+
let randIndex;
|
|
614
|
+
if (i === 0) {
|
|
615
|
+
randIndex = Math.floor(Math.random() * (chars.length - 11));
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
randIndex = Math.floor(Math.random() * (chars.length));
|
|
619
|
+
}
|
|
620
|
+
ret.push(chars[randIndex]);
|
|
621
|
+
}
|
|
622
|
+
return ret.join('');
|
|
623
|
+
};
|
|
624
|
+
const getAttachments = (input, excludeUnbound = false) => {
|
|
625
|
+
const items = input.items || [];
|
|
626
|
+
return items?.reduce((acc, item) => {
|
|
627
|
+
if (excludeUnbound && item.dataRef === null) {
|
|
628
|
+
return acc;
|
|
629
|
+
}
|
|
630
|
+
let ret = null;
|
|
631
|
+
if (item.isContainer) {
|
|
632
|
+
ret = getAttachments(item, excludeUnbound);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
if (isFile(item.getState())) {
|
|
636
|
+
ret = {};
|
|
637
|
+
const name = item.name || '';
|
|
638
|
+
const dataRef = (item.dataRef != null)
|
|
639
|
+
? item.dataRef
|
|
640
|
+
: (name.length > 0 ? item.name : undefined);
|
|
641
|
+
if (item.value instanceof Array) {
|
|
642
|
+
ret[item.id] = item.value.map((x) => {
|
|
643
|
+
return { ...x, 'dataRef': dataRef };
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
else if (item.value != null) {
|
|
647
|
+
ret[item.id] = { ...item.value, 'dataRef': dataRef };
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return Object.assign(acc, ret);
|
|
652
|
+
}, {});
|
|
653
|
+
};
|
|
654
|
+
const getFileSizeInBytes = (str) => {
|
|
655
|
+
let retVal = 0;
|
|
656
|
+
if (typeof str === 'string') {
|
|
657
|
+
const matches = fileSizeRegex.exec(str.trim());
|
|
658
|
+
if (matches != null) {
|
|
659
|
+
retVal = sizeToBytes(parseFloat(matches[1]), (matches[2] || 'kb').toUpperCase());
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return retVal;
|
|
663
|
+
};
|
|
664
|
+
const sizeToBytes = (size, symbol) => {
|
|
665
|
+
const sizes = { 'KB': 1, 'MB': 2, 'GB': 3, 'TB': 4 };
|
|
666
|
+
const i = Math.pow(1024, sizes[symbol]);
|
|
667
|
+
return Math.round(size * i);
|
|
668
|
+
};
|
|
669
|
+
const IdGenerator = function* (initial = 50) {
|
|
670
|
+
const initialize = function () {
|
|
671
|
+
const arr = [];
|
|
672
|
+
for (let i = 0; i < initial; i++) {
|
|
673
|
+
arr.push(randomWord(10));
|
|
674
|
+
}
|
|
675
|
+
return arr;
|
|
676
|
+
};
|
|
677
|
+
const passedIds = {};
|
|
678
|
+
let ids = initialize();
|
|
679
|
+
do {
|
|
680
|
+
let x = ids.pop();
|
|
681
|
+
while (x in passedIds) {
|
|
682
|
+
if (ids.length === 0) {
|
|
683
|
+
ids = initialize();
|
|
684
|
+
}
|
|
685
|
+
x = ids.pop();
|
|
686
|
+
}
|
|
687
|
+
passedIds[x] = true;
|
|
688
|
+
yield ids.pop();
|
|
689
|
+
if (ids.length === 0) {
|
|
690
|
+
ids = initialize();
|
|
691
|
+
}
|
|
692
|
+
} while (ids.length > 0);
|
|
693
|
+
};
|
|
694
|
+
const isDataUrl = (str) => {
|
|
695
|
+
const dataUrlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)?;(?:name=(.*);)?base64,(.*)$/;
|
|
696
|
+
return dataUrlRegex.exec(str.trim()) != null;
|
|
697
|
+
};
|
|
698
|
+
const extractFileInfo = (file) => {
|
|
699
|
+
if (file !== null) {
|
|
700
|
+
let retVal = null;
|
|
701
|
+
if (file instanceof FileObject) {
|
|
702
|
+
retVal = file;
|
|
703
|
+
}
|
|
704
|
+
else if (typeof File !== 'undefined' && file instanceof File) {
|
|
705
|
+
retVal = {
|
|
706
|
+
name: file.name,
|
|
707
|
+
mediaType: file.type,
|
|
708
|
+
size: file.size,
|
|
709
|
+
data: file
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
else if (typeof file === 'string' && isDataUrl(file)) {
|
|
713
|
+
const result = dataURItoBlob(file);
|
|
714
|
+
if (result !== null) {
|
|
715
|
+
const { blob, name } = result;
|
|
716
|
+
retVal = {
|
|
717
|
+
name: name,
|
|
718
|
+
mediaType: blob.type,
|
|
719
|
+
size: blob.size,
|
|
720
|
+
data: blob
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
let jFile = file;
|
|
726
|
+
try {
|
|
727
|
+
jFile = JSON.parse(file);
|
|
728
|
+
retVal = jFile;
|
|
729
|
+
if (!retVal.mediaType) {
|
|
730
|
+
retVal.mediaType = retVal.type;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
catch (ex) {
|
|
734
|
+
}
|
|
735
|
+
if (typeof jFile?.data === 'string' && isDataUrl(jFile?.data)) {
|
|
736
|
+
const result = dataURItoBlob(jFile?.data);
|
|
737
|
+
if (result !== null) {
|
|
738
|
+
const blob = result.blob;
|
|
739
|
+
retVal = {
|
|
740
|
+
name: jFile?.name,
|
|
741
|
+
mediaType: jFile?.type || jFile?.mediaType,
|
|
742
|
+
size: blob.size,
|
|
743
|
+
data: blob
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else if (typeof jFile === 'string') {
|
|
748
|
+
const fileName = jFile.split('/').pop();
|
|
749
|
+
retVal = {
|
|
750
|
+
name: fileName,
|
|
751
|
+
mediaType: 'application/octet-stream',
|
|
752
|
+
size: 0,
|
|
753
|
+
data: jFile
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
else if (typeof jFile === 'object') {
|
|
757
|
+
retVal = {
|
|
758
|
+
name: jFile?.name,
|
|
759
|
+
mediaType: jFile?.type || jFile?.mediaType,
|
|
760
|
+
size: jFile?.size,
|
|
761
|
+
data: jFile?.data
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (retVal !== null && retVal.data != null) {
|
|
766
|
+
return new FileObject(retVal);
|
|
767
|
+
}
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
const dataURItoBlob = (dataURI) => {
|
|
775
|
+
const regex = /^data:([a-z]+\/[a-z0-9-+.]+)?(?:;name=([^;]+))?(;base64)?,(.+)$/;
|
|
776
|
+
const groups = regex.exec(dataURI);
|
|
777
|
+
if (groups !== null) {
|
|
778
|
+
const type = groups[1] || '';
|
|
779
|
+
const name = groups[2] || 'unknown';
|
|
780
|
+
const isBase64 = typeof groups[3] === 'string';
|
|
781
|
+
if (isBase64) {
|
|
782
|
+
const binary = atob(groups[4]);
|
|
783
|
+
const array = [];
|
|
784
|
+
for (let i = 0; i < binary.length; i++) {
|
|
785
|
+
array.push(binary.charCodeAt(i));
|
|
786
|
+
}
|
|
787
|
+
const blob = new window.Blob([new Uint8Array(array)], { type });
|
|
788
|
+
return { name, blob };
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
const blob = new window.Blob([groups[4]], { type });
|
|
792
|
+
return { name, blob };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
const isFormOrSiteContainer = (model) => {
|
|
800
|
+
return (':items' in model || 'cqItems' in model) && (':itemsOrder' in model || 'cqItemsOrder' in model);
|
|
801
|
+
};
|
|
802
|
+
const sitesModelToFormModel = (sitesModel) => {
|
|
803
|
+
if (!sitesModel || !Object.keys(sitesModel).length) {
|
|
804
|
+
return sitesModel;
|
|
805
|
+
}
|
|
806
|
+
if (isFormOrSiteContainer(sitesModel)) {
|
|
807
|
+
const itemsArr = [];
|
|
808
|
+
const itemsOrder = sitesModel[':itemsOrder'] || sitesModel.cqItemsOrder;
|
|
809
|
+
const items = sitesModel[':items'] || sitesModel.cqItems;
|
|
810
|
+
itemsOrder.forEach((elemName) => {
|
|
811
|
+
itemsArr.push(sitesModelToFormModel(items[elemName]));
|
|
812
|
+
});
|
|
813
|
+
sitesModel.items = itemsArr;
|
|
814
|
+
}
|
|
815
|
+
return sitesModel;
|
|
816
|
+
};
|
|
817
|
+
const replaceTemplatePlaceholders = (str, values = []) => {
|
|
818
|
+
return str?.replace(/\${(\d+)}/g, (match, index) => {
|
|
819
|
+
const replacement = values[index];
|
|
820
|
+
return typeof replacement !== 'undefined' ? replacement : match;
|
|
821
|
+
});
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
const dateRegex = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
825
|
+
const emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
|
|
826
|
+
const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
827
|
+
const daysInMonth = (leapYear, month) => {
|
|
828
|
+
if (leapYear && month == 2) {
|
|
829
|
+
return 29;
|
|
830
|
+
}
|
|
831
|
+
return days[month - 1];
|
|
832
|
+
};
|
|
833
|
+
const isLeapYear = (year) => {
|
|
834
|
+
return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;
|
|
835
|
+
};
|
|
836
|
+
const coerceType = (param, type) => {
|
|
837
|
+
let num;
|
|
838
|
+
switch (type) {
|
|
839
|
+
case 'string':
|
|
840
|
+
return param + '';
|
|
841
|
+
case 'number':
|
|
842
|
+
num = +param;
|
|
843
|
+
if (!isNaN(num)) {
|
|
844
|
+
return num;
|
|
845
|
+
}
|
|
846
|
+
break;
|
|
847
|
+
case 'boolean':
|
|
848
|
+
if (typeof param === 'string') {
|
|
849
|
+
return param === 'true';
|
|
850
|
+
}
|
|
851
|
+
else if (typeof param === 'number') {
|
|
852
|
+
return param !== 0;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
throw `${param} has invalid type. Expected : ${type}, Actual ${typeof param}`;
|
|
856
|
+
};
|
|
857
|
+
const checkNumber = (inputVal) => {
|
|
858
|
+
if (inputVal === '' || inputVal == null) {
|
|
859
|
+
return {
|
|
860
|
+
value: '', valid: true
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
let value = parseFloat(inputVal);
|
|
864
|
+
const valid = !isNaN(value);
|
|
865
|
+
if (!valid) {
|
|
866
|
+
value = inputVal;
|
|
867
|
+
}
|
|
868
|
+
return {
|
|
869
|
+
value, valid
|
|
870
|
+
};
|
|
871
|
+
};
|
|
872
|
+
const checkInteger = (inputVal) => {
|
|
873
|
+
if (inputVal == '' || inputVal == null) {
|
|
874
|
+
return {
|
|
875
|
+
value: '', valid: true
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
let value = parseFloat(inputVal);
|
|
879
|
+
const valid = !isNaN(value) && Math.round(value) === value;
|
|
880
|
+
if (!valid) {
|
|
881
|
+
value = inputVal;
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
value, valid
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
const toArray = (inputVal) => {
|
|
888
|
+
if (inputVal != null && !(inputVal instanceof Array)) {
|
|
889
|
+
return [inputVal];
|
|
890
|
+
}
|
|
891
|
+
return inputVal;
|
|
892
|
+
};
|
|
893
|
+
const checkBool = (inputVal) => {
|
|
894
|
+
const valid = typeof inputVal === 'boolean' || inputVal === 'true' || inputVal === 'false';
|
|
895
|
+
const value = typeof inputVal === 'boolean' ? inputVal : (valid ? inputVal === 'true' : inputVal);
|
|
896
|
+
return { valid, value };
|
|
897
|
+
};
|
|
898
|
+
const checkFile = (inputVal) => {
|
|
899
|
+
const value = extractFileInfo(inputVal);
|
|
900
|
+
const valid = value !== null;
|
|
901
|
+
return {
|
|
902
|
+
value: valid ? value : inputVal,
|
|
903
|
+
valid
|
|
904
|
+
};
|
|
905
|
+
};
|
|
906
|
+
const matchMediaType = (mediaType, accepts) => {
|
|
907
|
+
return !mediaType || accepts.some((accept) => {
|
|
908
|
+
const trimmedAccept = accept.trim();
|
|
909
|
+
const prefixAccept = trimmedAccept.split('/')[0];
|
|
910
|
+
const suffixAccept = trimmedAccept.split('.')[1];
|
|
911
|
+
return ((trimmedAccept.includes('*') && mediaType.startsWith(prefixAccept)) ||
|
|
912
|
+
(trimmedAccept.includes('.') && mediaType.endsWith(suffixAccept)) ||
|
|
913
|
+
(trimmedAccept === mediaType));
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
const partitionArray = (inputVal, validatorFn) => {
|
|
917
|
+
const value = toArray(inputVal);
|
|
918
|
+
if (value == null) {
|
|
919
|
+
return [[], [value]];
|
|
920
|
+
}
|
|
921
|
+
return value.reduce((acc, x) => {
|
|
922
|
+
if (acc[1].length == 0) {
|
|
923
|
+
const r = validatorFn(x);
|
|
924
|
+
const index = r.valid ? 0 : 1;
|
|
925
|
+
acc[index].push(r.value);
|
|
926
|
+
}
|
|
927
|
+
return acc;
|
|
928
|
+
}, [[], []]);
|
|
929
|
+
};
|
|
930
|
+
const ValidConstraints = {
|
|
931
|
+
date: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'format'],
|
|
932
|
+
string: ['minLength', 'maxLength', 'pattern'],
|
|
933
|
+
number: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum'],
|
|
934
|
+
array: ['minItems', 'maxItems', 'uniqueItems'],
|
|
935
|
+
file: ['accept', 'maxFileSize'],
|
|
936
|
+
email: ['minLength', 'maxLength', 'format', 'pattern']
|
|
937
|
+
};
|
|
938
|
+
const validationConstraintsList = ['type', 'format', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'minItems',
|
|
939
|
+
'maxItems', 'uniqueItems', 'minLength', 'maxLength', 'pattern', 'required', 'enum', 'accept', 'maxFileSize'];
|
|
940
|
+
const Constraints = {
|
|
941
|
+
type: (constraint, inputVal) => {
|
|
942
|
+
let value = inputVal;
|
|
943
|
+
if (inputVal == undefined) {
|
|
944
|
+
return {
|
|
945
|
+
valid: true,
|
|
946
|
+
value: inputVal
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
let valid = true, res;
|
|
950
|
+
switch (constraint) {
|
|
951
|
+
case 'string':
|
|
952
|
+
valid = true;
|
|
953
|
+
value = inputVal.toString();
|
|
954
|
+
break;
|
|
955
|
+
case 'string[]':
|
|
956
|
+
value = toArray(inputVal);
|
|
957
|
+
break;
|
|
958
|
+
case 'number':
|
|
959
|
+
res = checkNumber(inputVal);
|
|
960
|
+
value = res.value;
|
|
961
|
+
valid = res.valid;
|
|
962
|
+
break;
|
|
963
|
+
case 'boolean':
|
|
964
|
+
res = checkBool(inputVal);
|
|
965
|
+
valid = res.valid;
|
|
966
|
+
value = res.value;
|
|
967
|
+
break;
|
|
968
|
+
case 'integer':
|
|
969
|
+
res = checkInteger(inputVal);
|
|
970
|
+
valid = res.valid;
|
|
971
|
+
value = res.value;
|
|
972
|
+
break;
|
|
973
|
+
case 'integer[]':
|
|
974
|
+
res = partitionArray(inputVal, checkInteger);
|
|
975
|
+
valid = res[1].length === 0;
|
|
976
|
+
value = valid ? res[0] : inputVal;
|
|
977
|
+
break;
|
|
978
|
+
case 'file':
|
|
979
|
+
res = checkFile(inputVal instanceof Array ? inputVal[0] : inputVal);
|
|
980
|
+
valid = res.valid;
|
|
981
|
+
value = res.value;
|
|
982
|
+
break;
|
|
983
|
+
case 'file[]':
|
|
984
|
+
res = partitionArray(inputVal, checkFile);
|
|
985
|
+
valid = res[1].length === 0;
|
|
986
|
+
value = valid ? res[0] : inputVal;
|
|
987
|
+
break;
|
|
988
|
+
case 'number[]':
|
|
989
|
+
res = partitionArray(inputVal, checkNumber);
|
|
990
|
+
valid = res[1].length === 0;
|
|
991
|
+
value = valid ? res[0] : inputVal;
|
|
992
|
+
break;
|
|
993
|
+
case 'boolean[]':
|
|
994
|
+
res = partitionArray(inputVal, checkBool);
|
|
995
|
+
valid = res[1].length === 0;
|
|
996
|
+
value = valid ? res[0] : inputVal;
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
valid,
|
|
1001
|
+
value
|
|
1002
|
+
};
|
|
1003
|
+
},
|
|
1004
|
+
format: (constraint, input) => {
|
|
1005
|
+
let valid = true;
|
|
1006
|
+
const value = input;
|
|
1007
|
+
if (input === null) {
|
|
1008
|
+
return { value, valid };
|
|
1009
|
+
}
|
|
1010
|
+
let res;
|
|
1011
|
+
switch (constraint) {
|
|
1012
|
+
case 'date':
|
|
1013
|
+
res = dateRegex.exec((input || '').trim());
|
|
1014
|
+
if (res != null) {
|
|
1015
|
+
const [match, year, month, date] = res;
|
|
1016
|
+
const [nMonth, nDate] = [+month, +date];
|
|
1017
|
+
const leapYear = isLeapYear(+year);
|
|
1018
|
+
valid = (nMonth >= 1 && nMonth <= 12) &&
|
|
1019
|
+
(nDate >= 1 && nDate <= daysInMonth(leapYear, nMonth));
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
valid = false;
|
|
1023
|
+
}
|
|
1024
|
+
break;
|
|
1025
|
+
case 'email':
|
|
1026
|
+
valid = new RegExp(emailRegex).test((input || '').trim());
|
|
1027
|
+
break;
|
|
1028
|
+
case 'data-url':
|
|
1029
|
+
valid = true;
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
return { valid, value };
|
|
1033
|
+
},
|
|
1034
|
+
minimum: (constraint, value) => {
|
|
1035
|
+
return { valid: value >= constraint, value };
|
|
1036
|
+
},
|
|
1037
|
+
maximum: (constraint, value) => {
|
|
1038
|
+
return { valid: value <= constraint, value };
|
|
1039
|
+
},
|
|
1040
|
+
exclusiveMinimum: (constraint, value) => {
|
|
1041
|
+
return { valid: value > constraint, value };
|
|
1042
|
+
},
|
|
1043
|
+
exclusiveMaximum: (constraint, value) => {
|
|
1044
|
+
return { valid: value < constraint, value };
|
|
1045
|
+
},
|
|
1046
|
+
minItems: (constraint, value) => {
|
|
1047
|
+
return { valid: (value instanceof Array) && value.length >= constraint, value };
|
|
1048
|
+
},
|
|
1049
|
+
maxItems: (constraint, value) => {
|
|
1050
|
+
return { valid: (value instanceof Array) && value.length <= constraint, value };
|
|
1051
|
+
},
|
|
1052
|
+
uniqueItems: (constraint, value) => {
|
|
1053
|
+
return { valid: !constraint || ((value instanceof Array) && value.length === new Set(value).size), value };
|
|
1054
|
+
},
|
|
1055
|
+
minLength: (constraint, value) => {
|
|
1056
|
+
return { ...Constraints.minimum(constraint, typeof value === 'string' ? value.length : 0), value };
|
|
1057
|
+
},
|
|
1058
|
+
maxLength: (constraint, value) => {
|
|
1059
|
+
return { ...Constraints.maximum(constraint, typeof value === 'string' ? value.length : 0), value };
|
|
1060
|
+
},
|
|
1061
|
+
pattern: (constraint, value) => {
|
|
1062
|
+
let regex;
|
|
1063
|
+
if (typeof constraint === 'string') {
|
|
1064
|
+
regex = new RegExp(constraint);
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
regex = constraint;
|
|
1068
|
+
}
|
|
1069
|
+
return { valid: regex.test(value), value };
|
|
1070
|
+
},
|
|
1071
|
+
required: (constraint, value) => {
|
|
1072
|
+
const valid = constraint ? value != null && value !== '' : true;
|
|
1073
|
+
return { valid, value };
|
|
1074
|
+
},
|
|
1075
|
+
enum: (constraint, value) => {
|
|
1076
|
+
return {
|
|
1077
|
+
valid: constraint.indexOf(value) > -1,
|
|
1078
|
+
value
|
|
1079
|
+
};
|
|
1080
|
+
},
|
|
1081
|
+
accept: (constraint, value) => {
|
|
1082
|
+
if (!constraint || constraint.length === 0 || value === null || value === undefined) {
|
|
1083
|
+
return {
|
|
1084
|
+
valid: true,
|
|
1085
|
+
value
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
const tempValue = value instanceof Array ? value : [value];
|
|
1089
|
+
const invalidFile = tempValue.some((file) => !matchMediaType(file.type, constraint));
|
|
1090
|
+
return {
|
|
1091
|
+
valid: !invalidFile,
|
|
1092
|
+
value
|
|
1093
|
+
};
|
|
1094
|
+
},
|
|
1095
|
+
maxFileSize: (constraint, value) => {
|
|
1096
|
+
const sizeLimit = typeof constraint === 'string' ? getFileSizeInBytes(constraint) : constraint;
|
|
1097
|
+
return {
|
|
1098
|
+
valid: !(value instanceof FileObject) || value.size <= sizeLimit,
|
|
1099
|
+
value
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
|
|
581
1104
|
const editableProperties = [
|
|
582
1105
|
'value',
|
|
583
1106
|
'label',
|
|
@@ -818,7 +1341,11 @@ class BaseNode {
|
|
|
818
1341
|
index: this.index,
|
|
819
1342
|
parent: undefined,
|
|
820
1343
|
qualifiedName: this.qualifiedName,
|
|
821
|
-
|
|
1344
|
+
...(this.repeatable === true ? {
|
|
1345
|
+
repeatable: true,
|
|
1346
|
+
minOccur: this.parent.minItems,
|
|
1347
|
+
maxOccur: this.parent.maxItems
|
|
1348
|
+
} : {}),
|
|
822
1349
|
':type': this[':type'],
|
|
823
1350
|
...(forRestore ? {
|
|
824
1351
|
_dependents: this._dependents.length ? this._dependents.map(x => x.node.id) : undefined,
|
|
@@ -904,6 +1431,9 @@ class BaseNode {
|
|
|
904
1431
|
this.notifyDependents(changeAction);
|
|
905
1432
|
}
|
|
906
1433
|
notifyChildren.call(this, changeAction);
|
|
1434
|
+
if (validationConstraintsList.includes(prop)) {
|
|
1435
|
+
this.validate();
|
|
1436
|
+
}
|
|
907
1437
|
return changeAction.payload.changes;
|
|
908
1438
|
}
|
|
909
1439
|
return [];
|
|
@@ -1844,250 +2374,7 @@ class EventQueue {
|
|
|
1844
2374
|
this._runningEventCount = {};
|
|
1845
2375
|
this._isProcessing = false;
|
|
1846
2376
|
}
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
class FileObject {
|
|
1850
|
-
data;
|
|
1851
|
-
mediaType = 'application/octet-stream';
|
|
1852
|
-
name = 'unknown';
|
|
1853
|
-
size = 0;
|
|
1854
|
-
constructor(init) {
|
|
1855
|
-
Object.assign(this, init);
|
|
1856
|
-
}
|
|
1857
|
-
get type() {
|
|
1858
|
-
return this.mediaType;
|
|
1859
|
-
}
|
|
1860
|
-
toJSON() {
|
|
1861
|
-
return {
|
|
1862
|
-
'name': this.name,
|
|
1863
|
-
'size': this.size,
|
|
1864
|
-
'mediaType': this.mediaType,
|
|
1865
|
-
'data': this.data.toString()
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
equals(obj) {
|
|
1869
|
-
return (this.data === obj.data &&
|
|
1870
|
-
this.mediaType === obj.mediaType &&
|
|
1871
|
-
this.name === obj.name &&
|
|
1872
|
-
this.size === obj.size);
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'.split('');
|
|
1877
|
-
const fileSizeRegex = /^(\d*\.?\d+)(\\?(?=[KMGT])([KMGT])(?:i?B)?|B?)$/i;
|
|
1878
|
-
const randomWord = (l) => {
|
|
1879
|
-
const ret = [];
|
|
1880
|
-
for (let i = 0; i <= l; i++) {
|
|
1881
|
-
let randIndex;
|
|
1882
|
-
if (i === 0) {
|
|
1883
|
-
randIndex = Math.floor(Math.random() * (chars.length - 11));
|
|
1884
|
-
}
|
|
1885
|
-
else {
|
|
1886
|
-
randIndex = Math.floor(Math.random() * (chars.length));
|
|
1887
|
-
}
|
|
1888
|
-
ret.push(chars[randIndex]);
|
|
1889
|
-
}
|
|
1890
|
-
return ret.join('');
|
|
1891
|
-
};
|
|
1892
|
-
const getAttachments = (input, excludeUnbound = false) => {
|
|
1893
|
-
const items = input.items || [];
|
|
1894
|
-
return items?.reduce((acc, item) => {
|
|
1895
|
-
if (excludeUnbound && item.dataRef === null) {
|
|
1896
|
-
return acc;
|
|
1897
|
-
}
|
|
1898
|
-
let ret = null;
|
|
1899
|
-
if (item.isContainer) {
|
|
1900
|
-
ret = getAttachments(item, excludeUnbound);
|
|
1901
|
-
}
|
|
1902
|
-
else {
|
|
1903
|
-
if (isFile(item.getState())) {
|
|
1904
|
-
ret = {};
|
|
1905
|
-
const name = item.name || '';
|
|
1906
|
-
const dataRef = (item.dataRef != null)
|
|
1907
|
-
? item.dataRef
|
|
1908
|
-
: (name.length > 0 ? item.name : undefined);
|
|
1909
|
-
if (item.value instanceof Array) {
|
|
1910
|
-
ret[item.id] = item.value.map((x) => {
|
|
1911
|
-
return { ...x, 'dataRef': dataRef };
|
|
1912
|
-
});
|
|
1913
|
-
}
|
|
1914
|
-
else if (item.value != null) {
|
|
1915
|
-
ret[item.id] = { ...item.value, 'dataRef': dataRef };
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
return Object.assign(acc, ret);
|
|
1920
|
-
}, {});
|
|
1921
|
-
};
|
|
1922
|
-
const getFileSizeInBytes = (str) => {
|
|
1923
|
-
let retVal = 0;
|
|
1924
|
-
if (typeof str === 'string') {
|
|
1925
|
-
const matches = fileSizeRegex.exec(str.trim());
|
|
1926
|
-
if (matches != null) {
|
|
1927
|
-
retVal = sizeToBytes(parseFloat(matches[1]), (matches[2] || 'kb').toUpperCase());
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
return retVal;
|
|
1931
|
-
};
|
|
1932
|
-
const sizeToBytes = (size, symbol) => {
|
|
1933
|
-
const sizes = { 'KB': 1, 'MB': 2, 'GB': 3, 'TB': 4 };
|
|
1934
|
-
const i = Math.pow(1024, sizes[symbol]);
|
|
1935
|
-
return Math.round(size * i);
|
|
1936
|
-
};
|
|
1937
|
-
const IdGenerator = function* (initial = 50) {
|
|
1938
|
-
const initialize = function () {
|
|
1939
|
-
const arr = [];
|
|
1940
|
-
for (let i = 0; i < initial; i++) {
|
|
1941
|
-
arr.push(randomWord(10));
|
|
1942
|
-
}
|
|
1943
|
-
return arr;
|
|
1944
|
-
};
|
|
1945
|
-
const passedIds = {};
|
|
1946
|
-
let ids = initialize();
|
|
1947
|
-
do {
|
|
1948
|
-
let x = ids.pop();
|
|
1949
|
-
while (x in passedIds) {
|
|
1950
|
-
if (ids.length === 0) {
|
|
1951
|
-
ids = initialize();
|
|
1952
|
-
}
|
|
1953
|
-
x = ids.pop();
|
|
1954
|
-
}
|
|
1955
|
-
passedIds[x] = true;
|
|
1956
|
-
yield ids.pop();
|
|
1957
|
-
if (ids.length === 0) {
|
|
1958
|
-
ids = initialize();
|
|
1959
|
-
}
|
|
1960
|
-
} while (ids.length > 0);
|
|
1961
|
-
};
|
|
1962
|
-
const isDataUrl = (str) => {
|
|
1963
|
-
const dataUrlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)?;(?:name=(.*);)?base64,(.*)$/;
|
|
1964
|
-
return dataUrlRegex.exec(str.trim()) != null;
|
|
1965
|
-
};
|
|
1966
|
-
const extractFileInfo = (file) => {
|
|
1967
|
-
if (file !== null) {
|
|
1968
|
-
let retVal = null;
|
|
1969
|
-
if (file instanceof FileObject) {
|
|
1970
|
-
retVal = file;
|
|
1971
|
-
}
|
|
1972
|
-
else if (typeof File !== 'undefined' && file instanceof File) {
|
|
1973
|
-
retVal = {
|
|
1974
|
-
name: file.name,
|
|
1975
|
-
mediaType: file.type,
|
|
1976
|
-
size: file.size,
|
|
1977
|
-
data: file
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
else if (typeof file === 'string' && isDataUrl(file)) {
|
|
1981
|
-
const result = dataURItoBlob(file);
|
|
1982
|
-
if (result !== null) {
|
|
1983
|
-
const { blob, name } = result;
|
|
1984
|
-
retVal = {
|
|
1985
|
-
name: name,
|
|
1986
|
-
mediaType: blob.type,
|
|
1987
|
-
size: blob.size,
|
|
1988
|
-
data: blob
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
else {
|
|
1993
|
-
let jFile = file;
|
|
1994
|
-
try {
|
|
1995
|
-
jFile = JSON.parse(file);
|
|
1996
|
-
retVal = jFile;
|
|
1997
|
-
if (!retVal.mediaType) {
|
|
1998
|
-
retVal.mediaType = retVal.type;
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
catch (ex) {
|
|
2002
|
-
}
|
|
2003
|
-
if (typeof jFile?.data === 'string' && isDataUrl(jFile?.data)) {
|
|
2004
|
-
const result = dataURItoBlob(jFile?.data);
|
|
2005
|
-
if (result !== null) {
|
|
2006
|
-
const blob = result.blob;
|
|
2007
|
-
retVal = {
|
|
2008
|
-
name: jFile?.name,
|
|
2009
|
-
mediaType: jFile?.type || jFile?.mediaType,
|
|
2010
|
-
size: blob.size,
|
|
2011
|
-
data: blob
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
else if (typeof jFile === 'string') {
|
|
2016
|
-
const fileName = jFile.split('/').pop();
|
|
2017
|
-
retVal = {
|
|
2018
|
-
name: fileName,
|
|
2019
|
-
mediaType: 'application/octet-stream',
|
|
2020
|
-
size: 0,
|
|
2021
|
-
data: jFile
|
|
2022
|
-
};
|
|
2023
|
-
}
|
|
2024
|
-
else if (typeof jFile === 'object') {
|
|
2025
|
-
retVal = {
|
|
2026
|
-
name: jFile?.name,
|
|
2027
|
-
mediaType: jFile?.type || jFile?.mediaType,
|
|
2028
|
-
size: jFile?.size,
|
|
2029
|
-
data: jFile?.data
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
if (retVal !== null && retVal.data != null) {
|
|
2034
|
-
return new FileObject(retVal);
|
|
2035
|
-
}
|
|
2036
|
-
return null;
|
|
2037
|
-
}
|
|
2038
|
-
else {
|
|
2039
|
-
return null;
|
|
2040
|
-
}
|
|
2041
|
-
};
|
|
2042
|
-
const dataURItoBlob = (dataURI) => {
|
|
2043
|
-
const regex = /^data:([a-z]+\/[a-z0-9-+.]+)?(?:;name=([^;]+))?(;base64)?,(.+)$/;
|
|
2044
|
-
const groups = regex.exec(dataURI);
|
|
2045
|
-
if (groups !== null) {
|
|
2046
|
-
const type = groups[1] || '';
|
|
2047
|
-
const name = groups[2] || 'unknown';
|
|
2048
|
-
const isBase64 = typeof groups[3] === 'string';
|
|
2049
|
-
if (isBase64) {
|
|
2050
|
-
const binary = atob(groups[4]);
|
|
2051
|
-
const array = [];
|
|
2052
|
-
for (let i = 0; i < binary.length; i++) {
|
|
2053
|
-
array.push(binary.charCodeAt(i));
|
|
2054
|
-
}
|
|
2055
|
-
const blob = new window.Blob([new Uint8Array(array)], { type });
|
|
2056
|
-
return { name, blob };
|
|
2057
|
-
}
|
|
2058
|
-
else {
|
|
2059
|
-
const blob = new window.Blob([groups[4]], { type });
|
|
2060
|
-
return { name, blob };
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
else {
|
|
2064
|
-
return null;
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2067
|
-
const isFormOrSiteContainer = (model) => {
|
|
2068
|
-
return (':items' in model || 'cqItems' in model) && (':itemsOrder' in model || 'cqItemsOrder' in model);
|
|
2069
|
-
};
|
|
2070
|
-
const sitesModelToFormModel = (sitesModel) => {
|
|
2071
|
-
if (!sitesModel || !Object.keys(sitesModel).length) {
|
|
2072
|
-
return sitesModel;
|
|
2073
|
-
}
|
|
2074
|
-
if (isFormOrSiteContainer(sitesModel)) {
|
|
2075
|
-
const itemsArr = [];
|
|
2076
|
-
const itemsOrder = sitesModel[':itemsOrder'] || sitesModel.cqItemsOrder;
|
|
2077
|
-
const items = sitesModel[':items'] || sitesModel.cqItems;
|
|
2078
|
-
itemsOrder.forEach((elemName) => {
|
|
2079
|
-
itemsArr.push(sitesModelToFormModel(items[elemName]));
|
|
2080
|
-
});
|
|
2081
|
-
sitesModel.items = itemsArr;
|
|
2082
|
-
}
|
|
2083
|
-
return sitesModel;
|
|
2084
|
-
};
|
|
2085
|
-
const replaceTemplatePlaceholders = (str, values = []) => {
|
|
2086
|
-
return str?.replace(/\${(\d+)}/g, (match, index) => {
|
|
2087
|
-
const replacement = values[index];
|
|
2088
|
-
return typeof replacement !== 'undefined' ? replacement : match;
|
|
2089
|
-
});
|
|
2090
|
-
};
|
|
2377
|
+
}
|
|
2091
2378
|
|
|
2092
2379
|
const request$1 = (url, data = null, options = {}) => {
|
|
2093
2380
|
const opts = { ...defaultRequestOptions, ...options };
|
|
@@ -2336,6 +2623,26 @@ class FunctionRuntimeImpl {
|
|
|
2336
2623
|
},
|
|
2337
2624
|
exportData: () => {
|
|
2338
2625
|
return FunctionRuntimeImpl.getInstance().getFunctions().exportData._func.call(undefined, args, data, interpreter);
|
|
2626
|
+
},
|
|
2627
|
+
submitForm: (payload, validateForm, contentType) => {
|
|
2628
|
+
const submitAs = contentType || 'multipart/form-data';
|
|
2629
|
+
const args = [payload, validateForm, submitAs];
|
|
2630
|
+
return FunctionRuntimeImpl.getInstance().getFunctions().submitForm._func.call(undefined, args, data, interpreter);
|
|
2631
|
+
},
|
|
2632
|
+
markFieldAsInvalid: (fieldIdentifier, validationMessage, option) => {
|
|
2633
|
+
if (!option || option.useId) {
|
|
2634
|
+
interpreter.globals.form.getElement(fieldIdentifier)?.markAsInvalid(validationMessage);
|
|
2635
|
+
}
|
|
2636
|
+
else if (option && option.useDataRef) {
|
|
2637
|
+
interpreter.globals.form.visit(function callback(f) {
|
|
2638
|
+
if (f.dataRef === fieldIdentifier) {
|
|
2639
|
+
f.markAsInvalid(validationMessage);
|
|
2640
|
+
}
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
else if (option && option.useQualifiedName) {
|
|
2644
|
+
interpreter.globals.form.resolveQualifiedName(fieldIdentifier)?.markAsInvalid(validationMessage);
|
|
2645
|
+
}
|
|
2339
2646
|
}
|
|
2340
2647
|
}
|
|
2341
2648
|
};
|
|
@@ -2437,11 +2744,24 @@ class FunctionRuntimeImpl {
|
|
|
2437
2744
|
},
|
|
2438
2745
|
submitForm: {
|
|
2439
2746
|
_func: (args, data, interpreter) => {
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2747
|
+
let success = null;
|
|
2748
|
+
let error = null;
|
|
2749
|
+
let submit_data;
|
|
2750
|
+
let validate_form;
|
|
2751
|
+
let submit_as;
|
|
2752
|
+
if (args.length > 0 && typeof valueOf(args[0]) === 'object') {
|
|
2753
|
+
submit_data = args.length > 0 ? valueOf(args[0]) : null;
|
|
2754
|
+
validate_form = args.length > 1 ? valueOf(args[1]) : true;
|
|
2755
|
+
submit_as = args.length > 2 ? toString(args[2]) : 'multipart/form-data';
|
|
2756
|
+
}
|
|
2757
|
+
else {
|
|
2758
|
+
interpreter.globals.form.logger.warn('This usage of submitForm is deprecated. Please see the documentation and update');
|
|
2759
|
+
success = toString(args[0]);
|
|
2760
|
+
error = toString(args[1]);
|
|
2761
|
+
submit_as = args.length > 2 ? toString(args[2]) : 'multipart/form-data';
|
|
2762
|
+
submit_data = args.length > 3 ? valueOf(args[3]) : null;
|
|
2763
|
+
validate_form = args.length > 4 ? valueOf(args[4]) : true;
|
|
2764
|
+
}
|
|
2445
2765
|
interpreter.globals.form.dispatch(new Submit({
|
|
2446
2766
|
success,
|
|
2447
2767
|
error,
|
|
@@ -2830,403 +3150,125 @@ function stringToNumber(str, language) {
|
|
|
2830
3150
|
}
|
|
2831
3151
|
|
|
2832
3152
|
function getStringToNumberFn(locale) {
|
|
2833
|
-
if (locale == null) {
|
|
2834
|
-
const localeOptions = new Intl.DateTimeFormat().resolvedOptions();
|
|
2835
|
-
locale = localeOptions.locale;
|
|
2836
|
-
}
|
|
2837
|
-
return (str) => stringToNumber(str, locale);
|
|
2838
|
-
}
|
|
2839
|
-
class RuleEngine {
|
|
2840
|
-
_context;
|
|
2841
|
-
_globalNames = [
|
|
2842
|
-
'$form',
|
|
2843
|
-
'$field',
|
|
2844
|
-
'$event'
|
|
2845
|
-
];
|
|
2846
|
-
customFunctions;
|
|
2847
|
-
debugInfo = [];
|
|
2848
|
-
constructor() {
|
|
2849
|
-
this.customFunctions = FunctionRuntime.getFunctions();
|
|
2850
|
-
}
|
|
2851
|
-
compileRule(rule, locale) {
|
|
2852
|
-
const formula = new Formula(this.customFunctions, getStringToNumberFn(locale), this.debugInfo);
|
|
2853
|
-
return { formula, ast: formula.compile(rule, this._globalNames) };
|
|
2854
|
-
}
|
|
2855
|
-
execute(node, data, globals, useValueOf = false) {
|
|
2856
|
-
const { formula, ast } = node;
|
|
2857
|
-
const oldContext = this._context;
|
|
2858
|
-
this._context = globals;
|
|
2859
|
-
let res = undefined;
|
|
2860
|
-
try {
|
|
2861
|
-
res = formula.run(ast, data, 'en-US', globals);
|
|
2862
|
-
}
|
|
2863
|
-
catch (err) {
|
|
2864
|
-
this._context?.form?.logger?.error(err);
|
|
2865
|
-
}
|
|
2866
|
-
while (this.debugInfo.length > 0) {
|
|
2867
|
-
this._context?.form?.logger?.debug(this.debugInfo.pop());
|
|
2868
|
-
}
|
|
2869
|
-
let finalRes = res;
|
|
2870
|
-
if (useValueOf) {
|
|
2871
|
-
if (typeof res === 'object' && res !== null) {
|
|
2872
|
-
finalRes = Object.getPrototypeOf(res).valueOf.call(res);
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
this._context = oldContext;
|
|
2876
|
-
return finalRes;
|
|
2877
|
-
}
|
|
2878
|
-
trackDependency(subscriber) {
|
|
2879
|
-
if (this._context && this._context.field !== undefined && this._context.field !== subscriber) {
|
|
2880
|
-
subscriber._addDependent(this._context.field);
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
class Fieldset extends Container {
|
|
2886
|
-
constructor(params, _options) {
|
|
2887
|
-
super(params, _options);
|
|
2888
|
-
if (_options.mode !== 'restore') {
|
|
2889
|
-
this._applyDefaults();
|
|
2890
|
-
this.queueEvent(new Initialize());
|
|
2891
|
-
this.queueEvent(new ExecuteRule());
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
_getDefaults() {
|
|
2895
|
-
return {
|
|
2896
|
-
...super._getDefaults(),
|
|
2897
|
-
visible: true,
|
|
2898
|
-
required: false,
|
|
2899
|
-
label: {
|
|
2900
|
-
visible: true,
|
|
2901
|
-
richText: false
|
|
2902
|
-
}
|
|
2903
|
-
};
|
|
2904
|
-
}
|
|
2905
|
-
_applyDefaults() {
|
|
2906
|
-
super._applyDefaultsInModel();
|
|
2907
|
-
if (this._jsonModel.dataRef && this._jsonModel.type === undefined) {
|
|
2908
|
-
this._jsonModel.type = 'object';
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
get type() {
|
|
2912
|
-
const ret = super.type;
|
|
2913
|
-
if (ret === 'array' || ret === 'object') {
|
|
2914
|
-
return ret;
|
|
2915
|
-
}
|
|
2916
|
-
return undefined;
|
|
2917
|
-
}
|
|
2918
|
-
get items() {
|
|
2919
|
-
return super.items;
|
|
2920
|
-
}
|
|
2921
|
-
get value() {
|
|
2922
|
-
return null;
|
|
2923
|
-
}
|
|
2924
|
-
get fieldType() {
|
|
2925
|
-
return 'panel';
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
class InstanceManager extends Fieldset {
|
|
2930
|
-
get maxOccur() {
|
|
2931
|
-
return this._jsonModel.maxItems;
|
|
2932
|
-
}
|
|
2933
|
-
set maxOccur(m) {
|
|
2934
|
-
this.maxItems = m;
|
|
2935
|
-
}
|
|
2936
|
-
get minOccur() {
|
|
2937
|
-
return this.minItems;
|
|
2938
|
-
}
|
|
2939
|
-
addInstance(action) {
|
|
2940
|
-
return this.addItem(action);
|
|
2941
|
-
}
|
|
2942
|
-
removeInstance(action) {
|
|
2943
|
-
return this.removeItem(action);
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
__decorate([
|
|
2947
|
-
dependencyTracked()
|
|
2948
|
-
], InstanceManager.prototype, "maxOccur", null);
|
|
2949
|
-
__decorate([
|
|
2950
|
-
dependencyTracked()
|
|
2951
|
-
], InstanceManager.prototype, "minOccur", null);
|
|
2952
|
-
|
|
2953
|
-
const dateRegex = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
2954
|
-
const emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
|
|
2955
|
-
const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
2956
|
-
const daysInMonth = (leapYear, month) => {
|
|
2957
|
-
if (leapYear && month == 2) {
|
|
2958
|
-
return 29;
|
|
2959
|
-
}
|
|
2960
|
-
return days[month - 1];
|
|
2961
|
-
};
|
|
2962
|
-
const isLeapYear = (year) => {
|
|
2963
|
-
return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;
|
|
2964
|
-
};
|
|
2965
|
-
const coerceType = (param, type) => {
|
|
2966
|
-
let num;
|
|
2967
|
-
switch (type) {
|
|
2968
|
-
case 'string':
|
|
2969
|
-
return param + '';
|
|
2970
|
-
case 'number':
|
|
2971
|
-
num = +param;
|
|
2972
|
-
if (!isNaN(num)) {
|
|
2973
|
-
return num;
|
|
2974
|
-
}
|
|
2975
|
-
break;
|
|
2976
|
-
case 'boolean':
|
|
2977
|
-
if (typeof param === 'string') {
|
|
2978
|
-
return param === 'true';
|
|
2979
|
-
}
|
|
2980
|
-
else if (typeof param === 'number') {
|
|
2981
|
-
return param !== 0;
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
throw `${param} has invalid type. Expected : ${type}, Actual ${typeof param}`;
|
|
2985
|
-
};
|
|
2986
|
-
const checkNumber = (inputVal) => {
|
|
2987
|
-
if (inputVal === '' || inputVal == null) {
|
|
2988
|
-
return {
|
|
2989
|
-
value: '', valid: true
|
|
2990
|
-
};
|
|
2991
|
-
}
|
|
2992
|
-
let value = parseFloat(inputVal);
|
|
2993
|
-
const valid = !isNaN(value);
|
|
2994
|
-
if (!valid) {
|
|
2995
|
-
value = inputVal;
|
|
2996
|
-
}
|
|
2997
|
-
return {
|
|
2998
|
-
value, valid
|
|
2999
|
-
};
|
|
3000
|
-
};
|
|
3001
|
-
const checkInteger = (inputVal) => {
|
|
3002
|
-
if (inputVal == '' || inputVal == null) {
|
|
3003
|
-
return {
|
|
3004
|
-
value: '', valid: true
|
|
3005
|
-
};
|
|
3006
|
-
}
|
|
3007
|
-
let value = parseFloat(inputVal);
|
|
3008
|
-
const valid = !isNaN(value) && Math.round(value) === value;
|
|
3009
|
-
if (!valid) {
|
|
3010
|
-
value = inputVal;
|
|
3011
|
-
}
|
|
3012
|
-
return {
|
|
3013
|
-
value, valid
|
|
3014
|
-
};
|
|
3015
|
-
};
|
|
3016
|
-
const toArray = (inputVal) => {
|
|
3017
|
-
if (inputVal != null && !(inputVal instanceof Array)) {
|
|
3018
|
-
return [inputVal];
|
|
3019
|
-
}
|
|
3020
|
-
return inputVal;
|
|
3021
|
-
};
|
|
3022
|
-
const checkBool = (inputVal) => {
|
|
3023
|
-
const valid = typeof inputVal === 'boolean' || inputVal === 'true' || inputVal === 'false';
|
|
3024
|
-
const value = typeof inputVal === 'boolean' ? inputVal : (valid ? inputVal === 'true' : inputVal);
|
|
3025
|
-
return { valid, value };
|
|
3026
|
-
};
|
|
3027
|
-
const checkFile = (inputVal) => {
|
|
3028
|
-
const value = extractFileInfo(inputVal);
|
|
3029
|
-
const valid = value !== null;
|
|
3030
|
-
return {
|
|
3031
|
-
value: valid ? value : inputVal,
|
|
3032
|
-
valid
|
|
3033
|
-
};
|
|
3034
|
-
};
|
|
3035
|
-
const matchMediaType = (mediaType, accepts) => {
|
|
3036
|
-
return !mediaType || accepts.some((accept) => {
|
|
3037
|
-
const trimmedAccept = accept.trim();
|
|
3038
|
-
const prefixAccept = trimmedAccept.split('/')[0];
|
|
3039
|
-
const suffixAccept = trimmedAccept.split('.')[1];
|
|
3040
|
-
return ((trimmedAccept.includes('*') && mediaType.startsWith(prefixAccept)) ||
|
|
3041
|
-
(trimmedAccept.includes('.') && mediaType.endsWith(suffixAccept)) ||
|
|
3042
|
-
(trimmedAccept === mediaType));
|
|
3043
|
-
});
|
|
3044
|
-
};
|
|
3045
|
-
const partitionArray = (inputVal, validatorFn) => {
|
|
3046
|
-
const value = toArray(inputVal);
|
|
3047
|
-
if (value == null) {
|
|
3048
|
-
return [[], [value]];
|
|
3049
|
-
}
|
|
3050
|
-
return value.reduce((acc, x) => {
|
|
3051
|
-
if (acc[1].length == 0) {
|
|
3052
|
-
const r = validatorFn(x);
|
|
3053
|
-
const index = r.valid ? 0 : 1;
|
|
3054
|
-
acc[index].push(r.value);
|
|
3055
|
-
}
|
|
3056
|
-
return acc;
|
|
3057
|
-
}, [[], []]);
|
|
3058
|
-
};
|
|
3059
|
-
const ValidConstraints = {
|
|
3060
|
-
date: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'format'],
|
|
3061
|
-
string: ['minLength', 'maxLength', 'pattern'],
|
|
3062
|
-
number: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum'],
|
|
3063
|
-
array: ['minItems', 'maxItems', 'uniqueItems'],
|
|
3064
|
-
file: ['accept', 'maxFileSize'],
|
|
3065
|
-
email: ['minLength', 'maxLength', 'format', 'pattern']
|
|
3066
|
-
};
|
|
3067
|
-
const Constraints = {
|
|
3068
|
-
type: (constraint, inputVal) => {
|
|
3069
|
-
let value = inputVal;
|
|
3070
|
-
if (inputVal == undefined) {
|
|
3071
|
-
return {
|
|
3072
|
-
valid: true,
|
|
3073
|
-
value: inputVal
|
|
3074
|
-
};
|
|
3153
|
+
if (locale == null) {
|
|
3154
|
+
const localeOptions = new Intl.DateTimeFormat().resolvedOptions();
|
|
3155
|
+
locale = localeOptions.locale;
|
|
3156
|
+
}
|
|
3157
|
+
return (str) => stringToNumber(str, locale);
|
|
3158
|
+
}
|
|
3159
|
+
class RuleEngine {
|
|
3160
|
+
_context;
|
|
3161
|
+
_globalNames = [
|
|
3162
|
+
'$form',
|
|
3163
|
+
'$field',
|
|
3164
|
+
'$event'
|
|
3165
|
+
];
|
|
3166
|
+
customFunctions;
|
|
3167
|
+
debugInfo = [];
|
|
3168
|
+
constructor() {
|
|
3169
|
+
this.customFunctions = FunctionRuntime.getFunctions();
|
|
3170
|
+
}
|
|
3171
|
+
compileRule(rule, locale) {
|
|
3172
|
+
const formula = new Formula(this.customFunctions, getStringToNumberFn(locale), this.debugInfo);
|
|
3173
|
+
return { formula, ast: formula.compile(rule, this._globalNames) };
|
|
3174
|
+
}
|
|
3175
|
+
execute(node, data, globals, useValueOf = false) {
|
|
3176
|
+
const { formula, ast } = node;
|
|
3177
|
+
const oldContext = this._context;
|
|
3178
|
+
this._context = globals;
|
|
3179
|
+
let res = undefined;
|
|
3180
|
+
try {
|
|
3181
|
+
res = formula.run(ast, data, 'en-US', globals);
|
|
3075
3182
|
}
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
case 'string':
|
|
3079
|
-
valid = true;
|
|
3080
|
-
value = inputVal.toString();
|
|
3081
|
-
break;
|
|
3082
|
-
case 'string[]':
|
|
3083
|
-
value = toArray(inputVal);
|
|
3084
|
-
break;
|
|
3085
|
-
case 'number':
|
|
3086
|
-
res = checkNumber(inputVal);
|
|
3087
|
-
value = res.value;
|
|
3088
|
-
valid = res.valid;
|
|
3089
|
-
break;
|
|
3090
|
-
case 'boolean':
|
|
3091
|
-
res = checkBool(inputVal);
|
|
3092
|
-
valid = res.valid;
|
|
3093
|
-
value = res.value;
|
|
3094
|
-
break;
|
|
3095
|
-
case 'integer':
|
|
3096
|
-
res = checkInteger(inputVal);
|
|
3097
|
-
valid = res.valid;
|
|
3098
|
-
value = res.value;
|
|
3099
|
-
break;
|
|
3100
|
-
case 'integer[]':
|
|
3101
|
-
res = partitionArray(inputVal, checkInteger);
|
|
3102
|
-
valid = res[1].length === 0;
|
|
3103
|
-
value = valid ? res[0] : inputVal;
|
|
3104
|
-
break;
|
|
3105
|
-
case 'file':
|
|
3106
|
-
res = checkFile(inputVal instanceof Array ? inputVal[0] : inputVal);
|
|
3107
|
-
valid = res.valid;
|
|
3108
|
-
value = res.value;
|
|
3109
|
-
break;
|
|
3110
|
-
case 'file[]':
|
|
3111
|
-
res = partitionArray(inputVal, checkFile);
|
|
3112
|
-
valid = res[1].length === 0;
|
|
3113
|
-
value = valid ? res[0] : inputVal;
|
|
3114
|
-
break;
|
|
3115
|
-
case 'number[]':
|
|
3116
|
-
res = partitionArray(inputVal, checkNumber);
|
|
3117
|
-
valid = res[1].length === 0;
|
|
3118
|
-
value = valid ? res[0] : inputVal;
|
|
3119
|
-
break;
|
|
3120
|
-
case 'boolean[]':
|
|
3121
|
-
res = partitionArray(inputVal, checkBool);
|
|
3122
|
-
valid = res[1].length === 0;
|
|
3123
|
-
value = valid ? res[0] : inputVal;
|
|
3124
|
-
break;
|
|
3183
|
+
catch (err) {
|
|
3184
|
+
this._context?.form?.logger?.error(err);
|
|
3125
3185
|
}
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
value
|
|
3129
|
-
};
|
|
3130
|
-
},
|
|
3131
|
-
format: (constraint, input) => {
|
|
3132
|
-
let valid = true;
|
|
3133
|
-
const value = input;
|
|
3134
|
-
if (input === null) {
|
|
3135
|
-
return { value, valid };
|
|
3186
|
+
while (this.debugInfo.length > 0) {
|
|
3187
|
+
this._context?.form?.logger?.debug(this.debugInfo.pop());
|
|
3136
3188
|
}
|
|
3137
|
-
let res;
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
const [match, year, month, date] = res;
|
|
3143
|
-
const [nMonth, nDate] = [+month, +date];
|
|
3144
|
-
const leapYear = isLeapYear(+year);
|
|
3145
|
-
valid = (nMonth >= 1 && nMonth <= 12) &&
|
|
3146
|
-
(nDate >= 1 && nDate <= daysInMonth(leapYear, nMonth));
|
|
3147
|
-
}
|
|
3148
|
-
else {
|
|
3149
|
-
valid = false;
|
|
3150
|
-
}
|
|
3151
|
-
break;
|
|
3152
|
-
case 'email':
|
|
3153
|
-
valid = new RegExp(emailRegex).test((input || '').trim());
|
|
3154
|
-
break;
|
|
3155
|
-
case 'data-url':
|
|
3156
|
-
valid = true;
|
|
3157
|
-
break;
|
|
3189
|
+
let finalRes = res;
|
|
3190
|
+
if (useValueOf) {
|
|
3191
|
+
if (typeof res === 'object' && res !== null) {
|
|
3192
|
+
finalRes = Object.getPrototypeOf(res).valueOf.call(res);
|
|
3193
|
+
}
|
|
3158
3194
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
return { valid: value <= constraint, value };
|
|
3166
|
-
},
|
|
3167
|
-
exclusiveMinimum: (constraint, value) => {
|
|
3168
|
-
return { valid: value > constraint, value };
|
|
3169
|
-
},
|
|
3170
|
-
exclusiveMaximum: (constraint, value) => {
|
|
3171
|
-
return { valid: value < constraint, value };
|
|
3172
|
-
},
|
|
3173
|
-
minItems: (constraint, value) => {
|
|
3174
|
-
return { valid: (value instanceof Array) && value.length >= constraint, value };
|
|
3175
|
-
},
|
|
3176
|
-
maxItems: (constraint, value) => {
|
|
3177
|
-
return { valid: (value instanceof Array) && value.length <= constraint, value };
|
|
3178
|
-
},
|
|
3179
|
-
uniqueItems: (constraint, value) => {
|
|
3180
|
-
return { valid: !constraint || ((value instanceof Array) && value.length === new Set(value).size), value };
|
|
3181
|
-
},
|
|
3182
|
-
minLength: (constraint, value) => {
|
|
3183
|
-
return { ...Constraints.minimum(constraint, typeof value === 'string' ? value.length : 0), value };
|
|
3184
|
-
},
|
|
3185
|
-
maxLength: (constraint, value) => {
|
|
3186
|
-
return { ...Constraints.maximum(constraint, typeof value === 'string' ? value.length : 0), value };
|
|
3187
|
-
},
|
|
3188
|
-
pattern: (constraint, value) => {
|
|
3189
|
-
let regex;
|
|
3190
|
-
if (typeof constraint === 'string') {
|
|
3191
|
-
regex = new RegExp(constraint);
|
|
3195
|
+
this._context = oldContext;
|
|
3196
|
+
return finalRes;
|
|
3197
|
+
}
|
|
3198
|
+
trackDependency(subscriber) {
|
|
3199
|
+
if (this._context && this._context.field !== undefined && this._context.field !== subscriber) {
|
|
3200
|
+
subscriber._addDependent(this._context.field);
|
|
3192
3201
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
class Fieldset extends Container {
|
|
3206
|
+
constructor(params, _options) {
|
|
3207
|
+
super(params, _options);
|
|
3208
|
+
if (_options.mode !== 'restore') {
|
|
3209
|
+
this._applyDefaults();
|
|
3210
|
+
this.queueEvent(new Initialize());
|
|
3211
|
+
this.queueEvent(new ExecuteRule());
|
|
3195
3212
|
}
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
required: (constraint, value) => {
|
|
3199
|
-
const valid = constraint ? value != null && value !== '' : true;
|
|
3200
|
-
return { valid, value };
|
|
3201
|
-
},
|
|
3202
|
-
enum: (constraint, value) => {
|
|
3213
|
+
}
|
|
3214
|
+
_getDefaults() {
|
|
3203
3215
|
return {
|
|
3204
|
-
|
|
3205
|
-
|
|
3216
|
+
...super._getDefaults(),
|
|
3217
|
+
visible: true,
|
|
3218
|
+
required: false,
|
|
3219
|
+
label: {
|
|
3220
|
+
visible: true,
|
|
3221
|
+
richText: false
|
|
3222
|
+
}
|
|
3206
3223
|
};
|
|
3207
|
-
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
value
|
|
3213
|
-
};
|
|
3224
|
+
}
|
|
3225
|
+
_applyDefaults() {
|
|
3226
|
+
super._applyDefaultsInModel();
|
|
3227
|
+
if (this._jsonModel.dataRef && this._jsonModel.type === undefined) {
|
|
3228
|
+
this._jsonModel.type = 'object';
|
|
3214
3229
|
}
|
|
3215
|
-
const tempValue = value instanceof Array ? value : [value];
|
|
3216
|
-
const invalidFile = tempValue.some((file) => !matchMediaType(file.type, constraint));
|
|
3217
|
-
return {
|
|
3218
|
-
valid: !invalidFile,
|
|
3219
|
-
value
|
|
3220
|
-
};
|
|
3221
|
-
},
|
|
3222
|
-
maxFileSize: (constraint, value) => {
|
|
3223
|
-
const sizeLimit = typeof constraint === 'string' ? getFileSizeInBytes(constraint) : constraint;
|
|
3224
|
-
return {
|
|
3225
|
-
valid: !(value instanceof FileObject) || value.size <= sizeLimit,
|
|
3226
|
-
value
|
|
3227
|
-
};
|
|
3228
3230
|
}
|
|
3229
|
-
|
|
3231
|
+
get type() {
|
|
3232
|
+
const ret = super.type;
|
|
3233
|
+
if (ret === 'array' || ret === 'object') {
|
|
3234
|
+
return ret;
|
|
3235
|
+
}
|
|
3236
|
+
return undefined;
|
|
3237
|
+
}
|
|
3238
|
+
get items() {
|
|
3239
|
+
return super.items;
|
|
3240
|
+
}
|
|
3241
|
+
get value() {
|
|
3242
|
+
return null;
|
|
3243
|
+
}
|
|
3244
|
+
get fieldType() {
|
|
3245
|
+
return 'panel';
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
class InstanceManager extends Fieldset {
|
|
3250
|
+
get maxOccur() {
|
|
3251
|
+
return this._jsonModel.maxItems;
|
|
3252
|
+
}
|
|
3253
|
+
set maxOccur(m) {
|
|
3254
|
+
this.maxItems = m;
|
|
3255
|
+
}
|
|
3256
|
+
get minOccur() {
|
|
3257
|
+
return this.minItems;
|
|
3258
|
+
}
|
|
3259
|
+
addInstance(action) {
|
|
3260
|
+
return this.addItem(action);
|
|
3261
|
+
}
|
|
3262
|
+
removeInstance(action) {
|
|
3263
|
+
return this.removeItem(action);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
__decorate([
|
|
3267
|
+
dependencyTracked()
|
|
3268
|
+
], InstanceManager.prototype, "maxOccur", null);
|
|
3269
|
+
__decorate([
|
|
3270
|
+
dependencyTracked()
|
|
3271
|
+
], InstanceManager.prototype, "minOccur", null);
|
|
3230
3272
|
|
|
3231
3273
|
const validTypes = ['string', 'number', 'integer', 'boolean', 'file', 'string[]', 'number[]', 'integer[]', 'boolean[]', 'file[]', 'array', 'object'];
|
|
3232
3274
|
class Field extends Scriptable {
|
|
@@ -3385,6 +3427,9 @@ class Field extends Scriptable {
|
|
|
3385
3427
|
get displayFormat() {
|
|
3386
3428
|
return this.withCategory(this._jsonModel.displayFormat);
|
|
3387
3429
|
}
|
|
3430
|
+
get displayValueExpression() {
|
|
3431
|
+
return this._jsonModel.displayValueExpression;
|
|
3432
|
+
}
|
|
3388
3433
|
get placeholder() {
|
|
3389
3434
|
return this._jsonModel.placeholder;
|
|
3390
3435
|
}
|
|
@@ -3451,7 +3496,6 @@ class Field extends Scriptable {
|
|
|
3451
3496
|
}
|
|
3452
3497
|
set required(r) {
|
|
3453
3498
|
this._setProperty('required', r);
|
|
3454
|
-
this.validate();
|
|
3455
3499
|
}
|
|
3456
3500
|
get maximum() {
|
|
3457
3501
|
if (this.type === 'number' || this.format === 'date' || this.type === 'integer') {
|
|
@@ -3503,6 +3547,9 @@ class Field extends Scriptable {
|
|
|
3503
3547
|
}
|
|
3504
3548
|
}
|
|
3505
3549
|
get displayValue() {
|
|
3550
|
+
if (this.displayValueExpression && typeof this.displayValueExpression === 'string' && this.displayValueExpression.length !== 0) {
|
|
3551
|
+
return this.executeExpression(this.displayValueExpression);
|
|
3552
|
+
}
|
|
3506
3553
|
const df = this.displayFormat;
|
|
3507
3554
|
if (df && this.isNotEmpty(this.value) && this.valid !== false) {
|
|
3508
3555
|
try {
|