1 Reply Latest reply on Aug 22, 2016 1:46 PM by cchimi

    Intercept click on listbox (scriptui)

    cchimi Level 2

      I have a dialog that, in part, lists the character styles in a document in a listbox that can be multi-clicked. In some cases the list is rather long and I'd like to be able to break it up into multiple columns. Peter Kahrel's ScriptUI for dummies points out that multi-column list boxes aren't really grids but sets of rows (so a click anywhere in a row selects the whole row). Not what I want. I set up multiple columns like he does in his table example. The issue I have is that each column functions independently of the others. So, for instance,

      • if I click on cell 1 of listbox 1 and then click on cell 2 of listbox 1, cell1 is deselected.
      • If I click on cell 1 of listbox 1 and then control-click on cell 2 of listbox 1, cell 1 and cell 2 are both selected.
      • If I click on cell 1 of listbox 1 and then click on cell 1 of listbox 2, both cell 1s are selected.
      • etc.

      This is kind of awkward and makes it seem as if the columns aren't linked. From what I can tell (and confirmed for older versions of CC here: http://indiscripts.com/blog/public/ScriptUIEvents.pdf ) listboxes don't register the left mouse click at all. I am working on getting better at understanding the more complex aspects of event handling, like bubbling and propogation, but there are some gaps in my knowledge (to say the least). Does anyone know if it is possible to, perhaps, access information about a click from a change event? Or some other, cleverer approach? I'd rather avoid the dreaded scrolling panel if I can.

       

      Here's a working example:

       

      main();
      
      
      function main(){
              testDlg(app.activeDocument);
      }
      
      
      function testDlg(funcDoc){
          //Creates a new modal dialog and sets some attributes.
          var myDialog = new Window("dialog", "Test Dialog", undefined);
          myDialog.location = [50, 50];
          myDialog.orientation = "column";
          myDialog.alignChildren = ["fill", "top"];
          
          var myStyGrp = myDialog.add("group");
          var myStyArr = funcDoc.characterStyles.everyItem().name;
          if (myStyArr[0] == "[None]"){myStyArr.shift();}
          var cols = myStyGrp.add("group"); 
          cols.spacing = 1;
          var i = 0, j = 20;
          var smallArr = [];
          while (j < myStyArr.length){
              smallArr = myStyArr.slice(i, j);
              cols.add("listBox", undefined, undefined, {multiselect: true, items: smallArr});
              i = j, j += 20;
          }
          var arrMod = (myStyArr.length - 1) % 20;
          if (arrMod > 0){
              smallArr = myStyArr.slice(arrMod * -1, myStyArr.length - 1);
              var smallCol = cols.add("listBox", undefined, undefined, {multiselect: true, items: smallArr});
      
      
          }
          cols.alignment = "left";
          cols.alignChildren = ["left", "top"];
      
      
          cols.addEventListener("change", function(){
              //I am using this event listener currently, but its code is irrelevant (enables/disables and OK button).
          });
      
      
      //Displays the dialog and watches for the return value. If it is 1, OK was pressed and the values are returned to the values variable
          //via an array. Otherwise, exit the script.
          if (myDialog.show() == 1){
              return true;
          }
          else {return false}
      }
      
        • 1. Re: Intercept click on listbox (scriptui)
          cchimi Level 2

          I've found a partial solution to this issue. I had to read a bit farther into the Tools Guide and work out how to use event phases and the keyboard state to mimic the click event. Interesting stuff! The one issue I'm still having, that perhaps someone has an idea about, is how to know if a listbox item has been clicked but not changed. To see what I'm describing, run the code below (I've given it some values to display so there's no need to set up a document with styles). Click on any item, then ctrl+click an item in a different listbox, then simple click that same item again. So, for instance:

          1. Click "across"

          2. Ctrl+click "considering"

          3. Click "considering"

           

          It should clear "across" from the selection, but because clicking "considering" doesn't deselect it, the "change" event never fires at all. Is there any way to grab that, or do I just need to accept slightly weird behavior in that case? TIA

           

          main(); 
          
          
          function main(){ 
                  testDlg(app.activeDocument); 
          } 
          
          
          function testDlg(funcDoc){ 
              //Creates a new modal dialog and sets some attributes. 
              var myDialog = new Window("dialog", "Test Dialog", undefined); 
              myDialog.location = [50, 50]; 
              myDialog.orientation = "column"; 
              myDialog.alignChildren = ["fill", "top"]; 
               
              var myStyGrp = myDialog.add("group"); 
              var myStyArr = ["aboard", "about", "above", "across", "after", "against", "along", "amid", "among", "anti", "around", "as", "at", "before", "behind", "below", "beneath", "beside", "besides", "between", "beyond", "but", "by", "concerning", "considering", "despite", "down", "during", "except", "excepting", "excluding", "following", "for", "from", "in", "inside", "into", "like", "minus", "near", "of", "off", "on", "onto", "opposite", "outside", "over", "past", "per", "plus", "regarding", "round", "save", "since", "than", "through", "to", "toward", "towards", "under", "underneath", "unlike", "until", "up", "upon", "versus", "via", "with", "within", "without"];
              //var myStyArr = funcDoc.characterStyles.everyItem().name; 
              //if (myStyArr[0] == "[None]"){myStyArr.shift();} 
              var cols = myStyGrp.add("group"); 
              cols.spacing = 1; 
              var i = 0, j = 20; 
              var smallArr = [];
              //Create one listbox for every chunk of 20 styles.
              while (j < myStyArr.length){ 
                  smallArr = myStyArr.slice(i, j); 
                  var smallCol = cols.add("listBox", undefined, undefined, {multiselect: true, items: smallArr}); 
                  smallCol.addEventListener("change", smallColClickHandler);
                  i = j, j += 20; 
              } 
              var arrMod = (myStyArr.length - 1) % 20;
              //If necessary, create a listbox for the overflow styles.
              if (arrMod > 0){ 
                  smallArr = myStyArr.slice(arrMod * -1, myStyArr.length - 1); 
                  var smallCol = cols.add("listBox", undefined, undefined, {multiselect: true, items: smallArr}); 
                  smallCol.addEventListener("change", smallColClickHandler); 
              } 
              cols.alignment = "left"; 
              cols.alignChildren = ["left", "top"]; 
          
          
              //This handler fires during the capture phase (see last argument
              //to addEventListener). It checks for a targeted listbox (custom
              //property) and if none are found sets the property for the
              //current target. This is necessary because the change handler for
              //the individual listboxes will get called multiple times if a
              //listbox's selection has to be nullified.
              cols.addEventListener("change", function(ev){
                  var bHasTarget = false;
                  for (var i = 0; i < this.children.length; i++){
                      var myChild = this.children[i];
                      if (myChild instanceof ListBox && !!myChild.targeted){
                          bHasTarget = true;
                          break;
                      }
                  }
                  if (!bHasTarget){ev.target.targeted = true;}
              }, true);
          
          
              //This handler fires for the individual listboxes. It checks the
              //state of the keyboard when it fires. See Keyboard state object
              //in JavaScript Tools Guide CC. This property is not linked to
              //any event and can be checked as needed. If the change was triggered
              //while Ctrl was clicked, we can let the event play out. Also, if the
              //only listbox with any selection is the one we are in, we can let
              //the event play out. The custom situation is when Ctrl is not clicked
              //and multiple listboxes have selections. In that case, we want
              //clicking an item to clear all of the other selections, as it does
              //when you click in a single listbox with multiple items selected.
              function smallColClickHandler(){
                  if (!!this.targeted){
                      var bCtrl = ScriptUI.environment.keyboardState.ctrlKey;
                      var bOnlySel = true;
                      var parGroup = this.parent;
                      for (var i = 0; i < parGroup.children.length; i++){
                          var myChild = parGroup.children[i];
                          if (myChild !== this && myChild.selection !== null){
                              bOnlySel = false;
                              break;
                          }
                      }
                      if (!!bOnlySel || !!bCtrl){}
                      else {
                          for (var i = 0; i < parGroup.children.length; i++){
                              var myChild = parGroup.children[i];
                              if (myChild !== this && myChild.selection !== null){
                                  myChild.selection = null;
                              }
                          }
                      }
                      this.targeted = false;
                  }
              }
          
          
              cols.addEventListener("change", function(){ 
                  //I am using this event listener currently,
                  //but its code is irrelevant (enables/disables and OK button).
                  //Just note that this fires during the bubbling phase.
              }); 
          
          
          //Displays the dialog and watches for the return value. If it is 1, OK was pressed and the values are returned to the values variable 
              //via an array. Otherwise, exit the script. 
              if (myDialog.show() == 1){ 
                  return true; 
              } 
              else {return false} 
          }