v1.5.0 — dynamic choice filtering: the @UVCHOICES tag (choices mode)
A fifth rule mode. REDCap's @HIDECHOICE hides options statically;
@UVCHOICES shows/hides individual options of a radio, dropdown or
checkbox field while a REDCap-style condition holds — cascading
country → region → site lists in one field instead of a near-duplicate field
per country.
- Grammar. JSON form only, exactly one of
show(whitelist — the
complement of the field's own choice list hides) orhide(blacklist) per
tag, plus optionalwhen,message,blockSave. Repeated tags with
differentwhenconditions branch through the existingBranching
machinery (one tag per country, at most one unconditional fallback); no
active branch means no filter. Codes are validated against the field's
select_choices_or_calculationsat rule-build time; unknown codes,
non-choice field types, and matrix membership are per-field config errors. - A hidden selection is never cleared. A currently-selected choice that
becomes hidden stays visible (dropdowns keep it in place, disabled), the
field is flagged invalid with the message, andblockSave(off/confirm/
hard) runs through the shared save guard. Values outside the field's choice
list (missing-data codes) are out of scope on both runtimes. - Plumbing. Rules carry
choicesAll(the full code list, attached from
the data dictionary) so the client computes ashowwhitelist's complement
without DOM enumeration — checkbox options are only findable by exact
__chk__<field>_RC_<code>name.choicesAllparticipates in the
groupMulticanonical key, so identically-tagged fields with different
choice lists never merge into one rule.projectFieldChoices()now
enumerates radio and dropdown rows too (previously checkbox-only;
Logic::checkRefsis unaffected — it only consults checkbox entries). - Client. New
QRIDChoiceFilterInitfactory (same variant/gate/boot
skeleton as required mode, own guard item, composes with the other modes).
Dropdown filtering physically removes and re-inserts<option>s in
original order — Safari ignoreshidden/display:noneon options; radio
and checkbox options hide their wrapper element. Live re-evaluation rides
the shared when-registry. - Audit + scan.
ruleFindingsgains achoicesblock: a saved value
(or checked checkbox code — the one mode that judges checkbox arrays) that
the active filter hides logstype: choices,reason: hidden-choice; the
Validation scan reports the same verdicts unchanged. - Tests.
tests/choices_php.php(37 checks: grammar, errors, grouping,
branching),tests/choices_dom_js.cjs(44 checks: remove/restore order,
stale-kept semantics, conflict, survey muting, blocking), and
tests/choices_fixture.json— the hidden-set contract consumed by BOTH
runtimes (hook_php.phpdrives every fixture case through the real audit;
the DOM test through the real factory).tests/hook_php.php210→246.
Full existing suite green (no regressions).
Universal Field Validator is a REDCap external module. Rule kinds: @UVALIDATE (check-character / regex), @UVASSERT (cross-field constraints), @UVREQUIRED (conditional required), @UVUNIQUE (no duplicates across records), @UVCHOICES (dynamic choice filtering).
Docs: README · User guide · Action-tag examples · Install