Highlighted

How can I create and use a Javascript class in a PhotoShop script?

Community Beginner ,
Nov 06, 2020

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.

 

TOPICS
Actions and scripting

Views

160

Likes

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

How can I create and use a Javascript class in a PhotoShop script?

Community Beginner ,
Nov 06, 2020

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.

 

TOPICS
Actions and scripting

Views

161

Likes

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
Nov 06, 2020 0
Adobe Community Professional ,
Nov 07, 2020

Copy link to clipboard

Copied

ExtendScript uses old implementaion from about 20 years.

Likes

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
Reply
Loading...
Nov 07, 2020 1
Community Beginner ,
Nov 07, 2020

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?

Likes

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
Reply
Loading...
Nov 07, 2020 0
Adobe Community Professional ,
Nov 07, 2020

Copy link to clipboard

Copied

Read documentation to see what is possible, also refer to version from 1997 year of JavaScript.

Likes

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
Reply
Loading...
Nov 07, 2020 0
Explorer ,
Nov 08, 2020

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.

Likes

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
Reply
Loading...
Nov 08, 2020 0
Adobe Community Professional ,
Nov 09, 2020

Copy link to clipboard

Copied

I tried it in ExtendScript ToolKit and got error: "illegal use of reserved word 'int'".

Likes

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
Reply
Loading...
Nov 09, 2020 0
Explorer ,
Nov 09, 2020

Copy link to clipboard

Copied

My bad... Replace int with var of course...

Likes

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
Reply
Loading...
Nov 09, 2020 0
Adobe Community Professional ,
Nov 09, 2020

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!

Likes

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
Reply
Loading...
Nov 09, 2020 0
Explorer ,
Nov 10, 2020

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.
     */
})();

 

Likes

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
Reply
Loading...
Nov 10, 2020 0
Contributor ,
Nov 11, 2020

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;

 

William Campbell
Mars Premedia

Likes

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
Reply
Loading...
Nov 11, 2020 0
Explorer ,
Nov 11, 2020

Copy link to clipboard

Copied

Cool!

Could you also add functions to that?

Likes

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
Reply
Loading...
Nov 11, 2020 0
Adobe Community Professional ,
Nov 11, 2020

Copy link to clipboard

Copied

 

function someFunction(){alert(callee.name)}
({objctFrstItm: someFunction}).objctFrstItm()

 

Likes

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
Reply
Loading...
Nov 11, 2020 0
Contributor ,
Nov 11, 2020

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:

https://www.marspremedia.com/software/photoshop/copy-alpha-channels

Lots of other useful free scripts here.

William Campbell
Mars Premedia

Likes

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
Reply
Loading...
Nov 11, 2020 1
Community Beginner ,
Nov 11, 2020

Copy link to clipboard

Copied

Very cool!  And thanks for the link.  I have bookmarked it.

Likes

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
Reply
Loading...
Nov 11, 2020 0
Explorer ,
Nov 11, 2020

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?

 

Likes

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
Reply
Loading...
Nov 11, 2020 0
Contributor ,
Nov 11, 2020

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.

 

William Campbell
Mars Premedia

Likes

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
Reply
Loading...
Nov 11, 2020 1
Explorer ,
Nov 11, 2020

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!

Likes

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
Reply
Loading...
Nov 11, 2020 0
Contributor ,
Nov 11, 2020

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.

William Campbell
Mars Premedia

Likes

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
Reply
Loading...
Nov 11, 2020 0
Contributor ,
Nov 11, 2020

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.

William Campbell
Mars Premedia

Likes

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
Reply
Loading...
Nov 11, 2020 0