JS noob question: How to have function open another file and THEN continue batchPlay action steps?

Hi all, apologies as I am very new to JavaScript, this is probably a very basic question. Sorry if this is not the right place to ask this, please let me know. Any help would be appreciated.

I have an action that does a few steps on the active file, opens a mockup template PSD and then continues action steps on the template file. I have learned that the UXP will not open a file unless it is in the plugin folder, so I grabbed some code from another post here to help open the file from the plugin folder. Once the file is open, how do I have the action continue steps on the newly opened template? Right now the code is trying to perform all of these action steps on the original design file, and then opening the template afterwards.

Here is my Frankenstein attempt:

async function createComp () {
  {
  let batchPlay = require("photoshop").action.batchPlay;
  let result = await batchPlay(
  [
    //First part of action to run on original active file
        // Make layer
        {"_obj":"make","_target":[{"_ref":"layer"}],"layerID":370},
        // Flatten Image
        {"_obj":"flattenImage"},
        // Convert Mode
        {"_obj":"convertMode","to":{"_class":"RGBColorMode"}},
        // Image Size
        {"_obj":"imageSize","height":{"_unit":"pixelsUnit","_value":400.0},"interfaceIconFrameDimmed":{"_enum":"interpolationType","_value":"automaticInterpolation"},"resolution":{"_unit":"densityUnit","_value":72.0},"width":{"_unit":"pixelsUnit","_value":1400.0}},
        // Set Selection
        {"_obj":"set","_target":[{"_property":"selection","_ref":"channel"}],"to":{"_enum":"ordinal","_value":"allEnum"}},
        // Copy
        {"_obj":"copyEvent","copyHint":"pixels"},
        // Set Selection
        {"_obj":"set","_target":[{"_property":"selection","_ref":"channel"}],"to":{"_enum":"ordinal","_value":"none"}},

  ],{
     synchronousExecution: false,
     modalBehavior: "wait"
  })

//Code to open template from plugins folder  
const { app, core } = require("photoshop");
const fs = require("uxp").storage.localFileSystem;

async function openDocument() {
  let pluginFolder = await fs.getPluginFolder();
  let theTemplate = await pluginFolder.getEntry("comp-PSDs/DBB_400x1400.psd");

  await app.open(theTemplate);
}
async function main() {
  await core.executeAsModal(openDocument);
}

try {
  main();
} catch (error) {
  console.error(error);
  console.trace(error);
}

{ 
  let batchPlay = require("photoshop").action.batchPlay;
  let result = await batchPlay(
  [
  //Second part of action to run within template file
        // Select layer “1400x400_DigitalBulletin_Art”
        {"_obj":"select","_target":[{"_name":"1400x400_DigitalBulletin_Art","_ref":"layer"}],"layerID":[525],"makeVisible":false},
        // Edit Contents
        {"_obj":"placedLayerEditContents"},
        // Paste
        {"_obj":"paste","antiAlias":{"_enum":"antiAliasType","_value":"antiAliasNone"},"as":{"_class":"pixel"},"inPlace":true},
        // Hide layer “Template - Turn OFF”
        {"_obj":"hide","null":[{"_name":"Template - Turn OFF","_ref":"layer"}]},
        // Save
        {"_obj":"save"},
        // Close
],{
  synchronousExecution: false,
  modalBehavior: "wait"
});
}
};
}

document
  .getElementById("btnAutoComp")
  .addEventListener("click", () =>
    require("photoshop").core.executeAsModal(createComp));

I’ll pseudocode this as from the code provided you’re maybe not such a beginner as you think!

In a nutshell, break your code up into two async functions:
• a function to open the file. It might be good to at very least return the document ID for use with your batchPlay actions
• a function to do stuff to your newly opened file

Then have a third function that is called by your event listener that in turn calls your other two functions. By making it all async you require JS to wait until the file is open before it runs whatever you want to do.

// Function triggered by button click event
const mainProcess = async () => {
    // First open file with async function and store returned value as variable 
    const docID = await openFile();

    // Second run batchPlay actions passing in returned value
    await doStuff(docID);
}

Wow, that means I don’t have to break up the batchPlay. Thank you!

And thanks, but my skills are only to (sometimes correctly) copy and paste code I’ve found. E.g. I don’t yet know how to " return the document ID for use with [my] batchPlay actions", these sorts of basics though I will try to look up… I appreciate your response and pointing me closer to my goal.

This is a fundamental pillar of JS and will help you understand how a function can return a value (or more precisely an expression).

It also ties in to asychronous JS - an async function invariably is used to make JS wait for a function to not just execute, but also for it to return a value.

As well as opening your file, app.open() should return an Object with all the file data - e.g. the ID, the metadata, etc.
So if your open file function ended with:

return await app.open(file)

then use openFile() in place of a variable expression like so:

const openedFile = await openFile(file)

and then the value of openedFile will be the value returned from your function.

Thank you, Timothy! I feel bad for the hand-holding, I understand if you do not have time to explain any further.

Having trouble understanding how to pass in the returned value within the batchPlay… Is it something
related to the info on this page: Photoshop API, Document

// The currently active document from the Photoshop object
const currentDocument = app.activeDocument

// Choose one of the open documents from the Photoshop object
const secondDocument = app.documents[1]

Here is the current state:

async function openFile() {
const { app, core } = require("photoshop");
const fs = require("uxp").storage.localFileSystem;

async function openDocument() {
  const pluginFolder = await fs.getPluginFolder();
  const theTemplate = await pluginFolder.getEntry("comp-PSDs/DBB_400x1400.psd");

  await app.open(theTemplate);
}
async function main() {
  await core.executeAsModal(openDocument);
}

try {
  main();
} catch (error) {
  console.error(error);
  console.trace(error);
}

return await app.open(file)
}

const openedFile = await openFile(file)

async function actionSteps () {
  const batchPlay = require("photoshop").action.batchPlay;

  const result = await batchPlay(
  [
    //First part of action to run on original active file
        // Make layer
        {"_obj":"make","_target":[{"_ref":"layer"}],"layerID":370},
        // Flatten Image
        {"_obj":"flattenImage"},
        // Convert Mode
        {"_obj":"convertMode","to":{"_class":"RGBColorMode"}},
        // Image Size
        {"_obj":"imageSize","height":{"_unit":"pixelsUnit","_value":400.0},"interfaceIconFrameDimmed":{"_enum":"interpolationType","_value":"automaticInterpolation"},"resolution":{"_unit":"densityUnit","_value":72.0},"width":{"_unit":"pixelsUnit","_value":1400.0}},
        // Set Selection
        {"_obj":"set","_target":[{"_property":"selection","_ref":"channel"}],"to":{"_enum":"ordinal","_value":"allEnum"}},
        // Copy
        {"_obj":"copyEvent","copyHint":"pixels"},
        // Set Selection
        {"_obj":"set","_target":[{"_property":"selection","_ref":"channel"}],"to":{"_enum":"ordinal","_value":"none"}},

  //Second part of action to run within template file
        // Select layer “1400x400_DigitalBulletin_Art”
        {"_obj":"select","_target":[{"_name":"1400x400_DigitalBulletin_Art","_ref":"layer"}],"layerID":[525],"makeVisible":false},
        // Edit Contents
        {"_obj":"placedLayerEditContents"},
        // Paste
        {"_obj":"paste","antiAlias":{"_enum":"antiAliasType","_value":"antiAliasNone"},"as":{"_class":"pixel"},"inPlace":true},
        // Hide layer “Template - Turn OFF”
        {"_obj":"hide","null":[{"_name":"Template - Turn OFF","_ref":"layer"}]},
        // Save
        {"_obj":"save"},
],{
  synchronousExecution: false,
  modalBehavior: "wait"
});
};

const createComp = async () => {
  const docID = await openFile();
  await actionSteps(docID);
}

document
  .getElementById("btnAutoComp")
  .addEventListener("click", () =>
    require("photoshop").core.executeAsModal(createComp));

More than happy to help!

From what I can see you’ve got all the ingredients in there, it’s just that your recipe is a bit overly complicated!
I’ve done a really rough refactor for you, but I haven’t tested it and it’s 2am here so I’m not going to :stuck_out_tongue_winking_eye:
It’ll suspect it will not work as written, but the intention was more to show you a more coherant structure for your code.
Things to note:

  • executeAsModal is called within each helper function - this is personal preference for only using it when needed, but it’s completely valid to do it as you had done and encapsulate the whole process
  • Using a return from your batchPlay requires thought and may need you to run a single descriptor for the thing you need rather than as part of an array of descriptors
// Global imports
const { app, core } = require("photoshop");
const fs = require("uxp").storage.localFileSystem;
const batchPlay = require("photoshop").action.batchPlay;


// Helper functions

// Open file - function is agnostic and can receive any file
async function openFile(file) {
  return await core.executeAsModal(async () => {
    return await app.open(file)
  }
}

// Action steps
async function actionSteps (value) {

  // Use value in batchPlay descriptors as necessary...

  return await core.executeAsModal(async () => {
    return await batchPlay(
    [
      // batchPlay descriptor Objects
    ],{});
} 
};

// Handle button click - calls the other two helper functions in series
async function handleClick() {
  // Get template file
  const pluginFolder = await fs.getPluginFolder();
  const theTemplate = await pluginFolder.getEntry("comp-PSDs/DBB_400x1400.psd");
  
   // Open template with helper
   const template = await openFile(theTemplate);

  // Now you can use the returned value...
  const templateDocumentId = template.id;

  // Get result of action steps - passing in whatever value you've extracted from the previous function
  const aNewValue = await actionSteps (templateDocumentId);
}


// Event listener
document
  .getElementById("btnAutoComp")
  .addEventListener("click", handleClick)