How can I create and use a Javascript class in a PhotoShop script?
Copy link to clipboard
Copied
I am working on a PhotoShop script that will add a frame around my photograph. There are quite a few parameters I want the user to enter before the frame is generated (frame width, title size, title position, etc.). I have the dialog set up the way I want it, but I realized that it would make my life quite a bit easier if it was wrapped inside a class. So, I tried to begin by creating an empty class:
class FrameDialog
{
}
And then, in my main script file, the first line is:
#include "FrameDialog.js"
When I run the main script file, I get an error saying "Invalid use of reserved word class".
Can I use classes in PhotoShop scripts? Can I include them from other files? If so, how?
Thanks very much.
Explore related tutorials & articles
Copy link to clipboard
Copied
ExtendScript uses old implementaion from about 20 years.
Copy link to clipboard
Copied
Thank you. So, is there a way to code an object in a PhotoShop script today? Some old syntax I don't know about that will accomplish the same thing?
Copy link to clipboard
Copied
Read documentation to see what is possible, also refer to version from 1997 year of JavaScript.
Copy link to clipboard
Copied
It's possible, but not in the way you might want/think...
As Kukurykus said, ExtendScript is about 20 yrs old and something called a Class isn't available, at least, the keyword Class doesn't mean much.
This is a way to make something that behaves like a class:
(function() {
this.MyClass = (function() {
int prop1 = 0;
function MyClass(param) {
prop1 = param;
};
MyClass.prototype.MyFunction = function(param1, param2) {
prop1 = param1;
return param2;
};
return MyClass;
})();
}).call(this);
This part --> (function(){/*... the 'class' content...*/}).call(this); turns an anonymous function into a "class.
And here's an example of how you would call it:
#include "./somefolders/MyClass.jsx";
var MyScript = (function() {
var HoldMyBeer_Instance = "HoldMyBeer will be an instance of MyClass... (sort of.)";
function constructor() {
HoldMyBeer_Instance = new MyClass(5);
};
constructor.prototype.MyThing = function() {
return HoldMyBeer_Instance.MyFunction(1,2);
};
return constructor;
})();
No need to explain the include part, you already got that. 🙂
You can think of The MyScript variable as the Program class in other languages.
In function constructor(){...}, constructor is an arbitrary name, not a reserved keyword.
To attach methods/properties to your class, use constructor.prototype.nameofyourthing = ...
In the example, in MyThing() will call the function MyFunction of the instance of our data class.
Copy link to clipboard
Copied
I tried it in ExtendScript ToolKit and got error: "illegal use of reserved word 'int'".
Copy link to clipboard
Copied
My bad... Replace int with var of course...
Copy link to clipboard
Copied
A little too adnvanced to me 🙂 I don't use classes, so could you show how and what for use exactly that you created, some practical example, thx!
Copy link to clipboard
Copied
Hi again. 🙂 A practical example... Alright...
But promise me you make yourself a hot beverage of choice (coffee-time!) before continuing if you're new to all this. Is important!
How to use this: the code below can be pasted in 1 single file and will work as normal when you run it. (at least, it does on my computer)
What it does:
It will give you 2 alerts. One where the stretchme class's instance IsStretch is still at it's default values, and one alert after modifying the value using SetLeft.
How it does it:
you'll see there are 2 big clumps of code, both starting with (function(){ and both ending with })();
These are both anonymous (they have no name) functions... Something similar to classes.
The final (); part is what "starts" them. At least, to my understanding. I'm by no means an excellent "programmer" so I might use somewhat confusing terminology at times. Apologies.
The top blob of code (the part containing this.StretchMe... ) is the class OP was asking about.
The bottom blob of code is the actual script/program/callitwhatyouwant that uses that top blob.
If we rephrase... the top blob The StretchMe class is used by the bottom blob Kukurykus class.
StretchMe contains data and can be used in different scripts easily.* Just write var x = new StretchMe(), include the file and it works.
The Kukurykys class is the part with a specific name, it immediately tells us something about what we're about to do. (In this case, the name is clear enough to me that when I read it, I know it's not part of my normal script files, not part of a "script that does anything", and your name is memorable enough for me to have my penny drop. 🙂
*move the whole top blob to a separate and #include it wherever you feel like using it.
I put it in one big chunk of code here just for demonstration purposes... So you can see everything easily.
Ok, hot beverage ready? here's commented code.
#target photoshop;
(function() {
this.StretchMe = (function() {
var _left = false;
var _right = false;
var _top = false;
var _bottom = false;
function constructor() {/* nothing special here */ };
constructor.prototype.GetLeft = function() {return _left;};
constructor.prototype.SetLeft = function(stretch) {_left = stretch;};
constructor.prototype.GetRight = function() {return _right;};
constructor.prototype.SetRight = function(stretch) {_right = stretch;};
constructor.prototype.GetTop = function() {return _top;};
constructor.prototype.SetTop = function(stretch) {_top = stretch;};
constructor.prototype.GetBottom = function() {return _bottom;};
constructor.prototype.SetBottom = function(stretch) {_bottom = stretch;};
constructor.prototype.NeedsStretching = function(){
return (_left && _right && _top && _bottom) ? true : false;
}
return constructor;
})();
}).call(this);
//A: This line wraps everything below in a nice little closed off /protected package
(function() {
var Kukurykus = (function() {
// These variables can be accessed from almost anywhere in the kukurykus class
var actDoc = "a reference to the active document set in the constructor.";
var IsStretch = "an instance of my StretchMe class";
function constructor() {
/* for me, this is where I fill up the variables I'm going to use everywhere
* there is usually not much in here...
*/
IsStretch = new StretchMe();
alert("constructor alert: "+IsStretch.GetLeft());
actDoc = app.activeDocument;
this.init();
}
constructor.prototype.init = function() {
/*this is called in the constructor: this.init(); */
// changing the value of isstretch
IsStretch.SetLeft(this.checkLeftForStretching());
//after gathering my data in init(), I move on to main() to do the actual changes
this.main();
};
constructor.prototype.main = function() {
// setup document
this.PrepareDocument();
/* do the stretching. (content-aware stretch, selection + transform, ... )
* in this case, give an alert that shows you that IsStretch is an object, and IsStretch.GetLeft() gives you a value.
*/
alert("main alert: " + IsStretch + ", " + IsStretch.GetLeft());
/* Why do all this?
* Well, I can now do this, and everybody will understand what it's doing.
* To me, that's the main benefit. It makes everything readable if you come back to it 2 years later.
*/
if(IsStretch.NeedsStretching()){
if(IsStretch.GetLeft()) {
//do the thing you want it to do
}
if(IsStretch.GetRight()) {
//do the thing you want it to do
}
if(IsStretch.GetBottom()) {
//do the thing you want it to do
}
if(IsStretch.GetTop()) {
//do the thing you want it to do
}
}
};
constructor.prototype.PrepareDocument = function() {
try {
//ps prefs
displayDialogs = DialogModes.NO;
app.preferences.rulerUnits = Units.PIXELS;
this.setColorPickerToDefault();
//doc settings
activeDocument.changeMode(ChangeMode.RGB);
activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
activeDocument.convertProfile('sRGB IEC61966-2.1', Intent.RELATIVECOLORIMETRIC, true, true);
activeDocument.resizeImage(activeDocument.width, activeDocument.height, 72);
} catch (e) {
$.writeln("Line: " + $.line + " in \" " + $.fileName + "\"\n--------------\n" + e + "\n");
}
};
constructor.prototype.checkLeftForStretching = function() {
var result = false;
/* Code to get a selection on the left of the document.
* Using SelectSubject, or MagicWand or whatever else */
var TheCodeSaysIts = true;
result = TheCodeSaysIts;
return result;
}
return constructor;
})();
/* This line "starts" the code inside the var Kukurykus. So all the code above here, up until A: is known to photoshop before starting. */
new Kukurykus();
/* This is the end of "A", wrapping up the whole code in one anonymous function. Ready for christmas.
* Why do this? --> You're now sure that all the variables inside here can't appear anywhere else.
* Very nice if you work with multiple people writing scripts or have another script running (event manager) that also happens to use IsStretch as a variable name ...
* --> It doesn't matter if your variables have the same name as the other script. They're protected in here.
*/
})();
Copy link to clipboard
Copied
ExtendScript is ES3, doesn't implement class keyword as does ES6 used by most browsers now. From what you describe, it doesn't seem you even need a class. If you want to store a collection of variables just make an object to store it.
person = {
name: "Joe",
age : 30,
gender: "male"
};
Then to use...
var gender = person.gender;
Copy link to clipboard
Copied
Cool!
Could you also add functions to that?
Copy link to clipboard
Copied
function someFunction(){alert(callee.name)}
({objctFrstItm: someFunction}).objctFrstItm()
Copy link to clipboard
Copied
Sure. Because in JS functions are first-class objects. Really they are just another variable.
person = {
name: "Joe",
age : 30,
gender: "male",
birthday: function() {
person.age++;
return person.age;
)
};
person.birthday();
alert(person.age);
This is just simple example. Much more can be done to get same results as classes/inheritance but that's beyond this discussion.
An actual example not quite the same, but showing how to get the kind of encapsulation I think you're looking for, is my code for a progress bar.
function progress(steps) {
var w = new Window("palette", "Progress");
var b;
var t = w.add("statictext");
t.preferredSize = [450, -1]; // 450px tall, default width
if (steps) {
b = w.add("progressbar", undefined, 0, steps);
b.preferredSize = [450, -1];
}
progress.close = function () {
w.close();
app.refresh();
};
progress.increment = function () {
b.value++;
};
progress.message = function (message) {
t.text = message;
w.update();
};
w.show();
app.refresh();
}
To use, call function "progress" and supply steps:
// Assumes 'files' is an array of file objects
progress(files.length);
for (i = 0; i < files.length; i++) {
// Assume file[i] has property 'name'
progress.message(File.decode(files[i].name));
// Do something with 'files[i]'
progress.increment();
}
progress.close();
To see this in actual use, inspect the code on this web page:
Lots of other useful free scripts here.
Copy link to clipboard
Copied
Very cool! And thanks for the link. I have bookmarked it.
Copy link to clipboard
Copied
That is really awesome. Thank you!
I'm wondering how you instantiate them if you need multiple objects...
Would you always write out the whole thing for every person?
What is your approach for that?
Copy link to clipboard
Copied
Yes objects can be made to be instantiated as classes are in other languages. However, in JS there is more than one way to do it (something I admire about JS). I will show a really simple example but there is so much more to this. Best to search other JS resources for more information. Problem is, most current stuff will be ES6 (now has class keyword implemented) but ExtendScript is older ES3. So in studying you need to find examples not using new stuff browsers now use. Anyway, below is one way to do it in older ES3. There are others ways too.
function Person (name, age, gender) {
var self = this;
this.name = name;
this.age = age;
this.gender = gender;
this.birthday = function() {
// 'self' var above was created because at this point
// 'this' referes to function 'birthday' so it no longer
// refers to what we want -- the "class" property (var) 'age'.
self.age++;
return self.age;
)
};
var joe = new Person("Joe", 30, "Male);
joe.birthday();
alert(joe.age);
var mary = new Person("Mary", "24", "Female");
// etc......
Ideally, one would use the prototype chain for the sub-functions. But in this example, that introduces other problems, and really, it's getting outside the scope of this question. There are many more details. My answer is just trying to show one idea of how to use this stuff, the simpliest way. It gets a lot more involved doing prototyope chain, inheritance, and all that.
Copy link to clipboard
Copied
Amazing. Super clear example.
I was wondering about the prototyping... But it's outside of the scope of OP's question, as you said.
Thank you for providing some context to your example. Mentioning the drawbacks as well kept me from blindly refactoring all my code. 🙂
I'll definitely check out your site. All the best!
Copy link to clipboard
Copied
I can say a little about prototype chain but in most cases this approach isn't needed. It really only becomes an issue for classes with lots of functions -- which if instantiating greatly, saying 1000s of instances, functions within the primary function (class) are repeated every instance. Wasteful. So instead the function goes in the prototype, that way it is shared by all instances of the primary function (class). But often I don't bother because the number of instances I expect are not enough to make it necessary. Just keep that in mind -- what is the expected use? Will there be 50 instances or 500,000? Also one thing about JS is that using the prototype chain prevents private variables. Any function in the prototype chain uses 'this' to reference properties which means it must be public. That's another thing I personally consider when deciding whether to prototype a function or have it be inside the function that acts as a class. I'm a fan of encapsulation using private variables whenever possble.
Here's a simple example of using the prototype chain...
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.birthday = function() {
this.age++;
return this.age;
}
When coded this way, there is only one instance of the birthday function no matter how many Person object are instantiated with the new keyword.
Copy link to clipboard
Copied
More thoughts on this. In Adobe scripts I've never used classes because I've never really needed it. Scripts are small programs, nothing like major stuff that might need classes. Besides, usually encapsulation and multiple similar objects can be achieved other ways. For example, use arrays of objects and free-standing functions to manipulate array items.
var people = [];
function addPerson(name, age, gender) {
people.push({
name: name,
age: age,
gender: gender
});
}
function birthday(person) {
person.age++;
return person.age;
}
addPerson("Joe", 30, "Male");
addPerson("Mary", 24, "Female");
// ...etc.
birthday(people[0]); // Joe has a birthday.
alert(people[0].age); // 31
Next question is, if many items in the array, how to find "Mary" ? You need a specialized indexOf function.
function peopleIndexOf(name) {
for (var i = people.length - 1; i > -1; i--) {
if (people[i].name == name) {
return i;
}
}
return i;
// will be -1 if name not found.
}
index = peopleIndexOf("Mary"); // 1
index = peopleIndexOf("Joe"); // 0
index = peopleIndexOf("Bob"); // -1 (not found)
These examples are just to show that "classes" are not the only way to solve a programming problem.

