How should I handle multiple event listeners in my HTML5 extension?

Explorer ,
Aug 24, 2015 Aug 24, 2015

Copy link to clipboard

Copied

Hi all,

I want to listen for make ("Mk  ") events and duplicate ("Dplc") events, each using a different handler. Currently both event handlers are triggered when either event is received.

The relevant javascript code is:

var make_e = new CSEvent(

  'com.adobe.PhotoshopRegisterEvent',

  'APPLICATION',

  csInterface.getApplicationID(),

  csInterface.getExtensionID()

);

make_e.data = 1298866208; // stringIDToTypeID('make')

csInterface.dispatchEvent(make_e);

csInterface.addEventListener( 'PhotoshopCallback', make_callback );

var dplc_e = new CSEvent(

  'com.adobe.PhotoshopRegisterEvent',

  'APPLICATION',

  csInterface.getApplicationID(),

  csInterface.getExtensionID()

);

dplc_e.data = 1148218467; // stringIDToTypeID('duplicate')

csInterface.dispatchEvent(dplc_e);

csInterface.addEventListener( 'PhotoshopCallback', dplc_callback );

function make_callback(e){ console.log('make'); }

function make_callback(e){ console.log('duplicate'); }

When either event is received the console logs the output of both handlers. In the below example I first created a new layer then duplicated a layer.

make c_LayerPanelSection.js:90

duplicate c_LayerPanelSection.js:94

make c_LayerPanelSection.js:90

duplicate c_LayerPanelSection.js:94

TOPICS
Actions and scripting

Views

1.5K

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
community guidelines

correct answers 1 Correct Answer

Explorer , Aug 27, 2015 Aug 27, 2015
Answering my own question again I've written this class based on a similar one from com.adobe.webpa.crema.You can use it by registering events with eventDelegate.addEventListener var PhotoshopCallback = function(e) {   var callback;   try {   if( e.data ) {   //  + before variable uses numerical representation of the variable   eventDelegate.invoke( +e.data.split(',')[0], e );   }   } catch(err) {   console.log( 'PhotoshopCallback Error : ' + err );   } } function EventDelegate() {   this._e...

Likes

Translate

Translate
Explorer ,
Aug 24, 2015 Aug 24, 2015

Copy link to clipboard

Copied

I've found a good solution in the Library extension (com.adobe.webpa.crema).

It's in the system level extensions dir (/Library/Application Support/Adobe/CEP/extensions/com.adobe.webpa.crema on OS X).

The file to look at is:

com.adobe.webpa.crema/PSPanel/js/PhotoshopDelegate/index.js

This implements a delegate system which will lookup the callback based on the event type and execute it.

Once I've figured out how to use this effectively I'll try to pose something 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
community guidelines
Explorer ,
Aug 27, 2015 Aug 27, 2015

Copy link to clipboard

Copied

Answering my own question again

I've written this class based on a similar one from com.adobe.webpa.crema.

You can use it by registering events with eventDelegate.addEventListener

var PhotoshopCallback = function(e) {

  var callback;

  try {

  if( e.data ) {

  //  + before variable uses numerical representation of the variable

  eventDelegate.invoke( +e.data.split(',')[0], e );

  }

  } catch(err) {

  console.log( 'PhotoshopCallback Error : ' + err );

  }

}

function EventDelegate() {

  this._events = [];

}

EventDelegate.prototype.invoke = function( eventType, e ) {

  var callback = this._events[eventType];

  if( callback ) {

  callback(e);

  }

}

EventDelegate.prototype.__registerPSEvent = function( typeID, clear ) {

  var eventEnding = null;

  if( clear ) {

  console.log( 'Unregistering ' + typeID );

  eventEnding = 'PhotoshopUnRegisterEvent';

  csInterface.removeEventListener("PhotoshopCallback", PhotoshopCallback);

  } else {

  console.log( 'Registering ' + typeID );

  eventEnding = 'PhotoshopRegisterEvent';

  csInterface.addEventListener("PhotoshopCallback", PhotoshopCallback);

  }

    var e = new CSEvent(

    "com.adobe." + eventEnding,

    "APPLICATION",

    csInterface.getApplicationID(),

    csInterface.getExtensionID()

    );

    // e.data = Object.keys(this._events).join(", ");

    e.data = typeID;

    csInterface.dispatchEvent(e);

}

EventDelegate.prototype.addEventListener = function( typeStrings, callback ) {

  csInterface.evalScript( "s2t('"+typeStrings+"')", function(r){

  this._events[+r.split(',')[0]] = callback;

  this.__registerPSEvent( r, true );

  this.__registerPSEvent( r );

  }.bind(this) );

}

EventDelegate.prototype.removeEventListener = function( typeStrings, callback ) {

  csInterface.evalScript( "s2t('"+typeStrings+"')", function(r){

  this._events[+r.split(',')[0]] = null;

  }.bind(this) );

}

var eventDelegate = new EventDelegate();

// module.exports = eventDelegate;

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
community guidelines
Advocate ,
Aug 28, 2015 Aug 28, 2015

Copy link to clipboard

Copied

Hi,

thanks for sharing this!

I've checked the original photoshopDelegate (the one in the Adobe's panel) and I'm not sure what the event.delegate is (line 65..75)

PhotoshopDelegate.prototype.addEvents = function(event) {

   /* if(!_.isArray(events)) {

        events = [events];

    }

    */

   csInterface.evalScript( "app.stringIDToTypeID('"+ event.name + "');",

        function(result) {

            this._eventMap[+result] = event.delegate;

            this._registerEvent();

        }.bind(this) );

};

It looks like they use addEvents passing an array of event objects in which the event.name is the typeID and the event.delegate is the callback - am I right?

---

UPDATE

Besides,

I've seen your removeEventListener, which I append below as a handy reference:

EventDelegate.prototype.removeEventListener = function( typeStrings, callback ) {

    csInterface.evalScript( "s2t('"+typeStrings+"')", function(r){

    this._events[+r.split(',')[0]] = null;

    }.bind(this) );

}

I have a couple of questions for you, if I may ask:

1. Is there a reason not to include a this.__registerPSEvent( r, true ); in there too? Basically in your code when the removeEventListener is called, the com.adobe.photoshopUnRegisterEvent is not actually dispatched - it's just removed the callback in the this._events array. So you don't remove the listener, just set the callback as null.

2. It looks like your s2t accepts / returns an array, since the param is typeStrings (plural) and you r.split(',')[0] - but you hardwire the first returned value so I guess you plan to use removeEventListener for a single event - am I correct?

---

Thank you,

Davide Barranca

---

www.davidebarranca.com

www.cs-extensions.com

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
community guidelines
Explorer ,
Aug 29, 2015 Aug 29, 2015

Copy link to clipboard

Copied

Hey,

As seen in LayerCollectionView line 58, event.delegate is the callback of the event.


photoshopDelegate.addEvents({name: "extractAssets", delegate: this.addSettingsToSelected.bind(this)});

I also found this, along with much of this project, confusing. It looks as though there was plans to be able to add more than one event listener at a time but due to the commented out section checking whether the paramo is an array or not PhotoshopDelegate.addEvents currently only accepts one event.

Much of what is confusing about this project is the names given to variables and functions. In this example, the event param is not actually an event object but an object describing the event listener; name is the event type ('make' would be a simple example) and delegate is the callback.

To answer your questions:

Is there a reason not to include a this.__registerPSEvent( r, true );

I don't unregister the photoshop event in removeEventListener because it is possible (and perhaps likely) that multiple callbacks will be triggered by one event type. For safety I just remove the callback so it won't be triggered but any others listening for the same event will be safe.

It looks like your s2t accepts / returns an array, since the param is typeStrings (plural) and you r.split(',')[0] - but you hardwire the first returned value so I guess you plan to use removeEventListener for a single event - am I correct?

For clarity, below is the s2t function in extendscript:

function s2t() {

  if( Object.prototype.toString.call( arguments[0] ) === '[object Array]' ) {

  log( 's2t apply' );

  return s2t.apply( null, arguments[0] );

  }

  var r = new Array();

  for( var i=0; i<arguments.length; i++) {

  var arg = arguments;

  r.push( stringIDToTypeID( arg ) );

  }

  return r.toString();

}

I use this function in other situations in which I want to convert multiple strings to type numbers so yes it returns a list. As you can see it can accept both a single string or an array of strings. It will always return an array though so in the addEventListener method I use the first item in the returned list. addEventListener could obviously be extended to add more than one event at once but I think for the sake of readability it's best to add each event one at a time.

This class was knocked up fairly quickly to get over this particular hurdle so by all means contribute any improvements you can see. It's working well enough for me at the moment but I expect it could be further generalised or improved. I plan to add some checks for the class of the object that triggered the event (see one of my other questions for an explanation: How can I find the target/result of a photoshop event? )

Hope this answers your questions

All the best,

Tom

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
community guidelines
Explorer ,
Aug 29, 2015 Aug 29, 2015

Copy link to clipboard

Copied

By the way, as I've re-read the s2t function I can see that it is needlessly complicated. It could very easily just iterate over arguments. If an array needs to passed to it apply could be used when the function is invoked rather than in the function itself. Same functionality but probably more succinct.

Photoshop scripting gets me in tangles.

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
community guidelines
Advocate ,
Aug 30, 2015 Aug 30, 2015

Copy link to clipboard

Copied

Thank you very much for the answer Tom!

I'm currently mixing your class with the example of the latest CEP 6 PhotoshopJSONCallback (which is going to replace PhotoshopCallback in PS-next), I'll post here as soon as I can get it done.

I don't unregister the photoshop event in removeEventListener because it is possible (and perhaps likely) that multiple callbacks will be triggered by one event type. For safety I just remove the callback so it won't be triggered but any others listening for the same event will be safe.

OK I see - it's not the approach I would take myself, but it makes sense.

Tangles? It's my middle name! 🙂

Best regards,

-Davide

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
community guidelines
Explorer ,
Aug 30, 2015 Aug 30, 2015

Copy link to clipboard

Copied

‌No worries David. i'd be interested in the approach you would take.

Loking forward to what you do with this

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
community guidelines
Advocate ,
Aug 31, 2015 Aug 31, 2015

Copy link to clipboard

Copied

As a side note, I think the approach Adobe took of using the:

this._eventMap = []

is a bit overkill. I mean, if you register the "make" event you get an array of 1298866209 values (of which, 1298866208 are undefined).

Wouldn't be better to have it as follows?

this._eventMap = {

   "1298866208" : {

        callback: function(e) {

            //..

        }

    }

}

Also, I was having a look at your code and how is it possible that a single event can have multiple callbacks, using the array scheme in the delegate?

I would say that multiple addingEventListener would simply replace the callback - I'd rather stick to a one-to-one match, or implement the eventMap as an object and store callbacks as objects themselves with a name prop.

Gee how twisted even simple things can become! 🙂

Cheers,

Davide

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
community guidelines
Explorer ,
Aug 31, 2015 Aug 31, 2015

Copy link to clipboard

Copied

I agree an object would be better.

What I meant by one event type having multiple callbacks is that an event in photoshop means different things have happened depending on the class of the object that the event was triggered by. For example, a 'make' event is triggered both when a new layer is created and also when a new document is created. We may want to register a handler for each of these situations, the handler checking that the class is correct for each situation.

You're right though - currently only one callback can be assigned to each event type. This is an error on my part. I guess a solution would be store each callback in a list and then execute each one.

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
community guidelines
Advocate ,
Aug 31, 2015 Aug 31, 2015

Copy link to clipboard

Copied

For example, a 'make' event is triggered both when a new layer is created and also when a new document is created. We may want to register a handler for each of these situations, the handler checking that the class is correct for each situation.

Right!

In Extendscript there's the Notifier's EventClass - here in Panels either you get back to JSX with desc.fromID or, possibly, using PhotoshopJSONCallback that is a bit more informative about the event.

For instance

// Here is an example event for when a new doucment was created:

{ "eventID":1298866208,

  "eventData":

    { "documentID":1566,

      "new":

      { "_obj":"document",

        "depth":8,

        "fill":

          { "_enum":"fill",

            "_value":"white"

          },

        "height":

          { "_unit":"distanceUnit",

            "_value":360

          },

        "mode":

          { "_class":"RGBColorMode"

          },

        "pixelScaleFactor":1,

        "profile":"sRGB IEC61966-2.1",

        "resolution":

          { "_unit":"densityUnit",

            "_value":300

          },

        "width":

          { "_unit":"distanceUnit",

            "_value":504

          }

        }

    }

}

Nice conversation by the way! 🙂

Are you developing a particular product if I may ask (out of personal curiosity), or just exploring PS features?

Cheers

Davide

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
community guidelines
Explorer ,
Aug 31, 2015 Aug 31, 2015

Copy link to clipboard

Copied

Could you supply the extendscript you used to get that JSON please? How do you use PhotoshopJSONCallback? I recognise that from my research into Generator and I'm hoping it will fill in some gaps in my understanding. If it uses CEP6 does that mean it's only available on CC2015?

Yeh, good to talk to you. You're tutorials and posts here have been a great help while I'm learning about extending Photoshop.

I am developing a product which I'd be happy to discuss over private message or email if you would like more information.

Cheers

Tom

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
community guidelines
Advocate ,
Aug 31, 2015 Aug 31, 2015

Copy link to clipboard

Copied

Hi Tom,

as follow is my implementation of the class using the new JSON syntax:

var csInterface   = new CSInterface();

var applicationID = csInterface.getApplicationID();

var extensionID   = csInterface.getExtensionID();

function PhotoshopCallbackUnique (csEvent) {

  try {

  var callback;

  if (typeof csEvent.data === "string") {

  // fixing the fact csEvent.data is a JSON string hidden in a string, e.g.

  // ver1,{ "eventID": 1148218467, "eventData": {"null":{"_enum":"ordinal","_ref":"document","_value":"first"}}}

  // stripping the "ver1,"

  var eventDataString = csEvent.data.replace("ver1,{", "{");

  var eventDataObj    = JSON.parse(eventDataString);

  // putting back the object

  csEvent.data = eventDataObj;

  // + needed?

  psEventDelegate.invoke(+eventDataObj.eventID, csEvent);

  console.log("PhotoshopCallbackUnique: " + JSON.stringify(eventDataObj));

  } else {

  console.log("PhotoshopCallbackUnique expecting string for csEvent.data!");

  }

  } catch(e) {

  console.log("PhotoshopCallbackUnique catch:" + e);

  }

}

function PhotoshopEventDelegate () {

  this.eventMap = {};

}

PhotoshopEventDelegate.prototype.invoke = function(eventTypeID, csEvent) {

  var callback = this.eventMap[eventTypeID].callback;

  if (callback) {

  callback(csEvent);

  }

}

PhotoshopEventDelegate.prototype.registerEvent = function(typeID, unRegister) {

  var eventEnding = null;

  if (unRegister) {

  console.log('UnRegistering ' + typeID);

  eventEnding = 'PhotoshopUnRegisterEvent';

  csInterface.removeEventListener('com.adobe.PhotoshopJSONCallback' + extensionID, PhotoshopCallbackUnique);

  } else {

  console.log('Registering ' + typeID);

  eventEnding = 'PhotoshopRegisterEvent';

  csInterface.addEventListener("com.adobe.PhotoshopJSONCallback" + extensionID, PhotoshopCallbackUnique);

  }

  var event = new CSEvent(

  "com.adobe." + eventEnding,

  "APPLICATION",

  applicationID,

  extensionID

  );

  event.data = typeID;

  csInterface.dispatchEvent(event);

};

PhotoshopEventDelegate.prototype.addEventListener = function(typeString, callback) {

  csInterface.evalScript("app.stringIDToTypeID('" + typeString + "')", function(res) {

  this.eventMap[res] = { "callback" : callback };

  this.registerEvent(res, true);  // unRegister (just in case)

  this.registerEvent(res, false); // Register

  }.bind(this));

}

PhotoshopEventDelegate.prototype.removeEventListener = function(typeString, callback) {

  csInterface.evalScript("app.stringIDToTypeID('" + typeString + "')", function(res) {

  // this.eventMap[+res] = null;

  this.eventMap[res] = null;

  this.registerEvent(res, true);  // unRegister

  }.bind(this));

}

module.exports = new PhotoshopEventDelegate();

I'll be glad to carry on the conversation privately - feel free to reach me using: name dot surname at the known mail service provided by google 🙂

Cheers,

Davide Barranca

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
community guidelines
Adobe Community Professional ,
Apr 10, 2021 Apr 10, 2021

Copy link to clipboard

Copied

Could you show some full example how to use fromID() method with only .jsx?

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
community guidelines
Adobe Community Professional ,
Apr 10, 2021 Apr 10, 2021

Copy link to clipboard

Copied

LATEST

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
community guidelines