cd ../cookbook

GlideAjax done right

The client script needs server data and there are three ways to get it, two of them wrong. The complete client-callable pair, the security angle nobody teaches, and when g_scratchpad is the better answer.

The problem

An agent picks a caller on the incident form and you need to show whether that caller is a VIP and how many incidents they already have open. The data lives on the server, your script runs in the browser, and the fields on the form do not carry it. This is the single most common client scripting need on the platform, and it is also where I see the most copy-pasted damage: synchronous calls that freeze the browser, script includes that hand out data to anyone who asks, and dot-walked getReference calls dragging whole records across the wire. GlideAjax is the right tool. Here is the whole pattern, done properly.

The quick version

The server side is a client-callable script include extending AbstractAjaxProcessor. Check the Client callable box when you create it.

var CallerInfoAjax = Class.create();
CallerInfoAjax.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

    getCallerSummary: function() {
        var out = { found: false, vip: false, openIncidents: 0 };

        var userId = this.getParameter('sysparm_user_id');
        if (!userId)
            return JSON.stringify(out);

        var user = new GlideRecord('sys_user');
        if (!user.get(userId) || !user.canRead())
            return JSON.stringify(out);

        out.found = true;
        out.vip = user.getValue('vip') == '1';

        var agg = new GlideAggregate('incident');
        agg.addQuery('caller_id', user.getUniqueValue());
        agg.addActiveQuery();
        agg.addAggregate('COUNT');
        agg.query();
        if (agg.next())
            out.openIncidents = parseInt(agg.getAggregate('COUNT'), 10) || 0;

        return JSON.stringify(out);
    },

    type: 'CallerInfoAjax'
});

The client side is an onChange script on caller_id:

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || !newValue) {
        g_form.hideFieldMsg('caller_id', true);
        return;
    }

    var requestedFor = newValue; // remember who we asked about

    var ga = new GlideAjax('CallerInfoAjax');
    ga.addParam('sysparm_name', 'getCallerSummary');
    ga.addParam('sysparm_user_id', newValue);
    ga.getXMLAnswer(function(answer) {
        // The agent may have changed the caller again while we waited
        if (g_form.getValue('caller_id') != requestedFor)
            return;

        var info = JSON.parse(answer);
        g_form.hideFieldMsg('caller_id', true);
        if (info.found && info.vip)
            g_form.showFieldMsg('caller_id',
                'VIP caller with ' + info.openIncidents + ' open incidents.', 'info');
    });
}

That is the whole thing. Everything below is why each line is shaped the way it is.

Building it step by step

Step one: the script include. Create it with the same name as the type property, extend global.AbstractAjaxProcessor, and check Client callable. Without that checkbox the client gets back an empty answer and no error, which is a fun afternoon the first time. If you are in a scoped app, you still extend global.AbstractAjaxProcessor, and your client code calls the include by its fully qualified name, scope prefix included.

The script include name, its type property, and the string you pass to new GlideAjax() have to match exactly. Two out of three matching is the single most common cause of "the callback never fires."

Step two: one method, one job. Each public function on the include is an endpoint. getCallerSummary reads its inputs with this.getParameter('sysparm_user_id'), never from anywhere else. I name my parameters sysparm_ plus something descriptive, because that is the convention the platform itself uses and future-you will grep for it.

Step three: return JSON. The answer travels back as a single string. For anything beyond a yes or no, build an object and JSON.stringify it on the server, JSON.parse it on the client. I also return the same shape on every path, including failures. The client code stays simple because it never has to guess whether the answer has structure.

Step four: the client call. GlideAjax takes the include name, sysparm_name picks the method, addParam carries your inputs, and getXMLAnswer hands your callback the answer string and nothing else. That callback is the only place the response exists. Anything you want to do with the data happens in there, not on the line after the call.

Step five: guard against stale answers. Fast typists change reference fields twice before your first response lands. Capture the value you asked about, and when the answer arrives, check the field still holds it. Two lines, and it removes a whole class of "the form shows the wrong caller's data" bugs that are miserable to reproduce.

What is actually happening under the hood

When you call getXMLAnswer, the browser fires an asynchronous POST to the instance's ajax processor endpoint, naming the script include, the method, and your parameters. The platform instantiates your include server-side, runs the method as the logged-in user, wraps the returned string in an XML envelope, and your callback receives the answer attribute from it. Two things follow from that. First, everything runs under the caller's session, so canRead() and ACLs evaluate as that user, which is exactly what you want. Second, nothing about the request is special: it is a plain HTTP endpoint, which brings us to security.

The part everyone skips: security

Client callable means clients can call it. Not your client script: any logged-in user, with any parameters they like, straight from the browser console. The moment you check that box, every method on the include is a public API for your authenticated population. So write it like one. Validate every parameter before using it. Re-check authorization on the server: my example calls user.canRead() even though the record came from a reference field, because the request might not have come from my form at all. Return the minimum: I send back one boolean and one count, not the user record. And never concatenate a parameter into an encoded query string; pass values through addQuery so they are treated as data, not query syntax.

Never put delete, update, or privilege-changing logic in a client-callable method just because a form script wanted it. Someone will call it with parameters you never imagined. Getters should get. If a client action must change data, route it through a UI Action whose server script does the write with proper checks.

Why getXMLWait is banned

There is a synchronous cousin, getXMLWait, and you will still find it in old code and old forum answers. Do not use it. It blocks the browser's main thread until the server responds, which means the entire form freezes for however long your query takes, on every change, for every agent, forever. It also simply does not exist outside the classic UI: Service Portal and configurable workspaces will throw at you. I treat any getXMLWait in a code review as a bug regardless of whether it currently works. Asynchronous with a callback is barely more code and it is correct in every UI.

The cheaper alternative: g_scratchpad

If the data you need is knowable when the form loads, do not make a round trip at all. A display business rule runs server-side as the form is being prepared, and anything you put on g_scratchpad ships to the client with the form, free of charge.

(function executeRule(current, previous /*null when async*/) {

    g_scratchpad.callerVip = current.caller_id.vip.toString() == 'true';

})(current, previous);

Then any client script just reads g_scratchpad.callerVip. No latency, no callback, no endpoint to secure. The limitation is the flip side of the benefit: scratchpad is a snapshot from load time. The moment the answer depends on something the user changes on the form, like our caller field, you are back to GlideAjax. My rule of thumb: known at load, scratchpad. Depends on user input, GlideAjax.

Gotchas

The classics, beyond the warn above: forgetting the Client callable checkbox (empty answer, no error), a typo in sysparm_name (same symptom), and in scoped apps, calling new GlideAjax('CallerInfoAjax') when the include lives in a scope and needs its prefix. Also watch booleans: getValue('vip') returns the string '1', not true, which is why the include compares against '1' explicitly. And remember onChange fires during form load too; the isLoading guard at the top is not decoration.

Variations worth knowing

getReference with a callback. For a quick field or two off a reference, g_form.getReference('caller_id', callback) works and needs no script include. The cost is that it fetches the whole record, you cannot shape or aggregate the result, and it is a classic-UI habit that ports awkwardly. I reach for it in throwaway scripts, not production forms.

Multiple values the old way. Ancient code returns several values by building XML nodes on the response and parsing responseXML in the client. It still works. JSON made it obsolete: one stringify, one parse, done.

Validation timing. Async answers and onSubmit validation mix badly, because the submit finishes before your callback runs. Rather than fighting that with re-submit tricks, validate earlier: do the GlideAjax check onChange and stash the verdict, so onSubmit only reads a value you already have.

Workspace and portal. The exact pair in this recipe runs unchanged in configurable workspaces and Service Portal client scripts. That is one more quiet argument for the async pattern: it is the only one that works everywhere.