Developing a Re-Usuable PCF Component in Dynamics CRM Model Driven Apps using typescript -Part 2

Subhamay Sur
6 min readMar 10, 2024

--

To start with PCF control development , we have discussed on typescript , node.js, power component framework, pfc file structure and pac commands in deatils in our last post -

Developing a Re-Usuable PCF Component in Dynamics CRM Model Driven Apps using typescript -Part 1

now let us build a new component from scratch and will start from where we left of earlier. Our agenda is to create a new control which will auto format input text / number and we shall reuse that in different part of our CRM form.

RECAP

  1. Create a folder in your desired location where you want to save the control code.
  2. Open the ‘Developer Command Prompt for VS 2017/2019’ and use cd command to point to the ‘Workspace -> PCF Library’ folder. In this case, PCF control folder name is ‘customstringformatter’. (image1)
  3. Since ‘Init’ creates a basic folder structure, after the command execution if you go to the folder, you would see following files created (image2 & image3)
  4. If you open the files from explorer, you would see ‘Manifest’ file (ControlManifest.Input.xml) with metadata
pac pcf init --namespace CustomStringFormatter --name customstringformatter --template field
image1 -Open visual studio command promt to execute pac commands
image2 — After pac command init successfully executed , execute npm install
image3-if you open the folder, you will see all these files and folders are created

Implementing manifest — Control

The control manifest is an XML file that contains the metadata of the code component. It also defines the behavior of the code component.

The control node defines the namespace, version, and display name of the code component. Now, define each property of the control node as shown here:

  <control namespace="CustomStringFormatter" constructor="customstringformatter" version="0.0.1" display-name-key="customstringformatter" description-key="customstringformatter description" control-type="standard" >

Implementing manifest — Property

The property node defines the properties of the code component like defining the data type of the column. The property node is specified as the child element under the control element. Define the property node as shown here:

name: Name of the property.

display-name-key: Display name of the property that is displayed on the UI.

description-name-key: Description of the property that is displayed on the UI.

of-type-group: The of-type-group is used when you want to have more than two data type columns. Add the of-type-group element as a sibling to the property element in the manifest. The of-type-group specifies the component value and can contain whole, currency, floating point, or decimal values.

usage: Has two properties, bound and input. Bound properties are bound only to the value of the column. Input properties are either bound to a column or allow a static value.

required: Defines whether the property is required.

<!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
<property name="formattedField" display-name-key="Field_Display_Key" description-key="Field_Display_Key" of-type="SingleLine.Text" usage="bound" required="true" />
<property name="productType" display-name-key="productType_Display_Key" description-key="productType_Display_Key" of-type="OptionSet" usage="bound" required="true" />
<property name="regExp1" display-name-key="regExp1_Display_Key" description-key="regExp1_Display_Key" of-type="SingleLine.Text" usage="bound" required="true" />
<property name="regExp2" display-name-key="regExp2_Display_Key" description-key="regExp2_Display_Key" of-type="SingleLine.Text" usage="bound" required="true" />
ControlManifest.Input.xml

Implementing manifest — Resource

The resources node defines the visualization of the code component. It contains all the resources that build the visualization and styling of the code component. The code is specified as a child element under the resources element. Define the resources as shown here:

code: Refers to the path where all the resource files are located.

<resources>
<code path="index.ts" order="1"/>
<css path="css/customstringformatter.css" order="2" />
<!-- UNCOMMENT TO ADD MORE RESOURCES
<css path="css/customstringformatter.css" order="1" />
<resx path="strings/customstringformatter.1033.resx" version="1.0.0" />
-->
</resources>

Implementing component logic

  1. Declare all the variable which we are going to use in our code later either to construct the html design or to populate all the values using client side code.

2. Let’s first initialize local variables from the input parameters.

3. Then create functions to handle events; inputHandeler() and blurHandeler() function. It will trigger while you are typing anything in the input field.

public inputHandeler(evt: Event): void {
debugger;
var tempValue = (this.inputElement.value as any) as string;
if(tempValue.length <= this._patternLength)
{
this._notifyOutputChanged();
tempValue = tempValue.replace(/-/gi,"");
tempValue = tempValue.replace(this._regEx!,this._patternType);
this._value = tempValue.replace(/-$|--$/gi,"");
this.inputElement.value = this._value.toUpperCase();
this.inputElement.setAttribute(this._fieldName,this._value?this._value:"");
}
else
{
this._notifyOutputChanged();
this._value=tempValue.substring(0,tempValue.length-1);
this.inputElement.value=this._value.toUpperCase();
this.inputElement.setAttribute(this._fieldName,this._value?this._value:"");
}
}

public blurHandeler(evt: Event): void {
debugger;
var tempValue = (this.inputElement.value as any) as string;
}

4. Now, Create input element as a text input , bind the event handlers to the variables defined for event listening.

  public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement): void
{
// Add control initialization code
debugger;
this._context = context;
this._container = document.createElement("div");
this._notifyOutputChanged = notifyOutputChanged;
this._blurHandler = this.blurHandeler.bind(this);
this._inputHandler = this.inputHandeler.bind(this);

this.inputElement = document.createElement("input");
this.inputElement.setAttribute("type","text");
this.inputElement.setAttribute("placeholder","-----");
this.inputElement.addEventListener("blur",this._blurHandler);
this.inputElement.addEventListener("input",this._inputHandler);


}

5. After initializing the local variable and html we are now focusing on dealing with the input value. The _inputHandler and _blurHandler event handler will take each of the input value to validate against the regex. We have two regex here and that were added as a part of Component Properties declared in ControlManifest.Input.xml. By changing the Regex values we can reuse the component in different formatter like credit card , phone number or any custom input format shown in the screenshot.

 public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement): void
{
// Add control initialization code
debugger;
this._context = context;
this._container = document.createElement("div");
this._notifyOutputChanged = notifyOutputChanged;
this._blurHandler = this.blurHandeler.bind(this);
this._inputHandler = this.inputHandeler.bind(this);

this.inputElement = document.createElement("input");
this.inputElement.setAttribute("type","text");
this.inputElement.setAttribute("placeholder","-----");
this.inputElement.addEventListener("blur",this._blurHandler);
this.inputElement.addEventListener("input",this._inputHandler);

this._value = context.parameters.formattedField.raw!;
this._productType = context.parameters.productType.raw!;
this._fieldName = context.parameters.formattedField.attributes?.LogicalName!;

if(this._productType!=17180002)
{
if(context.parameters.regExp1!=null)
{
this._tempRegx = context.parameters.regExp1.raw!;
this._regxPattern = this._tempRegx.split("|",3);
this._tempRegx = this._regxPattern[0];
this._patternType = this._regxPattern[1];
this._patternLength = Number(this._regxPattern[2]);
this._regEx = RegExp(this._tempRegx);
}
}
else
{
if(context.parameters.regExp2!=null)
{
this._tempRegx = context.parameters.regExp2.raw!;
this._regxPattern = this._tempRegx.split("|",3);
this._tempRegx = this._regxPattern[0];
this._patternType = this._regxPattern[1];
this._patternLength = Number(this._regxPattern[2]);
this._regEx = RegExp(this._tempRegx);
}
}
debugger;
var currentValue = context.parameters.formattedField.formatted ? context.parameters.formattedField.formatted : "----";
this.labelElement = document.createElement("label");
this._container.appendChild(this.inputElement);
this._container.appendChild(this.labelElement);
container.appendChild(this._container);


}

6. Replace the updateView function with the function below. This logic creates the React Element from the PriorityComponent and adds it to the virtual DOM

7. Once you are done developing all the component logic, update the default destroy()function by removing the EventListenerwe have added to deal with formatting the input

public destroy(): void
{
// Add code to cleanup control if necessary
this.inputElement.removeEventListener("input",this._inputHandler);
this.inputElement.removeEventListener("blur",this._blurHandler);
}

8. Make the following modification to the file .eslintrc.json to work around a new lint rule that was too strict. a) Open the .eslintrc.json file. Locate rules and paste the rules below.

"rules": {
"no-unused-vars": "off",
"no-undef":"off",
"no-mixed-spaces-and-tab":"off",
"no-debugger":1

}

Build & Debug the project

1.Directly open the terminal and type npm run build command to build the code.

2.Once build succeed type npm start command to run the code on localhost.

Once the component is loaded in the browser, press F12 to debug and find out logical issues if any.

We will discuss packaging and deployment in next post.

--

--