SAIL Tutorial

The walk-through on this page will help you create your first SAIL interface using the interface designer. We’ll start with a basic example, then add more components to it as we go. The interface designer allows you to create and modify your interface and gives you the ability to immediately see and test it.

After you have gone through this tutorial, you will be ready to go through the other tutorials that show how to create a SAIL interface for records and process models. See also: Records Tutorial and Process Model Tutorial

To understand the SAIL design, concepts, and functionality available, see SAIL Design.

For a full list of available SAIL components, and for examples of how to achieve various dynamic form behaviors, see SAIL Components and SAIL Recipes.

Use the data provided to understand how the configurations work. Then, try it with your own data. Keep in mind, the final configurations will need to change if your data has different field names.

Create the Appian Tutorial Application

The Appian Tutorial application is used to contain the design objects created while working through this tutorial.

The tutorial application only needs to be created once. If you have already created the tutorial application, skip the steps below.

To create the Appian Tutorial application

  1. Log in to the Appian Designer environment (for example, myappiansite.com/suite/design).
  2. Click New Application.
  3. In the Name field, type Appian Tutorial.
  4. Optionally, in the Description field, add a short description.
  5. Click Create.

The application contents view displays. Right now the application is empty. Each design object that you create during the course of this tutorial will appear in this list and be associated with the tutorial application.

Create an Interface

To start, we will create an interface named expenseReportForm, and store it in the Examples folder:

  1. Navigate to the application contents view of the Appian Tutorial application (if needed).
  2. Click New, and then click Interface.
  3. In the Create Interface dialog, complete the following fields:
    • Leave Create from Scratch selected
    • For Name, type expenseReportForm
    • For Save In, use the picker to select the Examples folder
  4. Click Create & Edit.

The newly created interface opens in the interface designer. By default, the interface designer opens in a new tab. If you don't see a new tab, check your browser to see if you have pop-ups enabled.

Create Interface Inputs

Each form input saves its value into a variable, so let's add the variables. Create the following interface inputs using the plus (+) button in the top right corner of the Interface Inputs pane:

  • expenseItem (Text)
  • expenseDate (Date)
  • expenseAmount (Number (Decimal))
  • cancel (Boolean)

Add Components to the Interface

Now let's add components to interface. We'll start by choosing a form layout, then adding a few components to it:

  1. In the design view in the left-hand pane, click the Basic Form template. The design view will display the configuration for the form layout.
  2. In the design view, update the label of the form to be "Submit Expense Report".
  3. Then, remove the "Lorem Ipsum" text from the Instructions field of the form. You should see the following:
  4. Click on List of Components under Contents.
  5. Delete the pre-populated Section and Columns Layouts.
  6. Click Add Component. In the basic inputs section, click Text to select the text component.
  7. Follow the same steps to add a date and a decimal component.

You should now have the following interface:

For more details on how to use the features of the interface designer, see also: Interface Designer

Configure Each Component

Now that we have all the components, let's configure them.

  1. Click on the text component in the live view or the Label (Text) link in the design view to view its configuration.
  2. Change the label to "Expense Item" by entering the new value in the Label text box in the design view.
  3. Using the inputs defined in the Create Interface Inputs section, define the variable that the user's input should be saved into.
    • In the picker, search for the input called expenseItem. You can also browse the available inputs by using the pulldown shown below.
  4. Configure the display value to make sure that the user's input is displayed in the field once they interact with it by following the same steps. You should see the following:
  5. Make the component required by checking the Required box.
  6. Follow the same steps to configure the date component. Change the label to "Expense Date", map the Display Value and Save Input To to the expenseDate input, and make it required.
  7. Finally, configure the decimal component. Change the label to "Expense Amount", map the Display Value and Save Input To to the expenseAmount input, and make it required.

You're now done configuring your form. You should see the following:

Save the interface. Keep this window open so you can quickly modify the interface as we continue through the following sections.

Conditionally Show SAIL Components

A common design requirement is to only display a component based on a condition. Because SAIL interfaces are defined using expressions, the full expression library can be used to add dynamic behavior to the interface. For this example, we'll add a paragraph component for entering comments and we will only display it when the expense amount is greater than $100.

  1. In the right-hand pane, add a new interface input called comments (Text).
  2. Go to the Contents configuration by using the breadcrumbs on the selected component. Breadcrumbs appear when you hover over the label in the live view.
  3. Click Add Component and add a paragraph component below the decimal component.
  4. Click on the paragraph to view its configuration.
  5. Change the Label to "Comments", set Display Value and Save Input To to the comments input, and make the field required by checking the Required box, as we did in the previous section.
  6. Change the Visibility to "Only show when…" and click on Edit Condition.
  7. In the Edit Expression dialog, enter the condition: ri!expenseAmount > 100
  8. Click OK to close the expression box.

Let's test the conditional display. Enter 101 in the "Expense Amount" field and click out of the field to see the paragraph component displayed.

Be sure to save your interface before continuing.

Add a Custom Validation

You can also configure custom validations for the SAIL form by adding them directly to the expression. For example, let’s add a custom validation to our form that only allows users to enter up to 100 characters in the Comments field. To do this,

  1. Go to the paragraph configuration.
  2. Click the Edit as Expression icon to the right of the Validations field.
  3. Enter the following expression in the expression box:

    1
    
    if(len(ri!comments)<=100, null, "Your text has exceeded 100 characters")
    
  4. Click OK to close the expression box.

By modifying this, you can create a variety of other validations based on the content added to the "Comments" field. And since the validations parameter is an array, you can add multiple validations.

See also: Validating User Inputs

In the live view, enter more than 100 characters in the "Comments" field and click out of the field; the validation message we configured above will appear.

Add a Reusable Component to Your Interface

The SAIL component picker provides all of the basic components that are available, but you may also want to select an interface that you've previously created.

For this example, let's take our conditionally displayed comments field and save it as a separate interface.

  1. Copy the "Comments" field from the expression using the edit icon in the header of the paragraph configuration, as shown below.
  2. From the application contents view of the Appian Tutorial application, create an interface called enterComments and save it in the Examples folder.
  3. Add inputs to the new interface.
    • showWhen (Boolean)
    • labelPosition (Text)
    • readOnly (Boolean)
    • required (Boolean)
    • commentValue (Text)
    • commentSaveInto (Text Array)
  4. In the design view, click Add Component to open the SAIL component picker.
  5. Click the enter an expression link at the top of the dialog under the title.
    • An expression input will appear, allowing you to enter an expression for the interface instead of selecting a basic component.
  6. Insert the expression into the expression box and then modify with the following:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    a!paragraphField(
      label: "Comments",
      labelPosition: `ri!labelPosition`,
      value: `ri!commentValue`,
      saveInto: `ri!commentSaveInto`,
      refreshAfter: "UNFOCUS",
      showWhen: `ri!showWhen`,
      required: `ri!required`,
      height: "MEDIUM",
    !  readOnly: ri!readOnly,
      validations: if(len(ri!commentSaveInto)<=100, null, "Your text has exceeded 100 characters")
    )
    
  7. Click Insert Expression to save the expression and close the dialog.
  8. Save the interface.

Now let's go back to the original interface and call this interface.

  1. Go to the Contents configuration.
  2. Delete the existing "Comments" paragraph field.
  3. Click Add Component to add a component below the decimal component.
  4. Click the enter an expression link at the top of the dialog under the title and enter the following expression:

    1
    2
    3
    4
    5
    6
    7
    8
    
    rule!enterComments(
      showWhen: ri!expenseAmount > 100,
      labelPosition: "ABOVE",
      readOnly: false,
      required: true,
      commentValue: ri!comments,
      commentSaveInto: ri!comments
    )
    
  5. Click Insert Expression to save the expression and close the dialog.

You should see the following:

Be sure to save your interface before continuing.

Make Your Interface Conditionally Read-Only

SAIL makes it easy to reuse an interface across start and task forms. For example, we could reuse this same form for the approval task once the expense report has been submitted. We'll make the form inputs read-only when we are in the approval step of the process, and we'll display different submit buttons.

In this section, we'll add a new field to track the approval.

  1. Add new inputs to the interface.
    • step (Text)
    • approve (Boolean)
  2. Go to the Contents configuration.
  3. Add a radio button below enterComments.
  4. Click on the radio button to view its configuration.
  5. Change the Label Position to "Hidden", check the Required box, and set the Selected Value and Save Selection To as the approve input.
  6. Click on the List of Text link under Choice Labels, and change the two items in the list to "Approve" and "Reject".
  7. Click on the Radio Buttons link at the top of the design view to navigate back to the main radio button component.
  8. Click the edit icon on the Choice Values parameter and modify the expression with the following: {true, false}

Now let's switch to the expression view to add the dynamic behavior.

  1. Click the Expression tab located at the bottom of the left-hand pane, as shown below. SAIL_Tutorial_Expression_View_Button.png

  2. Add two new local variables. The first, local!readOnly, checks whether the form should be read-only, based on the step. The second, local!labelPosition, controls the label position for better readability in read-only mode. Modify your expression with the following:

    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
    
    +	load(
    +	  local!readOnly: ri!step="APPROVAL",
    +	  local!labelPosition: if(local!readOnly, "ADJACENT", "ABOVE"),
    	  a!formLayout(
    	    label: "Submit Expense Report",
    	    contents: {
    	      a!textField(
    	        label: "Expense Item",
    	        labelPosition: "ABOVE",
    	        value: ri!expenseItem,
    	        saveInto: ri!expenseItem,
    	        refreshAfter: "UNFOCUS",
    	        required: true,
    	        validations: {},
    	        align: "LEFT"
    	      ),
    	      a!dateField(
    	        label: "Expense Date",
    	        labelPosition: "ABOVE",
    	        value: ri!expenseDate,
    	        saveInto: ri!expenseDate,
    	        required: true,
    	        validations: {},
    	        align: "LEFT"
    	      ),
    	      a!floatingPointField(
    	        label: "Expense Amount",
    	        labelPosition: "ABOVE",
    	        value: ri!expenseAmount,
    	        saveInto: ri!expenseAmount,
    	        refreshAfter: "UNFOCUS",
    	        required: true,
    	        validations: {},
    	        align: "LEFT"
    	      ),
    	      rule!enterComments(
    	        showWhen: ri!expenseAmount > 100,
    	        labelPosition: "ABOVE",
    	        readOnly: false,
    	        required: true,
    	        commentValue: ri!comments,
    	        commentSaveInto: ri!comments
    	      ),
    	      a!radioButtonField(
    	        labelPosition: "COLLAPSED",
    	        choiceLabels: {"Approve", "Reject"},
    	        choiceValues: {true, false},
    	        value: ri!approve,
    	        saveInto: ri!approve,
    	        required: true
    	      )
    	    },
    	    buttons: a!buttonLayout(
    	      primaryButtons: {
    	        a!buttonWidgetSubmit(
    	          label: "Submit",
    	          style: "PRIMARY"
    	        )
    	      },
    	      secondarybuttons: {
    	        a!buttonWidgetSubmit(
    	          label: "Cancel",
    	          style: "SECONDARY",
    	          value: true,
    	          saveInto: ri!cancel,
    	          skipvalidation: true
    	        )
    	      }
    	    )
    	  )
    + )
    
  3. Use the new local!readOnly variable to make each component conditionally read-only and optional when it is in read-only mode. Use the new local!labelPosition to control each component's label position (except where the label position is COLLAPSED). Modify your expression with the following:

    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
    
    load(
      local!readOnly: ri!step="APPROVAL",
      local!labelPosition: if(local!readOnly, "ADJACENT", "ABOVE"),
      a!formLayout(
        label: "Submit Expense Report",
        contents: {
          a!textField(
            label: "Expense Item",
            labelPosition: `local!labelPosition`,
            value: ri!expenseItem,
            saveInto: ri!expenseItem,
            refreshAfter: "UNFOCUS",
            required: `not(local!readOnly)`,
    +       readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!dateField(
            label: "Expense Date",
            labelPosition: `local!labelPosition`,
            value: ri!expenseDate,
            saveInto: ri!expenseDate,
            required: `not(local!readOnly)`,
    +       readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!floatingPointField(
            label: "Expense Amount",
            labelPosition: `local!labelPosition`,
            value: ri!expenseAmount,
            saveInto: ri!expenseAmount,
            refreshAfter: "UNFOCUS",
            required: `not(local!readOnly)`,
    +       readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          rule!enterComments(
            showWhen: ri!expenseAmount > 100,
            labelPosition: `local!labelPosition`,
            readOnly: `local!readOnly`,
            required: `not(local!readOnly)`,
            commentValue: ri!comments,
            commentSaveInto: ri!comments
          ),
          a!radioButtonField(
            labelPosition: "COLLAPSED",
            choiceLabels: {"Approve", "Reject"},
            choiceValues: {true, false},
            value: ri!approve,
            saveInto: ri!approve,
            required: true
          )
        },
        buttons: a!buttonLayout(
          primaryButtons: {
            a!buttonWidgetSubmit(
              label: "Submit",
              style: "PRIMARY"
            )
          },
          secondarybuttons: {
            a!buttonWidgetSubmit(
              label: "Cancel",
              style: "SECONDARY",
              value: true,
              saveInto: ri!cancel,
              skipvalidation: true
            )
          }
        )
      )
    )
    
  4. Only display the radio button when on the approval step by modifying your expression with the following:

    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
    
    load(
      local!readOnly: ri!step="APPROVAL",
      local!labelPosition: if(local!readOnly, "ADJACENT", "ABOVE"),
      a!formLayout(
        label: "Submit Expense Report",
        contents: {
          a!textField(
            label: "Expense Item",
            labelPosition: local!labelPosition,
            value: ri!expenseItem,
            saveInto: ri!expenseItem,
            refreshAfter: "UNFOCUS",
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!dateField(
            label: "Expense Date",
            labelPosition: local!labelPosition,
            value: ri!expenseDate,
            saveInto: ri!expenseDate,
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!floatingPointField(
            label: "Expense Amount",
            labelPosition: local!labelPosition,
            value: ri!expenseAmount,
            saveInto: ri!expenseAmount,
            refreshAfter: "UNFOCUS",
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          rule!enterComments(
            showWhen: ri!expenseAmount > 100,
            labelPosition: local!labelPosition,
            readOnly: local!readOnly,
            required: not(local!readOnly),
            commentValue: ri!comments,
            commentSaveInto: ri!comments
          ),
          a!radioButtonField(
            labelPosition: "COLLAPSED",
            choiceLabels: {"Approve", "Reject"},
            choiceValues: {true, false},
            value: ri!approve,
            saveInto: ri!approve,
            required: true`,`
    +       showWhen: local!readOnly
          )
        },
        buttons: a!buttonLayout(
          primaryButtons: {
            a!buttonWidgetSubmit(
              label: "Submit",
              style: "PRIMARY"
            )
          },
          secondarybuttons: {
            a!buttonWidgetSubmit(
              label: "Cancel",
              style: "SECONDARY",
              value: true,
              saveInto: ri!cancel,
              skipvalidation: true
            )
          }
        )
      )
    )
    
  5. Hide the cancel button on the approval step by modifying your expression with the following:

    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
    
    load(
      local!readOnly: ri!step="APPROVAL",
      local!labelPosition: if(local!readOnly, "ADJACENT", "ABOVE"),
      a!formLayout(
        label: "Submit Expense Report",
        contents: {
          a!textField(
            label: "Expense Item",
            labelPosition: local!labelPosition,
            value: ri!expenseItem,
            saveInto: ri!expenseItem,
            refreshAfter: "UNFOCUS",
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!dateField(
            label: "Expense Date",
            labelPosition: local!labelPosition,
            value: ri!expenseDate,
            saveInto: ri!expenseDate,
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!floatingPointField(
            label: "Expense Amount",
            labelPosition: local!labelPosition,
            value: ri!expenseAmount,
            saveInto: ri!expenseAmount,
            refreshAfter: "UNFOCUS",
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          rule!enterComments(
            showWhen: ri!expenseAmount > 100,
            labelPosition: local!labelPosition,
            readOnly: local!readOnly,
            required: not(local!readOnly),
            commentValue: ri!comments,
            commentSaveInto: ri!comments
          ),
          a!radioButtonField(
            labelPosition: "COLLAPSED",
            choiceLabels: {"Approve", "Reject"},
            choiceValues: {true, false},
            value: ri!approve,
            saveInto: ri!approve,
            required: true,
            showWhen: local!readOnly
          )
        },
        buttons: a!buttonLayout(
          primaryButtons: {
            a!buttonWidgetSubmit(
              label: "Submit",
              style: "PRIMARY"
            )
          },
          secondarybuttons: {
            a!buttonWidgetSubmit(
              label: "Cancel",
              style: "SECONDARY",
              value: true,
              saveInto: ri!cancel,
              skipvalidation: true`,`
    +         showWhen: not(local!readOnly)
            )
          }
        )
      )
    )
    
  6. Change the form title based on the step by modifying your expression with the following:

    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
    
    load(
      local!readOnly: ri!step="APPROVAL",
      local!labelPosition: if(local!readOnly, "ADJACENT", "ABOVE"),
      a!formLayout(
        label: `if(local!readOnly, "Approve Expense Report", "Submit Expense Report")`,
        contents: {
          a!textField(
            label: "Expense Item",
            labelPosition: local!labelPosition,
            value: ri!expenseItem,
            saveInto: ri!expenseItem,
            refreshAfter: "UNFOCUS",
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!dateField(
            label: "Expense Date",
            labelPosition: local!labelPosition,
            value: ri!expenseDate,
            saveInto: ri!expenseDate,
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          a!floatingPointField(
            label: "Expense Amount",
            labelPosition: local!labelPosition,
            value: ri!expenseAmount,
            saveInto: ri!expenseAmount,
            refreshAfter: "UNFOCUS",
            required: not(local!readOnly),
            readOnly: local!readOnly,
            validations: {},
            align: "LEFT"
          ),
          rule!enterComments(
            showWhen: ri!expenseAmount > 100,
            labelPosition: local!labelPosition,
            required: not(local!readOnly),
            readOnly: local!readOnly,
            commentValue: ri!comments,
            commentSaveInto: ri!comments
          ),
          a!radioButtonField(
            labelPosition: "COLLAPSED",
            choiceLabels: {"Approve", "Reject"},
            choiceValues: {true, false},
            value: ri!approve,
            saveInto: ri!approve,
            required: true,
            showWhen: local!readOnly
          )
        },
        buttons: a!buttonLayout(
          primaryButtons: {
            a!buttonWidgetSubmit(
              label: "Submit",
              style: "PRIMARY"
            )
          },
          secondarybuttons: {
            a!buttonWidgetSubmit(
              label: "Cancel",
              style: "SECONDARY",
              value: true,
              saveInto: ri!cancel,
              skipvalidation: true,
              showWhen: not(local!readOnly)
            )
          }
        )
      )
    )
    
  7. Click the Test button.
  8. Enter test values for your expense inputs. Make sure to put APPROVAL as the step value in order to test the read-only view. SAIL_Tutorial_Test_Read_Only.png
  9. Click Test Interface to check out the new read-only behavior.

You should see the following:

Be sure to save your interface.

Troubleshooting

When debugging your expression, take note of the line number mentioned in the error message. Use this line number to narrow down the issue.

FEEDBACK