Overcharging Model‑Driven Forms! JavaScript 101
Mastering Form Events & Best Practices
🚀 Why JavaScript Still Matters
Model‑driven apps in Dynamics 365 / Power Apps are built on declarative logic, but there are still scenarios where you need to:
- Hide/Show controls based on user role or data.
- Validate fields before a record is saved.
- Calculate values on the fly.
- Call external services from the form.
JavaScript gives you that flexibility. In this post we’ll dive into the three core form events—OnLoad, OnSave, and OnChange—and share the best practices that keep your code fast, maintainable, and future‑proof.
Note: We’re not covering Ribbon Bar buttons here; but we have a post about it right here! Ribbon Bar Redefined: Classic Workbench vs. Modern Command‑Bar Designer
📚 Understanding Form Events
Tip: UseformContextinstead of the deprecatedXrm.Page. It works in both web and mobile clients.
⚙️ OnLoad – The “First‑Look” Event
What it does
- Runs once per form open.
- Ideal for setting up the UI before the user interacts with the form.
Sample Code
function onFormLoad(executionContext) {
"use strict";
var formCtx = executionContext.getFormContext();
// 1️⃣ Hide a field on load
formCtx.getControl("new_customfield").setVisible(false);
// 2️⃣ Set a default value (if empty)
var attr = formCtx.getAttribute("new_defaultvalue");
if (!attr.getValue()) {
attr.setValue(42);
}
// 3️⃣ Load reference data asynchronously
loadReferenceData(formCtx);
}
async function loadReferenceData(formCtx) {
try {
const result = await Xrm.WebApi.retrieveMultipleRecords(
"new_referenceentity",
"?$select=new_name"
);
// Do something with the data...
} catch (err) {
console.error("Failed to load reference data:", err);
formCtx.ui.setFormNotification(
"Unable to load reference data.",
"ERROR",
"refDataError"
);
}
}Best Practices
💾 OnSave – The “Guard” Event
What it does
- Fires just before the record is written to the database.
- You can cancel the save, modify values, or trigger server‑side logic.
Sample Code
function onFormSave(executionContext) {
"use strict";
var formCtx = executionContext.getFormContext();
var eventArgs = executionContext.getEventArgs();
// Example: Ensure a required field is not blank
var reqAttr = formCtx.getAttribute("new_requiredfield");
if (!reqAttr.getValue()) {
eventArgs.preventDefault(); // Cancel the save
formCtx.ui.setFormNotification(
"The 'Required Field' cannot be empty.",
"ERROR",
"requiredFieldError"
);
return;
}
// Example: Update a calculated field before saving
var amount = formCtx.getAttribute("new_amount").getValue() || 0;
formCtx.getAttribute("new_total").setValue(amount * 1.2);
}Best Practices
🔄 OnChange – The “Reactive” Event
What it does
- Fires whenever a specific field’s value changes (user input or programmatic).
- Perfect for dynamic UI updates and calculations.
Sample Code
function onAmountChange(executionContext) {
"use strict";
var formCtx = executionContext.getFormContext();
var control = executionContext.getEventArgs().getControl();
if (control.getName() !== "new_amount") return; // Safety check
var amount = control.getValue() || 0;
var taxRate = 0.15;
// Update related field
formCtx.getAttribute("new_tax").setValue(amount * taxRate);
formCtx.getAttribute("new_total").setValue(amount + (amount * taxRate));
}Best Practices
📌 Registering Events
Via Form Editor (recommended)
- Open the form in the editor.
- Go to Form Properties → Events tab.
- Add your JavaScript web resource and select the function name for:
OnLoadOnSave(check “Pre‑save” if you want it before validation)OnChange(select the attribute in the drop‑down)
Programmatically
// Example: Register OnLoad programmatically
function registerEvents(executionContext) {
var formCtx = executionContext.getFormContext();
formCtx.data.entity.addOnLoad(onFormLoad);
formCtx.data.entity.addOnSave(onFormSave);
// For attribute change
formCtx.getAttribute("new_amount").addOnChange(onAmountChange);
}Tip: Registering events in the editor keeps your code cleaner and avoids accidental double‑registration.
⚠️ Common Pitfalls & Debugging Tips
🚀 Performance & Maintainability
- Minimize handlers: Combine logic where possible; avoid registering multiple functions for the same event unless necessary.
- Cache data: Store results of Web API calls in a scoped variable to reuse across events.
- Debounce rapid changes: Use
setTimeoutor libraries like lodash’sdebounce. - Use strict mode:
"use strict";catches common mistakes early. - Follow naming conventions: Prefix functions with the form name (
account_onLoad, etc.) to avoid collisions.
📌 Summary
By keeping each event focused and following the best practices above, you’ll write JavaScript that’s fast, reliable, and easy to maintain—no more “overcharging” your forms with unnecessary code.
🔜 Next in the Series
Ribbon Bar Buttons: Customizing the Command Bar with JavaScript.
Stay tuned for a deep dive into adding custom buttons, handling click events, and integrating with Power Automate flows.
Happy coding! 🚀