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 Form template. The design view will display the configratuion for the form layout.

  1. In the design view, update the label of the form to be "Submit Expense Report".
  2. Then, remove the "Lorem Ipsum" text from the "Instructions" label of the form. You should see the following:

  1. Under Column 1 Contents, click List of Components.
  2. Click Add Component. In the basic inputs section, click Date to select the text component.
  3. Follow the same steps to add 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 Lorem Ipsum 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. Make the component required by checking the Required box.
  4. 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.

  1. 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:

  1. Follow the same steps to configure the date and decimal components using the corresponding variables.

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. We'll use the if() function for this, see also: if()

  1. In the right-hand pane, add a new interface input called comments (Text).
  2. Go to the column configuration by clicking on the orange bracket labeled Column 1 in the live view, as shown below.

  1. Add a paragraph component below the decimal component.
  2. Click on the paragraph to view its configuration.
  3. Change the Label to "Comments", make the field required by checking the Required box, and set Display Value and Save Input To as ri!comments, as we did in the previous section.
  4. Click on the edit icon that click on the edit icon that displays when you hover over the header to view the expression for the paragraph component.

Now let's use the if() function to only return the paragraph component if the expense amount is greater than 100.

  1. Wrap the paragraph component in an if statement to conditionally display it using the following expression:
if(
  ri!expenseAmount >= 100,
  a!paragraphField(
    label: "Comments",
    labelPosition: "ABOVE",
    value: ri!comments,
    saveInto: ri!comments,
    refreshAfter: "UNFOCUS",
    required: true,
    height: "MEDIUM",
    validations: {}
  ),
  {}
)
  1. Click OK to save the expression and close the dialog.

You should see the following:

Notice that the if is now displayed in the design view. You can now come here to modify the value of an individual parameter if you need to.

Notice also that we could have modified the expression from the Column 1 Contents by clicking the edit icon next to the a paragraph component, as shown below.

Let's test the conditional display. Enter 100 as the "Expense Amount" 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 a custom validation to our form that only allows users to enter up to 100 characters in the Comments field. To do this,

  1. Open your Comments` interface and go to the paragraph configuration.
  2. Click the Edit as Expression icon to the right of the text field and enter the following expression in the expression box:
if(len(ri!commentValue)<=100, null, "Your text has exceeded 100 characters")
  1. Click OK to save the expression and 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 live view, if you enter more than 100 characters in the comments field, you should see the validation message we configured when you click one of the buttons or click elsewhere on the form.

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 entire if statement from the expression using the edit icon in the header of the if configuration, as shown below.

  1. From the application contents view of the Appian Tutorial application, create an interface called enterComments and save it in the Examples folder.
  2. Add inputs to the new interface.
    • displayWhen (Boolean)
    • readOnly (Boolean)
    • required (Boolean)
    • commentValue (Text)
    • commentSaveInto (Text Array)
  3. In the design view, click Add Component to open the SAIL component picker.
  4. 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.
  5. Insert the expression into the expression box and then modify with the following:
if(
  ri!displayWhen,
  a!paragraphField(
    label: "Comments",
    labelPosition: "ABOVE",
    value: ri!commentValue,
    saveInto: ri!commentSaveInto,
    refreshAfter: "UNFOCUS",
    required: ri!required,
    readOnly: ri!readOnly,
    validations: {}
  ),
  {}
)
  1. Click Insert Expression to save the expression and close the dialog.
  2. Save the interface.

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

  1. Go to the Column 1 Contents configuration.
  2. Delete the existing paragraph by clicking the Delete icon, as shown below.

  1. Click the Add Component link to add a component below the decimal component.
  2. Click the enter an expression link at the top of the dialog under the title and enter the following expression:
rule!enterComments(
  displayWhen: ri!expenseAmount >= 100,
  readOnly: false,
  required: true,
  commentValue: ri!comments,
  commentSaveInto: ri!comments
)
  1. 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 show when to switch to the expression view.

  1. Add new inputs to the interface.
    • step (Text)
    • approve (Boolean)
  1. Go to the Column 1 Contents configuration.
  2. Add a radio button below enterComments.
  3. Click on the radio button to view it's configuration.
  4. Change the Label Position to "Hidden", check the Required box, and set the Selected Value and Save Selection To as ri!approve.
  5. Click on the List of Text String link under Choice Labels, and change the two items in the list to "Approve" and "Reject".
  6. 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 View icon located at the bottom of the left-hand pane, as shown below.

  1. Add a local variable to check whether it should be read-only based on the step. Modify your expression with the following:
load(
  local!readOnly: ri!step="APPROVAL",
  a!formLayout(
    label: "Submit Expense Report",
    firstColumnContents: {
      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(
        displayWhen: ri!expenseAmount >= 100,
        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
      )
    },
    secondColumnContents: {},
    buttons: a!buttonLayout(
      primaryButtons: {
        a!buttonWidgetSubmit(
          label: "Submit",
          style: "PRIMARY"
        )
      },
      secondarybuttons: {
        a!buttonWidgetSubmit(
          label: "Cancel",
          style: "NORMAL",
          value: true,
          saveInto: ri!cancel,
          skipvalidation: true
        )
      }
    )
  )
)
  1. Use the new variable to make each component conditionally readOnly and optional when it is in read-only mode. Modify your expression with the following:
load(
  local!readOnly: ri!step="APPROVAL",
  a!formLayout(
    label: "Submit Expense Report",
    firstColumnContents: {
      a!textField(
        label: "Expense Item",
        labelPosition: "ABOVE",
        value: ri!expenseItem,
        saveInto: ri!expenseItem,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!dateField(
        label: "Expense Date",
        labelPosition: "ABOVE",
        value: ri!expenseDate,
        saveInto: ri!expenseDate,
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!floatingPointField(
        label: "Expense Amount",
        labelPosition: "ABOVE",
        value: ri!expenseAmount,
        saveInto: ri!expenseAmount,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      rule!enterComments(
        displayWhen: ri!expenseAmount >= 100,
        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
      )
    },
    secondColumnContents: {},
    buttons: a!buttonLayout(
      primaryButtons: {
        a!buttonWidgetSubmit(
          label: "Submit",
          style: "PRIMARY"
        )
      },
      secondarybuttons: {
        a!buttonWidgetSubmit(
          label: "Cancel",
          style: "NORMAL",
          value: true,
          saveInto: ri!cancel,
          skipvalidation: true
        )
      }
    )
  )
)
  1. Only display the radio button when on the approval step by modifying your expression with the following:
load(
  local!readOnly: ri!step="APPROVAL",
  a!formLayout(
    label: "Submit Expense Report",
    firstColumnContents: {
      a!textField(
        label: "Expense Item",
        labelPosition: "ABOVE",
        value: ri!expenseItem,
        saveInto: ri!expenseItem,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!dateField(
        label: "Expense Date",
        labelPosition: "ABOVE",
        value: ri!expenseDate,
        saveInto: ri!expenseDate,
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!floatingPointField(
        label: "Expense Amount",
        labelPosition: "ABOVE",
        value: ri!expenseAmount,
        saveInto: ri!expenseAmount,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      rule!enterComments(
        displayWhen: ri!expenseAmount >= 100,
        readOnly: local!readOnly,
        required: not(local!readOnly),
        commentValue: ri!comments,
        commentSaveInto: ri!comments
      ),
      if(
        local!readOnly,
        a!radioButtonField(
          labelPosition: "COLLAPSED",
          choiceLabels: {"Approve", "Reject"},
          choiceValues: {true, false},
          value: ri!approve,
          saveInto: ri!approve,
          required: true
        ),
        {}
      )
    },
    secondColumnContents: {},
    buttons: a!buttonLayout(
      primaryButtons: {
        a!buttonWidgetSubmit(
          label: "Submit",
          style: "PRIMARY"
        )
      },
      secondarybuttons: {
        a!buttonWidgetSubmit(
          label: "Cancel",
          style: "NORMAL",
          value: true,
          saveInto: ri!cancel,
          skipvalidation: true
        )
      }
    )
  )
)
  1. Hide the cancel button on the approval step by modifying your expression with the following:
load(
  local!readOnly: ri!step="APPROVAL",
  a!formLayout(
    label: "Submit Expense Report",
    firstColumnContents: {
      a!textField(
        label: "Expense Item",
        labelPosition: "ABOVE",
        value: ri!expenseItem,
        saveInto: ri!expenseItem,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!dateField(
        label: "Expense Date",
        labelPosition: "ABOVE",
        value: ri!expenseDate,
        saveInto: ri!expenseDate,
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!floatingPointField(
        label: "Expense Amount",
        labelPosition: "ABOVE",
        value: ri!expenseAmount,
        saveInto: ri!expenseAmount,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      rule!enterComments(
        displayWhen: ri!expenseAmount >= 100,
        readOnly: local!readOnly,
        required: not(local!readOnly),
        commentValue: ri!comments,
        commentSaveInto: ri!comments
      ),
      if(
        local!readOnly,
        a!radioButtonField(
          labelPosition: "COLLAPSED",
          choiceLabels: {"Approve", "Reject"},
          choiceValues: {true, false},
          value: ri!approve,
          saveInto: ri!approve,
          required: true
        ),
        {}
      )
    },
    secondColumnContents: {},
    buttons: a!buttonLayout(
      primaryButtons: {
        a!buttonWidgetSubmit(
          label: "Submit",
          style: "PRIMARY"
        )
      },
      secondarybuttons: {
        if(
          local!readOnly,
          {},
          a!buttonWidgetSubmit(
            label: "Cancel",
            style: "NORMAL",
            value: true,
            saveInto: ri!cancel,
            skipvalidation: true
          )
        )
      }
    )
  )
)
  1. Change the form title based on the step by modifying your expression with the following:
load(
  local!readOnly: ri!step="APPROVAL",
  a!formLayout(
    label: if(local!readOnly, "Approve Expense Report", "Submit Expense Report"),
    firstColumnContents: {
      a!textField(
        label: "Expense Item",
        labelPosition: "ABOVE",
        value: ri!expenseItem,
        saveInto: ri!expenseItem,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!dateField(
        label: "Expense Date",
        labelPosition: "ABOVE",
        value: ri!expenseDate,
        saveInto: ri!expenseDate,
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      a!floatingPointField(
        label: "Expense Amount",
        labelPosition: "ABOVE",
        value: ri!expenseAmount,
        saveInto: ri!expenseAmount,
        refreshAfter: "UNFOCUS",
        required: not(local!readOnly),
        readOnly: local!readOnly,
        validations: {},
        align: "LEFT"
      ),
      rule!enterComments(
        displayWhen: ri!expenseAmount >= 100,
        required: not(local!readOnly),
        readOnly: local!readOnly,
        commentValue: ri!comments,
        commentSaveInto: ri!comments
      ),
      if(
        local!readOnly,
        a!radioButtonField(
          labelPosition: "COLLAPSED",
          choiceLabels: {"Approve", "Reject"},
          choiceValues: {true, false},
          value: ri!approve,
          saveInto: ri!approve,
          required: true
        ),
        {}
      )
    },
    secondColumnContents: {},
    buttons: a!buttonLayout(
      primaryButtons: {
        a!buttonWidgetSubmit(
          label: "Submit",
          style: "PRIMARY"
        )
      },
      secondarybuttons: {
        if(
          local!readOnly,
          {},
          a!buttonWidgetSubmit(
            label: "Cancel",
            style: "NORMAL",
            value: true,
            saveInto: ri!cancel,
            skipvalidation: true
          )
        )
      }
    )
  )
)

Be sure to save your interface.

To use Approve/Reject buttons instead of a radio button, see the Approve/Reject Submit Buttons recipe.

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