Implementing increment component

[This topic is pre-release documentation and is subject to change.]

This sample component shows how to bind data with PowerApps component framework and error handling. This component renders as a textbox with an Increment button in the runtime. The text box shows the current value and the Increment button is clickable. Whenever you click on the button, the value within the textbox is increased by 1. The increment value can be changed to any number you wish.

To implement this component, the first thing you need to define the Manifest file, and then implement the custom logic in TypeScript.

Increment component

Manifest

<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="SampleNamespace" constructor="TSIncrementControl" version="1.0.0" display-name-key="TSIncrementControl_Display_Key" description-key="TSIncrementControl_Desc_Key" control-type="standard">
    <type-group name="numbers">
      <type>Whole.None</type>
      <type>Currency</type>
      <type>FP</type>
      <type>Decimal</type>
    </type-group>
    <property name="value" display-name-key="value_Display_Key" description-key="value_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
    <resources>
      <code path="index.ts" order="1" />
	    <css path="css/TS_IncrementControl.css" order="1" />
      <resx path="strings/TSIncrementControl.1033.resx" version="1.0.0" />
    </resources>
  </control>
</manifest>

Code

import {IInputs, IOutputs} from "./generated/ManifestTypes";
export class TSIncrementControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
	// Value of the field is stored and used inside the control 
	private _value: number;
	// PowerApps component framework framework delegate which will be assigned to this object which would be called whenever an update happens. 
	private _notifyOutputChanged: () => void;
	// label element created as part of this control
	private label: HTMLInputElement;
	// button element created as part of this control
	private button: HTMLButtonElement;
	// Reference to the control container HTMLDivElement
	// This element contains all elements of our custom control example
	private _container: 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 control is marked control-type='standard', it receives an empty div element within which it can render its content.
	 */
	public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
	{
		// Creating the label for the control and setting the relevant values.
		this.label = document.createElement("input");
		this.label.setAttribute("type", "label");
		this.label.addEventListener("blur", this.onInputBlur.bind(this));
		//Create a button to increment the value by 1.
		this.button = document.createElement("button");
		// Get the localized string from localized string 
		this.button.innerHTML = context.resources.getString("TS_IncrementControl_ButtonLabel");
		this.button.classList.add("SimpleIncrement_Button_Style");
		this._notifyOutputChanged = notifyOutputChanged;
		//this.button.addEventListener("click", (event) => { this._value = this._value + 1; this._notifyOutputChanged();});
		this.button.addEventListener("click", this.onButtonClick.bind(this));
		// Adding the label and button created to the container DIV.
		this._container = document.createElement("div");
		this._container.appendChild(this.label);
		this._container.appendChild(this.button);
		container.appendChild(this._container);
	}
	/**
	 * Button Event handler for the button created as part of this control
	 * @param event
	 */
	private onButtonClick(event: Event): void {this._value = this._value + 1; this._notifyOutputChanged();
	}
	/**
	 * Input Blur Event handler for the input created as part of this control
	 * @param event
	 */
	private onInputBlur(event: Event): void {
		let inputNumber = Number(this.label.value);
		this._value = isNaN(inputNumber) ? (this.label.value as any) as number: inputNumber;
		this._notifyOutputChanged();
	}
	/**
	 * 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
	{
		// This method would render the control with the updated values after we call NotifyOutputChanged
		//set the value of the field control to the raw value from the configured field
		this._value = context.parameters.value.raw;
		this.label.value = this._value != null ? this._value.toString(): "";
		if(context.parameters.value.error)
		{
			this.label.classList.add("SimpleIncrement_Input_Error_Style");
		}
		else
		{
			this.label.classList.remove("SimpleIncrement_Input_Error_Style");
		}
	}
	/** 
	 * 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
	{
		// custom code goes here - remove the line below and return the correct output
		let result: IOutputs = {
			value: this._value
		};
		return result;
	}
	/** 
	 * Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
	 * i.e. canceling any pending remote calls, removing listeners, etc.
	 */
	public destroy(): void
	{
	}
}

Resources

.SampleNamespace\.TSIncrementControl button.SimpleIncrement_Button_Style {
	text-decoration: none;
    display: inline-block;
    font-size: 14px;
    margin: 4px 6px;
    cursor: pointer;
	color: white;
    border-radius: 0px;
	background-color: rgb(59, 121, 183);
    border: none;
    padding: 5px;
    text-align: center;
}
.SampleNamespace\.TSIncrementControl button.SimpleIncrement_Input_Error_Style{
	color: red;
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
  <xsd:complexType>
	<xsd:choice maxOccurs="unbounded">
	  <xsd:element name="metadata">
		<xsd:complexType>
		  <xsd:sequence>
			<xsd:element name="value" type="xsd:string" minOccurs="0" />
		  </xsd:sequence>
		  <xsd:attribute name="name" use="required" type="xsd:string" />
		  <xsd:attribute name="type" type="xsd:string" />
		  <xsd:attribute name="mimetype" type="xsd:string" />
		  <xsd:attribute ref="xml:space" />
		</xsd:complexType>
	  </xsd:element>
	  <xsd:element name="assembly">
		<xsd:complexType>
		  <xsd:attribute name="alias" type="xsd:string" />
		  <xsd:attribute name="name" type="xsd:string" />
		</xsd:complexType>
	  </xsd:element>
	  <xsd:element name="data">
		<xsd:complexType>
		  <xsd:sequence>
			<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
			<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
		  </xsd:sequence>
		  <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
		  <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
		  <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
		  <xsd:attribute ref="xml:space" />
		</xsd:complexType>
	  </xsd:element>
	  <xsd:element name="resheader">
		<xsd:complexType>
		  <xsd:sequence>
			<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
		  </xsd:sequence>
		  <xsd:attribute name="name" type="xsd:string" use="required" />
		</xsd:complexType>
	  </xsd:element>
	</xsd:choice>
  </xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="TS_IncrementControl_ButtonLabel" xml:space="preserve">
<value>Increment</value>
<comment>Label for TS_IncrementControl's Button</comment>
</data>
</root>

When you click on the button, the value in the text box is increased by 1. The updated value will flow to PowerApps component framework through notifyOutputChanged method.

Note

You can change the increment value when you are configuring the component to the field on the form.

Edit the value in the text box, and if it is a valid integer, then it updates the value to PowerApps component framework. You can continuously click the Increment button and update it. If it’s an invalid integer, an error message pops out.

Download sample components
PowerApps component framework API Reference
PowerApps component framework Manifest Schema Reference