Skip to content

Instantly share code, notes, and snippets.

@undavide

undavide/blog.md Secret

Created March 21, 2018 13:13
Show Gist options
  • Save undavide/3e7d68000d604f6e25cd253576110d81 to your computer and use it in GitHub Desktop.
Save undavide/3e7d68000d604f6e25cd253576110d81 to your computer and use it in GitHub Desktop.

Our guest developer post this week is by Davide Barranca, a Photoshop retoucher and developer based in Italy. Davide is the author of the Adobe Photoshop HTML Panels Development and The Ultimate Guide on Native Installers and Automated Build Systems books. He blogs about panels and script development on his homepage, davidebarranca.com.

This post is aimed at beginning developers who have built a panel, or are just starting to building a panels, and would like to understand in how CEP communicates with CC host applications like Photoshop, InDesign, and so forth. (If you've never built a panel, check out our first post: "How to create your first Adobe panel in 6 easy steps").

—Erin Finnegan, Community Engineer, Adobe


In this post we'll start looking at the basic mechanisms with which HTML Panels communicate with their host application: namely, how to run scripts.

Panels 102

In a nutshell, you can think of HTML panels as web applications running within the context of Adobe Creative Cloud apps. The stack of technologies that makes this possible is known as CEP (Common Extensibility Platform), and can be illustrated this way:

A panel is literally made of a browser instance [Chromium Embedded Framework](https://bitbucket.org/chromiumembedded/cef that provides the environment to run HTML/CSS/JS code. The whole thing wouldn't be very exciting per se, except that CEP makes a communication channel available between the host application (Photoshop, InDesign, Illustrator, etc.) and the panel itself. As a result, the panel can do all the wonders web applications usually do, plus it can drive the host application, and get information from the host app, via scripting.

There's no shared language, though: while panels are fluent with modern JavaScript (JS), they cannot make any direct use of ExtendScript (JSX), the scripting language Photoshop and Creative Cloud apps deal with, even if the two share a common ancestor (a language specification). The process of interpreting code must involve two separate engines: the panel's (JS), and the host app's (JSX).

So, how does this communication go on? The panel/JS side can send strings of ExtendScript code to the host app/JSX side, and then wait for the host app/JSX side to run it and return the result (if any). It's like the panel saying "Hi Photoshop, I have no idea what this 'app.activeDocument.layers.length' thing could possibly mean, can you please give it a run?". And Photoshop, obligingly replies "15. You're welcome". This is the simplest scenario; you could make it event-based, but that would be a different blog post.

Let's look at some examples now. I will use Photoshop because it's what I'm mostly familiar with, but think about the Adobe application of your choice and it will work the very same.

Communicating from the panel to the host application

Everything is based on the CSInterface class (provided by the CSInterface.js file you can find in Adobe's GitHub repository), that you must instantiate first in the panel's JS code:

https://gist.github.com/a1b6291d6dbca779ca5aa2802fdd149d

Then, you can send messages from JS to JSX (i.e. from the panel to the host application) using the evalScript() method:

https://gist.github.com/cfcb6549f7ea5ac036ce749c610cf921

This is as simple as it gets. From the panel, you're telling the Adobe host application (like Photoshop): "please Photoshop, run alert("Hello World!"), can you?". And no doubts Photoshop will obligingly do so.

More meaningful communications may include proper function calls, such as the following example – let's pretend we've developed, say, a new sharpening routine for web-targeted JPGs:

https://gist.github.com/00d24b202f2b1c8986a6083b3914bf82

Here, from the panel's JS, you're sending this intriguing 'addSharpeningForWeb()' string to Photoshop, but what is Photoshop supposed to do? An HTML panel stores its own ExtendScript code in one (or more) .jsx file(s) – the main one being defined in the CSXS/manifest.xml, within the <ScriptPath> tag.

In that hostscript.jsx file will sit the actual code for that very function:

https://gist.github.com/c2f483e1c837a32fdf818d2037549383

What if you need to pass data to these functions? Say that the web wharpening function needs to know the strength of the sharpening – a numeric parameter that the user sets in the panel itself with a slider. How do you hand this parameter, along with the function call string, to the JSX engine?

https://gist.github.com/2980718dd71ffc90e0d720ad88475b61

By means of string interpolation, you can easily insert the parameter between the two parenthesis. You can also use the new ES2015 template strings to interpolate variables instead (see the Adobe I/O blogpost on string templates for examples). Just in case, I suggest you to test your code for proper backward compatibility: each host app version (say, Photoshop CC 2018 vs. CC 2017 vs. CC 2015.5) implements a different Chromium Embedded Framework, and your code may break on older versions.

There's definitely more to learn around this topic, so keep checking on Adobe I/O for the next blog post!

Then adding a new intro: Hi! I’m Davide B. and you might remember me from such books as Adobe Photoshop HTML panels Development full course and The Ultimate Guide on Native Installers and Automated Build Systems.

This is part two of the [previous article; Panels 102, Basic Panel Communication].

Welcome to Panels 201, where we will learn how to pass JSON string objects to the host application from our panel. As with any 201 course, this assumes you have covered the prerequisite articles, including How to create your first Adobe panel in 6 easy steps).

Let’s assume your web sharpening routine from [part one of our series](link TBA) has been updated and uses several parameters: the panel has grown, and features a multitude of sliders, checkboxes and buttons. It would be awkward to list them all, like:

https://gist.github.com/d08ae8bf9fbda115cd2caeef611b7013

It's string interpolation madness. It’s much better to provide an object parameter: in order to let the JSX engine to properly digest your code, remember to transform it into a JSON string first!

https://gist.github.com/63859a289b32d436fb275255541adfba

Without a doubt, this is nicer. Please note that even if the parameter is passed from the JS as a JSON string, the JSX side receives it as an actual object.

Communicating from the host application to the panel

The Communication flow is bidirectional, so the Adobe application can tell things to the panel too. In order for the panel to listen to what e.g. Photoshop has to say, you need to include a callback in the csInterface.evalScript() function:

https://gist.github.com/012d55246e8c5b5593500270865e86d0

In the snippets above, the callback function we've added will receive "true" back from Photoshop (mind you: "true" as a string, not an actual boolean). Why might we want this? Well, it could be that the panel needs to know whether the addSharpeningForWeb() function has been doing its job successfully or not. In case of errors thrown on the JSX side, the returned value is always the "EvalScript error." String.

If you're familiar with Promises, instead of callbacks, please refer to Steve’s post about ES6 promises .

In more sophisticated panels (this is just an example), the aspect of the panel itself may be dependent on the document currently open in Photoshop: say, for instance, that it needs to somehow display Layer names, and current Foreground/Background colors in the panel GUI. You may need to extract structured data from the host application. You know how to do this via scripting, but how can you pass this data, e.g. as an object from Photoshop to the panel?

The trick is to convert the object to a JSON string first (in the JSX), and then JSON.parse() it back (in the panel's JS).

https://gist.github.com/764889d3080e168521b0985ead58d5e5

Pay attention, though, because the ExtendScript engine has no way to know about JSON: it's not natively featured in the language specifications of the year 2009, so you have to include extra code to implement JSON.stringify() and JSON.parse() otherwise an error will be thrown when you use them in the JSX.

Usually, developers borrow code from Douglas Crockford json2.js, directly injecting it in their JSX; for better compatibility with the ExtendScript language itself, you can also use the code in json.jsx, written by the InDesign developer Marc Autret instead – please note that instead of stringify() and parse() he's implemented functions named lave() and eval().

Wrap up

In this post you've seen how two different and fundamental parts of CEP panels (the panel's JS engine, and the host application JSX engine) can communicate. On one side, the panel's JS can send ExtendScript strings to the host application by means of the evalScript() function. On the other side, the host application can send strings back to the panel that the evalScript() second parameter callback is able to receive.

If you need to pass complex parameters such as Objects from your panel's JS to the Host App's JSX, JSON.stringify() them first (no need to JSON.parse() them in the JSX – they arrive as objects). If you need to pass an Object the other way around (from the host application JSX to the panel JS), you need to JSON.stringify() it in the JSX first, and JSON.parse() it in the JS. There's no native support of JSON in ExtendScript, so you need either json2.js or json.jsx.

I'm Davide Barranca, a Photoshop retoucher and developer based in Italy. I'm the author of the Adobe Photoshop HTML panels Development full course, and The Ultimate Guide on Native Installers and Automated Build Systems book. I blog about panels/Scripts development on my homepage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment