• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
6

Spinning wheel prevents mouse input to cancel script (SUI)

Participant ,
Aug 16, 2023 Aug 16, 2023

Copy link to clipboard

Copied

Hello,

 

I've written a script that exports all documents from a selected folder as PDFs. I've added a progress bar that also has a Cancel button. However, while ID is exporting the PDFs it shows the spinning wheel the entire time, so I can't hit the cancel button to stop the script from progressing, it always processes all documents until the end.

 

Is this is common behaviour? I've tried adding $.sleep(500) and rapidly hit the cancel button to no avail.

 

Thanks,
Frank

TOPICS
Scripting

Views

369

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Aug 16, 2023 Aug 16, 2023

Copy link to clipboard

Copied

By default yes if you do export syncronously where you basically freeze the app (it's a modal process). If you use asynchronousExportFile You will have exports done in the background with the app non frozen (non modal). This will create background tasks that you can possibly monitor and "possibly" stop processes  (tbh I am not completely sure but that's where I would investigate).

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Aug 16, 2023 Aug 16, 2023

Copy link to clipboard

Copied

Thanks for your reply. I changed to the asynchronous method but the Cancel button is still not clickable.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Aug 16, 2023 Aug 16, 2023

Copy link to clipboard

Copied

What about your window type? Dialogs are modal by default, you may want ro use non modal duch as palette.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Aug 17, 2023 Aug 17, 2023

Copy link to clipboard

Copied

The window type is already palette, but good thinking.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Aug 17, 2023 Aug 17, 2023

Copy link to clipboard

Copied

Ok, so here is a possible approach:

 

 

 

#targetengine "session"

var palette = palette || getPalette();
var cancel = false;
palette.show();
var tasks = {};

function exportDoc(w){
    var doc = app.documents[0];
    if(!doc.isValid) return;
    var d = (new Date()).getTime().toString();
    var n = 15;
    while(n--){
        w.t.text = d;
        doc.textFrames[0].contents = cancel+":"+d;
        var t =  doc.asynchronousExportFile(ExportFormat.PDF_TYPE, File(Folder.desktop+"/pdfs/"+n+"_tutu_"+d+".pdf"), false);
        tasks[t.id] = t;
    }
    
}

function getPalette(){
    var u;
    var w = new Window("palette","Testing canceling");
    w.size = [200,500];
    var exportBtn = w.add("button", u, "Export");
    var cancelBtn = w.add("button",u,"Cancel");
    var resetBtn = w.add("button",u,"Reset");

    
    exportBtn.onClick = function(){
        exportDoc(w);
    }

    resetBtn.onClick = function(){
        cancel=false;
    }

    cancelBtn.onClick = function(){
       cancel=true;
       for(prop in tasks){
            tasks[prop].cancelTask();
            delete tasks[prop];
        }
    }

    w.t = w.add("statictext",u,"…");
    w.t.alignment = "fill";

    return w;
}

 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Aug 17, 2023 Aug 17, 2023

Copy link to clipboard

Copied

Nice, thanks so much for your help. I'll try it out tomorrow and let you know if that solution works for me. But it all makes sense, so I'm hopefull! Thank again.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Aug 19, 2023 Aug 19, 2023

Copy link to clipboard

Copied

Alright, so I have tested your script and it worked fine. I applied the same basic rules to my script, and the wheel kept spinning without even processing the first file… I used // @targetengine "session" in your sample but not in my script, could that be the problem? My scripts runs in main().

The changes to my script were:

- changed from exportFile to asynchronousExportFile

- saving each background task t in object tasks

- if cancel button is cliked, cancel the task and delete it from the object

 

My script runs through all documents in a previously selected folder (and sub-folder) and exports each one by one. In theory, it should work…

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Aug 19, 2023 Aug 19, 2023

Copy link to clipboard

Copied

I am having troubles understanding how you could have used a ScriptUI Window of type palette without a targetengine directive to maintain you palette open. But yes, you have to use this instrcution to keep things persistent.

 

That said, it's hard to say why your script doesn't work without seeing it. Might be a scope issue, at the moment, it's all I can think of.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Aug 19, 2023 Aug 19, 2023

Copy link to clipboard

Copied

Here is the full script if you don't mind checking it out (thanks!):

// @Target indesign
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.INTERACT_WITH_ALL;

var topFolder, targetFolder, printPreset, approvalPreset, exportOnly;
var arr = [];
var noOfDocs = 0;
var curDoc = 0;
var tasks = {};

arr.push(["Document name", "File", "Error"]);

var lstatus = {};
lstatus[1282237028] = "The file is embedded in the document.";
lstatus[1818848865] = "The url link is inaccessible.";
lstatus[1819109747] = "The linked file has been moved, renamed, or deleted.";
lstatus[1819242340] = "A more recent version of the file exists on the disk.";
lstatus[1852797549] = "The link is a normal link.";

function main() {
  
  app.scriptPreferences.userInteractionLevel = UserInteractionLevels.NEVER_INTERACT;

  if (checkbox2.value && Folder(targetFolder + "/Print").exists == false) {
    var f = new Folder(targetFolder + "/Print");
    f.create();
  }
  if (checkbox1.value && Folder(targetFolder + "/Approval").exists == false) {
    var f = new Folder(targetFolder + "/Approval");
    f.create();
  }


  //Main code
  if (topFolder != "") {

    countIndd(Folder(topFolder)); //Count docs
    progressbar1.maxvalue = noOfDocs;
    statictext2.text = curDoc + "  /  " + noOfDocs;
    palette.show();
    
    forEachDescendantFile(Folder(topFolder), doStuffIfdocument);

    //Output
    var message = "All files exported successfully";
    if (arr.length > 1) {
      message = "The following files could not be exported due to a linked file problem:";
      for (var i = 1; i < arr.length; i++) {
        message += "\n" + arr[i][0];
      }
      writeTable(arr);
    }
    alert(message);
  }
}

function countIndd(folder) {
  var myIndd = folder.getFiles();
  for (var i = 0; i < myIndd.length; i++) {
    if (myIndd[i] instanceof File && myIndd[i].fsName.indexOf(".indd") != -1 && myIndd[i].fsName.indexOf(exportOnly) != -1) {
      noOfDocs++;
    } else if (myIndd[i] instanceof Folder) {
      countIndd(myIndd[i]);
    }
  }
}

function doStuff(document) {
  //Action ========================================================================================

  var go = true;
  
  for (var i = 0; i < document.links.length; i++) {
    if (document.links[i].status == LinkStatus.LINK_OUT_OF_DATE) {
      document.links[i].update();
    }
    if (document.links[i].status != LinkStatus.NORMAL) {
      $.writeln(document.name + " not exported. " + lstatus[Number(document.links[i].status)]);
      arr.push([document.name, document.links[i].name, lstatus[Number(document.links[i].status)]]);
      go = false;
      curDoc++;
      progressbar1.value = curDoc;
      statictext2.text = curDoc + "  /  " + noOfDocs;
      break;
    }
  }
  //For Approval
  if (go && checkbox1.value) {
    curDoc++;
    progressbar1.value = curDoc;
    statictext2.text = curDoc + "  /  " + noOfDocs;

    app.pdfExportPreferences.pageRange = PageRange.ALL_PAGES;
    var t = document.asynchronousExportFile(ExportFormat.PDF_TYPE, File(targetFolder + "/Approval/" + document.name.replace(/indd$/gi,"pdf")), false, app.pdfExportPresets.itemByName(approvalPreset));
    tasks[t.id] = t;
  }
  //For Print
  if (go && checkbox2.value) {
    curDoc++;
    progressbar1.value = curDoc;
    statictext2.text = curDoc + "  /  " + noOfDocs;

    for (var i = 0; i < document.pages.length; i++) {
      app.pdfExportPreferences.pageRange = document.pages[i].name;
      var t = document.asynchronousExportFile(ExportFormat.PDF_TYPE, File(targetFolder + "/Print/" + document.name.replace(/\.indd$/gi,"_") + document.pages[i].name + ".pdf"), false, app.pdfExportPresets.itemByName(printPreset));
      tasks[t.id] = t;
    }
  }
}

function doStuffIfdocument(oFile) {
    if (matchExtension(oFile, "indd") && oFile.displayName.indexOf(exportOnly) != -1) { // ++++++++++++
      var document = app.open(oFile, false);
      doStuff(document);
      if (document.converted == true) {
        alert(document.name + " cannot be saved!");
        document.close(SaveOptions.NO);
      } else {
        document.close(SaveOptions.YES);
      }
    }
}
function forEachDescendantFile(folder, callback) {
    var aChildren = folder.getFiles();
    for (var i = 0; i < aChildren.length; i++) {
        var child = aChildren[i];
        if (child instanceof File) {
            callback(child);
        }
        else if (child instanceof Folder) {
          this.forEachDescendantFile(child, callback);
        }
        else {
            throw new Error("The object at \"" + child.fullName + "\" is a child of a folder and yet is not a file or folder.");
        }
    }
}
function matchExtension(iFile, sExtension) {
    sExtension = "." + sExtension.toLowerCase();
    var displayName = iFile.displayName.toLowerCase();
    if (displayName.length < sExtension.length) {
        return false;
    }
    return displayName.slice(-sExtension.length) === sExtension;
}


// Dialog ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

var allPresetNames = [];

for(var p = 0; p < app.pdfExportPresets.length; p++){
  allPresetNames.push(app.pdfExportPresets[p].name);
}

// PALETTE
// =======
var palette = new Window("palette"); 
    palette.text = "PDF Export Progress"; 
    palette.orientation = "column"; 
    palette.alignChildren = ["center","top"]; 
    palette.spacing = 8; 
    palette.margins = 16; 

var statictext2 = palette.add("statictext", undefined, undefined, {name: "statictext2"}); 
    statictext2.text = curDoc + "  /  " + noOfDocs;
    statictext2.preferredSize.height = 20; 

var progressbar1 = palette.add("progressbar", undefined, 0, noOfDocs);
    progressbar1.preferredSize.width = 400; 
    progressbar1.preferredSize.height = 15; 

var divider1 = palette.add("panel", undefined, undefined, {name: "divider1"}); 
    divider1.alignment = "fill"; 

var buttonp = palette.add("button", undefined, undefined, {name: "buttonp"}); 
    buttonp.text = "Cancel";
    buttonp.onClick = function() {
      go = false;
    }

// DIALOG
// ======
var dialog = new Window("dialog");
    dialog.text = "Export PDF";
    dialog.orientation = "column";
    dialog.alignChildren = ["center","top"];
    dialog.spacing = 10;
    dialog.margins = 16;

// PANEL1
// ======
var panel1 = dialog.add("panel", undefined, undefined, {name: "panel1"});
    panel1.text = "Export types and PDF Presets";
    panel1.preferredSize.width = 437;
    panel1.orientation = "column";
    panel1.alignChildren = ["left","top"];
    panel1.spacing = 6;
    panel1.margins = 16;

// GROUP1
// ======
var group1 = panel1.add("group", undefined, {name: "group1"});
    group1.preferredSize.width = 403;
    group1.orientation = "row";
    group1.alignChildren = ["left","center"];
    group1.spacing = 10;
    group1.margins = 0;

var checkbox1 = group1.add("checkbox", undefined, undefined, {name: "checkbox1"});
    checkbox1.text = "Export PDFs for approval";
    checkbox1.value = false;
    checkbox1.preferredSize.width = 175;

    checkbox1.onClick = function() {
      dropdown1.enabled = checkbox1.value;
      if (!checkbox2.value && !checkbox1.value) {
        button4.enabled = false;
      }
      else {
        button4.enabled = true;
      }
    }

var dropdown1_array = allPresetNames;
var dropdown1 = group1.add("dropdownlist", undefined, undefined, {name: "dropdown1", items: dropdown1_array});
    dropdown1.selection = 6;
    dropdown1.preferredSize.width = 216;

// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
    group2.preferredSize.width = 403;
    group2.orientation = "row";
    group2.alignChildren = ["left","center"];
    group2.spacing = 10;
    group2.margins = 0;

var checkbox2 = group2.add("checkbox", undefined, undefined, {name: "checkbox2"});
    checkbox2.text = "Export PDFs for print";
    checkbox2.value = true;
    checkbox2.preferredSize.width = 174;

    checkbox2.onClick = function() {
      dropdown2.enabled = checkbox2.value;
      if (!checkbox2.value && !checkbox1.value) {
        button4.enabled = false;
      }
      else {
        button4.enabled = true;
      }
    }

var dropdown2_array = allPresetNames;
var dropdown2 = group2.add("dropdownlist", undefined, undefined, {name: "dropdown2", items: dropdown2_array});
    dropdown2.selection = 9;
    dropdown2.preferredSize.width = 216;

// PANEL2
// ======
var panel2 = dialog.add("panel", undefined, undefined, {name: "panel2"});
    panel2.text = "Folders";
    panel2.orientation = "column";
    panel2.alignChildren = ["left","top"];
    panel2.spacing = 10;
    panel2.margins = 10;

// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
    group3.orientation = "row";
    group3.alignChildren = ["left","center"];
    group3.spacing = 10;
    group3.margins = 0;

var button1 = group3.add("button", undefined, undefined, {name: "button1"});
    button1.text = "Select folder";

button1.addEventListener("click",function (event) {
  var selectedFolder = "";
  if (app.documents.length > 0 && app.activeDocument.saved){
    selectedFolder = app.activeDocument.filePath.selectDlg();
  } else {
    selectedFolder = Folder.selectDialog();
  }
  if (selectedFolder != null) {
    edittext1.text = selectedFolder.fsName.replace(/\\/g,'/');
    edittext2.text = selectedFolder.fsName.replace(/\\/g,'/');
  }
});

var edittext1 = group3.add('edittext {properties: {name: "edittext1"}}');
    edittext1.text = "path to source folder...";
    edittext1.preferredSize.width = 300;
    edittext1.preferredSize.height = 25;

// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
    group4.orientation = "row";
    group4.alignChildren = ["left","center"];
    group4.spacing = 10;
    group4.margins = 0;

var button2 = group4.add("button", undefined, undefined, {name: "button2"});
    button2.text = "Select folder";

button2.addEventListener("click",function (event) {
  var selectedFolder = "";
  if (app.documents.length > 0 && app.activeDocument.saved){
    selectedFolder = app.activeDocument.filePath.selectDlg();
  } else {
    selectedFolder = Folder.selectDialog();
  }
  if (selectedFolder != null) {
    edittext2.text = selectedFolder.fsName.replace(/\\/g,'/');
  }
});

var edittext2 = group4.add('edittext {properties: {name: "edittext2"}}');
    edittext2.text = "path to target folder...";
    edittext2.preferredSize.width = 300;
    edittext2.preferredSize.height = 25;

// PANEL3
// ======
var panel3 = dialog.add("panel", undefined, undefined, {name: "panel3"}); 
    panel3.text = "Filter option"; 
    panel3.preferredSize.width = 446; 
    panel3.orientation = "column"; 
    panel3.alignChildren = ["left","top"]; 
    panel3.spacing = 10; 
    panel3.margins = [16,10,16,10]; 

// GROUP5
// ======
var group5 = panel3.add("group", undefined, {name: "group5"}); 
    group5.orientation = "row"; 
    group5.alignChildren = ["left","center"]; 
    group5.spacing = 10; 
    group5.margins = 0; 

var statictext1 = group5.add("statictext", undefined, undefined, {name: "statictext1"}); 
    statictext1.text = "File names must include:"; 

var edittext3 = group5.add('edittext {properties: {name: "edittext3"}}'); 
    edittext3.text = ".indd"; 
    edittext3.preferredSize.width = 246; 

// GROUP6
// ======
var group6 = dialog.add("group", undefined, {name: "group5"});
    group6.preferredSize.width = 437;
    group6.orientation = "row";
    group6.alignChildren = ["right","center"];
    group6.spacing = 10;
    group6.margins = 0;

var button3 = group6.add("button", undefined, undefined, {name: "button3"});
    button3.text = "Cancel";

    button3.onClick = function() {
      for (prop in tasks) {
        tasks[prop].cancelTask();
        delete tasks[prop];
      }
    }


var button4 = group6.add("button", undefined, undefined, {name: "button4"});
    button4.text = "Export";

    button4.onClick = function() {
      if (Folder(edittext1.text).exists && Folder(edittext2.text).exists) {
        topFolder = edittext1.text;
        targetFolder = edittext2.text;
        exportOnly = edittext3.text;
        printPreset = dropdown2.selection.toString();
        approvalPreset = dropdown1.selection.toString();
        dialog.close();
      } else {
        alert("Select valid folders first");
      }
    }

if (dialog.show () != 2) {
  main();
}

function writeTable(myArray) {

	var myTextFile = File("~/Desktop/export report.txt");
	myTextFile.encoding = "UTF-8";

	if (myTextFile.open("w")) {
		for (var i = 0; i < myArray.length; i++) {
			myLine = myArray[i].join("\t");
			myTextFile.writeln(myLine);
		}
	myTextFile.close();
	} else {
		alert("No file");
	}
}

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Aug 20, 2023 Aug 20, 2023

Copy link to clipboard

Copied

I can't really say. When I try to run your code, I get errors far before I get to exporting files. 
If I may, you could probably simplify your code greatly. For example, getting files (Folder.getFiles()) can use a filter to return indd files exclusively. And as theFiles would be an array, getting how many of them are they is quite easy and require no extra code.

You could possibly factorise some UI components but that's not the most critical part. 

Also, the first modal dialog, you may not want to run your export by the click of the button. But to close Window with a specific integer which is not 0, like close(1).

That way, you can easily check if the script was canceled or not at that first stage:

if(dialog.show()==1)…

Sorry for not helping much on the canceled exports here but i thought it was worrth mentioning.

HTH somehow.

Loic

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 21, 2023 Aug 21, 2023

Copy link to clipboard

Copied

I don't know about the spinning wheel, I don't see that on my Windows PC. Do you use a Mac?

 

For the cancel button, your code still works its way through the whole file array, it just doesn't do anything. Maybe change the cacel burron's function like this:

 

buttonp.onClick = function() {
  // Close all documents, then
  exit();
}

 

 

By the way, it looks as if there's a bug in asynchronousExportFile(). When you use it, document.close() returns an error. The error disappears when you use the synchronous exportFile(). The latter works fine, your script runs ok over here (with exportFile()).

 

On another note, like Loic I was surprised at your forEachDescendantFile() function. As I understand it, you open, export, and close each document inside a recursive function. That looks overwrought to me, and may explain why you run into difficulties that require your Monty Pythonesque error message ""The object at . . . is a child of a folder and yet is not a file or folder."

 

To find files in a folder and its subfolders and process them, this seems much simpler:

 

function findIndd (f, list) {
  var f = Folder(f).getFiles('*.*');
  for (var i = 0; i < f.length; i++) {
    if (f[i] instanceof Folder) {
      findIndd (f[i], list);
    } else if (/\.indd$/i.test (f[i].name)) {
      list.push (f[i]);
    }
  }
  return list;
}


indds = findIndd (myFolder, []);
for (i = 0; i < indds.length; i++) {
  // process file
}

 

 Just a thought. . .

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Aug 21, 2023 Aug 21, 2023

Copy link to clipboard

Copied

LATEST

Hi Peter,

Thanks for your input.

Yes, I'm using a Mac and whenever the system is more busy than usual the cursor changes into a spinning rainbow circle. While the spinning wheel is visible, the system usually doesn't recognise any button inputs. I'll try the exit() method that you suggested, but I think that cancel function won't be triggered.

As for the overly complicated find-files-in-a-folder process, yes, that was a template that I was using before I understood that I could be done much simpler (like your code snippet suggests). I've used your suggestion in many other smaller scripts, but now that you confirmed the long version is unnecessary, I should really get rid of it. Thanks!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines