Quick Tip: Solving Dexterity Trigger Clashes

David Meego - Click for blog homepageIn a recent support case, I had the rare situation where Dexterity Triggers clashed.  Dexterity allows third party developers to create triggers against various events and scripts in a Dexterity application (like Microsoft Dynamics GP).

There are times where more than one third party product registers a trigger against the same event or script. Most of the time this does not cause issues and both products work together without issues. But every now and then, you will have a situation where the triggers "clash". A clash is when one trigger somehow prevents the other trigger from working correctly (or at all).

The best way to explain how this could happen is to take you through the situation in the support case.


The case was raised by a developing partner who was working on a customization to the Cash Receipts Entry (RM_Cash_Receipts) form in Microsoft Dynamics GP. Part of the customization required some conditions to be checked before a Cash Receipt document can be deleted. If the conditions are not met the requested delete action should be aborted.

This functionality was achieved with a trigger running before the change event of the Delete Button. This trigger can then use the reject script command to abort any further processing when the appropriate conditions are met.

All worked fine until the code failed to work at a customer's site. After some research it was discovered that the Payment Document Management (PDM) module was installed at this site and the code was failing because PDM was somehow interfering with our customization.

So firstly, how did we identify that Payment Document Management was the problem?

The first step is to capture a Dexterity Script Log (either using the the ScriptDebugger=TRUE Dex.ini setting or the Manual Logging Mode of the Support Debugging Tool). Capturing the Script Log when pressing the Delete Button on the RM_Cash_Receipts window.

On a working system we can see the call to change script of the button:

'RM_Cash_Receipts Delete Button CHG on form RM_Cash_Receipts'

On the non working system, the above script was not called at all, instead the first call was to:

'rvlrmPmntDocCashReceiptsDelete'

To identify the product that the trigger script comes from, go to the Customization Status window (Tools >> Customization >> Customization Status) and print the Trigger Report. Then use the Find menu to look for the script name (rvlrmPmntDocCashReceiptsDelete).  In our case the report showed that the trigger was from Payment Document Management (PDM) and is registered before the Delete Button's change script..

Looking at the products in the Dynamics.set  we found Payment Document Management (Product ID: 2150) was installed on this system and not on our test system.

The second step was to prove that PDM was the cause. You can do this be disabling the product and seeing if the code works again. You can disable the product by using one of the three methods below:

  1. Removing the product's entries from the DYNAMICS.SET (after making a backup). This requires restarting the system and is more work than needed.
     
  2. You can use Tools >> Customize >> Customization Status to disable a product's triggers for the current session.
     

     
  3. Or you can use the Support Debugging Tool's Dictionary Control window which can also disable Alternate windows (without changing security) and also can remember that the products should be disabled until re-enabled.
     

So now we knew the exact cause of the issue...... The Payment Document Management dictionary was registering a trigger in the same location (Before Delete Button Change) and was using reject script to prevent the original script in the Dynamics dictionary from running. This in turn means that any other triggers on that same location will also be prevented from running.

So what is the solution?

Well, the solution is simple. We need our trigger to fire before PDM's trigger, so we can use reject script to abort further processing if needed. In our case we are using reject script to prevent further processing after some condition has failed, when PDM is using reject script to replace the original code with its own code.

Theory bit: Triggers on the same event are executed in the order they were registered. Triggers are usually registered in the Startup global procedure (or code called from Startup). The Startup global procedure is executed in the order the dictionaries are loaded. The order the dictionaries are loaded is controlled by the order of the products in the DYNAMICS.SET launch file.

So by placing our customization in the DYNAMICS.SET launch file before the Payment Document Management product, we can execute our trigger first and our code will work as desired.

To change the order of products in the DYNAMICS.SET you can manually edit the file with Notepad.exe or use the buttons on the right hand side of the Support Debugging Tool's Dictionary Control window (see screenshot above).

Once the change was made, the issue was resolved and the triggers no longer clashed.

Below is a  script excerpt which shows how you can pop up a warning dialog when products are not listed in the correct order in the DYNAMICS.SET. You can use this code when you have a known clashing issue to resolve.

Code Excerpts from Startup Global Procedure and Trigger Handler script

{ Startup }
local integer l_result;
local string l_message;

if Launch_GetProdPosition(2150) > 0 then
if Launch_GetProdPosition(2150) < Launch_GetProdPosition(Runtime_GetCurrentProductID()) then
l_message = "Please adjust the DYNAMICS.SET launch file to place %1 before %2."; { should be using getmsg() }
substitute l_message, Launch_GetProdName(Runtime_GetCurrentProductID()), Launch_GetProdName(2150);
warning l_message;
end if;
end if;

{Turn off the warning for literal strings}
pragma(disable warning LiteralStringUsed);

l_result = Trigger_RegisterFocus(anonymous('Delete Button' of window RM_Cash_Receipts of form RM_Cash_Receipts),
    TRIGGER_FOCUS_CHANGE, TRIGGER_BEFORE_ORIGINAL, script MBS_RM_Cash_Receipts_Delete_Button_CHG_PRE);
if l_result <> SY_NOERR then
warning "RM_Cash_Receipts Delete Button CHG PRE Focus trigger registration failed.";
end if;

{Turn the literal string warning back on}
pragma(enable warning LiteralStringUsed);

{ MBS_RM_Cash_Rececipts Delete Button CHG PRE }

if ask("Delete Button: Issue Reject Script?", getmsg(YES), getmsg(NO), "") = ASKBUTTON1 then
 reject script;
end if;

 

I hope you find this information useful. It describes the methods to troubleshoot and test as well as methods to fix and avoid the issue in the future. 

David

// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)