2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SlackAdvent Calendar 2021

Day 21

サンプルコードからみる Slack モーダルフローの構築

Last updated at Posted at 2021-12-20

はじめに

Slack モーダルとモーダルフローは公式のドキュメントに大変参考になる解説があります。しかし実際に作成しはじめると、この値はどこからとるの?この値はどう使うの?といくつか試行錯誤しました。そこで簡単な Slack モーダルフローのサンプルコードを解説をします。これから作成する方の参考にしてもらえればと思います。

よく参考にしたドキュメント

サンプルで用意したモーダルとフローの説明

display_comment_70.png
コマンド入力により "コメント表示モーダル" が開きます。このモーダルでは入力したコメントを表示するセクション(未入力 と表示している部分) と "コメント入力モーダル" を表示させる "コメント更新" ボタンがあります。"コメント更新" ボタンは "コメント表示モーダル" を "コメント入力モーダル" に置き換える処理になります。またモーダルの処理を中断させる "Cancel" ボタンと、表示しているコメントを Slack アプリのメッセージ欄に出力する "コメント出力" ボタンがあります。

input_comment_70.png
"コメント入力モーダル" にはコメント入力をキャンセルする "Cancel" ボタンと、コメントに入力した内容を "コメント表示モーダル" に反映させる "更新" ボタンがあります。"更新" ボタンにより "コメント入力モーダル" を "コメント表示モーダル" に置き換えます。

display_comment2_70.png
"コメント入力モーダル" でコメントを入力して "更新" ボタンを押すとコメント表示セクションにコメントが表示されます。

output.png
"コメント出力" ボタンを押すと Slack apps のメッセージ欄にコメントを出力します。

Step.1 コマンドによるモーダル起動

コマンドからコメント表示モーダルを開く処理です。

コード

app.command("/multi_modals", async ({ ack, body, client, logger }) => {
  await ack();
  const meta = {comment: ""};
  const metaStr = JSON.stringify(meta);
  try {
    await client.views.open({
      trigger_id: body.trigger_id,
      view: {
        "type": "modal",
        "title": {
          "type": "plain_text",
          "text": "コメント表示",
          "emoji": true
        },
        "callback_id": "output_comment",
        "submit": {
          "type": "plain_text",
          "text": "コメント出力",
          "emoji": true
        },
        "private_metadata": metaStr,
        "close": {
          "type": "plain_text",
          "text": "Cancel",
          "emoji": true
        },
        "blocks": [
          {
            "type": "section",
            "block_id": "comment_display_block",
            "text": {
              "type": "mrkdwn",
              "text": "未入力"
            },
            "accessory": {
              "type": "button",
              "text": {
                "type": "plain_text",
                "text": "コメント更新",
                "emoji": true
              },
              "value": metaStr,
              "action_id": "open_modal"
            }
          }
        ]
      }
    })
  } catch (error) {
    logger.error(error);
  }
});

コード解説

  • app.command メソッドで /multi_modals コマンドをリスニングします。このコマンドを受けてコメント表示モーダルを開く処理を行います。
  • モーダルを開くには views.open を利用します。そのとき trigger_id を指定する必要があります。
  • コメント表示モーダルには Input block がないため、"コメント出力" ボタンで表示している値を受け取るために private_metadata に入力している値(初期表示になるので空文字)を設定します。
  • コメント表示セクションには "未入力" として入力がないことを分かりやすくしています。
  • "コメント更新" ボタンの valueprivate_metadata と同じ値を設定します。ボタン押下時に "コメント入力モーダル" でコメント表示している値を取得できるようにします。

Step.2 コメント表示モーダルのコメント更新ボタン処理

コメント表示モーダルをコメント入力モーダルに更新する処理です。

コード

app.action({action_id: "open_modal"}, async ({ ack, body, payload, client, logger }) => {
  try {
    await ack();

    const trigger_id = body.trigger_id;
    const metaStr = payload.value;
    const meta = JSON.parse(metaStr);

    await client.views.push({
      trigger_id: trigger_id,
      view: {
        "type": "modal",
        "title": {
          "type": "plain_text",
          "text": "コメント入力",
          "emoji": true
        },
        "callback_id": "update_comment",
        "submit": {
          "type": "plain_text",
          "text": "更新",
          "emoji": true
        },
        "private_metadata": metaStr,
        "close": {
          "type": "plain_text",
          "text": "Cancel",
          "emoji": true
        },
        "blocks": [
          {
            "type": "input",
            "block_id": "comment_block",
            "element": {
              "type": "plain_text_input",
              "action_id": "comment_action",
              "placeholder": {
                type: 'plain_text',
                text: "コメントを入力してください。",
                emoji: true
              },
              "initial_value": meta.comment
            },
            "label": {
              "type": "plain_text",
              "text": "コメント",
              "emoji": true
            }
          }
        ]
      }
    })
  } catch (error) {
    logger.error(error);
  }
});

コード解説

  • app.action メソッドを利用して "コメント更新" ボタンの処理をリスニングします。"コメント更新" ボタンの処理をハンドリングするため action_id に指定していた "open_modal" を指定します。
  • "コメント更新" ボタンの valuepayload から取得できます。
  • view.push メソッドで表示していた "コメント入力モーダル" を "コメント表示モーダル" に置き換えます。
  • private_metadata に JSON 文字列を渡す必要はありませんが、コメント入力モーダルを再表示したのちの設定値を簡易に用意するために渡しています。
  • コメントの初期データとして initial_value に表示している値を設定しています。コメントがない場合には placeholder の値が表示されます。

Step.3 コメント入力モーダルの更新ボタン処理

コメント入力モーダルをコメント表示モーダルに更新する(もとに戻す)処理です。また入力したコメントをコメント表示モーダルに反映させます。

コード

app.view("update_comment", async ({ ack, view, client}) => {
  const view_id = view.root_view_id ?? "";

  const comment = view.state.values["comment_block"]["comment_action"].value ?? "";

  try {
    await ack();
    const meta = JSON.parse(view.private_metadata);

    // meta.view_id = view_id;
    meta.comment = comment;
    const metaStr = JSON.stringify(meta);

    await client.views.update({
      view_id: view_id,
      view: {
        "type": "modal",
        "title": {
          "type": "plain_text",
          "text": "コメント表示",
          "emoji": true
        },
        "callback_id": "output_comment",
        "submit": {
          "type": "plain_text",
          "text": "コメント出力",
          "emoji": true
        },
        "private_metadata": metaStr,
        "close": {
          "type": "plain_text",
          "text": "Cancel",
          "emoji": true
        },
        "blocks": [
          {
            "type": "section",
            "block_id": "comment_display_block",
            "text": {
              "type": "mrkdwn",
              "text": meta.comment
            },
            "accessory": {
              "type": "button",
              "text": {
                "type": "plain_text",
                "text": "コメント更新",
                "emoji": true
              },
              "value": metaStr,
              "action_id": "open_modal"
            }
          }
        ]
      }
    });
  } catch (error) {
    logger.error(error);
  }
});

コード解説

  • app.view メソッドを利用して "コメント入力モーダル" の "更新" ボタンの処理をリスニングします。"更新" ボタンの処理をハンドリングするため callback_id の "update_comment" を指定します。
  • "コメント表示モーダル" を再表示させるためには初期表示で割り振られた view_id が必要になります。初期表示の view_id は view から root_view_id で取得できます。
  • 入力したコメントは view.state.values から comment_block comment_action を指定して取得します。
  • view.update メソッドで、"コメント表示モーダル" に更新させます。ここで先ほど取得した view_id を指定します。
  • view の部分はコマンドによるモーダル起動で使った view とほぼ同じで、コメント表示セクションを入力値にしたものになります。実際にコーディングする際には共通化したりすることが可能です。

Step.4 コメント出力処理

コメント表示モーダルで表示している値をメッセージに出力します。

コード

app.view("output_comment", async ({ ack, body, view, client, logger }) => {
  try {
    await ack();

    const meta = JSON.parse(view.private_metadata);
    const out = meta.comment === "" ? "未入力" : meta.comment;

    await client.chat.postMessage({
      channel: body.user.id,
      text: out
    });
  } catch (error) {
    logger.error(error);
  }
});

コード解説

  • app.view メソッドを利用して "コメント表示モーダル" の "コメント出力" ボタンの処理をリスニングします。"コメント出力" ボタンの処理をハンドリングするため callback_id の "output_comment" を指定します。
  • private_metadata からコメントを取り出します。コメントがない場合には "未入力" を出力するようにします。
  • char.postMessage を使ってチャンネルにコメントを出力します。

最後に

説明では省いてきましたが、private_metadata には 3000 文字の制限や button の value には 2000 文字の制限があるのでモーダルを実装する前にいろいろなコンポーネントの制限を確認しておくことをお勧めします。
以上、最後までよんでいただきありがとうございました。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?