Modals are equivalent to alert, pop-up, or dialog boxes, and consist of one or more views.
Modals capture and maintain focus within Slack, allowing you to request and collect data from the user and display data to the user: a truly interactive experience!
This page will guide you through the waters of adding modal interactivity to your app. Ahoy, let's get started!
interactivity
in a function definition The first step in getting your app to set up for modal interactivity is to add the interactivity
property to an existing function definition as an input parameter with the type Schema.slack.types.interactivity. For example, see how we did this in the function definition below:
//diary_function.ts
import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";
export const TreasureMapFunction = DefineFunction({
callback_id: "treasure_map_function",
title: "Treasure map function",
description: "Search for treasure",
source_file: "functions/treasure_map_function.ts",
input_parameters: {
properties: {
interactivity: { // This tells Slack that your function will create interactive elements
type: Schema.slack.types.interactivity,
},
channel_id: {
type: Schema.slack.types.channel_id,
},
mapLocation: {
type: Schema.types.string,
description: "The starting map location",
},
},
required: ["interactivity", "mapLocation"],
},
output_parameters: {
properties: {
newMapLocation: {
type: Schema.types.string,
description: "The destination map location",
},
},
required: ["newMapLocation"],
},
});
Next, we're ready to define how your app will open your modal -- but first, if you'd like to become more familiar with how modals flow behind-the-scenes, be sure to check out Using modals in Slack apps.
The views that make up your modal utilize the views.open
and views.push
API methods to control when a modal is being triggered by the user. Once you have added the interactivity
input parameter, your function implementation can leverage the interactivity pointer provided by the interactivity
input that serves as a unique identifier for the interactivity event. This is accessed within your function using inputs.interactivity.interactivity_pointer
.
Read on to learn two methods of opening a modal view: from a function, or using a Block Kit action.
To open a modal view from within a function, use the views.open
API call within a function implementation as in the following example:
export default SlackFunction(TreasureMapFunction, async ({ inputs, client }) => {
console.log('X marks the spot!');
await client.views.open({
interactivity_pointer: inputs.interactivity.interactivity_pointer,
view: {
"type": "modal", // Specify the type as modal
"title": {
"type": "plain_text",
"text": "There be treasure",
},
"blocks": [{
"type": "input",
"block_id": "section1",
"element": {
"type": "plain_text_input",
"action_id": "map_input",
"placeholder": {
"type": "plain_text",
"text": "Where be treasure?",
},
},
"label": {
"type": "plain_text",
"text": "Map location",
},
"hint": {
"type": "plain_text",
"text": "A little to the left.",
},
}],
"submit": {
"type": "plain_text",
"text": "Dig",
},
"callback_id": "view_treasure_map",
"notify_on_close": true,
},
});
return {
completed: false,
};
});
Some important considerations:
callback_id
. We will use it to define view handlers that react to view open/closed events later.notify_on_close
to true
in order to trigger a view_closed
event.completed: false
. This should be set to true
later in your action handler.The above are important to ensure your modal isn't left floating in the vast ocean of suspended modals.
Similar to opening a modal view from within a function, you can also open a modal view using a Block Kit Action Handler. Below is an example of what your code might look like:
export default SlackFunction(TreasureMapFunction, async ({ inputs, client }) => {
// ... TreasureMapFunction implementation
}).addBlockActionsHandler(['open_treasure_map'],
async ({ action, body, client }) => {
await client.views.open({
interactivity_pointer: body.interactivity.interactivity_pointer,
view: view, // The view we defined earlier in TreasureMapFunction
});
}
);
Next, we will look at how to handle modal view events sent to your app. With your defined modal view equipped with a callback_id
, you can implement a handler to respond to interactions with your modal view.
With our TreasureMap function, we can "chain" additional calls to either the addViewSubmissionHandler
or addViewClosedHandler
methods from 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 code snippet for each method:
// ...
.addViewSubmissionHandler(
// The first argument is used to match the view's `callback_id`; the second is the handler function itself
"view_treasure_map",
async ({ view, body }) => {
console.log('Incoming view submission handler', body);
}
)
//...
//...
.addViewClosedHandler(
"view_treasure_map",
async({ view, body }) => {
console.log('Incoming view closed handler', body);
}
);
// ...
Remember to add a call to the client.functions.completeSuccess
endpoint to explicitly mark the function as complete. You can do that like this:
await client.functions.completeSuccess({
function_execution_id: body.function_data.execution_id,
outputs,
});
Or, if the function execution was not successful and you'd like to raise an error, like this:
await client.functions.completeError({
function_execution_id: body.function_data.execution_id,
error: "Error completing function",
});
Note that both functions.completeSuccess
and functions.completeError
are private methods.
In order to update a view or to push a new view, your view event handlers will use either of the views.update
or views.push
APIs or return an object with a special response_action
property.
If you haven't already, check out Using modals in Slack apps for details about how the view stack works. It may also be helpful to become familiar with the ways you can update a view, push a new view, and close a view.
In the examples below, the addViewSubmissionHandler
method registers a handler to push a new view on to the view stack. The first code snippet shows how to use the API to push a new view:
// ...
.addViewSubmissionHandler(
"view_treasure_map",
async ({ inputs, client, body }) => {
await client.views.push({
interactivity_pointer: inputs.interactivity.interactivity_pointer,
view: view, // The view we defined earlier in TreasureMapFunction
});
})
// ...
The second code snippet shows how to use response_action
to do the same thing. Both result in identical behavior!
// ...
.addViewSubmissionHandler(
"view_treasure_map",
async () => {
return {
response_action: "push",
view: view, // The view we defined earlier in TreasureMapFunction
};
})
// ...
Once you have opened a modal and handled your modal views, you may decide that you'd like to display any potential data validation error messages to your 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.
Yarr! You now have some shiny new views weaved into your app and are on a course to providing a keen user experience. Keep reading and learn how to trigger your workflows and to 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.