グリッドボタン
グリッドツールバーのボタンは、ユーザーが MainGrid または SubGridでレコードとどのように操作するかを制御します。ボタンは Buttons レンダリングフラグメント内に配置され、自動的にグリッドのツールバーに表示されます。
ボタンカテゴリ
グリッドボタンは、記録操作の扱い方に基づいて3つのカテゴリーに分かれます。
- ダイアログボタン — ダイアログ内でフォームを開き、ページを離れずにレコードを作成または編集できます。
- ナビゲーションボタン — レコードの作成や編集のために別のページURLに移動します。
- アクションボタン — レコードの削除、リンク、解除などの操作を直接実行できます。
適切なアプローチの選択
ユーザーがどれだけコンテキストを必要とし、どれだけ新しい状態が必要かに応じてボタンの形状を選びましょう。以下の3つのバケットは典型的なケースをカバーしています。
- ダイアログ(インライン) — フォームが単一のダイアログに収まり、ユーザーが現在のページに留まる恩恵がある場合(例:アカウントレコードに連絡先を追加し、保存されていない編集履歴を失わない場合)
NewRecordGridButton/OpenRecordGridButtonを使用します。Behavior="GridActionBehavior.WithGridContext"(デフォルト)と組み合わせて、新しいレコードのセーブを親コンテキストのトランザクションコミットに折りたたみます。 - ナビゲーション(全ページ) — 作成や編集がダイアログでは十分に提供できない豊かな表面を必要とする場合、
NavigateNewRecordGridButton/NavigateOpenRecordGridButtonを活用してください:多くのフィールド、複数のタブ、関連レコード、添付ファイル、共有可能なURL。ルートは独自のRecordContextを持っているため、親グリッドはフォームの複雑さを認識する必要はありません。 - ウィザード(多段階ダイアログ) — フォームのフィールドが十分で画面が混雑しているように感じるが、入力が相互依存しているため、ページを分割してもナビゲーションコストが見合う場合に
NewRecordGridButton FormType="FormType.WizardForm"を使います。共通の形状:1ページ目は身元や分類、2ページ目は1ページ目の選択に依存する詳細を表します。
セーブフローおよび振る舞いモード
すべてのダイアログベースのグリッドボタン(NewRecordGridButton、 OpenRecordGridButton、M2Mのリンク/アンリンクペア)は、基盤となるDataverseコールの発生を制御する Behavior パラメータを受け付けています。この2つの値は、実質的に異なるリクエストシーケンスにマッピングされます:
行動 = 即時
ダイアログの保存ボタンは、 ExecuteMultipleAsync経由でDataverseに直接作成/更新/関連付け/解除リクエストを送信し、グリッドを更新してからダイアログを閉じます。周囲の MainContext 関係者や RecordContext (もしあれば)は変化を感じていません。すでにコミットしています。
- ユーザーはダイアログで「保存」をクリックします。
- ダイアログは検証を行い、その後1つ以上の
OrganizationRequestをExecuteMultipleAsyncに送ります。 - サーバーが戻ってきます。グリッドが更新されます。会話は終わる。
このタイミングを選べばいい:ダイアログが単独で(ページレベルのセーブボタンが不要)、またはユーザーが本当に各行レベルのアクションを独立したコミットにしたい場合。ここでの各セーブは独立しており、複数行のワークフローの一部完了はサーバー上で残ります。
Behavior = WithGridContext(デフォルト)
ダイアログの保存ボタンは、グリッドの保留キュー(Blazorでは_rowsToCreate / _rowsToUpdate 、 useGridContext()ではReact対応)でリクエストを段階化し、ダイアログを閉じます。実際のDataverseコールは、周囲の MainContext / RecordContextの保存ボタンが押されるまで発動しません。
- ユーザーがダイアログで保存→レコード(または更新/アソシエイト/解除)をグリッド上でキューに置きます。
- 会話が閉じる。ページの親コンテキストが
IsDirty=trueに切り替わり、ページレベルの保存ボタンが有効になります。 - ユーザーはページレベルの保存をクリックし→、キューに待たれたグリッド変更+親レコードの更新+他のすべての子孫の保留中のリクエストが1つの
ExecuteMultipleAsyncにまとめられます。 - サーバーが戻ってきます。ページが更新されます。列が空き、
IsDirtyは元に戻る。
このタイミングを選んでください:ページには親のRecordContext保存ボタンがあるので、ユーザーは「ページ全体を保存する」という意味論を期待し、部分的なコミット(親は保存済み、子行は保存しない)は、すべてをまとめてロールバックするよりも悪い結果になります。そのため、これがデフォルトです。
デフォルトはWithGridContextです
WithGridContextはすべてのダイアログベースのボタンのデフォルトであり、Immediatelyはオプトインです。もしグリッドが親コンテキスト(上にレコードがないスタンドアロンのリストページ)の外にある場合、キューは排水する場所がなく、ダイアログは自動的に「即時セマンティクス」に戻ってしまいます。
NewRecordGridButton
新しいレコードを作成するためのダイアログフォームを開きます。Razorコンポーネントをフォームとして表示する指定の型パラメータ TForm が必要です。
Locationを使ってダイアログの表示位置を調整します:DialogLocation.Center(デフォルト)またはDialogLocation.Right(サイドパネル)。
標準フォーム(FormType.Form)かマルチステップウィザード(FormType.WizardForm)のどちらかを選ぶためにFormTypeを使いましょう。
Behaviorを使ってレコード作成のタイミングを制御します:GridActionBehavior.ImmediatelyはすぐにDataverseに保存し、GridActionBehavior.WithGridContext(デフォルト)は親コンテキストがコミットされるまで保存を延期します。
{/* 標準形の中央対話 */}
<NewRecordGridButton>
<NewContactForm />
</NewRecordGridButton>
{/* 標準形状のサイドパネル */}
<NewRecordGridButton location="panel">
<NewContactForm />
</NewRecordGridButton>
{/* ウィザード形態の中心対話 — 子供は <WizardRecordPage>
要素(FormTypeプロップは不要で、ボタンが自動検出します)。 */}
<NewRecordGridButton location="center">
<WizardRecordPage>
<TextEdit columnName="firstname" />
<TextEdit columnName="lastname" />
</WizardRecordPage>
<WizardRecordPage>
<MoneyEdit columnName="annualincome" />
</WizardRecordPage>
</NewRecordGridButton>
{/* すぐにセーブし、延期せずに */}
<NewRecordGridButton behavior="immediately">
<NewContactForm />
</NewRecordGridButton><!-- 標準形の中央対話 -->
<NewRecordGridButton TForm="NewContactForm" />
<!-- 標準形状のサイドパネル -->
<NewRecordGridButton TForm="NewContactForm"
Location="DialogLocation.Right" />
<!-- ウィザードフォームの中央ダイアログ -->
<NewRecordGridButton TForm="NewContactForm"
Location="DialogLocation.Center"
FormType="FormType.WizardForm" />
<!-- すぐにセーブし、延期せずに -->
<NewRecordGridButton TForm="NewContactForm"
Behavior="GridActionBehavior.Immediately" />標準形の例
TForm型パラメータは、フォームレイアウトを定義するRazorコンポーネントを指定します。標準フォームとは、エディタコンポーネントを含むRazorコンポーネントのことです。タブやセクション、必要なレイアウトなど、すべてを含めることができます。
// ContactForm.tsx 編集
<TabList>
<Tab value="general">一般</Tab>
<Tab value="other">その他</Tab>
</TabList>
{activeTab === 'general' && (
<>
<TextEdit columnName="firstname" />
<TextEdit columnName="middlename" />
<TextEdit columnName="lastname" />
</>
)}
{activeTab === 'other' && (
<>
<MoneyEdit columnName="annualincome" />
<TextEdit columnName="telephone1" type="tel" />
</>
)}<!-- 編集:ContactForm.razorを -->
<FluentTabs Style="width: 100%">
<FluentTab Label="一般">
<TextEdit ColumnName="firstname" />
<TextEdit ColumnName="middlename" />
<TextEdit ColumnName="lastname" />
</FluentTab>
<FluentTab Label="その他">
<MoneyEdit ColumnName="annualincome" />
<TextEdit ColumnName="telephone1"
TextFieldType="TextFieldType.Tel" />
</FluentTab>
</FluentTabs>ウィザードフォームの例
ウィザードフォームは作成プロセスを複数のステップに分割します。各ステップを WizardRecordPage コンポーネントで定義します。ウィザードモードを有効にするにはボタンの FormType="FormType.WizardForm" を使います。
// NewContactForm.tsx
<WizardRecordPage>
<TextEdit columnName="firstname" />
<TextEdit columnName="middlename" />
<TextEdit columnName="lastname" />
</WizardRecordPage>
<WizardRecordPage>
<MoneyEdit columnName="annualincome" />
<TextEdit columnName="telephone1" type="tel" />
</WizardRecordPage><!-- NewContactForm.razor(新しい連絡先フォーム).razor -->
<WizardRecordPage>
<TextEdit ColumnName="firstname" />
<TextEdit ColumnName="middlename" />
<TextEdit ColumnName="lastname" />
</WizardRecordPage>
<WizardRecordPage>
<MoneyEdit ColumnName="annualincome" />
<TextEdit ColumnName="telephone1"
TextFieldType="TextFieldType.Tel" />
</WizardRecordPage>注記
ウィザードフォームを使う場合は、
NewRecordGridButtonにFormType="FormType.WizardForm"を設定してください。ウィザードは「戻る/次へ」ナビゲーションを表示し、各ページを検証してから進みます。
ページごとの検証
各 WizardRecordPage デフォルトは ForceSuccessfulValidationBeforeSave="true"で、ウィザードの「次」ボタンは進行前にアクティブなページの検証器を実行し、必要欄が空または無効であれば遷移をキャンセルします。オプションフィールドのみを含むページに false 設定し、ユーザーがスキップできるようにします。最終ページの「終了」ボタンはこのフラグに関係なく常に有効です。サーバー側で作成を拒否するしかありません。
<WizardRecordPage>
{/* 必須項目については、入力が完了するまで進めません */}
<TextEdit columnName="firstname" />
<TextEdit columnName="lastname" />
</WizardRecordPage>
<WizardRecordPage forceSuccessfulValidationBeforeSave={false}>
{/* オプションフィールド — 検証エラーがあってもユーザーが進められる */}
<MoneyEdit columnName="annualincome" />
</WizardRecordPage><WizardRecordPage>
<!-- 必須項目については、入力が完了するまで進めません -->
<TextEdit ColumnName="firstname" />
<TextEdit ColumnName="lastname" />
</WizardRecordPage>
<WizardRecordPage ForceSuccessfulValidationBeforeSave="false">
<!-- オプションフィールド — 検証エラーがあってもユーザーが進められる -->
<MoneyEdit ColumnName="annualincome" />
</WizardRecordPage>ページをまたいだ共有記録
1つのNewRecordGridButton内のすべてのWizardRecordPageコンポーネントは同じ基盤となるTableRecordオブジェクトを共有しており、ページ1のfirstname編集とページ2のannualincome編集は、最後に単一の作成ペイロードにまとめられます。ページごとのRecordContextは同じレコードインスタンスにバインドされるため、ステップ間のナビゲーションは進行中の編集を保持します。
OpenRecordGridButton
選択したレコードを編集するためのダイアログフォームを開きます。 NewRecordGridButtonと同様に、 TForm 型パラメータが必要です。複数のレコードを選択すると、フォームは共有フィールドを表示し、選択したすべてのレコードに変更を適用します。
グリッド内の行をダブルクリックすると自動的に編集ボタンが作動します。グリッド上の AllowNavigateOnRowDoubleClick="false" を設定してダブルクリックハンドラーを抑制してください。
デフォルトでは、テーブルのプライマリーネーム列は各行でハイパーリンクとして表示され、リンクをクリックするとダブルクリックと同じ編集アクションがトリガーされます。グリッド上の AllowNavigateOnPrimaryNameClick="false" を設定してハイパーリンクを抑制し、プライマリーネームセルをプレーンテキストとして表示してください。ハイパーリンクは OpenRecordGridButton や NavigateOpenRecordGridButton が登録された場合にのみ表示されるため、編集ボタンのないグリッドは影響を受けません。
{/* 中央ダイアログ(デフォルト) */}
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
{/* サイドパネル */}
<OpenRecordGridButton location="panel">
<EditContactForm />
</OpenRecordGridButton>
{/* すぐにセーブしてください */}
<OpenRecordGridButton behavior="immediately">
<EditContactForm />
</OpenRecordGridButton>
{/* プライマリーネームのハイパーリンクを抑制してください(行ダブルクリックでも編集がトリガーされます) */}
<MainGrid tableName="contact" allowNavigateOnPrimaryNameClick={false}>
<GridButtons>
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
</GridButtons>
</MainGrid>
{/* 行のダブルクリックハンドラを抑制する(プライマリーネームのハイパーリンクは依然として有効) */}
<MainGrid tableName="contact" allowOpenOnRowDoubleClick={false}>
<GridButtons>
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
</GridButtons>
</MainGrid><!-- 中央ダイアログ(デフォルト) -->
<OpenRecordGridButton TForm="EditContactForm" />
<!-- サイドパネル -->
<OpenRecordGridButton TForm="EditContactForm"
Location="DialogLocation.Right" />
<!-- すぐにセーブしてください -->
<OpenRecordGridButton TForm="EditContactForm"
Behavior="GridActionBehavior.Immediately" />
<!-- プライマリーネームのハイパーリンクを抑制してください(行ダブルクリックでも編集がトリガーされます) -->
<MainGrid TableName="contact"
AllowNavigateOnPrimaryNameClick="false">
<Buttons>
<OpenRecordGridButton TForm="EditContactForm" />
</Buttons>
</MainGrid>
<!-- 行のダブルクリックハンドラを抑制する(プライマリーネームのハイパーリンクは依然として有効) -->
<MainGrid TableName="contact"
AllowNavigateOnRowDoubleClick="false">
<Buttons>
<OpenRecordGridButton TForm="EditContactForm" />
</Buttons>
</MainGrid>NavigateNewRecordGridButton
URLに移動して新しいレコードを作成します。 Url パラメータをターゲットページに設定します。 SubGridで使用する場合、親レコードの関係コンテキストは自動的にクエリ文字列パラメータとして付加されます。
OnClickコールバックを使ってグリッドコンテキストに基づいて動的にURLを設定します。これは、URLが選択したビューのテーブルに依存するマルチテーブルグリッドで有用です。
{/* 静的URL */}
<NavigateNewRecordGridButton url="/contacts/new" />
{/* 選択したビューに基づく動的URL */}
<NavigateNewRecordGridButton
url="/contacts/new"
onClick={(ctx) => {
switch (ctx.gridContext.selectedView?.tableName) {
case 'contact': ctx.url = '/contacts/new'; break;
case 'account': ctx.url = '/accounts/new'; break;
default: throw new Error('不明表');
}
}}
/><!-- 静的URL -->
<NavigateNewRecordGridButton Url="/contacts/new" />
<!-- 選択したビューに基づく動的URL -->
<NavigateNewRecordGridButton OnClick="OnNewClick" />
@code {
private async Task OnNewClick(NavigateGridButtonContext ctx)
{
ctx.Url = ctx.GridContext.SelectedView.TableName switch
{
"contact" => "/contacts/new",
"account" => "/accounts/new",
_ => throw new Exception("不明表"),
};
}
}NavigateOpenRecordGridButton
選択したレコードを編集するためにURLにナビゲートします。 Url パラメータは、選択したレコードのIDのプレースホルダーとして {0} をサポートしています。
{/* URLはurlFor を使ってレコードごとに構築します */}
<NavigateOpenRecordGridButton
urlFor={(record) => `/contacts/edit?contactId=${record.id}`}
/>
{/* 選択したビューに基づく動的URL */}
<NavigateOpenRecordGridButton
urlFor={(record, ctx) => {
switch (ctx.selectedView?.tableName) {
case 'contact': return `/contacts/edit?contactId=${record.id}`;
case 'account': return `/accounts/edit?accountId=${record.id}`;
default: throw new Error('不明表');
}
}}
/><!-- {0}は選択したレコードのIDに置き換えられます -->
<NavigateOpenRecordGridButton Url="/contacts/edit?contactId={0}" />
<!-- 動的URL -->
<NavigateOpenRecordGridButton OnClick="OnEditClick" />
@code {
private async Task OnEditClick(NavigateGridButtonContext ctx)
{
ctx.Url = ctx.GridContext.SelectedView.TableName switch
{
"contact" => "/contacts/edit?contactId={0}",
"account" => "/accounts/edit?accountId={0}",
_ => throw new Exception("不明表"),
};
}
}NavigateRecordGridButton
カスタムラベル、アイコン、URLを備えた汎用ナビゲーションボタンです。これは、新規や編集パターンに合わないカスタムナビゲーションアクションに使うべきです。
<NavigateRecordGridButton
title="詳細を見る"
url="/records/details?id={0}"
buttonEnabledBehavior={GridButtonBehavior.OnlyOneSelected}
/><NavigateRecordGridButton Title="詳細を見る"
Url="/records/details?id={0}"
ButtonEnabledBehavior="GridButtonBehavior.WhenOneSelected" />DeleteRecordGridButton
確認を求めるプロンプトの後に選択したレコードを削除します。 Mode を使って、レコードを BulkOperationMode.Individually (進捗とともに一つずつ)削除するか、あるいは一括で削除するかを制御します。
{/* 進行状況に応じて一つずつ削除してください */}
<DeleteRecordGridButton mode="individually" />
{/* 一括で削除する */}
<DeleteRecordGridButton mode="bulk" />
{/* すぐに削除し、延期せずに */}
<DeleteRecordGridButton behavior="immediately" /><!-- 進行状況に応じて一つずつ削除してください -->
<DeleteRecordGridButton Mode="BulkOperationMode.Individually" />
<!-- 一括で削除する -->
<DeleteRecordGridButton Mode="BulkOperationMode.Batch" />
<!-- すぐに削除し、延期せずに -->
<DeleteRecordGridButton Behavior="GridActionBehavior.Immediately" />LinkExistingRecordGridButton
検索ダイアログを開き、多対多関係を通じて既存レコードを見つけて関連付けます。N:N関係の SubGrid にのみ適用されます。
<LinkExistingRecordGridButton />
{/* すぐにアソシエイト */}
<LinkExistingRecordGridButton behavior="immediately" /><LinkExistingRecordGridButton />
<!-- すぐにアソシエイト -->
<LinkExistingRecordGridButton Behavior="GridActionBehavior.Immediately" />UnlinkExistingRecordGridButton
確認を求めた後、選択された記録を多対多の関係から切り離します。N:N関係の SubGrid にのみ適用されます。
<UnlinkExistingRecordGridButton />
{/* すぐに解離してください */}
<UnlinkExistingRecordGridButton behavior="immediately" /><UnlinkExistingRecordGridButton />
<!-- すぐに解離してください -->
<UnlinkExistingRecordGridButton Behavior="GridActionBehavior.Immediately" />グリッドボタン
現在のGridContextを受け取る完全カスタムボタンで、OnClickコールバック機能を備えています。これを使ってカスタムツールバーのアクションを実装してください。
<SubGrid relationshipName="contact_customer_accounts">
<GridButtons>
<NewRecordGridButton>
<NewContactForm />
</NewRecordGridButton>
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
<DeleteRecordGridButton />
{/* カスタムボタン */}
<GridButton
label="輸出"
icon={<ArrowDownload20Regular />}
enabledBehavior={GridButtonBehavior.OneOrMoreSelected}
onClick={async (ctx) => {
const selectedRecords = ctx.selectedRecords;
// カスタムロジック — エクスポート、印刷、メール送信など。
}}
/>
</GridButtons>
</SubGrid><SubGrid RelationshipName="contact_customer_accounts">
<Buttons>
<NewRecordGridButton TForm="NewContactForm" />
<OpenRecordGridButton TForm="EditContactForm" />
<DeleteRecordGridButton />
<!-- カスタムボタン -->
<GridButton Label="輸出"
Icon="@(new Icons.Regular.Size20.ArrowDownload())"
IsButtonEnabled="DefaultGridButtonBehavior.GetBehavior(GridButtonBehavior.WhenOneOrMoreSelected)"
OnClick="OnExportClick" />
</Buttons>
</SubGrid>
@code {
private async Task OnExportClick(GridContext context)
{
var selectedRecords = context.SelectedRecords;
// カスタムロジック — エクスポート、印刷、メール送信など。
}
}一般的なパラメータ
Behavior— 操作が即時実行されるか(GridActionBehavior.Immediately)か、親コンテキストが保存されるまで延期されるか(GridActionBehavior.WithGridContext)制御。Mode— 削除/リンク/アンリンクボタンは、進行状況フィードバック付きで個別に実行するか、単一のバッチリクエストで実行するかを制御します。IsButtonEnabled/IsButtonVisible— 現在の行選択に基づいてボタンの状態を制御する述語。
GridButton クラス
パラメータ
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
Appearance | Appearance? | Stealth | |
Enabled | bool? | True | |
Icon | Icon? | ||
IsButtonEnabled | Func<IEnumerable<GridRowContext>, bool> | ||
IsButtonVisible | Func<IEnumerable<GridRowContext>, bool> | ||
IsOpenRecordButton | bool | False | |
Label | string? | ||
Tooltip | string? |
AppearanceEnabledIconIsButtonEnabledIsButtonVisibleIsOpenRecordButtonLabelTooltipイベント
名称 | 種類 | 概要 |
|---|---|---|
OnClick | EventCallback<GridContext> |
OnClick