PCFControl Guide
PCFControl Guide
Power Platform
02 Design Contributions
04 Components 33 Contributions
07 Typescript File
03 Develop
04 Test
Introduction
PowerApps Component Framework are used to create code components for model-driven apps and canvas
apps (preview) in order to provide an enhanced user experience for users to view and work with data in forms,
views and dashboards. PowerApps Component Framework controls can be used in the Unified Interface of
Dynamics 365.
2|Page
Prerequisites
The following steps are required in order to start developing PowerApps Component Framework custom
controls.
If you are using Visual Studio 2019, you can use either the Developer Command Prompt for Visual Studio 2019
or Developer PowerShell for Visual Studio 2019.
To download node.js, navigate to http://nodejs.org/en, and download the LTS version, which is the
recommended version for most users, and what is required for this lab.
After you have downloaded installer file, go ahead and complete the installation of node.js.
3|Page
If this is your first time installing or downloading Microsoft PowerApps CLI, you can download the msi file from
the Microsoft web site, by following the link below:
https://aka.ms/PowerAppsCLI
After you have finished download the installation file, install the Command Line Interface.
4|Page
Components
There are a few files that make up a PowerApps Component Framework custom control project. The two
primary files that make up the control are the manifest file and the typescript file.
The manifest file is an Xml file which contains information about the namespace of the control, the properties
of the control and the resources that make up the control which include the name of the typescript file, and
any stylesheet or resource files that are included with part of the project.
The stylesheet …
Manifest File
The manifest file is an Xml file which contains information about the namespace of the control, the properties
of the control and the resources that make up the control which include the name of the typescript file, and
any stylesheet or resource files that are included with part of the project.
The display-name-key is what will appear inside of properties of the control inside Dynamics form editor, when
selecting the controls tab, and adding a different control to display on web, mobile or tablet.
The description-key is the description that will show up inside of the control properties within Dynamics form
editor. The text below shown us the content of the control tag after adding the correct values to the display-
name-key and description-key attributes.
<control namespace="PCFNamespace" constructor="PCFConstructor" version="0.0.1" display-name-key=
"PCFNamespace.PCFConstructor" description-key="Custom Control for PCF" control-type="standard">
Name – this is the name of the property, which will be referred to from the typescript file in order to
read and update content from the control.
Display Name Key – this is the name of the property that would appear within Dynamics.
Description Key – this is the description of the property that would appear within Dynamics
Of Type – this attribute represents the data type of the property. When the usage of the of-type
attribute is set to bound, then you bind it to the corresponding field within Dynamics. The of-type
attribute can contains values such as Currency, DateAndTime, Decimal, Enum and more. The full list is
available on the Microsoft Document site.
Usage – this attribute can be either bound as previously stated, which means that this property is
bound to a control within Dynamics or input which means that it will be used for read-only values.
The other two elements which are includes in the manifest file and are part of the parent resources element are
css and resx. The css is the stylesheet that will be used for the control. This is not required, but if you want your
control to blend in to the Unified Interface and look somewhat like other controls on the form, this is highly
recommended.
You can also use existing styles that are available in Unified Interface and link them to the control itself. You will
see this in the next sections. The resx is a file that contains localized content in order to display data for the
control in additiona languages. For the purpose of this demo, we will not use it.
Typescript File
Same as the automatic generation of the Manifest file, the index.ts file is also generated for us when we call the
pac pcf init with the method signatures that we need to implement. Even if you don’t know typescript, this is
something that you should be able to grasp pretty quickly, especially for users with JavaScript experience.
The newly created component library (Typescript file) implements the following methods (which control the
lifecycle of the code component): init, updateView, getOutputs, destroy. The getOutputs method is optional.
context Context yes Includes the Input Properties containing the parameters,
component metadata and interface functions
The updateView contains a single context parameter which contains the values that are mapped to the name
that is defined in the manifest as well as in the utility functions.
8|Page
We will create in these next few chapters a credit card validation control, which will validate the user input on
the CRM form, and will display the logo of the selected credit card or a validation error message if the credit
card number is invalid.
In our case we will use Visual Studio Code to perform that required action. Open Visual Studio Code and from
the Home Page Start click on Open folder…
Select the location of the folder where you are going to start creating your project. In our case it will be
D:\PowerPlatform\PCF\PCFCreditCardValidator.
Once we opened the folder, click on the View menu and select Terminal. This will allow us to execute
PowerShell commands right from within Visual Studio Code.
We will use the command we mentioned earlier in previous chapters to initialize the project, by calling the pac
pcf init command.
11 | P a g e
We can now go ahead and install all the required dependencies that are required. This steps can take a few
minutes to complete. The command to install the dependencies is npm install, as was shown in the screenshot
from the previous task. While this task is executing you will see progression on your command prompt window,
and you might see a few warnings or errors.
You will notice in Visual Studio code that a node_modules folder was created with a lot of subfolders
containing script files. This contains a large variety of available modules that can be added to your typescript
project.
12 | P a g e
We will start by modifying the control element. The information here is based on what was entered in the pac
pcf init command, so likely there is not much to modify, but we can change the description or other attributes
as required. The final result here should look like this:
Next we will create a bound property for this control which will be based on a single line of text in our model
driven application. We will provide the property name, display name key and description, the type of control,
the usage (whether it is bound, input or output), and whether it is required. After making the changes it will
look like this:
Finally, we will specify the resources that make up this project. In our case, we will be using a TypeScript file
that will include the code elements, and a stylesheet that will define the look and feel of the control.
<resources>
<code path="index.ts" order="1"/>
<css path="pcfcontrols.css" order="1" />
</resources>
After making all the changes to the manifest file, and removing all of the comments, the manifest file will be
short and clear.
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="PCFControls" constructor="PCFCreditCardValidator" version="0.0.1" displ
ay-name-key="PCFControls.PCFCreditCardValidator" description-
key="Credit Card Control for PCF" control-type="standard">
<property name="CreditCardNumber" display-name-
key="PCFCreditCardValidator_CreditCardNumber" description-key="Credit Card Number field" of-
type="SingleLine.Text" usage="bound" required="true" />
<resources>
<code path="index.ts" order="1"/>
<css path="pcfcontrols.css" order="1" />
</resources>
</control>
</manifest>
14 | P a g e
/**
* Empty constructor.
*/
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 con
tains 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 h
as new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be se
t at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @param container If a control is marked control-
type='standard', 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)
{
// Add control initialization code
}
/**
* Called when any value in the property bag has changed. This includes field values, dat
a-
15 | P a g e
sets, global values such as container height and width, offline status, control metadata valu
es such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It con
tains 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
{
// Add code to update control view
}
/**
* 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 {};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this c
all for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void
{
// Add code to cleanup control if necessary
}
}
The import and export portions of the file remain untouched, as well as the constructor. We will start by
declaring variables above the construction that can be used as shared variables within the class. The list of
global variables includes the PowerApps Component Framework required parameters (context, notify changes
and the output HTML Div container), the HTML elements that will make up the control, and an event listener.
To assign the signature values to the public variables we will call the following lines of code. This will be similar
in most of your projects. Within the init method we will start by adding the following code:
Next we will add code to respond to the event of the control as shown in the function below.
// Add control initialization code
this._creditCardNumberChanged = this.creditCardChanged.bind(this);
Then, we will customize the control and add the attributes and an event to the new Input element and image
element. The input element will be where the user will enter their credit card number on the CRM form, and the
image element will be the image that will be displayed when the user enters the correct credit card number
that corresponds to the rules of the card.
// Add the image control that will display the logo of the credit card
this._creditCardTypeElement = document.createElement("img");
this._creditCardTypeElement.setAttribute("class", "pcfimagecontrol");
this._creditCardTypeElement.setAttribute("height", "24px");
We will also add HTML elements to display an error message under the HTML control in case the credit card
number is invalid. The logic of the error uses similar styles as that of Dynamics 365 Unified Interface.
// Add an error visual to show the error message when there is an invalid credit card
this._creditCardErrorElement = document.createElement("div");
this._creditCardErrorElement.setAttribute("class", "pcferrorcontroldiv");
var creditCardErrorChild1 = document.createElement("label");
creditCardErrorChild1.setAttribute("class", "pcferrorcontrolimage")
creditCardErrorChild1.innerText = " ";
this._creditCardErrorElement.appendChild(creditCardErrorChild1);
this._creditCardErrorElement.appendChild(creditCardErrorChild2);
this._creditCardErrorElement.style.display = "none";
Finally, we will add the three elements (text box, image and error HTML element) to the container that was
defined and passed by the control.
this._container.appendChild(this._creditCardNumberElement);
this._container.appendChild(this._creditCardTypeElement);
this._container.appendChild(this._creditCardErrorElement);
For our credit card field, we will get the logical name of the field, and set the value to its formatted value.
18 | P a g e
// @ts-ignore
Xrm.Page.getAttribute(crmCreditCardNumberAttribute).setValue(this._context.parameters
.CreditCardNumber.formatted);
Notice the @ts-ignore comment above the Xrm.Page.getAttribute call. When we use the Xrm namespace calls
that are used in JavaScript in Dynamics, we have to tell the compiler to ignore these calls so that it does not
error out.
We also add to the updateView method a notification in case there is a validation error. This is a notification
that appears on the top of the form, and disappears after a few seconds.
if (this._creditCardErrorElement.style.display != "none")
{
var message = "The credit card number is not valid.";
var type = "ERROR"; //INFO, WARNING, ERROR
var id = "9444"; //Notification Id
var time = 4000; //Display time in milliseconds
// @ts-ignore
Xrm.Page.ui.setFormNotification(message, type, id);
We check using regular expressions if this is a Visa, Mastercard, American Express or Discover card. If the card
is one of the above, we will display the image of the card next to the text control. If the card is not valid, we will
display an error message.
At the end of the method we call the notifyOutputChanged to let the notify the component that it needs to
update the output.
if (!isValid)
{
// Is this a Mastercard card?
_regEx = new RegExp('^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-
9]{2}|27[01][0-9]|2720)[0-9]{12}$');
if (_regEx.test(creditCardNumber))
{
isValid = true;
imageUrl = "https://xyz.blob.core.windows.net/shared/imgs/ico/mc24.png";
}
}
20 | P a g e
if (!isValid)
{
// Is this an American Express card?
_regEx = new RegExp('^3[47][0-9]{13}$');
if (_regEx.test(creditCardNumber))
{
isValid = true;
imageUrl = "https://xyz.blob.core.windows.net/shared/imgs/ico/amex24.png"
;
}
}
if (!isValid)
{
// Is this a Discover card?
_regEx = new RegExp('6(?:011|5[0-9]{2})[0-9]{12}');
if (_regEx.test(creditCardNumber))
{
isValid = true;
imageUrl = "https://xyz.blob.core.windows.net/shared/imgs/ico/disc24.png"
;
}
}
Below is the code for the destroy methods, which in our case is used only to remove the event listener change
event from the bound control.
.pcfinputcontrol
{
border-color: transparent;
padding-right: 0.5rem;
padding-left: 0.5rem;
padding-bottom: 0px;
padding-top: 0px;
color: rgb(0,0,0);
box-sizing: border-box;
border-style: solid;
border-width: 1px;
line-height: 2.5rem;
font-weight:600;
font-size: 1rem;
height: 2.5rem;
margin-right: 0px;
margin-left: 0px;
text-overflow: ellipsis;
width: 90%
}
.pcfinputcontrol:hover
{
border-color: black;
}
.pcfimagecontrol
{
padding-left: 5px;
vertical-align: middle;
}
.pcferrorcontroldiv
{
display: inline-flex;
padding-top: 0.5rem;
23 | P a g e
padding-bottom: 0.5rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
background-color: rgba(191, 9, 0, 0.075)
}
.pcferrorcontrolimage
{
font-family: "Dyn CRM Symbol", "Segoe MDL2 Assets";
font-weight: 600;
font-size: 1rem;
line-height: 2rem;
color: rgb(191, 9, 0);
padding-right: 0.5rem;
}
.pcferrorcontrollabel
{
display: initial;
color: rgb(191, 9, 0);
font-weight: 600;
font-size: 1rem;
font-family: SegoeUI-Semibold, "Segoe UI Semibold", "Segoe UI Regular", "Segoe UI";
line-height: 2rem;
word-break: break-word;
}
24 | P a g e
We can still use the Terminal command npm run build to build the component and make it available for
testing
You will notice a Succeeded message at the end of the command if no errors are encountered.
Once the build has been completed, we can run the npm start command to start the testing the application in
the PowerApps component framework Test Environment.
The first screenshot shows how the control will be display in the test environment when no errors occur.
25 | P a g e
In case of errors, we will be able to see the way the control behaves:
The code that displays the error on the top of the Model-Driven app will only be displayed within the Model-
Driven app and not in the PCF Test environment.
26 | P a g e
The first thing that we need to do is create a folder for the deployment of the solution. We will name the folder
the same as the name we want to call our solution. In this case, I created a folder called PCFCreditCardSolution.
The following steps can still be done either from within Visual Studio Code or Developer Command
Prompt/Developer PowerShell for Visual Studio.
In our case I will created a publisher called PCFControls, and use the customizationPrefix PCFC:
The following screenshot shows the results after we run this command:
27 | P a g e
You will receive a message that says: Project Reference successfully added to CDS project.
Running MSBuild
The final two steps to generate the solution zip files are to run the msbuild command. These steps can ONLY
be run from within the Developer Command Prompt/Developer PowerShell for Visual Studio.
The first time we will run it with the /t:restore parameter, and then run it by itself.
28 | P a g e
Next we will run msbuild in the same directory. We will be able to see the solution file required to import after
that.
Once you navigated to the solutions area, click on the Import button:
In the Select Solution package window, click on Choose File, and then navigate in the popup Open window to
the directory containing your zip file. In our case this will be the bin/Debug folder under the
PCFCreditCardSolution, as shown in the image below.
30 | P a g e
Click Next to see the solution information, and then click Import to start the Import process of the solution.
Once the import has been completed, click on Publish All Customizations button to complete the process.
When the publishing is done, click on Close.
Open the record type where you created the solution, and enter a valid credit card number. The control will
display the correct credit card image.
Now change the credit card number, so that the number is no longer valid and you will see the error messages
displayed in both the control and at the top of the form.
33 | P a g e
Contributions
Author
MVP Contributions
Alex Shlega
Andrew Butenko
Andrew Ly
Guido Preite
Microsoft Contributions
Hemant Gaur
34 | P a g e
Resources
Community PCF Gallery