@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
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { InstanceState } from '../../../client/serialization/InstanceState.ts';
|
|
2
|
-
import { TemplatedNodeAttributeSerializationError } from '../../../error/TemplatedNodeAttributeSerializationError.ts';
|
|
3
2
|
import type { ClientReactiveSerializableTemplatedNode } from '../../../instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.ts';
|
|
4
3
|
import { serializeParentElementXML } from '../../xml-serialization.ts';
|
|
5
4
|
|
|
6
|
-
/**
|
|
7
|
-
* @see {@link TemplatedNodeAttributeSerializationError}
|
|
8
|
-
*/
|
|
9
5
|
export const createTemplatedNodeInstanceState = (
|
|
10
6
|
node: ClientReactiveSerializableTemplatedNode
|
|
11
7
|
): InstanceState => {
|
|
@@ -15,17 +11,11 @@ export const createTemplatedNodeInstanceState = (
|
|
|
15
11
|
return '';
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (attributes != null) {
|
|
25
|
-
throw new TemplatedNodeAttributeSerializationError();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return serializeParentElementXML(node.definition.qualifiedName, serializedChildren);
|
|
14
|
+
return serializeParentElementXML(
|
|
15
|
+
node.definition.qualifiedName,
|
|
16
|
+
node.currentState.children,
|
|
17
|
+
node.currentState.attributes
|
|
18
|
+
);
|
|
29
19
|
},
|
|
30
20
|
};
|
|
31
21
|
};
|
|
@@ -14,8 +14,9 @@ export const createValueNodeInstanceState = (
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const xmlValue = escapeXMLText(node.currentState.instanceValue);
|
|
17
|
+
const attributes = node.currentState.attributes;
|
|
17
18
|
|
|
18
|
-
return serializeLeafElementXML(qualifiedName, xmlValue);
|
|
19
|
+
return serializeLeafElementXML(qualifiedName, xmlValue, attributes);
|
|
19
20
|
},
|
|
20
21
|
};
|
|
21
22
|
};
|
|
@@ -208,6 +208,7 @@ export const prepareInstancePayload = <PayloadType extends InstancePayloadType>(
|
|
|
208
208
|
instanceRoot: ClientReactiveSerializableInstance,
|
|
209
209
|
options: PrepareInstancePayloadOptions<PayloadType>
|
|
210
210
|
): InstancePayload<PayloadType> => {
|
|
211
|
+
instanceRoot.root.parent.model.triggerXformsRevalidateListeners();
|
|
211
212
|
const validation = validateInstance(instanceRoot);
|
|
212
213
|
const submissionMeta = instanceRoot.definition.submission;
|
|
213
214
|
const instanceFile = new InstanceFile(instanceRoot);
|
|
@@ -10,7 +10,7 @@ export type NoteRuntimeValue<V extends ValueType> =
|
|
|
10
10
|
// prettier-ignore
|
|
11
11
|
export type NoteInputValue<V extends ValueType> =
|
|
12
12
|
| RuntimeInputValue<V>
|
|
13
|
-
| RuntimeValue<V>
|
|
13
|
+
| RuntimeValue<V> // eslint-disable-line @typescript-eslint/no-redundant-type-constituents
|
|
14
14
|
| null;
|
|
15
15
|
|
|
16
16
|
export class NoteCodec<V extends ValueType> extends ValueCodec<
|
|
@@ -12,9 +12,7 @@ export type SingleValueSelectRuntimeValues =
|
|
|
12
12
|
* @see {@link encodeValueFactory}
|
|
13
13
|
*/
|
|
14
14
|
// prettier-ignore
|
|
15
|
-
type SingleValueSelectCodecValues =
|
|
16
|
-
| SingleValueSelectRuntimeValues
|
|
17
|
-
| readonly string[];
|
|
15
|
+
type SingleValueSelectCodecValues = readonly string[];
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* @todo This is more permissive than it should be, allowing an array of any
|
|
@@ -11,7 +11,7 @@ type NamedNodeDefinitionMap = ReadonlyMap<QualifiedName, NamedNodeDefinition>;
|
|
|
11
11
|
export interface NamedSubtreeDefinition extends NamedNodeDefinition {
|
|
12
12
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
13
13
|
readonly parent: NamedSubtreeDefinition | null;
|
|
14
|
-
readonly attributes
|
|
14
|
+
readonly attributes: NamedNodeDefinitionMap | null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Accessor, Setter, Signal } from 'solid-js';
|
|
2
|
+
import { createSignal } from 'solid-js';
|
|
3
|
+
import type { Attribute } from '../../instance/Attribute.ts';
|
|
4
|
+
import type { ReactiveScope } from './scope.ts';
|
|
5
|
+
|
|
6
|
+
export interface AttributeState {
|
|
7
|
+
readonly attributes: Signal<readonly Attribute[]>;
|
|
8
|
+
readonly getAttributes: Accessor<readonly Attribute[]>;
|
|
9
|
+
readonly setAttributes: Setter<readonly Attribute[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates attributes state suitable for all node types
|
|
14
|
+
*
|
|
15
|
+
* The produced {@link AttributeState.attributes} (and its get/set convenience
|
|
16
|
+
* methods) signal is intended to be used to store the engine's attribute state,
|
|
17
|
+
* and update that state when appropriate.
|
|
18
|
+
*/
|
|
19
|
+
export const createAttributeState = (scope: ReactiveScope): AttributeState => {
|
|
20
|
+
return scope.runTask(() => {
|
|
21
|
+
const baseState = createSignal<readonly Attribute[]>([]);
|
|
22
|
+
const [getAttributes, baseSetAttributes] = baseState;
|
|
23
|
+
|
|
24
|
+
type AttributeSetterCallback = (prev: readonly Attribute[]) => readonly Attribute[];
|
|
25
|
+
type AttributeOrSetterCallback = AttributeSetterCallback | readonly Attribute[];
|
|
26
|
+
|
|
27
|
+
const setAttributes: Setter<readonly Attribute[]> = (
|
|
28
|
+
valueOrSetterCallback: AttributeOrSetterCallback
|
|
29
|
+
) => {
|
|
30
|
+
let setterCallback: AttributeSetterCallback;
|
|
31
|
+
|
|
32
|
+
if (typeof valueOrSetterCallback === 'function') {
|
|
33
|
+
setterCallback = valueOrSetterCallback;
|
|
34
|
+
} else {
|
|
35
|
+
setterCallback = (_) => valueOrSetterCallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return baseSetAttributes((prev) => {
|
|
39
|
+
return setterCallback(prev);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const attributes: Signal<readonly Attribute[]> = [getAttributes, setAttributes];
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
attributes,
|
|
47
|
+
getAttributes,
|
|
48
|
+
setAttributes,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
import type { Signal } from 'solid-js';
|
|
2
2
|
import { createComputed, createMemo, createSignal, untrack } from 'solid-js';
|
|
3
|
+
import type { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
|
|
3
4
|
import type { InstanceValueContext } from '../../instance/internal-api/InstanceValueContext.ts';
|
|
5
|
+
import { ActionComputationExpression } from '../../parse/expression/ActionComputationExpression.ts';
|
|
4
6
|
import type { BindComputationExpression } from '../../parse/expression/BindComputationExpression.ts';
|
|
7
|
+
import { ActionDefinition } from '../../parse/model/ActionDefinition.ts';
|
|
8
|
+
import type { AnyBindPreloadDefinition } from '../../parse/model/BindPreloadDefinition.ts';
|
|
9
|
+
import { XFORM_EVENT } from '../../parse/model/Event.ts';
|
|
5
10
|
import { createComputedExpression } from './createComputedExpression.ts';
|
|
6
11
|
import type { SimpleAtomicState, SimpleAtomicStateSetter } from './types.ts';
|
|
7
12
|
|
|
8
|
-
const
|
|
13
|
+
const REPEAT_INDEX_REGEX = /([^[]*)(\[[0-9]+\])/g;
|
|
14
|
+
|
|
15
|
+
type ValueContext = AttributeContext | InstanceValueContext;
|
|
16
|
+
|
|
17
|
+
const isInstanceFirstLoad = (context: ValueContext) => {
|
|
9
18
|
return context.rootDocument.initializationMode === 'create';
|
|
10
19
|
};
|
|
11
20
|
|
|
21
|
+
const isAddingRepeatChild = (context: ValueContext) => {
|
|
22
|
+
return context.rootDocument.isAttached();
|
|
23
|
+
};
|
|
24
|
+
|
|
12
25
|
/**
|
|
13
26
|
* Special case, does not correspond to any event.
|
|
14
|
-
*
|
|
15
|
-
* @see {@link shouldPreloadUID}
|
|
16
27
|
*/
|
|
17
|
-
const isEditInitialLoad = (context:
|
|
28
|
+
const isEditInitialLoad = (context: ValueContext) => {
|
|
18
29
|
return context.rootDocument.initializationMode === 'edit';
|
|
19
30
|
};
|
|
20
31
|
|
|
21
|
-
const getInitialValue = (context:
|
|
32
|
+
const getInitialValue = (context: ValueContext): string => {
|
|
22
33
|
const sourceNode = context.instanceNode ?? context.definition.template;
|
|
23
34
|
|
|
24
35
|
return context.decodeInstanceValue(sourceNode.value);
|
|
@@ -36,7 +47,7 @@ type RelevantValueState = SimpleAtomicState<string>;
|
|
|
36
47
|
* node/context's relevance is restored
|
|
37
48
|
*/
|
|
38
49
|
const createRelevantValueState = (
|
|
39
|
-
context:
|
|
50
|
+
context: ValueContext,
|
|
40
51
|
baseValueState: BaseValueState
|
|
41
52
|
): RelevantValueState => {
|
|
42
53
|
return context.scope.runTask(() => {
|
|
@@ -59,7 +70,7 @@ const createRelevantValueState = (
|
|
|
59
70
|
* (client/user) writes when the field is in a `readonly` state.
|
|
60
71
|
*/
|
|
61
72
|
const guardDownstreamReadonlyWrites = (
|
|
62
|
-
context:
|
|
73
|
+
context: ValueContext,
|
|
63
74
|
baseState: SimpleAtomicState<string>
|
|
64
75
|
): SimpleAtomicState<string> => {
|
|
65
76
|
const { readonly } = context.definition.bind;
|
|
@@ -83,47 +94,86 @@ const guardDownstreamReadonlyWrites = (
|
|
|
83
94
|
return [getValue, setValue];
|
|
84
95
|
};
|
|
85
96
|
|
|
86
|
-
/**
|
|
87
|
-
* Per {@link https://getodk.github.io/xforms-spec/#preload-attributes:~:text=concatenation%20of%20%E2%80%98uuid%3A%E2%80%99%20and%20uuid()}
|
|
88
|
-
*/
|
|
89
|
-
const PRELOAD_UID_EXPRESSION = 'concat("uuid:", uuid())';
|
|
90
|
-
|
|
91
97
|
/**
|
|
92
98
|
* @todo It feels increasingly awkward to keep piling up preload stuff here, but it won't stay that way for long. In the meantime, this seems like the best way to express the cases where `preload="uid"` should be effective, i.e.:
|
|
93
99
|
*
|
|
94
100
|
* - When an instance is first loaded ({@link isInstanceFirstLoad})
|
|
95
101
|
* - When an instance is initially loaded for editing ({@link isEditInitialLoad})
|
|
96
102
|
*/
|
|
97
|
-
const
|
|
103
|
+
const isLoading = (context: ValueContext) => {
|
|
98
104
|
return isInstanceFirstLoad(context) || isEditInitialLoad(context);
|
|
99
105
|
};
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
const setValueIfPreloadDefined = (
|
|
108
|
+
context: ValueContext,
|
|
109
|
+
setValue: SimpleAtomicStateSetter<string>,
|
|
110
|
+
preload: AnyBindPreloadDefinition
|
|
111
|
+
) => {
|
|
112
|
+
const value = preload.getValue(context);
|
|
113
|
+
if (value) {
|
|
114
|
+
setValue(value);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const postloadValue = (
|
|
119
|
+
context: ValueContext,
|
|
120
|
+
setValue: SimpleAtomicStateSetter<string>,
|
|
121
|
+
preload: AnyBindPreloadDefinition
|
|
122
|
+
) => {
|
|
123
|
+
const ref = context.contextReference();
|
|
124
|
+
context.definition.model.registerXformsRevalidateListener(ref, () => {
|
|
125
|
+
setValueIfPreloadDefined(context, setValue, preload);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const preloadValue = (context: ValueContext, setValue: SimpleAtomicStateSetter<string>): void => {
|
|
114
130
|
const { preload } = context.definition.bind;
|
|
131
|
+
if (!preload) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
115
134
|
|
|
116
|
-
if (preload
|
|
135
|
+
if (preload.event === XFORM_EVENT.xformsRevalidate) {
|
|
136
|
+
postloadValue(context, setValue, preload);
|
|
117
137
|
return;
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
if (isLoading(context)) {
|
|
141
|
+
setValueIfPreloadDefined(context, setValue, preload);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const referencesCurrentNode = (context: ValueContext, ref: string): boolean => {
|
|
146
|
+
const nodes = context.evaluator.evaluateNodes(ref, {
|
|
121
147
|
contextNode: context.contextNode,
|
|
122
148
|
});
|
|
149
|
+
if (nodes.length > 1) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
'You are trying to target a repeated field. Currently you may only target a field in a specific repeat instance. XPath nodeset has more than one node.'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return nodes.includes(context.contextNode);
|
|
155
|
+
};
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
157
|
+
// Replaces the unbound repeat references in source and ref, with references
|
|
158
|
+
// bound to the repeat instace of the context.
|
|
159
|
+
const bindToRepeatInstance = (
|
|
160
|
+
context: ValueContext,
|
|
161
|
+
action: ActionDefinition
|
|
162
|
+
): { source: string | undefined; ref: string } => {
|
|
163
|
+
let source = action.source;
|
|
164
|
+
let ref = action.ref;
|
|
165
|
+
if (source) {
|
|
166
|
+
const contextRef = context.contextReference();
|
|
167
|
+
for (const part of contextRef.matchAll(REPEAT_INDEX_REGEX)) {
|
|
168
|
+
const unbound = part[1] + '/';
|
|
169
|
+
if (source.includes(unbound)) {
|
|
170
|
+
const bound = part[0] + '/';
|
|
171
|
+
source = source.replace(unbound, bound);
|
|
172
|
+
ref = ref.replace(unbound, bound);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { source, ref };
|
|
127
177
|
};
|
|
128
178
|
|
|
129
179
|
/**
|
|
@@ -131,30 +181,78 @@ const setPreloadUIDValue = (
|
|
|
131
181
|
* computations to the provided value setter, on initialization and any
|
|
132
182
|
* subsequent reactive update.
|
|
133
183
|
*
|
|
134
|
-
* @see {@link
|
|
184
|
+
* @see {@link preloadValue} for important details about spec ordering of
|
|
135
185
|
* events and computations.
|
|
136
186
|
*/
|
|
137
187
|
const createCalculation = (
|
|
138
|
-
context:
|
|
188
|
+
context: ValueContext,
|
|
139
189
|
setRelevantValue: SimpleAtomicStateSetter<string>,
|
|
140
|
-
|
|
190
|
+
computation: ActionComputationExpression<'string'> | BindComputationExpression<'calculate'>
|
|
141
191
|
): void => {
|
|
142
|
-
context
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
192
|
+
const calculate = createComputedExpression(context, computation);
|
|
193
|
+
createComputed(() => {
|
|
194
|
+
if (context.isAttached() && context.isRelevant()) {
|
|
195
|
+
const calculated = calculate();
|
|
196
|
+
const value = context.decodeInstanceValue(calculated);
|
|
197
|
+
setRelevantValue(value);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
};
|
|
151
201
|
|
|
152
|
-
|
|
202
|
+
const createValueChangedCalculation = (
|
|
203
|
+
context: ValueContext,
|
|
204
|
+
setRelevantValue: SimpleAtomicStateSetter<string>,
|
|
205
|
+
action: ActionDefinition
|
|
206
|
+
): void => {
|
|
207
|
+
const { source, ref } = bindToRepeatInstance(context, action);
|
|
208
|
+
if (!source) {
|
|
209
|
+
// no element to listen to
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let previous = '';
|
|
213
|
+
const sourceElementExpression = new ActionComputationExpression('string', source);
|
|
214
|
+
const calculateValueSource = createComputedExpression(context, sourceElementExpression); // registers listener
|
|
215
|
+
createComputed(() => {
|
|
216
|
+
if (context.isAttached() && context.isRelevant()) {
|
|
217
|
+
const valueSource = calculateValueSource();
|
|
218
|
+
if (previous !== valueSource) {
|
|
219
|
+
// only update if value has changed
|
|
220
|
+
if (referencesCurrentNode(context, ref)) {
|
|
221
|
+
const calc = context.evaluator.evaluateString(action.computation.expression, context);
|
|
222
|
+
const value = context.decodeInstanceValue(calc);
|
|
223
|
+
setRelevantValue(value);
|
|
224
|
+
}
|
|
153
225
|
}
|
|
154
|
-
|
|
226
|
+
previous = valueSource;
|
|
227
|
+
}
|
|
155
228
|
});
|
|
156
229
|
};
|
|
157
230
|
|
|
231
|
+
const registerAction = (
|
|
232
|
+
context: ValueContext,
|
|
233
|
+
setValue: SimpleAtomicStateSetter<string>,
|
|
234
|
+
action: ActionDefinition
|
|
235
|
+
) => {
|
|
236
|
+
if (action.events.includes(XFORM_EVENT.odkInstanceFirstLoad)) {
|
|
237
|
+
if (isInstanceFirstLoad(context)) {
|
|
238
|
+
createCalculation(context, setValue, action.computation);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (action.events.includes(XFORM_EVENT.odkInstanceLoad)) {
|
|
242
|
+
if (!isAddingRepeatChild(context)) {
|
|
243
|
+
createCalculation(context, setValue, action.computation);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (action.events.includes(XFORM_EVENT.odkNewRepeat)) {
|
|
247
|
+
if (isAddingRepeatChild(context)) {
|
|
248
|
+
createCalculation(context, setValue, action.computation);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (action.events.includes(XFORM_EVENT.xformsValueChanged)) {
|
|
252
|
+
createValueChangedCalculation(context, setValue, action);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
158
256
|
export type InstanceValueState = SimpleAtomicState<string>;
|
|
159
257
|
|
|
160
258
|
/**
|
|
@@ -168,25 +266,26 @@ export type InstanceValueState = SimpleAtomicState<string>;
|
|
|
168
266
|
* nodes defined with one
|
|
169
267
|
* - prevents downstream writes to nodes in a readonly state
|
|
170
268
|
*/
|
|
171
|
-
export const createInstanceValueState = (context:
|
|
269
|
+
export const createInstanceValueState = (context: ValueContext): InstanceValueState => {
|
|
172
270
|
return context.scope.runTask(() => {
|
|
173
271
|
const initialValue = getInitialValue(context);
|
|
174
272
|
const baseValueState = createSignal(initialValue);
|
|
175
273
|
const relevantValueState = createRelevantValueState(context, baseValueState);
|
|
176
274
|
|
|
177
|
-
|
|
178
|
-
* @see {@link setPreloadUIDValue} for important details about spec ordering of events and computations.
|
|
179
|
-
*/
|
|
180
|
-
setPreloadUIDValue(context, relevantValueState);
|
|
275
|
+
const [, setValue] = relevantValueState;
|
|
181
276
|
|
|
182
|
-
|
|
277
|
+
preloadValue(context, setValue);
|
|
183
278
|
|
|
279
|
+
const { calculate } = context.definition.bind;
|
|
184
280
|
if (calculate != null) {
|
|
185
|
-
const [, setValue] = relevantValueState;
|
|
186
|
-
|
|
187
281
|
createCalculation(context, setValue, calculate);
|
|
188
282
|
}
|
|
189
283
|
|
|
284
|
+
const action = context.definition.model.actions.get(context.contextReference());
|
|
285
|
+
if (action) {
|
|
286
|
+
registerAction(context, setValue, action);
|
|
287
|
+
}
|
|
288
|
+
|
|
190
289
|
return guardDownstreamReadonlyWrites(context, relevantValueState);
|
|
191
290
|
});
|
|
192
291
|
};
|
|
@@ -13,10 +13,10 @@ import { isComputedPropertySpec, isMutablePropertySpec } from './createSpecified
|
|
|
13
13
|
// prettier-ignore
|
|
14
14
|
type MutableKeyOf<Spec extends StateSpec> = {
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
-
[K in
|
|
16
|
+
[K in Extract<keyof Spec, string>]: Spec[K] extends MutablePropertySpec<any>
|
|
17
17
|
? K
|
|
18
18
|
: never;
|
|
19
|
-
}[
|
|
19
|
+
}[Extract<keyof Spec, string>];
|
|
20
20
|
|
|
21
21
|
type SetEnginePropertyState<Spec extends StateSpec> = <K extends MutableKeyOf<Spec>>(
|
|
22
22
|
key: K,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { GeneralChildNode } from '../client/hierarchy.ts';
|
|
2
|
+
import type { Attribute } from '../instance/Attribute.ts';
|
|
1
3
|
import type { NamespaceDeclarationMap } from './names/NamespaceDeclarationMap.ts';
|
|
2
4
|
import type { QualifiedName } from './names/QualifiedName.ts';
|
|
3
5
|
|
|
@@ -78,15 +80,6 @@ export const escapeXMLText = <Text extends string>(
|
|
|
78
80
|
: (out as EscapedXMLText);
|
|
79
81
|
};
|
|
80
82
|
|
|
81
|
-
interface SerializableElementAttribute {
|
|
82
|
-
serializeAttributeXML(): string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface ElementXMLSerializationOptions {
|
|
86
|
-
readonly namespaceDeclarations?: NamespaceDeclarationMap;
|
|
87
|
-
readonly attributes?: readonly SerializableElementAttribute[];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
83
|
const serializeElementNamespaceDeclarationXML = (
|
|
91
84
|
namespaceDeclarations?: NamespaceDeclarationMap
|
|
92
85
|
): string => {
|
|
@@ -103,24 +96,11 @@ const serializeElementNamespaceDeclarationXML = (
|
|
|
103
96
|
.join('');
|
|
104
97
|
};
|
|
105
98
|
|
|
106
|
-
const serializeElementAttributeXML = (
|
|
107
|
-
attributes?: readonly SerializableElementAttribute[]
|
|
108
|
-
): string => {
|
|
109
|
-
if (attributes == null) {
|
|
110
|
-
return '';
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return attributes
|
|
114
|
-
.map((attribute) => {
|
|
115
|
-
return attribute.serializeAttributeXML();
|
|
116
|
-
})
|
|
117
|
-
.join('');
|
|
118
|
-
};
|
|
119
|
-
|
|
120
99
|
const serializeElementXML = (
|
|
121
100
|
qualifiedName: QualifiedName,
|
|
122
101
|
children: string,
|
|
123
|
-
|
|
102
|
+
attributes: string,
|
|
103
|
+
namespaceDeclarations?: NamespaceDeclarationMap
|
|
124
104
|
): string => {
|
|
125
105
|
// See JSDoc for the `getPrefixedName` method. If we find we do actually need
|
|
126
106
|
// custom element (subtree) prefix resolution, we'd uncomment the argument
|
|
@@ -133,11 +113,9 @@ const serializeElementXML = (
|
|
|
133
113
|
const nodeName = qualifiedName.getPrefixedName(
|
|
134
114
|
// options.namespaceDeclarations
|
|
135
115
|
);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const attributes = serializeElementAttributeXML(options.attributes);
|
|
140
|
-
const prefix = `<${nodeName}${namespaceDeclarations}${attributes}`;
|
|
116
|
+
const serializedNamespaceDeclarations =
|
|
117
|
+
serializeElementNamespaceDeclarationXML(namespaceDeclarations);
|
|
118
|
+
const prefix = `<${nodeName}${serializedNamespaceDeclarations}${attributes}`;
|
|
141
119
|
|
|
142
120
|
if (children === '') {
|
|
143
121
|
return `${prefix}/>`;
|
|
@@ -146,18 +124,44 @@ const serializeElementXML = (
|
|
|
146
124
|
return `${prefix}>${children}</${nodeName}>`;
|
|
147
125
|
};
|
|
148
126
|
|
|
127
|
+
export const serializeAttributeXML = (
|
|
128
|
+
qualifiedName: QualifiedName,
|
|
129
|
+
xmlValue: EscapedXMLText
|
|
130
|
+
): string => {
|
|
131
|
+
const nodeName = qualifiedName.getPrefixedName();
|
|
132
|
+
return ` ${nodeName}="${xmlValue.normalize()}"`;
|
|
133
|
+
};
|
|
134
|
+
|
|
149
135
|
export const serializeParentElementXML = (
|
|
150
136
|
qualifiedName: QualifiedName,
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
children: readonly GeneralChildNode[],
|
|
138
|
+
attributes: readonly Attribute[],
|
|
139
|
+
namespaceDeclarations?: NamespaceDeclarationMap
|
|
153
140
|
): string => {
|
|
154
|
-
|
|
141
|
+
const serializedChildren = children.map((child) => child.instanceState.instanceXML).join('');
|
|
142
|
+
const serializedAttributes = attributes
|
|
143
|
+
.map((attribute) => attribute.instanceState.instanceXML)
|
|
144
|
+
.join('');
|
|
145
|
+
return serializeElementXML(
|
|
146
|
+
qualifiedName,
|
|
147
|
+
serializedChildren,
|
|
148
|
+
serializedAttributes,
|
|
149
|
+
namespaceDeclarations
|
|
150
|
+
);
|
|
155
151
|
};
|
|
156
152
|
|
|
157
153
|
export const serializeLeafElementXML = (
|
|
158
154
|
qualifiedName: QualifiedName,
|
|
159
155
|
xmlValue: EscapedXMLText,
|
|
160
|
-
|
|
156
|
+
attributes: readonly Attribute[],
|
|
157
|
+
namespaceDeclarations?: NamespaceDeclarationMap
|
|
161
158
|
): string => {
|
|
162
|
-
|
|
159
|
+
const serializedAttributes =
|
|
160
|
+
attributes?.map((attribute) => attribute.instanceState.instanceXML).join('') ?? '';
|
|
161
|
+
return serializeElementXML(
|
|
162
|
+
qualifiedName,
|
|
163
|
+
xmlValue.normalize(),
|
|
164
|
+
serializedAttributes,
|
|
165
|
+
namespaceDeclarations
|
|
166
|
+
);
|
|
163
167
|
};
|
package/src/parse/XFormDOM.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { DefaultEvaluator } from '@getodk/xpath';
|
|
12
12
|
|
|
13
13
|
interface DOMBindElement extends KnownAttributeLocalNamedElement<'bind', 'nodeset'> {}
|
|
14
|
+
interface DOMSetValueElement extends KnownAttributeLocalNamedElement<'setvalue', 'event'> {}
|
|
14
15
|
|
|
15
16
|
const getMetaElement = (primaryInstanceRoot: Element): Element | null => {
|
|
16
17
|
for (const child of primaryInstanceRoot.children) {
|
|
@@ -336,6 +337,7 @@ export class XFormDOM {
|
|
|
336
337
|
|
|
337
338
|
readonly model: Element;
|
|
338
339
|
readonly binds: readonly DOMBindElement[];
|
|
340
|
+
readonly setValues: readonly DOMSetValueElement[];
|
|
339
341
|
readonly primaryInstance: Element;
|
|
340
342
|
readonly primaryInstanceRoot: Element;
|
|
341
343
|
|
|
@@ -368,6 +370,12 @@ export class XFormDOM {
|
|
|
368
370
|
contextNode: model,
|
|
369
371
|
}
|
|
370
372
|
);
|
|
373
|
+
const setValues: readonly DOMSetValueElement[] = evaluator.evaluateNodes<DOMSetValueElement>(
|
|
374
|
+
'./xf:setvalue[@event]',
|
|
375
|
+
{
|
|
376
|
+
contextNode: model,
|
|
377
|
+
}
|
|
378
|
+
);
|
|
371
379
|
|
|
372
380
|
const instances = evaluator.evaluateNodes<DOMInstanceElement>('./xf:instance', {
|
|
373
381
|
contextNode: model,
|
|
@@ -417,6 +425,7 @@ export class XFormDOM {
|
|
|
417
425
|
this.title = title;
|
|
418
426
|
this.model = model;
|
|
419
427
|
this.binds = binds;
|
|
428
|
+
this.setValues = setValues;
|
|
420
429
|
this.primaryInstance = primaryInstance;
|
|
421
430
|
this.primaryInstanceRoot = primaryInstanceRoot;
|
|
422
431
|
this.itextTranslationElements = itextTranslationElements;
|
|
@@ -39,8 +39,8 @@ export class GroupElementDefinition extends BodyElementDefinition<'group'> {
|
|
|
39
39
|
return childName !== 'label';
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
this.children = this.body.getChildElementDefinitions(form, this, element, childElements);
|
|
43
42
|
this.reference = parseNodesetReference(parent, element, 'ref');
|
|
43
|
+
this.children = this.body.getChildElementDefinitions(form, this, element, childElements);
|
|
44
44
|
this.appearances = structureElementAppearanceParser.parseFrom(element, 'appearance');
|
|
45
45
|
this.label = LabelDefinition.forGroup(form, this);
|
|
46
46
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { parseToFloat, parseToInteger } from '../../../lib/number-parsers.ts';
|
|
1
2
|
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
2
3
|
import type { InputAppearanceDefinition } from '../appearance/inputAppearanceParser.ts';
|
|
3
4
|
import { inputAppearanceParser } from '../appearance/inputAppearanceParser.ts';
|
|
4
5
|
import type { BodyElementParentContext } from '../BodyDefinition.ts';
|
|
5
|
-
import { parseToFloat, parseToInteger } from '../../../lib/number-parsers.ts';
|
|
6
6
|
import { ControlDefinition } from './ControlDefinition.ts';
|
|
7
7
|
|
|
8
8
|
export class InputControlDefinition extends ControlDefinition<'input'> {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DependentExpression,
|
|
3
|
+
type DependentExpressionResultType,
|
|
4
|
+
} from './abstract/DependentExpression.ts';
|
|
5
|
+
|
|
6
|
+
export class ActionComputationExpression<
|
|
7
|
+
Type extends DependentExpressionResultType,
|
|
8
|
+
> extends DependentExpression<Type> {
|
|
9
|
+
constructor(resultType: Type, expression: string) {
|
|
10
|
+
super(resultType, expression);
|
|
11
|
+
}
|
|
12
|
+
}
|