So many new features came up in April release but the one I was very excited about was creating custom controls using PowerApp Component Framework. If you are not sure what custom control is please read this article.

I am documenting my experience of developing the custom controls (end-to-end) in this blog.

Prepare your development environment

Installations

  1. Install npm from here. NTS or Current – any version of npm will do
  2. If you don’t have Visual Studio 2017 then download and install VS2017
  3. Download and Install PowerApp CLI on your machine from here

Currently PowerApps CLI is supported only on Windows 10

Create & configure PowerApp Component Framework components

  1. Create or identify your working folder. In my case it is “C:\Users\dnaglekar\Source\Workspaces\xxx\PowerAppCustomControl”
  2. Open Visual Studio command prompt
  3. Navigate to you recently created folder (step 1) using cd command. In my case it is
    cd C:\Users\dnaglekar\Source\Workspaces\xxx\PowerAppCustomControl
  4. Run the pac command to create new component project. Command is as follows:
    pac pcf init --namespace [Your Namespace] --name [Your Component Name] --template [Component Type]
    

    In my case, following is the pac command:

    pac pcf init --namespace DanzMaverick --name Tags --template field

    As of today there are two component types: field and dataset.

  5. Now we want to retrieve all project dependencies. To do that we want to run the following command:
    npm install

Develop custom control

Now that the project is ready; navigate in your project folder. You can use any development tool to implement your custom control. I have used Visual Studio 2017. Below are the steps to be followed to successfully develop your control.

Following will be the file structure you will find after all project dependencies are installed.

ProjectFiles

As you can see there is one folder named “Tags” (you will see different folder as per the name of your control), that was the name of the control I gave in my pac command. Navigate inside that particular folder and you will find the following structure.

ProjectSubFolderFiles

Configure Manifest file

ControlManifest.Input.xml

This is a very important file. It contains the component’s definition. Open this file in your favorite developer interface.

First thing you would need to change in this file is the definition of the custom control. You will see an example configuration with a tag named “control” – shown below:

<control namespace="DanzMaverick" constructor="Tags" version="0.0.1" display-name-key="Control_Display_Key" description-key="Control_Desc_Key" control-type="standard">
Following are the attributes of control tag:
  • namespace: this was provided earlier in the pac command
  • constructor: this is the name of your control that you had provided in the pac command
  • version: change it if needed; else keep what is
  • display-name-key: change it to be the display name (no spaces) of your custom control
  • description-key: change it to be the description of your custom control that you want to show in D365
  • control-type: do not change this

Next you will find is a tag named “property” – shown below:

<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />

Following are the attributes of property tag:

  • name: change it to be the name of your control
  • display-name-key: change it to be the display name (no spaces) of your custom control
  • description-key: change it to be the description of your custom control that you want to show in D365
  • of-type: if your control is going to support only single data-type then use of-type attribute.
    Valid values are:

    • Currency
    • DateAndTime.DateAndTime
    • DateAndTime.DateOnly
    • Decimal
    • Enum
    • FP
    • Multiple
    • Optionset
    • SingleLine.Email
    • SingleLine.Phone
    • SingleLine.Text
    • SingleLine.TextArea
    • SingleLine.Ticker
    • SingleLine.URL
    • TwoOptions
    • Whole.None

    More information about of-type values is here

  • of-type-group: if your control is going to support multiple data-type then use of-type-group attribute. If you use this attribute then you have to define the type-group tag and the name of that type-group should be mentioned here.
    Below is a sample for defining a type-group:

    <type-group name="numbers"> 
      <type>Whole.None</type>
      <type>Currency</type>
      <type>FP</type>
      <type>Decimal</type>
     </type-group>
    

After you configured control and property tags; you should move towards the end of manifest file. You’ll find resource tag. This tag lists three type of child tags; code, css & resx.

  • code: it contains the relative path to the typescript file that will contain the code for our custom control
  • css: it contains the relative path of the css file that our custom control will use while rendering the controls
  • resx: it contains the relative path of resx file that will contain localized string

Make sure that if you are referencing relative path; it is correct

My final manifest file looks like below:

<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="DanzMaverick" constructor="Tags" version="0.0.1" display-name-key="DanzMaverick.Tags" description-key="Takes your comma separated string and converts it into Tags" control-type="standard">
    <!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
    <type-group name="forTags">
      <type>SingleLine.Text</type>
      <type>SingleLine.TextArea</type>
    </type-group>
    <property name="Tags" display-name-key="Tags" description-key="Tags" of-type-group="forTags" usage="bound" required="true" />
    <!-- 
      Property node's of-type attribute can be of-type-group attribute. 
      Example:
      <type-group name="numbers">
        <type>Whole.None</type>
        <type>Currency</type>
        <type>FP</type>
        <type>Decimal</type>
      </type-group>
      <property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
    -->
    <resources>
      <code path="index.ts" order="1"/>
      <css path="css/index.css" order="1" />
      <!-- UNCOMMENT TO ADD MORE RESOURCES
      <css path="css/Tags.css" order="1" />
      <resx path="strings/Tags.1033.resx" version="1.0.0" />
      -->
    </resources>
  </control>
</manifest>

Implement logic in typescript

First open the index.ts file in your favorite development tool. I am using Visual Studio 2017. You will find following methods:

  • init: this will be the first method that system will invoke. All your design should happen in this method
  • updateView: this method is invoked when property bag is changed; which includes fields, data-sets, global variables such as height and/or width
  • getOutputs: this method is called prior to receiving any data
  • destroy: add your cleanup code here

In my case as I am creating a control to read a comma-separated value in the text field and create tag-like elements. So follow along with manipulating the index.ts file.

HTML Elements

Let’s start by adding HTML elements we need. As we are writing a .ts file we do not have HTML tags (per say). But using the power of typescript we are going to create dynamic HTML elements in the form of variables and later use them in the init function to add it on the control.

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

export class Tags implements ComponentFramework.StandardControl {

/**
* Variables for HTML element
*/
private tagsElement: HTMLElement;
private spaceElement: HTMLElement;
private refreshButton: HTMLElement;

I have added an element for my tags and space. I have also added a button for refresh. I am going for the following look & feel.

Pill
Global Variables

Now that I have decided what all HTML elements I need; let’s decide all variables I may need during the implementation process. I have decided I will need a variable to store the value that will contain a comma-separated string. So adding to the above code tagsString will be the variable that will contain the attribute value.

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

export class Tags implements ComponentFramework.StandardControl {

/**
* Variables for HTML element
*/
private tagsElement: HTMLElement;
private spaceElement: HTMLElement;
private refreshButton: HTMLElement;

/**
* Variables for Properties
*/
private tagsString: string;

As I said it is not an HTML so we have to define variable for all. Hence, let’s define variable for event listener for the “refresh button”. Adding to the same code and expanding it – “refreshClicked” will be my event listener when “Refresh” button is clicked.

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

export class Tags implements ComponentFramework.StandardControl {

/**
* Variables for HTML element
*/
private tagsElement: HTMLElement;
private spaceElement: HTMLElement;
private refreshButton: HTMLElement;
private divElement: HTMLElement;

/**
* Variables for Properties
*/
private tagsString: string;

/**
* Variables for Event Listener
*/
private refreshClicked: EventListenerOrEventListenerObject;

We also need some local variable to capture the input parameter from init function. Expanding the code; it looks like below.

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

export class Tags implements ComponentFramework.StandardControl {

/**
* Variables for HTML element
*/
private tagsElement: HTMLElement;
private spaceElement: HTMLElement;
private refreshButton: HTMLElement;
private divElement: HTMLElement;

/**
* Variables for Properties
*/
private tagsString: string;

/**
* Variables for Event Listener
*/
private refreshClicked: EventListenerOrEventListenerObject;

/**
* Local Variables
*/
private localContext: ComponentFramework.Context;
private localNotifyOutputChanged: () => void;
private localContainer: HTMLDivElement;
init() function

In this function, let’s first initialize local variables from the input parameters. Then create a functions to handle events; in my case, I have created refreshClick() function. Now, bind the event handlers to the variables defined for event listening.

From here onward the logic to add the HTML elements may defer based on the logic, in my case, I am adding refresh button first to the body of the control; to do so I have written the below code.

// Refresh button
this.refreshButton = document.createElement("button");
this.refreshButton.setAttribute("type", "button");
this.refreshButton.setAttribute("value", "Refresh");
this.refreshButton.setAttribute("class", "btn btn-default btn-sm glyphicon glyphicon-refresh");
this.refreshButton.addEventListener("click", this.refreshClick);

// Add elements to container
this.localContainer.appendChild(this.refreshButton);

Here, I initialized my variable as input tag using document.createElement. Then I defined some attributes to that tag; in this case, type & value. Later, I added the event listener to that element. Finally, I add my element to the div container.

Now, I read the attribute value bound to the control from the context, split the values based on comma and in the for-loop I initialize my tags tag as span HTML element and set class attributes on that element.

Below is my entire init() function

/**
* 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, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {
 // Init local variables
 this.localContext = context;
 this.localNotifyOutputChanged = notifyOutputChanged;
 this.localContainer = container;

 // Register EventHandler
 this.refreshClicked = this.refreshClick.bind(this);

 // Refresh button
 this.refreshButton = document.createElement("button");
 this.refreshButton.setAttribute("type", "button");
 this.refreshButton.setAttribute("value", "Refresh");
 this.refreshButton.setAttribute("class", "btn btn-default btn-sm glyphicon glyphicon-refresh");
 this.refreshButton.addEventListener("click", this.refreshClick);

 // Add elements to container
 this.localContainer.appendChild(this.refreshButton);

 // @ts-ignore 
 var crmTagStringsAttributeValue =  context.parameters.Tags.raw;
 var data = crmTagStringsAttributeValue.split(",");

 for (var i in data) {
 // Create controls
 // Tag element
 this.tagsElement = document.createElement("span");
 this.tagsElement.setAttribute("class", "badge badge-pill badge-primary");
 var ele = this.localContainer.appendChild(this.tagsElement);
 ele.innerHTML = data[i];

 // Space element
 this.spaceElement = document.createElement("span");
 var space = this.localContainer.appendChild(this.spaceElement);
 space.innerHTML = "  ";
 }
}

Below is my refreshClick() function

/**
* Custom Event Handlers
 */
public refreshClick(evnt: Event): void {
	this.localNotifyOutputChanged();
}
updateView() function

In here, we need to refresh the HTML elements based on the new data entered on the screen.

Below is my updateView() function

/**
* 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): void {
 // Add code to update control view

        // @ts-ignore 
        var crmTagStringsAttributeValue =  // @ts-ignore 
 var crmTagStringsAttributeValue =  context.parameters.Tags.raw != null ?  context.parameters.Tags.raw : "red,green,blue";
        var data = crmTagStringsAttributeValue.split(",");

        // Delete all elements first
        var tagCollection = this.localContainer.getElementsByTagName("span");
        var loopLength = tagCollection.length;
        for (var ti = 0; ti < loopLength; ti++) {
            this.localContainer.removeChild(tagCollection[0]);
        }

        // Add new tags
        for (var i in data) {
            // Create controls
            // Tag element
            this.tagsElement = document.createElement("span");
            this.tagsElement.setAttribute("class", "badge badge-pill badge-primary");

            var ele = this.localContainer.appendChild(this.tagsElement);
            ele.innerHTML = data[i];

            // Space element
            this.spaceElement = document.createElement("span");

            var space = this.localContainer.appendChild(this.spaceElement);
            space.innerHTML = "  ";
        }
}

Use // @ts-ignore to use Xrm.Page module

My entire index.ts code file

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

export class Tags implements ComponentFramework.StandardControl {

    /**
     * Variables for HTML element
     */
    private tagsElement: HTMLElement;
    private spaceElement: HTMLElement;
    private refreshButton: HTMLElement;
    private divElement: HTMLElement;

    /**
     * Variables for Properties
     */
    private tagsString: string;

    /**
     * Variables for Event Listener
     */
    private refreshClicked: EventListenerOrEventListenerObject;

    /**
     * Local Variables
     */
    private localContext: ComponentFramework.Context;
    private localNotifyOutputChanged: () => void;
    private localContainer: HTMLDivElement;

	/**
	 * 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 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, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {
        // Init local variables
        this.localContext = context;
        this.localNotifyOutputChanged = notifyOutputChanged;
        this.localContainer = container;

        // Register EventHandler
        this.refreshClicked = this.refreshClick.bind(this);

        // Refresh button
        this.refreshButton = document.createElement("button");
        this.refreshButton.setAttribute("type", "button");
        this.refreshButton.setAttribute("value", "Refresh");
        this.refreshButton.setAttribute("class", "btn btn-default btn-sm glyphicon glyphicon-refresh");
        this.refreshButton.addEventListener("click", this.refreshClick);

        // Add elements to container
        this.localContainer.appendChild(this.refreshButton);

        // CRM attributes bound to the control properties. 
        // @ts-ignore 
        var crmTagStringsAttribute = context.parameters.Tags.attributes.LogicalName;

        // @ts-ignore 
        var crmTagStringsAttributeValue = Xrm.Page.getAttribute(crmTagStringsAttribute).getValue();
        var data = crmTagStringsAttributeValue.split(",");

        for (var i in data) {
            // Create controls
            // Tag element
            this.tagsElement = document.createElement("span");
            this.tagsElement.setAttribute("class", "badge badge-pill badge-primary");

            var ele = this.localContainer.appendChild(this.tagsElement);
            ele.innerHTML = data[i];

            // Space element
            this.spaceElement = document.createElement("span");

            var space = this.localContainer.appendChild(this.spaceElement);
            space.innerHTML = "  ";
        }
    }


	/**
	 * 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): void {
        // Add code to update control view

        // CRM attributes bound to the control properties. 
        // @ts-ignore 
        var crmTagStringsAttribute = this.localContext != null && this.localContext.parameters != null ? this.localContext.parameters.Tags.attributes.LogicalName : null;

        // @ts-ignore 
        var crmTagStringsAttributeValue = crmTagStringsAttribute != null ? Xrm.Page.getAttribute(crmTagStringsAttribute).getValue() : "red,green,blue";
        var data = crmTagStringsAttributeValue.split(",");

        // Delete all elements first
        var tagCollection = this.localContainer.getElementsByTagName("span");
        var loopLength = tagCollection.length;
        for (var ti = 0; ti < loopLength; ti++) {
            this.localContainer.removeChild(tagCollection[0]);
        }

        // Add new tags
        for (var i in data) {
            // Create controls
            // Tag element
            this.tagsElement = document.createElement("span");
            this.tagsElement.setAttribute("class", "badge badge-pill badge-primary");

            var ele = this.localContainer.appendChild(this.tagsElement);
            ele.innerHTML = data[i];

            // Space element
            this.spaceElement = document.createElement("span");

            var space = this.localContainer.appendChild(this.spaceElement);
            space.innerHTML = "  ";
        }
    }

	/** 
	 * 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 {
            Tags: this.tagsString
        };
    }

	/** 
	 * 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.refreshButton.removeEventListener("click", this.refreshClick);
    }

    /**
     * Custom Event Handlers
     */
    public refreshClick(evnt: Event): void {
        this.localNotifyOutputChanged();
    }
}

Customize UI using CSS

As you may have observed I have used class attributes on my HTML tags but that would not render unless I add the class definitions in the .css file

.badge-primary {
    color: #fff;
    background-color: #007bff;
}

.badge-pill {
    padding-right: 0.6em;
    padding-left: 0.6em;
    border-radius: 10rem;
}

.badge {
    display: inline-block;
    padding: 0.25em 0.4em;
    font-size: 75%;
    font-weight: bold;
    line-height: 1;
    color: #fff;
    text-align: center;
    white-space: nowrap;
    vertical-align: baseline;
    border-radius: 0.25rem;
}

button {
    text-rendering: auto;
    color: initial;
    letter-spacing: normal;
    word-spacing: normal;
    text-transform: none;
    text-indent: 0px;
    text-shadow: none;
    display: inline-block;
    text-align: start;
    margin: 0em;
    font: 400 13.3333px Arial;
}

.btn-sm {
    padding: 5px 10px;
    font-size: 12px;
    line-height: 1.5;
    border-radius: 3px;
}

.btn-default {
    color: #333;
    background-color: #fff;
    border-color: #ccc;
}

.btn {
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    -ms-touch-action: manipulation;
    touch-action: manipulation;
    cursor: pointer;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background-image: none;
    border: 1px solid transparent;
    border-radius: 4px;
}

.glyphicon {
    position: relative;
    top: 1px;
    display: inline-block;
    font-family: 'Glyphicons Halflings';
    font-style: normal;
    font-weight: 400;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.glyphicon-refresh:before {
    content: "\e031";
}

Build the project

Again, open Visual Studio command prompt and navigate to the folder where the index.ts file resides using cd command.

To build the package, run npm run build command as shown below.

build-command

If the build is succeeded, then we can test it by running the control in a browser. To do so, you will need to use npm start command as shown below. In my case, as init() function needs an input to load I have hard-coded some default values to test.

run-command

When testing in the browser; screen might look something like this. In my case as I do not have any output sent back I don’t see anything in there but if you have output sent back from the control you’ll see your output variables values as well.

testing-screen

You can use debugger in your index.ts file and using F12 you can do a step-by-step debugging

Create a solution package for D365 CE

For this create a new directory inside your control folder. In my case I have created a folder named “deployment” inside the “Tags” folder.

deployment-folder

Now, use cd command to navigate inside this newly created folder and run the following command to create a new solution project for D365 CE.

pac solution init --publisherName [publisher name] --customizationPrefix [publisher prefix]

For example, in my case the command was

pac solution init --publisherName DanzMaverick --customizationPrefix dm

Once the solution project is created we need to add the component into this solution. To do this, we need to use the following command. The path needs to be where the project file (pcfproj) resides

pac solution add-reference --path [path or relative path of your PowerApps component framework project on disk]

In my case the command was

pac solution add-reference --path C:\Users\dnaglekar\Source\Workspaces\xxx\PowerAppCustomControl
deployment-screen

Once the above command is executed, you’ll see deployment.cdsproj created.

deployment-folder-after-command-executes

We now have to run few more commands to create the .zip file we need for importing the solution in D365 CE. To do so, we need to execute msbuild /t:restore command followed by msbuild command.

After running both the command, if you navigate to your “deployment” folder (bin->debug) you should see a .zip file created.

SolutionZipFile

Import this solution zip file in any of your favorite D365 CE instance and publish customization.

Custom Control in D365 CE

Let’s look at how to configure the custom control in D365 CE. I have created a custom attribute called “Tag” on “Account” entity which contains comma-separated value. I am going to customized the form to add a control to this attribute.

Customization-Custom-Control.gif

Once the form is customized and published; let’s navigate to the Account form and see the custom control in action.

CustomControl-Final
Update-InAction

Based on the data it created several tags. A basic custom control to give you a glimpse of what we can do with it.

Please leave a feedback or comment below. Thank you

45 comments

  1. I get an error every time I do the last step when creating the solution package. When I run the command “msbuild” I get the following message:

    C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\bin
    \Microsoft.Common.CurrentVersion.targets(2628,7): error MSB4057: The target “Cr
    eateManifestResourceNames” does not exist in the project. [C:\PCF\TestControls\
    TestControls.pcfproj]

    This test project was created following the instructions documented here but none of the customizing. I have updated NPM and Node.js to the latest version and still get the error. Any ideas?

    Like

      1. I found the issue. I had to restore .net within the project directory(s) first and then it was able to build properly within the solution project.

        Like

  2. Hi Danish,

    I am getting an error “Object doesn’t support property or method ‘localNotifyOutputChanged'”. Is this function missing your code or am I missing something.

    Regards,
    Pramod

    Like

    1. That is a locally declared variable. Before the init() function you should declare some local variable. Look at a section named “Global Variables” which is above the section “init() function”

      /**
      * Local Variables
      */
      private localContext: ComponentFramework.Context;
      private localNotifyOutputChanged: () => void;
      private localContainer: HTMLDivElement;

      Like

  3. Hi Danish,

    I am trying to bind quote status to an input parameter with type OptionSet. But status is not showing in the options of entity fields. Realized that status has Datatype = Status which is system datatype.

    Is there a way to get the Quote Status value?

    I am looking at the Status Reason to bind instead. But binding this field is giving me issue also. Binding the OptionSet Input param to the Status Reason entity field is okay. But when I’m saving it, it shows me error message.
    “Message: The type declared for property quotestatus (type OptionSet) doesn�t match the type of the bound attribute (type OptionSet)Detail: ”

    Hope to get some input to you. Thanks.

    — Euclides

    Like

  4. i got an error message while using this article

    Error occured during initialization of control:
    Name.Tags.Message:’Xrm’ is not defined

    Like

      1. yes i am using here is the code

        public init(context: ComponentFramework.Context, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {
        // Init local variables
        this.localContext = context;
        this.localNotifyOutputChanged = notifyOutputChanged;
        this.localContainer = container;

        // Register EventHandler
        this.refreshClicked = this.refreshClick.bind(this);

        // Refresh button
        this.refreshButton = document.createElement(“button”);
        this.refreshButton.setAttribute(“type”, “button”);
        this.refreshButton.setAttribute(“value”, “Refresh”);
        this.refreshButton.setAttribute(“class”, “btn btn-default btn-sm glyphicon glyphicon-refresh”);
        this.refreshButton.addEventListener(“click”, this.refreshClick);

        // Add elements to container
        this.localContainer.appendChild(this.refreshButton);

        // CRM attributes bound to the control properties.
        // @ts-ignore
        var crmTagStringsAttribute = context.parameters.Tags.attributes.LogicalName;

        // @ts-ignore
        var crmTagStringsAttributeValue = Xrm.Page.getAttribute(crmTagStringsAttribute).getValue();
        var data = crmTagStringsAttributeValue.split(“,”);

        for (var i in data) {
        // Create controls
        // Tag element
        this.tagsElement = document.createElement(“span”);
        this.tagsElement.setAttribute(“class”, “badge badge-pill badge-primary”);
        var ele = this.localContainer.appendChild(this.tagsElement);
        ele.innerHTML = data[i];

        // Space element
        this.spaceElement = document.createElement(“span”);
        var space = this.localContainer.appendChild(this.spaceElement);
        space.innerHTML = ” “;
        }
        }

        Like

      2. Hi, the post was old and I found a better way to retrieve data. I have updated the post now.

        Please replace your current code
        // @ts-ignore
        var crmTagStringsAttributeValue = Xrm.Page.getAttribute(crmTagStringsAttribute).getValue();
        var data = crmTagStringsAttributeValue.split(“,”);

        with
        // @ts-ignore
        var crmTagStringsAttributeValue = context.parameters.Tags.raw;
        var data = crmTagStringsAttributeValue.split(“,”);

        Let me know if that worked. Thanks.

        Like

  5. Declare Xrm at the top where you use import
    declare var Xrm: any;

    You do not have to use @ts-ignore once you do that.

    Like

      1. I am not getting any error while I download the solution and do npm install . Are you getting any error ?

        Like

      2. Hi Asghar,

        I did not do anything in your code. Just had the npm install done after I downloaded your solution. However your solution did not contain package.json, tsconfig.json ,pcfconfig.js file, so I took them out of my own solution . Rest all same.

        Thanks

        Like

  6. hi danish,
    thanks for the nice article
    I am able to create a custom control successfully able to create a build import it into the dynamic CRM
    also I have added the custom control in the field on a from

    My question is that after binding control on-field, if anything I am writing on that field I am not able to save it in CRM, is there any way I can save it the CRM?

    Like

  7. Hi Danish,

    i got an error when write command “pac pcf init –namespace DanzMaverick –name Tags –template field” that pac is not recognaized.

    Like

  8. Hi Danish,

    While doing npm run build. I am getting below error.

    Error in index.ts file
    Generic Type ‘StandardControl’ requires 2 type of arguments
    Generic Type ‘Context’ requires 1 type of argument

    Like

  9. Hi Danish,

    While doing npm run build. I am getting below error.

    Error in index.ts file
    Generic Type ‘StandardControl’ requires 2 type of arguments
    Generic Type ‘Context’ requires 1 type of argument

    Like

    1. The logs should also provide a line number where the error is. Can you share the code snippet where the logs are pointing at a possible error?

      Like

      1. Thanks for your quick reply danish.

        It shows error in index.ts with following lie.
        (3,30)
        (26,27)
        (45,26)
        (94,32)

        Not sure if above are line number but it should this above number in bracket against each error.

        Like

  10. Hi

    i got error on this line during local context varibale initialization .
    private localContext: ComponentFramework.Context so i changed to private localContext: ComponentFramework.Context;

    After above change , facing issue in update view method .. its saying Tags doesnt exist on type IInputs

    var crmTagStringsAttributeValue = context.parameters.Tags.raw != null ? context.parameters.Tags.raw : “red,green,blue”;

    Could you please help?

    Like

  11. Hi Danish,

    During local variable initialization i got the error on below line

    private localContext: ComponentFramework.Context;
    Hence i have changed to private localContext: ComponentFramework.Context;

    after above code change now getting error on updateview method

    var crmTagStringsAttributeValue = context.parameters.Tags.raw != null ? context.parameters.Tags.raw : “red,green,blue”;

    saying Property Tags doesnt exist on type IInputs.

    Like

    1. This code was written using PCF CLI version 0.0.1 and new version has some syntax changes. I’ll update the code in the blog. Thanks for your inputs

      Like

  12. Hi Maverick,
    We are unable to deploy power apps solution while using XrmToolBox. We are seeing the below error “No profiles were found on this computer. Please run “pac auth create” to create one”. Kindly help us resolve this issue.

    Like

    1. When using “Quick Deploy” you will need authentication profiles created on that machine. To do that from the ribbon (menu) select “Authentication Profiles” > “Create Profile”. Provide your org URL. Once the command is successfully executed a profile will be created on that machine. Now you can use “Quick Deploy”.

      Like

  13. Hi Maverick,
    I am new to PCF. I have created a Org chart PCF component which works fine locally but looses it’s interactive part while using it in PowerApps. What might be the issue?

    Like

Leave a comment