How to create Dexterity Cross Dictionary Lookups

David Meego - Click for blog homepageAs you may know, I was one of the pioneers of Cross Dictionary Dexterity Development and have posted about it a number of times (also see Can I customise a 3rd party form with Dexterity? and Understanding Cross Dictionary Dexterity Development).

I am currently involved as a technical advisor on a large consulting project which requires customisation to the core Dynamics dictionary as well as other add-on dictionaries. During the development of the customisation we needed to be able to use lookups from 3rd party dictionaries.... A Cross Dictionary Lookup.

This post will explain the method used as it provides a reasonably simple technique for making 3rd party lookups easily available within your own dictionary.

 

Preparation

You will need to know how the lookup is called from its own dictionary. You have a number of options to obtain this information:

  1. Use ScriptDebugger=TRUE and ScriptDebuggerProduct=XXX Dex.ini settings to enable the Debug menu at runtime and then use the Debug >> Log Scripts feature to capture a script log of the lookup being used.
     
  2. Use the Support Debugging Tool's Manual Logging Mode to capture a script log of the lookup being used. See the Support Debugging Tool Portal for more information.
     
  3. If you have access to source code, you can lookup the calls used to open a lookup from a window where it is used.
     
  4. If the lookup is from a Microsoft Add-on product, the SDK (Software Development Kit) might have a document with all the parameters for functions and procedures.
     
  5. If any of the methods above don't get you everything you need, you can always contact the developer of the lookup.

 

In this example I will use the Fixed Assets module's Asset Lookup. The form is called FA_General_Lookup and the script called to open it is the form procedure Open of form FA_General_Lookup.

The parameter list for Open of form FA_General_Lookup is as follows:

in 'Sort By' IN_Sort_By;
in 'Asset Status' IN_Asset_Status;
in integer IN_Return_By;
in 'Asset Index' IN_Asset_Index;
in 'Asset ID' IN_Asset_ID;
in 'Asset ID Suffix' IN_Asset_ID_Suffix;
in 'Asset Description' IN_Asset_Description;
in 'Location ID' IN_Location_ID;
in 'Asset Class ID' IN_Asset_Class_ID;
in 'Structure ID' IN_Structure_ID;
in boolean IN_Disable_Zoom;
out anonymous field OUT_Return_Value;

To allow us to call this procedure without creating the fields used in the parameter list, we need to identify the control types for the fields and change the parameters accordingly:

in 'Sort By' IN_Sort_By;
in integer IN_Asset_Status;
in integer IN_Return_By;
in long IN_Asset_Index;
in string IN_Asset_ID;
in integer IN_Asset_ID_Suffix;
in string IN_Asset_Description;
in string IN_Location_ID;
in string IN_Asset_Class_ID;
in string IN_Structure_ID;
in boolean IN_Disable_Zoom;
out anonymous field OUT_Return_Value;

Note: This lookup allows the selection of the data to return with the IN_Return_By parameter, we only want the Asset Index and so we will hard code the Cross Dictionary lookup to return the Asset Index. If you want to support other data being returned, you could build upon this code to grab the data from the other fields as desired.

Finally, you will need a constant created with the Product ID (Dictionary ID) of the 3rd party dictionary. This can be easily read from the DYNAMICS.SET launch file. In our case, we have the constant MBS_FA_PROD_ID with a value of 309.

 

Development

Below are the steps to create a Cross Dictionary lookup. The concept is to be able to call our form exactly the same way you would call the 3rd party lookup and that our form will behave as a wrapper to the original lookup.

  1. Create a new form in your dictionary having the same name as the third party lookup with a prefix to identify your code: MBS_FA_General_Lookup.
     

  2. Create a window with the Name Dummy, change the AutoOpen property to False and set the Title of the window to ~internal~ so that it is ignored by the security subsystem.
     

  3. Create two integer local fields called Return Trigger Tag and Close Trigger Tag. Create a local field or a global field with the data type that matches the data to be returned from the lookup. In this case we created a global field called Asset Index with a long integer datatype.
     

  4. Drag the 3 fields onto the window and set them all has Editable = False, TabStop = False, Visible = False. Then for the fields '(L) Return Trigger Tag' and '(L) Close Trigger Tag' set the background to Yellow, for the return data field set the background to Pink.

             
      

  5. Now it is time to add the scripts required. I will list them in the order that they will be executed so that it is easier to follow the flow of how the Cross Dictionary Lookup works. Note that the trigger registrations in the FORM_PRE will not compile until the two global procedures have been created.

 

Form Procedure: Open of form MBS_FA_General_Lookup

{ Hard coded to return Asset Index }

in 'Sort By' IN_Sort_By;
in integer IN_Asset_Status;
in integer IN_Return_By;
in long IN_Asset_Index;
in string IN_Asset_ID;
in integer IN_Asset_ID_Suffix;
in string IN_Asset_Description;
in string IN_Location_ID;
in string IN_Asset_Class_ID;
in string IN_Structure_ID;
in boolean IN_Disable_Zoom;
out anonymous field OUT_Return_Value;
{
local text code;
local string compiler_error;
}
pragma(disable warning LiteralStringUsed);

open form MBS_FA_General_Lookup return to OUT_Return_Value;
if isopen(form MBS_FA_General_Lookup) then
call with name "Open of form FA_General_Lookup" in dictionary MBS_FA_PROD_ID, IN_Sort_By, IN_Asset_Status, 1 {Asset Index} {IN_Return_By}, IN_Asset_Index, IN_Asset_ID, IN_Asset_ID_Suffix, IN_Asset_Description, IN_Location_ID, IN_Asset_Class_ID, IN_Structure_ID, IN_Disable_Zoom, OUT_Return_Value;
{
{Build the pass-through sanScript code.}
clear code;
code = code + "in 'Sort By' IN_Sort_By;";
code = code + "in integer IN_Asset_Status;";
code = code + "in integer IN_Return_By;";
code = code + "in integer IN_Asset_Index;";
code = code + "in string IN_Asset_ID;";
code = code + "in integer IN_Asset_ID_Suffix;";
code = code + "in string IN_Asset_Description;";
code = code + "in string IN_Location_ID;";
code = code + "in string IN_Asset_Class_ID;";
code = code + "in string IN_Structure_ID;";
code = code + "in boolean IN_Disable_Zoom;";
code = code + "out anonymous field OUT_Return_Value;";

code = code + "call Open of form FA_General_Lookup, IN_Sort_By, IN_Asset_Status, IN_Return_By, IN_Asset_Index, IN_Asset_ID, IN_Asset_ID_Suffix, IN_Asset_Description, IN_Location_ID, IN_Asset_Class_ID, IN_Structure_ID, IN_Disable_Zoom, 'Asset Index' of window Dummy; ";

{Execute the code.}
if execute(MBS_FA_PROD_ID, code, compiler_error, IN_Sort_By, IN_Asset_Status, 1 {Asset Index} {IN_Return_By}, IN_Asset_Index, IN_Asset_ID, IN_Asset_ID_Suffix, IN_Asset_Description, IN_Location_ID, IN_Asset_Class_ID, IN_Structure_ID, IN_Disable_Zoom, 'Asset Index' of window Dummy) <> 0 then
{A compiler error occurred. Display the error.}
warning compiler_error;
end if;
}
else
abort script;
end if;
pragma(enable warning LiteralStringUsed); 

pragma(disable warning UnusedVariable);

 

Note: The script above includes two methods for calling the original Open of form FA_General_Lookup script. If the script being called is a procedure you can use either call with name in dictionary or execute() with passthrough sanScript. If the script being called is a function, then you must use execute() with passthrough sanScript.

 

Form Pre Script: MBS_FA_General_Lookup_FORM_PRE

local integer result;
local integer tag;

pragma(disable warning LiteralStringUsed); 

if MBS_FA_Installed() then
result =Trigger_RegisterFocusByName(MBS_FA_PROD_ID, "'Select Button' of window FA_General_Lookup of form FA_General_Lookup", TRIGGER_FOCUS_CHANGE, TRIGGER_BEFORE_ORIGINAL,
script MBS_FA_General_Lookup_Return, tag);
if result <> SY_NOERR then
warning "Trigger Register for Return of form FA_General_Lookup failed: "+str(result)+".";
else
'(L) Return Trigger Tag' of window Dummy = tag ;
end if;
result = Trigger_RegisterFocusByName(MBS_FA_PROD_ID, "form FA_General_Lookup", TRIGGER_FOCUS_POST, TRIGGER_AFTER_ORIGINAL,
script MBS_FA_General_Lookup_Close, tag);

if result <> SY_NOERR then
warning "Trigger Register for Close of form FA_General_Lookup failed: "+str(result)+".";
else
'(L) Close Trigger Tag' of window Dummy = tag ;
end if;
else
close form MBS_FA_General_Lookup;
abort script;
end if;

 

pragma(enable warning LiteralStringUsed);

 

Note: This script registers Cross Dictionary triggers to handle when the Select button on the lookup is pressed and when the lookup is closed. The tags for the triggers are stored in the appropriate window fields.

 

Global Function: MBS_FA_Installed

function returns boolean OUT_Installed;

OUT_Installed = false;

if empty(Launch_GetFileName()) then
 { Running in Test Mode }
else
 { Running in Runtime Mode }
 if Launch_GetProdPosition(MBS_FA_PROD_ID) > 0 then
  { Installed }
  OUT_Installed = true;
 end if;
end if;

 

Note: This function can be used everywhere to wrap any cross dictionary code, so that it only executes when the target dictionary is actually installed and available.

 

Global Procedure: MBS_FA_General_Lookup_Return

if isopen(form MBS_FA_General_Lookup) then
 run script '(L) Return Trigger Tag' of window Dummy of form MBS_FA_General_Lookup;
end if;

 

Note: This script just redirects the execution flow back to the '(L) Return Trigger Tag' field change script when the Select button is pressed.

  

'(L) Return Trigger Tag' Field Change Script: Dummy l_Return Trigger Tag_CHG

local text code;
local string compiler_error;
local long l_Asset_Index;

pragma(disable warning LiteralStringUsed);

 {Build the pass-through sanScript code.}
clear code;
code = code + "inout anonymous field ioReturnField;";

 code = code + "if isopen(form FA_General_Lookup) then";
code = code + " ioReturnField = 'Asset Index' of window FA_General_Lookup_Scroll of form FA_General_Lookup;";
code = code + "end if;";

 {Execute the code.}
if execute(MBS_FA_PROD_ID, code, compiler_error, l_Asset_Index) <> 0 then
{A compiler error occurred. Display the error.}
warning compiler_error;
end if;

pragma(enable warning LiteralStringUsed);

 

'Asset Index' = l_Asset_Index;
return 'Asset Index';

 

Note: This script uses Cross Dictionary code to read the return field from the lookup's scrolling window.

  

Global Procedure: MBS_FA_General_Lookup_Close

if isopen(form MBS_FA_General_Lookup) then
 run script '(L) Close Trigger Tag' of window Dummy of form MBS_FA_General_Lookup;
end if;

 

Note: This script just redirects the execution flow back to the '(L) Close Trigger Tag' field change script when the lookup window is closed.

 

'(L) Close Trigger Tag' Field Change Script: Dummy l_Close Trigger Tag_CHG

close form MBS_FA_General_Lookup;

 

Note: This script closes our hidden form when the original lookup's form closes.

 

Form Post Script: MBS_FA_General_Lookup_FORM_POST

if not empty('(L) Return Trigger Tag' of window Dummy) then
 Trigger_Unregister('(L) Return Trigger Tag' of window Dummy);
end if;
if not empty('(L) Close Trigger Tag' of window Dummy) then
 Trigger_Unregister('(L) Close Trigger Tag' of window Dummy);
end if;

 

Note: This script unregisters the cross dictionary triggers created in the PRE script as their work is now complete. 

 

Calling the Lookup

Once the Cross Dictionary lookup has been created with the steps above, you can call it using exactly the same syntax as would have been used in the original 3rd party application with only one subtle difference.  Just add your prefix to the form name (as highlighted below).

call Open of form MBS_FA_General_Lookup, 'Sort By', { Sort By }
                     0, { restrict by Asset Status }
                     1, { Return by }
                                                { Start values: }
                     'Asset Index', { Asset Index }
                     'Asset ID', { Asset ID }
                     'Asset ID Suffix', { Asset ID Suffix }
                     'Asset Description', { Asset Description }
                     "", { Location ID }
                     "", { Asset Class ID }
                     "", { Structure ID }
                     false, { Disable Zoom }
                     '(L) Temp Control Number'; { Return to }
  

I hope you find this technique useful, we have used it multiple times in this customisation project. Once created, you can use third party lookups as though they are actually part of your dictionary.

Enjoy.

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.)