A few days ago we had a workshop in the company about events. Even during our internal overview, we found out some thing which weren’t very clear before.
So I decided to try to list a bit about it here.
Types
Business
According to Microsoft the Business event type is meant to be defined once and basically never changed. There are actually no checks in place, but when you publish an event of type Business, you make a commitment that it’ll never change.
As far as I could see in the current NAV Version, even Microsoft hasn’t committed any event to never change.
Choosing the type business or integration will actually not have any influence on the way the event is handled.
Type Business is one of the 2 possible types to give to a custom event.
Integration
The Integration event type is the second of the 2 possible types of a custom event.
It’s purpose is to let other code “integrate” with your coding.
Triggers
Page Trigger events
A few actions on a page will automatically create page trigger events. This is an overview:
- OnBeforeActionEvent
- OnAfterActionEvent
- OnAfterGetCurrRecordEvent
- OnAfterGetRecordEvent
- OnBeforeValidateEvent (this is called when a field loses focus and it’s value was changed)
- OnAfterValidateEvent (same as above, but after page validate trigger)
- OnClosePageEvent
- OnDeleteRecordEvent
- OnInsertRecordEvent
- OnModifyRecordEvent
- OnNewRecordEvent
- OnPageOpenEvent
- OnQueryClosePageEvent
Using these will allow us to execute code on a page, without putting code on the page object itself.
Database Trigger Events
Also creating a table will automatically create available events. These will be created:
- OnBeforeDeleteEvent
- OnAfterDeleteEvent
- OnBeforeInsertEvent
- OnAfterInsertEvent
- OnBeforeModifyEvent
- OnAfterModifyEvent
- OnBeforeRenameEvent
- OnAfterRenameEvent
- OnBeforeValidateEvent (for every field. When subscribing, you first select validate event, another option to select the field will become available)
- OnAfterValidateEvent (same as above, but after validation trigger of the field)
Global Events
Global events are predefined events that are raised typically in Codeunit 1. Examples for this are OnAfterCompanyOpen and GetSystemIndicator
Order of Execution
Code
When you change a record, it might be interesting to know in which order the code will execute.
Let’s assume we are doing Record.INSERT;
This is how it is specified by the help files:
- OnBeforeInsert (Table Trigger Event)
- Table Insert Trigger
- OnBeforeDatabaseInsert (Global event)
- DatabaseInsert trigger in codeunit 1 will execute
- OnAfterDatabaseInsert (Global Event)
- The record insertion itself will happen on the database
- OnAfterInsert (Table Trigger Event)
This list has 3 and 5 added, while the help files do not, but I thought it might be good to explicitly add them, since there are events for these.
Events
Let’s say we have 1 event published and multiple subscribers. There is currently no way to determine which event subscriber will be executed first. I heard someone say that they are currently executed based on naming. There is probably not a way to make sure it is, or to be sure it stays like this. Don’t try funky stuff to ensure your position.
Just know that if you have subscribed to an event, and your subscription is ok (meaning all parameters are correct, binding is ok), your subscriber will be executed.
Publishing events
When we create a new event ourselfs we have a few properties:
Event Type
Business or Integration event type. See the description above. Let’s say that we will mostly make events of type Integration.
IncludeSender
When we subscribe to our publisher, the Sender object will automatically be added to our signature ByVar. This allows to get access to the public functions of the object while we have the exact instance of the object.
GlobalVarAccess
This option is only available to event type Integration. It will allow the subscribers access to globally defined variables in the object. I will explain in “Subscribing to an Event” how to access them.
Triggering an event
After you create a custom event, all you have to do is to “Raise” it. This is simply done by calling the event publisher function.
LOCAL [IntegrationEvent] PROCEDURE OnBeforeDoSomethingEvent() LOCAL [IntegrationEvent] PROCEDURE OnAfterDoSomethingEvent() LOCAL PROCEDURE DoSomething() BEGIN OnBeforeDoSomethingEvent; //Triggering on before event //Do Something OnAfterDoSomethingEvent; //Triggering on after event END
Best Practices
In my opinion, it’s good practice to have custom events as local. They shouldn’t really be called from anywhere else than the object itself. It also makes finding the trigger point a lot easier when going through your code.
An event should not contain any code or locals.
Another good best practice is to never call a publisher from more than one place. You want subscribers to know what the event is. If there is need to subscribe to a publisher that is executing from multiple places, these should be done through the predefined trigger events.
Subscribing to an Event
Subscribing to an event is only possible in a codeunit.
EventPublisherObject
Here we can select an object where we know an event is published.
EventFunction
We can select the available events. These can be trigger events or custom events.
EventPublisherElement
When we select EventFunction of type validate or page action, we get an extra property. In this list we get an overview of possible element names.
- In the page action, we get an overview of all defined actions.
- In the page validate, we get an overview of all fields on the page
- in the table validate, we get a list of all fields in the table.
EventSubscriberInstance
This property is a property on codeunit level. It can be:
- Static-Automatic: NAV will automatically enable the subscriber and link it to the publisher you defined.
- Manual: The subscriber will not be executed until you enable it through code with the BINDSUBSCRIPTION and disable it with the UNBINDSUBSCRIPTION
In most cases the Static-Automatic, which is the default, is the way to go.
Accessing Sender
When the publisher has the IncludeSender enabled, it will include the sender in the parameters of the subscriber.
Accessing Global Vars
Let me first start with saying that it is best to avoid accessing global variables. If they ever change, you might not notice when compiling, but your event subscriber will probably no longer work…
Anyway, how to do it. Actually pretty simple. First make sure the publisher has the property enabled, or it won’t work. Next in the subscriber, you add an extra parameter with the EXACT SAME name and data type. You need to do this manually for each global variable you wish to access. By setting it ByVar, you can also change them. Setting it ByVar is however not mandatory.
Again, you need to note that if the publisher object removes a global var you access, or changes it’s name, you will not get a compile error, but your subscriber will not get executed.
Best Practices
The subscriber functions should also be declared local. It is never a function that needs to be called manually.
Debugging
If we go through coding, we just see the publisher functions, but we do not have the option to use “Go to definition” to see subscribers. In the “Event Subscriptions” overview, we can see everything that is subscribed, so this is the way to go.
When actually debugging the code, the debugger just steps into the subscription, while maintaining the call stack. Debugging would be just like the function was called luckily.
Overview of event subscribers
NAV contains a virtual table with all event subscriptions. This can be accessed from the development client by going to Tools => Debugger => Event Subscriptions.
Another method of opening this page is by opening the Sessions page (also known as the “debugger”) from the windows client and clicking the “Event Subscriptions” button.
In this page we see the subscriber information (Codeunit, Function) and the publisher information (Object ID, Function, Type).
The type of subscriber instance is also visible. But you have to note that if the instance is set to manual, the event subscription will only appear in the table as soon as it has been bound once. Another field shows how many times the subscriber has been fired.
Conclusion
Events are a really great tool to interact with the default coding of NAV without having to change the default coding.
The more default integration events become available, the more we can use them. This will result in easier upgrading. Let’s say we subscribe to Codeunit 80 events to do extra stuff, we don’t need to worry if codeunit 80 gets changed completely.
One big issue, which we do need to keep in mind, is “loosely binding” of events. We have the Event Subscriptions table where we can see if they are active, but we do not get compile errors, nor do the users get an error if the function is no longer bound. This could result in a lot of problems in a production database when some code is no longer run.
It is imperative to check the event subscriptions after changing coding, before putting code live. Perhaps in a next version, NAV will come with a better mechanism to detect these problems. But for now, keep checking that table, or build some smart mechanism yourself.