Back to Work

Declarative Form Logic: Taming Complex Conditional Forms

Designing a custom attribute-based system for government forms with heavy conditional logic—single source of truth across front-end and back-end.

The Problem

The Arizona Department of Education needed web forms for organizations to get reimbursed through federal USDA food programs — meals for impoverished children and adults.

These weren't simple forms. They had heavy conditional logic: fields appearing or hiding based on other answers, different input types (radio buttons, checkboxes, multiline text), validation rules that changed depending on context.

The standard approach — imperative show/hide callbacks scattered across jQuery handlers — would have been spaghetti. Every new conditional rule meant tracing callback chains and hoping you didn't break something else.

My Role

I designed this system entirely. The department had standardized on ASP.NET / jQuery / SQL Server, and I created an approach that worked within those constraints.

The Solution: Attribute-Based Form Definition

Instead of writing imperative logic, I made the data model the source of truth:

Custom C# Attributes

Each model property could be decorated with custom attributes that defined conditional evaluation logic (when to show/hide) and behavior (what input type, validation rules, etc).

Front-End: jQuery Behaviors

Custom ASP.NET components rendered the attributes as data-* attributes. jQuery observed these and applied show/hide animations, input behaviors, and client-side validation automatically.

Back-End: Same Validation

The same attributes drove server-side validation. No duplicate logic — if the attribute said a field was required under condition X, both client and server enforced it.

Why This Worked

  • Single source of truth: Conditional logic lived in one place (the model attributes), not scattered across jQuery handlers
  • Declarative over imperative: New fields meant adding attributes, not tracing callback chains
  • Consistent validation: Front-end and back-end always agreed because they read the same definitions
  • Predictable debugging: When something broke, it was either a mis-attributed property or a failed attribute implementation — known categories, not mystery callbacks

Tradeoffs

Initial Learning Curve

The pattern wasn't immediately obvious to developers who hadn't seen it. Understanding how attributes flowed into jQuery behaviors required ramping up on the system.

Debugging Complexity (Initially)

Early on, debugging required understanding the full stack. But once the mental model clicked, debugging became more predictable than scattered callbacks would have been.

Outcomes

The system was used across multiple forms for several USDA programs. Complex conditional logic was manageable. New developers could add fields by following the attribute patterns rather than reverse-engineering callback chains.

What I'd Do Differently

Better onboarding documentation. The pattern was powerful but not self-documenting — new developers needed explicit walkthroughs. I'd invest more upfront in explaining the "why" alongside the "how."