Rules and formulas
A FormRule connects fields: when its condition matches the current values, its effects apply to target fields. Rules belong to the form (FormDefinition.rules), not to any field.
FormRule({
required String id,
required FormCondition when,
required Iterable<FormEffect> effects,
String? scopeGroupId,
})Conditions and effects
Conditions (sealed FormCondition) | Meaning |
|---|---|
FieldEquals(fieldKey, value) | The field's value equals value. |
FieldIsEmpty(fieldKey) | The field has no value. |
AllConditions([...]) | Every condition matches. |
AnyCondition([...]) | At least one condition matches. |
NotCondition(condition) | The condition does not match. |
Effects (sealed FormEffect) | Meaning |
|---|---|
SetVisibility(target, visible) | Shows or hides the target field. |
SetEnabled(target, enabled) | Enables or disables the target field. |
ActivateValidation(target, validationIndex) | Turns on a validation already declared on the target field. |
RefreshOptions(target, providerId:, parameters:, clearInvalidSelection: true) | Asks a host provider for new options, passing current source values as named parameters. |
The deviation intake example shows the CAPA owner, and makes it required, only for major or critical deviations:
const _majorOrWorse = [
FieldEquals('severity', 'major'),
FieldEquals('severity', 'critical'),
];
final rules = [
FormRule(
id: 'capa_hidden_for_minor',
when: NotCondition(AnyCondition(_majorOrWorse)),
effects: const [SetVisibility('capa_owner', false)],
),
FormRule(
id: 'capa_required_for_major',
when: AnyCondition(_majorOrWorse),
effects: const [ActivateValidation('capa_owner', 0)],
),
];How rules are evaluated
FormRuleEngine.evaluate(form, values) returns an EffectiveFieldState for every field key:
| Member | Meaning |
|---|---|
visible | Whether the field is shown. Hidden fields are skipped by validation. |
enabled | Whether the field accepts input. |
activeValidationIndexes | Which of the field's validations run. |
optionRequest | A FormOptionRequest(providerId, parameters, clearInvalidSelection) when a RefreshOptions effect applies. |
- Every field starts visible, enabled unless
readOnly, with every validation active except those targeted by anActivateValidation. - For each rule whose condition matches,
SetVisibilityandSetEnabledcombine with AND: once a matching rule saysfalse, a latertruecannot undo it. Write rules that hide, with the condition for hiding. ActivateValidationturns its index on.RefreshOptionsproduces aFormOptionRequestwhose parameters are read from the current values. The host fetches the options.
final values = <String, Object?>{'severity': 'major'};
final states = FormRuleEngine.evaluate(deviationIntake, values);
final capa = states['capa_owner']!;
assert(capa.visible);
assert(capa.activeValidationIndexes.contains(0)); // required is now active
final issues = FormValidationEngine.validate(deviationIntake, values, fieldStates: states);FormView runs this on every build and applies the states to its controls.
scopeGroupId
scopeGroupId is a hint for the editor: it may show the rule on that group. The constructor checks that the group exists and every effect targets a field inside it. Evaluation ignores it. Renaming the group renames the scope; deleting a node drops the rules that reference it.
Formulas and scopes
A FormulaFormField declares only expression and outputType. Its inputs are found by compiling the expression, where $key refers to a field, against the fields in its scope. There is no dependency list to keep in sync.
- Scopes.
formulaScopesOf(root)gives form-level fields the scopenulland fields inside a repeating group that group's id. A formula reads only fields in its own scope, soline_costin the example computes once per entry from that entry'sqtyandunit_cost. - Readable types. Formulas read numbers, money and quantity amounts, durations, ratings, dates and times, toggles as booleans, and other formulas. Text, choice, reference, ranges, attachments and custom fields are not readable.
- Compile errors. A formula that does not compile is a design error: the constructor throws a
FormatExceptionnaming the field, the problem and a remedy.formulaDependenciesOfdrives cycle detection. - Results.
FormFormulaEngine.evaluate(definition, values)returns the values with every formula result filled in, ornullwhere an input is missing.FormViewshows results but never writes them back throughonChanged.
final inputs = formulaInputsOf(deviationIntake, 'line_cost');
// {qty: FormulaType.number, unit_cost: FormulaType.number}Current limits
- Conditions test equality and emptiness only: no comparisons, "contains", date or formula conditions.
- Effects target fields, not groups, so hiding a section takes one effect per field.
- Rules evaluate against the form-level values, not per repeating-group entry.
FormConditionandFormEffectare sealed; an app cannot add its own kinds.- Rules are written in code or JSON; the editor has no rules panel yet.
FormViewdoes not act onRefreshOptionsrequests yet.
Extensible rules, with comparison and formula conditions, group targets, set-required and set-value effects, per-entry evaluation and a Rules panel, are planned. See the Roadmap.