このチュートリアルでは、キャンバス アプリ データセット コード コンポーネントを作成し、デプロイして画面に追加し、Visual Studio Code を使用してコンポーネントをテストします。 コード コンポーネントには、並べ替え可能な列とフィルター可能な列を提供する、ページングされたスクロール可能なデータセット グリッドが表示されます。 また、インジケーター列を構成することで、特定の行を強調表示することもできます。 これはアプリ作成者からの一般的な要求であり、ネイティブ キャンバス アプリ コンポーネントを使用して実装するのが複雑な場合があります。 コード コンポーネントは、キャンバス アプリとモデル駆動型アプリの両方で動作するように記述できます。 ただし、このコンポーネントは、キャンバス アプリ内で特にターゲット用途に書き込まれます。
これらの要件に加えて、コード コンポーネントがベスト プラクティス ガイダンスに従っていることを確認します。
- Microsoft Fluent UI の使用
- デザインと実行時の両方でのコード コンポーネント ラベルのローカライズ
- 親キャンバス アプリ画面によって提供される幅と高さでコード コンポーネントがレンダリングされることを保証する
- 入力プロパティと外部アプリ要素を可能な限り使用してユーザー インターフェイスをカスタマイズするためのアプリ 作成者の考慮事項
注
開始する前に、 前提条件となるコンポーネントがすべてインストールされていることを確認してください。
Code
PowerApps-Samples/component-framework/CanvasGridControl/から完全なサンプルをダウンロードできます。
新しい pcfproj プロジェクトを作成する
コード コンポーネントに使用する新しいフォルダーを作成します。 たとえば、「
C:\repos\CanvasGrid」のように入力します。Visual Studio Code を開き、[>フォルダーを開き、
CanvasGridフォルダーを選択します。 Visual Studio Code のインストール中に Windows エクスプローラー拡張機能を追加した場合は、フォルダー内の [ コードで開く ] コンテキスト メニュー オプションを使用できます。 現在のディレクトリがその場所に設定されている場合は、コマンド プロンプトでcode .を使用して、任意のフォルダーを Visual Studio Code に読み込むこともできます。新しい Visual Studio Code PowerShell ターミナル (ターミナル>New ターミナル) 内 で、pac pcf init コマンドを使用して新しいコード コンポーネント プロジェクトを作成します。
pac pcf init --namespace SampleNamespace --name CanvasGrid --template datasetまたは短い形式を使用します。
pac pcf init -ns SampleNamespace -n CanvasGrid -t datasetこれにより、必要なモジュールを定義する
pcfprojなど、新しいpackages.jsonと関連ファイルが現在のフォルダーに追加されます。 必要なモジュールをインストールするには、 npm インストールを使用します。npm install注
メッセージが表示された場合は、
The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program.、特に node.js (LTS バージョンをお勧めします)、すべての前提条件がインストールされていることを確認します。
テンプレートには、さまざまな構成ファイルと共に index.ts ファイルが含まれています。 これはコード コンポーネントの開始点であり、「コンポーネントの 実装」で説明されているライフサイクル メソッドが含まれています。
Microsoft Fluent UI をインストールする
UI の作成には Microsoft Fluent UI と React を使用するため、これらを依存関係としてインストールする必要があります。 ターミナルで次のコマンドを使用します。
npm install react react-dom @fluentui/react
これにより、モジュールが packages.json に追加され、 node_modules フォルダーにインストールされます。 必要なすべてのモジュールはnode_modulesを使用して復元できるため、ソース管理にnpm installをコミットしません。
Microsoft Fluent UI の利点の 1 つは、一貫性があり 、アクセシビリティ の高い UI を提供することです。
設定する eslint
pac pcf init によって使用されるテンプレートは、eslint モジュールをプロジェクトにインストールし、.eslintrc.json ファイルを追加して構成します。
Eslint では、TypeScript と React のコーディング スタイルを構成する必要があります。 詳細情報: Linting - コード コンポーネントのベスト プラクティスとガイダンス。
データセットのプロパティを定義する
CanvasGrid\ControlManifest.Input.xml ファイルは、コード コンポーネントの動作を記述するメタデータを定義します。
コントロール属性には、コンポーネントの名前空間と名前が既に含まれています。
ヒント
XML は、属性が別々の行に表示されるように書式設定することで、読みやすくなる場合があります。 Visual Studio Code Marketplace で、選択した XML 書式設定ツールを検索してインストールします。 XML 書式設定拡張機能を検索します。
次の例は、読みやすくするために、別々の行に属性を使用して書式設定されています。
コード コンポーネントがバインドできるレコードを定義するには、既存のcontrol要素を置き換えて、data-set要素内に以下を追加する必要があります。
-
の前に
-
後の
<data-set name="sampleDataSet"
display-name-key="Dataset_Display_Key">
</data-set>
コード コンポーネントがキャンバス アプリに追加されると、レコード データ セット はデータ ソースにバインドされます。 プロパティ セットは、ユーザーがそのデータセットの列の 1 つを行の強調表示インジケーターとして使用するように構成する必要があることを示します。
ヒント
複数のデータセット要素を指定できます。 これは、1 つのデータセットを検索し、2 つ目を使用してレコードの一覧を表示する場合に便利です。
入力プロパティと出力プロパティの定義
データセットに加えて、次の 入力 プロパティを指定できます。
-
HighlightValue- アプリ 作成者が、HighlightIndicatorproperty-setとして定義された列と比較する値を提供できるようにします。 値が等しい場合は、行が強調表示されます。 -
HighlightColor- アプリ 作成者が行を強調表示するために使用する色を選択できるようにします。
ヒント
キャンバス アプリで使用するコード コンポーネントを作成する場合は、コード コンポーネントの一般的な側面のスタイルを設定するための入力プロパティを提供することをお勧めします。
入力プロパティに加えて、というFilteredRecordCountプロパティは、コード コンポーネント内でフィルター アクションが適用されたために行数が変更されたときに更新されます (また、OnChange イベントをトリガーします)。 これは、親アプリ内に No Rows Found メッセージを表示する場合に役立ちます。
注
将来、コード コンポーネントはカスタム イベントをサポートするため、汎用の OnChange イベントを使用するのではなく、特定のイベントを定義できます。
これら 3 つのプロパティを定義するには、CanvasGrid\ControlManifest.Input.xml ファイルの 要素のdata-setに次を追加します。
<property name="FilteredRecordCount"
display-name-key="FilteredRecordCount_Disp"
description-key="FilteredRecordCount_Desc"
of-type="Whole.None"
usage="output" />
<property name="HighlightValue"
display-name-key="HighlightValue_Disp"
description-key="HighlightValue_Desc"
of-type="SingleLine.Text"
usage="input"
required="true"/>
<property name="HighlightColor"
display-name-key="HighlightColor_Disp"
description-key="HighlightColor_Desc"
of-type="SingleLine.Text"
usage="input"
required="true"/>
このファイルを保存し、コマンド ラインで次のコマンドを使用します。
npm run build
注
npm run buildの実行中に次のようなエラーが発生した場合:
[2:48:57 PM] [build] Running ESLint...
[2:48:57 PM] [build] Failed:
[pcf-1065] [Error] ESLint validation error:
C:\repos\CanvasGrid\CanvasGrid\index.ts
2:47 error 'PropertyHelper' is not defined no-undef
index.tsファイルを開き、次を追加します: // eslint-disable-next-line no-undef、行のすぐ上に:
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
もう一度実行 npm run build 。
コンポーネントがビルドされると、次のことがわかります。
自動的に生成されたファイル
CanvasGrid\generated\ManifestTypes.d.tsがプロジェクトに追加されます。 これは、ControlManifest.Input.xmlからビルド プロセスの一部として生成され、入力/出力プロパティを操作するための型を提供します。ビルド出力が
outフォルダーに追加されます。bundle.jsは、ブラウザー内で実行されるトランスパイルされた JavaScript であり、ControlManifest.xmlはデプロイ時に使用されるControlManifest.Input.xmlファイルの再フォーマットされたバージョンです。注
generatedフォルダーとoutフォルダーの内容を直接変更しないでください。 ビルド プロセスの一部として上書きされます。
Grid Fluent UI React コンポーネントを追加する
コード コンポーネントで React を使用する場合は、 updateView メソッド内にレンダリングされる 1 つのルート コンポーネントが必要です。
CanvasGrid フォルダー内に、Grid.tsxという名前の新しい TypeScript ファイルを追加し、次の内容を追加します。
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Overlay } from '@fluentui/react/lib/Overlay';
import {
ScrollablePane,
ScrollbarVisibility
} from '@fluentui/react/lib/ScrollablePane';
import { Stack } from '@fluentui/react/lib/Stack';
import { Sticky } from '@fluentui/react/lib/Sticky';
import { StickyPositionType } from '@fluentui/react/lib/Sticky';
import { IObjectWithKey } from '@fluentui/react/lib/Selection';
import { IRenderFunction } from '@fluentui/react/lib/Utilities';
import * as React from 'react';
type DataSet = ComponentFramework.PropertyHelper.DataSetApi.EntityRecord & IObjectWithKey;
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
}
const onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => {
if (props && defaultRender) {
return (
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>
{defaultRender({
...props,
})}
</Sticky>
);
}
return null;
};
const onRenderItemColumn = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord,
index?: number,
column?: IColumn,
) => {
if (column && column.fieldName && item) {
return <>{item?.getFormattedValue(column.fieldName)}</>;
}
return <></>;
};
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);
const items: (DataSet | undefined)[] = React.useMemo(() => {
setIsLoading(false);
const sortedRecords: (DataSet | undefined)[] = sortedRecordIds.map((id) => {
const record = records[id];
return record;
});
return sortedRecords;
}, [records, sortedRecordIds, hasNextPage, setIsLoading]);
const gridColumns = React.useMemo(() => {
return columns
.filter((col) => !col.isHidden && col.order >= 0)
.sort((a, b) => a.order - b.order)
.map((col) => {
const sortOn = sorting && sorting.find((s) => s.name === col.name);
const filtered =
filtering &&
filtering.conditions &&
filtering.conditions.find((f) => f.attributeName == col.name);
return {
key: col.name,
name: col.displayName,
fieldName: col.name,
isSorted: sortOn != null,
isSortedDescending: sortOn?.sortDirection === 1,
isResizable: true,
isFiltered: filtered != null,
data: col,
} as IColumn;
});
}, [columns, sorting]);
const rootContainerStyle: React.CSSProperties = React.useMemo(() => {
return {
height: height,
width: width,
};
}, [width, height]);
return (
<Stack verticalFill grow style={rootContainerStyle}>
<Stack.Item grow style={{ position: 'relative', backgroundColor: 'white' }}>
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
></DetailsList>
</ScrollablePane>
{(itemsLoading || isComponentLoading) && <Overlay />}
</Stack.Item>
</Stack>
);
});
Grid.displayName = 'Grid';
注
このファイルには、React で使用される XML スタイル構文をサポートする TypeScript ファイルである拡張子 tsx があります。 ビルド プロセスによって標準の JavaScript にコンパイルされます。
グリッドデザインノート
このセクションには、 Grid.tsx コンポーネントの設計に関する共通点が含まれています。
機能コンポーネントです
これは React 関数コンポーネントですが、 クラス コンポーネントである場合もあります。 これは、好みのコーディング スタイルに基づいています。 クラス コンポーネントと機能コンポーネントは、同じプロジェクトで混在させることもできます。 関数コンポーネントとクラス コンポーネントの両方で、React で使用される tsx XML スタイル構文が使用されます。 詳細情報: 関数コンポーネントとクラス コンポーネント
bundle.js サイズを最小限に抑える
「パスベースのインポートを使用して ChoiceGroup Fluent UI コンポーネントをインポートする場合は、次の方法ではなく、これを使用します。」
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
Stack
} from "@fluentui/react";
このコードでは、次のコードを使用します。
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Stack } from '@fluentui/react/lib/Stack';
これにより、バンドル サイズが小さくなり、容量要件が低くなり、実行時のパフォーマンスが向上します。
別の方法として、ツリーシェーキングを使用します。
destructuring の割り当て
このコード:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
分割代入を使用します。 この方法では、使用するたびに props. を前に付けるのではなく、プロパティからレンダリングに必要な属性を抽出します。
また、このコードでは React.memo を使用して関数コンポーネントをラップし、入力プロパティのいずれかが変更されない限りレンダリングされないようにします。
React.useMemo の使用
React.useMemo は、入力プロパティが options または configuration 変更されたときにのみ、作成された項目配列が変更されるようにするために、いくつかの場所で使用されます。 これは、子コンポーネントの不要なレンダリングを減らす関数コンポーネントのベスト プラクティスです。
その他の注意事項:
- 後でページング コントロールを使用してフッター要素を追加するため、
DetailsList内のStackがラップされます。 - Fluent UI
Stickyコンポーネントは、(onRenderDetailsHeaderを使用して) ヘッダー列をラップするために使用され、グリッドをスクロールするときに表示されたままにします。 -
setKeyは、現在のページが変更されたときにスクロール位置と選択範囲がリセットされるように、DetailsListと共にinitialFocusedIndexに渡されます。 -
onRenderItemColumn関数は、セルの内容をレンダリングするために使用されます。 行項目を受け取り、getFormattedValue を使用して列の表示値を返しています。 getValue メソッドは、代替レンダリングを提供するために使用できる値を返します。getFormattedValueの利点は、日付や参照などの文字列以外の型の列に対して書式設定された文字列が含まれていることです。 -
gridColumnsブロックは、データセット コンテキストによって提供される列のオブジェクト図形を、DetailsList列のプロパティで想定される図形にマッピングします。これは React.useMemo フックにラップされているため、出力はcolumnsまたはsortingのプロパティが変更されたときにのみ変更されます。 コード コンポーネント コンテキストによって提供される並べ替えとフィルター処理の詳細が、マップされている列と一致する列に並べ替えアイコンとフィルター アイコンを表示できます。 列は、column.orderプロパティを使用して並べ替え、アプリ 作成者によって定義されたグリッド上の正しい順序であることを確認します。 - あなたは、React コンポーネントで
isComponentLoadingの内部状態を管理しています。 これは、ユーザーが並べ替えとフィルター処理の操作を選択すると、sortedRecordIdsが更新され、状態がリセットされるまで、グリッドを視覚的な手掛かりとしてグレー表示できるためです。itemsLoadingと呼ばれる追加の入力プロパティがあり、データセット コンテキストによって提供される dataset.loading プロパティにマップされます。 どちらのフラグも、Fluent UIOverlayコンポーネントを使用して実装される視覚的な読み込みキューを制御するために使用されます。
index.ts の更新
次の手順では、定義されているプロパティと一致するように index.ts ファイルに変更を加えます。 Grid.tsx.
import ステートメントを追加してアイコンを初期化する
index.tsのヘッダーに、既存のインポートを次のように置き換えます。
-
の前に
-
後の
import {IInputs, IOutputs} from './generated/ManifestTypes';
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
注
このコードでは Fluent UI アイコン セットを使用するため、 initializeIcons のインポートが必要です。
initializeIconsを呼び出して、テスト ハーネス内のアイコンを読み込みます。 キャンバス アプリ内では、既に初期化されています。
CanvasGrid クラスにフィールドを追加する
CanvasGrid クラスに次のフィールドを追加します。
-
の前に
-
後の
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor() {
}
init メソッドを更新する
次を initに追加します。
-
の前に
-
後の
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary,
container: HTMLDivElement): void {
// Add control initialization code
}
init関数は、コード コンポーネントがアプリ画面で最初に初期化されるときに呼び出されます。 次への参照を格納します。
-
notifyOutputChanged: これは、いずれかのプロパティが変更されたことをキャンバス アプリに通知するために呼び出したコールバックです。 -
container:これは、コード コンポーネント UI を追加する DOM 要素です。 -
resources:これは、現在のユーザーの言語でローカライズされた文字列を取得するために使用されます。
context.mode.trackContainerResize(true)) を使用して、コード コンポーネントのサイズが変更されたときにupdateViewが呼び出されるようにします。
注
現時点では、コード コンポーネントがテスト ハーネス内で実行されているかどうかを判断する方法はありません。
control-dimensions
div要素がインジケーターとして存在するかどうかを検出する必要があります。
updateView メソッドを更新する
次を updateViewに追加します。
-
の前に
-
後の
public updateView(context: ComponentFramework.Context<IInputs>): void {
// Add code to update control view
}
次のことがわかります。
-
React.createElement を呼び出し、
init関数内で受け取った DOM コンテナーへの参照を渡します。 -
GridコンポーネントはGrid.tsx内で定義され、ファイルの先頭にインポートされます。 -
allocatedWidthとallocatedHeightは、関数内でinitの呼び出しを行ったので、親コンテキストが変更されるたびに (たとえば、アプリがコード コンポーネントのサイズを変更したり、全画面表示モードに入ったり)、提供されます。 -
updatedProperties 配列に
dataset文字列が含まれている場合に、表示する新しい行があるかどうかを検出できます。 - テスト ハーネスでは、
updatedProperties配列は設定されないため、isTestHarness関数で設定したinitフラグを使用して、sortedRecordIdとrecordsを設定するロジックをショートサーキットできます。 データの再レンダリングが必要でない限り、子コンポーネントに渡されるときにこれらを変更しないように、現在の値への参照は変更されるまで保持します。 - コード コンポーネントは表示されるページの状態を維持するため、親コンテキストがレコードを最初のページにリセットすると、ページ番号がリセットされます。
hasPreviousPageが false の場合、最初のページに戻ったときにわかります。
destroy メソッドを更新する
最後に、コード コンポーネントが破棄されたときに整理する必要があります。
-
の前に
-
後の
public destroy(): void {
// Add code to cleanup control if necessary
}
テスト ハーネスを起動する
すべてのファイルが保存されていることを確認し、ターミナルで次のコマンドを使用してください。
npm start watch
サンプルの 3 つのレコードを使用して設定されたコード コンポーネント グリッドを表示するには、幅と高さを設定する必要があります。 その後、Dataverse から CSV ファイルにレコードのセットをエクスポートし、データ 入力>Records パネルを使用してテスト ハーネスに読み込むことができます。
.csv ファイルに保存して使用できる、コンマ区切りのサンプル データを次に示します。
address1_city,address1_country,address1_stateorprovince,address1_line1,address1_postalcode,telephone1,emailaddress1,firstname,fullname,jobtitle,lastname
Seattle,U.S.,WA,7842 Ygnacio Valley Road,12150,555-0112,someone_m@example.com,Thomas,Thomas Andersen (sample),Purchasing Manager,Andersen (sample)
Renton,U.S.,WA,7165 Brock Lane,61795,555-0109,someone_j@example.com,Jim,Jim Glynn (sample),Owner,Glynn (sample)
Snohomish,U.S.,WA,7230 Berrellesa Street,78800,555-0106,someone_g@example.com,Robert,Robert Lyon (sample),Owner,Lyon (sample)
Seattle,U.S.,WA,931 Corte De Luna,79465,555-0111,someone_l@example.com,Susan,Susan Burk (sample),Owner,Burk (sample)
Seattle,U.S.,WA,7765 Sunsine Drive,11910,555-0110,someone_k@example.com,Patrick,Patrick Sands (sample),Owner,Sands (sample)
Seattle,U.S.,WA,4948 West Th St,73683,555-0108,someone_i@example.com,Rene,Rene Valdes (sample),Purchasing Assistant,Valdes (sample)
Redmond,U.S.,WA,7723 Firestone Drive,32147,555-0107,someone_h@example.com,Paul,Paul Cannon (sample),Purchasing Assistant,Cannon (sample)
Issaquah,U.S.,WA,989 Caravelle Ct,33597,555-0105,someone_f@example.com,Scott,Scott Konersmann (sample),Purchasing Manager,Konersmann (sample)
Issaquah,U.S.,WA,7691 Benedict Ct.,57065,555-0104,someone_e@example.com,Sidney,Sidney Higa (sample),Owner,Higa (sample)
Monroe,U.S.,WA,3747 Likins Avenue,37925,555-0103,someone_d@example.com,Maria,Maria Campbell (sample),Purchasing Manager,Campbell (sample)
Duvall,U.S.,WA,5086 Nottingham Place,16982,555-0102,someone_c@example.com,Nancy,Nancy Anderson (sample),Purchasing Assistant,Anderson (sample)
Issaquah,U.S.,WA,5979 El Pueblo,23382,555-0101,someone_b@example.com,Susanna,Susanna Stubberod (sample),Purchasing Manager,Stubberod (sample)
Redmond,U.S.,WA,249 Alexander Pl.,86372,555-0100,someone_a@example.com,Yvonne,Yvonne McKay (sample),Purchasing Manager,McKay (sample)
注
読み込まれた CSV ファイルに指定した列に関係なく、テスト ハーネスに表示される列は 1 つだけです。 これは、テスト ハーネスが定義されている場合にのみ property-set を表示するためです。
property-setが定義されていない場合は、読み込まれた CSV ファイル内のすべての列が設定されます。
行の選択を追加する
Fluent UI DetailsList では既定でレコードを選択できますが、選択したレコードはコード コンポーネントの出力にリンクされません。 関連するコンポーネントを更新できるように、キャンバス アプリ内で選択したレコードを反映するには、 Selected プロパティと SelectedItems プロパティが必要です。 この例では、一度に 1 つの項目のみを選択できるため、 SelectedItems には 1 つのレコードのみが含まれます。
Grid.tsx インポートの更新
Grid.tsx内のインポートに次を追加します。
-
の前に
-
後の
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Overlay } from '@fluentui/react/lib/Overlay';
import {
ScrollablePane,
ScrollbarVisibility
} from '@fluentui/react/lib/ScrollablePane';
import { Stack } from '@fluentui/react/lib/Stack';
import { Sticky } from '@fluentui/react/lib/Sticky';
import { StickyPositionType } from '@fluentui/react/lib/Sticky';
import { IObjectWithKey } from '@fluentui/react/lib/Selection';
import { IRenderFunction } from '@fluentui/react/lib/Utilities';
import * as React from 'react';
GridProps に setSelectedRecords を追加する
GridProps インターフェイスに、Grid.tsx内に次のコードを追加します。
-
の前に
-
後の
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
}
setSelectedRecords プロパティを Grid に追加する
Grid.tsx関数コンポーネント内で、propsのデストラクチャを更新して、新しい prop setSelectedRecordsを追加します。
-
の前に
-
後の
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
その真下に、次のように追加します。
const forceUpdate = useForceUpdate();
const onSelectionChanged = React.useCallback(() => {
const items = selection.getItems() as DataSet[];
const selected = selection.getSelectedIndices().map((index: number) => {
const item: DataSet | undefined = items[index];
return item && items[index].getRecordId();
});
setSelectedRecords(selected);
forceUpdate();
}, [forceUpdate]);
const selection: Selection = useConst(() => {
return new Selection({
selectionMode: SelectionMode.single,
onSelectionChanged: onSelectionChanged,
});
});
React.useCallback と useConst フックを使用すると、これらの値がレンダリング間で変更されず、不要な子コンポーネントのレンダリングが発生します。
useForceUpdate フックを使用すると、選択が更新されると、更新された選択数を反映するようにコンポーネントが再レンダリングされます。
DetailsList に選択範囲を追加する
選択の状態を維持するために作成された selection オブジェクトは、 DetailsList コンポーネントに渡されます。
-
の前に
-
後の
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
></DetailsList>
setSelectedRecords コールバックを定義する
setSelectedRecords内で新しいindex.tsコールバックを定義し、Grid コンポーネントに渡す必要があります。
CanvasGridクラスの先頭付近に、次のコードを追加します。
-
の前に
-
後の
export class CanvasGrid
implements ComponentFramework.StandardControl<IInputs, IOutputs>
{
notifyOutputChanged: () => void;
container: HTMLDivElement;
context: ComponentFramework.Context<IInputs>;
sortedRecordsIds: string[] = [];
resources: ComponentFramework.Resources;
isTestHarness: boolean;
records: {
[id: string]: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord;
};
currentPage = 1;
filteredRecordCount?: number;
setSelectedRecordIds の呼び出しは、SelectedItemsおよびSelectedを参照する他のコンポーネントが更新されるように、選択範囲が変更されたことをキャンバス アプリに通知します。
入力プロパティに新しいコールバックを追加する
最後に、Grid メソッドのupdateView コンポーネントの入力プロパティに新しいコールバックを追加します。
-
の前に
-
後の
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
}),
this.container
);
OnSelect イベントの呼び出し
キャンバス アプリには、ギャラリーまたはグリッドで項目の選択が呼び出された場合 (シェブロン アイコンの選択など)、 OnSelect イベントが発生するパターンがあります。 このパターンは、データセットの openDatasetItem メソッドを使用して実装できます。
GridProps インターフェイスに onNavigate を追加する
前と同様に、Grid内のGridProps インターフェイスに次のコードを追加して、Grid.tsx コンポーネントにコールバック プロパティを追加します。
-
の前に
-
後の
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<
string,
ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
}
グリッドのプロパティに onNavigate を追加する
ここでも、新しい小道具を小道具の分解に追加する必要があります。
-
の前に
-
後の
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
} = props;
DetailsList に onItemInvoked を追加する
DetailListには、onItemInvokedと呼ばれるコールバック プロパティがあり、次のようにコールバックを渡します。
-
の前に
-
後の
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
></DetailsList>
index.tsに onNavigate メソッドを追加する
onNavigate メソッドを、index.ts メソッドのすぐ下のsetSelectedRecordsに追加します。
onNavigate = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
): void => {
if (item) {
this.context.parameters.records.openDatasetItem(item.getNamedReference());
}
};
これにより、単にデータセット レコードに対して openDatasetItem メソッドが呼び出され、コード コンポーネントによって OnSelect イベントが発生します。 メソッドは、コード コンポーネントの現在の インスタンスにバインドするためのthisとして定義されます。
このコールバックを、Grid メソッド内のupdateView コンポーネントのプロパティに渡す必要があります。
-
の前に
-
後の
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
}),
this.container
);
すべてのファイルを保存すると、テスト ハーネスが再読み込みされます。
Ctrl
+
Shift
+
I (またはF12) を使用し、ファイルを開く (Ctrl + P) を使用してindex.tsを検索し、onNavigate メソッド内にブレークポイントを配置できます。
EnterがDetailsListコールバックを呼び出すので、行をダブルクリック (またはカーソル キーで強調表示してonNavigateを押すと) ブレークポイントにヒットします。
関数が_this関数として定義され、のインスタンスをキャプチャするために JavaScript クロージャーにトランスパイルされているため、thisへの参照があります。
ローカライズの追加
先に進む前に、ページング、並べ替え、フィルター処理などのメッセージにローカライズされた文字列を使用できるように、コード コンポーネントにリソース文字列を追加する必要があります。 新しいファイル CanvasGrid\strings\CanvasGrid.1033.resx 追加し、Visual Studio リソース エディターまたは Visual Studio Code と拡張機能を使用して、次のように入力します。
| 名前 | 価値 |
|---|---|
Records_Dataset_Display |
レコード |
FilteredRecordCount_Disp |
フィルター処理されたレコード数 |
FilteredRecordCount_Desc |
フィルター処理後のレコードの数 |
HighlightValue_Disp |
値の強調表示 |
HighlightValue_Desc |
行を強調表示する必要があることを示す値 |
HighlightColor_Disp |
強調表示の色 |
HighlightColor_Desc |
使用して行を強調表示する色 |
HighlightIndicator_Disp |
インジケーター フィールドの強調表示 |
HighlightIndicator_Desc |
強調表示値と比較するフィールドの名前に設定します。 |
Label_Grid_Footer |
ページ {0} ({1} 選択済み) |
Label_SortAZ |
A から Z |
Label_SortZA |
Z から A |
Label_DoesNotContainData |
データを含まない |
Label_ShowFullScreen |
全画面表示を表示する |
ヒント
resxファイルを直接編集することはお勧めしません。 代わりに、Visual Studio のリソース エディターまたは Visual Studio Code の拡張機能を使用します。 Visual Studio Code 拡張機能の検索: Visual Studio Marketplace で resx エディターを検索する
このファイルのデータは、メモ帳で CanvasGrid.1033.resx ファイルを開き、以下の XML コンテンツをコピーして設定することもできます。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Records_Dataset_Display" xml:space="preserve">
<value>Records</value>
</data>
<data name="FilteredRecordCount_Disp" xml:space="preserve">
<value>Filtered Record Count</value>
</data>
<data name="FilteredRecordCount_Desc" xml:space="preserve">
<value>The number of records after filtering</value>
</data>
<data name="HighlightValue_Disp" xml:space="preserve">
<value>Highlight Value</value>
</data>
<data name="HighlightValue_Desc" xml:space="preserve">
<value>The value to indicate a row should be highlighted</value>
</data>
<data name="HighlightColor_Disp" xml:space="preserve">
<value>Highlight Color</value>
</data>
<data name="HighlightColor_Desc" xml:space="preserve">
<value>The color to highlight a row using</value>
</data>
<data name="HighlightIndicator_Disp" xml:space="preserve">
<value>Highlight Indicator Field</value>
</data>
<data name="HighlightIndicator_Desc" xml:space="preserve">
<value>Set to the name of the field to compare against the Highlight Value</value>
</data>
<data name="Label_Grid_Footer" xml:space="preserve">
<value>Page {0} ({1} Selected)</value>
</data>
<data name="Label_SortAZ" xml:space="preserve">
<value>A to Z</value>
</data>
<data name="Label_SortZA" xml:space="preserve">
<value>Z to A</value>
</data>
<data name="Label_DoesNotContainData" xml:space="preserve">
<value>Does not contain data</value>
</data>
<data name="Label_ShowFullScreen" xml:space="preserve">
<value>Show Full Screen</value>
</data>
</root>
input
/
outputプロパティとdatasetおよび関連付けられたproperty-setのリソース文字列があります。 これらは、メーカーのブラウザー言語に基づいて、デザイン時に Power Apps Studio で使用されます。
getString を使用して実行時に取得できるラベル文字列を追加することもできます。 詳細情報: ローカライズ API コンポーネントの実装。
この新しいリソース ファイルを、ControlManifest.Input.xml要素内のresources ファイルに追加します。
-
の前に
-
後の
<resources>
<code path="index.ts"
order="1" />
</resources>
列の並べ替えとフィルター処理を追加する
ユーザーがグリッド列ヘッダーを使用して並べ替えとフィルター処理を行えるようにするには、Fluent UI DetailList を使用して、列ヘッダーにコンテキスト メニューを簡単に追加できます。
GridProps に onSort と onFilter を追加する
まず、onSort内のonFilter インターフェイスにGridPropsとGrid.tsxを追加して、並べ替えとフィルター処理のためのコールバック関数を提供します。
-
の前に
-
後の
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<
string,
ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
onNavigate: (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
) => void;
}
onSort、onFilter、およびリソースを props に追加する
続いて、これらの新しいプロップと resources の参照を (ソートやフィルターをする目的でローカライズされたラベルを取得できるように)、プロップスのデストラクションに追加します:
-
の前に
-
後の
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
} = props;
ContextualMenu コンポーネントのインポート
Fluent UI で提供されるGrid.tsx コンポーネントを使用できるように、ContextualMenuの上部にいくつかのインポートを追加する必要があります。 パスベースのインポートを使用して、バンドルのサイズを小さくできます。
import { ContextualMenu, DirectionalHint, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu';
コンテキスト メニューのレンダリング機能を追加する
次に、コンテキスト メニューのレンダリング機能を行のすぐ下に Grid.tsx 追加します
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);:
-
の前に
-
後の
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);
次のことがわかります。
-
contextualMenuProps状態は、Fluent UIContextualMenuコンポーネントを使用してレンダリングされるコンテキスト メニューの可視性を制御します。 - このコードは、フィールドにデータが含まれていない値のみを表示する単純なフィルターを提供します。 これを拡張して、追加のフィルター処理を提供できます。
- このコードでは、
resources.getStringを使用して、ローカライズ可能なコンテキスト メニューにラベルを表示します。 -
React.useCallbackフックは、React.useMemoと同様に、依存値が変更されたときにのみコールバックが変更されるようにします。 これにより、子コンポーネントのレンダリングが最適化されます。
列の選択イベントとコンテキスト メニュー イベントに新しいコンテキスト メニュー関数を追加する
これらの新しいコンテキスト メニュー関数を列の選択イベントとコンテキスト メニュー イベントに追加します。
const gridColumnsを更新して、onColumnContextMenuコールバックとonColumnClickコールバックを追加します。
-
の前に
-
後の
const gridColumns = React.useMemo(() => {
return columns
.filter((col) => !col.isHidden && col.order >= 0)
.sort((a, b) => a.order - b.order)
.map((col) => {
const sortOn = sorting && sorting.find((s) => s.name === col.name);
const filtered =
filtering &&
filtering.conditions &&
filtering.conditions.find((f) => f.attributeName == col.name);
return {
key: col.name,
name: col.displayName,
fieldName: col.name,
isSorted: sortOn != null,
isSortedDescending: sortOn?.sortDirection === 1,
isResizable: true,
isFiltered: filtered != null,
data: col,
} as IColumn;
});
}, [columns, sorting]);
レンダリングされた出力にコンテキスト メニューを追加する
コンテキスト メニューを表示するには、レンダリングされた出力にコンテキスト メニューを追加する必要があります。 返された出力の DetailsList コンポーネントのすぐ下に次のコードを追加します。
-
の前に
-
後の
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
onItemInvoked={onNavigate}
></DetailsList>
</ScrollablePane>
onSort 関数と OnFilter 関数を追加する
並べ替えとフィルター処理の UI を追加したので、コード コンポーネントにバインドされているレコードに対して並べ替えとフィルター処理を実際に実行するために、 index.ts にコールバックを追加する必要があります。
index.ts関数のすぐ下のonNavigateに次のコードを追加します。
onSort = (name: string, desc: boolean): void => {
const sorting = this.context.parameters.records.sorting;
while (sorting.length > 0) {
sorting.pop();
}
this.context.parameters.records.sorting.push({
name: name,
sortDirection: desc ? 1 : 0,
});
this.context.parameters.records.refresh();
};
onFilter = (name: string, filter: boolean): void => {
const filtering = this.context.parameters.records.filtering;
if (filter) {
filtering.setFilter({
conditions: [
{
attributeName: name,
conditionOperator: 12, // Does not contain Data
},
],
} as ComponentFramework.PropertyHelper.DataSetApi.FilterExpression);
} else {
filtering.clearFilter();
}
this.context.parameters.records.refresh();
};
次のことがわかります。
- 並べ替えとフィルターは、 並べ替え と フィルター処理 のプロパティを使用してデータセットに適用されます。
- 並べ替え列を変更する場合は、置換する並べ替え配列自体ではなく、pop を使用して既存の並べ替え定義を削除する必要があります。
- 更新は、並べ替えとフィルター処理を適用した後で呼び出す必要があります。 フィルターと並べ替えが同時に適用される場合は、更新を 1 回だけ呼び出す必要があります。
OnSort コールバックと OnFilter コールバックを Grid レンダリングに追加する
最後に、次の 2 つのコールバックを Grid レンダリング呼び出しに渡すことができます。
-
の前に
-
後の
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
onNavigate: this.onNavigate,
}),
this.container
);
注
この時点で、テスト ハーネスは並べ替えとフィルター処理のサポートを提供していないため、テスト ハーネスを使用してテストできなくなります。 後で、 pac pcf プッシュ を使用してデプロイし、テスト用のキャンバス アプリに追加できます。 必要に応じて、その手順に進んで、キャンバス アプリ内のコード コンポーネントがどのように表示されるかを確認できます。
FilteredRecordCount 出力プロパティを更新する
グリッドでレコードを内部でフィルター処理できるようになったため、表示されるレコードの数をキャンバス アプリに報告することが重要です。 これは、"レコードなし" 型のメッセージを表示できるようにするためです。
ヒント
これはコード コンポーネント内で内部的に実装することもできますが、アプリ 作成者の柔軟性が向上するため、キャンバス アプリに任せるユーザー インターフェイスをできるだけ多くすることをお勧めします。
FilteredRecordCountで ControlManifest.Input.xml という出力プロパティが既に定義されています。 フィルター処理が行われ、フィルター処理されたレコードが読み込まれると、updateView関数が dataset 配列内の文字列で呼び出されます。 レコードの数が変更された場合は、 notifyOutputChanged を呼び出して、 FilteredRecordCount プロパティを使用するすべてのコントロールを更新する必要があることをキャンバス アプリが認識できるようにする必要があります。
updateViewのindex.tsメソッド内で、ReactDOM.renderのすぐ上と下のallocatedHeightの上に次のコードを追加します。
-
の前に
-
後の
const allocatedHeight = parseInt(
context.mode.allocatedHeight as unknown as string
);
FilteredRecordCount を getOutputs に追加する
これにより、前に定義したコード コンポーネント クラスの filteredRecordCount が、受信した新しいデータと異なる場合に更新されます。
notifyOutputChangedが呼び出されたら、getOutputsが呼び出されたときに値が返されるようにする必要があるため、getOutputs メソッドを次のように更新します。
-
の前に
-
後の
public getOutputs(): IOutputs {
return {};
}
グリッドにページングを追加する
大規模なデータセットの場合、キャンバス アプリは複数のページにレコードを分割します。 ページ ナビゲーション コントロールを表示するフッターを追加できます。 各ボタンは、インポートする必要がある Fluent UI IconButtonを使用してレンダリングされます。
IconButton をインポートに追加
これを Grid.tsx内のインポートに追加します。
import { IconButton } from '@fluentui/react/lib/Button';
stringFormat 関数を追加する
次の手順では、リソース文字列 ("Page {0} ({1} Selected)") からページ インジケーター ラベルの形式を読み込み、単純な stringFormat 関数を使用して書式を読み込む機能を追加します。 この関数は、便宜上、個別のファイル内にあり、コンポーネント間で共有される場合があります。
このチュートリアルでは、Grid.tsxのすぐ下の type DataSet ... の上部に追加します。
function stringFormat(template: string, ...args: string[]): string {
for (const k in args) {
template = template.replace("{" + k + "}", args[k]);
}
return template;
}
ページング ボタンの追加
Grid.tsxで、Stack.Itemを含む既存のStack.Itemの下に次のScrollablePaneを追加します。
-
の前に
-
後の
return (
<Stack verticalFill grow style={rootContainerStyle}>
<Stack.Item grow style={{ position: 'relative', backgroundColor: 'white' }}>
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
onItemInvoked={onNavigate}
></DetailsList>
{contextualMenuProps && <ContextualMenu {...contextualMenuProps} />}
</ScrollablePane>
{(itemsLoading || isComponentLoading) && <Overlay />}
</Stack.Item>
</Stack>
);
次のことがわかります。
-
Stackにより、フッターがDetailsListの下に積み重ねられます。grow属性は、グリッドが展開されて使用可能な領域を埋めるようにするために使用されます。 - 前の手順で追加した
"Page {0} ({1} Selected)"関数を使用して、リソース文字列 (stringFormat) と書式からページ インジケーター ラベルの形式を読み込みます。 - ページング
altのアクセシビリティのためにIconButtonsテキストを指定できます。 - フッターのスタイルは、コード コンポーネントに追加された CSS ファイルを参照する CSS クラス名を使用して同じように適用できます。
ページングをサポートするためにコールバックプロップを追加する
次に、不足している loadFirstPage、 loadNextPage、および loadPreviousPage コールバック プロパティを追加する必要があります。
GridProps インターフェイスに、次のコードを追加します。
-
の前に
-
後の
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
onNavigate: (item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord) => void;
onSort: (name: string, desc: boolean) => void;
onFilter: (name: string, filtered: boolean) => void;
}
グリッドに新しいページング プロパティを追加する
これらの新しいプロップを、プロップの destructuring に追加します:
-
の前に
-
後の
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
onSort,
onFilter,
resources,
} = props;
index.tsにコールバックを追加する
これらのコールバックを、index.ts メソッドの下のonFilterに追加します。
loadFirstPage = (): void => {
this.currentPage = 1;
this.context.parameters.records.paging.loadExactPage(1);
};
loadNextPage = (): void => {
this.currentPage++;
this.context.parameters.records.paging.loadExactPage(this.currentPage);
};
loadPreviousPage = (): void => {
this.currentPage--;
this.context.parameters.records.paging.loadExactPage(this.currentPage);
};
次に、 Grid レンダリング呼び出しを更新して、これらのコールバックを含めます。
-
の前に
-
後の
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
onNavigate: this.onNavigate,
onSort: this.onSort,
onFilter: this.onFilter,
}),
this.container
);
全画面表示のサポートを追加する
コード コンポーネントは、全画面表示モードで表示する機能を提供します。 これは、画面サイズが小さい場合や、キャンバス アプリ画面内のコード コンポーネントの領域が限られている場合に特に便利です。
Fluent UI Link コンポーネントのインポート
全画面表示モードを起動するには、Fluent UI Link コンポーネントを使用できます。
Grid.tsxの上部にあるインポートに追加します。
import { Link } from '@fluentui/react/lib/Link';
リンク コントロールを追加する
全画面表示リンクを追加するには、ページング コントロールを含む既存の Stack に以下を追加します。
注
これをネストされた Stack に必ず追加してください。ルート Stack には追加しないでください。
-
の前に
-
後の
<Stack horizontal style={{ width: '100%', paddingLeft: 8, paddingRight: 8 }}>
<IconButton
alt="First Page"
iconProps={{ iconName: 'Rewind' }}
disabled={!hasPreviousPage}
onClick={loadFirstPage}
/>
<IconButton
alt="Previous Page"
iconProps={{ iconName: 'Previous' }}
disabled={!hasPreviousPage}
onClick={loadPreviousPage}
/>
<Stack.Item align="center">
{stringFormat(
resources.getString('Label_Grid_Footer'),
currentPage.toString(),
selection.getSelectedCount().toString(),
)}
</Stack.Item>
<IconButton
alt="Next Page"
iconProps={{ iconName: 'Next' }}
disabled={!hasNextPage}
onClick={loadNextPage}
/>
</Stack>
次のことがわかります。
- このコードでは、リソースを使用して、ローカライズをサポートするラベルを表示します。
- 全画面表示モードが開いている場合、リンクは表示されません。 代わりに、親アプリ コンテキストによって閉じるアイコンが自動的にレンダリングされます。
全画面表示をサポートするプロパティを GridProps に追加する
onFullScreenとisFullScreenのプロパティをGridProps内のGrid.tsx インターフェイスに追加して、並べ替えとフィルター処理のためのコールバック関数を提供します。
-
の前に
-
後の
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
onNavigate: (item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord) => void;
onSort: (name: string, desc: boolean) => void;
onFilter: (name: string, filtered: boolean) => void;
loadFirstPage: () => void;
loadNextPage: () => void;
loadPreviousPage: () => void;
}
全画面表示をサポートするプロパティをグリッドに追加する
これらの新しいプロップを、プロップの destructuring に追加します:
-
の前に
-
後の
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
onSort,
onFilter,
resources,
loadFirstPage,
loadNextPage,
loadPreviousPage,
} = props;
グリッドの全画面表示をサポートするようにindex.tsを更新する
これらの新しいプロパティを提供するには、 index.ts内で、 loadPreviousPageの下に次のコールバック メソッドを追加します。
onFullScreen = (): void => {
this.context.mode.setFullScreen(true);
};
setFullScreen を呼び出すと、コード コンポーネントが全画面表示モードを開き、allocatedHeight メソッドでallocatedWidthを呼び出したため、それに応じてtrackContainerResize(true)とinitを調整します。 全画面表示モードが開くと、 updateView が呼び出され、コンポーネントのレンダリングが新しいサイズで更新されます。
updatedPropertiesには、発生している遷移に応じて、fullscreen_openまたはfullscreen_closeが含まれます。
全画面表示モードの状態を格納するには、isFullScreen内のCanvasGrid クラスに新しいindex.ts フィールドを追加します。
-
の前に
-
後の
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
notifyOutputChanged: () => void;
container: HTMLDivElement;
context: ComponentFramework.Context<IInputs>;
sortedRecordsIds: string[] = [];
resources: ComponentFramework.Resources;
isTestHarness: boolean;
records: {
[id: string]: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord;
};
currentPage = 1;
filteredRecordCount?: number;
updateView を編集して状態を追跡する
状態を追跡するために、 updateView メソッドに次のコードを追加します。
-
の前に
-
後の
public updateView(context: ComponentFramework.Context<IInputs>): void {
const dataset = context.parameters.records;
const paging = context.parameters.records.paging;
const datasetChanged = context.updatedProperties.indexOf("dataset") > -1;
const resetPaging =
datasetChanged &&
!dataset.loading &&
!dataset.paging.hasPreviousPage &&
this.currentPage !== 1;
if (resetPaging) {
this.currentPage = 1;
}
コールバックと isFullScreen フィールドを渡して Grid にレンダリングする
これで、コールバックフィールドと isFullScreen フィールドを Grid レンダリングプロパティに渡すことができます。
-
の前に
-
後の
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
onNavigate: this.onNavigate,
onSort: this.onSort,
onFilter: this.onFilter,
loadFirstPage: this.loadFirstPage,
loadNextPage: this.loadNextPage,
loadPreviousPage: this.loadPreviousPage,
}),
this.container
);
行の強調表示
これで、条件付き行の強調表示機能を追加する準備ができました。
HighlightValueとHighlightColor入力プロパティ、およびHighlightIndicatorproperty-setは既に定義されています。
property-setを使用すると、作成者は、HighlightValueで指定した値と比較するために使用するフィールドを選択できます。
強調表示をサポートするために型をインポートする
DetailsListでのカスタム行のレンダリングには、追加のインポートが必要です。
@fluentui/react/lib/DetailsListには既にいくつかの型があるため、IDetailsListPropsでその import ステートメントにIDetailsRowStyles、DetailsRow、Grid.tsxを追加します。
-
の前に
-
後の
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps
} from '@fluentui/react/lib/DetailsList';
次に、 const rootContainerStyle ブロックのすぐ下に次を追加して、カスタム行レンダラーを作成します。
const onRenderRow: IDetailsListProps['onRenderRow'] = (props) => {
const customStyles: Partial<IDetailsRowStyles> = {};
if (props && props.item) {
const item = props.item as DataSet | undefined;
if (highlightColor && highlightValue && item?.getValue('HighlightIndicator') == highlightValue) {
customStyles.root = { backgroundColor: highlightColor };
}
return <DetailsRow {...props} styles={customStyles} />;
}
return null;
};
次のことがわかります。
- 次を使用して、
HighlightIndicatorエイリアスを使用して、作成者が選択したフィールドの値を取得できます。
item?.getValue('HighlightIndicator')。 -
HighlightIndicatorフィールドの値がコード コンポーネントの入力プロパティによって提供されるhighlightValueの値と一致する場合は、行に背景色を追加できます。 -
DetailsRowコンポーネントは、定義した列をレンダリングするためにDetailsListによって使用されます。 背景色以外の動作を変更する必要はありません。
強調表示をサポートする追加のプロパティを追加する
highlightColor内のレンダリングによって提供されるhighlightValueとupdateViewの追加のプロパティをいくつか追加します。 すでに GridProps のインターフェイスに追加しているため、あとはプロップの destructuring に追加するだけです:
-
の前に
-
後の
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
onSort,
onFilter,
resources,
loadFirstPage,
loadNextPage,
loadPreviousPage,
onFullScreen,
isFullScreen,
} = props;
OnRenderRow メソッドを DetailsList に追加する
onRenderRow メソッドをDetailsListプロパティに渡します。
-
の前に
-
後の
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
onItemInvoked={onNavigate}
></DetailsList>
コンポーネントをデプロイして構成する
すべての機能を実装したので、テストのためにコード コンポーネントを Microsoft Dataverse にデプロイする必要があります。
Dataverse 環境内で、
samplesのプレフィックスを持つパブリッシャーが作成されていることを確認します。
これは、以下の pac pcf push 呼び出しでパブリッシャー プレフィックス パラメーターを更新した場合でも、あなた自身のパブリッシャーである可能性があります。 詳細: ソリューション発行元を作成する。
パブリッシャーを保存したら、コンパイル済みのコード コンポーネントをプッシュできるように、環境に対して CLI を承認する準備ができました。 コマンド ラインで、次のコマンドを使用します。
pac auth create --url https://myorg.crm.dynamics.commyorg.crm.dynamics.comを独自の Dataverse 環境の URL に置き換えます。 メッセージが表示されたら、管理者/カスタマイザー ユーザーでサインインします。 Dataverse にコード コンポーネントをデプロイするには、これらのユーザー ロールによって提供される特権が必要です。コード コンポーネントをデプロイするには、次を使用します。
pac pcf push --publisher-prefix samples注
エラーが発生した場合は、
Missing required tool: MSBuild.exe/dotnet.exe. Please add MSBuild.exe/dotnet.exe in Path environment variable or use 'Developer Command Prompt for VS、 Visual Studio 2019 for Windows & Mac または Build Tools for Visual Studio 2019 をインストールする必要があります。前提条件の説明に従って、必ず '.NET ビルド ツール' ワークロードを選択してください。完了すると、このプロセスによって環境内に PowerAppTools_samples という名前の小さな一時的なソリューションが作成され、
CanvasGridコード コンポーネントがこのソリューションに追加されます。 必要に応じて、後でコード コンポーネントを独自のソリューションに移動できます。 詳細情報: コード コンポーネント アプリケーション ライフサイクル管理 (ALM)。
キャンバス アプリ内でコード コンポーネントを使用するには、使用している環境で キャンバス アプリの Power Apps コンポーネント フレームワーク を有効にする必要があります。
a. 管理センター (admin.powerplatform.microsoft.com) を開き、環境に移動します。 b。 設定>Product>Features に移動します。 キャンバス アプリの Power Apps コンポーネント フレームワークがオンになっていることを確認します。
タブレット レイアウトを使用して新しいキャンバス アプリを作成します。
[ 挿入 ]パネルで、[ その他のコンポーネントを取得]を選択します。
[コンポーネントのインポート] ウィンドウの [コード] タブを選択します。
CanvasGridコンポーネントを選択します。インポート を選択します。 コード コンポーネントは、[挿入] パネルの [コード コンポーネント] の下に表示されます。
CanvasGridコンポーネントを画面にドラッグし、Microsoft Dataverse のContactsテーブルにバインドします。プロパティ パネルを使用して、
CanvasGridコード コンポーネントで次のプロパティを設定します。-
値の強調表示 =
1- これは、レコードが非アクティブな場合statecode持つ値です。 -
強調表示の色 =
#FDE7E9- レコードが非アクティブな場合に使用する色です。 -
HighlightIndicator="statecode"- これは比較対象のフィールドです。 これは、[データ] セクションの [詳細設定] パネルに表示されます。
-
値の強調表示 =
新しい
TextInputコンポーネントを追加し、txtSearch名前を付けます。CanvasGrid.ItemsされるようにSearch(Contacts,txtSearch.Text,"fullname")プロパティを更新します。テキスト入力を入力すると、連絡先がグリッドでフィルター処理されていることがわかります。
新しい テキスト ラベル を追加し、テキストを "レコードが見つかりません" に設定します。 キャンバス グリッドの上にラベルを配置します。
Text ラベルの Visible プロパティを
CanvasGrid1.FilteredRecordCount=0に設定します。
つまり、 txtSearch 値に一致するレコードがない場合、またはレコードを返さないコンテキスト メニューを使用して列フィルターが適用された場合 (たとえば、 Full Name にデータが含まれていない場合)、ラベルが表示されます。
表示フォームを追加します ([挿入] パネルの [入力] グループから)。
フォーム
DataSourceをContactsテーブルに設定し、 いくつかのフォーム フィールドを追加します。フォーム
ItemプロパティをCanvasGrid1.Selectedに設定します。グリッド上の項目を選択すると、選択した項目がフォームに表示されます。
という名前のキャンバス アプリに新しい
scrDetailsを追加します。前の画面からフォームをコピーし、新しい画面に貼り付けます。
CanvasGrid1.OnSelectプロパティをNavigate(scrDetails)に設定します。グリッド行選択アクションを呼び出すと、アプリが項目が選択された状態で 2 番目の画面に移動することがわかります。
デプロイ後のデバッグ
Ctrl+Shift+Iを使用して開発者ツールを開くと、キャンバス アプリ内で実行中のコード コンポーネントを簡単にデバッグできます。
Ctrl+Pを選択し、「Grid.tsxまたはIndex.ts」と入力します。 ブレークポイントを設定し、コードをステップ実行できます。
コンポーネントをさらに変更する必要がある場合は、毎回デプロイする必要はありません。 代わりに、「 コード コンポーネントのデバッグ 」で説明されている手法を使用して Fiddler AutoResponder を 作成し、 npm start watch の実行中にローカル ファイル システムからファイルを読み込みます。
AutoResponder は次のようになります。
REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.CanvasGrid[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\CanvasGrid\out\controls\CanvasGrid\${folder}\${fname}
また、フィルターを有効にして、 Access-Control-Allow-Origin ヘッダーを追加する必要もあります。 詳細: Microsoft Dataverse に展開した後のデバッグ。
AutoResponder ファイルを取得するには、ブラウザー セッションで空のキャッシュとハード更新を行う必要があります。 読み込まれたら、Fiddler がキャッシュ コントロール ヘッダーをファイルに追加してキャッシュされないようにするため、ブラウザーを更新するだけです。
変更に満足したら、マニフェストでパッチ バージョンをインクリメントし、 pac pcf プッシュを使用して再デプロイできます。
ここまでは、開発ビルドをデプロイしました。これは最適化されておらず、実行時に実行速度が低下します。
を編集して、CanvasGrid.pcfprojを使用して最適化されたビルドをデプロイすることを選択できます。
OutputPathの下に、次のコードを追加します。<PcfBuildMode>production</PcfBuildMode>
-
の前に
-
後の
<PropertyGroup>
<Name>CanvasGrid</Name>
<ProjectGuid>a670bba8-e0ae-49ed-8cd2-73917bace346</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
</PropertyGroup>
関連資料
Microsoft Power Platform によるアプリケーション ライフサイクル管理 (ALM)
Power Apps コンポーネント フレームワーク API リファレンス
最初のコンポーネントを作成する
コード コンポーネントのデバッグ