Advanced Portal Designer Documentation
Index

JavaScript API Overview

APD provides a JavaScript library that enables AJAX communication with Business Central, allowing you to build dynamic, interactive web portals without full page refreshes. The library is served by APD itself as an action — reference it from your templates via <?action('apdscript')?>.

Getting Started

Including the Library

The library is served as an APD action. Reference it from your templates with the <?action?> tag — APD expands it to the full URL that returns the JavaScript:

<script src="<?action('apdscript')?>"></script>

Do not ship a static apdscript.js file with your templates. APD generates the script per request so that RootLocation and other settings are populated from APD Setup automatically.

Configuration

RootLocation (the base URL the library posts AJAX requests back to) is populated automatically by APD when the script is generated — there is no file to edit. The value comes from the Serverpath field on APD Setup (the same value <?apd?> returns).

Core Concepts

Form Field Naming

APD uses a specific naming convention for form fields:

Format: TableNo:FieldNo

Element Example
Customer Name 18:2
Item Description 27:3
Sales Line Quantity 37:15

AJAX Operations

APD supports these AJAX operations:

Operation Function Purpose
Validate APD_validate() Validate and trigger field logic
Find APD_Find() Search for records
Insert APD_Insert() Create new records
Modify APD_Modify() Update existing records
Lookup APD_Lookup() Open lookup windows

Response Format

AJAX responses follow this pattern:

Prefix Meaning
DATA1 Success with data
ERROR Error occurred
EMPTY No records found

Function Reference

Data Validation

APD_validate(entry, table, field)

Validates a field and triggers Business Central validation logic.

<input type="text" 
       name="18:2" 
       onchange="APD_validate(this, 18, 2)"
       value="<?field(Customer,Name)?>">

Record Operations

APD_Insert(form, table)

Inserts a new record.

<form onsubmit="APD_Insert(this, 27); return false;">
    <input type="text" name="27:3" placeholder="Description">
    <button type="submit">Create Item</button>
</form>

APD_Modify(form, table)

Modifies an existing record.

<form onsubmit="APD_Modify(this, 18); return false;">
    <input type="hidden" name="TABLE" value="18">
    <!-- For AJAX modify, emit the primary key field(s) as hidden inputs.
         <?key?> is only for URL query strings, not form bodies. -->
    <input type="hidden" name="18:1" value="<?field(Customer,"No.")?>">
    <input type="text" name="18:2" value="<?field(Customer,Name)?>">
    <button type="submit">Save</button>
</form>

Record Navigation

APD_Find(table, step, viewString, inputForm, outputForm)

Finds records with navigation.

// Find first record
APD_Find(18, '-', 'undefined', 'myform', 'myform');

// Find next record
APD_Find(18, '>', 'undefined', 'myform', 'myform');

// Find previous record
APD_Find(18, '<', 'undefined', 'myform', 'myform');

Value Helpers

APD_GetValue(form, index)

Gets a value from a form field.

var name = APD_GetValue(document.forms[0], 2);

APD_SetValue(form, index, value)

Sets a value in a form field.

APD_SetValue(document.forms[0], 2, 'New Value');

APD_GetSingleValue(table, field)

Gets a single field value by table and field number.

var customerNo = APD_GetSingleValue(18, 1);

Session Storage

APD_Set(key, value)

Stores a value in the session.

APD_Set('lastSearch', 'widgets');

APD_Get(key)

Retrieves a stored value.

var lastSearch = APD_Get('lastSearch');

APD_Has(key)

Checks if a key exists.

if (APD_Has('lastSearch')) {
    // Key exists
}

Lookup Windows

APD_Lookup(tableNo, fieldNo, lookupTable, lookupField, descField, entry)

Opens a lookup window.

<button onclick="APD_Lookup(18, 86, 13, 1, 2, this)">
    Select Salesperson
</button>

Alerts

APDAlert(message)

Shows an alert message (customizable).

APDAlert('Record saved successfully!');

You can override the default alert:

APDAlert = function(message) {
    // Custom alert implementation
    showMyModal(message);
};

Examples

AJAX Form Example

<?open(Customer)?>
<?filter(Customer,"No.",Parm('custno'))?>
<?findfirst(Customer)?>

<form id="customerForm">
    <input type="hidden" name="TABLE" value="18">
    <!-- Primary key as a hidden input (APD_Modify reads form elements) -->
    <input type="hidden" name="18:1" value="<?field(Customer,"No.")?>">
    
    <div class="form-group">
        <label>Name</label>
        <input type="text" 
               name="18:2" 
               value="<?field(Customer,Name)?>"
               onfocus="APD_Focus(this, 18, 2)"
               onchange="APD_validate(this, 18, 2)">
    </div>
    
    <div class="form-group">
        <label>Address</label>
        <input type="text" 
               name="18:5" 
               value="<?field(Customer,Address)?>"
               onfocus="APD_Focus(this, 18, 5)"
               onchange="APD_validate(this, 18, 5)">
    </div>
    
    <div class="form-group">
        <label>City</label>
        <input type="text" 
               name="18:7" 
               value="<?field(Customer,City)?>"
               onfocus="APD_Focus(this, 18, 7)"
               onchange="APD_validate(this, 18, 7)">
    </div>
    
    <button type="button" onclick="APD_Modify(this.form, 18)">Save</button>
</form>

Lookup with Validation

<div class="input-group">
    <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>
</div>
<div class="record-nav">
    <button onclick="APD_Find(18, '-', 'undefined', 'custform', 'custform')">
        First
    </button>
    <button onclick="APD_Find(18, '<', 'undefined', 'custform', 'custform')">
        Previous
    </button>
    <button onclick="APD_Find(18, '>', 'undefined', 'custform', 'custform')">
        Next
    </button>
    <button onclick="APD_Find(18, '+', 'undefined', 'custform', 'custform')">
        Last
    </button>
</div>

Custom Validation Handler

// Define extra validation function
var ExtraValidateFunction = function(form) {
    // Calculate totals after validation
    var qty = parseFloat(APD_GetValue(form, 2));
    var price = parseFloat(APD_GetValue(form, 3));
    var total = qty * price;
    document.getElementById('total').textContent = total.toFixed(2);
};

Error Handling

Built-in Error Display

APD uses APDAlert() for error messages. Override for custom handling:

APDAlert = function(message) {
    var errorDiv = document.getElementById('error-message');
    errorDiv.textContent = message;
    errorDiv.style.display = 'block';
    setTimeout(function() {
        errorDiv.style.display = 'none';
    }, 5000);
};

Error Codes

Check errorCode after validation:

function afterValidate(form) {
    if (errorCode === 0) {
        console.log('Validation successful');
    } else {
        console.log('Validation failed');
    }
}

Best Practices

Field Focus

Always use APD_Focus() to track original values:

<input onfocus="APD_Focus(this, 18, 2)" 
       onchange="APD_validate(this, 18, 2)">

Form Naming

Name forms for easier reference:

<form name="customerForm" id="customerForm">

Loading States

Show loading indicators during AJAX:

function showLoading() {
    document.body.classList.add('loading');
}

function hideLoading() {
    document.body.classList.remove('loading');
}

Validation Chaining

Use ExtraValidateFunction for post-validation logic:

var ExtraValidateFunction = function(form) {
    updateTotals();
    checkRequiredFields();
    enableSubmitButton();
};

Security Considerations

  1. AJAX endpoints are secured by APD's security system
  2. Table permissions apply to AJAX operations
  3. Security filters are enforced server-side
  4. Validate all input on the server, not just client