Power Apps Component FrameworkでGitHubAPIを使ったコントロールを作成する

# Power Apps Component Framework でコントロールを作成する

この記事は[こちらの動画](https://www.youtube.com/watch?v=3LnPaKtfKhw)をもとに作成してあります。

## Power Apps Component Frameworkとは

キャンバスアプリやモデル駆動型アプリのUIを作成するフレームワークのことです。
例えば数値を入力するスライダーや、日付を入力する際にカレンダーなどUIの部品を作成できます。
詳細は[Power Apps Component Frameworkの概要](https://docs.microsoft.com/ja-jp/powerapps/developer/component-framework/overview)で説明してあります。

## 環境構築

まずはMicrosoft Power Apps CLIをインストールする必要があります。
Microsoft Power Apps CLIはカスタマイズの作成、構築、デバッグ、公開を行うコマンドのことです。
[Microsoft Power Apps CLIインストールとコマンド一覧](https://docs.microsoft.com/ja-jp/powerapps/developer/common-data-service/powerapps-cli)に詳細が書かれています。
不足しているものをインストールしておいてください。

## component framework プロジェクト作成

プロジェクトを作成したいディレクトリに移動しておきます。
以下のコマンドを実行します。

*> pac pcf init --namespace [specify your namespace here] --name [Name of the code component] --template [component type]*

今回はコンポーネントのタイプをフィールドに設定します。

```
>pac pcf init -ns fic -n GitControl -t field
PowerApps component framework プロジェクトが 'C:\Microsoft\GitControl' 内に正常に作成されました。
プロジェクトの依存関係をインストールするために、このディレクトリで 'npm install' を実行してください。
```

Node.jsのパッケージをインストールします。

```
>npm i
```

作成したプロジェクトをVisual Studio Codeで開きます。

```
>cd GitControl

>code .
(Visual Studio Codeが起動)
```

作成したプロジェクトのファイルに以下が用意されているか確認します。

- PCF コントロールフォルダ
   - マニフェスト(ControlManifest.Input.xml)
         - control metadata
         - configuration propaties
         - resources
    - コントローラ(index.ts)
         - init()
         - updateView()
         - getOutputs()
         - destroy()

コントローラにデフォルトで用意されているメソッドについて軽く説明します。

- init()
     - indexが呼び出されたときに最初に1度だけ処理される
- updateView()
     - スクリーンサイズが変わったときやイベントが走った時などに呼び出される
- getOutputs()
     - UIに送るreturn値の定義
- destroy()
     - 全てのイベントハンドラのClean up用


## 画面にテキストボックスを表示/valueの取得
### マニフェスト修正

namespaceは大文字始まりが良いようです。
ControlManifest.Input.xmlを修正します。

```xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="Fic" constructor="GitControl" version="0.0.1" display-name-key="GitControl" description-key="GitControl description" control-type="standard">
    <!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
    <property name="gitUsername" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
 
 ...
```

### エレメントの追加

画面にテキストボックスとイメージを追加します。
init()に以下を追記します。

```ts
	public init(context: ComponentFramework.Context<IInputs>, 
                notifyOutputChanged: () => void, 
                state: ComponentFramework.Dictionary,
                container:HTMLDivElement)
	{
    	let textbox = document.createElement("input");

		let image = document.createElement("img");
        
		container.appendChild(textbox);
		container.appendChild(image);
    }
```

ここでビルドを走らせ、ファイルが更新する度に自動でビルドを行うようにします。

```
> npm run build
```

実際に追加したテキストボックスを見てみたいと思います。

```
> npm start watch
```

画面が切り替わり、テキストボックスが表示されていたら成功です。
イメージをまだセットしていないため、画像は表示されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599668/5eff2c24-a3b7-abe4-69db-927f0a9a4fc4.png)

### valueの取得

init()に以下を追加します。

```ts
	let username = context.parameters.gitUsername.raw || "";
	let textbox = document.createElement("input");
	textbox.value = username;
```

これでgitUsernameをセットした状態で画面を更新するとテキストボックスにデフォルト値が入ります。

## GitHubAPIを使ってみる
テキストボックスに入力されたユーザネームからアバターを表示してみます。
まずは、ユーザネームからアバターを取り出すメソッドをindex.tsに追加します。

```ts
	private async setAvatarImage(imageContainer: HTMLImageElement, 
                                username: string) {
		const response = await fetch("https://api.github.com/users/" + username);
		const body = await response.json();
		imageContainer.src = body.avatar_url;
	}
```
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　
次に、テキストボックス内のテキストが変更された時に呼び出されるイベントハンドラをindex.tsに追加します。

```ts
	public textboxOnChange(): void {
		this._value = this._textbox.value || "";
		this.setAvatarImage(this._image, this._value);
		this._notifyOutputChanged();	
	}
```

このままだと変数未定義のエラーが出てしまうので、必要な変数を定義していきます。

```ts
export class GitControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {

	private _notifyOutputChanged: () => void;

	private _textbox: HTMLInputElement;
	private _image: HTMLImageElement;

	private _value: string;

	private _textboxOnChange: EventListenerOrEventListenerObject;
```

init()が呼び出されたタイミングでこれらの変数に値を入れていきます。

```ts
	public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
	{
		this._notifyOutputChanged = notifyOutputChanged;
		this._textboxOnChange = this.textboxOnChange.bind(this);

		let username = context.parameters.gitUsername.raw || "";
		// Add control initialization code
		let textbox = document.createElement("input");
		textbox.addEventListener('input', this._textboxOnChange);

		textbox.value = username;
		this._textbox = textbox;

		let image = document.createElement("img");
		this._image = image;

		container.appendChild(textbox);
		container.appendChild(image);
	}
```

最後に、変更されたusernameをgitUsernameプロパティにセットするためにgetOutputs()を使って定義します。

```ts
	public getOutputs(): IOutputs
	{
		return {
			gitUsername: this._value
		};
	}
```

これでテキストボックス内を変更するたびにAPIが呼び出されアバターが更新されます。
また、画面右側のデータの出力の値もテキストボックスが変更される度に更新されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599668/1227dad0-35dc-e10d-00f7-da6f2b6d2a45.png)

## アバターの枠を変更する

ドロップダウンでアバターの枠を変更できるように設定してみます。
まずはプロパティを追加します。

```xml
    <property name="gitUsername" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
    <property name="imageShape" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="Enum" usage="input" required="true" >
      <value name="square" display-name-key="square" description-key="Square">Square</value>
      <value name="circle" display-name-key="circle" description-key="Circle">Circle</value>    
    </property>
```

次にinit()から追加したプロパティを受け取ります。
gitUsernameを受け取るタイミングでshapeも受け取ります。

```ts
		let username = context.parameters.gitUsername.raw || "";
		let shape = context.parameters.imageShape.raw;
```

次に、cssファイルを追加します。
GitControlフォルダにcssフォルダを追加し、GitControl.cssファイルを作成します。

```
GitControl\css\GitControl.css
```

```css

.Fic\.GitControl img{
    border-style: solid;
    border-width: 5px;
    border-color: #742774;
    height: 150px;
    width: 150px;
}

.Fic\.GitControl input{
    width: -webkit-fill-available;
}

.Fic\.GitControl .circle{
    border-radius: 50%;
}

```
これで実行すると、アバターに枠がついています。

しかし、この状態でimageShapeをcircleに変更してもアバターの枠を変えることはできません。
initializaは文字通り1度しか呼ばれないからです。
ここからは枠を丸く変化させる実装をしていきます。

```ts
	public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
	{
		this._notifyOutputChanged = notifyOutputChanged;
		this._textboxOnChange = this.textboxOnChange.bind(this);

		let username = context.parameters.gitUsername.raw || "";
		// Add control initialization code
		let textbox = document.createElement("input");
		textbox.addEventListener('input', this._textboxOnChange);

		textbox.value = username;
		this._textbox = textbox;

    let image = document.createElement("img");
		if(shape == "Circle") { image.classList.add("circle") }
		this._image = image;

		container.appendChild(textbox);
		container.appendChild(image);
	}
```
これで「circle」に設定してからリロードすると
アバターの枠が丸くなったかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599668/e23439b8-8175-3ab2-2c6e-42861e691a07.png)


しかし、代わりにアバターがクリアされてしまいます。
アバターをセットするタイミングは、テキストボックスを変更したときしか用意されていないためです。
ここでinit()に、枠と同時にアバターをセットするコードを追加します。

```ts
		let image = document.createElement("img");
		if(shape == "Circle") { image.classList.add("circle") }
		this.setAvatarImage(image, username);
		this._image = image;
```

これでリロードしてもセットしていたusernameからアバターを表示することができます。

## フィールドレベルのセキュリティ実装
ここからはフィールドレベルのセキュリティを実装します。
初めに、読み取り専用の際は変更無効のロジックをupdateView()に追加します。

```ts
	public updateView(context: ComponentFramework.Context<IInputs>): void
	{
		// Add code to update control view
		this._textbox.disabled = context.mode.isControlDisabled;
	}
```

次にページを離れた時のイベントの破棄をdestroy()に実装します。

```ts
	public destroy(): void
	{
		// Add code to cleanup control if necessary
		this._textbox.removeEventListener('input', this.textboxOnChange);
	}
```

## デプロイ
ここまでできたらデプロイしていきます。

まずは埋め込むアプリを認証する必要があります。
認証プロファイルの作成から行いましょう

```
  使用法: pac auth [create] [list] [select] [delete] [clear]

    create                      認証プロファイルを作成してこのコンピューターに保存します
    list                        このコンピューターに保存されている認証プロファイルをリストします
    select                      アクティブにする認証プロファイルを選択します
    delete                      特定の認証プロファイルを削除
    clear                       このコンピューターに保存された認証プロファイルをすべてクリア
```

こちらに従って

    > pac auth create -u [環境URL]

環境URLはDynamics 365 Administration Centerから確認することができます。

サインインを求められますので、アカウント名とパスワードを入力してください。

    認証が正常に作成されました。
    接続の検証中...
    接続先...[環境名]

こちらが表示されたら成功です。
コンポーネントをプッシュしていきます。

    --publisher-prefix          CDS ソリューション発行者を表すカスタマイズの接頭辞の値 (エイリアス: -pp)
    --verbosity                 一時的なソリューション ラッパーをビルドする際の MSBuild の詳細レベル。 (エイリアス: -v)
                                値: minimal, normal, detailed, diagnostic
    --force-import              Force a full update of the control (エイリアス: -f                 テレメトリ設定の管理


-pp で接頭語を入れます。


    > pac pcf push -pp fic

初回のプッシュは3分ほどかかります。

    現在の組織に一時的なソリューション ラッパーをインポート: 完了。
    Updating the control in the current org: done.

プッシュが完了しました。
今後のプッシュは修正箇所のみがプッシュされる為、より早くなります。

## 作成したソリューションを使う
それではソリューションを確認します。
ブラウザでPowerAppsの左メニューからSolutionsを開くとPowerAppsTools_ficが追加されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599668/512e9edc-c497-c671-eb72-a769755caa67.png)

ここでGitHub_usernameを格納するフィールドを、任意のエンティティに用意します。
データの型はsingle line textとしておいてください。
フィールドのプロパティを開き、コントロールを追加します。
Controlsタブを選択し「Add Control...」から作成したコントロールを選びます。
Web, Phone, Tablet全てにチェックを入れます。
下のプロパティをcircleに設定しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599668/ab1f9425-5146-bc38-3e0f-c8cf07cfdafa.png)

フォームに作成したフィールドを挿入し、実際にアプリを動かして見ると、アバターが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599668/b02b281b-5595-5141-20ab-145904a6cbe1.png)

これでGitHubのユーザ名を登録する際に、正しく入力することができているかアバターを使って確認できるようになりました。