PowerApps│感謝を送り合う社内SNSアプリの作り方 ④投稿画面

こんにちは、あんこ先生です。

ここまでで、投稿が並ぶタイムラインと、いいね機能ができました。

でも、肝心の「ありがとう」を送る画面がまだありませんでしたよね。

今回はそこを作ります。

送信先を選んで、メッセージを書いて、タグを付けて、画像も添えて。

これができると、ようやくアプリの中だけで一周できるようになりますよ。

この記事で作るもの
・送信先を部署メンバーから選ぶドロップダウン
・メッセージの入力欄
・タップで選べるタグ(複数選択)
・画像の添付(1枚)
・投稿ボタンと、相手へのメール通知

まだ読んでいない人は、先にいいね機能とコレクションの回まで進めておいてくださいね。

画面の骨組み

新しい画面を追加して、名前を scrPost にします。

第2回のボトムメニューで、行き先として名前だけ作っておいた画面ですね。

上から順に、送信先・メッセージ・タグ・画像を縦に並べて、いちばん下に投稿ボタンを置きます。

入力欄をまとめるコンテナ conPostForm を1つ置いて、その中に部品を入れていきましょう。

プロパティ設定する式
FillRGBA(255, 255, 255, 1)
Y70
HeightParent.Height – 70 – 64
WidthParent.Width

画面の OnVisible には、入力状態のリセットを入れておきます。

//画面 scrPost の OnVisible(入力状態を初期化する)
//二重送信を防ぐフラグを倒して、前回選んだタグを消す
UpdateContext({ ctxIsSubmitting: false });
Clear(colSelectedTags)

この colSelectedTags は、あとでタグを選ぶときに使うコレクションです。

前の投稿のタグが残っていると気持ち悪いので、画面を開くたびに空にしています。

部品は縦に積んでいく

この画面は「見出しラベル+入力欄」を4組、縦に並べる形になります。

ここで、第2回のタイムラインでやった「ひとつ上の下端から積む」を、そのまま使います。

//入力欄の Y(ひとつ上の見出しラベルの下端から積む)
lblToUser.Y + lblToUser.Height

Y座標を数字で決め打ちしないのは、今回も同じ理由です。

あとから見出しの文字を大きくしたり、入力欄の高さを変えたりしても、下の部品が勝手についてきてくれます。

とくにタグの部分は、タグの数によって高さが変わります

決め打ちしていると、タグを1つ足しただけで画像欄が隠れる、なんてことになりますからね。

送信先を選ぶ

まずは「誰にありがとうを送るか」の部分から。

見出しのテキストラベル lblToUser を置いて、「送信先」と入れておきます。

プロパティ設定する式
Text“送信先”
FontWeightFontWeight.Semibold
Size16
Height32
WidthParent.Width – 40
X20
Y16

その下に、モダンドロップダウンを置いて名前を drpToUser にします。

プロパティ設定する式
ItemsglbMembers
ItemDisplayTextThisItem.DisplayName
WidthParent.Width – 40
X20
YlblToUser.Y + lblToUser.Height

glbMembers は、第1回の App.OnStart で作った部署メンバーの一覧です。

ここでいきなり、ふたつの落とし穴があります。

ここが落とし穴:表示する列は ItemDisplayText で決める
ドロップダウンに何を表示するかは、ItemDisplayText で指定します。クラシックコントロールのようにValue という名前のプロパティはありません。そちらで書くと「そのプロパティは存在しません」と怒られてしまうので、気をつけてください。
ここが落とし穴:編集中は OnStart が動かない
貼り付けたのに、ドロップダウンに名前がひとつも出てこない。しかも ThisItem.DisplayName のところが赤い波線になっている。そんなときは、OnStartがまだ実行されていないのが原因です。編集中は自動で走らないので、glbMembers が空っぽのまま。中身が無いと、Power Apps は「DisplayNameという列がある」ことすら分かりません。ツリービューでApp(いちばん上)を右クリックして、「OnStartの実行」を押してみてください。これだけで、名前がずらっと出てくるはずです。

グループからメンバーを取ってくる方法は、全社員を把握!Office365ユーザー&グループコネクタの使い方にまとめてあります。

メッセージを書く

次はメッセージの入力欄です。

同じように、見出しラベル lblBodyLabel(「メッセージ」)を置きます。

Yは、ひとつ上のドロップダウンの下端から。

プロパティ設定する式
Text“メッセージ”
Height32
YdrpToUser.Y + drpToUser.Height + 5

その下にモダンテキスト入力を置いて、名前を txtBody に。

プロパティ設定する式
TypeTextInputType.Multiline
Placeholder“感謝の気持ちを書きましょう”
Height120
WidthParent.Width – 40
X20
YlblBodyLabel.Y + lblBodyLabel.Height

複数行にしたいので TypeMultiline にしています。

ここが落とし穴:クラシックとは名前が違う
クラシックのテキスト入力に慣れていると、ここで3回つまずきます。複数行にしたいときは Mode ではなく Type。薄い文字を出したいときは HintText ではなく Placeholder。そして入力された値を読むときは .Value ではなく .Text です。とくに最後の .Text は、あとで投稿ボタンを作るときに効いてきます。.Value で書くと値が取れないので、覚えておいてください。

表にすると、こんな対応になります。

やりたいことつい書いてしまう正しい書き方
複数行にするModeType
薄い文字を出すHintTextPlaceholder
入力された値を読む.Value.Text

タグを選べるようにする

さて、ここがこの画面でいちばん楽しいところです。

タグは「#助かりました」と「#ナイス頑張」のように、複数まとめて付けられるようにしたいですよね。

コンボボックスでも選べるのですが、スマホで使うことを考えると、指でポンポン押せるほうが気持ちいいです。

そこで、タグをチップ(丸いボタン)にして、ギャラリーで並べます

チップを並べる

まず見出しラベル lblTagsLabel(「タグ(複数選択可)」)を置きます。

プロパティ設定する式
Text“タグ(複数選択可)”
Height32
YtxtBody.Y + txtBody.Height + 5

その下にギャラリーを置いて、名前を galTags にします。

レイアウトは「縦」を選んでください

横に並べたいのに縦?と思いますよね。

縦ギャラリーには WrapCount という「1行に何個並べるか」の設定があって、これを使うと格子状に並べられます。

行の高さが TemplateSize で決まるので、全体の高さを計算しやすいのがうれしいところです。

プロパティ設定する式
ItemsChoices(Posts.Tags)
WrapCount3
TemplateSize44
HeightRoundUp(CountRows(Choices(Posts.Tags)) / 3, 0) * 44
TemplatePadding0
WidthParent.Width – 40
X20
YlblTagsLabel.Y + lblTagsLabel.Height

Choices を使うと、SharePointの選択肢列に登録した文言をそのまま持ってこられます。

タグを増やしたくなったらSharePoint側に足すだけでいいので、アプリを直さずに済みますね。

タグが増えても崩れないようにする

Height の式が、この画面でいちばん大事なところかもしれません。

//タグギャラリー galTags の Height(タグの数から必要な段数を出して高さを決める)
//1行に3個並べるので、3で割って切り上げた数が段数になる
RoundUp(CountRows(Choices(Posts.Tags)) / 3, 0) * 44

タグの数を1行あたりの数(3)で割って、RoundUp で切り上げると、必要な段数が出ます。

それに1段の高さ(44)を掛ければ、ちょうど収まる高さになるわけですね。

タグが5個なら2段で88、7個なら3段で132、という具合に自動で伸びます。

ここが落とし穴:数えているのはタグ?それとも投稿?
タグの数を数えるとき、こう書きたくなります。

//NG:これは「タグの数」ではない
CountRows(Posts.Tags)

//OK:選択肢の数を数える
CountRows(Choices(Posts.Tags))

Posts のうしろに列名を付けた Posts.Tags は、全部の投稿のTags列を縦に並べた表になります。つまり数えているのは投稿の件数であって、タグの選択肢の数ではないんですね。これに気づかないと、投稿が増えるたびにタグの並びが変わるという、なんとも不思議な動きになります。選択肢そのものを数えたいときは、Choices でくるむのを忘れずに。

タップで選択・解除できるようにする

ギャラリーの中にモダンボタンを1つ置いて、名前を btnTag に。

プロパティ設定する式
TextThisItem.Value
AppearanceIf(ThisItem.Value in colSelectedTags.Value, ButtonAppearance.Primary, ButtonAppearance.Transparent)
BasePaletteColorRGBA(0, 120, 212, 1)
Height36
WidthParent.TemplateWidth – 8
RadiusTopLeft ほか4隅18

角丸を18にしているので、丸っこいチップの見た目になります。

そして OnSelect に、選択と解除を切り替える式を入れます。

//タグボタン btnTag の OnSelect(押すたびに、選択したり外したりする)
//すでに選んでいれば外す、まだなら足す
If(
    ThisItem.Value in colSelectedTags.Value,
    RemoveIf(colSelectedTags, Value = ThisItem.Value),
    Collect(colSelectedTags, { Value: ThisItem.Value })
)

前回のいいね機能を作った人なら、この形に見覚えがあるはずです。

そう、まったく同じトグルの書き方ですね。

ここが個人的に一番おもしろい:また Value が出てきた
前回、コレクションの列名は Value にそろえる、という話をしました。ここでも同じです。colSelectedTags は「選んだタグを並べただけ」の入れ物なので、列名は Value。しかも今回は、うれしいおまけがあります。Choices が返してくるレコードも、もともと列名が Value なんですね。だから ThisItem.Value でタグ名が取れるし、そのままの形でコレクションに入れられる。そしてこのあと出てきますが、SharePointの選択肢列にも、この形のまま書き込めます。列名をそろえておくと、最初から最後まで詰め替えずに済む。気持ちいいですよ。

画像を添える

画像は「画像の追加」コントロールを使います。

まず見出しラベル lblImageLabel(「画像(任意・1枚)」)を、タグギャラリーの下に置きます。

プロパティ設定する式
Text“画像(任意・1枚)”
Height32
YgalTags.Y + galTags.Height + 5

タグが増えて galTags が伸びても、この見出しがちゃんと下がってくれます。

その下に「画像の追加」コントロール medAddImage と、プレビュー用の画像コントロール imgPreview を置きます。

コントロールプロパティ設定する式
medAddImageHeight48
medAddImageYlblImageLabel.Y + lblImageLabel.Height
imgPreviewImagemedAddImage.Media
imgPreviewVisible!IsBlank(medAddImage.Media)
imgPreviewWidthIf(IsBlank(medAddImage.Media), 0, 300)
imgPreviewHeightSelf.Width
imgPreviewYmedAddImage.Y + medAddImage.Height + 5

選んだ画像がその場で見えるので、間違えたときに気づけます。

じつはこのプレビュー、見た目のためだけではありません。

ここが落とし穴:AddMediaの中身は直接保存できない
「画像の追加」で選んだ画像は medAddImage.Media で参照できます。ところがこれを、そのままSharePointのイメージ列に Patch しようとすると、うまくいきません。そこで、いったん画像コントロールに表示させて、そのコントロール経由で保存します

//NG:選んだ画像をそのまま保存しようとする
Image: medAddImage.Media

//OK:プレビュー用の画像コントロール経由で保存する
Image: imgPreview.Image

つまり imgPreview は、プレビューであると同時に保存のための中継役でもあるわけですね。「なぜかエラーになる」で止まりやすいところなので、覚えておくと助かります。

投稿ボタンを作る

いよいよ保存です。

モダンボタンを画面の下に置いて、名前を btnSubmit にします。

プロパティ設定する式
TextIf(ctxIsSubmitting, “送信中…”, “投稿する”)
DisplayModeIf(ctxIsSubmitting || IsBlank(drpToUser.Selected) || IsBlank(txtBody.Text), DisplayMode.Disabled, DisplayMode.Edit)
Height44
WidthParent.Width – 40
X20
YParent.Height – 64 – 44 – 12

DisplayMode で、送信先とメッセージが空のあいだはボタンを押せないようにしています。

送信中も押せなくしているので、二重送信の防止にもなりますね。

保存の中身

OnSelect に入れる式です。

//投稿ボタン btnSubmit の OnSelect(入力内容をPostsリストに保存する)
//送信中フラグを立ててから登録し、終わったらタイムラインへ戻る
UpdateContext({ ctxIsSubmitting: true });

Patch(
    Posts,
    Defaults(Posts),
    {
        PostID: GUID(),
        Body: txtBody.Text,
        ToUser: drpToUser.Selected,
        Tags: colSelectedTags,
        Image: imgPreview.Image,
        LikeCount: 0,
        CommentCount: 0
    }
);

Notify("ありがとうを送りました", NotificationType.Success);
Navigate(scrTimeline, ScreenTransition.None)

PostID には GUID で作った文字列を入れています。

いいねやコメントが、この投稿を見つけるための目印ですね。

Tags には、さっき作った colSelectedTags をそのまま渡しています。

列名を Value でそろえておいたおかげで、詰め替えなしで入りました。

LikeCountCommentCount は、0 で始めておきます。

Patch の基本は、Patch関数の使い方 基本編もあわせてどうぞ。

ユーザー列に、あっさり入る理由

ここで ToUser の行を見てください。

//送信先。ドロップダウンで選んだ相手を、そのまま渡すだけ
ToUser: drpToUser.Selected

たったこれだけです。

SharePointのユーザー列は書き込みにクセがあることで有名なのですが、ここではあっさり通ります。

じつはこれ、第1回の下ごしらえが効いているんですね。

SharePointのユーザー列は、決まった顔ぶれのキーがそろったかたまりでないと受け取ってくれません。

‘@odata.type’ClaimsDisplayNameEmailJobTitlePictureDepartment の7つです。

そして第1回で作った glbMembers を思い出してください。

//第1回のおさらい:ユーザー列と同じ形にそろえてある
//前半で @odata.type / Claims / DisplayName / Email / JobTitle
//後半の AddColumns で Picture / Department  → あわせて7つ
Set(glbTemp, ForAll(..., { '@odata.type': ..., Claims: ..., DisplayName: ..., Email: ..., JobTitle: ... }));
Set(glbMembers, AddColumns(glbTemp, Picture, ..., Department, Blank()))

ちょうど7つ、そろっていますよね。

第1回で「ユーザー列が受け取れる形に翻訳している」と書いたのは、まさにこのことでした。

出どころのデータを最初から正しい形で作っておいたので、選んだレコードをそのまま流し込むだけで入るというわけです。

ここが個人的に一番おもしろい:値が効くのは Claims だけ
面白いのは、7つそろえておきながら、実際に効いている値は Claims だけというところ。Claims はログインIDのようなもので、これさえ渡せば「誰のことか」は伝わります。では残りの6つは何のためにあるかというと、キーの顔ぶれをそろえるためだけ。中身は空でもかまいません。なので、もし出どころのデータが違う形だったとしても、Patchのところで7つのキーを書きそろえてやれば通ります。値は Blank(空)でOK、キーだけは全部。ここ、覚えておくと助かりますよ。
ひとことメモ:フォームなら Claims だけでいい
これがフォーム(データカード)の場合は、Update{ Claims: … } と書くだけで通ります。データカードは最初からその列にひもづいているので、Power Apps側が形を知っているんですね。いっぽう Patch は、渡すかたまりを自分で用意する必要がある。「Patchのほうが自由だけど、そのぶん形は自分の責任」と考えると納得しやすいですよ。

ユーザー列への書き込みは方法がいくつかあるので、様々なアプローチでユーザー列に書き込む方法もどうぞ。

相手にメールで知らせる

最後に、ひと手間だけ足しましょう。

せっかくありがとうを送っても、相手がアプリを開かないと気づけませんよね。

そこで、投稿と同時にメールを飛ばします。

さきほどの Patch の直後、Notify の前に、こちらを差し込んでください。

//投稿ボタン btnSubmit の OnSelect(保存のあとに追記する)
//送信先へありがとうメールを飛ばす。タグを選んでいれば本文に添える
Office365Outlook.SendEmailV2(
    drpToUser.Selected.Email,
    User().FullName & "さんからありがとうが届きました",
    "<p><b>" & User().FullName & "</b> さんからありがとうが届きました。</p>"
    & "<p>" & txtBody.Text & "</p>"
    & If(CountRows(colSelectedTags) > 0,
        "<p>#" & Concat(colSelectedTags, Value, "  #") & "</p>",
        ""
    )
);

本文はHTMLで書けるので、名前を太字にしたり、タグを並べたりできます。

Concat でタグをつなげているところは、タイムラインでタグを表示したときと同じ書き方ですね。

ひとことメモ:誰から届くメールになる?
このメールは、アプリを操作している本人のメールボックスから送られます。つまり、ありがとうを送った人から相手へ、直接メールが届く形ですね。社内で使うぶんには、そのほうが温かみがあっていいと思います。「no-replyのような共通アドレスから送りたい」という場合は、Power Automateのフローに切り出す方法もありますよ。

なお、この機能を使うには Office365Outlook コネクタをアプリに追加しておく必要があります。

データの追加から「Office 365 Outlook」を選んでおいてください。

スクショを貼り付ける:完成した投稿画面。送信先ドロップダウン、メッセージ欄、タグのチップが3段に並び、画像プレビューが表示されている状態。

まとめ

今回はここまでにしましょう。

・ドロップダウンの表示列は ItemDisplayText。名前が出ないときは、まずOnStartを実行してみる。
・モダンのテキスト入力は TypePlaceholder.Text。クラシックとは名前が違う。
・タグの複数選択は、チップのギャラリーで自作すると気持ちいい。列名を Value でそろえておけば、そのまま保存できる。
・入力欄のYは、第2回と同じく「ひとつ上の下端から」積む。タグの数で高さが変わるので、決め打ちすると崩れる。
・タグの段数は RoundUp で計算して、ギャラリーの高さに反映させる。数えるのは Choices の中身。
・画像は、プレビュー用のコントロール経由で保存する。
・ユーザー列は、出どころのデータを同じ形で作っておけばそのまま渡せる。自分で組むときは、値は Claims だけでいいがキーは7つ全部そろえる。

これでアプリの中だけで、投稿してタイムラインで見て、いいねを押すところまで一周できるようになりました。

次の一手として、次回はコメント機能を作ります。

投稿をタップすると詳細画面が開いて、そこにコメントが並ぶ形ですね。

いいねで使った「関連するリストを投稿IDで探す」考え方が、そのまま活きてきますよ。

それでは、次の記事でお会いしましょう。

ABOUT US
七草あんこ
非IT系中間管理職やってます。社命によりoffice365を主軸とした業務改善プロジェクトメンバーに任命されたことでPowerAppsと出会えました。いまではビジネス・プライベートを問わず、欠かせないツールになっています。導入初期やアプリ作成時に遭遇した諸問題の解決法とサンプルアプリの作り方を紹介していきます。主にTwitterで情報収集しているので不明点など呟いているとお邪魔するかもしれません。