11 Replies Latest reply on Apr 23, 2011 9:55 AM by Dirk Becker 

    [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN

    Marc Autret Level 4

      Still working on a huge ScriptUI project, I discovered a weird issue which appears to *only* affect 'binary compiled' scripts (JSXBIN export), not the original script!

       

      When you repeatedly use addEventListener() with the same event type, you theorically have the possibility to attach several handlers to the same component and event, which can be really useful in a complex framework:

       

       

      // Declare a 'click' manager on myWidget (at this point of the code)
      // ---
      myWidget.addEventListener('click', eventHandler1);
      
      
      // . . .
      
      
      // Add another 'click' manager (for the same widget)
      // ---
      myWidget.addEventListener('click', eventHandler2);
      

       

      When you do this, both eventHandler1 and eventHandler2 are registered, and when the user clicks on myWidget, each handler is called back.

       

      The following script shows that this perfectly works in ID CS4 and CS5:

       

      // Create a dialog UI
      // ---
      var     u,
           w = new Window('dialog'),
           p = w.add('panel'),
           g = p.add('group'),
           e1 = p.add('statictext'),
           e2 = p.add('statictext');
      
      // Set some widget properties
      // ---
      e1.characters = e2.characters = 30;
      g.minimumSize = [50,50];
      var gx = g.graphics;
      gx.backgroundColor = gx.newBrush(gx.BrushType.SOLID_COLOR, [.3, .6, .9, 1]);
      
      // g->click Listener #1
      // ---
      g.addEventListener('click', function(ev)
      {
           e1.text = 'click handler 1';
      });
      
      // g->click Listener #2
      // ---
      g.addEventListener('click', function(ev)
      {
           e2.text = 'click handler 2';
      });
      
      
      w.show();
      

       

      The result is that when you click on the group box, e1.text AND e2.text are updated.

       

      But now, if I export the above code as a JSXBIN from the ESTK, the 2nd event handler sounds to be ignored! When I test the 'compiled' script and click on the group box, only e1.text is updated. (Tested in ID CS4 and CS5, Win32.)

       

      By studying the JSXBIN code as precisely as possible, I didn't find anything wrong in the encryption. Each addEventListener() statement is properly encoded, with its own function handler, nothing is ignored. Hence I don't believe that the JSXBIN code is defective by itself, so I suppose that the ExtendScript/ScriptUI engine behaves differently when interpreting a JSXBIN... Does anyone have an explanation?

       

      @+

      Marc

        • 1. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
          John Hawkinson Level 5

          Not an explanation, but of course we know that in JSXBIN you can't use the .toSource() method on functions.

          So the implication here is that perhaps the .toSource() is somehow implicated in event handlers and there's some kind of static workaround for it?

           

          Perhaps you can get around this by eval() or doScript()-ing strings?

           

          Alternatively, you could use some horrible loader mechanism like XHTMLExportMenuItemLoader.jsx and define your event handlers in the non-binary area.

           

          Could I suggest perhaps this is Adobe's way of encouraging open source scripting?

          1 person found this helpful
          • 2. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
            Dirk Becker  Level 4

            Hmm, in my first reply (via twitter) I missed the modal dialog - so the functions should not run out of scope.

            It is still better to bind them to more than the event handling, e.g. a global name, so that the garbage collection has no chance to purge them.

            The following jsxbin works for me ...

             

            Regards,

            Dirk

             

             

            // Create a dialog UI

            // ---

            var     u,

                 w = new Window('dialog'),

                 p = w.add('panel'),

                 g = p.add('group'),

                 e1 = p.add('statictext'),

                 e2 = p.add('statictext');

             

            // Set some widget properties

            // ---

            e1.characters = e2.characters = 30;

            g.minimumSize = [50,50];

            var gx = g.graphics;

            gx.backgroundColor = gx.newBrush(gx.BrushType.SOLID_COLOR, [.3, .6, .9, 1]);

             

            // g->click Listener #1

            // ---

            g.addEventListener('click', function eh1(ev)

            {

                 e1.text = 'click handler 1';

            });

             

            // g->click Listener #2

            // ---

            g.addEventListener('click', function eh2(ev)

            {

                 e2.text = 'click handler 2';

            });

             

             

            w.show();

            1 person found this helpful
            • 3. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
              Marc Autret Level 4

              John Hawkinson wrote:

               

              Not an explanation, but of course we know that in JSXBIN you can't use the .toSource() method on functions.

              So the implication here is that perhaps the .toSource() is somehow implicated in event handlers and there's some kind of static workaround for it?

               

              Perhaps you can get around this by eval() or doScript()-ing strings?

               

              Thanks a lot, John, I'm convinced you're on the good track. Dirk Becker suggested me that this is an "engine scope" issue and Martinho da Gloria emailed me another solution —which also works— and joins Dirk's assumption.

               

              Following is the result of the various tests I did from your various suggestions:

               

               

              // #1 - THE INITIAL PROBLEM// =====================================
              
              
              // EVALUATED BINARY CODE
              /*
              var handler1 = function(ev)
              {
                   ev.target.parent.children[1].text = 'handler 1';
              };
              
              var handler2 = function(ev)
              {
                   ev.target.parent.children[2].text = 'handler 2';
              };
              
              var     u,
                   w = new Window('dialog'),
                   p = w.add('panel'),
                   g = p.add('group'),
                   e1 = p.add('statictext'),
                   e2 = p.add('statictext');
              
              e1.characters = e2.characters = 30;
              g.minimumSize = [50,50];
              var gx = g.graphics;
              gx.backgroundColor = gx.newBrush(gx.BrushType.SOLID_COLOR, [.3, .6, .9, 1]);
              
              g.addEventListener('click', handler1);
              
              g.addEventListener('click', handler2);
              
              w.show();
              */
              
              eval("@JSXBIN@ES@2.0@MyBbyBn0AKJAnASzIjIjBjOjEjMjFjShRByBNyBnA . . . . .");
              
              // RESULT
              // ---
              // handler 1 only, that's the issue!
              

               

              Now to John's approach:

               

              // #2 - JOHN'S TRICK
              // =====================================
              
              
              var handler1 = function(ev)
              {
                   ev.target.parent.children[1].text = 'handler 1';
              };
              
              var handler2 = function(ev)
              {
                   ev.target.parent.children[2].text = 'handler 2';
              };
              
              // EVALUATED BINARY CODE
              /*
              var     u,
                   w = new Window('dialog'),
                   p = w.add('panel'),
                   g = p.add('group'),
                   e1 = p.add('statictext'),
                   e2 = p.add('statictext');
              e1.characters = e2.characters = 30;
              g.minimumSize = [50,50];
              var gx = g.graphics;
              gx.backgroundColor = gx.newBrush(gx.BrushType.SOLID_COLOR, [.3, .6, .9, 1]);
              g.addEventListener('click', handler1);
              g.addEventListener('click', handler2);
              w.show();
              */
              eval("@JSXBIN@ES@2.0@MyBbyBn0AIbCn0AFJDnA . . . . .");
              
              // RESULT
              // ---
              // handler1 + handler2 OK!
              

               

              This test shows that if handler1 and handler2's bodies are removed from the binary, the script works. Note that the handlers are declared vars that refer to (anonymous) function expressions. (BTW, no need to use a non-main #targetengine.) This is not a definitive workaround, of course, because I also want to hide handlers code...

               

              Meanwhile, Martinho suggested me an interesting approach in using 'regular' function declarations:

               

              // #3 - MARTINHO'S TRICK
              // =====================================
              
              
              // EVALUATED BINARY CODE
              /*
              function handler1(ev)
              {
                   ev.target.parent.children[1].text = 'handler 1';
              };
              
              function handler2(ev)
              {
                   ev.target.parent.children[2].text = 'handler 2';
              };
              
              var     u,
                   w = new Window('dialog'),
                   p = w.add('panel'),
                   g = p.add('group'),
                   e1 = p.add('statictext'),
                   e2 = p.add('statictext');
              e1.characters = e2.characters = 30;
              g.minimumSize = [50,50];
              var gx = g.graphics;
              gx.backgroundColor = gx.newBrush(gx.BrushType.SOLID_COLOR, [.3, .6, .9, 1]);
              g.addEventListener('click', handler1);
              g.addEventListener('click', handler2);
              w.show();
              */
              
              eval("@JSXBIN@ES@2.0@MyBbyBnACMAbyBn0ABJCnA . . . . .");
              
              // RESULT
              // ---
              // handler1 + handler2 OK!
              

               

              In the above test the entire code is binary-encoded, and the script works. What's the difference? It relies on function declarations rather than function expressions. As we know, function declarations and function expressions are not treated the same way by the interpreter. I suppose that function declarations are stored at a very persistent level... But I don't really understand what happens under the hood.

               

              (Note that I also tried to use function expressions in global variables, but this gave the original result...)

               

              Thanks to John, Dirk, and Martinho for the tracks you've cleared. As my library components cannot use neither the global scope nor direct function declarations my problem is not solved, but you have revealed the root of my troubles.

               

              @+

              Marc

              • 4. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                Marc Autret Level 4

                Dirk Becker  wrote:

                 

                It is still better to bind [the functions] to more than the event handling, e.g. a global name, so that the garbage collection has no chance to purge them.

                 

                Thanks Dirk, great minds think alike! (You reach the same conclusion as Martinho.)

                 

                Actually, using global var that contain function expressions does not work:

                handler = function(){...};

                 

                while using pure function declarations works:

                function handler() {...}

                 

                What I can not clarify is the connection between this subject and event listeners —and JSXBIN!

                 

                @+

                Marc

                • 5. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                  Marc Autret Level 4

                  SOLVED!

                   

                  Thanks to you all guys, I finally found a simple and generic workaround that preserves the whole structure of my project.

                   

                  Instead of:

                  var myHandler = function() { /* . . . */};

                   

                  just append a redundant function name to the declaration:

                  var myHandler = function anyHandlerFuncName() { /* . . . */};

                   

                  Then, other parts of the code don't need to be changed:

                  myWidget.addEventListener('click', myHandler);

                  // etc.

                   

                  I just tested this in a complex 'module pattern' which has a deep object/function hierarchy —including some closures— and the JSXBIN now works like a charm:-)

                   

                  The redundant name is arbitrary and, in fact, it is never used in the code. For whatever reason, this simple addition makes multiple event handlers more 'persistent'... Does it cause performance costs or memory leaks? I don't know. But since this fix specifically regards event listeners, I don't fear it's too harmful (?)

                   

                  A possible conclusion for this topic is that we always should use named functions as ScriptUI event listeners.

                   

                  Thanks again for your generous help.

                  Marc

                  • 6. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                    John Hawkinson Level 5

                    Does this cause global namespace pollution? I guess function declarations are automatically scoped to their outer function (and hoisted to the top)?

                    • 7. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                      Marc Autret Level 4

                      John Hawkinson wrote:

                       

                      Does this cause global namespace pollution?

                       

                      It doesn't seem to:

                       

                      // Regular function declaration
                      function foo1() {}
                      
                      // Named function assignement
                      var myFunc = function foo2(){};
                      
                      alert( typeof $.global['foo1'] ); // => function
                      alert( typeof $.global['foo2'] ); // => undefined
                      

                       

                      I guess function declarations are automatically scoped to their outer function (and hoisted to the top)?

                       

                      I don't know how this actually works in ExtendScript, but I think that function foo2(){} cannot be regarded as a true function declaration in the context of:

                      var myFunc = function foo2(){};

                       

                      According to Zaytsev's "Named function expressions demystified"http://kangax.github.com/nfe/ — this is a just function expression, not a declaration.

                       

                      Well... this is a quite complicated subject!

                       

                      @+

                      Marc

                      • 8. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                        John Hawkinson Level 5

                        Yeah, it's definitely a function expression -- apparently they have optional identifiers that can be used to recursively call the function. See chapter 13 of the spec on p. 71, quoted with some elision::

                         

                        13 Function Definition
                        Syntax
                        FunctionExpression : function Identifier_opt ( FormalParameterList_opt ) { FunctionBody }

                        The production FunctionExpression : function ( FormalParameterList_opt ) { FunctionBody } is evaluated as follows:
                        1. Create a new Function object as specified in 13.2 with parameters specified by FormalParameterList_opt and body specified by FunctionBody. Pass in the scope chain of the running execution context as the Scope.
                        2. Return Result(2).

                        The production FunctionExpression : function Identifier ( FormalParameterList_opt ) { FunctionBody } is evaluated as follows:

                        1. Create a new object as if by the expression new Object().
                        2. Add Result(1) to the front of the scope chain.
                        3. Create a new Function object as specified in 13.2 with parameters specified by FormalParameterList_opt and body specified by FunctionBody. Pass in the scope chain of the running execution context as the Scope.
                        4. Create a property in the object Result(1). The property's name is Identifier, value is Result(3), and attributes are { DontDelete, ReadOnly }.
                        5. Remove Result(1) from the front of the scope chain.
                        6. Return Result(3).

                        NOTE
                        The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a
                        FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

                        As to why this matters for JSXBIN? Well... umm.

                        • 9. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                          Marc Autret Level 4

                          As to why this matters for JSXBIN? Well... umm.

                           

                          Yeah, that's our perennial problem in Adobe scripting! How is ECMA-262+ECMA-357 implemented in ExtendScript? How does ScriptUI interact with ES and the DOM? How is the W3C Event model implemented in ScriptUI? How does the ES 'engine' system interact with JS scope? How does JSXBIN interact with all that stuff? Etc. Too many recurring questions...

                           

                          ;-)

                          Marc

                          • 10. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                            John Hawkinson Level 5

                            You almost had me with:

                             

                            How does the ES 'engine' system interact with JS scope?

                            Don't we know this one? Every engine is a seperate JS interpreter, right?

                            • 11. Re: [JS CS4/CS5] ScriptUI Click Event Issue in JSXBIN
                              Dirk Becker  Level 4

                              Marc:

                              > You reach the same conclusion as Martinho ...

                               

                              I saw that a little later.

                               

                              John:

                              > Every engine is a seperate JS interpreter, right?

                               

                              At least it is a mostly isolated execution context.

                               

                              Let me explain why I initially thought it was an engine issue: when the "main" engine quits everything inside including functions is purged, so I made it a habit to always use a more permanent, dedicated session for InDesign event handlers where the objects have a chance to survive. I'm not doing so much with UI therefor I missed on the "modal" nature of the dialog that also *should* keep its session alive while the window is open. I would anyway not invoke such a dialog straight out of a top level script but provide a menu instead, and then you again have the need for a permanent session.

                               

                              I would not read so much into the difference between function declaration and function expression as the results are so interchangeable most of the time, instead for myself I will consider it just a little bug in garbage collection. The outcome is the same anyway.

                               

                              Dirk