2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PowerApps コンポーネントフレームワーク : 画像アップロードサンプル

Last updated at Posted at 2019-07-30

前回に引き続き、今回は画像をアップロードするサンプルを解説します。

プロジェクトの作成

今回も既に作成した SampleSolution に追加するように開発していきます。

1. 前回まで作ってきた PCFControls フォルダに移動して、フィールド用のコントロールを作成。

mkdir ImageUploadControl
cd ImageUploadControl

2. 以下のコマンドを実行してプロジェクトを作成。

  • 名前空間とプロジェクト名の指定
  • template 引数は field を指定
pac pcf init --namespace SampleNamespace --name TSImageUploadControl --template field

3. npm パッケージをリストアして任意の IDE でフォルダを開く。ここでは Visual Studio Code を利用。

npm install
code .

コントロールマニフェストの編集とリソースの追加

これまで同様にまずはマニフェストから編集します。

1. ControlManifest.Input.xml を以下の様に編集。

  • Multiple 文字列フィールドに画像データを保存
  • resources に ts と css、既定の画像 を指定
  • feature-usage : 今回は利用する機能として Device.pickFile を指定
ControlManifest.Input.xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="SampleNamespace" constructor="TSImageUploadControl" version="1.0.0" 
    display-name-key="Image Upload Control" description-key="Image Upload Control" control-type="standard">
    <property name="value" display-name-key="image value" description-key="image value" 
      of-type="Multiple" usage="bound" required="true" />
    <resources>
      <code path="index.ts" order="1" />
      <css path="css/TS_ImageUploadControl.css" order="1" />
      <img path="img/default.png" />
    </resources>
    <feature-usage>
      <uses-feature name="Device.pickFile" required="true" />
    </feature-usage>
  </control> 
</manifest>

2. ファイルの作成から css フォルダパスを含めて TS_ImageUploadControl.css を作成。中身を以下に差し替え。

TS_ImageUploadControl.css
.SampleNamespace\.TSImageUploadControl button{
text-decoration: none;
display: block;
font-size: 14px;
margin: 4px 6px;
cursor: pointer;
color: white;
border-radius: 0px;
background-color: rgb(59, 121, 183);
border: none;
padding: 5px;
text-align: center;
}
.SampleNamespace\.TSImageUploadControl img{
display: block;
box-shadow: 0 5px 10px 0 rgba(30, 30, 30, 0.3);
width: 100px;
height: 100px;
}
.SampleNamespace\.TSImageUploadControl .NoImage>.RemoveButton {
display: none;
}
.SampleNamespace\.TSImageUploadControl label{
display: none;
}
.SampleNamespace\.TSImageUploadControl .ShowError>label{
display: block;
color: red;
}

3. img フォルダを作成して、既定の画像を指定。今回は本家のサンプルにあった画像をそのまま利用。

4. ターミナルより以下コマンドでビルドを実行。

npm run build

index.ts の編集

1. まずは import の追加と定数の定義。

import {IInputs, IOutputs} from "./generated/ManifestTypes";
// 既定の画像名
const DefaultImageFileName:string = "default.png";
// エラー表示用クラス名
const ShowErrorClassName = "ShowError";
// 画像無し用クラス名
const NoImageClassName = "NoImage";
// 画像削除用クラス名
const RemoveButtonClassName = "RemoveButton";

2. クラスとプロパティの定義。

export class TSImageUploadControl implements ComponentFramework.StandardControl<IInputs, IOutputs> 
{
    // 画像の値 
    private _value: string | null;
    // コンテキスト
    private _context: ComponentFramework.Context<IInputs>;
    // 変更を画面に通知するイベントハンドラー
    private _notifyOutputChanged: () => void;
    // コントロールをホストするコンテナ
    private _container: HTMLDivElement;
    // 画像アップロードボタン
    private uploadButton: HTMLButtonElement;
    // 画像削除ボタン
    private removeButton: HTMLButtonElement;
    // 画像エレメント
    private imgElement: HTMLImageElement;
    // エラー用ラベル
    private errorLabelElement: HTMLLabelElement;
}

3. init 初期化メソッドを追加。

/**
    * init メソッド
    * @param context : 各種オブジェクトや API へのアクセスを提供するコンテキスト
    * @param notifyOutputChanged : 出力変更通知のコールバック
    * @param state : 前回保存したステート
    * @param container : UI コントロールを保持するコンテナ
    */
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
{
    this._context = context;
    this._notifyOutputChanged = notifyOutputChanged;
    // コンテナ
    this._container = document.createElement("div");
    // アップロードボタンの作成およびイベントの設定
    this.uploadButton = document.createElement("button");
    this.uploadButton.innerHTML = "画像のアップロード";
    this.uploadButton.addEventListener("click", this.onUploadButtonClick.bind(this));
    // 画像エレメントの作成
    this.imgElement = document.createElement("img");
    // 削除ボタンの作成およびイベントの設定
    this.removeButton = document.createElement("button");
    this.removeButton.classList.add(RemoveButtonClassName);
    this.removeButton.innerHTML = "画像の削除"
    this.removeButton.addEventListener("click", this.onRemoveButtonClick.bind(this));
    // エラーラベルの作成
    this.errorLabelElement = document.createElement("label");
    // 画像データがある場合は表示
    if(this._context.parameters.value.raw)
    {
        this.imgElement.src = context.parameters.value.raw;
    }
    else
    {
        this.setDefaultImage();
    }
    // すべてのコントロールをコンテナに設定し PCF に設定
    this._container.appendChild(this.uploadButton);
    this._container.appendChild(this.imgElement);
    this._container.appendChild(this.removeButton);
    this._container.appendChild(this.errorLabelElement);
    container.appendChild(this._container);
}

4. PCF からの変更通知を処理する updateView メソッドを追加。ここではコンテキストのみ保存。

public updateView(context: ComponentFramework.Context<IInputs>): void
{
    this._context = context;
}

5. ボタンクリック時の処理である onUploadButtonClick と onRemoveButtonClick メソッドを追加。

  • context.device.pickFile: ファイル選択のダイアログを開く
private onUploadButtonClick(event: Event): void 
{	
    // context.device.pickFile(successCallback, errorCallback) でファイルを取得
    this._context.device.pickFile().then(this.processFile.bind(this), this.showError.bind(this));
}

private onRemoveButtonClick(event: Event): void 
{
    // 規定の画像にセット
    this.setDefaultImage();
}

6. ボタンの処理から呼ばれる processFile メソッドを追加。

  • string|undefined と記述することで複数の型を指定可能
  • pop() : 配列の最後を取得
private processFile(files: ComponentFramework.FileObject[]): void
{
    // ファイルが選択された場合
    if(files.length > 0)
    {
        // 初めのファイルだけ処理
        let file: ComponentFramework.FileObject = files[0];
        try
        {
            // string または undefined 型として拡張子を取得
            let fileExtension: string|undefined;
            if(file && file.fileName)
            {
                fileExtension = file.fileName.split('.').pop();
            }
            if(fileExtension)
            {
                this.setImage(true, fileExtension, file.fileContent);
                this._container.classList.remove(NoImageClassName);
            }
            else
            {
                this.showError();
            }
        }
        catch(err)
        {
            this.showError();
        }
    }
}

7. 画像設定関連のメソッドを追加。

  • context.resources.getResource : リソースを取得。ここでは既定の画像を取得
private setDefaultImage():void
{
    // context.resources よりリソースとして登録している画像を取得
    this._context.resources.getResource(DefaultImageFileName, this.setImage.bind(this, false, "png"), this.showError.bind(this));
    this._container.classList.add(NoImageClassName);
    // 画像がある場合は値を削除
    if(this._context.parameters.value.raw)
    {
        this._value = null;
        this._notifyOutputChanged();
    }
}

/**
* 画像の設定
* @param shouldUpdateOutput PCF 側の値を戻すか指定
* @param fileType ファイルの種類。"png", "gif", "jpg"
* @param fileContent base64 フォーマットの画像データ
*/
private setImage(shouldUpdateOutput:boolean, fileType: string, fileContent: string): void
{
    // 画像のタイプと合わせて画像エレメント用にソースデータを作成して設定
    let imageUrl:string = this.generateImageSrcUrl(fileType, fileContent);
    this.imgElement.src = imageUrl;
    if(shouldUpdateOutput)
    {
        this._container.classList.remove(ShowErrorClassName);
        this._value = imageUrl;
        this._notifyOutputChanged();
    }
}

/**
* 画像ソース用のデータ作成
* @param fileType ファイルの種類。"png", "gif", "jpg"
* @param fileContent base64 フォーマットの画像データ
*/
private generateImageSrcUrl(fileType: string, fileContent: string): string
{
    return  "data:image/" + fileType + ";base64, " + fileContent;
}

8. エラー出力用のメソッドを追加。

private showError(): void
{
    this.errorLabelElement.innerText = "ファイルが見つかりませんでした";
    this._container.classList.add(ShowErrorClassName);
}

9. 必須である getOutputs と destroy メソッドを追加。

public getOutputs(): IOutputs
{
    let result: IOutputs = 
        {
            value: this._value!
        };
        return result;
}

public destroy(): void
{
}

コントロールのパッケージ化と配布

これまでと同じ方法でパッケージ化と配布を実行。

1. SampleSolution フォルダに移動し、以下のコマンドを実行。

pac solution add-reference --path ../ImageUploadControl

2. SampleSolution\Other\Solution.xml でバージョン情報を更新。
image.png

3. 以下コマンドでパッケージをビルド。

msbuild /restore
msbuild /p:configuration=Release

4. https://make.powerapps.com に接続。管理者権限でログイン。すでに SampleSolution を入れた環境で Solutions を選択し、Import をクリック。コンパイルした zip をインポート。インポート時にパッケージが更新される旨が表示されるので、そのまま次へ。

5. 次の画面も既定のまま「インポート」をクリック。

6. インポートが終わったら Data | Entities | 取引先企業 | Fields で「Add field」をクリック。
image.png

7. Data Type で 「Multiline Text」を選択し、「Advanced options」をクリック。

8. 値に最大値である 1,048,576 を設定して OK をクリック。フィールド一覧で「Save Entity」をクリックして変更を確定。

9. Forms | 取引先企業フォームを開き、「Switch to classic」をクリック。
image.png

10. 作成した image フィールドをフォームに配置。プロパティよりコントロールを選択し、開発したコントロールを追加。
image.png

11. すべての保存してフォームを公開。

動作確認

最後に動作を確認しましょう。

1. アプリより取引先企業のレコードを開き、開発したコントロールを確認。
image.png

2. 「画像のアップロード」をクリックして任意の画像を開く。
image.png

3. レコードを保存後、レコードを開きなおして画像が存在することを確認。ビューで image 列を表示すれば実際のデータも確認可能。
image.png

まとめ

今回のソリューションは画像を base64 文字列としてそのまま保存していますが、Azure Storage に保存するなどいろいろやり方は考えられます。次回は Web API のサンプルを見ていきます。

目次へ戻る
次の記事へ

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?