Build an Interface Wizard

Interface patterns give you an opportunity to explore different interface designs. Be sure to check out How to Adapt a Pattern for Your Application.

Goal

Divide a big form into sections presented one step at a time with validation.

This design pattern is not recommended for offline interfaces because reflecting immediate changes in an interface based on user interaction requires a connection to the server.

Wizards should be used to break up a large form into smaller steps rather than activity-chaining. Users can step back and forth within a wizard without losing data in between steps.

All the fields must be valid before the user is allowed to move to the next steps. However, the user is allowed to move to a previous step even if the fields in the current step aren't valid. The last step in the wizard is a confirmation screen.

This scenario demonstrates:

  • How to create a wizard using the showWhen parameter of an interface component
  • How to conditionally set readOnly and required on an interface component
  • How to show and change the style of buttons

Expression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
a!localVariables(
  local!employee:{ firstName:null, lastName:null, department:null, title:null, phoneNumber:null, startDate:null },
  local!currentStep: 1,
  a!formLayout(
    label: "Example: Onboarding Wizard",
    contents:{
      a!columnsLayout(
        columns:{
          a!columnLayout(
            contents:{
              a!textField(
                label: "First Name",
                labelPosition: if( local!currentStep = 3, "ADJACENT","ABOVE"),
                value: local!employee.firstName,
                saveInto: local!employee.firstName,
                readOnly: local!currentStep = 3,
                required: not( local!currentStep = 3),
                showWhen: or( local!currentStep = {1,3} )
              ),
              a!textField(
                label: "Last Name",
                labelPosition: if( local!currentStep = 3, "ADJACENT","ABOVE"),
                value: local!employee.lastName,
                saveInto: local!employee.lastName,
                readOnly: local!currentStep = 3,
                required: not( local!currentStep = 3),
                showWhen: or( local!currentStep = {1,3} )
              ),
              a!textField(
                label: "Phone Number",
                labelPosition: if( local!currentStep = 3, "ADJACENT","ABOVE"),
                value: local!employee.phoneNumber,
                saveInto: local!employee.phoneNumber,
                readOnly: local!currentStep = 3,
                required: not( local!currentStep = 3),
                showWhen: or( local!currentStep = {2,3} )
              )
            }
          ),
          a!columnLayout(
            contents:{
             a!textField(
              label: "Department",
              labelPosition: if( local!currentStep = 3, "ADJACENT","ABOVE"),
              value: local!employee.department,
              saveInto: local!employee.department,
              readOnly: local!currentStep = 3,
              required: not( local!currentStep = 3),
              showWhen: or( local!currentStep = {1,3} )
            ),
            a!textField(
              label: "Title",
              labelPosition: if( local!currentStep = 3, "ADJACENT","ABOVE"),
              value: local!employee.title,
              saveInto: local!employee.title,
              readOnly: local!currentStep = 3,
              required: not( local!currentStep = 3),
              showWhen: or( local!currentStep = {1,3} )
            ),
            a!dateField(
              label: "Start Date",
              labelPosition: if( local!currentStep = 3, "ADJACENT","ABOVE"),
              value: local!employee.startDate,
              saveInto: local!employee.startDate,
              readOnly: local!currentStep = 3,
              required: not( local!currentStep = 3),
              showWhen: or( local!currentStep = {2,3} )
            )
            }
          )
        }
      )
    },
    buttons: a!buttonLayout(
      primaryButtons: {
        a!buttonWidget(
          label: "Go Back",
          value: local!currentStep - 1,
          saveInto: local!currentStep,
          showWhen: or( local!currentStep = {2,3} )
        ),
        a!buttonWidget(
          label: if( local!currentStep = 1, "Next", "Go to Review"),
          value: local!currentStep + 1,
          saveInto: local!currentStep,
          validate: true,
          showWhen: or( local!currentStep = {1,2} )
        ),
        a!buttonWidget(
          label: "Onboard Employee",
          style: "PRIMARY",
          submit: true,
          showWhen: local!currentStep = 3
        )
      }
    )
  )
)

Test it out

  1. Click Next without entering a value in any of the fields. The user will stay on step 1 until all fields are entered.
  2. Enter values for all the field. Click Next. Notice part two of the form will appear.
  3. Click Go Back. Notice that you can go back to part 1.
  4. Enter values for all required fields in step 2. Click Go to Review. Notice that all values entered will appear as a set of read only data.

Notable implementation details

  • The showWhen parameter is used extensively to conditionally show particular fields as well as set requiredness & read only setting.
  • Each "Next" button is configured to increment local!currentStep. Validate is set to true, which ensures all fields are required before continuing on to the next step.
  • Each "Go Back" button is not configured to enforce validation. This allows the user to go to a previous step even if the current step has null required fields and other invalid fields. This button also decrements local!currentStep by one.
FEEDBACK