Built something you're proud of? Tell the story. A quick G2 review of App Engine or Build Agent helps other developers see what's possible on ServiceNow. Share your experience.

CSM Workspace – Modal popup validation not stopping submit (UI Page + UI Builder approach)

AviKadam
Giga Contributor

Click to collapse CRM and Industry solutions category

Hi everyone,

I am working on a requirement in CSM Workspace where I need to implement a modal popup that contains:

  • Notes (mandatory)
  • Date/Time (mandatory, should not allow past date)

On clicking a button from the Case form:

  • A modal popup opens
  • User fills both fields
  • On Submit → values should update fields on the Case record

Current Approach (UI Action + UI Page + Glide Ajax)

  • Workspace UI Action → opens modal using "g_model.showframe"
  • UI Page contains form (text area + datetime)
  • Client script performs validation
  • Script Include updates the case record

Issue

Validation is working (error message shows), but:

 The modal still closes even when validation fails


Expected Behavior

  • If fields are empty → modal should NOT close
  • If date is in past → modal should NOT close
  • Modal should only close after successful save

What I tried

  • Using "return false" in submit function
  • Controlling button behavior
  • Handling validation before GlideAjax call

Still seeing inconsistent modal behavior in Workspace.

 

Sharing the full code base -

 

UI action-

function onClick(g_form) {

var sysId = g_form.getUniqueValue();

g_modal.showFrame({
title: "Enter Information for Manual ORT",
url: "/sn_customerservice_openmanualort_InfoDialog.do?sysparm_sys_id=" + sysId,
type: "page",
size: "lg",
height: "280px"
});
}

 

UI Page-

HTML

<?xml version="1.0" encoding="utf-8" ?>

<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <style>
        /* Removes the vertical scrollbar inside the modal iframe */
        html,
        body {
            overflow: hidden !important;
        }

        /* For some Workspace versions */
        .now-modal-body,
        now-modal-body {
            overflow-y: hidden !important;
        }

        /* Prevent inner divs from forcing scrollbars */
        #ort_wrapper {
            overflow: hidden !important;
        }
    </style>
    <div style="padding: 5px; font-family: Arial, sans-serif, max-width: 500px; margin: auto;">

        <j:set var="jvar_sysid" value="${sysparm_sys_id}" />
        <g:ui_form>
            <div style="margin-bottom: 16px; font-family: Arial, sans-serif">
                <label for="info_text" style="display:block; font-weight:bold; margin-bottom:6px;">
                    Notes / Comments <span style="color:red;">*</span>
                </label>
                <textarea id="info_text" name="info_text" rows="4" style="width:100%; padding:8px; border:1px solid #ccc; border-radius:4px; box-sizing:border-box;" placeholder="Enter your notes here..."></textarea>
            </div>

            <div style="margin-bottom: 20px;  font-family: Arial, sans-serif">
                <label for="info_datetime" style="display:block; font-weight:bold; margin-bottom:6px;">
                    Scheduled Date / Time <span style="color:red;">*</span>
                </label>
                <input type="datetime-local" id="info_datetime" name="info_datetime" style="width:100%; padding:8px; border:1px solid #ccc; border-radius:4px; box-sizing:border-box;" />
            </div>

            <div id="error_msg" style="color:red; margin-bottom:12px; display:none;"></div>

            <div style="text-align:right;">
                <button onclick="cancelDialog()" style="margin-right:10px; padding:8px 16px; border:1px solid #ccc;
               background:#fff; border-radius:4px; cursor:pointer;">
                    Cancel
                </button>
                <!-- <button onclick="submitDialog()" style="padding:8px 16px; background:#0070d2; color:#fff;
               border:none; border-radius:4px; cursor:pointer;">
                    Submit
                </button> -->
                <button id="submit_btn" onclick="return submitDialog()" style="padding:8px 16px; background:#0070d2; color:#fff; border:none; border-radius:4px; cursor:pointer;">
                    Submit
                </button>
            </div>

            <!-- <div style="text-align:right;">
                <button type="button" onclick="cancelDialog()" style="margin-right:10px; padding:8px 16px; border:1px solid #ccc;
               background:#fff; border-radius:4px; cursor:pointer;">
                    Cancel
                </button>

                <button type="button" onclick="submitDialog()" style="padding:8px 16px; background:#0070d2; color:#fff;
               border:none; border-radius:4px; cursor:pointer;">
                    Submit
                </button>
            </div> -->

        </g:ui_form>

    </div>
</j:jelly>
 
Client Script-
function submitDialog() {
    try {

        var notes = document.getElementById('info_text').value.trim();
        var datetime = document.getElementById('info_datetime').value;
        var errorDiv = document.getElementById('error_msg');
        var submitBtn = document.getElementById("submit_btn");
        if (!notes || !datetime) {
            errorDiv.style.display = 'block';
            errorDiv.style.color = "red";
            errorDiv.innerHTML = 'Both fields are required before submitting.';
            return;
        }

        if (!validateORTDate()) {
            //submitBtn.disabled = true;
            submitBtn.style.display = 'none';
            //return; // stop submit
        }

        errorDiv.style.display = 'none';

        var sysId = getParameterByName('sysparm_sys_id');

        if (!sysId) {
            alert("ERROR: sys_id is empty");
            return;
        }

        var ga = new window.parent.GlideAjax('sn_customerservice.CaseInfoDialogAjax');

        ga.addParam('sysparm_name', 'saveInfo');
        ga.addParam('sysparm_sys_id', sysId);
        ga.addParam('sysparm_notes', notes);
        ga.addParam('sysparm_datetime', datetime);

        ga.getXMLAnswer(function(answer) {

            if (answer === 'success') {
                try {
                    GlideDialogWindow.get().destroy();
                    //top.NOW.modal.close();
                    //g_modal.hide();
                } catch (e) {
                    window.close();
                }

                if (window.parent && window.parent.g_form) {
                    window.parent.g_form.reload();
                }

            } else {
                errorDiv.style.display = 'block';
                errorDiv.style.color = "red";
                errorDiv.innerHTML = 'Save failed. Please try again.';
            }
        });

        return false;

    } catch (e) {
        alert("inside catch block" + e);
    }

}
function cancelDialog() {
    // try {
    //     GlideDialogWindow.get().destroy();
    // } catch (e) {
    //     window.close();
    // }
    top.NOW.modal.close();
    //g_modal.hide();
}

function getParameterByName(name) {
    var url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
    var results = regex.exec(url);
    if (!results) return "";
    if (!results[2]) return "";
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

function validateORTDate() {
    var dtField = document.getElementById("info_datetime");
    var errorDiv = document.getElementById("error_msg");

    var entered = dtField.value;
    if (!entered) return true; // nothing to validate

    var selectedDate = new Date(entered);
    var now = new Date();

    // Compare selected date with current date/time
    if (selectedDate < now) {

        dtField.value = "";
        errorDiv.style.display = 'block';
        errorDiv.style.color = "red";
        errorDiv.innerHTML = "Only current or future date/time can be selected.";
        return false;
    }

    // Hide error if valid
    errorDiv.style.display = "none";
    return true;
}
 
 
 
Script Include- Client callable is enabled and values are being updated as per requirement
 
var CaseInfoDialogAjax = Class.create();
CaseInfoDialogAjax.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

    saveInfo: function() {
        var sysId = this.getParameter('sysparm_sys_id');
        var notes = this.getParameter('sysparm_notes');
        var datetime = this.getParameter('sysparm_datetime');

        if (!sysId) {
            return 'error: missing sys_id';
        }
        var gr = new GlideRecord('sn_customerservice_case');
        if (gr.get(sysId)) {

            gr.setValue('u_manual_ort_reason', notes);
            gr.setValue('u_sap_manual_ort', datetime);

            gr.update();
            return 'success';
        }

        return 'error: record not found';
    },

    type: 'CaseInfoDialogAjax'
});
 
 This approach is working, but I am not able to handle the "submit" and "cancel" buttons they get closed even if there is any applied validation.
 
<div style="text-align:right;">
                <button onclick="cancelDialog()" style="margin-right:10px; padding:8px 16px; border:1px solid #ccc;
               background:#fff; border-radius:4px; cursor:pointer;">
                    Cancel
                </button>
                <!-- <button onclick="submitDialog()" style="padding:8px 16px; background:#0070d2; color:#fff;
               border:none; border-radius:4px; cursor:pointer;">
                    Submit
                </button> -->
                <button id="submit_btn" onclick="return submitDialog()" style="padding:8px 16px; background:#0070d2; color:#fff; border:none; border-radius:4px; cursor:pointer;">
                    Submit
                </button>
            </div>

            <!-- <div style="text-align:right;">
                <button type="button" onclick="cancelDialog()" style="margin-right:10px; padding:8px 16px; border:1px solid #ccc;
               background:#fff; border-radius:4px; cursor:pointer;">
                    Cancel
                </button>

                <button type="button" onclick="submitDialog()" style="padding:8px 16px; background:#0070d2; color:#fff; border:none; border-radius:4px; cursor:pointer;">
                    Submit
                </button>
            </div> -->
 
 
The comment "div" when enabled makes the validation applied in client script work, but the "popup" stops closing on click of "submit" and "cancel".

Questions

  1. Is using UI Page + g_modal the correct approach in Workspace?
  2. How can we properly stop modal from closing on validation failure?
  3. Is there a recommended way to implement this using UI Builder (Modal component + data resource)?

Any guidance or best practices would be really helpful.

Thanks!

 

1 REPLY 1

Samantha-JaS
ServiceNow Employee

Here's the revised response without code:


Hi,

Thanks for sharing the full code, it makes it much easier to give useful feedback. Let me work through your questions and flag a few things I spotted in the implementation itself.


Before answering the questions, a couple of things worth checking in your current code

Looking at your submitDialog() function, there is a logic issue worth investigating. When validateORTDate() returns false, the code hides the submit button but does not return early, meaning execution continues and the GlideAjax call fires regardless of the validation result. Adding an early return after the failed date validation check should prevent the Ajax call from proceeding when validation has not passed.

Also worth checking: your buttons are inside a g:ui_form Jelly tag. This tag can trigger native HTML form submission behaviour that bypasses the return value from your JavaScript function entirely. If you do not need g:ui_form for anything else on the page (GlideAjax does not require it), removing it and ensuring your buttons explicitly use type="button" is likely to give you more predictable control over submission behaviour.


1. Is UI Page + g_modal.showFrame the right approach for Workspace?

It is supported, but generally considered a compatibility or migration pattern rather than the recommended approach for net-new Workspace functionality. The modal lifecycle in Workspace is influenced by iframe state and internal runtime events, not purely by your client-side logic, which is why behaviour can appear inconsistent even when your validation is firing correctly.


2. How can you reliably stop the modal closing on validation failure?

With showFrame, there is no fully guaranteed way to control modal closure from within the UI Page. Even with correct validation logic, the modal can close due to iframe navigation, form handling, or Workspace runtime events outside your control. Addressing the two points above may improve your current behaviour, but complete reliability is not always achievable with this pattern.


3. Is there a recommended Workspace-native approach?

Yes. For Workspace, the recommended approach is to use a UI Builder modal opened via a Declarative Action. This avoids the iframe entirely and gives you direct control over validation, submission, and modal lifecycle in a way that aligns with how Workspace is designed to operate. ServiceNow's documentation on Declarative Actions and UI Builder components on docs.servicenow.com is a good starting point if you want to explore that route.


One important caveat

I can only see the code shared here. There may be other factors in your implementation I am not aware of, including other client scripts or UI policies running in the workspace, declarative action mappings, global scripts, or workspace runtime configuration. Any of these could be contributing to the behaviour you are seeing, so it is worth reviewing those areas if the above does not fully resolve things.


Hope this helps move things forward. If it does, please mark it as helpful or accept it as the answer so it helps others in the community find useful answers more quickly.