@getodk/xforms-engine 0.14.0 → 0.16.0
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 +50 -0
- package/dist/client/BaseNode.d.ts +5 -0
- package/dist/client/form/FormInstanceConfig.d.ts +15 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/node-types.d.ts +1 -1
- package/dist/client/validation.d.ts +7 -1
- package/dist/index.js +730 -294
- package/dist/index.js.map +1 -1
- package/dist/instance/Attribute.d.ts +64 -0
- package/dist/instance/Group.d.ts +2 -0
- package/dist/instance/InputControl.d.ts +2 -0
- package/dist/instance/PrimaryInstance.d.ts +2 -0
- package/dist/instance/Root.d.ts +2 -0
- package/dist/instance/UploadControl.d.ts +2 -0
- package/dist/instance/abstract/InstanceNode.d.ts +5 -2
- package/dist/instance/abstract/ValueNode.d.ts +2 -0
- package/dist/instance/attachments/buildAttributes.d.ts +7 -0
- package/dist/instance/internal-api/AttributeContext.d.ts +35 -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 +15 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableParentNode.d.ts +2 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.d.ts +2 -2
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +4 -0
- package/dist/instance/repeat/BaseRepeatRange.d.ts +5 -0
- package/dist/instance/repeat/RepeatInstance.d.ts +2 -2
- package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +2 -2
- package/dist/lib/client-reactivity/instance-state/createAttributeNodeInstanceState.d.ts +3 -0
- package/dist/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.d.ts +0 -3
- package/dist/lib/codecs/items/SingleValueItemCodec.d.ts +1 -1
- package/dist/lib/names/NamespaceDeclarationMap.d.ts +1 -1
- package/dist/lib/reactivity/createAttributeState.d.ts +16 -0
- 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 +5 -9
- package/dist/parse/XFormDOM.d.ts +4 -1
- 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 +24 -0
- package/dist/parse/model/{RootAttributeMap.d.ts → AttributeDefinitionMap.d.ts} +4 -10
- package/dist/parse/model/BindPreloadDefinition.d.ts +6 -10
- package/dist/parse/model/Event.d.ts +8 -0
- package/dist/parse/model/GroupDefinition.d.ts +4 -1
- package/dist/parse/model/LeafNodeDefinition.d.ts +5 -1
- package/dist/parse/model/ModelActionMap.d.ts +9 -0
- package/dist/parse/model/ModelDefinition.d.ts +8 -1
- package/dist/parse/model/NodeDefinition.d.ts +8 -3
- package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
- package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
- package/dist/parse/model/RepeatDefinition.d.ts +4 -1
- package/dist/parse/model/RootDefinition.d.ts +3 -2
- package/dist/solid.js +730 -294
- package/dist/solid.js.map +1 -1
- package/package.json +21 -17
- package/src/client/AttributeNode.ts +59 -0
- package/src/client/BaseNode.ts +6 -0
- package/src/client/form/FormInstanceConfig.ts +17 -0
- package/src/client/index.ts +1 -0
- package/src/client/node-types.ts +1 -0
- package/src/client/validation.ts +9 -1
- package/src/entrypoints/FormInstance.ts +1 -0
- package/src/instance/Attribute.ts +164 -0
- package/src/instance/Group.ts +7 -0
- package/src/instance/InputControl.ts +8 -0
- package/src/instance/ModelValue.ts +7 -0
- package/src/instance/Note.ts +6 -0
- package/src/instance/PrimaryInstance.ts +7 -0
- package/src/instance/RangeControl.ts +6 -0
- package/src/instance/RankControl.ts +7 -0
- package/src/instance/Root.ts +7 -0
- package/src/instance/SelectControl.ts +6 -0
- package/src/instance/TriggerControl.ts +6 -0
- package/src/instance/UploadControl.ts +5 -0
- package/src/instance/abstract/DescendantNode.ts +0 -1
- package/src/instance/abstract/InstanceNode.ts +4 -1
- package/src/instance/abstract/ValueNode.ts +2 -0
- package/src/instance/attachments/buildAttributes.ts +15 -0
- package/src/instance/children/normalizeChildInitOptions.ts +1 -1
- package/src/instance/internal-api/AttributeContext.ts +40 -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 +18 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableParentNode.ts +2 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.ts +2 -3
- package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +4 -0
- package/src/instance/repeat/BaseRepeatRange.ts +14 -0
- package/src/instance/repeat/RepeatInstance.ts +5 -5
- package/src/integration/xpath/adapter/XFormsXPathNode.ts +3 -1
- package/src/lib/client-reactivity/instance-state/createAttributeNodeInstanceState.ts +16 -0
- package/src/lib/client-reactivity/instance-state/createParentNodeInstanceState.ts +5 -5
- package/src/lib/client-reactivity/instance-state/createRootInstanceState.ts +6 -9
- package/src/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.ts +5 -15
- 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/names/NamespaceDeclarationMap.ts +1 -1
- package/src/lib/reactivity/createAttributeState.ts +51 -0
- package/src/lib/reactivity/createInstanceValueState.ts +152 -53
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +2 -2
- package/src/lib/xml-serialization.ts +38 -34
- 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 +59 -0
- package/src/parse/model/{RootAttributeMap.ts → AttributeDefinitionMap.ts} +7 -13
- 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/GroupDefinition.ts +6 -0
- package/src/parse/model/LeafNodeDefinition.ts +5 -0
- package/src/parse/model/ModelActionMap.ts +37 -0
- package/src/parse/model/ModelDefinition.ts +18 -3
- package/src/parse/model/NodeDefinition.ts +11 -3
- package/src/parse/model/NoteNodeDefinition.ts +5 -2
- package/src/parse/model/RangeNodeDefinition.ts +5 -2
- package/src/parse/model/RepeatDefinition.ts +8 -1
- package/src/parse/model/RootDefinition.ts +27 -9
- package/src/parse/model/nodeDefinitionMap.ts +1 -1
- package/dist/error/TemplatedNodeAttributeSerializationError.d.ts +0 -22
- package/dist/parse/model/RootAttributeDefinition.d.ts +0 -21
- package/src/error/TemplatedNodeAttributeSerializationError.ts +0 -24
- package/src/parse/model/RootAttributeDefinition.ts +0 -44
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { XMLNS_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
|
|
2
|
+
import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
|
|
3
|
+
import {
|
|
4
|
+
NamespaceDeclarationMap,
|
|
5
|
+
type NamedNodeDefinition,
|
|
6
|
+
} from '../../lib/names/NamespaceDeclarationMap.ts';
|
|
7
|
+
import { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
8
|
+
import { escapeXMLText, serializeAttributeXML } from '../../lib/xml-serialization.ts';
|
|
9
|
+
import type { BindDefinition } from './BindDefinition.ts';
|
|
10
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
11
|
+
import { NodeDefinition } from './NodeDefinition.ts';
|
|
12
|
+
import type { RootDefinition } from './RootDefinition.ts';
|
|
13
|
+
|
|
14
|
+
export class AttributeDefinition
|
|
15
|
+
extends NodeDefinition<'attribute'>
|
|
16
|
+
implements NamedNodeDefinition
|
|
17
|
+
{
|
|
18
|
+
private readonly serializedXML: string;
|
|
19
|
+
|
|
20
|
+
readonly value: string;
|
|
21
|
+
readonly type = 'attribute';
|
|
22
|
+
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
23
|
+
readonly bodyElement = null;
|
|
24
|
+
readonly root: RootDefinition;
|
|
25
|
+
readonly isTranslated: boolean = false;
|
|
26
|
+
readonly parent = null;
|
|
27
|
+
readonly children = null;
|
|
28
|
+
readonly attributes = null;
|
|
29
|
+
|
|
30
|
+
readonly qualifiedName: QualifiedName;
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
readonly model: ModelDefinition,
|
|
34
|
+
bind: BindDefinition,
|
|
35
|
+
readonly template: StaticAttribute
|
|
36
|
+
) {
|
|
37
|
+
super(bind);
|
|
38
|
+
|
|
39
|
+
const { value } = template;
|
|
40
|
+
|
|
41
|
+
this.root = model.root;
|
|
42
|
+
|
|
43
|
+
this.value = value;
|
|
44
|
+
this.qualifiedName = template.qualifiedName;
|
|
45
|
+
this.namespaceDeclarations = new NamespaceDeclarationMap(this);
|
|
46
|
+
|
|
47
|
+
// We serialize namespace declarations separately
|
|
48
|
+
if (this.qualifiedName.namespaceURI?.href === XMLNS_NAMESPACE_URI) {
|
|
49
|
+
this.serializedXML = '';
|
|
50
|
+
} else {
|
|
51
|
+
const xmlValue = escapeXMLText(this.value, true);
|
|
52
|
+
this.serializedXML = serializeAttributeXML(this.qualifiedName, xmlValue);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
serializeAttributeXML(): string {
|
|
57
|
+
return this.serializedXML;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -2,8 +2,8 @@ import { XMLNS_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
|
|
|
2
2
|
import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
|
|
3
3
|
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
4
4
|
import type { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
5
|
-
import {
|
|
6
|
-
import type {
|
|
5
|
+
import { AttributeDefinition } from './AttributeDefinition.ts';
|
|
6
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @todo We should probably just distinguish these as separate `StaticNode`
|
|
@@ -14,12 +14,6 @@ const isNonNamespaceAttribute = (attribute: StaticAttribute) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @todo This can be trivially expanded to a narrowly general case when we
|
|
18
|
-
* prioritize work to
|
|
19
|
-
* {@link https://github.com/getodk/web-forms/issues/285 | support attributes}
|
|
20
|
-
* (as modeled form nodes on par with elements). It's been deferred here to
|
|
21
|
-
* avoid expanding scope of an already fairly large yak shave.
|
|
22
|
-
*
|
|
23
17
|
* @todo There's a **much more expansive** general case just waiting for a good
|
|
24
18
|
* opportuntity to prioritize it. E.g. a `NamedNodeMap<T>`, where T is any
|
|
25
19
|
* generalized concept of a named node. This expansive generalization would have
|
|
@@ -29,17 +23,17 @@ const isNonNamespaceAttribute = (attribute: StaticAttribute) => {
|
|
|
29
23
|
*
|
|
30
24
|
* @see {@link QualifiedName} for more detail.
|
|
31
25
|
*/
|
|
32
|
-
export class
|
|
33
|
-
static from(
|
|
26
|
+
export class AttributeDefinitionMap extends Map<QualifiedName, AttributeDefinition> {
|
|
27
|
+
static from(model: ModelDefinition, instanceNode: StaticElement) {
|
|
34
28
|
const nonNamespaceAttributes = instanceNode.attributes.filter(isNonNamespaceAttribute);
|
|
35
29
|
const definitions = nonNamespaceAttributes.map((attribute) => {
|
|
36
|
-
|
|
30
|
+
const bind = model.binds.getOrCreateBindDefinition(attribute.nodeset);
|
|
31
|
+
return new AttributeDefinition(model, bind, attribute);
|
|
37
32
|
});
|
|
38
|
-
|
|
39
33
|
return new this(definitions);
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
private constructor(definitions: readonly
|
|
36
|
+
private constructor(definitions: readonly AttributeDefinition[]) {
|
|
43
37
|
super(
|
|
44
38
|
definitions.map((attribute) => {
|
|
45
39
|
return [attribute.qualifiedName, attribute];
|
|
@@ -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];
|
|
@@ -3,8 +3,10 @@ import { NamespaceDeclarationMap } from '../../lib/names/NamespaceDeclarationMap
|
|
|
3
3
|
import { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
4
4
|
import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
|
|
5
5
|
import type { GroupElementDefinition } from '../body/GroupElementDefinition.ts';
|
|
6
|
+
import { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
|
|
6
7
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
7
8
|
import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
|
|
9
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
8
10
|
import type { ChildNodeDefinition, ParentNodeDefinition } from './NodeDefinition.ts';
|
|
9
11
|
|
|
10
12
|
export class GroupDefinition extends DescendentNodeDefinition<
|
|
@@ -16,8 +18,10 @@ export class GroupDefinition extends DescendentNodeDefinition<
|
|
|
16
18
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
17
19
|
readonly qualifiedName: QualifiedName;
|
|
18
20
|
readonly children: readonly ChildNodeDefinition[];
|
|
21
|
+
readonly attributes: AttributeDefinitionMap;
|
|
19
22
|
|
|
20
23
|
constructor(
|
|
24
|
+
model: ModelDefinition,
|
|
21
25
|
parent: ParentNodeDefinition,
|
|
22
26
|
bind: BindDefinition,
|
|
23
27
|
bodyElement: AnyBodyElementDefinition | null,
|
|
@@ -37,6 +41,8 @@ export class GroupDefinition extends DescendentNodeDefinition<
|
|
|
37
41
|
this.qualifiedName = template.qualifiedName;
|
|
38
42
|
this.namespaceDeclarations = new NamespaceDeclarationMap(this);
|
|
39
43
|
this.children = root.buildSubtree(this, template);
|
|
44
|
+
|
|
45
|
+
this.attributes = AttributeDefinitionMap.from(model, template);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
toJSON() {
|
|
@@ -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,8 +22,10 @@ export class LeafNodeDefinition<V extends ValueType = ValueType>
|
|
|
20
22
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
21
23
|
readonly qualifiedName: QualifiedName;
|
|
22
24
|
readonly children = null;
|
|
25
|
+
readonly attributes: AttributeDefinitionMap;
|
|
23
26
|
|
|
24
27
|
constructor(
|
|
28
|
+
readonly model: ModelDefinition,
|
|
25
29
|
parent: ParentNodeDefinition,
|
|
26
30
|
bind: BindDefinition,
|
|
27
31
|
bodyElement: AnyBodyElementDefinition | null,
|
|
@@ -36,6 +40,7 @@ export class LeafNodeDefinition<V extends ValueType = ValueType>
|
|
|
36
40
|
this.valueType = bind.type.resolved satisfies ValueType as V;
|
|
37
41
|
this.qualifiedName = template.qualifiedName;
|
|
38
42
|
this.namespaceDeclarations = new NamespaceDeclarationMap(this);
|
|
43
|
+
this.attributes = AttributeDefinitionMap.from(model, template);
|
|
39
44
|
}
|
|
40
45
|
|
|
41
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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
|
|
1
2
|
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
2
3
|
import type {
|
|
3
4
|
NamedSubtreeDefinition,
|
|
@@ -6,6 +7,8 @@ import type {
|
|
|
6
7
|
import type { QualifiedName } from '../../lib/names/QualifiedName.ts';
|
|
7
8
|
import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
|
|
8
9
|
import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
|
|
10
|
+
import type { AttributeDefinition } from './AttributeDefinition.ts';
|
|
11
|
+
import type { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
|
|
9
12
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
10
13
|
import type { GroupDefinition } from './GroupDefinition.ts';
|
|
11
14
|
import type { LeafNodeDefinition } from './LeafNodeDefinition.ts';
|
|
@@ -45,13 +48,16 @@ export type GroupNodeType = 'group';
|
|
|
45
48
|
*/
|
|
46
49
|
export type LeafNodeType = 'leaf-node';
|
|
47
50
|
|
|
51
|
+
export type AttributeNodeType = 'attribute';
|
|
52
|
+
|
|
48
53
|
// prettier-ignore
|
|
49
54
|
export type NodeDefinitionType =
|
|
50
55
|
// eslint-disable-next-line @typescript-eslint/sort-type-constituents
|
|
51
56
|
| RootNodeType
|
|
52
57
|
| RepeatType
|
|
53
58
|
| GroupNodeType
|
|
54
|
-
| LeafNodeType
|
|
59
|
+
| LeafNodeType
|
|
60
|
+
| AttributeNodeType;
|
|
55
61
|
|
|
56
62
|
// prettier-ignore
|
|
57
63
|
export type ParentNodeDefinition =
|
|
@@ -76,8 +82,9 @@ export abstract class NodeDefinition<Type extends NodeDefinitionType>
|
|
|
76
82
|
abstract readonly isTranslated: boolean;
|
|
77
83
|
abstract readonly root: RootDefinition;
|
|
78
84
|
abstract readonly parent: ParentNodeDefinition | null;
|
|
79
|
-
abstract readonly template: StaticElement;
|
|
85
|
+
abstract readonly template: StaticAttribute | StaticElement;
|
|
80
86
|
abstract readonly children: readonly ChildNodeDefinition[] | null;
|
|
87
|
+
abstract readonly attributes: AttributeDefinitionMap | null;
|
|
81
88
|
|
|
82
89
|
readonly nodeset: string;
|
|
83
90
|
|
|
@@ -92,4 +99,5 @@ export type AnyNodeDefinition =
|
|
|
92
99
|
| RootDefinition
|
|
93
100
|
| AnyRepeatDefinition
|
|
94
101
|
| GroupDefinition
|
|
95
|
-
| LeafNodeDefinition
|
|
102
|
+
| LeafNodeDefinition
|
|
103
|
+
| AttributeDefinition;
|
|
@@ -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
|
}
|
|
@@ -19,9 +19,11 @@ import type { NamespaceURL } from '../../lib/names/NamespaceURL.ts';
|
|
|
19
19
|
import type { QualifiedName, QualifiedNameSource } from '../../lib/names/QualifiedName.ts';
|
|
20
20
|
import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
|
|
21
21
|
import { RepeatCountControlExpression } from '../expression/RepeatCountControlExpression.ts';
|
|
22
|
+
import { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
|
|
22
23
|
import type { BindDefinition } from './BindDefinition.ts';
|
|
23
24
|
import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
|
|
24
25
|
import type { GroupDefinition } from './GroupDefinition.ts';
|
|
26
|
+
import type { ModelDefinition } from './ModelDefinition.ts';
|
|
25
27
|
import type { ChildNodeDefinition, ParentNodeDefinition } from './NodeDefinition.ts';
|
|
26
28
|
import type { RootDefinition } from './RootDefinition.ts';
|
|
27
29
|
|
|
@@ -313,18 +315,20 @@ export interface UncontrolledRepeatDefinition extends RepeatDefinition {
|
|
|
313
315
|
*/
|
|
314
316
|
export class RepeatDefinition extends DescendentNodeDefinition<'repeat', RepeatElementDefinition> {
|
|
315
317
|
static from(
|
|
318
|
+
model: ModelDefinition,
|
|
316
319
|
parent: ParentNodeDefinition,
|
|
317
320
|
bind: BindDefinition,
|
|
318
321
|
bodyElement: RepeatElementDefinition,
|
|
319
322
|
instanceNodes: RepeatInstanceNodes
|
|
320
323
|
): AnyRepeatDefinition;
|
|
321
324
|
static from(
|
|
325
|
+
model: ModelDefinition,
|
|
322
326
|
parent: ParentNodeDefinition,
|
|
323
327
|
bind: BindDefinition,
|
|
324
328
|
bodyElement: RepeatElementDefinition,
|
|
325
329
|
instanceNodes: RepeatInstanceNodes
|
|
326
330
|
): RepeatDefinition {
|
|
327
|
-
return new this(parent, bind, bodyElement, instanceNodes);
|
|
331
|
+
return new this(model, parent, bind, bodyElement, instanceNodes);
|
|
328
332
|
}
|
|
329
333
|
|
|
330
334
|
readonly type = 'repeat';
|
|
@@ -333,8 +337,10 @@ export class RepeatDefinition extends DescendentNodeDefinition<'repeat', RepeatE
|
|
|
333
337
|
readonly template: StaticElement;
|
|
334
338
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
335
339
|
readonly qualifiedName: QualifiedName;
|
|
340
|
+
readonly attributes: AttributeDefinitionMap;
|
|
336
341
|
|
|
337
342
|
private constructor(
|
|
343
|
+
model: ModelDefinition,
|
|
338
344
|
parent: ParentNodeDefinition,
|
|
339
345
|
bind: BindDefinition,
|
|
340
346
|
bodyElement: RepeatElementDefinition,
|
|
@@ -353,6 +359,7 @@ export class RepeatDefinition extends DescendentNodeDefinition<'repeat', RepeatE
|
|
|
353
359
|
this.children = root.buildSubtree(self, template);
|
|
354
360
|
|
|
355
361
|
const initialCount = this.omitTemplate(instanceNodes).length;
|
|
362
|
+
this.attributes = AttributeDefinitionMap.from(model, template);
|
|
356
363
|
|
|
357
364
|
this.count = RepeatCountControlExpression.from(bodyElement, initialCount);
|
|
358
365
|
}
|