I have a user interface with numerous controls and data inputs on it. Buttons, text fields, and checkboxes, etc. Imagine the following "rules" exist for a sample UI with 2 of those controls:

B2 cannot be used until TF1 and TF2 both have values

TF2 cannot be entered until TF1 has a value

CB2 cannot be checked unless TF1 has a value

CB2 cannot be checked unless CB1 is checked

These rules are arbitrary here, but I have a variety of business rules which will ultimately govern the UI functionality.

I would like to implement logic for this in my UI code to enforce these rules. However, I am having a hard time determining a good way to do so. I can think of two primary ways but I'm not really a fan of either.

Method 1 - adding logic to each control callback

One way to do this would be to add the logical check(s) to each method callback. So for the first rule, in the code associated with B2, include hard coded checks like if TF1 == "" and if TF2 == "". I am not a fan of this idea because I'm going to scatter any sequencing/logic everywhere in my UI and it seems inevitable sometime this process will result in obscure and impossible to track down bugs. Also seems like maintenance will be impossibly complex.

Method 2 - build collection of all rules

The way I'm leaning towards is building a collection of rules. The first step would be defining a set of rules for "passing" for each type of control and its associated lookups, such as:

For checkboxes, "passes" check for true, "fails" if false

For textboxes, "passes" if not empty, "fails" if = ""

I can then create a collection of all the prerequisite pass/fail checks for each UI component and its associated components. This can be abstracted into a single check method called in each UI action like:

myChecker(mControlName)

where myChecker is a method which iterates through the list of all matching rules, checks them, and returns true/false based on whether the rules pass. This method could be called from each UI control before any control-specific logic takes place.

This is nice, but it seems creating the rules and not forgetting any might be even more complex than the first method. Additionally, all control types have to have a "pass/fail" criteria or the myChecker method might become ridiculously complex with lots of hard to read if statements.

Am I missing something? There has to be a more clearly defined pattern or some better strategy for incorporating these sorts of validation/rules into a UI.

3 Answers
3

I think you are on the right track with method 2 (though in some cases it might be overkill, I don't think it is even close to overkill in your case).

There are some patterns at least somewhat relevant - MVC, MVVM, MVP would help you move the logic for "passing" from the UI controls. Instead of business logic happening in the control, some other code could run the business logic, update a model, then data bind from the model to the controls.

You can do a more manual data bind if you are running on older framework like VB6, or, god forbid, Microsoft Access.

This may seem like extra work to do the same thing, but it lets you write unit tests of the business logic. It may help some with concurrency issues, since updating the model doesn't immediately start firing UI events.

There are also ways to make the business logic more maintainable than a forest of if statements. Usually at that point you turn the rules that govern the if/then statements into whatever data structure can capture it. Then you run through the data structure and apply the rules. Often this is easier to follow than code, but since the business rules could potentially be anything there is no real general case to go by.

Method 3 - adding custom logic to each control callback and a collection of (shared) rules:

Trying to create a single check method that works for all your UI controls may prove very difficult. ...and worse, it may have to contain so many options and exceptions that it will just be too unwieldy and hard-to-maintain. It's generally a bad idea to make a kitchen sink method.

Adding logic to each control callback isn't that bad. If there are many controls with the same/similar logic you should factor that part out, and they can all call the same validation check. Bugs should actually be easier to track down, since there isn't some monstrous method that handles everything to look through each time. The bugs will be more local to each control.