Part 4 – Develop your Power Apps Component Framework (PCF) control UI and events

Follow my blog for more interesting topics on Dynamics 365, Portals and Power Platform. For training and consulting, write to us at info@xrmforyou.com

The article is a part of series of blog posts on PowerApps Component Framework (PCF). In order to get the context I would suggest to get started from beginning.

Let’s continue from where we left. So now we are where it matters the most. In this article we will write code to develop our custom control, register events to our controls so that our control behaves dynamically with response to use interactions.

If we remember from start, we would be developing a HTML file input control and a submit button. So let’s open our code file – index.ts. I use Visual studio 2017 to open my file.

You will see a bare bone class with some methods in place that we described in one of our previous article.

Create PCF Control step by step

All we need is to implement these methods. A prior knowledge of type script definitely help. But believe me a little bit of practice and you should be up and running.

So first of all, we are going to define some variables for our control. Let’s start with defining variables for our control

image

I defined three HTML elements in typescript. To represent the file control and submit button. The breakelement is to represent the “<br />” tag.

Next I define the properties that I would play with. If you remember while creating the manifest, we defined three properties – FileName, FileSize and Content. I will define variables in my code for all three properties of the control.

Create PCF Control step by step

Next I declare variables for event handlers. I will register two event handlers. The first one to handle onchange of input file control and the second one to handle the click event of the button.

Create PCF Control step by step

The final set of variables. I will define

  • a local variable for to hold the control context which has all information regarding the control
  • one delegate to handle the notifyOutputChanged event which is fired when the control has new outputs to be received 
  • A HTML div container which will contain the final set of UI elements to be bound to the UI.
Create PCF Control step by step

So our variable declaration is done. Now the main method to focus is the init method. Below is the complete code of my control. If you observe inside the init method I am creating my control and registering the onchange and click event handlers.

Inside the change event handler of the file, I am getting the file information and calling the _notifyOutputChanged() method which in turn calls the getOutput method.

Also in the updateView method, observe how I am accessing the CRM controls bound to individual properties. And setting them with the values from properties of the control we defined earlier. The context object contains entire control information including the client details and page details as well.

Some places you would find @ts-ignore command. This is because I have used Xrm.Page which typescript is not aware of. Hence I explicitly put a statement so that the compiler ignore that line during build to prevent build errors.

Now that we are done with the control, the next step is to build and deploy. Please refer to the next article

Context screenshot:

Create PCF Control step by step

Control code:

import {IInputs, IOutputs} from “./generated/ManifestTypes”;

export class FileControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {

    /** variable for HTML elements */
    private _fileElement: HTMLInputElement;
    private _submitButton: HTMLElement;
    private _breakElement: HTMLElement;

    /** variable for file properties */
    private _fileName: string;
    private _fileSize: number;
    private _content: string;

    /** event variables */
    private _submitClicked: EventListenerOrEventListenerObject;
    private _fileChanged: EventListenerOrEventListenerObject;

    private _context: ComponentFramework.Context<IInputs>;
    private _notifyOutputChanged: () => void;
    private _container: HTMLDivElement;

    constructor()
    {

    }

    /**
     * Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
     * Data-set values are not initialized here, use updateView.
     * @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
     * @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
     * @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling ‘setControlState’ in the Mode interface.
     * @param container If a control is marked control-type=’starndard’, it will receive an empty div element within which it can render its content.
      */
    public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
    {
        // assigning environment variables.
        this._context = context;
        this._notifyOutputChanged = notifyOutputChanged;
        this._container = container;

        // register eventhandler functions
        this._submitClicked = this.submitClick.bind(this);
         this._fileChanged = this.fileChanged.bind(this);

        // file control
        this._fileElement = document.createElement(“input”);
        this._fileElement.setAttribute(“type”, “file”);
        this._fileElement.addEventListener(“change”, this._fileChanged);

       
        // break (<br/>) element
        this._breakElement = document.createElement(“br”);

        // submit button
        this._submitButton = document.createElement(“input”);
        this._submitButton.setAttribute(“type”, “button”);
        this._submitButton.setAttribute(“value”, “Submit”);
         this._submitButton.addEventListener(“click”, this._submitClicked);

        // finally add to the container so that it renders on the UI.
        this._container.appendChild(this._fileElement);
        this._container.appendChild(this._breakElement);
        this._container.appendChild(this._submitButton);
    }

    /**
     * Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
     * @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
     */
    public updateView(context: ComponentFramework.Context<IInputs>): void
    {
        debugger;

        // CRM attributes bound to the control properties.
        // @ts-ignore
        var crmFileNameAttr = this._context.parameters.FileName.attributes.LogicalName;

        // @ts-ignore
        var crmFileSizeAttr = this._context.parameters.FileSize.attributes.LogicalName;
        // setting CRM field values here.
        // @ts-ignore
        Xrm.Page.getAttribute(crmFileNameAttr).setValue(this._context.parameters.FileName.formatted);

        // @ts-ignore
        Xrm.Page.getAttribute(crmFileSizeAttr).setValue(this._context.parameters.FileSize.formatted);
     }

    /**
     * It is called by the framework prior to a control receiving new data.
     * @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
     */
    public getOutputs(): IOutputs
    {
        return {
            FileName: this._fileName,
            FileSize: this._fileSize
        };
    }

    /**
     * Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
     * i.e. cancelling any pending remote calls, removing listeners, etc.
      */
    public destroy(): void
    {
        // remove the event handlers.
        this._fileElement.removeEventListener(“change”, this._fileChanged);
        this._submitButton.removeEventListener(“click”, this._submitClicked);
    }

    // event handlers
    public fileChanged(evt: Event): void {
        debugger;
         var files = this._fileElement.files;

        if (files != null && files.length > 0) {
            var file = files[0];

            var fileName = file.name;
            var fileSize = file.size;
           
            this._fileName = fileName;
             this._fileSize = fileSize;

            // this will call the getOutputs method.
            this._notifyOutputChanged();
        }
    }

    public submitClick(evt: Event): void {
         debugger;
        // do your file processing here
        var files = this._fileElement.files;

        if(files != null && files.length > 0) {
            var file = files[0];

            var fileSize = file.size;

            if (fileSize > 1048576) {
                // you can alert here…for brevity showing Xrm.Utility..you should use Xrm.Navigation
                // @ts-ignore
                Xrm.Utility.alertDialog(“File size should be less than 1 MB”);

                this._fileName = “”;
                 this._fileSize = 0;

                this._notifyOutputChanged();
            }
        }
    }
}

You will also like the below posts

Debajit Dutta
Business Solutions MVP

4 thoughts on “Part 4 – Develop your Power Apps Component Framework (PCF) control UI and events”

    1. Hi Khoa,
      Thanks for reading my blog and for the wonderful suggestion. Yes indeed it will make xrm understandable to type script.
      Thanks for the suggestion.
      Cheers!
      Debajit

  1. Hi, great blog, quick question though, here you are referencing Xrm.Page, this is marked a deprecated in V9 as we should be using the ExecutionContext that gets passed to the form, are you able to access the new Context rather than using the Xrm namespace, if so how!?
    Thanks
    Mike

Comments are closed.