チュートリアル: モデル駆動型アプリのフィールド コンポーネントを作成する

このチュートリアルでは、モデル駆動型アプリの field コンポーネントを作成し、デプロイ、構成、Visual Studio Code を使用してフォーム上でコンポーネントのテストを行います。 このコードコンポーネントは、フォームに一連の選択肢を表示し、各選択肢の値の横にアイコンを表示します。 このコンポーネントは、選択列の定義 (メタデータ) やフィールド レベルのセキュリティなど、モデル駆動型アプリの高度な機能の一部を使用します。

これらに加えて、コード コンポーネントがベスト プラクティスのガイダンスに従っていることも確認する必要があります。

  1. Microsoft Fluent UI を使用して一貫性とアクセシビリティを管理します
  2. 設計時と実行時の両方でコード コンポーネント ラベルをローカライズします
  3. 再利用性を高めるために、コード コンポーネントがメタデータ駆動型であることを確認してください
  4. コード コンポーネントがフォーム ファクターと利用可能な幅に応じて表示されることを確認し、スペースが限られている場合にはアイコン付きのコンパクトなドロップダウンを表示します

ChoicesPicker コンポーネント。

開始する前に、すべてのコンポーネントの前提条件を満たしていることを確認してください。

コード

完全なサンプルは、こちら からダウンロードできます。

新しい pcfproj プロジェクトを作成します

新規 pcfproj の作成:

  1. コード コンポーネントを保持する新しいフォルダを作成します。 たとえば、C:\repos\ChoicesPicker などとします。

  2. Visual Studio Code を開き、ファイル > フォルダを開く に移動し、上記手順で作成済みの ChoicesPicker フォルダを選択します。 Visual Studio Code のインストール時にウィンドウズ エクスプローラーの拡張機能を追加した場合は、フォルダ内のコンテキスト メニュー オプション コードで開く を使用することもできます。 また、現在のディレクトリがこの場所に設定されている場合、コマンド プロンプトで code . を使って任意のフォルダを Visual Studio Code に追加することもできます。

  3. 新たな Visual Studio Code PowerShell ターミナル (ターミナル > 新規ターミナル) 内で、次のコマンドを使用して、新しいコードコンポーネント プロジェクトを作成します:

    pac pcf init --namespace SampleNamespace --name ChoicesPicker --template field
    

    または、次の短いフォームを使用します:

    pac pcf init -ns SampleNamespace -n ChoicesPicker -t field
    

これにより、必要なモジュールを定義した packages.json を含む、新しい pcfproj と関連ファイルが現在のフォルダに追加されます。 上記のコマンドでは、必要なモジュールをインストールするための npm install コマンドも実行されます。

Running 'npm install' for you...

注意

The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program. というエラー メッセージが表示された場合、node.js (LTS 版を推奨) とその他すべての前提条件がインストールされているかどうかを確認してください。

pac pcf init を使用してコード コンポーネントを作成する。

テンプレートには、さまざまな設定ファイルとともに、index.ts ファイルが含まれています。 これは、コード コンポーネントの開始点であり、コンポーネントの実装で説明されているライフサイクル メソッドが含まれています。

Microsoft Fluent UI をインストールする

UI の作成には Microsoft Fluent UI と React を使用するため、これらを依存関係のあるものとしてインストールする必要があります。 依存関係をインストールするには、次を使用します:

npm install react react-dom @fluentui/react

これにより、モジュールが packages.json に追加され、node_modules フォルダにインストールされます。 必要なモジュールは後で npm install を使って復元するため、node_modules をソース コントロールにコミットする必要はありません。

Microsoft Fluent UI の利点の一つは、一貫性のある アクセシビリティ の高い UI を提供することです。

eslint を構成する

pac pcf init が使用するテンプレートは eslint モジュールをプロジェクトにインストールし、.eslintrc.json ファイルを追加することで構成します。 Eslint は、TypeScript および React のコーディング スタイルを構成する必要があります。 詳細については、リンティング - コード・コンポーネントのベスト・プラクティスとガイダンスを参照してください。

コード コンポーネントの入力とバインドされたプロパティの定義

ChoicesPicker\ControlManifest.Input.xml ファイルは、コード コンポーネントの動作を説明するメタデータを定義します。 コントロール属性には、コンポーネントの名前空間と名前がすでに含まれています。 次の入力プロパティと出力プロパティを定義する必要があります:

  • - これは、選択肢の列にリンクされる OptionSet 型の バインド プロパティです。 コード コンポーネントは現在の値を受け取り、この値が変更されたときに親コンテキストに通知します。
  • 構成 - これは 複数行テキスト タイプの 入力 プロパティで、アプリ作成者がコード コンポーネントをフォームに追加したときに値が設定されます。 各選択値に使用可能なアイコンを構成する際に使用できる JSON 文字列が含まれています。 詳細: マニフェスト

ChoicesPicker\ControlManifest.Input.xml を開き、コントロールの要素内に以下を貼り付けます (既存の sampleProperty を置換えます):

<property name="value" display-name-key="Value" description-key="Value of the Choices Control" of-type="OptionSet" usage="bound" required="true"/>
<property name="configuration" display-name-key="Icon Mapping" description-key="Configuration that maps the choice value to a fluent ui icon." of-type="Multiple" usage="input" required="true"/>

変更内容を保存し、次のコマンドでコンポーネントをビルドします:

npm run build

コンポーネントがビルドされると、以下が表示されます:

  1. 自動生成されたファイル ChoicesPicker\generated\ManifestTypes.d.ts がプロジェクトに追加されます。 これは、ビルド プロセスの一部として ControlManifest.Input.xml から生成され、入力/出力プロパティの操作に使用する型を提供します。

  2. ビルドの出力が out フォルダに追加されます。 bundle.js は、ブラウザ内で実行されるトランスパイルされた JavaScript です。 ControlManifest.xml は、デプロイ時に使用される ControlManifest.Input.xml ファイルを再フォーマットしたものです。

    注意

    generatedout フォルダの内容を直接変更しないでください。 これらはビルドの過程で上書きされます。

選択肢ピッカー Fluent UI React コンポーネント

コード コンポーネントが React を使用している場合、updateView メソッド内でレンダリングされるルート コンポーネントは 1 つである必要があります。 ChoicesPicker フォルダの中に、 ChoicesPickerComponent.tsxという名前の新しい TypeScript ファイルを追加し、以下の内容を追加します:

import { ChoiceGroup } from '@fluentui/react/lib/components/ChoiceGroup/ChoiceGroup';
import { IChoiceGroupOption } from '@fluentui/react/lib/components/ChoiceGroup/ChoiceGroup.types';
import * as React from 'react';

export interface ChoicesPickerComponentProps {
    label: string;
    value: number | null;
    options: ComponentFramework.PropertyHelper.OptionMetadata[];
    configuration: string | null;
    onChange: (newValue: number | undefined) => void;
}

export const ChoicesPickerComponent = React.memo((props: ChoicesPickerComponentProps) => {
    const { label, value, options, onChange, configuration } = props;
    const valueKey = value != null ? value.toString() : undefined;
    const items = React.useMemo(() => {
        let iconMapping: Record<number, string> = {};
        let configError: string | undefined;
        if (configuration) {
            try {
                iconMapping = JSON.parse(configuration) as Record<number, string>;
            } catch {
                configError = `Invalid configuration: '${configuration}'`;
            }
        }

        return {
            error: configError,
            choices: options.map((item) => {
                return {
                    key: item.Value.toString(),
                    value: item.Value,
                    text: item.Label,
                    iconProps: { iconName: iconMapping[item.Value] },
                } as IChoiceGroupOption;
            }),
        };
    }, [options, configuration]);

    const onChangeChoiceGroup = React.useCallback(
        (ev?: unknown, option?: IChoiceGroupOption): void => {
            onChange(option ? (option.value as number) : undefined);
        },
        [onChange],
    );

    return (
        <>
            {items.error}
            <ChoiceGroup
                label={label}
                options={items.choices}
                selectedKey={valueKey}
                onChange={onChangeChoiceGroup}
            />
        </>
    );
});
ChoicesPickerComponent.displayName = 'ChoicesPickerComponent';

注意

このファイルの拡張子 tsx は、React で使われる XML スタイルの構文をサポートする TypeScript ファイルです。 ビルド プロセスによって標準の JavaScript にコンパイルされます。

上記のコードから、次のことがわかります:

  1. const { label, value, options, onChange, configuration } = props; は「destructuring」と呼ばれ、レンダリングに必要な属性をプロップから抽出し、使用するたびに props を前置するのではなく、その属性を使用します。

  2. 入力プロップは、index.ts で提供される以下の属性を持っています:

    • label -コンポーネントにラベルを付けるために使用されます。 コンポーネントのラベル付けに使用され、モデル駆動型アプリ内で選択された UI 言語を使用して、親コンテキストで提供されるメタデータ フィールドのラベルにバインドされます。

    • value - マニフェストで定義された入力プロパティにリンクされます。 これは、レコードが新しい場合や、フィールドが設定されていない場合には、null 値となります。 プロパティ値の受け渡しには、undefined ではなく、TypeScript null が使用されます。

    • options - コード コンポーネントがモデル駆動型アプリの choices 列にバインドされている場合、プロパティには、利用可能な選択肢を記述した OptionMetadata が含まれます。 これをコンポーネントに渡して、各アイテムをレンダリングできるようにします。

    • configuration-コンポーネントの目的は、利用可能な各選択肢のアイコンを表示することです。 この構成は、アプリの作成者がコード コンポーネントをフォームに追加する際に提供されます。 このプロパティには、各数字の選択肢の値を Fluent UI のアイコン名にマッピングする JSON の文字列を指定します。 たとえば、{"0":"ContactInfo","1":"Send","2":"Phone"} などとします。

    • onChange - ユーザーが選択肢の選択を変更すると、React コンポーネントが onChange イベントをトリガーします。 その後、コード コンポーネントが notifyOutputChanged を呼び出し、モデル駆動型プリが新しい値で列を更新できるようにします。 React コンポーネントには 2 種類あります:

      1. 制御されていない - これらのコンポーネントは、内部の状態を維持し、入力されたプロップを既定の値としてのみ使用します。
      2. 制御 - これらのコンポーネントは、コンポーネントのプロップから渡された値をレンダリングします。 onChange イベントでプロップの値が更新されない場合、ユーザーに UI の変更が表示されません。

      ChoicesPickerComponent はコントロールされたコンポーネントであるため、モデル駆動型アプリが値を更新すると (notifyOutputChanged の呼び出し後)、新しい値で updateView を呼び出し、その値がコンポーネントのプロップに渡されて、更新された値を表示する再レンダリングが行われます。

  3. パス ベースのインポートを使用して ChoiceGroup Fluent UI コンポーネントをインポートすることで、バンドルサイズが小さくなり、必要な容量が少なくなり、ランタイムパフォーマンスが向上します。 代替案:

    import { ChoiceGroup, IChoiceGroupOption } from '@fluentui/react';
    

    次を使用します:

    import { ChoiceGroup } from "@fluentui/react/lib/components/ChoiceGroup/ChoiceGroup";
    import { IChoiceGroupOption } from "@fluentui/react/lib/components/ChoiceGroup/ChoiceGroup.types";
    

    別の方法としては、ツリー シェイクがあります。

  4. これは React の機能的なコンポーネントですが、同様にクラスのコンポーネントである可能性もあります。 これは、好みのコーディング スタイルに基づくものです。 クラス コンポーネントと機能コンポーネントを同じプロジェクトに混在させることもできます。 関数コンポーネントとクラス コンポーネントは、Reactで使用されている tsx XML スタイルの構文を使用します。

  5. React.memo を使用して機能コンポーネントをラップし、入力プロップが変更されない限りレンダリングされないようにします。

  6. React.useMemo は、入力されたプロップス options または configuration が変更されたときにのみ、作成されたアイテム配列が更されるようにする目的で使用します。 これは関数コンポーネントのベスト プラクティスであり、子コンポーネントの不要なレンダリングを減らすことができます。

  7. React.useCallback は、Fluent UI の ChoiceGroup の値が変化したときに呼び出されるコールバッククロージャを作成するために使用されます。 この React フックにより、入力プロップ onChange が変更されたときにのみコールバック クロージャーが変更されるようになります。 これは useMemo と共通するパフォーマンスのベスト プラクティスです。

  8. JSON 構成入力プロパティの解析に失敗した場合は、items.error を使用してエラーが表示されます。

React コンポーネントを index.ts の内側からレンダリングする

コード コンポーネントの内部で React を使用する場合、ルート コンポーネントのレンダリングは updateView 内で行われます。 コンポーネントのレンダリングに必要なすべての値は、コンポーネントに渡され、それらが変更されると、再レンダリングされます。 index.tsChoicesPickerComponent を追加する前に、ファイルの先頭に以下を追加する必要があります:

import { IInputs, IOutputs } from './generated/ManifestTypes';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { initializeIcons } from '@fluentui/react/lib/Icons';
import { ChoicesPickerComponent } from './ChoicesPickerComponent';

initializeIcons(undefined, { disableWarnings: true });

注意

Fluent UI のアイコンセットを使用しているため、initializeIcons のインポートが必要となります。 テストハーネス内でアイコンを読み込むには、initializeIcons を呼び出す必要があります。 これらはモデル駆動型アプリの内部ですでに初期化されています。

コード コンポーネントは、属性を使用してインスタンスの状態を維持します。 (これは React コンポーネントの状態とは異なります)。 index.ts ChoicesPicker クラス内で、次の属性を追加します:

notifyOutputChanged: () => void;
rootContainer: HTMLDivElement;
context: ComponentFramework.Context<IInputs>;
selectedValue: number | undefined;
  • notifyOutputChanged - ユーザーが選択肢の値を変更し、コード コンポーネントがその値を親コンテキストに戻す準備ができたことをモデル駆動型アプリに通知する目的で使用するメソッドへの参照を保持します。
  • rootContainer - モデル駆動型アプリ内のコード コンポーネントを保持する目的で作成する HTML DOM 要素です。
  • context - Power Apps Component Framework のコンテキストで、マニフェストで定義されたプロパティやその他のランタイム プロパティを読み取り、trackContainerResize などの API メソッドにアクセスする目的で使用されます。
  • selectedValue - ユーザーが選択した選択肢の状態を保持し、getOutputs メソッド内で返せるようにします。

これらの属性を設定するには、次の init メソッドを更新します:

public init(
    context: ComponentFramework.Context<IInputs>,
    notifyOutputChanged: () => void,
    state: ComponentFramework.Dictionary,
    container: HTMLDivElement,
  ): void {
    this.notifyOutputChanged = notifyOutputChanged;
    this.rootContainer = container;
    this.context = context;
  }

init メソッドは、アプリ画面でコード コンポーネントが初期化される際に呼び出されます。 以下への参照を保存します:

  • notifyOutputChanged - これは、プロパティのひとつが変更されたことを canvas アプリに通知する目的で呼び出す場合のコールバックです。

  • rootContainer - これは、コード コンポーネントの UI を追加する DOM 要素です。

ユーザーが選択した値を変更した場合は、onChange イベントから notifyOutputChanged を呼び出す必要があります。 関数の追加:

onChange = (newValue: number | undefined): void => {
     this.selectedValue = newValue;
     this.notifyOutputChanged();
};

getOutputs メソッドを更新します:

public getOutputs(): IOutputs {
    return { value: this.selectedValue } as IOutputs;
}

ヒント

モデル駆動型アプリでクライアント API スクリプトを記述した経験があれば、フォームのコンテキストを使って属性値を更新することに慣れているかもしれません。 コード コンポーネントを、このコンテキストにアクセスさせないようにしてください。 代わりに、notifyOutputChangedgetOutputs に依存して、1 つ以上複数の変更された値を提供します。 IOutput インターフェースで定義されたすべてのバインドされたプロパティを返す必要はなく、値が変更されたプロパティだけを返します。

updateView を更新して ChoicesPickerComponent をレンダリングします:

public updateView(context: ComponentFramework.Context<IInputs>): void {
    const { value, configuration } = context.parameters;
    if (value && value.attributes && configuration && configuration.raw) {
        ReactDOM.render(
            React.createElement(ChoicesPickerComponent, {
                label: value.attributes.DisplayName,
                options: value.attributes.Options,
                configuration: configuration.raw,
                value: value.raw,
                onChange: this.onChange,
            }),
            this.rootContainer,
        );
    }
}

ラベルとオプションを context.parameters.value から引き出していることに注目してください。value.raw は選択された数字の選択肢を提供し、値が選択されていない場合は null を提供します。

最後に、コードコンポーネントが破棄されたときに整理する必要があります:

public destroy(): void {
    ReactDOM.unmountComponentAtNode(this.rootContainer);
}

テスト ハーネスを開始します

すべてのファイルが保存され、端末で使用されていることを確認します:

npm start watch

テスト ハーネスが、選択肢の選択が新しいブラウザ ウィンドウ内にレンダリングされた状態で開始されることがわかります。 最初は、文字列プロパティ configuration に既定の値 val があるため、エラーが表示されます。 テスト ハーネスの既定の選択肢 0、1、2 と、以下の Fluent UI のアイコンを対応させるように設定します:

{"0":"ContactInfo","1":"Send","2":"Phone"}

アイコンを使用してハーネスの選択をテストします。

選択したオプションを変更すると、右側のパネルの データ入力 の値が表示されます。 また、値を変更すると、コンポーネントには関連する値が更新されて表示されます。

読み取り専用、フィールド レベルのセキュリティに対応

モデル駆動型のアプリケーション field コンポーネントを作成する際、エンタープライズ アプリケーションでは、フィールド レベルのセキュリティにより読み取り専用、またはマスキングされたコントロールの状態を尊重する必要があります。 列が読み取り専用の場合にコード コンポーネントが読み取り専用の UI をレンダリングしない場合、状況によっては (例えば、レコードが非アクティブになっている場合など)、更新されるべきではない列をユーザーが更新してしまう場合があります。 詳細は、モデル駆動型アプリのフィールド レベルのセキュリティを参照してください。

updateView 内で、次のコードを追加して、無効フラグとマスキング フラグを取得します:

let disabled = context.mode.isControlDisabled;
let masked = false;
if (value.security) {
    disabled = disabled || !value.security.editable;
    masked = !value.security.readable;
}

バインドされた列にフィールド レベルのセキュリティ設定が適用されている場合、モデル駆動型アプリの内部でのみ value.security が入力されます。

これらの値は、プロップスを介して React コンポーネントに渡すことができます:

ReactDOM.render(
    React.createElement(ChoicesPicker, {
    ... 
    disabled: disabled,
    masked: masked,

ChoicesPickerComponent 内で、ChoicesPickerProps インターフェースに追加することで、これを受け入れることができます:

export interface ChoicesPickerProps {
  ...
  disabled: boolean;
  masked: boolean;
}

ChoicesPickerComponent 内で React ノードを返すときに、これらの新しい入力プロップを使用して、ピッカーが無効またはマスキングされていることを確認できます。

return (
        <>
            {items.error}
            {masked && '****'}
    
            {!items.error && !masked && (
                <ChoiceGroup
                    label={label}
                    options={items.choices}
                    selectedKey={valueKey}
                    disabled={disabled}
                    onChange={onChangeChoiceGroup}
                />
            )}
        </>
    );

また、プロップの「destructuring」には、disabledmasked を加える必要があります:

 const { label, value, options, onChange, configuration, disabled, masked } = props;

コード コンポーネントをレスポンシブにする

コード コンポーネントは、Web、タブレット、モバイル アプリでレンダリングできます。 使用可能なスペースを考慮することが重要です。 使用可能な幅が制限されている場合に、選択肢コンポーネントをドロップダウンで表示します。

まず、新しいプロップ formFactor に応じて異なるレンダリングを行うようにコード コンポーネントを更新します。 ChoicesPickerProps インターフェースに以下の属性を追加します:

export interface ChoicesPickerProps {
    ...
    formFactor: 'small' | 'large';

このコンポーネントは、Fluent UI の Dropdown コンポーネントを使って小さいバージョンをレンダリングするため、それをインポートに追加します:

import { Dropdown } from '@fluentui/react/lib/components/Dropdown/Dropdown';
import { IDropdownOption } from '@fluentui/react/lib/components/Dropdown/Dropdown.types';
import { Icon } from '@fluentui/react/lib/components/Icon/Icon';

ドロップダウン コンポーネントは、いくつかの異なるレンダリング方法を必要とするため、ChoicesPickerComponent の上に次のものを追加します:

const iconStyles = { marginRight: '8px' };

const onRenderOption = (option?: IDropdownOption): JSX.Element => {
    if (option) {
        return (
            <div>
                {option.data && option.data.icon && (
                    <Icon style={iconStyles} iconName={option.data.icon} aria-hidden="true" title={option.data.icon} />
                )}
                <span>{option.text}</span>
            </div>
        );
    }
    return <></>;
};

const onRenderTitle = (options?: IDropdownOption[]): JSX.Element => {
    if (options) {
        return onRenderOption(options[0]);
    }
    return <></>;
};

これらのメソッドは、ドロップダウンの値の横に正しいアイコンを表示する目的で Dropdown が使用します。

また、ChoiceGroup のイベント ハンドラと同様に DropdownonChange メソッドも必要となります。 既存の onChangeChoiceGroup のすぐ上に、新しい Dropdown のバージョンを追加します:

const onChangeDropDown = React.useCallback(
        (ev: unknown, option?: IDropdownOption): void => {
            onChange(option ? (option.data.value as number) : undefined);
        },
        [onChange],
    );

以上で、これらすべてをアップデートされたレンダリング出力に追加することができます:

return (
        <>
            {items.error}

            {masked && '****'}

            {formFactor == 'large' && !items.error && !masked && (
                <ChoiceGroup
                    label={label}
                    options={items.choices}
                    selectedKey={valueKey}
                    disabled={disabled}
                    onChange={onChangeChoiceGroup}
                />
            )}

            {formFactor == 'small' && !items.error && !masked && (
                <Dropdown
                    placeholder={'---'}
                    label={label}
                    ariaLabel={label}
                    options={items.options}
                    selectedKey={valueKey}
                    disabled={disabled}
                    onRenderOption={onRenderOption}
                    onRenderTitle={onRenderTitle}
                    onChange={onChangeDropDown}
                />
            )}
        </>
    );

formFactor が大きい場合には ChoiceGroup コンポーネントを出力し、小さい場合には Dropdown を使用していることがわかります。 ここでも上記と同様に、入力されたプロップスの「destructuring」に formFactor を追加する必要があります:

const { label, value, options, onChange, configuration, disabled, masked, formFactor } = props;

最後に、オプションのメタデータを ChoicesGroup で使用したものとは若干異なる方法でマッピングする必要があります、items のリターン ブロック内で、既存の選択肢の下にある options.map の下に次のように追加します:

options: options.map((item) => {
                return {
                    key: item.Value.toString(),
                    data: { value: item.Value, icon: iconMapping[item.Value] },
                    text: item.Label,
                } as IDropdownOption;
            }),

formFactor のプロップに応じて選択肢コンポーネントのレンダリング方法が変更されるため、index.ts 内部のレンダー呼び出しから正しい値を渡す必要があります。

まず、index.ts 内部の export class ChoicesPicker クラスのすぐ上に以下を追加します。

const SmallFormFactorMaxWidth = 350;

const enum FormFactors {
  Unknown = 0,
  Desktop = 1,
  Tablet = 2,
  Phone = 3,
}

SmallFormFactorMaxWidth は、コンポーネントが ChoiceGroup ではなく Dropdown を使用してレンダリングを開始するときの幅を表わします。 FormFactors enumcontext.client.getFormFactor を呼び出す際に便宜的に使用されます。

既存のプロップの配下にある React.createElement のプロップに以下を追加します:

formFactor:
    context.client.getFormFactor() == FormFactors.Phone ||
        context.mode.allocatedWidth < SmallFormFactorMaxWidth
        ? 'small'
    : 'large',

allocatedWidth を使用しているため、利用可能な幅が変更されたときに (updateView への呼び出しを介して) 更新情報を受信することを、モデル駆動型のアプリに通知する必要があります。 init メソッド内で trackContainerResize の呼び出しを追加して行います:

this.context.mode.trackContainerResize(true);

すべての変更を保存すると、テスト ハーネスのブラウザ ウィンドウに自動的に反映されます (npm start watch がまだ残留しているためです)。 ここで、コンポーネントコンテナの幅 の値を 349350 の間で切り替えると、レンダリングの動作が変わります。 また、フォーム ファクターウェブ電話 で入れ替えても、同じ動作を確認できます。

trackContainerResize.

ローカライズ

複数の言語に対応する場合は、コード コンポーネントに、デザイン文字列とランタイム文字列の両方の翻訳を提供するリソースファイルを保持できます。

  1. その場所 ChoicesPicker\strings\ChoicesPicker.1033.resx に新しいファイルを追加します。 異なるロケールのラベルを追加する場合は、1033 (en-us) を選択したロケールに変更してください。

  2. 次のコンテンツを追加します:

    <?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="ChoicesPicker_Name" xml:space="preserve">
        <value>Choices Picker (Model Driven)</value>
        <comment/>
      </data>
      <data name="ChoicesPicker_Desc" xml:space="preserve">
        <value>Shows choices as a picker with icons</value>
        <comment/>
      </data>
      <data name="Value_Name" xml:space="preserve">
        <value>Value</value>
        <comment/>
      </data>
      <data name="Value_Desc" xml:space="preserve">
        <value>The choices field to bind the control to</value>
        <comment/>
      </data>
      <data name="Configuration_Name" xml:space="preserve">
        <value>Icon Mapping Configuration</value>
        <comment/>
      </data>
      <data name="Configuration_Desc" xml:space="preserve">
        <value>Configuration that maps the choice value to a fluent ui icon. E.g. {"1":"ContactInfo","2":"Send"}</value>
        <comment/>
      </data>
    </root>
    

    ヒント

    resx ファイルを直接編集することはお勧めしません。 代わりに、Visual Studio Code リソース エディタ、または Visual Studio Code の拡張機能のどちらかを使用できます。

    以上で、リソース文字列ができたので、以下のように ControlManifest.Input.xml を更新することで、リソース文字列を参照することができます:

    <?xml version="1.0" encoding="utf-8" ?>
    <manifest>
      <control namespace="SampleNamespace" constructor="ChoicesPicker" version="0.0.1" display-name-key="ChoicesPicker_Name" description-key="ChoicesPicker_Desc" control-type="standard" >
        <property name="value" display-name-key="Value_Name" description-key="Value_Desc" of-type="OptionSet" usage="bound" required="true"/>
        <property name="configuration" display-name-key="Configuration_Name" description-key="Configuration_Desc" of-type="Multiple" usage="input" required="true"/> 
        <resources>
          <code path="index.ts" order="1"/>
          <resx path="strings/ChoicesPicker.1033.resx" version="1.0.0" />
        </resources>
      </control>
    </manifest>
    

以下が表示されます:

  1. display-name-keydescription-key の値は、resx ファイルの対応するキーを指すようになっています。
  2. resources 要素には、コード コンポーネントが参照されたファイルからリソースを読み込むことを示すエントリが追加されています。

コンポーネントで使用する追加の文字列が必要な場合は、resx に追加し、getString を使用して実行時に文字列を読み込むことができます。 詳しくは、ローカリゼーション API コンポーネントの実装を参照してください。

テスト ハーネスの制限として、リソースファイルを読み込まないことが挙げられます。そのため、コンポーネントを完全にテストするには、コンポーネントを Microsoft Dataverse にデプロイする必要があります。

モデル駆動型アプリにおけるデプロイと構成

テスト ハーネスで基本的な機能をテストした後は、コンポーネントを Microsoft Dataverse にデプロイして、モデル駆動のアプリ内でコード コンポーネントをエンド ツー エンドで完全にテストできるようにする必要があります。

  1. Dataverse 環境で、接頭辞が samples の公開元が作成されていることを確認してください:

    新しい公開元の追加。

    同様に、ご利用の公開元である可能性もあります。ただし、以下の pac pcf push へのコールで公開元の接頭辞パラメータを更新してください。 詳細については、ソリューションの公開元を作成するを参照してください。

  2. 公開元の保存後は、ご利用の環境に対して Microsoft Power Platform CLI を認証する準備が整ったことを意味します。コンパイルされたコード コンポーネントを プッシュできます。 コマンドラインでは、以下を使用します:

    pac auth create --url https://myorg.crm.dynamics.com
    

    myorg.crm.dynamics.com は Dataverse 環境の URL に置き換えます。 プロンプトが表示されたら、システム管理者またはカスタマイザー権限でログインします。 これらのロールによって提供される特権は、任意のコードコンポーネントを Dataverse にデプロイするために必要です。

  3. コード コンポーネントをデプロイするには、次を使用します:

    pac pcf push --publisher-prefix samples
    

    注意

    エラー (Missing required tool: MSBuild.exe/dotnet.exe) が発生した場合、パス環境変数に MSBuild.exe/dotnet.exe を追加すか、Developer Command Prompt for Visual Studio Code を使用します。 Visual Studio2019 for Windows&Macまたビルドツール Visual Studio 2019のどちらかをインストールする必要があります。 前提条件に記載されている通り、必ず .NET build tools のワークロードを選択してください。

  4. このプロセスが完了すると、ご利用の環境に PowerAppTools_samples という名前の一時的なソリューションが作成されます。 ChoicesPicker コード コンポーネントがこのソリューションに追加されます。 必要に応じて、コード コンポーネントを後からソリューションに移行できます。 詳しくは、コード コンポーネント アプリケーションの ライフサイクル管理 (ALM) を参照してください。

    PowerAppsTools_sample 一時的ソリューション。

  5. 次に、クラシック エディターメイン フォーム に移動し、 優先する連絡方法 > プロパティの変更 > コントロール タブ > コントロールの追加 > 選択肢ピッカーを選択 > 追加 を選択して、連絡先 フォームにコード コンポーネントを追加します。

    注意

    将来的には、モデル駆動型アプリ フォームのコード コンポーネントの構成にはクラシック エディターは必要なくなります。

  6. コンポーネントに以下のプロパティを設定します:

    • 選択肢ピッカーを Web、電話、タブレットの既定として設定します。

    • 編集アイコンを選択して 静的な値にバインドする を選択すると、アイコンのマッピング構成 に以下の文字列が入力されます。

      {
          "1":"ContactInfo",
          "2":"Send", 
          "3":"Phone",
          "4":"Fax",
          "5":"DeliveryTruck"
      }
      

      これらは、各選択肢の値に使用される Fluent UI のアイコンです。

      コントロール プロパティ

    • 表示タブ を選択し、フォームにラベルを表示する のチェックを外します。選択肢ピッカーの上にラベルが表示されるため、これは必要ありません。

  7. フォームを 保存 して 公開 します。

  8. 正しいフォームが選択された状態で、モデル駆動型アプリ内の連絡先レコードを開きます。 標準のドロップダウンコントロールの代わりに、 ChoicesPicker コード コンポーネントが表示されます。 (コンポーネントを表示するには、ページのハード リロードの実行が必要となる場合があります)。

    注意

    テスト ハーネスでは、モデル駆動型アプリと比較して、テキストの配置が若干異なります。 これは、テスト ハーネスの CSS のルールがモデル駆動型アプリのものとは異なるためです。 そのため、デプロイ後は必ずコード コンポーネントの完全なテストをお勧めします。

Dataverse にデプロイ後のデバッグ

コンポーネントにさらに変更を加える必要がある場合は、毎回デプロイする必要はありません。 その代わり、デバッグ コード コンポーネント で説明した手法でFiddler AutoResponder を作成し、npm start watch の実行中にローカル ファイル システムからファイルを読み込むようにします。

注意

テストハーネスを使ってすべての機能がテストできるのであれば、Dataverse にデプロイした後のデバッグは必要ない可能性もあります。 ただし、コード コンポーネントを配布する前に、必ず Dataverse の中でデプロイとテストを行うことをお勧めします。

AutoResponder は、次のようになります:

REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.ChoicesPicker[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\ChoicesPicker\out\controls\ChoicesPicker\${folder}\${fname}

オートレスポンダーのルール。

AutoResponder ファイルを取り込むには、ブラウザの キャッシュを空にして最新の情報に更新 する必要があります。 読み込んだ後は、フィドラーがファイルにキャッシュ コントロール ヘッダを追加してキャッシュされないようにするため、ブラウザを更新できます。

変更の完了後は、マニフェストのパッチ バージョンを増分し、pac pcf push を使用して再デプロイできます。

これまでは、最適化されていない開発用のビルドをデプロイしていたため、実行時の動作が遅くなっていました。 ChoicesPicker.pcfproj を編集することで、pac pcf push を使って最適化されたビルドをデプロイできます。 OutputPath の配下に、以下を追加します:

<PcfBuildMode>production</PcfBuildMode>

Microsoft Power Platform によるアプリケーション ライフサイクル管理 (ALM)
Power Apps Component Framework API の参照
最初のコンポーネントを作成する
コード コンポーネントのデバッグ

注意

ドキュメントの言語設定についてお聞かせください。 簡単な調査を行います。 (この調査は英語です)

この調査には約 7 分かかります。 個人データは収集されません (プライバシー ステートメント)。