Why the error stays after the user fixes the field
On a Workday Extend edit page, an error set with setError stays on the widget until a script calls clearError on that same widget; Workday's examples always pair the two, and nothing in the docs clears a scripted error for you. The onSubmit documentation adds the rule that causes the stuck page: while an error or warning remains, the user can't resubmit, and the page can't fire onSubmit again.
So a rule checked only in onSubmit sets an error. The user corrects the field. Nothing runs to clear the error, because the only code that would clear it is the onSubmit handler, which won't run again until the error is gone.
What onSubmit and onChange are each for
Workday draws the line itself: use onChange on the widget for field-level validation and onSubmit for page-level validation.
onChange fires when the user changes a widget's value on an edit page. It applies to checkBox, checkBoxList, currency, date, dropdown, instanceList, number, radioGroup, richText, text, textArea and time. It can read and set every widget on the page and can invoke deferred endpoints, so it's also where you check a value against Workday data.
onSubmit is a root property of the PMD, not a widget attribute. It fires when the user clicks OK and runs before any outbound endpoint. If it calls setError or setWarning on any widget, the page shows the message and calls no outbound endpoint at all. That's what you want for checks that span fields, such as "end date after start date". Values the handler sets during submission aren't shown to the user, because the page is already submitting.
Both run on edit pages only. Event handlers on a view page can't reference the page's widgets.
The pattern that always lets the user recover
Write each rule once, as a function in the PMD's script block, and call it from both places: from the onChange of every widget the rule reads, and from onSubmit. The function sets the error when the rule fails and clears it when the rule passes. Workday's validation example in Reference: PMD Scripting Examples uses the same shape: one function per rule, with a set branch and a clear branch, called from the onChange of each widget in the pair.
{
"id": "equipmentRequest",
"script": "<%
var checkDates = function() {
if (empty startDate.value || empty endDate.value || date:after(endDate.value, startDate.value)) {
endDate.clearError();
} else {
endDate.setError('End date must be after the start date.');
}
};
%>",
"onSubmit": "<% checkDates(); %>",
"presentation": {
"pageType": "edit",
"body": {
"type": "fieldSet",
"children": [
{ "type": "date", "id": "startDate", "label": "Start date", "onChange": "<% checkDates(); %>" },
{ "type": "date", "id": "endDate", "label": "End date", "onChange": "<% checkDates(); %>" }
]
}
}
}
The function runs from startDate's onChange as well, though the error sits on endDate. That's deliberate. A user can fix a cross-field error by changing either field, and whichever one they change has to be able to clear the error.
Three variants of the same mistake:
- The error is on a widget the user can't change. If the message lands on a read-only or hidden widget, no user action fires that widget's
onChange. Set the error on a field the user can edit, or clear it from the fields they can. - The clear branch is missing. An
ifthat callssetErrorwith noelsethat callsclearErrorleaves the first error in place forever. - The error is set in
onSubmitwith noonChangeat all. The page then has no path back.
Workday's own onSubmit sample sets location.value = '' before calling setError. You don't have to blank the field; leaving the value in place lets the user correct it instead of retyping it.
Errors versus warnings
setWarning and clearWarning work the same way as errors, and a warning also blocks resubmission until it's cleared. The difference is event.ignoreWarnings(). Call it inside onSubmit and the page submits despite warnings. Errors can't be ignored. Use a warning for "this looks unusual, continue?" and an error for "this will be rejected."
"onSubmit": "<%
event.ignoreWarnings();
checkDates();
%>"
Validating against Workday data with a deferred endpoint
When the rule needs data, such as "is this cost center active" or "is this phone number valid", have onChange invoke a deferred endpoint and set or clear the error from the result. Workday documents a POST validation endpoint whose onSend builds the request body, invoked from a text widget's onChange:
{
"name": "phoneValidation",
"baseUrlType": "workday-person",
"url": "/phoneValidation",
"httpMethod": "POST",
"deferred": true,
"onSend": "<% { 'completePhoneNumber': thePhoneNumber.value } %>"
}
"onChange": "<%
var result = phoneValidation.invoke();
if (!empty result.error) { self.setError(result.error); }
else { self.clearError(); }
%>"
Keep these calls fast. Workday applies a 24-second timeout to each endpoint and a 60-second timeout to a whole request, and a request includes remote validations as well as page loads and submissions. If a validation calls several slow endpoints every time a value changes, move it to onSubmit or cache what it fetches in pageVariables.
Errors that come back from the outbound endpoint
Everything above runs before submission. Once onSubmit passes, the page calls the outbound endpoints in the order they appear in the PMD, and a business rule enforced by Workday or by your orchestration can still reject the request. That rejection isn't a widget error and clearError doesn't apply to it. The user sees the endpoint's error, and you shape its text with the single responseErrorDetail in outboundData, which covers every outbound endpoint on the page. Showing an orchestration's error on an Extend page covers that side. Validate on the page anything the user can fix on the page, so the endpoint rarely has to refuse.
Grids follow the same rules, with one cell per row to validate. Grid validation in Extend covers reaching cells in onSubmit and setting an error on the right row.
Testing that the user can recover
Test the recovery, not just the rejection.
- Enter values that break the rule and click OK. The message should appear and nothing should be saved. Confirm in the app's data that no record was created.
- Fix the value by changing the field that carries the error. The error should disappear immediately. Then reset, break the rule again, and this time fix it by changing the other field. It should also clear.
- Click OK again. The page should submit, and the record you read back should hold the corrected values.
If step 2 leaves the error showing, log from the handler to see whether it ran. console.debug writes to the app's logs, visible under Analytics > Logs on the Workday Developer Site with the filter wd_category is console. A script exception logs under wd_category is pageError.script. A console call can't be the last line of a script whose return value matters, so put it before the final expression.