import { Callout } from "zudoku/ui/Callout";

# Address Prediction API

Codeunit `5523621` `bdev.Address Prediction API` drives the type-ahead: while the user types into a bound field, the app offers matching addresses, and picking one hands you the address in structured form.

<Callout type="info" title="Version">
The Address Prediction API has been introduced with 365 business Address Validation **18.9**.
</Callout>

Prediction needs three things on your page:

1. the `bdev.Address Autocomplete` control add-in, which does the typing and the suggestion list in the browser;
2. the codeunit, which connects the control to the prediction service;
3. for a table of your own, a subscriber to [OnIsAddressPredictionEnabledForTableNo](extensibility-events.mdx#onisaddresspredictionenabledfortableno---event).

## The page: putting it together

The control add-in is one pixel in size and carries no UI of its own - it attaches to the fields you bind to it. A page that offers prediction looks like this:

```pascal
page 50000 "Broker Card"
{
    SourceTable = Broker;

    layout
    {
        area(Content)
        {
            group(General)
            {
                field(Name; Rec.Name) { ApplicationArea = All; }
                field(Address; Rec.Address) { ApplicationArea = All; }
                field("Address 2"; Rec."Address 2") { ApplicationArea = All; }
                field("Post Code"; Rec."Post Code") { ApplicationArea = All; }
                field(City; Rec.City) { ApplicationArea = All; }
                field(County; Rec.County) { ApplicationArea = All; }
                field("Country/Region Code"; Rec."Country/Region Code") { ApplicationArea = All; }
            }

            usercontrol("bdev.Address Prediction Control"; "bdev.Address Autocomplete")
            {
                ApplicationArea = All;

                trigger OnControlReady()
                begin
                    AddressPrediction.OnControlAddInReady(Database::Broker, CurrPage."bdev.Address Prediction Control");
                end;

                trigger GetAddressPredictions(fieldName: Text; input: Text)
                begin
                    AddressPrediction.GetAddressPredictions(fieldName, input, CurrPage."bdev.Address Prediction Control");
                end;

                trigger OnPredictionSelected(fieldName: Text; id: Text)
                var
                    address: Dictionary of [Text, Text];
                begin
                    if (not AddressPrediction.GetSelectedAddressPrediction(
                        address, AddressPrediction.GetAddressType(fieldName), id)) then
                        exit;

                    ApplyAddress(address);
                end;
            }
        }
    }

    trigger OnOpenPage()
    begin
        AddressPrediction.InitializeAddressPrediction();
    end;

    trigger OnAfterGetRecord()
    begin
        BindAddressPredictionFields();
    end;

    trigger OnNewRecord(BelowxRec: Boolean)
    begin
        BindAddressPredictionFields();
    end;

    local procedure BindAddressPredictionFields()
    var
        fieldNames: Dictionary of [Text, Enum "bdev.Address Prediction Type"];
    begin
        // look up businesses while the user types into "Name"
        fieldNames.Add(Rec.FieldName(Name), Enum::"bdev.Address Prediction Type"::Establishment);
        // look up addresses while the user types into "Address"
        fieldNames.Add(Rec.FieldName(Address), Enum::"bdev.Address Prediction Type"::Address);

        AddressPrediction.BindAddressPrediction(fieldNames, CurrPage."bdev.Address Prediction Control");
    end;

    var
        AddressPrediction: Codeunit "bdev.Address Prediction API";
}
```

<Callout type="caution" title="Bind on every record change">
The binding lives in the control, not in the record. Bind in `OnAfterGetRecord` **and** in `OnNewRecord`, or the type-ahead stops working as soon as the user moves to another record or creates one.
</Callout>

## InitializeAddressPrediction()

Resets the prediction instance. Call it in `OnOpenPage`, before anything else.

```pascal
    procedure InitializeAddressPrediction()
```

## OnControlAddInReady(Integer, ControlAddIn)

Activates the control once the browser has loaded it, and states which table the page shows.

```pascal
    procedure OnControlAddInReady(tableNo: Integer; control: ControlAddIn "bdev.Address Autocomplete")
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tableNo` | Integer | The table id of the page source. Decides whether prediction is enabled - see [Extensibility Events](extensibility-events.mdx). |
| `control` | ControlAddIn "bdev.Address Autocomplete" | The control of the current page. |

Call it from the `OnControlReady` trigger of the control and from nowhere else - the control is not ready before.

## BindAddressPrediction(...)

Binds page fields to the type-ahead. Four overloads, differing only in how much you say about each field:

```pascal
    procedure BindAddressPrediction(fieldNames: Dictionary of [Text, Enum "bdev.Address Prediction Type"]; control: ControlAddIn "bdev.Address Autocomplete")
    procedure BindAddressPrediction(fieldNames: List of [Text]; control: ControlAddIn "bdev.Address Autocomplete")
    procedure BindAddressPrediction(fieldName: Text; control: ControlAddIn "bdev.Address Autocomplete")
    procedure BindAddressPrediction(fieldName: Text; addrPreditionType: Enum "bdev.Address Prediction Type"; control: ControlAddIn "bdev.Address Autocomplete")
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `fieldName` / `fieldNames` | Text, List or Dictionary | The **field name** as the page knows it. Use `Rec.FieldName(...)` rather than a literal, so a rename cannot break the binding silently. |
| `addrPreditionType` | Enum "bdev.Address Prediction Type" | `Address` or `Establishment`. The overloads without it bind as `Address`. |
| `control` | ControlAddIn "bdev.Address Autocomplete" | The control of the current page. |

Binding a field that is already bound rebinds it - the codeunit unbinds it first - so calling the binding again on every record change is safe.

## UnbindAddressPrediction(Text, ControlAddIn)

Removes the type-ahead from a field, for example when your page turns it off for a record.

```pascal
    procedure UnbindAddressPrediction(fieldName: Text; control: ControlAddIn "bdev.Address Autocomplete")
```

## GetAddressPredictions(Text, Text, ControlAddIn)

Fetches the suggestions for the current input and hands them to the control, which shows them.

```pascal
    procedure GetAddressPredictions(fieldName: Text; input: Text; control: ControlAddIn "bdev.Address Autocomplete")
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `fieldName` | Text | The field the user is typing into, as passed by the trigger. |
| `input` | Text | What the user has typed so far. |
| `control` | ControlAddIn "bdev.Address Autocomplete" | The control of the current page. |

Call it from the `GetAddressPredictions` trigger of the control. It fires on every keystroke, so do not put work of your own around it.

## GetSelectedAddressPrediction(Dictionary, Enum, Text)

Resolves the suggestion the user picked into the address behind it.

```pascal
    procedure GetSelectedAddressPrediction(var address: Dictionary of [Text, Text]; addressType: Enum "bdev.Address Prediction Type"; id: Text): Boolean
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `address` | Dictionary of [Text, Text] | The address of the suggestion. |
| `addressType` | Enum "bdev.Address Prediction Type" | The type the field was bound as. [GetAddressType](#getaddresstypetext) answers it for a field name. |
| `id` | Text | The id of the suggestion, as passed by the trigger. |

**Returns** `false` when the address could not be fetched.

The dictionary carries the parts of the address the service knows. **Not every key is present** - a rural address without a street number has no `address`, and the business keys only appear for an `Establishment`. Ask with `ContainsKey` before reading:

| Key | Content |
| --- | --- |
| `address` | Street and house number. |
| `address2` | Additional address information. |
| `city` | City. |
| `postalCode` | Post code. |
| `county` | Region, state or county. |
| `countryCode` | ISO alpha-2 country code. |
| `companyName` | Name of the business. `Establishment` only. |
| `phoneNumber` | International phone number. `Establishment` only. |
| `homePage` | Website, reduced to the domain. `Establishment` only. |

```pascal
    local procedure ApplyAddress(address: Dictionary of [Text, Text])
    begin
        if (address.ContainsKey('countryCode')) then
            Rec.Validate("Country/Region Code", CopyStr(address.Get('countryCode'), 1, MaxStrLen(Rec."Country/Region Code")));
        if (address.ContainsKey('address')) then
            Rec.Validate(Address, CopyStr(address.Get('address'), 1, MaxStrLen(Rec.Address)));
        if (address.ContainsKey('postalCode')) then
            Rec.Validate("Post Code", CopyStr(address.Get('postalCode'), 1, MaxStrLen(Rec."Post Code")));
        if (address.ContainsKey('city')) then
            Rec.Validate(City, CopyStr(address.Get('city'), 1, MaxStrLen(Rec.City)));
        if (address.ContainsKey('county')) then
            Rec.Validate(County, CopyStr(address.Get('county'), 1, MaxStrLen(Rec.County)));

        CurrPage.Update(true);
    end;
```

Validate `Country/Region Code` first: the format of the other fields depends on the country.

## GetAddressType(Text)

Returns the type a field was bound as, so that the selection trigger can pass it on without keeping its own state.

```pascal
    procedure GetAddressType(fieldName: Text): Enum "bdev.Address Prediction Type"
```

## See also

- [365 business Address Validation - Overview](readme.mdx)
- [Extensibility Events](extensibility-events.mdx)
- [Address Validation API](address-validation-api.mdx)
- [Documentation - Address Prediction](../../en-us/365-business-address-validation/address-prediction.mdx)
