Category Archives: AL Language

Forget Confirm() method; start with Confirm Management module

For a long time, if we wanted to get input from the user to confirm something, we used Confirm() method. This method is straightforward – it has two parameters (+ unlimited number of constant values similar to StrSubstNo() method). The first one accepts text shown to the user, the second one (optional) defines which button (OK/Cancel) is chosen as default.

 ...
 if Confirm('Confirm or Cancel?', true) then
   Message('Confirmed')
 else
   Message('Canceled');
 ...

The biggest issue with this design is that every time we use this method, we have to think about the process as a whole – whether a user action will call the method or whether it will also be called from the system (job queue, APIs, …) process. In that case, we have to think about how the confirmation dialogue should behave during the process with no user input and suppress the dialogue using GuiAllowed() method.

“Confirm Management” module

Confirm management is a new module (it is not really so new; it is available since the first versions of the Business Central). The source of the module is available on Microsoft GitHub.

The module contains two methods GetResponse and GetResponseOrDefault. The difference is what the method returns if Gui is not allowed.


 codeunit 27 "Confirm Management"
 {
   procedure GetResponseOrDefault(ConfirmQuestion: Text; DefaultButton: Boolean): Boolean
   begin
     if not IsGuiAllowed() then
       exit(DefaultButton);
     exit(Confirm(ConfirmQuestion, DefaultButton));
   end;

   procedure GetResponse(ConfirmQuestion: Text; DefaultButton: Boolean): Boolean
   begin
     if not IsGuiAllowed() then
       exit(false);
     exit(Confirm(ConfirmQuestion, DefaultButton));
   end;

   ..
 }

In that case, GetResponse() always returns false in comparison to GetResponseOrDefault() that returns the default value passed to the method as one of the parameters.

What’s more, there is also the publisher method OnBeforeGuiAllowed() that we can use to manage whether the Gui is allowed or not. It can be used, for example, to simulate job queue runs called directly by the user (admin).

Returning complex types in AL Language

In Microsoft Dynamics 365 Business Central 2021 w1 (released in May 2021), a profound change was introduced to ways how we can design our applications

Until this version, the only way to return complex data types (like records, codeunits, Lists etc.) was using the var parameter (see example below).


 trigger OnRun()
 var
   SalesHeader: Record "Sales Header";
 begin
   FindSalesOrder('SO210001', SalesHeader);
   SalesHeader.TestStatusOpen();
 end;

 local procedure FindSalesOrder(OrderNo: Code[20]; var SalesHeader: Record "Sales Header")
 begin
   Clear(SalesHeader);
   SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
   SalesHeader.SetRange("No.", OrderNo);
   SalesHeader.FindFirst();
 end;

Although it allows returning any data type, working with var parameters is not as friendly as working with return parameters. For example, var parameters do not allow method chaining.

It was not possible in C/AL or AL Language. Until now!


 trigger OnRun()
 var
   SalesHeader: Record "Sales Header";
 begin
   FindSalesOrder('SO210001').TestStatusOpen();
 end;

 local procedure FindSalesOrder(OrderNo: Code[20]) SalesHeader: Record "Sales Header"
 begin
   Clear(SalesHeader);
   SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
   SalesHeader.SetRange("No.", OrderNo);
   SalesHeader.FindFirst();
 end;

With returning of complex types, we can chain methods similarly to C# (or any other modern object-oriented language) with returning of complex types.


 // With var parameter
 FindSalesOrder('SO210001', SalesHeader);
 SalesHeader.TestStatusOpen();

 // With returning complex types
 FindSalesOrder('SO210001').TestStatusOpen();

What is/isn’t possible to return

It is possible to return almost any data type.

  • Any simple type
    • Boolean, Integer, Text, Char, …
    • RecordRef, FieldRef, Variant
  • Any AL object
    • Records (Record “Sales Header”)
    • Pages (Page “Customer Card”)
    • Codeunits (Codeunit “Sales-Post”)
  • Selected DotNet objects
    • HttpClient, HttpContent, …
  • Other complex types
    • Lists (List of [Text])
    • Dictionaries (Dictionary of [Integer, Text])

The only things that are not possible to return are collections of complex types (something like List of [Records “Sales Header”]). However, it is not a limitation of return statements, but it is impossible to use collections of complex type in AL Language in general.

For this purpose, use Temporary Records instead.

Let’s chain it!

So how we can advance from method chaining in AL Language?

For example, imagine you want to work with data from the customer card for a customer from the sales header. Without chaining, the only way was to create a Customer variable and find the corresponding customer manually or using var parameter using the method in the Sales Header.

With chaining, we can use the getter method in the Sales Header that returns the customer (unfortunately, there isn’t the method yet). See the example below.

Example

Unfortunately, there aren’t any useful function in the base app yet, so the example uses methods as if they were in the base app.

I added a method procedure GetCustomer(): Record Customer to the Sales Header table and procedure GetShipToAddress(ShipToAddressCode: Code[10]): Record “Ship-to Address” to the Customer table.


 // Method in Sales Header table
 procedure GetCustomer(): Record Customer
 var
   Customer: Record Customer;
 begin
   Customer.Get(Rec."Sell-to Customer No.");
   exit(Customer);
 end;

 // Method in Customer table
 procedure GetShipToAddress(ShipToAddressCode: Code[10]): Record "Ship-to Address"
 var
   ShipToAddress: Record "Ship-to Address";
 begin
   ShipToAddress.Get(Rec."No.", ShipToAddressCode);
   exit(ShipToAddress);
 end;

Now we can get values (in our example, the value of email field) from Ship-to Address only with one line of custom code.


 procedure DoSomething(SalesHeader: Record "Sales Header")
 var
   EmailFromShipToAddressTable: Text[80];
 begin
   EmailFromShipToAddressTable := SalesHeader.GetCustomer().GetShipToAddress('ADDR-001')."E-Mail";
   Message(EmailFromShipToAddressTable);
 end;

Import various file types with Interfaces

A few weeks ago, I described a solution for importing an Excel file to the Business Central using Excel Buffer in AL Language (see here). In today’s article, we will look at how to build a more complex solution that can be used for file imports without the needs to identify the imported file type.

The example is available on my GitHub (here). In the next parts, we will look in details at some of the parts from this repository.

Enum & Interface

First of all, we need to create an interface that describes which procedures must be implemented by each file format implementation (if you want to know more about AL Language enums & interfaces, check my previous articles about Enum Data Type or the series of Interfaces in AL (part 1), (part 2) and (part 3)).

For our example, we need only two procedures. You will probably need some more procedures to make a (super) universal solution in real extension, but two methods are enough for our example.

So every implementation that uses this interface has to implement ImportFile procedure (that accept Code[20] and returns temporary CSV Buffer) and FieldSeparatorSupported procedure (returns boolean).


 /// <summary>
 /// Interface TKA IImport File Format.
 /// </summary>
 interface "TKA IImport File Format"
 {
     /// <summary>
     /// Import lines from the select file. The file is selected by user. Based on implementation, some other selections may be neccessary (specific sheet/list/range, ...).
     /// </summary>
     /// <param name="ImportConfigurationCode">Code[20], definition of used import configuration</param>
     /// <returns>Return value of type Record "CSV Buffer" temporary, contains all lines from imported file.</returns>
     procedure ImportFile(ImportConfigurationCode: Code[20]) TempCSVBuffer: Record "CSV Buffer" temporary;

     /// <summary>
     /// Specifies whether field separator is supported for the format
     /// </summary>
     /// <returns>Return value of type Boolean.</returns>
     procedure FieldSeparatorSupported(): Boolean;
 }

We will also create an enum that defines our supported file types. For now, we will add support for Excel (xlsx) and CSV files. The important thing is to define enum using the keyword “implements” followed by the name of our interface. This construction specifies that all enum values have to implement this interface (it does not matter whether the value is included in the Enum definition or is added through EnumExtension).


 /// <summary>
 /// Enum TKA Import File Format (ID 50000) implements Interface TKA IImport File Format.
 /// </summary>
 enum 50000 "TKA Import File Format" implements "TKA IImport File Format"
 {
     Extensible = true;

     value(5; "Excel file (.xlsx)")
     {
         Caption = 'Excel file (.xlsx)';
         Implementation = "TKA IImport File Format" = "TKA Import File Format XLSX";
     }
     value(10; "CSV file (.csv)")
     {
         Caption = 'CSV file (.csv)';
         Implementation = "TKA IImport File Format" = "TKA Import File Format CSV";
     }
 }

Interface Implementations

Interfaces can be implemented using Codeunits with the “implements” keyword (similarly to enums).

CSV Files (Code on GitHub)

There is nothing special about the implementation of CSV file imports. The import is done using “CSV Buffer” functionality (that manage everything internally) and, as the ImportFile procedure must return CSV Buffer, we just can return the loaded records.

The procedure FieldSeparatorSupported returns true as CSV files needs separator character to split the values.

Excel Files (Code on GitHub)

The implementation of Excel files is very similar. It is done using Excel buffer (as was discussed in this article). The only difference from CSV file implementation is that we need to change the structure of loaded values from “Excel Buffer” to “CSV Buffer”.

FieldSeparatorSupported returns false as Excel does not use separator characters.

Finally, let’s import file

Once we have defined and implemented all supported file types, we can use our functionality just by calling the ImportFile procedure through an interface defined with the enum value.

For our example, I created a table that defined file configuration (file type and field separator, if necessary), available on GitHub. Using values in this table, we know which file type we are importing and which field separator we are using.


 /// <summary>
 /// Import lines using import configuration form current Record (Rec)
 /// </summary>
 /// <returns>Return value of type Record "CSV Buffer" temporary, contains all lines from imported file.</returns>
 procedure ImportFile(): Record "CSV Buffer" temporary
 var
     IImportFileFormat: Interface "TKA IImport File Format";
 begin
     IImportFileFormat := "TKA Import File Format";
     exit(IImportFileFormat.ImportFile(Rec."TKA Code"));
 end;

Business Central Bootcamp – AL Language: Coding for performance session

The Business Central Bootcamp conference is almost here! Let’s check all sessions and find your favourite ones at https://events.powercommunity.com/business-central-bootcamp/. You can register for the event using this registering form for FREE!

The mine one will definitely be interesting – do you know about performance patterns in AL Language? And do you know how to use Partial Records? After this session, you will be able to use both of them properly in your own projects.

Business Central Bootcamp, 16th – 17th April 2021
AL Language: Coding for Performace
Presenter: Ing. Tomas Kapitan; Kepty.cz
10:00 – 11:00 am GMT/UTC
EDIT: New time of session: 11:00 am – 12:00 pm GMT/UTC (01:00 – 02:00 pm CET)

Kepty4Beginners: Flowfields in AL Language (Calculated Fields)

Not all fields in AL Language are stored in SQL Database. Flowfields are a great example of these fields. Flowfields are special field type that allows you to create a field that loads a value from another field within the same or even different table.

In the AL Language, flowfields are very often used, and it is one of the necessities any developer have to know. Check this video to find out how to create a flowfield and what types of flowfield are supported.

This video is a part of my series for beginners in AL Language. To see the full list of episodes, see https://kepty.cz/category/youtube-channel/kepty4beginners/.

New version of ALRM: AL ID Range Manager has just been released!

Today I have released a new version of both the ALRM (AL ID Range Manager) extension for Visual Studio Code and Microsoft Business Central. If you do not know what ALRM is, check some of previous articles here on my blog.

Both extensions are available on my GitHub (link below). The new version of VS Code extension is also available for download on Visual Studio Code Marketplace and directly in VS Code extensions.

What are the most interesting changes?

New command: “ALRM: Synchronize (beta)”

ALRM: Synchronize is a new command that scans all AL files in the project and registries founded AL objects (and fields/values for table and enum extensions). Using this command, it is much easier (and definitely quicker) to start using ALRM for your existing projects. You can just open your project, run ALRM: Initialize and then ALRM: Synchronize. Once both these commands finish, you can work with your project as if it is a project managed by ALRM since the beginning.

The project you are synchronising must meet all defined rules on the corresponding Assignable Range (object ID range, field ID range, object name structure and object ID and name uniqueness). If there is any error, other objects are synchronised, and errors are shown to the user once the synchronisation ends.

New option for Assignable Ranges: “Fill Object ID Gaps”

The Business Central extension generates new IDs using incrementation of the last assigned ID. However, sometimes, you need to use all IDs within the range, even if it was already used (but is not in use anymore). For these cases, there is a new option on Assignable Range “Fill Object ID Gaps” that check whether there are some gaps in used ranges and reassign the IDs to the newly created objects.

This setting can be potentially slower than assigning the following ID from the range; however, the performance is not much different based on my tests.

Ready for the Business Central 2021 W1

With the upcoming major version (Business Central 2021w1), there are new object types ReportExtension, PermissionSet, PermissionSetExtension and Entitlement. Both VS Code extension and the Business Central extensions are now ready for these object types.

There are much more interesting news and changes to the VS Code extension and Business Central that are described directly on GitHub! Do you have a new idea for improvement? Have you found a bug? Feel free to send me an Issue in GitHub repos.