Skip to content

Commit aaae503

Browse files
committed
Fix modal document page's style
1 parent 149b753 commit aaae503

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

docs/guides/modals.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,53 @@ title: "Modals"
44
lang: en
55
---
66
# Modals
7+
78
[Modals](https://api.slack.com/surfaces/modals/using) are a focused surface to collect data from users or display dynamic and interactive information. To users, modals appear as focused surfaces inside of Slack enabling brief, yet deep interactions with apps. Modals can be assembled using the visual and interactive components found in [Block Kit](https://api.slack.com/block-kit).
9+
810
### Slack App Configuration
11+
912
The first step to use modals in your app is to enable Interactive Components. Visit the [Slack App configuration page](http://api.slack.com/apps), choose the app you're working on, and go to **Features** > **Interactivity & Shortcuts** on the left pane. There are three things to do on the page.
13+
1014
* Turn on the feature
1115
* Set the **Request URL** to `https://{your app's public URL domain}/slack/events`
1216
* Click the **Save Changes** button at the bottom for sure
17+
1318
### What Your Bolt App Does
19+
1420
There are three patterns to handle on modals. As always, your app has to respond to the request within 3 seconds by `ack()` method. Otherwise, the user will see the timeout error on Slack.
21+
1522
#### `"block_actions"` requests
23+
1624
When someone uses an [interactive component](https://api.slack.com/reference/block-kit/interactive-components) in your app's modal views, the app will receive a [block_actions payload](https://api.slack.com/reference/interaction-payloads/block-actions). All you need to do to handle the `"block_actions"` requests are:
25+
1726
1. [Verify requests](https://api.slack.com/docs/verifying-requests-from-slack) from Slack
1827
1. Parse the request body, and check if the `type` is `"block_actions"` and the `action_id` in a block is the one you'd like to handle
1928
1. [Modify/push a view via API](https://api.slack.com/surfaces/modals/using#modifying) and/or update the modal to hold the sent data as [private_metadata](https://api.slack.com/surfaces/modals/using#carrying_data_between_views)
2029
1. Respond to the Slack API server with 200 OK as an acknowledgment
30+
2131
#### `"view_submission"` requests
32+
2233
When a modal view is submitted, you'll receive a [view_submission payload](https://api.slack.com/reference/interaction-payloads/views#view_submission). All you need to do to handle the `"view_submission"` requests are:
34+
2335
1. [Verify requests](https://api.slack.com/docs/verifying-requests-from-slack) from Slack
2436
1. Parse the request body, and check if the `type` is `"view_submission"` and the `callback_id` is the one you'd like to handle
2537
1. Extract the form data from `view.state.values`
2638
1. Do whatever to do such as input validations, storing them in a database, talking to external services
2739
1. Respond with 200 OK as the acknowledgment by either of the followings:
2840
* Sending an empty body means closing only the modal
2941
* Sending a body with `response_action` (possible values are `"errors"`, `"update"`, `"push"`, `"clear"`)
42+
3043
#### `"view_closed"` requests (only when `notify_on_close` is `true`)
44+
3145
Your app can optionally receive [view_closed payloads](https://api.slack.com/reference/interaction-payloads/views#view_closed) whenever a user clicks on the Cancel or x buttons. These buttons are standard, not blocks, in all app modals. To receive the `"view_closed"` payload when this happens, set `notify_on_close` to `true` when creating a view with [views.open](https://api.slack.com/methods/views.open) and [views.push](https://api.slack.com/methods/views.push) methods.
3246
All you need to do to handle the `"view_closed"` requests are:
3347
1. [Verify requests](https://api.slack.com/docs/verifying-requests-from-slack) from Slack
3448
1. Parse the request body, and check if the `type` is `"view_closed"` and the `callback_id` is the one you'd like to handle
3549
1. Do whatever to do at the timing
3650
1. Respond with 200 OK as the acknowledgment
51+
3752
### Modal Development Tips
53+
3854
In general, there are a few things to know when working with modals. They would be:
3955
* You need `trigger_id` in user interaction payloads to open a modal view
4056
* Only the inputs in `"type": "input"` blocks will be included in `view_submission`'s `view.state.values`
@@ -43,8 +59,11 @@ In general, there are a few things to know when working with modals. They would
4359
* You can use `view.private_metadata` to hold the internal state and/or `"block_actions"` results on the modal
4460
* You respond to `"view_submission"` requests with `response_action` to determine the next state of a modal
4561
* [views.update](https://api.slack.com/methods/views.update) and [views.push](https://api.slack.com/methods/views.push) API methods are supposed to be used when receiving `"block_actions"` requests in modals, NOT for `"view_submission"` requests
62+
4663
---
64+
4765
## Examples
66+
4867
**NOTE**: If you're a beginner to using Bolt for Slack App development, consult [Getting Started with Bolt]({{ site.url | append: site.baseurl }}/guides/getting-started-with-bolt), first.
4968
Let's start with opening a modal. Let's say, we're going to open the following modal.
5069
```javascript
@@ -81,13 +100,16 @@ Let's start with opening a modal. Let's say, we're going to open the following m
81100
]
82101
}
83102
```
103+
84104
**slack-api-client** offers a smooth DSL for building blocks and views. The following code generates **View** objects in a type-safe way.
105+
85106
```java
86107
import com.slack.api.model.view.View;
87108
import static com.slack.api.model.block.Blocks.*;
88109
import static com.slack.api.model.block.composition.BlockCompositions.*;
89110
import static com.slack.api.model.block.element.BlockElements.*;
90111
import static com.slack.api.model.view.Views.*;
112+
91113
View buildView() {
92114
return view(view -> view
93115
.callbackId("meeting-arrangement")
@@ -120,26 +142,34 @@ View buildView() {
120142
);
121143
}
122144
```
145+
123146
If you need to carry some information to the modal, setting `private_metadata` is a good way for it. The `private_metadata` is a single string with a maximum of 3000 characters. So, if you have multiple values, you need to serialize them into a string in a format.
147+
124148
```java
125149
import com.slack.api.bolt.util.JsonOps;
150+
126151
class PrivateMetadata {
127152
String responseUrl;
128153
String commandArgument;
129154
}
155+
130156
app.command("/meeting", (req, ctx) -> {
131157
PrivateMetadata data = new PrivateMetadata();
132158
data.responseUrl = ctx.getResponseUrl();
133159
data.commandArgument = req.getPayload().getText();
160+
134161
return view(view -> view.callbackId("meeting-arrangement")
135162
.type("modal")
136163
.notifyOnClose(true)
137164
.privateMetadata(JsonOps.toJsonString(data))
138165
// omitted ...
139166
```
167+
140168
A `trigger_id` is required to open a modal. You can access it in payloads sent by user interactions such as slash command invocations, clicking a button. In Bolt, you can acquire the value by calling `Request.getPayload().getTriggerId()` as it's a part of payloads. More easily, it's also possible to get it by `Context.getTriggerId()`. These methods are defined only when `trigger_id` exists in a payload.
169+
141170
```java
142171
import com.slack.api.methods.response.views.ViewsOpenResponse;
172+
143173
app.command("/meeting", (req, ctx) -> {
144174
ViewsOpenResponse viewsOpenRes = ctx.client().viewsOpen(r -> r
145175
.triggerId(ctx.getTriggerId())
@@ -148,7 +178,9 @@ app.command("/meeting", (req, ctx) -> {
148178
else return Response.builder().statusCode(500).body(viewsOpenRes.getError()).build();
149179
});
150180
```
181+
151182
The same code in Kotlin looks as below. (New to Kotlin? [Getting Started in Kotlin]({{ site.url | append: site.baseurl }}/guides/getting-started-with-bolt#getting-started-in-kotlin) may be helpful)
183+
152184
```kotlin
153185
app.command("/meeting") { req, ctx ->
154186
val res = ctx.client().viewsOpen { it
@@ -159,7 +191,9 @@ app.command("/meeting") { req, ctx ->
159191
else Response.builder().statusCode(500).body(res.error).build()
160192
}
161193
```
194+
162195
In Kotlin, it's much easier to embed multi-line string data in source code. It may be handier to use `viewAsString(String)` method instead.
196+
163197
```kotlin
164198
// Build a view using string interpolation
165199
val commandArg = req.payload.text
@@ -182,15 +216,19 @@ val modalView = """
182216
]
183217
}
184218
""".trimIndent()
219+
185220
val res = ctx.client().viewsOpen { it
186221
.triggerId(ctx.triggerId)
187222
.viewAsString(modalView)
188223
}
189224
```
225+
190226
Alternatively, you can use the [Block Kit DSL]({{ site.url | append: site.baseurl }}/guides/composing-messages#block-kit-kotlin-dsl) in conjunction with the Java Builder to construct your view. The Java example above would look like this in Kotlin:
227+
191228
```kotlin
192229
import com.slack.api.model.kotlin_extension.view.blocks
193230
import com.slack.api.model.view.Views.*
231+
194232
fun buildView(): View {
195233
return view { thisView -> thisView
196234
.callbackId("meeting-arrangement")
@@ -236,15 +274,19 @@ fun buildView(): View {
236274
}
237275
}
238276
```
277+
239278
### `"block_actions"` requests
279+
240280
Basically it's the same with [Interactive Components]({{ site.url | append: site.baseurl }}/guides/interactive-components) but the only difference is that a payload coming from a modal has `view` and also its `private_metadata`
281+
241282
```java
242283
import com.google.gson.Gson;
243284
import com.slack.api.model.view.View;
244285
import com.slack.api.model.view.ViewState;
245286
import com.slack.api.methods.response.views.ViewsUpdateResponse;
246287
import com.slack.api.util.json.GsonFactory;
247288
import java.util.Map;
289+
248290
View buildViewByCategory(String categoryId, String privateMetadata) {
249291
Gson gson = GsonFactory.createSnakeCase();
250292
Map<String, String> metadata = gson.fromJson(privateMetadata, Map.class);
@@ -268,6 +310,7 @@ View buildViewByCategory(String categoryId, String privateMetadata) {
268310
))
269311
);
270312
}
313+
271314
app.blockAction("category-selection-action", (req, ctx) -> {
272315
String categoryId = req.getPayload().getActions().get(0).getSelectedOption().getValue();
273316
View currentView = req.getPayload().getView();
@@ -281,7 +324,9 @@ app.blockAction("category-selection-action", (req, ctx) -> {
281324
return ctx.ack();
282325
});
283326
```
327+
284328
It looks like below in Kotlin.
329+
285330
```kotlin
286331
app.blockAction("category-selection-action") { req, ctx ->
287332
val categoryId = req.payload.actions[0].selectedOption.value
@@ -296,16 +341,21 @@ app.blockAction("category-selection-action") { req, ctx ->
296341
ctx.ack()
297342
}
298343
```
344+
299345
### `"view_submission"` requests
346+
300347
Bolt does many of the commonly required tasks for you. The steps you need to handle would be:
348+
301349
* Specify the `callback_id` to handle (by either of the exact name or regular expression)
302350
* Do whatever to do such as input validations, storing them in a database, talking to external services
303351
* Call `ack()` as an acknowledgment with either of the followings:
304352
* Sending an empty body means closing only the modal
305353
* Sending a body with `response_action` (possible values are `"errors"`, `"update"`, `"push"`, `"clear"`)
354+
306355
```java
307356
import com.slack.api.model.view.ViewState;
308357
import java.util.*;
358+
309359
// when a user clicks "Submit"
310360
app.viewSubmission("meeting-arrangement", (req, ctx) -> {
311361
String privateMetadata = req.getPayload().getView().getPrivateMetadata();
@@ -325,7 +375,9 @@ app.viewSubmission("meeting-arrangement", (req, ctx) -> {
325375
}
326376
});
327377
```
378+
328379
It looks like below in Kotlin.
380+
329381
```kotlin
330382
// when a user clicks "Submit"
331383
app.viewSubmission("meeting-arrangement") { req, ctx ->
@@ -346,25 +398,35 @@ app.viewSubmission("meeting-arrangement") { req, ctx ->
346398
}
347399
}
348400
```
401+
349402
If you respond with `"response_action": "update"` or `"push"`, `response_action` and `view` are required in the response body.
403+
350404
```java
351405
ctx.ack(r -> r.responseAction("update").view(renewedView));
352406
ctx.ack(r -> r.responseAction("push").view(newViewInStack));
353407
```
408+
354409
It looks like below in Kotlin.
410+
355411
```kotlin
356412
ctx.ack { it.responseAction("update").view(renewedView) }
357413
ctx.ack { it.responseAction("push").view(newViewInStack) }
358414
```
415+
359416
#### Publishing Messages After Modal Submissions
417+
360418
`view_submission` payloads don't have `response_url` by default. However, if you have an `input` block asking users a channel to post a message, payloads may provide `response_urls` (`List<ResponseUrl> responseUrls` in Java).
419+
361420
To enable this, set the block element type as either [`channels_select`](https://api.slack.com/reference/block-kit/block-elements#channel_select) or [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) and add `"response_url_enabled": true`. Refer to [the API document](https://api.slack.com/surfaces/modals/using#modal_response_url) for details.
421+
362422
Also, if you want to automatically set the channel a user is viewing when opening a modal to`initial_conversation(s)`, turn `default_to_current_conversation` on in [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) / [`multi_conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select) elements.
423+
363424
```java
364425
import static com.slack.api.model.block.Blocks.*;
365426
import static com.slack.api.model.block.composition.BlockCompositions.*;
366427
import static com.slack.api.model.block.element.BlockElements.*;
367428
import static com.slack.api.model.view.Views.*;
429+
368430
View modalView = view(v -> v
369431
.type("modal")
370432
.callbackId("request-modal")
@@ -380,11 +442,15 @@ View modalView = view(v -> v
380442
)
381443
)));
382444
```
445+
383446
### `"view_closed"` requests (only when `notify_on_close` is `true`)
447+
384448
Bolt does many of the commonly required tasks for you. The steps you need to handle would be:
449+
385450
* Specify the `callback_id` to handle (by either of the exact name or regular expression)
386451
* Do whatever to do at the timing
387452
* Call `ack()` as an acknowledgment
453+
388454
```java
389455
// when a user clicks "Cancel"
390456
// "notify_on_close": true is required
@@ -393,7 +459,9 @@ app.viewClosed("meeting-arrangement", (req, ctx) -> {
393459
return ctx.ack();
394460
});
395461
```
462+
396463
It looks like below in Kotlin.
464+
397465
```kotlin
398466
// when a user clicks "Cancel"
399467
// "notify_on_close": true is required
@@ -402,8 +470,11 @@ app.viewClosed("meeting-arrangement") { req, ctx ->
402470
ctx.ack()
403471
}
404472
```
473+
405474
### Under the Hood
475+
406476
If you hope to understand what is actually happening with the above code, reading the following (a bit pseudo) code may be helpful.
477+
407478
```java
408479
import java.util.Map;
409480
import com.google.gson.Gson;
@@ -415,6 +486,7 @@ import com.slack.api.app_backend.views.payload.ViewClosedPayload;
415486
import com.slack.api.app_backend.util.JsonPayloadExtractor;
416487
import com.slack.api.app_backend.util.JsonPayloadTypeDetector;
417488
import com.slack.api.util.json.GsonFactory;
489+
418490
PseudoHttpResponse handle(PseudoHttpRequest request) {
419491
// 1. Verify requests from Slack
420492
// https://api.slack.com/docs/verifying-requests-from-slack

0 commit comments

Comments
 (0)