@getodk/xforms-engine 0.15.0 → 0.16.1
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/dist/client/AttributeNode.d.ts +4 -6
- package/dist/client/BaseNode.d.ts +5 -0
- package/dist/client/form/FormInstanceConfig.d.ts +15 -0
- package/dist/index.js +654 -429
- package/dist/index.js.map +1 -1
- package/dist/instance/Attribute.d.ts +13 -19
- package/dist/instance/Group.d.ts +2 -1
- package/dist/instance/InputControl.d.ts +5 -0
- package/dist/instance/ModelValue.d.ts +4 -0
- package/dist/instance/Note.d.ts +4 -0
- package/dist/instance/PrimaryInstance.d.ts +3 -2
- package/dist/instance/RangeControl.d.ts +4 -0
- package/dist/instance/RankControl.d.ts +5 -1
- package/dist/instance/Root.d.ts +2 -1
- package/dist/instance/SelectControl.d.ts +5 -1
- package/dist/instance/TriggerControl.d.ts +4 -0
- package/dist/instance/UploadControl.d.ts +3 -2
- package/dist/instance/abstract/DescendantNode.d.ts +5 -4
- package/dist/instance/abstract/InstanceNode.d.ts +6 -5
- package/dist/instance/abstract/ValueNode.d.ts +2 -1
- package/dist/instance/attachments/buildAttributes.d.ts +6 -2
- package/dist/instance/hierarchy.d.ts +2 -2
- package/dist/instance/internal-api/AttributeContext.d.ts +6 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
- package/dist/instance/internal-api/InstanceValueContext.d.ts +6 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +0 -1
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +4 -0
- package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +1 -1
- package/dist/integration/xpath/adapter/kind.d.ts +5 -3
- package/dist/integration/xpath/adapter/traversal.d.ts +3 -3
- package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +1 -0
- package/dist/lib/codecs/items/SingleValueItemCodec.d.ts +1 -1
- package/dist/lib/reactivity/createInstanceValueState.d.ts +4 -1
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +2 -2
- package/dist/lib/xml-serialization.d.ts +1 -1
- package/dist/parse/XFormDOM.d.ts +3 -0
- package/dist/parse/expression/ActionComputationExpression.d.ts +4 -0
- package/dist/parse/model/ActionDefinition.d.ts +15 -0
- package/dist/parse/model/AttributeDefinition.d.ts +5 -1
- package/dist/parse/model/BindPreloadDefinition.d.ts +6 -10
- package/dist/parse/model/Event.d.ts +8 -0
- package/dist/parse/model/LeafNodeDefinition.d.ts +5 -2
- package/dist/parse/model/ModelActionMap.d.ts +9 -0
- package/dist/parse/model/ModelDefinition.d.ts +8 -1
- package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
- package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
- package/dist/parse/model/RootDefinition.d.ts +1 -0
- package/dist/solid.js +654 -429
- package/dist/solid.js.map +1 -1
- package/package.json +21 -17
- package/src/client/AttributeNode.ts +4 -6
- package/src/client/BaseNode.ts +6 -0
- package/src/client/form/FormInstanceConfig.ts +17 -0
- package/src/client/validation.ts +1 -1
- package/src/entrypoints/FormInstance.ts +1 -0
- package/src/instance/Attribute.ts +43 -59
- package/src/instance/Group.ts +5 -6
- package/src/instance/InputControl.ts +16 -1
- package/src/instance/ModelValue.ts +16 -1
- package/src/instance/Note.ts +15 -1
- package/src/instance/PrimaryInstance.ts +8 -10
- package/src/instance/RangeControl.ts +15 -1
- package/src/instance/RankControl.ts +17 -2
- package/src/instance/Root.ts +5 -6
- package/src/instance/SelectControl.ts +16 -2
- package/src/instance/TriggerControl.ts +15 -1
- package/src/instance/UploadControl.ts +9 -8
- package/src/instance/abstract/DescendantNode.ts +4 -8
- package/src/instance/abstract/InstanceNode.ts +7 -5
- package/src/instance/abstract/ValueNode.ts +2 -1
- package/src/instance/attachments/buildAttributes.ts +15 -4
- package/src/instance/children/childrenInitOptions.ts +2 -1
- package/src/instance/children/normalizeChildInitOptions.ts +1 -1
- package/src/instance/hierarchy.ts +2 -2
- package/src/instance/internal-api/AttributeContext.ts +6 -0
- package/src/instance/internal-api/InstanceConfig.ts +6 -1
- package/src/instance/internal-api/InstanceValueContext.ts +6 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +0 -1
- package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +4 -0
- package/src/instance/repeat/RepeatInstance.ts +3 -4
- package/src/integration/xpath/adapter/XFormsXPathNode.ts +1 -0
- package/src/integration/xpath/adapter/engineDOMAdapter.ts +2 -2
- package/src/integration/xpath/adapter/kind.ts +6 -1
- package/src/integration/xpath/adapter/names.ts +1 -0
- package/src/integration/xpath/adapter/traversal.ts +5 -6
- package/src/integration/xpath/static-dom/StaticAttribute.ts +1 -0
- package/src/lib/client-reactivity/instance-state/createValueNodeInstanceState.ts +2 -1
- package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +1 -0
- package/src/lib/codecs/NoteCodec.ts +1 -1
- package/src/lib/codecs/items/SingleValueItemCodec.ts +1 -3
- package/src/lib/reactivity/createInstanceValueState.ts +177 -52
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +2 -2
- package/src/lib/xml-serialization.ts +9 -1
- package/src/parse/XFormDOM.ts +9 -0
- package/src/parse/body/GroupElementDefinition.ts +1 -1
- package/src/parse/body/control/InputControlDefinition.ts +1 -1
- package/src/parse/expression/ActionComputationExpression.ts +12 -0
- package/src/parse/model/ActionDefinition.ts +70 -0
- package/src/parse/model/AttributeDefinition.ts +10 -2
- package/src/parse/model/AttributeDefinitionMap.ts +1 -1
- package/src/parse/model/BindDefinition.ts +1 -6
- package/src/parse/model/BindPreloadDefinition.ts +44 -12
- package/src/parse/model/Event.ts +9 -0
- package/src/parse/model/LeafNodeDefinition.ts +5 -1
- package/src/parse/model/ModelActionMap.ts +37 -0
- package/src/parse/model/ModelDefinition.ts +18 -3
- package/src/parse/model/NoteNodeDefinition.ts +5 -2
- package/src/parse/model/RangeNodeDefinition.ts +5 -2
- package/src/parse/model/RootDefinition.ts +22 -4
- package/dist/lib/reactivity/createAttributeValueState.d.ts +0 -15
- package/src/lib/reactivity/createAttributeValueState.ts +0 -189
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isTextNode } from '@getodk/common/lib/dom/predicates.ts';
|
|
2
|
+
import { ActionComputationExpression } from '../expression/ActionComputationExpression.ts';
|
|
3
|
+
import { type XFormEvent, XFORM_EVENT } from './Event.ts';
|
|
4
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
5
|
+
|
|
6
|
+
export class ActionDefinition {
|
|
7
|
+
static getRef(model: ModelDefinition, setValueElement: Element): string | null {
|
|
8
|
+
if (setValueElement.hasAttribute('ref')) {
|
|
9
|
+
return setValueElement.getAttribute('ref') ?? null;
|
|
10
|
+
}
|
|
11
|
+
if (setValueElement.hasAttribute('bind')) {
|
|
12
|
+
const bindId = setValueElement.getAttribute('bind');
|
|
13
|
+
const bindDefinition = Array.from(model.binds.values()).find((definition) => {
|
|
14
|
+
return definition.bindElement.getAttribute('id') === bindId;
|
|
15
|
+
});
|
|
16
|
+
return bindDefinition?.nodeset ?? null;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static getValue(element: Element): string {
|
|
22
|
+
if (element.hasAttribute('value')) {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
24
|
+
return element.getAttribute('value') || "''";
|
|
25
|
+
}
|
|
26
|
+
if (element.firstChild && isTextNode(element.firstChild)) {
|
|
27
|
+
// use the text content as the literal value
|
|
28
|
+
return `'${element.firstChild.nodeValue}'`;
|
|
29
|
+
}
|
|
30
|
+
return "''";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static isKnownEvent = (event: XFormEvent): event is XFormEvent => {
|
|
34
|
+
return Object.values(XFORM_EVENT).includes(event);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
static getEvents(element: Element): XFormEvent[] {
|
|
38
|
+
const events = element.getAttribute('event')?.split(' ') ?? [];
|
|
39
|
+
const unknownEvents = events.filter((event) => !this.isKnownEvent(event as XFormEvent));
|
|
40
|
+
if (unknownEvents.length) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`An action was registered for unsupported events: ${unknownEvents.join(', ')}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return events as XFormEvent[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
readonly ref: string;
|
|
49
|
+
readonly events: XFormEvent[];
|
|
50
|
+
readonly computation: ActionComputationExpression<'string'>;
|
|
51
|
+
readonly source: string | undefined;
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
model: ModelDefinition,
|
|
55
|
+
readonly element: Element,
|
|
56
|
+
source?: string
|
|
57
|
+
) {
|
|
58
|
+
const ref = ActionDefinition.getRef(model, element);
|
|
59
|
+
if (!ref) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'Invalid setvalue element - you must define either "ref" or "bind" attribute'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
this.ref = ref;
|
|
65
|
+
this.events = ActionDefinition.getEvents(element);
|
|
66
|
+
const value = ActionDefinition.getValue(element);
|
|
67
|
+
this.computation = new ActionComputationExpression('string', value);
|
|
68
|
+
this.source = source;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
8
8
|
import { escapeXMLText, serializeAttributeXML } from '../../lib/xml-serialization.ts';
|
|
9
9
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
10
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
10
11
|
import { NodeDefinition } from './NodeDefinition.ts';
|
|
11
12
|
import type { RootDefinition } from './RootDefinition.ts';
|
|
12
13
|
|
|
@@ -18,6 +19,7 @@ export class AttributeDefinition
|
|
|
18
19
|
|
|
19
20
|
readonly value: string;
|
|
20
21
|
readonly type = 'attribute';
|
|
22
|
+
readonly valueType = 'string';
|
|
21
23
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
22
24
|
readonly bodyElement = null;
|
|
23
25
|
readonly root: RootDefinition;
|
|
@@ -29,7 +31,7 @@ export class AttributeDefinition
|
|
|
29
31
|
readonly qualifiedName: QualifiedName;
|
|
30
32
|
|
|
31
33
|
constructor(
|
|
32
|
-
|
|
34
|
+
readonly model: ModelDefinition,
|
|
33
35
|
bind: BindDefinition,
|
|
34
36
|
readonly template: StaticAttribute
|
|
35
37
|
) {
|
|
@@ -37,7 +39,7 @@ export class AttributeDefinition
|
|
|
37
39
|
|
|
38
40
|
const { value } = template;
|
|
39
41
|
|
|
40
|
-
this.root = root;
|
|
42
|
+
this.root = model.root;
|
|
41
43
|
|
|
42
44
|
this.value = value;
|
|
43
45
|
this.qualifiedName = template.qualifiedName;
|
|
@@ -55,4 +57,10 @@ export class AttributeDefinition
|
|
|
55
57
|
serializeAttributeXML(): string {
|
|
56
58
|
return this.serializedXML;
|
|
57
59
|
}
|
|
60
|
+
|
|
61
|
+
toJSON() {
|
|
62
|
+
const { bind, bodyElement, parent, root, ...rest } = this;
|
|
63
|
+
|
|
64
|
+
return rest;
|
|
65
|
+
}
|
|
58
66
|
}
|
|
@@ -28,7 +28,7 @@ export class AttributeDefinitionMap extends Map<QualifiedName, AttributeDefiniti
|
|
|
28
28
|
const nonNamespaceAttributes = instanceNode.attributes.filter(isNonNamespaceAttribute);
|
|
29
29
|
const definitions = nonNamespaceAttributes.map((attribute) => {
|
|
30
30
|
const bind = model.binds.getOrCreateBindDefinition(attribute.nodeset);
|
|
31
|
-
return new AttributeDefinition(model
|
|
31
|
+
return new AttributeDefinition(model, bind, attribute);
|
|
32
32
|
});
|
|
33
33
|
return new this(definitions);
|
|
34
34
|
}
|
|
@@ -38,10 +38,7 @@ export class BindDefinition<T extends BindType = BindType> extends DependencyCon
|
|
|
38
38
|
// https://github.com/getodk/collect/issues/3758 mentions deprecation.
|
|
39
39
|
readonly saveIncomplete: BindComputationExpression<'saveIncomplete'>;
|
|
40
40
|
|
|
41
|
-
// TODO:
|
|
42
|
-
// readonly preload: string | null;
|
|
43
|
-
// readonly preloadParams: string | null;
|
|
44
|
-
// readonly 'max-pixels': string | null;
|
|
41
|
+
// TODO: deferred until prioritized: readonly 'max-pixels': string | null;
|
|
45
42
|
|
|
46
43
|
protected _parentBind: BindDefinition | null | undefined;
|
|
47
44
|
|
|
@@ -95,8 +92,6 @@ export class BindDefinition<T extends BindType = BindType> extends DependencyCon
|
|
|
95
92
|
this.constraintMsg = MessageDefinition.from(this, 'constraintMsg');
|
|
96
93
|
this.requiredMsg = MessageDefinition.from(this, 'requiredMsg');
|
|
97
94
|
|
|
98
|
-
// this.preload = BindComputation.forExpression(this, 'preload');
|
|
99
|
-
// this.preloadParams = BindComputation.forExpression(this, 'preloadParams');
|
|
100
95
|
// this['max-pixels'] = BindComputation.forExpression(this, 'max-pixels');
|
|
101
96
|
}
|
|
102
97
|
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { JAVAROSA_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
|
|
2
2
|
import type { PartiallyKnownString } from '@getodk/common/types/string/PartiallyKnownString.ts';
|
|
3
|
+
import type { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
|
|
4
|
+
import type { InstanceValueContext } from '../../instance/internal-api/InstanceValueContext.ts';
|
|
3
5
|
import type { BindElement } from './BindElement.ts';
|
|
6
|
+
import { XFORM_EVENT, type XFormEvent } from './Event.ts';
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Per {@link https://getodk.github.io/xforms-spec/#preload-attributes:~:text=concatenation%20of%20%E2%80%98uuid%3A%E2%80%99%20and%20uuid()}
|
|
10
|
+
*/
|
|
11
|
+
const PRELOAD_UID_EXPRESSION = 'concat("uuid:", uuid())';
|
|
12
|
+
|
|
13
|
+
type PartiallyKnownPreloadParameter<Known extends string> = PartiallyKnownString<
|
|
14
|
+
NonNullable<Known>
|
|
15
|
+
>;
|
|
8
16
|
|
|
9
17
|
interface PreloadParametersByType {
|
|
10
18
|
readonly uid: string | null;
|
|
@@ -61,15 +69,6 @@ const getPreloadInput = (bindElement: BindElement): AnyPreloadInput | null => {
|
|
|
61
69
|
*
|
|
62
70
|
* - {@link type}, a `jr:preload`
|
|
63
71
|
* - {@link parameter}, an associated `jr:preloadParams` value
|
|
64
|
-
*
|
|
65
|
-
* @todo It would probably make sense for the _definition_ to also convey:
|
|
66
|
-
*
|
|
67
|
-
* 1. Which {@link https://getodk.github.io/xforms-spec/#events | event} the
|
|
68
|
-
* preload is semantically associated with (noting that the spec may be a tad
|
|
69
|
-
* overzealous about this association).
|
|
70
|
-
*
|
|
71
|
-
* 2. The constant XPath expression (or other computation?) expressed by the
|
|
72
|
-
* combined {@link type} and {@link parameter}.
|
|
73
72
|
*/
|
|
74
73
|
export class BindPreloadDefinition<Type extends PreloadType> implements PreloadInput<Type> {
|
|
75
74
|
static from(bindElement: BindElement): AnyBindPreloadDefinition | null {
|
|
@@ -84,10 +83,43 @@ export class BindPreloadDefinition<Type extends PreloadType> implements PreloadI
|
|
|
84
83
|
|
|
85
84
|
readonly type: Type;
|
|
86
85
|
readonly parameter: PreloadParameter<Type>;
|
|
86
|
+
readonly event: XFormEvent;
|
|
87
|
+
|
|
88
|
+
getValue(context: AttributeContext | InstanceValueContext): string | undefined {
|
|
89
|
+
if (this.type === 'uid') {
|
|
90
|
+
return context.evaluator.evaluateString(PRELOAD_UID_EXPRESSION);
|
|
91
|
+
}
|
|
92
|
+
if (this.type === 'timestamp') {
|
|
93
|
+
return context.evaluator.evaluateString('now()');
|
|
94
|
+
}
|
|
95
|
+
if (this.type === 'date') {
|
|
96
|
+
return context.evaluator.evaluateString('today()');
|
|
97
|
+
}
|
|
98
|
+
if (this.type === 'property') {
|
|
99
|
+
const properties = context.instanceConfig.preloadProperties;
|
|
100
|
+
if (this.parameter === 'deviceid') {
|
|
101
|
+
return properties.deviceID;
|
|
102
|
+
}
|
|
103
|
+
if (this.parameter === 'email') {
|
|
104
|
+
return properties.email;
|
|
105
|
+
}
|
|
106
|
+
if (this.parameter === 'phonenumber') {
|
|
107
|
+
return properties.phoneNumber;
|
|
108
|
+
}
|
|
109
|
+
if (this.parameter === 'username') {
|
|
110
|
+
return properties.username;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
87
115
|
|
|
88
116
|
private constructor(input: PreloadInput<Type>) {
|
|
89
117
|
this.type = input.type;
|
|
90
118
|
this.parameter = input.parameter;
|
|
119
|
+
this.event =
|
|
120
|
+
this.type === 'timestamp' && this.parameter === 'end'
|
|
121
|
+
? XFORM_EVENT.xformsRevalidate
|
|
122
|
+
: XFORM_EVENT.odkInstanceFirstLoad;
|
|
91
123
|
}
|
|
92
124
|
}
|
|
93
125
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const XFORM_EVENT = {
|
|
2
|
+
odkInstanceLoad: 'odk-instance-load',
|
|
3
|
+
odkInstanceFirstLoad: 'odk-instance-first-load',
|
|
4
|
+
odkNewRepeat: 'odk-new-repeat',
|
|
5
|
+
xformsRevalidate: 'xforms-revalidate',
|
|
6
|
+
xformsValueChanged: 'xforms-value-changed',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type XFormEvent = (typeof XFORM_EVENT)[keyof typeof XFORM_EVENT];
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
} from '../../lib/names/NamespaceDeclarationMap.ts';
|
|
7
7
|
import { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
8
8
|
import type { AnyBodyElementDefinition, ControlElementDefinition } from '../body/BodyDefinition.ts';
|
|
9
|
+
import { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
|
|
9
10
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
10
11
|
import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
|
|
12
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
11
13
|
import type { ParentNodeDefinition } from './NodeDefinition.ts';
|
|
12
14
|
|
|
13
15
|
export class LeafNodeDefinition<V extends ValueType = ValueType>
|
|
@@ -20,9 +22,10 @@ export class LeafNodeDefinition<V extends ValueType = ValueType>
|
|
|
20
22
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
21
23
|
readonly qualifiedName: QualifiedName;
|
|
22
24
|
readonly children = null;
|
|
23
|
-
readonly attributes
|
|
25
|
+
readonly attributes: AttributeDefinitionMap;
|
|
24
26
|
|
|
25
27
|
constructor(
|
|
28
|
+
readonly model: ModelDefinition,
|
|
26
29
|
parent: ParentNodeDefinition,
|
|
27
30
|
bind: BindDefinition,
|
|
28
31
|
bodyElement: AnyBodyElementDefinition | null,
|
|
@@ -37,6 +40,7 @@ export class LeafNodeDefinition<V extends ValueType = ValueType>
|
|
|
37
40
|
this.valueType = bind.type.resolved satisfies ValueType as V;
|
|
38
41
|
this.qualifiedName = template.qualifiedName;
|
|
39
42
|
this.namespaceDeclarations = new NamespaceDeclarationMap(this);
|
|
43
|
+
this.attributes = AttributeDefinitionMap.from(model, template);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
toJSON() {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ActionDefinition } from './ActionDefinition.ts';
|
|
2
|
+
import { XFORM_EVENT } from './Event.ts';
|
|
3
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
4
|
+
|
|
5
|
+
const REPEAT_REGEX = /(\[[^\]]*\])/gm;
|
|
6
|
+
|
|
7
|
+
export class ModelActionMap extends Map<string, ActionDefinition> {
|
|
8
|
+
static fromModel(model: ModelDefinition): ModelActionMap {
|
|
9
|
+
return new this(model);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static getKey(ref: string): string {
|
|
13
|
+
return ref.replace(REPEAT_REGEX, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected constructor(model: ModelDefinition) {
|
|
17
|
+
super(
|
|
18
|
+
model.form.xformDOM.setValues.map((setValueElement) => {
|
|
19
|
+
const action = new ActionDefinition(model, setValueElement);
|
|
20
|
+
if (action.events.includes(XFORM_EVENT.odkNewRepeat)) {
|
|
21
|
+
throw new Error('Model contains "setvalue" element with "odk-new-repeat" event');
|
|
22
|
+
}
|
|
23
|
+
const key = ModelActionMap.getKey(action.ref);
|
|
24
|
+
return [key, action];
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override get(ref: string): ActionDefinition | undefined {
|
|
30
|
+
return super.get(ModelActionMap.getKey(ref));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
add(action: ActionDefinition) {
|
|
34
|
+
const key = ModelActionMap.getKey(action.ref);
|
|
35
|
+
this.set(key, action);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -6,6 +6,7 @@ import { parseStaticDocumentFromDOMSubtree } from '../shared/parseStaticDocument
|
|
|
6
6
|
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
7
7
|
import { generateItextChunks, type ChunkExpressionsByItextId } from './generateItextChunks.ts';
|
|
8
8
|
import { ItextTranslationsDefinition } from './ItextTranslationsDefinition.ts';
|
|
9
|
+
import { ModelActionMap } from './ModelActionMap.ts';
|
|
9
10
|
import { ModelBindMap } from './ModelBindMap.ts';
|
|
10
11
|
import type { AnyNodeDefinition } from './NodeDefinition.ts';
|
|
11
12
|
import type { NodeDefinitionMap } from './nodeDefinitionMap.ts';
|
|
@@ -13,18 +14,23 @@ import { nodeDefinitionMap } from './nodeDefinitionMap.ts';
|
|
|
13
14
|
import { RootDefinition } from './RootDefinition.ts';
|
|
14
15
|
import { SubmissionDefinition } from './SubmissionDefinition.ts';
|
|
15
16
|
|
|
17
|
+
type XformsRevalidateListener = () => void;
|
|
18
|
+
|
|
16
19
|
export class ModelDefinition {
|
|
17
20
|
readonly binds: ModelBindMap;
|
|
21
|
+
readonly actions: ModelActionMap;
|
|
18
22
|
readonly root: RootDefinition;
|
|
19
23
|
readonly nodes: NodeDefinitionMap;
|
|
20
24
|
readonly instance: StaticDocument;
|
|
21
25
|
readonly itextTranslations: ItextTranslationsDefinition;
|
|
22
26
|
readonly itextChunks: Map<string, ChunkExpressionsByItextId>;
|
|
27
|
+
readonly xformsRevalidateListeners: Map<string, XformsRevalidateListener>;
|
|
23
28
|
|
|
24
29
|
constructor(readonly form: XFormDefinition) {
|
|
25
30
|
const submission = new SubmissionDefinition(form.xformDOM);
|
|
26
31
|
|
|
27
32
|
this.binds = ModelBindMap.fromModel(this);
|
|
33
|
+
this.actions = ModelActionMap.fromModel(this);
|
|
28
34
|
this.instance = parseStaticDocumentFromDOMSubtree(form.xformDOM.primaryInstanceRoot, {
|
|
29
35
|
nodesetPrefix: '/',
|
|
30
36
|
});
|
|
@@ -32,6 +38,7 @@ export class ModelDefinition {
|
|
|
32
38
|
this.nodes = nodeDefinitionMap(this.root);
|
|
33
39
|
this.itextTranslations = ItextTranslationsDefinition.from(form.xformDOM);
|
|
34
40
|
this.itextChunks = generateItextChunks(form.xformDOM.itextTranslationElements);
|
|
41
|
+
this.xformsRevalidateListeners = new Map();
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
getNodeDefinition(nodeset: string): AnyNodeDefinition {
|
|
@@ -54,10 +61,12 @@ export class ModelDefinition {
|
|
|
54
61
|
return definition;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
registerXformsRevalidateListener(ref: string, listener: XformsRevalidateListener) {
|
|
65
|
+
this.xformsRevalidateListeners.set(ref, listener);
|
|
66
|
+
}
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
triggerXformsRevalidateListeners() {
|
|
69
|
+
this.xformsRevalidateListeners.forEach((listener: XformsRevalidateListener) => listener());
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
getTranslationChunks(
|
|
@@ -67,4 +76,10 @@ export class ModelDefinition {
|
|
|
67
76
|
const languageMap = this.itextChunks.get(activeLanguage.language);
|
|
68
77
|
return languageMap?.get(itextId) ?? [];
|
|
69
78
|
}
|
|
79
|
+
|
|
80
|
+
toJSON() {
|
|
81
|
+
const { form, ...rest } = this;
|
|
82
|
+
|
|
83
|
+
return rest;
|
|
84
|
+
}
|
|
70
85
|
}
|
|
@@ -9,6 +9,7 @@ import type { HintDefinition } from '../text/HintDefinition.ts';
|
|
|
9
9
|
import type { LabelDefinition } from '../text/LabelDefinition.ts';
|
|
10
10
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
11
11
|
import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
|
|
12
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
12
13
|
import type { ParentNodeDefinition } from './NodeDefinition.ts';
|
|
13
14
|
|
|
14
15
|
// prettier-ignore
|
|
@@ -41,6 +42,7 @@ export type NoteTextDefinition =
|
|
|
41
42
|
*/
|
|
42
43
|
export class NoteNodeDefinition<V extends ValueType = ValueType> extends LeafNodeDefinition<V> {
|
|
43
44
|
static from<V extends ValueType>(
|
|
45
|
+
model: ModelDefinition,
|
|
44
46
|
parent: ParentNodeDefinition,
|
|
45
47
|
bind: BindDefinition<V>,
|
|
46
48
|
bodyElement: AnyBodyElementDefinition | null,
|
|
@@ -57,16 +59,17 @@ export class NoteNodeDefinition<V extends ValueType = ValueType> extends LeafNod
|
|
|
57
59
|
return null;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
return new this(parent, bind, bodyElement, noteTextDefinition, node);
|
|
62
|
+
return new this(model, parent, bind, bodyElement, noteTextDefinition, node);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
constructor(
|
|
66
|
+
model: ModelDefinition,
|
|
64
67
|
parent: ParentNodeDefinition,
|
|
65
68
|
override readonly bind: NoteBindDefinition<V>,
|
|
66
69
|
override readonly bodyElement: InputControlDefinition,
|
|
67
70
|
readonly noteTextDefinition: NoteTextDefinition,
|
|
68
71
|
template: StaticLeafElement
|
|
69
72
|
) {
|
|
70
|
-
super(parent, bind, bodyElement, template);
|
|
73
|
+
super(model, parent, bind, bodyElement, template);
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from '../body/control/RangeControlDefinition.ts';
|
|
10
10
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
11
11
|
import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
|
|
12
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
12
13
|
import type { ParentNodeDefinition } from './NodeDefinition.ts';
|
|
13
14
|
|
|
14
15
|
const RANGE_VALUE_TYPES = ['decimal', 'int'] as const;
|
|
@@ -89,6 +90,7 @@ export class RangeNodeDefinition<V extends RangeValueType = RangeValueType>
|
|
|
89
90
|
implements RangeLeafNodeDefinition<V>
|
|
90
91
|
{
|
|
91
92
|
static from<V extends ValueType>(
|
|
93
|
+
model: ModelDefinition,
|
|
92
94
|
parent: ParentNodeDefinition,
|
|
93
95
|
bind: BindDefinition<V>,
|
|
94
96
|
bodyElement: RangeControlDefinition,
|
|
@@ -96,18 +98,19 @@ export class RangeNodeDefinition<V extends RangeValueType = RangeValueType>
|
|
|
96
98
|
): RangeNodeDefinition<Extract<V, RangeValueType>> {
|
|
97
99
|
assertRangeBindDefinition(bind);
|
|
98
100
|
|
|
99
|
-
return new this(parent, bind, bodyElement, node);
|
|
101
|
+
return new this(model, parent, bind, bodyElement, node);
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
readonly bounds: RangeNodeBoundsDefinition<V>;
|
|
103
105
|
|
|
104
106
|
private constructor(
|
|
107
|
+
model: ModelDefinition,
|
|
105
108
|
parent: ParentNodeDefinition,
|
|
106
109
|
override readonly bind: BindDefinition<V>,
|
|
107
110
|
override readonly bodyElement: RangeControlDefinition,
|
|
108
111
|
node: StaticLeafElement
|
|
109
112
|
) {
|
|
110
|
-
super(parent, bind, bodyElement, node);
|
|
113
|
+
super(model, parent, bind, bodyElement, node);
|
|
111
114
|
|
|
112
115
|
this.bounds = RangeNodeBoundsDefinition.from(bodyElement.bounds, bind);
|
|
113
116
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
2
2
|
import { NamespaceDeclarationMap } from '../../lib/names/NamespaceDeclarationMap.ts';
|
|
3
3
|
import { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
4
|
-
import type { BodyClassList } from '../body/BodyDefinition.ts';
|
|
4
|
+
import type { AnyBodyElementDefinition, BodyClassList } from '../body/BodyDefinition.ts';
|
|
5
5
|
import type { XFormDefinition } from '../XFormDefinition.ts';
|
|
6
|
+
import { ActionDefinition } from './ActionDefinition.ts';
|
|
6
7
|
import { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
|
|
7
8
|
import { GroupDefinition } from './GroupDefinition.ts';
|
|
8
9
|
import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
|
|
@@ -56,6 +57,19 @@ export class RootDefinition extends NodeDefinition<'root'> {
|
|
|
56
57
|
this.children = this.buildSubtree(this, template);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
private mapActions(bodyElement: AnyBodyElementDefinition) {
|
|
61
|
+
const source = bodyElement.reference;
|
|
62
|
+
if (!source) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const child of bodyElement.element.children) {
|
|
66
|
+
if (child.nodeName === 'setvalue') {
|
|
67
|
+
const action = new ActionDefinition(this.model, child, source);
|
|
68
|
+
this.model.actions.add(action);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
buildSubtree(parent: ParentNodeDefinition, node: StaticElement): readonly ChildNodeDefinition[] {
|
|
60
74
|
const { form, model } = this;
|
|
61
75
|
const { body } = form;
|
|
@@ -86,6 +100,10 @@ export class RootDefinition extends NodeDefinition<'root'> {
|
|
|
86
100
|
const bodyElement = body.getBodyElement(nodeset);
|
|
87
101
|
const [firstChild, ...restChildren] = children;
|
|
88
102
|
|
|
103
|
+
if (bodyElement) {
|
|
104
|
+
this.mapActions(bodyElement);
|
|
105
|
+
}
|
|
106
|
+
|
|
89
107
|
if (bodyElement?.type === 'repeat') {
|
|
90
108
|
return RepeatDefinition.from(model, parent, bind, bodyElement, children);
|
|
91
109
|
}
|
|
@@ -98,12 +116,12 @@ export class RootDefinition extends NodeDefinition<'root'> {
|
|
|
98
116
|
|
|
99
117
|
if (element.isLeafElement()) {
|
|
100
118
|
if (bodyElement?.type === 'range') {
|
|
101
|
-
return RangeNodeDefinition.from(parent, bind, bodyElement, element);
|
|
119
|
+
return RangeNodeDefinition.from(model, parent, bind, bodyElement, element);
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
return (
|
|
105
|
-
NoteNodeDefinition.from(parent, bind, bodyElement, element) ??
|
|
106
|
-
new LeafNodeDefinition(parent, bind, bodyElement, element)
|
|
123
|
+
NoteNodeDefinition.from(model, parent, bind, bodyElement, element) ??
|
|
124
|
+
new LeafNodeDefinition(model, parent, bind, bodyElement, element)
|
|
107
125
|
);
|
|
108
126
|
}
|
|
109
127
|
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
|
|
2
|
-
import { SimpleAtomicState } from './types.ts';
|
|
3
|
-
export type InstanceValueState = SimpleAtomicState<string>;
|
|
4
|
-
/**
|
|
5
|
-
* Provides a consistent interface for value nodes of any type which:
|
|
6
|
-
*
|
|
7
|
-
* - derives initial state from either an existing instance (e.g. for edits) or
|
|
8
|
-
* the node's definition (e.g. initializing a new instance)
|
|
9
|
-
* - decodes current primary instance state into the value node's runtime type
|
|
10
|
-
* - encodes updated runtime values to store updated instance state
|
|
11
|
-
* - initializes reactive computation of `calculate` bind expressions for those
|
|
12
|
-
* nodes defined with one
|
|
13
|
-
* - prevents downstream writes to nodes in a readonly state
|
|
14
|
-
*/
|
|
15
|
-
export declare const createAttributeValueState: (context: AttributeContext) => InstanceValueState;
|