What would cause the PCF Framework to fire the updateView method twice on one update?
To ilustrate, here is an example using about the most basic control one can make: a text box input. Code is below.
inIt creates a simple input box with an onChange handler to notify the system when the value is changed. It doesn't set any values. I've confirmed the onChange handler isn't firing multiple times.
updateView simply sets the value of the inputBox from the context parameter it receives.
notifyOutputChanged just sends the value from the input field.
When first, the framework fires the init, then updateView functions, This populates the initial value. It does not cause the onChange handler to fire. All good.
When the user updates the value, the onChange handler fires, which calls notifyOutputChanged. In response, the framework calls getOutputs, which returns the value from the input field. After this, the framework calls the updateView method TWICE. The first time, it passes the old value, causing the updateView function to set the input value back to what it was. The 2nd Time, it passes the new value, causing it to set the correct value.
For a simple text field, this brief oscillation of value back to the old value, before settling on the new isn't a big problem, but it is causing me grief on a more complicated control with multiple bindings. I'm not sure if this is a peculiarity of the harness... My next step is to test in a real environment. If it works there, great, but having to publish to an environment to test sounds tedious. Any ideas on the cause and fix would be appreciated.
import { IInputs, IOutputs } from "./generated/ManifestTypes";
export class TextControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
private notifyOutputChanged: () => void;
//private currentValue: string;
private inputField: HTMLInputElement;
/**
* 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='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) {
//Make passed in functions available to other methods.
this.notifyOutputChanged = notifyOutputChanged;
//Build the control UI
this.inputField = document.createElement("input");
container.appendChild(this.inputField);
//Add Change Event Listener.
this.inputField.addEventListener("change", () => {
console.log("fieldOnChange Fired.");
debugger;
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 {
debugger;
this.inputField.value = context.parameters.value.raw || "";
}
/**
* 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 {
debugger;
return {
//value: this.currentValue
value: this.inputField.value
};
}
/**
* 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 {
// Add code to cleanup control if necessary
}
}