The page background task was introduced back in Business Central 2019 Wave 2 (AKA BC 15.2), but I don’t see it get a lot of attention, so let’s create a use case and an example.

One of the base examples of this technology is the auto-refresh of either a Page or a Factbox. There are times where you want to watch a list and not spam the F5 key waiting for something to happen. Let’s embrace our inner ADD and create a page that refreshes itself.

For this example, let’s say that you are monitoring Sales Order for a status of Released so that you can move them forward to the next step in a manual process.

Here is the first design challenge: When you refresh a page, you refresh the Page Background Worker. For this to work, we must have a page that is static (the refresher), and a page that is dynamic (the refreshed). We are going to go with a Card and a List Part.

Here is the Monitoring Page ARDOrderMonitor.Page.al

// Order Monitor page displays released sales orders with periodic auto-refresh capability
page 50000 "ARD_Order Monitor"
{
    ApplicationArea = All;
    Caption = 'Order Monitor';
    usagecategory = lists;
    PageType = Card;

    layout
    {
        area(Content)
        {
            // Embeds the Released Sales Orders list part that handles the auto-refresh functionality
            part(ReleasedSalesOrdersPart; ARD_ReleasedSalesOrder)
            {
                ApplicationArea = All;
            }
        }
    }
    actions
    {
        area(Processing)
        {
            // Action to initiate the background refresh task for released sales orders
            action(StartRefresh)
            {
                ApplicationArea = All;
                Caption = 'Start Refresh';
                tooltip = 'Start the background refresh of the released sales orders.';
                Image = Refresh;
                trigger OnAction()
                begin
                    // Invoke the StartRefresh procedure on the embedded part page
                    CurrPage.ReleasedSalesOrdersPart.Page.StartRefresh();
                end;
            }
        }
    }
}

Not much of a page, but it is static, and holds the List Part that will be automatically refreshed. There is an action that tells the List Part that it should start it’s refresh loop.

The List Part is ARDReleasedSalesOrder.Page.al

page 50001 ARD_ReleasedSalesOrder
{
    ApplicationArea = All;
    Caption = 'Released Sales Orders';
    PageType = ListPart;
    SourceTable = "Sales Header";
    ModifyAllowed = false;
    insertallowed = false;
    deleteallowed = false;
    refreshonactivate = true;
    SourceTableView = where("Document Type" = const(Order), "Status" = const(Released));

    layout
    {
        area(Content)
        {
            repeater(DataRepeater)
            {
                field("No."; Rec."No.")
                {
                }
                field("Sell-to Customer No."; Rec."Sell-to Customer No.")
                {
                }
                field("Sell-to Customer Name"; Rec."Sell-to Customer Name")
                {
                }
                field("Document Date"; Rec."Document Date")
                {
                }
            }
        }
    }

    var
        // Tracks the ID of the background task used for periodic data refresh
        PbtTaskId: Integer;

    // Initiates the background task that handles periodic page refresh with a delay
    procedure StartRefresh()
    begin
        CurrPage.EnqueueBackgroundTask(PbtTaskId, Codeunit::"ARD_AutoRefreshDelay");
    end;

    // Handles completion of background tasks and re-enqueues the next refresh cycle
    trigger OnPageBackgroundTaskCompleted(TaskId: Integer; Results: Dictionary of [Text, Text])
    begin
        // Check if this is our refresh background task
        if (TaskId = PbtTaskId) then begin
            // Re-enqueue the background task to continue the refresh cycle
            CurrPage.EnqueueBackgroundTask(PbtTaskId, Codeunit::"ARD_AutoRefreshDelay");
            // Refresh the page data with the latest records from the database
            RefreshData();
        end;

    end;

    // Refreshes the page data with the latest released sales orders from the database
    procedure RefreshData()
    var
        ReleasedHeaders: Record "Sales Header";
    begin
        // Filter to only show Order type documents
        ReleasedHeaders.SetRange("Document Type", ReleasedHeaders."Document Type"::Order);
        // Further filter to only show Released status orders
        ReleasedHeaders.SetRange(Status, ReleasedHeaders.Status::Released);

        // If there are records matching our filter, update the page with the latest data
        if not ReleasedHeaders.IsEmpty() then
            currPage.SetRecord(ReleasedHeaders)
        else
            // If there are no records matching our filter, clear the page data
            currPage.SetRecord(Rec);

        // Refresh the page UI with the updated record set (false = don't activate page)
        CurrPage.Update(false);
    end;
}

When the StartRefresh procedure is called, the Current Pages EneueueBackgroundTask is called. The procedure is passed an interger variable which is populated by the process with the integer id of the background task. We also pass it a code unit we want to run. When the code units Run Trigger is complete, the background task is complete.

When ANY page background task is completed the OnPageBackgroundTaskCompleted trigger fires. This trigger has a Task Id and results in a dictionary of text values. We need to keep track of the task id we are following so that we don’t intercept another background task. The first thing we do is check if the TaskId is the same as our PbtTaskId.

If this is our task, then we requeue the background task and refresh the page. The RefreshData procedure is a simple re-query of the data and sets the current pages data to the new data set.

The last element is the code unit. ARDAutoRefreshDelay.Codeunit.al

codeunit 50000 ARD_AutoRefreshDelay
{
    trigger OnRun()
    begin
        // This is a delay so that the refresh happens at a reasonable interval.
        // This could be replaced with a task that takes a long time to complete, but for this sample we just want to simulate a delay.
        Sleep(2000);
    end;
}

Now remember, this code unit could be replaced with real work. As opposed to just sleeping, this could be a posting routine or something else that takes time that we would want to refresh a list of results when complete.

So what does it look like? About what you would expect.

Keep in mind that the auto refresh example is fine, but the “start long running work and auto refresh when complete” is a much better use case for this concept. It will help keep the user from spamming the page refresh and allow you to provide information as soon as it is available.

Also, don’t limit your imagination to just page refreshes. Depending on the result of your long running process you could display a message, send the user to a new page, download a report data object, start an AI assistive process.

This example is adapted from one provided in the BCTech GitHub.

You can get this example from the Aardvark Labs GitHub.

Leave a comment

Trending