
The changes inside a dataset PCF used on a subgrid might influence the other information showed on the Model-Driven Apps Forms. It could influence a counter on a form, or another subgrid which happens to show the same records. Then you need to refresh that controls. But using the Xrm object inside a PCF is unsupported. We cannot directly access the other controls directly.
If we have had a field PCF, we would have included another property in the manifest, and update the value of that property. But the combination of a dataset & bound property in the same manifest doesn’t work in model-driven apps. It is possible to build a PCF having both dataset and bound properties, but on some point you cannot go on to register it on a form. Not a very consistent behavior in my tests: sometimes you cannot find the control in the list of available PCFs, sometimes you cannot save the form customizing after you customized both the dataset and the bound property, sometimes the customizing offers you only the possibility to save a static value. It depends if you use the old or new customizing, or if the property was there from the beginning, or just added later on. By in the end, I couldn’t register this combination.
In the past I’ve thought about solutions using postMessage, which are almost supported. But the solution from this blog is 100% supported: using the output properties.The getOutputs and addOnOutputChange API methods are relative new; I’ve learned about them for the first time from Mehdi El Amri blog post . Thanks for this Mehdi!
My use case
I have a form where I’ve registered my PCF for ToDos (basically an easier way to work with “my open tasks”). On the same form I need to show a counter with my open Todos and I know that the tasks I show inside my Todos subgrid could also be listed in the Timeline control. When I mark a task as completed or canceled, or when I add or delete a task, I need to refresh the counter control and the timeline control on the form.
The solution
We cannot use a combination of dataset and bound property, but we can use a combination of dataset & output property for the PCF manifest. This one works.
I’ve took a property of type DateAndTime for my output property. We’re not able to raise events from the PCFs (yet), but my datetime property will have the same purpose: each time I want to trigger the event, I’ll set the current timestamp value for my onDataChanged property, which will trigger an event OnOutputChange on the form.
So on the form scripting I need to attach to the OnOutputChange event. For form onLoad, I register a script like this:
function AddOnPCFDataChanged(executionContext){
const formContext = executionContext.getFormContext();
const subgridName = "TodosSubgrid";
const subgrid = formContext.getControl(subgridName);
subgrid.addOnOutputChange(() =>{
const outputs = subgrid.getOutputs();
console.log(outputs);
//here I can make the needed refreshes
})
}
The complete code of the PCF and form scripting can be found in my github repository brasov2de/ToDosDataGridFluent9. The form scripting is here
The data of the output properties
We have now the event, so we can refresh the forms and the subgrids, but we can pass even some data with the output properties. Using that I don’t need to refresh the whole form after each change, I can pass the value for the open tasks counter with my event.
I’ve defined more output properties in my manifest:
Inside my PCF, the getOutputs method looks like this
public getOutputs(): IOutputs {
return {
onDataChanged : this.lastDatasetChanged,
totalCount: this.totalRecordCount,
objectOutput: { resordIds: this.dataset.sortedRecordIds, count : this.dataset.paging.totalResultCount }
};
}
- the lastDatasetChanged is set each time the user clicks on the “complete” or “cancel” buttons, I have on each row inside my PCF
- the objectOutput was my try to see if I can pass more complex data (the objectOutput property is of type “object”, so I can pass any object that way)
- to the totalCount I’ll come in a minute
The code for setting the lastDatasetChanged:
private raiseDataChanged(id:string) {
this.lastDatasetChanged = new Date;
this.notifyOutputChanged();
}
And inside the console.log in my form scripting I see this
The names of the properties are defined by concatenating the name of the PCF control on the form and the name of the output property (separated with a dot)
`${controlName}.${outputProperty}`
Each output property has a “value” property, where I can find the data passed from my PCF (even the complete JSON object for objectOutput property)
The PCF must be well documented though, in order to know in form scripting what to look for.
Providing the record count
There are several actions which changes the data. It could be the “complete” or “cancel” button, changing the subject of the task, adding or deleting a record.
My “complete” button is using this code (set the values for statecode and statuscode, triggers the dataset.save() and then a dataset.refresh() (so using the dataset setValue and save methods). The updated data is provided only after I call the dataset.refresh(), but not directly in a callback: after dataset.refresh the platform will call another updateView. So in onClick event I can only presume the total count.
const complete = (item:any) => {
const id = item.getRecordId();
(dataset.records[id] as any).setValue("statecode", 1);
(dataset.records[id] as any).setValue("statuscode", 5);
(dataset.records[id] as any).save().then(() => {
onChanged(id);
dataset.refresh()
});
}
Also, this won’t update my output parameters if a new record gets added (using the “+ New Task” button).
An easier way to get the count, is checking inside may PCF updateView method: if the totalRecordCount was changed, I can raise an event:
public updateView(context: ComponentFramework.Context): ReactElement {
/...
this.dataset = context.parameters.dataset;
if(this.totalRecordCount != this.dataset.paging.totalResultCount){
this.totalRecordCount = this.dataset.paging.totalResultCount;
this.lastDatasetChanged = new Date();
this.notifyOutputChanged();
}
return createElement(ToDos, props);
}
Now the counter is accurate.
Of course I wouldn’t rely only on this in order to save the count to dataverse, because other subgrids/grids or plugIns/Flows might change the counter too. This is only a fast way to show the counter from the available data, without having to make another request to refresh the form/the counter control.
So now I can change the form scripting to refresh the needed controls. This is the final form scripting:
function AddOnPCFDataChanged(executionContext){
const formContext = executionContext.getFormContext();
const subgridName = "TodosSubgrid";
const subgrid = formContext.getControl(subgridName);
const timeline = formContext.getControl("Timeline");
const counter = formContext.getAttribute("diana_counter");
subgrid.addOnOutputChange(() =>{
//get the output data
const outputs = subgrid.getOutputs();
let newcounter = outputs[`${subgridName}.totalCount`];
//refresh the timeline
timeline.refresh();
// set the value for the counter control on the form
counter.setValue(newcounter.value);
})
}
Now we can keep that form in sync with all the changes