top of page

Dynamics 365 JavaScript toggle validation on form save

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.


Screenshot of a Model Driven app: shows that the toggle values are false and form cannot save
Screenshot of a Model Driven app: shows that the toggle values are false and form cannot save

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


Screenshot of a Model Driven app: shows that one of the toggle values are false and form cannot save
Screenshot of a Model Driven app: shows that one of the toggle values are false and form cannot save


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


Screenshot of a Model Driven app: shows all of the toggle values are true and form can be save
Screenshot of a Model Driven app: shows all of the toggle values are true and form can be save

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


Screenshot of a Model Driven app form, after a change to the Start Location field, all toggles change to false.
Screenshot of a Model Driven app form, after a change to the Start Location field, all toggles change 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


Subscribe Form

©2019 by Jon Does Flow. Proudly created with Wix.com

bottom of page