9 Replies Latest reply on Aug 1, 2016 12:34 PM by erica59044543

    Sticky Dialog Documentation?

    erica59044543 Level 1

      I’ve written a script using InDesign’s simple Dialog object. I now want to:

       

      1) make the dialog’s user selections (2 dropdowns) sticky on a per document basis,

      2) make the script live update the document based on Document events.

       

      *Not sure if "sticky" is the correct term. I mean only that a user's dialog box selections are saved in the document, such that:

      a) when the dialog is re-opened, the prior selections are still selected in the dialog object,

      b) a live update script based on Document events could be run against the selections values saved in the "sticky" dialog.

       

      I think #2 will require adding events and event listeners to the script itself. But I don’t think those will work without sticky values from the dialog. I’m not finding in the documentation any way to make the dialog sticky.

       

      Are sticky dialogs using the simple Dialog object possible, and I am just looking for documentation in the wrong place? Or is ScriptUI required for sticky dialogs?

       

      Thank you.

        • 1. Re: Sticky Dialog Documentation?
          Loic.Aigon Adobe Community Professional

          You could use inserLabel and extractLabel methodsof the Document object to store the document based values.

          As for the events, you should consider idleEvents.

          Loic

          • 2. Re: Sticky Dialog Documentation?
            erica59044543 Level 1

            Excellent. I will read up on those. Thank you.

            • 3. Re: Sticky Dialog Documentation?
              erica59044543 Level 1

              Loic.Aigon,

               

              I got the script working with insertLabel and extractLabel. Thanks for pointing me in the right direction on that.

               

              I then read about idleEvents for the “live update” functionality, and I’m thinking that my previous concept of “live update” may have been incorrect. The script just needs to fire whenever an open document 1) receives a new asset link (afterPlace), or 2) an existing asset link is updated (afterUpdate). Perhaps this still requires idleEvents, as you suggested, but given my newness to InDesign scripting, that seems to me like two discrete events that should not require an idle listener that runs at intervals. Am I wrong about that? Is the object model not built out fully enough to support triggering a script based on a combination of afterPlace and afterUpdate?

               

              I’ve tried it both a) with idleEvents, and b) with afterPlace and afterUpdate.

               

              a) Using idleEvents, the script gets stuck in the listener and I don’t know how to stop it—it just keeps running at intervals—which is not the functionality I require. (I can post that code if you like.)

               

              b) Using afterPlace & afterUpdate seems to be working as required (code posted below), but I have some questions:

               

              1) While developing this code, every time I want to run an iteration of updated code, the code runs through previous iterations *first*, before running through the updated code in the code file, even *after* I’ve started a new InDesign session. I don’t understand what’s happening here. I suspect that the event listeners of previous code file iterations are somehow still active, but I don’t know how to get rid of them. I’ve read all the documentation about sessions and removing event listeners, but I don’t really understand what I should conclude from it. Should all code with event listeners have operations to remove the event listeners under some condition, and if so, do you know of any sample code I can study to understand that design pattern?

               

              2) The dev code uses a find/change text function at the end to verify that required functions will truly execute AFTER the events afterPlace and afterUpdate. With afterUpdate, it verifies. With afterPlace, it appears to the human eye to both display the placed docx file and execute the find/change function *all in one operation*, which is certainly not how the code is actually executing. However, because it’s based on an event listener, I don’t know how to step through the code to watch the actual sequence of executions. It seems that it must because the find/change function follows the afterPlace handler, but it’s unclear in ExtendScript Toolkit debugger. Do you know how I can step through this code or otherwise verify the sequence of executions?

               

              3) One of the final steps to this code will be to somehow save it at the document level, so that it only runs for relevant InDesign documents, rather than all InDesign documents. I was planning to save the code or a reference to the code in a string in a document label via the same insertLabel & extractLabel technique that you directed me to earlier, to be run conditionally, something like what’s presented here: https://forums.adobe.com/thread/1147434  Do you think this is the correct approach, or is it headed in the wrong direction? Some other technique?

               

              Thanks for your help!

              (Having trouble posting in Adobe Forum's code formatting. Hope this is readable.)

               

              ~~~~~~~code~~~~~~~

              //eventListenerTest.js

              #targetengine "session2"
              main();

              function main(){

                 var myDoc = app.activeDocument;

                 var myLinks = myDoc.links;

                 //alert("myLinks = " + myLinks.length);

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

                 //alert(myLinks[i].filePath);
                //AFTER_UPDATE
                 var afterUpdate_Link_EventListener = myLinks[i].eventListeners.add("afterUpdate", afterUpdate_Link_Handler);

                }

                 //AFTER_PLACE
                 var afterPlace_Document_EventListener = myDoc.eventListeners.add("afterPlace", afterPlace_Document_Handler);

              }

               

              function afterUpdate_Link_Handler(myEvent){

                 $.sleep(1000);

                 alert("afterUPDATE handler was fired by listener + \n"
                 + "doc visibly updated in UI? (y/n)"); //yes
                 findChange();

              }

               

              function afterPlace_Document_Handler(myEvent){

                 $.sleep(1000);

                 alert("afterPLACE handler fired by listener AFTER loaded pointer mouse click? (y/n)\n" //yes
                 + "placed file visible in UI? (y/n)"); //no. placed doc not visible in UI
                 findChange();

              }

               

              function findChange(){

                app.findTextPreferences.findWhat = "lesson";

                 //alert(app.findTextPreferences.findWhat);
                 app.changeTextPreferences.changeTo = "banana";

                 //alert(app.changeTextPreferences.changeTo);
                 app.changeText();

              }

              • 4. Re: Sticky Dialog Documentation?
                Loic.Aigon Adobe Community Professional

                Hi,

                 

                Dealing with idle events implies indeed to be careful about when to let the handler get fired or not. I don't pretend my approach is better than dedicated event listeners. I just like the generic approach of idle events. So here is a proposal:

                 

                #targetengine "checkLinks"
                main();
                function main()
                {
                  var db = {};
                
                  var myIdleTask = app.idleTasks.item("checkLinksStatus");
                
                  if ( !myIdleTask.isValid) {
                  myIdleTask = app.idleTasks.add({name:"checkLinksStatus", sleep:10});
                  myIdleTask.addEventListener(IdleEvent.ON_IDLE, onIdleEventHandler, false);
                  }
                
                
                  function onIdleEventHandler(myIdleEvent)
                  {
                  var doc = app.properties.activeDocument;
                  var docId, lks, lk, changed = false, lkId;
                  if ( !doc ) {
                  db = {};
                  return;
                  }
                
                  docId = doc.id;
                  db[docId] = db[docId] || {};
                  db[docId].links = db[docId].links || {};
                
                  lks = doc.links.everyItem().getElements();
                  var n = lks.length, c = 0;
                
                  while ( lk = lks.pop() ) {
                  lkId = lk.id;
                
                  db[docId].links[lkId] = db[docId].links[lkId] || {};
                
                  if (!db[docId].links[lkId].status) c++;
                
                  if ( db[docId].links[lkId].status != lk.status ) {
                  db[docId].links[lkId].status = lk.status;
                  !changed && changed = true;
                  }
                  else {
                  db[docId].links[lkId].status = lk.status;
                  }
                  }
                
                  if (c<n && changed ) {
                  alert ( "Something changed in your links" );
                  }
                  }
                }
                

                 

                 

                The trick here is to use "database" object that will store both documents ids and links ids. So it's easy to go and check for links status changes. Of course it's somehow less precise that afterUpdate events for example because you have to determine which link actually got changed but it's far from complicated at this stage.

                 

                Good news is that you only have one global listener compared to the dozens of listeners you would add for every single link instance. I just find this more manageable but it's my humble opinion, not a sacred truth engraved in stone

                 

                FWIW

                 

                Loic

                www.ozalto.com

                • 5. Re: Sticky Dialog Documentation?
                  erica59044543 Level 1

                  Loic.Aigon,

                   

                  Thank you very much. I spent all morning studying your script (I’m not a programmer, so I’m quite slow at this). I like the idea of having just one global listener, and it seems to have many potential applications, so I definitely want to understand this.

                   

                  Your script is based on LinkStatus. My script, however, must be triggered: 1) by after an asset link is placed (for which I’ve verified that afterPlace works… mostly), and 2) by after an asset link is updated (verified that afterUpdate) works. The *after* part is key.

                   

                  These are the only values I could locate in the OMV for LinkStatus:

                  http://www.indesignjs.de/extendscriptAPI/indesign10/index.html#LinkStatus.html

                  1. LinkStatus.LINK_EMBEDDED : The File is embedded in the document.
                  2. LinkStatus.LINK_INACCESSIBLE : The url link is inaccessible.
                  3. LinkStatus.LINK_MISSING : The linked file has been moved, renamed, or deleted.
                  4. LinkStatus.LINK_OUT_OF_DATE : A more recent version of the file exists on the disk.
                  5. LinkStatus.NORMAL : The link is a normal link.

                   

                  None of these five statuses indicate either that an asset link has been placed or that an asset link has been updated.

                   

                  These are the values I find for afterPlace & afterUpdate:

                  1. Event.AFTER_PLACE: Dispatched after a Event is placed.
                  2. Document.AFTER_PLACE: I can’t even locate this one in the OMV for InDesign 11.3, but it works. I learned about it here: https://forums.adobe.com/thread/1083069
                  3. Link.AFTER_UPDATE: Dispatched after a Link is updated.

                   

                  If I understand your code correctly, it compares stored link status value to current link status value and conditionally runs a command on the result. I’m not seeing in the documentation how I can modify that to look at the links’ before/after ‘placed’ and before/after ‘update’ values. Is that possible? Or am I totally failing to understand something here?

                  • 6. Re: Sticky Dialog Documentation?
                    Loic.Aigon Adobe Community Professional

                    Your script is based on LinkStatus. My script, however, must be triggered: 1) by after an asset link is placed (for which I’ve verified that afterPlace works… mostly), and 2) by after an asset link is updated (verified that afterUpdate) works. The *after* part is key.

                    1) by after an asset link is placed

                    !db[docId].links[lkId].status will be undefined if the link is new. That would intrinsecally means it was just placed a moment ago so it's a kind of AFTER_PLACED event.

                     

                    2) by after an asset link is updated

                    Normally the status of the link prior to being updated should be OUT_OF_DATE and so a consequent change (NORMAL) should imply an update event. However you can definitively stored modification of the file and do the same comparison as I do for status. A different modified date of the file between two loops would imply a change of the file.

                     

                    FWIW

                     

                    Loic

                    Ozalto | Productivity Oriented - Loïc Aigon

                    • 7. Re: Sticky Dialog Documentation?
                      erica59044543 Level 1

                      Loic.Aigon,

                       

                      I appreciate your time and help.

                       

                      I tested this again a few times with a fresh brain. Results:

                       

                      * new doc > manually run script == nothing. (desired result)

                      * place Word file into new doc == nothing. (undesired result)

                      * make change to linked Word file in Word > script is triggered while still in Word interface and before even returning to InDesign interface. (undesired result)

                      * close Word file > return to InD > click yellow Modified icon to update link > script runs. (desired result)

                      * Ctrl+D to place new asset > select file > ok == script runs (undesired result)… continue… loaded pointer > click to place == script does not run (undesired result).

                       

                      I also read up on the File.modified property. Given the workflow this script is designed to support, the reality is that the script should fire ONLY upon afterPlace or afterUpdate, else it can create problems for the user.

                       

                      I’m totally open to the one global idleEvent listener, I’m just having trouble figuring out how to make it fire on only those two events, if that’s even possible. In case I cannot get idleEvent to do what I need it to do, let me ask you this:

                       

                      In my July 10 post, I mentioned that I had gotten the afterPlace and afterUpdate working as desired, except that: “every time I want to run an iteration of updated code, the code runs through previous iterations *first*, before running through the updated code in the code file, even *after* I’ve started a new InDesign session.”

                       

                      Can you help me understand what causes that behavior so that I can figure out how to eliminate it? If I can do that, I *think* I’ll have a script that I can put into production, at least for the short term. Unless I’m overlooking something??

                       

                      Again, thank you.

                      • 8. Re: Sticky Dialog Documentation?
                        Loic.Aigon Adobe Community Professional

                        Hi,

                         

                        There isn't much to change to get the code to work as expected.

                         

                        #targetengine "checkLinks"  
                        main();  
                        function main()  
                        {  
                          var db = {};  
                          
                          var myIdleTask = app.idleTasks.item("checkLinksStatus");  
                          
                          if ( !myIdleTask.isValid) {  
                          myIdleTask = app.idleTasks.add({name:"checkLinksStatus", sleep:10});  
                          myIdleTask.addEventListener(IdleEvent.ON_IDLE, onIdleEventHandler, false);  
                          }  
                          
                          
                          function onIdleEventHandler(myIdleEvent)  
                          {  
                          var doc = app.properties.activeDocument;  
                          var docId, lks, lk, changed = false, lkId;  
                          if ( !doc || !doc.links.length ) {  
                          db = {};  
                          return;  
                          }
                        
                          if ( !app.visible ) return;
                        
                          docId = doc.id;  
                          db[docId] = db[docId] || {};  
                          db[docId].links = db[docId].links || {};  
                          
                          lks = doc.links.everyItem().getElements();  
                          var n = lks.length, c = 0;  
                          var frame;
                          
                          while ( lk = lks.pop() ) {  
                          lkId = lk.id;  
                          frame = (lk.parent instanceof Story)? lk.parent.textContainers[0] : lk.parent.parent;
                          
                          if (!frame.properties.geometricBounds){
                          continue;
                          }
                        
                        
                          db[docId].links[lkId] = db[docId].links[lkId] || {};  
                          
                          
                          if ( db[docId].links[lkId].status != lk.status ) {  
                          db[docId].links[lkId].status = lk.status;  
                          !changed && changed = true;  
                          }  
                          else {  
                          db[docId].links[lkId].status = lk.status;  
                          }  
                          }  
                          
                           if (changed ) {  
                          //Something to do when something changed
                           }  
                          else {
                          //potentially doing something when nothing occured
                          }
                          }  
                        } 
                        

                         

                        * new doc > manually run script == nothing. (desired result)

                        Still.

                         

                        * place Word file into new doc == nothing. (undesired result)

                        Now it works given the file is linked

                         

                        * make change to linked Word file in Word > script is triggered while still in Word interface and before even returning to InDesign interface. (undesired result)

                         

                        Now fixed. We look at Indesign application visible property. So the handler will quit if InDesign isn't visible and it will be run once you get back to InDesign.

                         

                        * close Word file > return to InD > click yellow Modified icon to update link > script runs. (desired result)

                         

                        Still.

                         

                        * Ctrl+D to place new asset > select file > ok == script runs (undesired result)… continue… loaded pointer > click to place == script does not run (undesired result).

                        Now wait that the frame is actually drawn. Bounds will be undefined until it's drawn. Weirdly enough the frame exists in the memory. You can reference it but it's a ghost object.

                         

                        HTH

                         

                        Loic

                        http://www.ozalto.com/

                        • 9. Re: Sticky Dialog Documentation?
                          erica59044543 Level 1

                          Loic.Aigon,

                           

                          Thanks for your help with this. I also sent you a PM.