The core of D365FO was always about adding additional data and displaying it to the user in a clear way.
The data that is presented on a form can be from a table that is not related to the table that is used as the main data source on the form, so it can’t just be added directly from that data source.
The easiest and most common way of adding such data is to use display methods, but these have a major drawback, they are not filterable!
Presenting a huge amount of data is meaningless if the user won’t be able to filter out the data that is not needed. One of the ways to add additional data on a form, while keeping it searchable, is to use outer joined data sources.
In this article we will discuss how to add fields with different editabilities, from different outer joined data sources and how to solve some problems that may arise from this.
Adding an outer joined data source to a form
Let’s first go over how to add more data to a form, by adding an additional data source to a form.
For this purpose, we will use the Batch email sending status form, this form shows all pending outgoing emails.
Emails in this form can originate from alert events that send out an email, but on the default form we don’t know the ID of the alert that’s the source of the email, so let’s try to add the Alert rule ID to the form.
Because the Batch email sending status form is a system form, we first need to create an extension for it, like this:
Next, we add a new data source to the form and define the underlying table in the properties:
We now need to join this new data source to the primary data source, we do so by defining the join source and the link type:
We just want to display this data and not allow editting, so we also need to toggle the appropriate properties:
Then we can add the field we want to the grid on the form just by dragging it from the data source tab to the grid control:
One short build and sync later and now when we look on the form, we can see the new Alert rule ID field we added on the grid:
And the most important thing is that it is filterable!
Making only certain fields editable
Let’s keep using the Batch email sending status form as an example. We know that this form is completely read-only, since the main data source is set to Allow edit: no.
What if we wanted to edit some of the fields on this form or even add new editable fields to it?
For example, it would be helpful to us to be able to edit the Email recipients field of on the form, so that we can easily fix any mistakes we may notice before the email is processed.
Or perhaps we’d like to add an editable Sender name field, so that we can change the display name of the sender to something different that the default.
Whatever the case, this will require us to make these fields editable, while keeping all the other fields noneditable.
Since the Allow edit property on a data source cannot be changed on the form extension, we will need to create an extension class and allow for editing of the fields we want in code.
In this new class we will extend the init() method via CoC (Chain of Command) and make the data source editable, then loop through all the fields and make only the ones we want editable, while making every other field noneditable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[ExtensionOf(formStr(SysOutgoingEmailTable))] final class SysOutgoingEmailTableForm_DC_Extension { public void init() { next init(); FormDataSource fds = this.dataSource(1); fds.allowEdit(true); QueryBuildFieldList fieldList = fds.queryBuildDataSource().fields(); Set fieldIdsToEditSet = new Set(Types::Integer); fieldIdsToEditSet.add(fieldNum(SysOutgoingEmailTable, Origin)); fieldIdsToEditSet.add(fieldNum(SysOutgoingEmailTable, SenderName)); for (int i = 1; i <= fieldList.fieldCount(); i++) { FieldId currentFieldId = fieldList.field(i); FormDataObject fdsField = fds.object(currentFieldId); If (fieldIdsToEditSet.in(currentFieldId)) { fdsField.allowEdit(true); } else { fdsField.allowEdit(false); } } } } |
Now if we go back to the form, we can see that the fields are editable:
But, if we try to save, we will get an error!
Where did these mandatory fields come from when we didn’t add them?
If we look through the AOT we will find that they are part of the BenefitWorkerEmailNotification table, that has been added as an outer joined datasource in a standard system extension to the form:
Why are we getting the error when we’re not even editing these fields?
To answer this, we first need to understand that in the background when we edit any field in a grid on a form and try to save, all fields from every joined datasource will also be saved. This means that a validation and the write() method will be triggered for every joined data source. And this brings us to our final point.
Skipping writing to outer joined data sources
When thinking about how to skip the writing of the newly joined data sources, the first thing that comes to mind is to just set the properties accordingly, like we did back in the first chapter of this article:
But this won’t work, because no matter the properties, every outer joined data source will be written to, when the main data source is being saved.
So we will have to solve this in code by overriding the write() and validateWrite() methods on all non-primary data sources.
Luckily, this is easy to do and requires the following code to be added in the init() method we have extended before:
1 2 3 4 5 6 7 8 9 10 11 |
for (int i = 1; i <= this.dataSourceCount(); i++) { fds = this.dataSource(i); if (fds.table() == tableNum(SysOutgoingEmailTable) { continue; } fds.registerOverrideMethod(methodStr(FormDataSource, validateWrite), formMethodStr(SysOutgoingEmailTable, FormDataSource_OnValidateWrite_DC), this); fds.registerOverrideMethod(methodStr(FormDataSource, write), formMethodStr(SysOutgoingEmailTable, FormDataSource_OnWrite_DC), this); } |
Now we just create an empty method to be called instead of the write() method:
1 2 3 |
public void FormDataSource_OnWrite_DC(FormDataSource _dataSource) { } |
And a method that always returns true to be called instead of the validateWrite() method:
1 2 3 4 |
public boolean FormDataSource_OnValidateWrite_DC(FormDataSource _dataSource) { return true; } |
Our entire form extension class should look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
[ExtensionOf(formStr(SysOutgoingEmailTable))] final class SysOutgoingEmailTableForm_DC_Extension { public void init() { next init(); FormDataSource fds = this.dataSource(1); fds.allowEdit(true); QueryBuildFieldList fieldList = fds.queryBuildDataSource().fields(); Set fieldIdsToEditSet = new Set(Types::Integer); fieldIdsToEditSet.add(fieldNum(SysOutgoingEmailTable, Origin)); fieldIdsToEditSet.add(fieldNum(SysOutgoingEmailTable, SenderName)); for (int i = 1; i <= fieldList.fieldCount(); i++) { FieldId currentFieldId = fieldList.field(i); FormDataObject fdsField = fds.object(currentFieldId); if (fieldIdsToEditSet.in(currentFieldId)) { fdsField.allowEdit(true); } else { fdsField.allowEdit(false); } } for (int i = 1; i <= this.dataSourceCount(); i++) { fds = this.dataSource(i); if (fds.table() == tableNum(SysOutgoingEmailTable) { continue; } fds.registerOverrideMethod(methodStr(FormDataSource, validateWrite), formMethodStr(SysOutgoingEmailTable, FormDataSource_OnValidateWrite_DC), this); fds.registerOverrideMethod(methodStr(FormDataSource, write), formMethodStr(SysOutgoingEmailTable, FormDataSource_OnWrite_DC), this); } } public boolean FormDataSource_OnValidateWrite_DC(FormDataSource _dataSource) { return true; } public void FormDataSource_OnWrite_DC(FormDataSource _dataSource) { } } |
Thus, we have successfully added new fields from different data sources on the Batch email sending status form, which have differing editability and are all searchable!