Field Validation with JavaScript
APD provides real-time field validation that triggers Business Central's field validation logic, ensuring data integrity without page refreshes.
APD_validate Function
Syntax
APD_validate(entry, table, field)
Parameters:
| Parameter | Type | Description |
|---|---|---|
| entry | HTMLElement | The input element that changed |
| table | number | Business Central table number |
| field | number | Business Central field number |
How It Works
- User modifies a field value
onchangeevent triggersAPD_validate()- AJAX request sent to Business Central
- BC validates the field and runs any OnValidate triggers
- Response updates all related fields in the form
- If validation fails, original value is restored
Basic Usage
<input type="text"
name="18:2"
value="<?field(Customer,Name)?>"
onfocus="APD_Focus(this, 18, 2)"
onchange="APD_validate(this, 18, 2)">
Field Focus Tracking
APD_Focus Function
Always pair APD_validate() with APD_Focus() to enable value rollback on validation failure.
APD_Focus(entry, table, field)
Example:
<input type="text"
name="27:6"
value="<?field(Item,No.)?>"
onfocus="APD_Focus(this, 27, 6)"
onchange="APD_validate(this, 27, 6)">
What Happens
- On Focus: Original value stored in
xRecValue - On Change: Validation attempted
- If Error: Value reverts to
xRecValue, field refocused
Validation Response Handling
Successful Validation
When validation succeeds:
- Server returns
DATA1prefix with updated field values - All form fields for the table are updated
- Related fields reflect any changes from OnValidate triggers
Example Response:
DATA118:1=10000&18:2=Contoso%20Ltd.&18:5=123%20Main%20St&18:21=50000
Failed Validation
When validation fails:
- Server returns
ERRORprefix with message APDAlert()displays the error- Field value reverts to original
- Field receives focus for correction
Error Code Tracking
The errorCode Variable
After validation, check errorCode:
// 0 = success, 1 = error
if (errorCode === 0) {
console.log('Validation successful');
} else {
console.log('Validation failed');
}
Usage in Custom Logic
function validateAndContinue() {
APD_validate(document.getElementById('myField'), 18, 2);
// Check result
if (errorCode === 0) {
proceedToNextStep();
}
}
Custom Post-Validation Logic
ExtraValidateFunction
Define a function to run after each successful validation:
var ExtraValidateFunction = function(form) {
// This runs after every successful APD_validate call
// Example: Update calculated totals
updateTotals();
// Example: Enable/disable fields
updateFieldStates();
// Example: Update UI indicators
showSaveStatus('Modified');
};
Example: Order Line Totals
var ExtraValidateFunction = function(form) {
// Get current values
var qty = parseFloat(APD_GetSingleValue(37, 15)) || 0;
var price = parseFloat(APD_GetSingleValue(37, 22)) || 0;
var discount = parseFloat(APD_GetSingleValue(37, 27)) || 0;
// Calculate (though BC does this server-side too)
var lineAmount = qty * price * (1 - discount / 100);
// Update display
document.getElementById('lineTotal').textContent = lineAmount.toFixed(2);
// Update order totals
recalculateOrderTotal();
};
Validation Examples
Customer Card
<?open(Customer)?>
<?filter(Customer,"No.",Parm('id'))?>
<?findfirst(Customer)?>
<form id="customerCard">
<input type="hidden" name="TABLE" value="18">
<!-- Primary key as a hidden input (<?key?> only writes URL query fragments) -->
<input type="hidden" name="18:1" value="<?field(Customer,"No.")?>">
<!-- Customer No. - typically not editable -->
<div class="field">
<label>No.</label>
<input type="text" value="<?field(Customer,"No.")?>" disabled>
</div>
<!-- Name - with validation -->
<div class="field">
<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>
<!-- Country/Region - might trigger address format changes -->
<div class="field">
<label>Country/Region</label>
<input type="text"
name="18:35"
value="<?field(Customer,Country/Region Code)?>"
onfocus="APD_Focus(this, 18, 35)"
onchange="APD_validate(this, 18, 35)">
<button type="button"
onclick="APD_Lookup(18, 35, 9, 1, 2, this)">...</button>
</div>
<!-- VAT Registration - has format validation -->
<div class="field">
<label>VAT Registration No.</label>
<input type="text"
name="18:86"
value="<?field(Customer,VAT Registration No.)?>"
onfocus="APD_Focus(this, 18, 86)"
onchange="APD_validate(this, 18, 86)">
</div>
<!-- Credit Limit -->
<div class="field">
<label>Credit Limit (LCY)</label>
<input type="number"
name="18:21"
value="<?field(Customer,Credit Limit (LCY))?>"
step="0.01"
onfocus="APD_Focus(this, 18, 21)"
onchange="APD_validate(this, 18, 21)">
</div>
</form>
Sales Line with Cascading Updates
<form id="salesLine">
<input type="hidden" name="TABLE" value="37">
<!-- Sales Line primary key: Document Type (1), Document No. (3), Line No. (4) -->
<input type="hidden" name="37:1" value="<?field("Sales Line","Document Type")?>">
<input type="hidden" name="37:3" value="<?field("Sales Line","Document No.")?>">
<input type="hidden" name="37:4" value="<?field("Sales Line","Line No.")?>">
<!-- Item No. - triggers description, price, UOM updates -->
<div class="field">
<label>Item No.</label>
<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>
</div>
<!-- Description - auto-filled from item -->
<div class="field">
<label>Description</label>
<input type="text"
name="37:11"
value="<?field(Sales Line,Description)?>"
onfocus="APD_Focus(this, 37, 11)"
onchange="APD_validate(this, 37, 11)">
</div>
<!-- Quantity - triggers amount calculation -->
<div class="field">
<label>Quantity</label>
<input type="number"
name="37:15"
value="<?field(Sales Line,Quantity)?>"
min="0"
onfocus="APD_Focus(this, 37, 15)"
onchange="APD_validate(this, 37, 15)">
</div>
<!-- Unit Price - auto-filled, but can be overridden -->
<div class="field">
<label>Unit Price</label>
<input type="number"
name="37:22"
value="<?field(Sales Line,Unit Price)?>"
step="0.01"
onfocus="APD_Focus(this, 37, 22)"
onchange="APD_validate(this, 37, 22)">
</div>
<!-- Line Amount - calculated, display only -->
<div class="field">
<label>Amount</label>
<span id="lineAmount"><?field(Sales Line,Line Amount)?></span>
</div>
</form>
<script>
var ExtraValidateFunction = function(form) {
// Update displayed amount after validation
var amount = APD_GetSingleValue(37, 103); // Line Amount field
document.getElementById('lineAmount').textContent = amount;
};
</script>
Validation with Lookups
Combine validation with lookup windows:
<div class="lookup-field">
<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)"
title="Select Salesperson">
...
</button>
</div>
When a value is selected from lookup:
- Value is placed in the input field
APD_validate()is automatically triggered- Related fields update based on selection
Message Handling
Server Messages
The server can send messages via the _MESSAGE field:
// In APD_validate response handling
if (vaerdi[0] == "_MESSAGE") {
Message = 1;
}
// Display message
if (Message == 1) {
var msg = getvar["_MESSAGE"];
msg = msg.replace(/\x2B/g, " ");
msg = msg.replace(/\\/g, "\n");
APDAlert(msg);
}
Custom Alert Implementation
Override APDAlert for custom error display:
// Modal-based alert
APDAlert = function(message) {
var modal = document.createElement('div');
modal.className = 'apd-alert-modal';
modal.innerHTML = `
<div class="apd-alert-content">
<p>${message}</p>
<button onclick="this.parentElement.parentElement.remove()">OK</button>
</div>
`;
document.body.appendChild(modal);
};
.apd-alert-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
}
.apd-alert-content {
background: white;
padding: 20px;
border-radius: 8px;
max-width: 400px;
}
Best Practices
Always Pair Focus and Change
<input onfocus="APD_Focus(this, T, F)"
onchange="APD_validate(this, T, F)">
Let BC Handle Calculations
Don't duplicate BC logic client-side. Let validation update calculated fields:
// Don't do this
var total = qty * price;
document.getElementById('total').value = total;
// Do this - let BC calculate
APD_validate(qtyField, 37, 15);
// Total is updated automatically in response
Provide Visual Feedback
input:focus {
border-color: #007bff;
}
input.validating {
background-color: #fffde7;
}
input.valid {
border-color: #28a745;
}
input.invalid {
border-color: #dc3545;
}
Handle Network Errors
var originalAlert = APDAlert;
APDAlert = function(message) {
if (message.indexOf('Communication error') >= 0) {
// Retry logic or special handling
showRetryDialog(message);
} else {
originalAlert(message);
}
};