Advanced Portal Designer Documentation
Index

Lookup Windows

APD provides lookup functionality that opens popup windows for selecting values from Business Central tables, similar to the lookup experience in BC pages.

APD_Lookup Function

Syntax

APD_Lookup(tableNo, fieldNo, lookupTableNo, lookupFieldNo, descriptionFieldNo, entry)

Parameters:

Parameter Type Description
tableNo number Target table number
fieldNo number Target field number
lookupTableNo number Lookup source table
lookupFieldNo number Primary lookup field
descriptionFieldNo number Description field to display
entry HTMLElement Triggering element

Basic Usage

<input type="text" 
       name="18:86" 
       value="<?field(Customer,Salesperson Code)?>"
       onfocus="APD_Focus(this, 18, 86)"
       onchange="APD_validate(this, 18, 86)">
<button type="button" 
        onclick="APD_Lookup(18, 86, 13, 1, 2, this)">
    ...
</button>

In this example:

  • 18: Customer table
  • 86: Salesperson Code field
  • 13: Salesperson/Purchaser table
  • 1: Code field (lookup key)
  • 2: Name field (description)

Common Lookups

Salesperson Lookup

<!-- For Customer.Salesperson Code -->
<input type="text" name="18:86" value="<?field(Customer,Salesperson Code)?>">
<button onclick="APD_Lookup(18, 86, 13, 1, 2, this)">...</button>

Customer Lookup

<!-- For Sales Header.Sell-to Customer No. -->
<input type="text" name="36:2" value="<?field(Sales Header,Sell-to Customer No.)?>">
<button onclick="APD_Lookup(36, 2, 18, 1, 2, this)">...</button>

Item Lookup

<!-- For Sales Line.No. (Item) -->
<input type="text" name="37:6" value="<?field(Sales Line,No.)?>">
<button onclick="APD_Lookup(37, 6, 27, 1, 3, this)">...</button>

Country/Region Lookup

<!-- For Customer.Country/Region Code -->
<input type="text" name="18:35" value="<?field(Customer,Country/Region Code)?>">
<button onclick="APD_Lookup(18, 35, 9, 1, 2, this)">...</button>

Payment Terms Lookup

<!-- For Customer.Payment Terms Code -->
<input type="text" name="18:8" value="<?field(Customer,Payment Terms Code)?>">
<button onclick="APD_Lookup(18, 8, 3, 1, 3, this)">...</button>

How Lookups Work

Opening the Lookup

  1. User clicks lookup button
  2. APD_Lookup() is called
  3. Form data is collected for context
  4. Popup window opens with lookup page

URL Parameters

The lookup action receives these parameters:

Parameter Description
_WINDOW Parent window name
_PTABLE Target table number
_PFIELD Target field number
_TABLE Lookup table number
_FIELD Lookup key field
_FIELD2 Description field

Plus all current form field values for filtering context.

Selecting a Value

  1. User selects a value in the popup
  2. APD_LookupReturn(value) is called
  3. Value is placed in the target field
  4. APD_validate() is triggered if onchange handler exists
  5. Popup closes

APD_LookupReturn Function

Syntax

APD_LookupReturn(value)

Called from: The lookup popup window when user makes a selection

How It Works

  1. Gets target table/field from URL parameters
  2. Finds the matching input in the parent window
  3. Sets the value using APD_SetValue()
  4. Triggers validation if configured
  5. Closes the popup window

Lookup Page Template

The lookup page receives the source table and fields via TABLE, FIELD, and FIELD2 URL parameters. APD does not allow nested tags, so you cannot write <?open(<?parm(TABLE)?>)?> — instead, build one lookup template per target table (e.g. one for Salesperson, one for Customer), or use a Codeunit / Script action that resolves the parameter and outputs the markup.

Example: a dedicated Salesperson lookup template hard-codes the source table and fields:

<!DOCTYPE html>
<html>
<head>
    <title>Select Salesperson</title>
    <script src="<?action('apdscript')?>"></script>
    <style>
        table { width: 100%; border-collapse: collapse; }
        tr { cursor: pointer; }
        tr:hover { background: #f0f0f0; }
        td { padding: 8px; border-bottom: 1px solid #ddd; }
    </style>
</head>
<body>
    <h2>Select Salesperson</h2>

    <?open("Salesperson/Purchaser")?>

    <table>
        <thead>
            <tr>
                <th>Code</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            <?loop("Salesperson/Purchaser",'-')?>
            <tr onclick="APD_LookupReturn('<?field("Salesperson/Purchaser",Code)?>')">
                <td><?field("Salesperson/Purchaser",Code)?></td>
                <td><?field("Salesperson/Purchaser",Name)?></td>
            </tr>
            <?endloop("Salesperson/Purchaser")?>
        </tbody>
    </table>

    <button onclick="window.close()">Cancel</button>
</body>
</html>

Custom Lookup Implementation

Creating a Custom Lookup

For more control, create your own lookup logic:

<input type="text" id="customerNo" name="36:2">
<button type="button" onclick="openCustomerLookup()">Select Customer</button>

<script>
function openCustomerLookup() {
    var popup = window.open(
        '?action=CUSTOMER-LOOKUP',
        'CustomerLookup',
        'width=600,height=400,scrollbars=yes'
    );
}

function selectCustomer(customerNo, customerName) {
    document.getElementById('customerNo').value = customerNo;
    document.getElementById('customerName').textContent = customerName;
    
    // Trigger validation
    var input = document.getElementById('customerNo');
    APD_validate(input, 36, 2);
}
</script>

Custom Lookup Page

<!DOCTYPE html>
<html>
<head>
    <title>Select Customer</title>
</head>
<body>
    <h2>Select Customer</h2>
    
    <input type="text" id="search" placeholder="Search..." 
           oninput="filterCustomers(this.value)">
    
    <?open(Customer)?>
    
    <div id="customerList">
        <?loop(Customer,'-')?>
        <div class="customer-row"
             onclick="selectCustomer('<?field(Customer,"No.")?>', '<?field(Customer,Name)?>')">
            <strong><?field(Customer,"No.")?></strong>
            <span><?field(Customer,Name)?></span>
            <span><?field(Customer,City)?></span>
        </div>
        <?endloop(Customer)?>
    </div>
    
    <script>
    function selectCustomer(no, name) {
        window.opener.selectCustomer(no, name);
        window.close();
    }
    
    function filterCustomers(searchText) {
        var rows = document.querySelectorAll('.customer-row');
        rows.forEach(function(row) {
            var text = row.textContent.toLowerCase();
            row.style.display = text.includes(searchText.toLowerCase()) ? '' : 'none';
        });
    }
    </script>
</body>
</html>

Lookup with Filters

Passing Filter Context

Current form values are automatically passed to the lookup:

<!-- Sales Line lookup -->
<input type="text" name="37:1" value="Sales">  <!-- Document Type -->
<input type="text" name="37:3" value="ORD001"> <!-- Document No. -->

<!-- Item lookup uses above context -->
<input type="text" name="37:6" value="">
<button onclick="APD_Lookup(37, 6, 27, 1, 3, this)">...</button>

Using Filters in Lookup

<!-- In lookup template -->
<?open(Item)?>
<?filter(Item,Blocked,false)?>
<?filter(Item,"Sales Blocked",false)?>

<table>
    <?loop(Item,'-')?>
    <tr onclick="APD_LookupReturn('<?field(Item,"No.")?>')">
        <td><?field(Item,"No.")?></td>
        <td><?field(Item,Description)?></td>
    </tr>
    <?endloop(Item)?>
</table>

Styling Lookup Buttons

Icon Button

<div class="lookup-field">
    <input type="text" name="18:86" value="<?field(Customer,Salesperson Code)?>">
    <button type="button" 
            class="lookup-btn" 
            onclick="APD_Lookup(18, 86, 13, 1, 2, this)">
        <svg viewBox="0 0 24 24" width="16" height="16">
            <path d="M7 10l5 5 5-5z"/>
        </svg>
    </button>
</div>
.lookup-field {
    display: flex;
    gap: 0;
}

.lookup-field input {
    flex: 1;
    border-right: none;
    border-radius: 4px 0 0 4px;
}

.lookup-btn {
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-left: none;
    border-radius: 0 4px 4px 0;
    background: #f5f5f5;
    cursor: pointer;
}

.lookup-btn:hover {
    background: #e0e0e0;
}

Lookup with Validation

Ensure validation triggers after lookup selection:

<input type="text" 
       name="37:6"
       value="<?field(Sales Line,No.)?>"
       onfocus="APD_Focus(this, 37, 6)"
       onchange="APD_validate(this, 37, 6)">
<button onclick="APD_Lookup(37, 6, 27, 1, 3, this)">...</button>

The APD_LookupReturn function checks for onchange handlers:

// In APD_LookupReturn
if (attr[a].name == "onchange") {
    if (attr[a].value.toString().slice(0,8) == 'Navision') {
        APD_validate(pwin.document.forms[w].elements[d], ptable, pfield);
    }
}

Best Practices

Provide Both Text Entry and Lookup

Allow users to type directly or use lookup:

<input type="text" name="18:86"
       onfocus="APD_Focus(this, 18, 86)"
       onchange="APD_validate(this, 18, 86)">
<button onclick="APD_Lookup(...)">...</button>

Show Current Selection

Display the description alongside the code:

<div class="lookup-group">
    <input type="text" name="36:2" 
           value="<?field(Sales Header,Sell-to Customer No.)?>">
    <span id="customerName"><?field(Sales Header,Sell-to Customer Name)?></span>
    <button onclick="APD_Lookup(...)">...</button>
</div>

Handle Large Lists

For tables with many records, implement search/filter in the lookup:

<!-- Lookup template with search -->
<input type="text" id="searchBox" 
       placeholder="Type to search..."
       onkeyup="if(event.key==='Enter') doSearch()">
<button onclick="doSearch()">Search</button>

Disable When Appropriate

Disable lookup for non-editable records. The <?if?> tag takes Table,Field,Operator,Value directly — do not nest a <?field?> tag inside it:

<?if("Sales Header",Status,'<>',Open)?>
    <input type="text" value="<?field("Sales Header","Sell-to Customer No.")?>" disabled>
<?else?>
    <input type="text" name="36:2" value="<?field("Sales Header","Sell-to Customer No.")?>">
    <button onclick="APD_Lookup(36, 2, 18, 1, 2, this)">...</button>
<?endif?>