Dynamics 365 JavaScript toggle validation on form save
- Jon Russell

- 14 minutes ago
- 4 min read
Hey there
This is really a blog post for future Jon, just in case he ever needs to use it again. The situation is, we have a form with four toggles on it, and we need to make sure that on change of a certain set of fields, we are using Dynamics 365 JavaScript toggle validation on form save.

And then even if one of the toggles is not set to true, we get this error

Finally, able to save when all are set to true:

Then as soon as I set one of the fields to a different value, all toggles switch to false:

Process repeats.
Javascript:
/** * PowerApps Form Handler - Toggle Validation Module * Manages toggle field validation and automatic reset functionality */(function (window) { "use strict"; // Configuration constants const TOGGLE_FIELDS = ["jdr_toggle1", "jdr_toggle2", "jdr_toggle3", "jdr_toggle4"]; const TRIGGER_FIELDS = ["jdr_enddate", "jdr_endlocation", "jdr_startdate", "jdr_startlocation"]; const NOTIFICATION_ID = "jdr_toggle_check"; // Track registered handlers to prevent duplicates const registeredHandlers = new WeakMap(); /** * Safely retrieves an attribute from the form context * @param {Object} formContext - The Dynamics 365 form context * @param {string} fieldName - The logical name of the field * @returns {Object|null} The attribute object or null if not found */ function getAttribute(formContext, fieldName) { try { return formContext.getAttribute(fieldName); } catch (error) { console.warn(`Field '${fieldName}' not found on form:`, error); return null; } } /** * Validates that the form context is valid * @param {Object} formContext - The form context to validate * @returns {boolean} True if valid, false otherwise */ function isValidFormContext(formContext) { return formContext && formContext.data && formContext.ui; } /** * Validates field configuration on form load * @param {Object} formContext - The Dynamics 365 form context */ function validateFieldConfiguration(formContext) { const missingFields = []; // Check toggle fields TOGGLE_FIELDS.forEach(field => { if (!getAttribute(formContext, field)) { missingFields.push(field); } }); // Check trigger fields TRIGGER_FIELDS.forEach(field => { if (!getAttribute(formContext, field)) { missingFields.push(field); } }); if (missingFields.length > 0) { console.warn( "Toggle validation module: The following fields are missing from the form:", missingFields ); } } /** * Resets all toggle fields to false and clears notifications * @param {Object} formContext - The Dynamics 365 form context */ function resetToggles(formContext) { if (!isValidFormContext(formContext)) { console.error("Invalid form context provided to resetToggles"); return; } let resetCount = 0; TOGGLE_FIELDS.forEach(field => { const attr = getAttribute(formContext, field); if (attr) { attr.setValue(false); attr.setSubmitMode("always"); resetCount++; } }); formContext.ui.clearFormNotification(NOTIFICATION_ID); if (resetCount > 0) { console.log(`Reset ${resetCount} toggle field(s)`); } } /** * Event handler for trigger field changes - resets toggle fields * @param {Object} executionContext - The execution context from the event */ function resetTogglesOnChange(executionContext) { try { const formContext = executionContext.getFormContext(); if (!isValidFormContext(formContext)) { throw new Error("Invalid form context in resetTogglesOnChange"); } const changedField = executionContext.getEventSource(); const fieldName = changedField ? changedField.getName() : "unknown"; console.log(`Trigger field '${fieldName}' changed - resetting toggles`); resetToggles(formContext); } catch (error) { console.error("Error resetting toggles on field change:", error); } } /** * Event handler for form save - validates all toggles are true * @param {Object} executionContext - The execution context from the save event */ function validateTogglesOnSave(executionContext) { try { const formContext = executionContext.getFormContext(); if (!isValidFormContext(formContext)) { throw new Error("Invalid form context in validateTogglesOnSave"); } const toggleStates = TOGGLE_FIELDS.map(field => { const attr = getAttribute(formContext, field); return { field, value: attr ? attr.getValue() : null }; }); const allTrue = toggleStates.every(state => state.value === true); if (!allTrue) { executionContext.getEventArgs().preventDefault(); const unsetToggles = toggleStates .filter(state => state.value !== true) .map(state => state.field) .join(", "); const errorMessage = `All toggles must be set to true before saving. Unset fields: ${unsetToggles}`; formContext.ui.setFormNotification( errorMessage, "ERROR", NOTIFICATION_ID ); console.warn("Save prevented - toggles not all true:", unsetToggles); } else { formContext.ui.clearFormNotification(NOTIFICATION_ID); } } catch (error) { console.error("Error validating toggles on save:", error); } } /** * Registers all event handlers for the form * @param {Object} executionContext - The execution context from the onLoad event */ function registerHandlers(executionContext) { try { const formContext = executionContext.getFormContext(); if (!isValidFormContext(formContext)) { throw new Error("Invalid form context in registerHandlers"); } // Prevent duplicate registration if (registeredHandlers.has(formContext)) { console.log("Handlers already registered for this form context"); return; } // Validate field configuration validateFieldConfiguration(formContext); // Only reset toggles automatically if it's NOT a new record // A new record has no ID (getId() returns null) const isNewRecord = !formContext.data.entity.getId(); if (!isNewRecord) { console.log("Resetting toggles on existing record load"); resetToggles(formContext); } else { console.log("New record - skipping automatic toggle reset"); } // Register onchange handlers for trigger fields only let changeHandlerCount = 0; TRIGGER_FIELDS.forEach(field => { const attr = getAttribute(formContext, field); if (attr) { attr.addOnChange(resetTogglesOnChange); changeHandlerCount++; } }); // Register onsave validation formContext.data.entity.addOnSave(validateTogglesOnSave); // Mark as registered registeredHandlers.set(formContext, true); console.log( `Toggle validation module initialized: ${changeHandlerCount} trigger field(s) monitored, 1 save handler` ); } catch (error) { console.error("Error registering handlers:", error); } } /** * Removes all registered event handlers (cleanup) * @param {Object} executionContext - The execution context */ function cleanupHandlers(executionContext) { try { const formContext = executionContext.getFormContext(); if (!isValidFormContext(formContext)) { console.warn("Invalid form context in cleanupHandlers"); return; } // Remove onchange handlers from trigger fields TRIGGER_FIELDS.forEach(field => { const attr = getAttribute(formContext, field); if (attr) { attr.removeOnChange(resetTogglesOnChange); } }); // Remove onsave handler formContext.data.entity.removeOnSave(validateTogglesOnSave); // Clear registration tracking registeredHandlers.delete(formContext); console.log("Toggle validation handlers cleaned up"); } catch (error) { console.error("Error cleaning up handlers:", error); } } // Public API window.jdr_toggle = { onLoad: registerHandlers, cleanup: cleanupHandlers, // Expose for testing/debugging _internal: { resetToggles, validateTogglesOnSave, resetTogglesOnChange } };})(window);


Comments