Skip to main content
Inspiring
April 7, 2025
Answered

AutocloseAlert, CustomConfirm, and CustomPrompt Messages

  • April 7, 2025
  • 1 reply
  • 2826 views

I found this thread: https://community.adobe.com/t5/illustrator-discussions/auto-close-alert-message/td-p/9398258

Images here: https://github.com/Marshall-Brooks/Sandbox/issues/2

 

See also:

https://community.adobe.com/t5/photoshop-ecosystem-discussions/simulate-button-press-in-script-ui/m-p/7460138

 

I'm somewhat spoiled by the Enhanced Message Box add-on for Microsoft Access which shows a standard Msgbox with a countdown of seconds until the box closes.

 

I used @Peter Kahrel's Scipt UI guide to modify the first linked script as follows:

function AutocloseAlert(message, delaySeconds, title){
    title = title || 'Alert';
    var alertWindow = new Window('palette', title);
//    var control_text = alertWindow.add('edittext', [0, 0, 500, 200], message, {multiline: true});
//  var control_text = alertWindow.add('edittext', , message, {multiline: true});   
    var control_text = alertWindow.add('edittext', undefined, message, {multline:true})
//    if(delaySeconds === 0){
        var control_close = alertWindow.add('button', undefined, 'OK');       
        control_close.onClick = function(){
            if(alertWindow){
                alertWindow.hide();
                alertWindow = null;
            }
        };
 // }

    alertWindow.show();
    alertWindow.update();
   
    if(delaySeconds > 0){
        $.sleep(delaySeconds * 1000);
        alertWindow.hide();
        alertWindow = null;
    }  
}

I'm having the following issues:

  • I don't like that the window shows grayed out (disabled) - presumably b/c of the sleep function. I'd prefer it to look like the zero second but disappear when I clicked OK or when the timer expired. I tried commenting out the line "$.Sleep(DelaySeconds * 1000) and replacing it with: 
        var startTime = new Date().getTime();
        while ( new Date().getTime() - startTime < delaySeconds * 1000 ) {alertWindow.update()} ​

But it didn't change how it worked - i.e. the window was still grayed out, and clicking the OK button did not close the window, but it did disappear after the delaySeconds interval.

  • I would prefer to have the OK button show the seconds remaining, but I think I need two timers for this and I'm not sure how to implement them.
  • Probably easiest - I'd prefer to have a minimum size for the window and a maximum before it wraps to two lines, but I can basically set my "message" text to achieve this. (An alert that is more than one line is probably too long anyway).

Thank you in advance!!!

Correct answer Marshall_Brooks

I think I see the problem, but not the solution.

"answer" is being returned by the .onClick of either button, but nothing is being returned by CustomPrompt(), but I don't see how to pass "answer" to CustomPrompt() so that it can then be passed to the calling function.

 

Solved - I'll update the code above in a minute ...


CustomPrompt Code:

var result;
result = CustomPrompt("test","input","title");
alert (result);
result = prompt("test","input","title");
alert (result);

function CustomPrompt(message, defaultinput, title)
{
    var usercancelled = false;
    title = title || "Script Alert";
    defaultinput = defaultinput || "";
    var win = new Window("dialog", title, undefined, {closeButton:false});
    win.orientation = "row"; // not deprecated and required here, despite what VS Code says.
    win.alignChildren = "top";
    win.margins = 10;

    var myInputGroup = win.add ("group");
    myInputGroup.orientation = "column";
    myInputGroup.alignChildren = "left";
    // Dialog Width To Be Defined:
    var w_width = 275;

    //  150 below is somewhat arbitrary. Run the script and see where the first line ends. Adjust w_static text to a bit longer than that. Uncomment the line below to get the text length,
    // Use that in the "if" statement.
//    alert (message.length)
    if (message.length < 150 && message.indexOf("\r")===-1 && message.indexOf("\n")===-1)
    {
    // single line
         var msg = myInputGroup.add('statictext', undefined, message);
    }
    else
    
    {
    // multiline
        var msg = myInputGroup.add('statictext', undefined, 'X', {multiline: true});
        // if not using standard font size, Font must be defined below, even if using set_font, or the message will truncate unpredictably.
        msg.graphics.font="Tahoma:14";

        var X_width = msg.preferredSize[0];
        msg.preferredSize = [-1,-1];
        msg.characters = ~~(w_width/X_width);
        msg.preferredSize[1] = -1;
        msg.text = message;
    }
    var InputBox = myInputGroup.add ("edittext", undefined, defaultinput);
    InputBox.characters = 30;
    InputBox.active = true;
    var myButtonGroup = win.add ("group");
    myButtonGroup.orientation = "column";
    var btnOk = myButtonGroup.add ("button", undefined, "OK", {name: "ok"});
    var btnCancel = myButtonGroup.add ("button", undefined, "Cancel", {name: "cancel"});
    btnOk.onClick = function ()
    {
        win.close();
    }
    btnCancel.onClick = function ()
    {
        win.close();
        usercancelled = true;
    }
    set_font (win, "Tahoma:14");
    win.show ();
    if (usercancelled ===true)
    {
        return null;
    }
    else
    {
        return InputBox.text
    }
} // end CustomPrompt

function set_font (control, font)
{
    for (var i = 0; i < control.children.length; i++)
    {
        if ("GroupPanel".indexOf (control.children[i].constructor.name) > -1)
        {
            set_font (control.children[i], font);
        }
        else
        {
        control.children[i].graphics.font = font;
        }
    }
}//--end set_font

Minor edits above. I had "if message.length < 15" instead of "if message.length < 150". Also I changed w_width to 275. Script was truncating after 6 lines of text with 250, but I don't know why and I am not sure why 275 works better.

1 reply

Community Expert
April 7, 2025

Try using Idle Task instead of sleep 

 

function AutocloseAlert(message, delaySeconds, title) {
    title = title || 'Alert';
    delaySeconds = delaySeconds || 5;

    var alertWindow = new Window('palette', title);
    alertWindow.orientation = 'column';
    alertWindow.alignChildren = 'fill';

    var textBox = alertWindow.add('statictext', undefined, message, { multiline: true });
    textBox.characters = 50;

    var button = alertWindow.add('button', undefined, 'OK (' + delaySeconds + ')');

    var countdown = delaySeconds;
    var idleTask;

    // Button closes early
    button.onClick = function () {
        if (idleTask && idleTask.isValid) {
            idleTask.remove();
        }
        alertWindow.close();
    };

    // This function will be called repeatedly by IdleTask
    function onIdleEvent() {
        countdown--;

        if (!button || !button.text) return;

        if (countdown <= 0) {
            if (idleTask && idleTask.isValid) {
                idleTask.remove();
            }
            alertWindow.close();
        } else {
            button.text = 'OK (' + countdown + ')';
        }
    }

    // Create IdleTask
    idleTask = app.idleTasks.add({ name: "AutocloseAlertTimer", sleep: 1000 });

    // Attach event listener
    idleTask.addEventListener(IdleEvent.ON_IDLE, function () {
        onIdleEvent();
    });

    alertWindow.show();
}
Inspiring
April 7, 2025

@Eugene Tyson - I didn't understand your code, although it looks really good, but I am getting an error "Undefined is not an object" on the line idleTask = app.itleTasks.add({name:"AutocloseAlertTimer", sleep: 1000})

I should have added - my script is being run in FrameMaker, but I was told the InDesign forum had better ScriptUI support.

Inspiring
April 14, 2025

Back again - minor changes to the script. I made delaySeconds default to 0. This way you can use the script exactly the same as the alert() function, except the text will use the custom font. And a added a parameter for the bgcolor, but if you don't pass it a color, it uses gray:

function AutocloseAlert(message, delaySeconds, title, bgcolor) {
    title = title || "Script Alert";
    delaySeconds = delaySeconds || 0
    bgcolor = bgcolor || "gray";

     if(delaySeconds===0){
            var win = new Window("dialog", title, undefined, {closeButton:false});
    }
    else{
        var win = new Window("palette", title, undefined, {closeButton:false});
    }
    win.orientation = "column";
    win.alignChildren = "center";
    if(bgcolor==="white"){
        win.graphics.backgroundColor = win.graphics.newBrush (win.graphics.BrushType.SOLID_COLOR, [1.0, 1.0, 1.0]);
    }
    var msg = win.add("statictext", undefined, message);
    if (delaySeconds>0){
        var btn = win.add("button", undefined, "OK (" + delaySeconds + ")");
    }
    else{
        var btn = win.add("button", undefined, "OK",{name: "ok"});      
    }

    var cancelled = false;

    // Function for button click
    btn.onClick = function () {
        cancelled = true;
        win.close();
        $.writeln("User clicked OK");
    };

    // Show the window
    set_font (win, "Tahoma:14");
    win.show();

    if (delaySeconds>0){
        // Countdown loop
        while (delaySeconds > 0 && !cancelled) {
            $.sleep(1000); // wait 1 second
            delaySeconds--;
            try {
                if(delaySeconds > 0){    
                    btn.text = "OK (" + delaySeconds + ")";
                }
                else {
                    btn.text = "OK";   
                }
                //win.update();  // Force the window to update
            } catch (e) {
                break; // window might be closed already
            }
        }

        // Close window after countdown, unless user clicked OK
        if (!cancelled) {
            win.close();
            $.writeln("Auto-closed after timeout");
        }
    }
} // End AutocloseAlert

@Eugene Tyson New issue: I wanted something like the Enhanced Message Box for Access (but not quite as fancy (without custom button colors, delay before buttons are enabled, etc.): https://datenbank-projekt.de/projekte/improved-enhanced-message-box-ms-access

Script UI has the Confirm() function (which uses Yes/No, even though Javascript Confirm uses OK/Cancel), but this doesn't work if, for example, you wanted Abort/Retry/Ignore. I came up with the following with 4 possible buttons:

function CustomConfirm(message, title, DefaultBtn, Btn1Text, Btn2Text, Btn3Text, Btn4Text, bgcolor) {
    // Yes/No, OKOnly, OK/Cancel, Abort/Retry/Ignore, Yes/No/Cancel, Retry/Ignore, 
    title = title || "Script Alert";
    Btn1Text = Btn1Text || "OK";
    Btn2Text = Btn2Text || "";
    Btn3Text = Btn3Text || "";
    Btn4Text = Btn4Text || "";
    DefaulBtn = DefaultBtn || 0;
    bgcolor = bgcolor || "gray";
    var result = "";
    var win = new Window("dialog", title, undefined, {closeButton:false});
    win.orientation = "column";
    win.alignChildren = "left";
    if(bgcolor==="white"){
        win.graphics.backgroundColor = win.graphics.newBrush (win.graphics.BrushType.SOLID_COLOR, [1.0, 1.0, 1.0]);
    }
    var msg = win.add("statictext", undefined, message);
    var myButtonGroup = win.add ("group");
    myButtonGroup.alignment = "center";
    var Btn1 = myButtonGroup.add ("button", undefined, Btn1Text);
    if(Btn2Text!==""){
        var Btn2 = myButtonGroup.add ("button", undefined, Btn2Text);
    }
    if(Btn3Text!==""){
        var Btn3 = myButtonGroup.add ("button", undefined, Btn3Text);
    }
    if(Btn4Text!==""){
        var Btn4 = myButtonGroup.add ("button", undefined, Btn4Text);
    }
    switch(DefaultBtn){
        case 1:
            win.defaultElement=Btn1;
            break;
        case 2:
            win.defaultElement=Btn2;
            break;            
        case 3:
            win.defaultElement=Btn3;
            break;            
        case 4:
            win.defaultElement=Btn4;
            break;            
    }
// Event Listeners
    Btn1.onClick=function(){
//        alert(Btn1Text);
        result = Btn1Text;
//alert(result);
//        return result;
        win.close();
         $.writeln("User clicked Abort");
         $.writeln(Btn1Text);
      return result;
    };   
    Btn2.onClick=function(){
        result = Btn2Text;
        win.close();
         $.writeln("User clicked Retry");
         $.writeln(Btn2Text);
        return result;
    };
    Btn3.onClick=function(){
        result = Btn3Text;
        win.close();
         $.writeln("User clicked Ignore");
         $.writeln(Btn3Text);
        return result;
    };
    Btn4.onClick=function(){
        result = Btn4Text;
        win.close();
         $.writeln("User clicked Retry");
         $.writeln(Btn4Text);
        return result;
    };
    // Show the window
    set_font (win, "Tahoma:14");
    win.show();

} // End  CustomConfirm

// Usage
switch(CustomConfirm("Select A Button", undefined, 2, "Abort", "Retry", "Ignore")){
    Case "Abort"
        alert("You selected Abort")
        break;
     Case "Retry"
        alert("You selected Retry")
        break;
      Case "Ignore"
        alert("You selected Ignore")
        break;
    }

The script sets up the window properly and looks fine, but it returns "undefined" instead of the button text, and I don't know why - which makes it pretty much useless. The commented out alert buttons work and display the correct results, so I'm not sure why the return statement is not working.

 

(I'm also open to suggestions on the position of the title statement. You can't specify a title for the confirm statement. The way I have it, you have to specify "undefined" for the second position. If I move it to the end, you could ignore it, but then for a simple Yes/No pop-up with a custom button, you would have to specify "CustomConfirm("Select a button", 2, "Yes", "No", undefined, undefined, "Button Selection:")"


New problems:

  • I'm getting an error on the Btn4.OnClick line if I don't specify a value for Btn4Text. Previously, it was working. I tried surrounding the code with if(Btn4.ObjectValid()){ ...}, but then I get the same error "Undefined is not an object" on that line.
  • I thought I figured out the script returning undefined I was returning the button text as the result of the .onCLick function and not the result of the CustomConfirm function, but I tried passing the Btn1.onclick result to the main function return and it didn't work, and I don't know how to pass it the "selected" button.