The new Slack platform and the features described below are in beta and under active development.

Modal View Interactivity

As you build your new Slack app or even update an existing app, you might be asking yourself - how can I display or collect information in a focused space within Slack? Look no further than utilizing the power of modals. Modals provide a space for your app to request and collect data from users or display dynamic and interactive data.

What's a modal without some rich interactive components or better yet, Block Kit interactive components. Vast possibilities lie ahead when you begin to add levels of interactivity into your Slack app. This page will look at the ways to add and handle modal interactivity events throughout your app.

Say you want to collect some feedback from your team or display data from a 3rd party API; you can build modal views to help you accomplish those tasks. Views within your app are either submitted, closed, or updated. Become familiar with the modal functionality here.

This page will provide information about how to handle view_submission and view_closed events within your app. You will also learn how each view can be triggered at different places in your app. Modal views within your app can be triggered within your function or within a Block Kit action.

Adding interactivity in a function definition

Before your app is able to recognize interactivity, you must have the below requirements:

  1. Have a complete function definition
  2. Include interactivity as an input parameter in your function with type: Schema.slack.types.interactivity

This is an example function definition with the interactivity input parameter. This will exist in your manifest.ts file.

import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";

export const DiaryFunction = DefineFunction({
  callback_id: "diary",
  title: "Diary",
  description: "Write a diary entry",
  source_file: "functions/diary_function.ts", // <-- important! Make sure this is where the logic for your function
  input_parameters: {
    properties: {
      interactivity: { // <-- important! This gives Slack a hint that your function will create interactive elements like views
        type: Schema.slack.types.interactivity,
      },
      channel_id: {
        type: Schema.slack.types.channel_id,
      },
    },
    required: ["interactivity"],
  },
  output_parameters: {
    properties: {},
    required: [],
  },
});

Next we will look at how to open a modal within your app.

Opening a modal view

Modal views utilize the views.open API method and views.push API method to control when a modal is being triggered by the user.

When you create an input parameter named interactivity of type Schema.slack.types.interactivity, your implementation can leverage an interactivity pointer that serves as a unique identifier for the interactivity event. This is accessed in your functions via inputs.interactivity.interactivity_pointer.

Opening a View from a function

Here's an example of a views.open API call within a function implementation. This code could exist under functions/diary_function.ts.

import { SlackFunction } from "deno-slack-sdk/types.ts";
// DiaryFunction is the function we defined in the previous section
import { DiaryFunction } from "../manifest.ts";

export default SlackFunction(DiaryFunction,
  async ({ inputs, client }) => {
  console.log('Someone might want to write a diary entry...');

  await client.views.open({
    interactivity_pointer: inputs.interactivity.interactivity_pointer

    view: {
      "type": "modal", //Be sure to specify the view type
      "title": {
        "type": "plain_text",
        "text": "Modal title",
      },
      "blocks": [
        {
          "type": "input",
          "block_id": "section1",
          "element": {
            "type": "plain_text_input",
            "action_id": "diary_input",
            "multiline": true,
            "placeholder": {
              "type": "plain_text",
              "text": "What is on your mind today?",
            },
          },
          "label": {
            "type": "plain_text",
            "text": "Diary Entry",
          },
          "hint": {
            "type": "plain_text",
            "text": "Don't worry, no one but you will see this.",
          },
        },
      ],
      "submit": {
        "type": "plain_text",
        "text": "Save",
      },
      "callback_id": "view_diary", // <-- remember this ID, we will use it to route events to handlers!
      "notify_on_close": true, // <-- this must be defined in order to trigger `view_closed` events!
    },
  });
  // Important to set completed: false! We will set the function's complete
  // status later - in our view submission handler
  return {
    completed: false,
  };
};

In this code sample keep in mind of a few things.

  • We defined a callback_id which will we will use to define handlers that react to the above view being closed or submitted. More on this below.
  • Set notify_on_close to true in order to trigger the view_closed event.
  • Set the return to completed: false

The above are important to remember to ensure your modal isn't left suspended in the modal wasteland.

Opening a view using Block Kit

Similarly to opening a view within a function, you can also open a view using a Block Kit Action Handler.

Below is an example of what your code might look like:

export default SlackFunction(DiaryFunction,
  // ...the rest of the function implementation goes here...
).addBlockActionsHandler(
  'open_diary',
  async ({ action, body, client }) => {
    await client.views.open({
      interactivity_pointer: body.interactivity.interactivity_pointer,
      view: { /* your view object goes here */ },
    });
  }
);

Next, we will look at how to handle view events sent to your app.

Handling view events

Now with your defined modal view equipped with a callback_id, you can implement a handler to respond to interactions with the view.

With our Diary function above, we can "chain" additional calls to either the addViewSubmissionHandler or addViewClosedHandler methods off of our top-level function. These methods allow you to register specific function handlers to respond to either view submission or view closed events, respectively.

Here is an example of both methods:

export default SlackFunction(DiaryFunction,
  // ...the rest of the function implementation goes here...
).addViewSubmissionHandler(
    "view_diary", // The first argument to any of the add*Handler methods can accept a string, array of strings, or RegExp.
    // This first argument will be used to match the view's `callback_id`
    async ({ view, body }) => { // The second argument is the handler function itself
      console.log('Incoming view submission handler invocation', body);
    }
  )
  .addViewClosedHandler(
    "view_diary",
    async({ view, body }) => {
      console.log('Incoming view closed handler invocation', body);
    }
  );

Updating and pushing a view

Become familiar with the ways you can update a view, push a new view and close a view. In order to update or push a new view you will use either the views.update API, views.push API or via the returned view handler called response_action from within any of the view event handlers.

Following is an example of using the addViewSubmissionHandler method to register a handler to push a new view onto the view stack. The first part shows how to use the API to push a new view, while the second part shows how to use response_action to do the same thing. Both result in identical behavior!

export default SlackFunction(DiaryFunction,
  // ...the rest of the function implementation goes here...
).addViewSubmissionHandler(/view/, async ({ inputs, client, body }) => {
  // A view submission handler that pushes a new view using the API
  await client.views.push({
    interactivity_pointer: inputs.interactivity.interactivity_pointer,
    view: { /* your view object goes here */ },
  });
})
.addViewSubmissionHandler(/view/, async () => {
// A view submission handler that pushes a new view using the `response_action`
  return {
    response_action: "push",
    view: { /* your view object goes here */ },
  });
});

Data validation

Once you have opened a modal and defined a ViewRouter as described above, you may also decide that you'd like to display any data validation error messages to users. To do that, you can apply functionality from the view-related APIs you know and love in the next-gen platform.

As long as your submission handler returns an error object defined on this page, the error messages you include in that object will be displayed right next to the relevant form fields based on their field IDs.

Onward

Hooray! You now have some cool new views weaved within your app. You are on a path to providing a keen user experience. Keep on reading to learn how to trigger your workflows and include even more rich Block Kit components.

Have 2 minutes to provide some feedback?

We'd love to hear about your experience with the new Slack platform. Please complete our short survey so we can use your feedback to improve.