@aemforms/af-core 0.22.74 → 0.22.75
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 +917 -891
- package/esm/types/src/BaseNode.d.ts +4 -2
- package/esm/types/src/Checkbox.d.ts +1 -1
- package/esm/types/src/Container.d.ts +4 -2
- package/esm/types/src/Field.d.ts +1 -1
- package/esm/types/src/Form.d.ts +4 -2
- 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 +1 -1
- package/lib/Container.d.ts +4 -2
- package/lib/Field.d.ts +1 -1
- package/lib/Field.js +0 -1
- package/lib/Form.d.ts +4 -2
- package/lib/rules/FunctionRuntime.js +23 -5
- 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,285 +578,812 @@ const resolveData = (data, input, create) => {
|
|
|
578
578
|
return result;
|
|
579
579
|
};
|
|
580
580
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
'
|
|
584
|
-
'
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
'errorMessage',
|
|
589
|
-
'readOnly',
|
|
590
|
-
'enum',
|
|
591
|
-
'enumNames',
|
|
592
|
-
'required',
|
|
593
|
-
'properties',
|
|
594
|
-
'exclusiveMinimum',
|
|
595
|
-
'exclusiveMaximum',
|
|
596
|
-
'maximum',
|
|
597
|
-
'maxItems',
|
|
598
|
-
'minimum',
|
|
599
|
-
'minItems',
|
|
600
|
-
'checked'
|
|
601
|
-
];
|
|
602
|
-
const dynamicProps = [
|
|
603
|
-
...editableProperties,
|
|
604
|
-
'index',
|
|
605
|
-
'activeChild'
|
|
606
|
-
];
|
|
607
|
-
const staticFields = ['plain-text', 'image'];
|
|
608
|
-
class ActionImplWithTarget {
|
|
609
|
-
_action;
|
|
610
|
-
_target;
|
|
611
|
-
constructor(_action, _target) {
|
|
612
|
-
this._action = _action;
|
|
613
|
-
this._target = _target;
|
|
581
|
+
class FileObject {
|
|
582
|
+
data;
|
|
583
|
+
mediaType = 'application/octet-stream';
|
|
584
|
+
name = 'unknown';
|
|
585
|
+
size = 0;
|
|
586
|
+
constructor(init) {
|
|
587
|
+
Object.assign(this, init);
|
|
614
588
|
}
|
|
615
589
|
get type() {
|
|
616
|
-
return this.
|
|
617
|
-
}
|
|
618
|
-
get payload() {
|
|
619
|
-
return this._action.payload;
|
|
620
|
-
}
|
|
621
|
-
get metadata() {
|
|
622
|
-
return this._action.metadata;
|
|
623
|
-
}
|
|
624
|
-
get target() {
|
|
625
|
-
return this._target;
|
|
626
|
-
}
|
|
627
|
-
get isCustomEvent() {
|
|
628
|
-
return this._action.isCustomEvent;
|
|
590
|
+
return this.mediaType;
|
|
629
591
|
}
|
|
630
|
-
|
|
631
|
-
return
|
|
592
|
+
toJSON() {
|
|
593
|
+
return {
|
|
594
|
+
'name': this.name,
|
|
595
|
+
'size': this.size,
|
|
596
|
+
'mediaType': this.mediaType,
|
|
597
|
+
'data': this.data.toString()
|
|
598
|
+
};
|
|
632
599
|
}
|
|
633
|
-
|
|
634
|
-
return this.
|
|
600
|
+
equals(obj) {
|
|
601
|
+
return (this.data === obj.data &&
|
|
602
|
+
this.mediaType === obj.mediaType &&
|
|
603
|
+
this.name === obj.name &&
|
|
604
|
+
this.size === obj.size);
|
|
635
605
|
}
|
|
636
606
|
}
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
};
|
|
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));
|
|
647
616
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if (get != undefined) {
|
|
653
|
-
descriptor.get = function () {
|
|
654
|
-
if (fieldTypes.indexOf(this.fieldType) > -1 === includeOrExclude) {
|
|
655
|
-
return get.call(this);
|
|
656
|
-
}
|
|
657
|
-
return undefined;
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
const set = descriptor.set;
|
|
661
|
-
if (set != undefined) {
|
|
662
|
-
descriptor.set = function (value) {
|
|
663
|
-
if (fieldTypes.indexOf(this.fieldType) > -1 === includeOrExclude) {
|
|
664
|
-
set.call(this, value);
|
|
665
|
-
}
|
|
666
|
-
};
|
|
617
|
+
else {
|
|
618
|
+
randIndex = Math.floor(Math.random() * (chars.length));
|
|
619
|
+
}
|
|
620
|
+
ret.push(chars[randIndex]);
|
|
667
621
|
}
|
|
622
|
+
return ret.join('');
|
|
668
623
|
};
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
_lang = '';
|
|
675
|
-
_callbacks = {};
|
|
676
|
-
_dependents = [];
|
|
677
|
-
_jsonModel;
|
|
678
|
-
_tokens = [];
|
|
679
|
-
get isContainer() {
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
constructor(params, _options) {
|
|
683
|
-
this._options = _options;
|
|
684
|
-
this[qualifiedName] = null;
|
|
685
|
-
this._jsonModel = {
|
|
686
|
-
...params,
|
|
687
|
-
id: 'id' in params ? params.id : this.form.getUniqueId()
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
setupRuleNode() {
|
|
691
|
-
const self = this;
|
|
692
|
-
this._ruleNode = new Proxy(this.ruleNodeReference(), {
|
|
693
|
-
get: (ruleNodeReference, prop) => {
|
|
694
|
-
return self.getFromRule(ruleNodeReference, prop);
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
ruleNodeReference() {
|
|
699
|
-
return this;
|
|
700
|
-
}
|
|
701
|
-
getRuleNode() {
|
|
702
|
-
return this._ruleNode;
|
|
703
|
-
}
|
|
704
|
-
getFromRule(ruleNodeReference, prop) {
|
|
705
|
-
if (prop === Symbol.toPrimitive || (prop === 'valueOf' && !ruleNodeReference.hasOwnProperty('valueOf'))) {
|
|
706
|
-
return this.valueOf;
|
|
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;
|
|
707
629
|
}
|
|
708
|
-
|
|
709
|
-
|
|
630
|
+
let ret = null;
|
|
631
|
+
if (item.isContainer) {
|
|
632
|
+
ret = getAttachments(item, excludeUnbound);
|
|
710
633
|
}
|
|
711
|
-
else
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
return
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
return retValue;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
else {
|
|
728
|
-
if (ruleNodeReference.hasOwnProperty(prop)) {
|
|
729
|
-
return ruleNodeReference[prop];
|
|
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
|
+
});
|
|
730
645
|
}
|
|
731
|
-
else if (
|
|
732
|
-
|
|
646
|
+
else if (item.value != null) {
|
|
647
|
+
ret[item.id] = { ...item.value, 'dataRef': dataRef };
|
|
733
648
|
}
|
|
734
649
|
}
|
|
735
650
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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());
|
|
743
660
|
}
|
|
744
|
-
return 0;
|
|
745
|
-
}
|
|
746
|
-
get parent() {
|
|
747
|
-
return this._options.parent;
|
|
748
|
-
}
|
|
749
|
-
get type() {
|
|
750
|
-
return this._jsonModel.type;
|
|
751
|
-
}
|
|
752
|
-
get repeatable() {
|
|
753
|
-
return this.parent?.hasDynamicItems();
|
|
754
|
-
}
|
|
755
|
-
get fieldType() {
|
|
756
|
-
return this._jsonModel.fieldType || 'text-input';
|
|
757
661
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
get dataRef() {
|
|
771
|
-
return this._jsonModel.dataRef;
|
|
772
|
-
}
|
|
773
|
-
get visible() {
|
|
774
|
-
if (this.parent?.visible !== undefined) {
|
|
775
|
-
return this.parent?.visible ? this._jsonModel.visible : false;
|
|
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));
|
|
776
674
|
}
|
|
777
|
-
|
|
778
|
-
|
|
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();
|
|
779
686
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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);
|
|
786
767
|
}
|
|
768
|
+
return null;
|
|
787
769
|
}
|
|
788
|
-
|
|
789
|
-
return
|
|
770
|
+
else {
|
|
771
|
+
return null;
|
|
790
772
|
}
|
|
791
|
-
|
|
792
|
-
|
|
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
|
+
}
|
|
793
794
|
}
|
|
794
|
-
|
|
795
|
-
return
|
|
795
|
+
else {
|
|
796
|
+
return null;
|
|
796
797
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
this.notifyDependents(changeAction);
|
|
805
|
-
}
|
|
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;
|
|
806
805
|
}
|
|
807
|
-
|
|
808
|
-
|
|
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;
|
|
809
814
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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;
|
|
813
830
|
}
|
|
814
|
-
|
|
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) {
|
|
815
859
|
return {
|
|
816
|
-
|
|
817
|
-
properties: this.properties,
|
|
818
|
-
index: this.index,
|
|
819
|
-
parent: undefined,
|
|
820
|
-
qualifiedName: this.qualifiedName,
|
|
821
|
-
repeatable: this.repeatable === true ? true : undefined,
|
|
822
|
-
':type': this[':type'],
|
|
823
|
-
...(forRestore ? {
|
|
824
|
-
_dependents: this._dependents.length ? this._dependents.map(x => x.node.id) : undefined,
|
|
825
|
-
allowedComponents: undefined,
|
|
826
|
-
columnClassNames: undefined,
|
|
827
|
-
columnCount: undefined,
|
|
828
|
-
gridClassNames: undefined
|
|
829
|
-
} : {})
|
|
860
|
+
value: '', valid: true
|
|
830
861
|
};
|
|
831
862
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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) {
|
|
835
874
|
return {
|
|
836
|
-
|
|
837
|
-
this._callbacks[eventName] = this._callbacks[eventName].filter(x => x !== callback);
|
|
838
|
-
}
|
|
875
|
+
value: '', valid: true
|
|
839
876
|
};
|
|
840
877
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const propsToLook = [...dynamicProps, 'items'];
|
|
846
|
-
const isPropChanged = changes.findIndex(x => {
|
|
847
|
-
return propsToLook.indexOf(x.propertyName) > -1;
|
|
848
|
-
}) > -1;
|
|
849
|
-
if (isPropChanged) {
|
|
850
|
-
dependent.dispatch(new ExecuteRule());
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
this._dependents.push({ node: dependent, subscription });
|
|
854
|
-
}
|
|
878
|
+
let value = parseFloat(inputVal);
|
|
879
|
+
const valid = !isNaN(value) && Math.round(value) === value;
|
|
880
|
+
if (!valid) {
|
|
881
|
+
value = inputVal;
|
|
855
882
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
+
|
|
1104
|
+
const editableProperties = [
|
|
1105
|
+
'value',
|
|
1106
|
+
'label',
|
|
1107
|
+
'description',
|
|
1108
|
+
'visible',
|
|
1109
|
+
'enabled',
|
|
1110
|
+
'valid',
|
|
1111
|
+
'errorMessage',
|
|
1112
|
+
'readOnly',
|
|
1113
|
+
'enum',
|
|
1114
|
+
'enumNames',
|
|
1115
|
+
'required',
|
|
1116
|
+
'properties',
|
|
1117
|
+
'exclusiveMinimum',
|
|
1118
|
+
'exclusiveMaximum',
|
|
1119
|
+
'maximum',
|
|
1120
|
+
'maxItems',
|
|
1121
|
+
'minimum',
|
|
1122
|
+
'minItems',
|
|
1123
|
+
'checked'
|
|
1124
|
+
];
|
|
1125
|
+
const dynamicProps = [
|
|
1126
|
+
...editableProperties,
|
|
1127
|
+
'index',
|
|
1128
|
+
'activeChild'
|
|
1129
|
+
];
|
|
1130
|
+
const staticFields = ['plain-text', 'image'];
|
|
1131
|
+
class ActionImplWithTarget {
|
|
1132
|
+
_action;
|
|
1133
|
+
_target;
|
|
1134
|
+
constructor(_action, _target) {
|
|
1135
|
+
this._action = _action;
|
|
1136
|
+
this._target = _target;
|
|
1137
|
+
}
|
|
1138
|
+
get type() {
|
|
1139
|
+
return this._action.type;
|
|
1140
|
+
}
|
|
1141
|
+
get payload() {
|
|
1142
|
+
return this._action.payload;
|
|
1143
|
+
}
|
|
1144
|
+
get metadata() {
|
|
1145
|
+
return this._action.metadata;
|
|
1146
|
+
}
|
|
1147
|
+
get target() {
|
|
1148
|
+
return this._target;
|
|
1149
|
+
}
|
|
1150
|
+
get isCustomEvent() {
|
|
1151
|
+
return this._action.isCustomEvent;
|
|
1152
|
+
}
|
|
1153
|
+
get originalAction() {
|
|
1154
|
+
return this._action.originalAction;
|
|
1155
|
+
}
|
|
1156
|
+
toString() {
|
|
1157
|
+
return this._action.toString();
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
const target = Symbol('target');
|
|
1161
|
+
const qualifiedName = Symbol('qualifiedName');
|
|
1162
|
+
function dependencyTracked() {
|
|
1163
|
+
return function (target, propertyKey, descriptor) {
|
|
1164
|
+
const get = descriptor.get;
|
|
1165
|
+
if (get != undefined) {
|
|
1166
|
+
descriptor.get = function () {
|
|
1167
|
+
this.ruleEngine.trackDependency(this);
|
|
1168
|
+
return get.call(this);
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
const addOnly = (includeOrExclude) => (...fieldTypes) => (target, propertyKey, descriptor) => {
|
|
1174
|
+
const get = descriptor.get;
|
|
1175
|
+
if (get != undefined) {
|
|
1176
|
+
descriptor.get = function () {
|
|
1177
|
+
if (fieldTypes.indexOf(this.fieldType) > -1 === includeOrExclude) {
|
|
1178
|
+
return get.call(this);
|
|
1179
|
+
}
|
|
1180
|
+
return undefined;
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
const set = descriptor.set;
|
|
1184
|
+
if (set != undefined) {
|
|
1185
|
+
descriptor.set = function (value) {
|
|
1186
|
+
if (fieldTypes.indexOf(this.fieldType) > -1 === includeOrExclude) {
|
|
1187
|
+
set.call(this, value);
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
const include = addOnly(true);
|
|
1193
|
+
const exclude = addOnly(false);
|
|
1194
|
+
class BaseNode {
|
|
1195
|
+
_options;
|
|
1196
|
+
_ruleNode;
|
|
1197
|
+
_lang = '';
|
|
1198
|
+
_callbacks = {};
|
|
1199
|
+
_dependents = [];
|
|
1200
|
+
_jsonModel;
|
|
1201
|
+
_tokens = [];
|
|
1202
|
+
get isContainer() {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
constructor(params, _options) {
|
|
1206
|
+
this._options = _options;
|
|
1207
|
+
this[qualifiedName] = null;
|
|
1208
|
+
this._jsonModel = {
|
|
1209
|
+
...params,
|
|
1210
|
+
id: 'id' in params ? params.id : this.form.getUniqueId()
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
setupRuleNode() {
|
|
1214
|
+
const self = this;
|
|
1215
|
+
this._ruleNode = new Proxy(this.ruleNodeReference(), {
|
|
1216
|
+
get: (ruleNodeReference, prop) => {
|
|
1217
|
+
return self.getFromRule(ruleNodeReference, prop);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
ruleNodeReference() {
|
|
1222
|
+
return this;
|
|
1223
|
+
}
|
|
1224
|
+
getRuleNode() {
|
|
1225
|
+
return this._ruleNode;
|
|
1226
|
+
}
|
|
1227
|
+
getFromRule(ruleNodeReference, prop) {
|
|
1228
|
+
if (prop === Symbol.toPrimitive || (prop === 'valueOf' && !ruleNodeReference.hasOwnProperty('valueOf'))) {
|
|
1229
|
+
return this.valueOf;
|
|
1230
|
+
}
|
|
1231
|
+
else if (prop === target) {
|
|
1232
|
+
return this;
|
|
1233
|
+
}
|
|
1234
|
+
else if (typeof (prop) === 'string') {
|
|
1235
|
+
if (prop.startsWith('$')) {
|
|
1236
|
+
prop = prop.substr(1);
|
|
1237
|
+
if (typeof this[prop] !== 'function') {
|
|
1238
|
+
const retValue = this[prop];
|
|
1239
|
+
if (retValue instanceof BaseNode) {
|
|
1240
|
+
return retValue.getRuleNode();
|
|
1241
|
+
}
|
|
1242
|
+
else if (retValue instanceof Array) {
|
|
1243
|
+
return retValue.map(r => r instanceof BaseNode ? r.getRuleNode() : r);
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
return retValue;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
if (ruleNodeReference.hasOwnProperty(prop)) {
|
|
1252
|
+
return ruleNodeReference[prop];
|
|
1253
|
+
}
|
|
1254
|
+
else if (typeof ruleNodeReference[prop] === 'function') {
|
|
1255
|
+
return ruleNodeReference[prop];
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
get id() {
|
|
1261
|
+
return this._jsonModel.id;
|
|
1262
|
+
}
|
|
1263
|
+
get index() {
|
|
1264
|
+
if (this.parent) {
|
|
1265
|
+
return this.parent.indexOf(this);
|
|
1266
|
+
}
|
|
1267
|
+
return 0;
|
|
1268
|
+
}
|
|
1269
|
+
get parent() {
|
|
1270
|
+
return this._options.parent;
|
|
1271
|
+
}
|
|
1272
|
+
get type() {
|
|
1273
|
+
return this._jsonModel.type;
|
|
1274
|
+
}
|
|
1275
|
+
get repeatable() {
|
|
1276
|
+
return this.parent?.hasDynamicItems();
|
|
1277
|
+
}
|
|
1278
|
+
get fieldType() {
|
|
1279
|
+
return this._jsonModel.fieldType || 'text-input';
|
|
1280
|
+
}
|
|
1281
|
+
get ':type'() {
|
|
1282
|
+
return this._jsonModel[':type'] || this.fieldType;
|
|
1283
|
+
}
|
|
1284
|
+
get name() {
|
|
1285
|
+
return this._jsonModel.name;
|
|
1286
|
+
}
|
|
1287
|
+
get description() {
|
|
1288
|
+
return this._jsonModel.description;
|
|
1289
|
+
}
|
|
1290
|
+
set description(d) {
|
|
1291
|
+
this._setProperty('description', d);
|
|
1292
|
+
}
|
|
1293
|
+
get dataRef() {
|
|
1294
|
+
return this._jsonModel.dataRef;
|
|
1295
|
+
}
|
|
1296
|
+
get visible() {
|
|
1297
|
+
if (this.parent?.visible !== undefined) {
|
|
1298
|
+
return this.parent?.visible ? this._jsonModel.visible : false;
|
|
1299
|
+
}
|
|
1300
|
+
else {
|
|
1301
|
+
return this._jsonModel.visible;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
set visible(v) {
|
|
1305
|
+
if (v !== this._jsonModel.visible) {
|
|
1306
|
+
const changeAction = propertyChange('visible', v, this._jsonModel.visible);
|
|
1307
|
+
this._jsonModel.visible = v;
|
|
1308
|
+
this.notifyDependents(changeAction);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
get form() {
|
|
1312
|
+
return this._options.form;
|
|
1313
|
+
}
|
|
1314
|
+
get ruleEngine() {
|
|
1315
|
+
return this.form.ruleEngine;
|
|
1316
|
+
}
|
|
1317
|
+
get label() {
|
|
1318
|
+
return this._jsonModel.label;
|
|
1319
|
+
}
|
|
1320
|
+
set label(l) {
|
|
1321
|
+
if (l !== this._jsonModel.label) {
|
|
1322
|
+
const changeAction = propertyChange('label', l, this._jsonModel.label);
|
|
1323
|
+
this._jsonModel = {
|
|
1324
|
+
...this._jsonModel,
|
|
1325
|
+
label: l
|
|
1326
|
+
};
|
|
1327
|
+
this.notifyDependents(changeAction);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
get uniqueItems() {
|
|
1331
|
+
return this._jsonModel.uniqueItems;
|
|
1332
|
+
}
|
|
1333
|
+
isTransparent() {
|
|
1334
|
+
const isNonTransparent = this.parent?._jsonModel.type === 'array';
|
|
1335
|
+
return !this._jsonModel.name && !isNonTransparent;
|
|
1336
|
+
}
|
|
1337
|
+
getState(forRestore = false) {
|
|
1338
|
+
return {
|
|
1339
|
+
...this._jsonModel,
|
|
1340
|
+
properties: this.properties,
|
|
1341
|
+
index: this.index,
|
|
1342
|
+
parent: undefined,
|
|
1343
|
+
qualifiedName: this.qualifiedName,
|
|
1344
|
+
...(this.repeatable === true ? {
|
|
1345
|
+
repeatable: true,
|
|
1346
|
+
minOccur: this.parent.minItems,
|
|
1347
|
+
maxOccur: this.parent.maxItems
|
|
1348
|
+
} : {}),
|
|
1349
|
+
':type': this[':type'],
|
|
1350
|
+
...(forRestore ? {
|
|
1351
|
+
_dependents: this._dependents.length ? this._dependents.map(x => x.node.id) : undefined,
|
|
1352
|
+
allowedComponents: undefined,
|
|
1353
|
+
columnClassNames: undefined,
|
|
1354
|
+
columnCount: undefined,
|
|
1355
|
+
gridClassNames: undefined
|
|
1356
|
+
} : {})
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
subscribe(callback, eventName = 'change') {
|
|
1360
|
+
this._callbacks[eventName] = this._callbacks[eventName] || [];
|
|
1361
|
+
this._callbacks[eventName].push(callback);
|
|
1362
|
+
return {
|
|
1363
|
+
unsubscribe: () => {
|
|
1364
|
+
this._callbacks[eventName] = this._callbacks[eventName].filter(x => x !== callback);
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
_addDependent(dependent) {
|
|
1369
|
+
if (this._dependents.find(({ node }) => node === dependent) === undefined) {
|
|
1370
|
+
const subscription = this.subscribe((change) => {
|
|
1371
|
+
const changes = change.payload.changes;
|
|
1372
|
+
const propsToLook = [...dynamicProps, 'items'];
|
|
1373
|
+
const isPropChanged = changes.findIndex(x => {
|
|
1374
|
+
return propsToLook.indexOf(x.propertyName) > -1;
|
|
1375
|
+
}) > -1;
|
|
1376
|
+
if (isPropChanged) {
|
|
1377
|
+
dependent.dispatch(new ExecuteRule());
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
this._dependents.push({ node: dependent, subscription });
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
removeDependent(dependent) {
|
|
1384
|
+
const index = this._dependents.findIndex(({ node }) => node === dependent);
|
|
1385
|
+
if (index > -1) {
|
|
1386
|
+
this._dependents[index].subscription.unsubscribe();
|
|
860
1387
|
this._dependents.splice(index, 1);
|
|
861
1388
|
}
|
|
862
1389
|
}
|
|
@@ -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 [];
|
|
@@ -1837,257 +2367,14 @@ class EventQueue {
|
|
|
1837
2367
|
this._isProcessing = true;
|
|
1838
2368
|
while (this._pendingEvents.length > 0) {
|
|
1839
2369
|
const e = this._pendingEvents[0];
|
|
1840
|
-
this.logger.info(`Dequeued event : ${e.event.type} node: ${e.node.id} - ${e.node.name}`);
|
|
1841
|
-
e.node.executeAction(e.event);
|
|
1842
|
-
this._pendingEvents.shift();
|
|
1843
|
-
}
|
|
1844
|
-
this._runningEventCount = {};
|
|
1845
|
-
this._isProcessing = false;
|
|
1846
|
-
}
|
|
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
|
-
};
|
|
2370
|
+
this.logger.info(`Dequeued event : ${e.event.type} node: ${e.node.id} - ${e.node.name}`);
|
|
2371
|
+
e.node.executeAction(e.event);
|
|
2372
|
+
this._pendingEvents.shift();
|
|
2373
|
+
}
|
|
2374
|
+
this._runningEventCount = {};
|
|
2375
|
+
this._isProcessing = false;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2091
2378
|
|
|
2092
2379
|
const request$1 = (url, data = null, options = {}) => {
|
|
2093
2380
|
const opts = { ...defaultRequestOptions, ...options };
|
|
@@ -2336,6 +2623,11 @@ 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 = ['custom:submitSuccess', 'custom:submitError', submitAs, payload, validateForm];
|
|
2630
|
+
return FunctionRuntimeImpl.getInstance().getFunctions().submitForm._func.call(undefined, args, data, interpreter);
|
|
2339
2631
|
}
|
|
2340
2632
|
}
|
|
2341
2633
|
};
|
|
@@ -2437,11 +2729,24 @@ class FunctionRuntimeImpl {
|
|
|
2437
2729
|
},
|
|
2438
2730
|
submitForm: {
|
|
2439
2731
|
_func: (args, data, interpreter) => {
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2732
|
+
let success = 'custom:submitSuccess';
|
|
2733
|
+
let error = 'custom:submitError';
|
|
2734
|
+
let submit_data;
|
|
2735
|
+
let validate_form;
|
|
2736
|
+
let submit_as;
|
|
2737
|
+
if (args.length > 0 && typeof valueOf(args[0]) === 'object') {
|
|
2738
|
+
submit_data = args.length > 0 ? valueOf(args[0]) : null;
|
|
2739
|
+
validate_form = args.length > 1 ? valueOf(args[1]) : true;
|
|
2740
|
+
submit_as = args.length > 2 ? toString(args[2]) : 'multipart/form-data';
|
|
2741
|
+
}
|
|
2742
|
+
else {
|
|
2743
|
+
interpreter.globals.form.logger.warn('This usage of submitForm is deprecated. Please see the documentation and update');
|
|
2744
|
+
success = toString(args[0]);
|
|
2745
|
+
error = toString(args[1]);
|
|
2746
|
+
submit_as = args.length > 2 ? toString(args[2]) : 'multipart/form-data';
|
|
2747
|
+
submit_data = args.length > 3 ? valueOf(args[3]) : null;
|
|
2748
|
+
validate_form = args.length > 4 ? valueOf(args[4]) : true;
|
|
2749
|
+
}
|
|
2445
2750
|
interpreter.globals.form.dispatch(new Submit({
|
|
2446
2751
|
success,
|
|
2447
2752
|
error,
|
|
@@ -2828,405 +3133,127 @@ function stringToNumber(str, language) {
|
|
|
2828
3133
|
}
|
|
2829
3134
|
return 0;
|
|
2830
3135
|
}
|
|
2831
|
-
|
|
2832
|
-
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
|
-
};
|
|
3136
|
+
|
|
3137
|
+
function getStringToNumberFn(locale) {
|
|
3138
|
+
if (locale == null) {
|
|
3139
|
+
const localeOptions = new Intl.DateTimeFormat().resolvedOptions();
|
|
3140
|
+
locale = localeOptions.locale;
|
|
3141
|
+
}
|
|
3142
|
+
return (str) => stringToNumber(str, locale);
|
|
3143
|
+
}
|
|
3144
|
+
class RuleEngine {
|
|
3145
|
+
_context;
|
|
3146
|
+
_globalNames = [
|
|
3147
|
+
'$form',
|
|
3148
|
+
'$field',
|
|
3149
|
+
'$event'
|
|
3150
|
+
];
|
|
3151
|
+
customFunctions;
|
|
3152
|
+
debugInfo = [];
|
|
3153
|
+
constructor() {
|
|
3154
|
+
this.customFunctions = FunctionRuntime.getFunctions();
|
|
3155
|
+
}
|
|
3156
|
+
compileRule(rule, locale) {
|
|
3157
|
+
const formula = new Formula(this.customFunctions, getStringToNumberFn(locale), this.debugInfo);
|
|
3158
|
+
return { formula, ast: formula.compile(rule, this._globalNames) };
|
|
3159
|
+
}
|
|
3160
|
+
execute(node, data, globals, useValueOf = false) {
|
|
3161
|
+
const { formula, ast } = node;
|
|
3162
|
+
const oldContext = this._context;
|
|
3163
|
+
this._context = globals;
|
|
3164
|
+
let res = undefined;
|
|
3165
|
+
try {
|
|
3166
|
+
res = formula.run(ast, data, 'en-US', globals);
|
|
3075
3167
|
}
|
|
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;
|
|
3168
|
+
catch (err) {
|
|
3169
|
+
this._context?.form?.logger?.error(err);
|
|
3125
3170
|
}
|
|
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 };
|
|
3171
|
+
while (this.debugInfo.length > 0) {
|
|
3172
|
+
this._context?.form?.logger?.debug(this.debugInfo.pop());
|
|
3136
3173
|
}
|
|
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;
|
|
3174
|
+
let finalRes = res;
|
|
3175
|
+
if (useValueOf) {
|
|
3176
|
+
if (typeof res === 'object' && res !== null) {
|
|
3177
|
+
finalRes = Object.getPrototypeOf(res).valueOf.call(res);
|
|
3178
|
+
}
|
|
3158
3179
|
}
|
|
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);
|
|
3180
|
+
this._context = oldContext;
|
|
3181
|
+
return finalRes;
|
|
3182
|
+
}
|
|
3183
|
+
trackDependency(subscriber) {
|
|
3184
|
+
if (this._context && this._context.field !== undefined && this._context.field !== subscriber) {
|
|
3185
|
+
subscriber._addDependent(this._context.field);
|
|
3192
3186
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
class Fieldset extends Container {
|
|
3191
|
+
constructor(params, _options) {
|
|
3192
|
+
super(params, _options);
|
|
3193
|
+
if (_options.mode !== 'restore') {
|
|
3194
|
+
this._applyDefaults();
|
|
3195
|
+
this.queueEvent(new Initialize());
|
|
3196
|
+
this.queueEvent(new ExecuteRule());
|
|
3195
3197
|
}
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
required: (constraint, value) => {
|
|
3199
|
-
const valid = constraint ? value != null && value !== '' : true;
|
|
3200
|
-
return { valid, value };
|
|
3201
|
-
},
|
|
3202
|
-
enum: (constraint, value) => {
|
|
3198
|
+
}
|
|
3199
|
+
_getDefaults() {
|
|
3203
3200
|
return {
|
|
3204
|
-
|
|
3205
|
-
|
|
3201
|
+
...super._getDefaults(),
|
|
3202
|
+
visible: true,
|
|
3203
|
+
required: false,
|
|
3204
|
+
label: {
|
|
3205
|
+
visible: true,
|
|
3206
|
+
richText: false
|
|
3207
|
+
}
|
|
3206
3208
|
};
|
|
3207
|
-
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
value
|
|
3213
|
-
};
|
|
3209
|
+
}
|
|
3210
|
+
_applyDefaults() {
|
|
3211
|
+
super._applyDefaultsInModel();
|
|
3212
|
+
if (this._jsonModel.dataRef && this._jsonModel.type === undefined) {
|
|
3213
|
+
this._jsonModel.type = 'object';
|
|
3214
3214
|
}
|
|
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
3215
|
}
|
|
3229
|
-
|
|
3216
|
+
get type() {
|
|
3217
|
+
const ret = super.type;
|
|
3218
|
+
if (ret === 'array' || ret === 'object') {
|
|
3219
|
+
return ret;
|
|
3220
|
+
}
|
|
3221
|
+
return undefined;
|
|
3222
|
+
}
|
|
3223
|
+
get items() {
|
|
3224
|
+
return super.items;
|
|
3225
|
+
}
|
|
3226
|
+
get value() {
|
|
3227
|
+
return null;
|
|
3228
|
+
}
|
|
3229
|
+
get fieldType() {
|
|
3230
|
+
return 'panel';
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
class InstanceManager extends Fieldset {
|
|
3235
|
+
get maxOccur() {
|
|
3236
|
+
return this._jsonModel.maxItems;
|
|
3237
|
+
}
|
|
3238
|
+
set maxOccur(m) {
|
|
3239
|
+
this.maxItems = m;
|
|
3240
|
+
}
|
|
3241
|
+
get minOccur() {
|
|
3242
|
+
return this.minItems;
|
|
3243
|
+
}
|
|
3244
|
+
addInstance(action) {
|
|
3245
|
+
return this.addItem(action);
|
|
3246
|
+
}
|
|
3247
|
+
removeInstance(action) {
|
|
3248
|
+
return this.removeItem(action);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
__decorate([
|
|
3252
|
+
dependencyTracked()
|
|
3253
|
+
], InstanceManager.prototype, "maxOccur", null);
|
|
3254
|
+
__decorate([
|
|
3255
|
+
dependencyTracked()
|
|
3256
|
+
], InstanceManager.prototype, "minOccur", null);
|
|
3230
3257
|
|
|
3231
3258
|
const validTypes = ['string', 'number', 'integer', 'boolean', 'file', 'string[]', 'number[]', 'integer[]', 'boolean[]', 'file[]', 'array', 'object'];
|
|
3232
3259
|
class Field extends Scriptable {
|
|
@@ -3451,7 +3478,6 @@ class Field extends Scriptable {
|
|
|
3451
3478
|
}
|
|
3452
3479
|
set required(r) {
|
|
3453
3480
|
this._setProperty('required', r);
|
|
3454
|
-
this.validate();
|
|
3455
3481
|
}
|
|
3456
3482
|
get maximum() {
|
|
3457
3483
|
if (this.type === 'number' || this.format === 'date' || this.type === 'integer') {
|