Skip to content

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.

dart
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:

dart
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:

MemberMeaning
visibleWhether the field is shown. Hidden fields are skipped by validation.
enabledWhether the field accepts input.
activeValidationIndexesWhich of the field's validations run.
optionRequestA FormOptionRequest(providerId, parameters, clearInvalidSelection) when a RefreshOptions effect applies.
  1. Every field starts visible, enabled unless readOnly, with every validation active except those targeted by an ActivateValidation.
  2. For each rule whose condition matches, SetVisibility and SetEnabled combine with AND: once a matching rule says false, a later true cannot undo it. Write rules that hide, with the condition for hiding.
  3. ActivateValidation turns its index on.
  4. RefreshOptions produces a FormOptionRequest whose parameters are read from the current values. The host fetches the options.
dart
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 scope null and fields inside a repeating group that group's id. A formula reads only fields in its own scope, so line_cost in the example computes once per entry from that entry's qty and unit_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 FormatException naming the field, the problem and a remedy. formulaDependenciesOf drives cycle detection.
  • Results. FormFormulaEngine.evaluate(definition, values) returns the values with every formula result filled in, or null where an input is missing. FormView shows results but never writes them back through onChanged.
dart
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.
  • FormCondition and FormEffect are sealed; an app cannot add its own kinds.
  • Rules are written in code or JSON; the editor has no rules panel yet.
  • FormView does not act on RefreshOptions requests 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.