Custom Events for PCF is a much awaited feature. Lately we’ve got the documentation for events:

The types are not there yet (so it seems that the events are not fully supported for now), but to be able to test them, we can add a “featureconfig.json” file to our project, and define the {“pcfAllowEvents”: “on”} there. Please note that this is unsupported, and shouldn’t be used in production fow now.

Events available before

We were able to use events before. But we were very limited, because we could use only trigger standard events.

If you are aware about out-of-the-box events, and would like to read only about the new “custom events”, you can jump to Custom Events section.

OnChange event

To trigger onChange events, all we have to do is to call the notifyOutputChanged callback provided through the parameters of the PCF “init” method. This will call the “getOutputs” method, where we can provide the new values for the properties. After that the platform runtime will evaluate the values, accept or reject them, and call the PCF “updateView” method with the valid value.

This is working both on model-driven forms and canvas apps/custom pages. But they work different:

OnChange in Model-Driven Apps

In model-driven apps we can attach to “OnChange” events only for “bound fields” (so for field PCF). For one PCF we can register several onChange callbacks; because each “bound” property can trigger one OnChange event.

Registering to OnChange events can be made using form customizing, or by using the addOnChange sdk attribute.addOnChange(yourCallback). We have only one “notifyOutputChanged”, only one “getOutputs” method. But depending on which values we pass back to the platform runtime, it could trigger more OnChange events (one for each changed attribute).

The value changed can be accessed through the eventArguments or by getting the attribute.getValue()

Attaching onChange events on model-driven forms

OnChange in Canvas Apps/CustomPages

In canvas apps/custom pages we have only one OnChange event per PCF. We can still have more properties inside the PCF, but it generates only one OnChange event. In case it’s important to know what changed, we need to treat that inside the callback.

The callback is a PowerFx expression. The changed values can be accessed by using the PCF properties.

Attaching to OnChange events in canvas apps/custom pages. Todo1 is the PCF triggering the OnChange event, and we can use the changed property totalCount

OnOutputChange – special event for model-driven apps

This is a special event for model-driven apps. This event is not registered on attributes, like the other ones on model-driven forms. We can attach the OnOutputChanged event using the control.addOnOutputChange . This is a very useful event because:

  • since is not attached to attributes, we don’t need to change dummy attribute values, just to interact with the form. We can attach to control instead.
  • it can be used also for dataset PCF. For more details, you can read my blog “Let Your Subgrid Dataset PCF Communicate with the Form“. There I’m implementing a subgrid PCF, and using the OnOutputChange event I’m able to trigger events when the aggregate values from the dataset get changed. That was not possible, before the OnOutputChange event.

When we trigger events, we need to be able to access the changed data too (the data we pass through the getOutputs() PCF method). In the case of “OnOutputChanged” event, we get the changed data using the control.getOutputs(). We get here a dictionary with the properties of type “output” (properties declared in the PCF manifest).

Custom Events – for Canvas Apps/Custom Pages

Everything above was already available. Now the new stuff 🙂

The new introduced “events” is a feature available only for canvas apps/custom pages. That’s where (for 3rd party PCFs) we had only the OnChange event until now.

Event declaration

To offer a custom event for a PCF, we need to declare it in the manifest.

 

Trigger the event

To trigger the event, we just need to call the event callback provided through

context.events.OnYourEvent()

Event arguments

We saw that the events used until now needed some arguments; a way for the attached function to access the context of the event (or the changed data). We cannot directly pass arguments to the callback function. But we can use the PCF properties for that. We only need to take care to update them before we trigger the event.

Not sure about the best practices, but here is how it worked for me:

To trigger an event, first I save the values in a private property, and add the callback ( context.events.OnMyEvent) to an array with events which need to be triggered.
Then I trigger the notifyOutputChanged; then the platform runtime will call the getOutputs for me. I try to keep the invocation of the event callback together with the new data passed. So I call the event callbacks inside the getOutputs, and then I return the event context data. As an example

const dispatchEvent = (value: any ) => {
                this.yourEventData = value;
                this.events.push((context as any).events?.OnYourEvent);
                this.notifyOutputChanged();
            } 
  public getOutputs(): IOutputs {        
        this.events.forEach((e) => e());
        this.events = [];        
        return { 
           yourEventData : this.yourEventData
        };
    }

As I said, this worked for me. I’m not very confident that this is working 100% in all cases, since I’m triggering the events right before I pass the (new) event data. In case I’ll have issues, I would change to a second approach:

Here I trigger the event only in updateView (and this is called since I call notifyOutputChanged, and provide new values). But since updateView can be called a lot of times (not only because I’m triggering the event), I need to make sure that the getOutputs was called before triggering the events (so the event properties are set). But I would consider this approach, only if the first way will make issues. Until now it worked like a charm.

Examples

You can find the code for the following examples, in my gitHub Repository ToDosDataGridFluent9

Here are a few use-cases I wanted to try out right away.

OnRecordSelect Event

You remember the ToDo PCF I’ve made to show how we can provide output properties from a dataset PCF?

That one works in Custom Pages too. It is a dataset PCF, that I’ve attached to a collection from a Dataverse Table (I’ve took the Tasks table, and filtered on open tasks)

Setting the Items for my Todo PCF.

Since I have the OnChange event out of the box, I can use my PCF output properties to make further actions (of course, the Notify() I’m using there is only an example)

I’ve also implemented the dataset.setSelectedRecordIds([id]) when a row is selected. I have a state with “selected”, and I set it using the DataGrid.onSelectionChange

const [selected, setSelected] = React.useState>(new Set(dataset.getSelectedRecordIds()));
const changeSelection = (e: any, data: any) => {
    const newIds = data.selectedItems;      
    setSelected(newIds);      
    dataset.setSelectedRecordIds(Array.from(newIds));
}
 

Now I can show the selected record in a Custom Page, using the Todo1.Selected.Subject.

Until now I don’t need an event.

But before custom events, there was no way to make an action when a record is selected. I’m using again Notify as an example, but it stands for other actions like navigating, and so on.

Now I can implement the OnRowSelected event.

//adding the event to the manifest
  

To trigger the event, I just need to add a line of code

 const changeSelection = (e: any, data: any) => {
      //....previous code
      (context as any).events.OnRecordSelected();   
  }

Now I can attach the OnRecordSelect to my ToDo PCF

And here is the notification with the subject of the selected record.

OnRowCommand – custom event with event context

In the Todo PCF I have already two commands for each row: complete and cancel. The code is implemented inside the PCF and is using dataset.setValue(), dataset.save() and dataset.refresh() to implement the commands. That works in both model-driven apps forms(subgrids) and Custom Pages.

Now I want to allow adding the 3rd command. But this one should let the maker decide what to do with this command.

For that I’ve added an event (OnRowCommand) and an output property (rowCommandOutputs) to my manifest.


     

For the implementation I have a RowCommand component:

export const RowCommand = ({rowCommand, id}: IRowCommandProps) => {
    if(rowCommand.dispatchEvent == null) return (<>);
    const classes = useStyles();
    const onClick = () => {
        if(rowCommand.dispatchEvent){
            rowCommand.dispatchEvent(id);
        } 
    }
    
    return (
        } shape="square" aria-label="Complete" color='grape' className={classes.button} 
    onClick={onClick}/>
    )
};

The definition of the dispatchEvent method:

    public updateView(context: ComponentFramework.Context): ReactElement {    
        const props : IToDosProps = { 
            dataset: context.parameters.dataset, 
            onChanged: this.raiseDataChanged.bind(this),
            isCustomPage : context.parameters.isCustomPage.raw || false,
            onRecordSelected: (context as any).events.OnRecordSelected,
            commandProps: {                          
            dispatchEvent: (context as any).events?.OnRowCommand ? (value: any ) => {
                this.rowCommandOutputs = value;
                //(context as any).events?.OnRowCommand();
                this.events.push((context as any).events?.OnRowCommand);
                this.notifyOutputChanged();
            } : undefined
           }
         };

I cannot call the OnRowCommand event directly here, since the rowCommandOutputs are not passed outside yet. Instead, I add the OnRowCommand to an array with events, and call notifyOutputChanged(). That will call my getOutputs:

    public getOutputs(): IOutputs {        
    //trigger the events here
        this.events.forEach((e) => e());
        this.events = [];        
   //return the data needed for event callback
        return { 
            onDataChanged : this.lastDatasetChanged, 
            totalCount: this.totalRecordCount, 
            objectOutput: { resordIds: this.dataset.sortedRecordIds, count : this.dataset.paging.totalResultCount }, 
            rowCommandOutputs : this.rowCommandOutputs
        };
    }

Now I’m able to register the OnRowCommand event using the canvas maker portal. I wasn’t able to pass the record reference from my PCF (maybe you know a better way). So I’ve defined the rowCommandOutputs as string, passing there the id, and I use Lookup() in PowerFx to grab the Record. Using that I can call the Patch function, and set the task to the “Status code”: “In Progress”.

Note: I know is possible to implement this command also using the dataset.selectedRecordIds the same as I did with OnRecordSelect event. In that case I don’t need my rowCommandOutputs property. But there could be some cases where I need to pass more context information for the event. I did this test because of those cases.

And here is short demo:

Conclusion

The custom events are closing a gap, offering better possibilities for PCF implementation. Looking forward to see how you will use it.

The post featured image was generated with Microsoft Designer: https://designer.microsoft.com/