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.
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
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.
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.
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.
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:
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
Discover more from Debajit's Power Apps & Dynamics 365 Blog
Subscribe to get the latest posts sent to your email.
Nice article! If you want ts to understand Xrm.Page just install @type/xrm: npm install –save-dev @types/xrm
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
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
Hi Mike. Thanks for reading the article.
Yeah it’s been deprecated and I made reference to it somewhere in the same blog. However I tried using formContext which is passed but it is kind of not a full proof solution. You can refer my blog for this – https://debajmecrm.com/2019/05/09/getting-formcontext-in-power-apps-custom-component-framework-gotchas/
Cheers!
Debajit