5 Replies Latest reply on Aug 8, 2015 9:10 PM by Colin Flashman

    Arrays from listbox and file naming

    Colin Flashman Adobe Community Professional

      To ask this question, I have to give some abridged background. I'm making a UI to data merge individual records to PDF or INDD, and currently have a UI that works:

      Screen Shot 2015-08-08 at 2.25.33 pm.png

      The dropdown fields call the header field from a tab delimited TXT file in the following fashion:

       

         myDoc.dataMergeProperties.selectDataSource(csvFile,);  
         //CSV import 
         var fileName1 = new Array(); 
         csvFile.open('r'); 
         while (!csvFile.eof) 
          { 
          fileName1.push(csvFile.readln().split(/\t/)[myName1-1]); 
          } 
         csvFile.close(); 
         var fileName2 = new Array(); 
         csvFile.open('r'); 
         while (!csvFile.eof) 
          { 
          fileName2.push(csvFile.readln().split(/\t/)[myName2-1]); 
          } 
         csvFile.close(); 
      
      

       

      The filename1 and filename2 arrays are then called upon later in the filenaming portion

       

      myDoc.dataMergeProperties.exportFile((finished + fileName1 [i+start] + " "+fileName2 [i+start] +'.pdf'
      

       

      So that's great and I know that works.


      However, two fields isn't enough, and if the database only has one field, it needs an if/else. Alas i've made an improved UI:

       

      Screen Shot 2015-08-08 at 2.28.49 pm.png

      So now the UI allows one field, two, ten... even the same field over and over again for naming purposes... the UI works but I'm now having trouble with how the filename is added to the output.

       

      I can get the listbox values on the right hand column to have the same indexOf values as the column on the left by using the following code:

       

       var myNameSplit = new Array();
       for(var i = 0; i < myWin.group5.groupb.myNewList.items.length; i++) 
         {
         myFutureFiles.push(myWin.group5.groupb.myNewList.items[i].text);
         } 
       for(var i = 0; i < myFutureFiles.length; i++) 
       {
         for(var j = 0; j < myDropPresets.length; j++)
         {if (myFutureFiles[i] == myDropPresets[j])
          {
          myNameSplit.push([j]);
          }
         }
         }
      
      

       

      myFutureFiles is an array made from the contents of the right hand side listbox (once the OK button is pressed), and myNameSplit is an array made by x-referencing the left listbox's indexOf so that fields still refer to the correct positions in the database (e.g. Last Name still refers to Last Name instead of Dt BP Sort Code if it were using the array reference 1)

       

      BUT, this is where I come unstuck. If I replace the myNameSplit.push([j]); to push a csv split of the filename e.g.

       csvFile.open('r'); 
       while (!csvFile.eof) 
       { 
       myNameSplit.push(csvFile.readln().split(/\t/)[j]); 
       } 
       csvFile.close(); 
      
      

       

      I know that I'll get an array that is three times longer than it should be (as there are 3 fields in the example), and that the filenames won't be correct.

       

      Unlike the last UI that had two known fields for naming output, this UI allows for an unknown amount of fields for naming output. This is where I can't get any further, and need some direction. I honestly don't know what loop to use, or a multidirectional array, I have no clue.

       

      For once, I've been trying NOT to bother the forum and to work it all out myself. For a solo effort so far to produce this interface has been a monumental task for me.

       

      Many thanks, Colin.

        • 1. Re: Arrays from listbox and file naming
          Colin Flashman Adobe Community Professional

          I've solved the first problem myself (how to get the one massive array into smaller but variable arrays):

           

           csvFile.open('r'); 
           while (!csvFile.eof) 
           { 
           myNameSplit.push(csvFile.readln().split(/\t/)[j]); //pushes the results of the fields being used into an array "myNameSplit"
           }
           csvFile.close(); 
           }
           }
           }
           var len = myNameSplit.length,out = [], k = 0; n = myFFlength;
           while (k < len) {
           var size = Math.ceil((len - k) / n--);
           out.push(myNameSplit.slice(k, k += size));}
          
          

           

          (answer was from georg on this forum: http://stackoverflow.com/questions/8188548/splitting-a-js-array-into-n-arrays)

           

          However, the file-naming is still an issue. If I put the string out[i] into the line that returns the filename, I get this (using the opening example):

           

          Sort Order,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28.pdf

           

          I think I know what is going on here, but any advice would still be appreciated.

           

          Colin

          • 2. Re: Arrays from listbox and file naming
            Marc Autret Level 4

            Hi Colin,

             

            I'm trying to understand your problem…

             

            So you have some data stream encoded in a CSV (in fact, a Tab-Separated-Values file) and I suppose the field names (headers) are reflected at the first line of that file, right?

             

            Dealing with field orders in the right listbox (in your UI) sounds to me unnecessarily complicated. Take advantage of the associative array mechanism:

             

            1. Store the detected headers in a simple array of strings.

            2. Store your data in an array of objects—each item being a field-to-value set {field1:value, field2:value, etc.}

            3. Create your UI.

            4. Retrieve the custom field names from the right listbox.

            5. Loop in the data array and just get the field-driven values.

             

            Here is basic demo (raw code!)

             

            // Let's suppose lines in the CSV are as follows:
            var CSV = [
                "Title\tName\tCity",
                // ------------------
                "MyTitle1\tBob\tLos Angeles",
                "MyTitle2\tJohn\tDublin",
                "MyTitle3\tEva\tParis"
                ];
            
            
            // Extract all headers (field captions) from CSV.
            // => headers :: ['Title','Name','City']
            // ---
            var headers = CSV[0].split(/\t/),
                nh = headers.length;
            
            
            // Extract data from CSV (skip the 1st line.)
            // => data :: [ {Title:'MyTitle1', Name:'Bob', City:'Los Angeles'}, etc. ]
            var data = [],
                n = CSV.length,
                i, t, a, j;
            for( i=1 ; i < n ; ++i )
                {
                t = (data[i-1]={});       // new empty item.
                a = CSV[i].split(/\t/);   // array of values.
                a.length = nh;            // make sure `a` has `nh` placeholders.
                for( j=-1 ; ++j < nh ; t[headers[j]]=a[j] );
                a.length = 0;             // cleanup.
                }
            
            
            // UI (very simplified!)
            // ---
            var u,
                w = new Window('dialog',"Test Field Selection"),
                p = w.add('panel',u),
                lHeaders = p.add('listbox',u,headers),
                bAdd = p.add('button',u,'+'),
                lFields = p.add('listbox',u,[]),
                bOK = w.add('button',u,'OK');
            
            var addFieldHandler = function EH_ADD_FIELD()
            {
                var t = lHeaders.selection;
                if( !t ) return;
                lFields.add('item',t.text);
            };
            
            p.orientation = 'row';
            lHeaders.preferredSize = lFields.preferredSize = [100,160];
            bAdd.onClick = addFieldHandler;
            
            w.show();
            
            
            // Retrieve the custom fields.
            // (Note that dups are allowed.)
            // ---
            var fields = function(r,items,i)
                {
                i = items.length;
                while( i-- ) r[i] = items[i].text;
                return r;
                }([],lFields.items);
            
            
            // Format the final data.
            // ---
            const SEPARATOR = " - ";
            
            var results = [],
                nf = fields.length;
            
            for( i=0, n=data.length ; nf && i < n ; ++i )
                {
                t = data[i];
                for( j=-1,a=[] ; ++j < nf ; a[j]=t[fields[j]] );
                results[i] = a.join(SEPARATOR);
                }
            
            alert( results.length ? results.join('\r') : 'No field selected.' );
            

             

            Hope that helps.

             

            @+

            Marc

            • 3. Re: Arrays from listbox and file naming
              Colin Flashman Adobe Community Professional

              Hello there Marc.

               

              Yes, my code is rather verbose and certainly isn't very neat. The script works in this way:

              1. General error correction (file has to be open and recently saved, file has to have items on a page, DM window must be open with preview unchecked, etc)
              2. A file open dialog box prompts the user for their txt file, and limited error correction tries to enforce that a txt file is used as opposed to a csv
              3. The UI appears that populates the left hand side listbox by calling the first line of the txt file with - csvFile.readln().split(/\t/); to get the header rows
              4. There are buttons that add the items to the right hand side listbox. I've kept the ability for the same field to appear twice, and to change the order, because no doubt users would ask for it eventually.
              5. Once OK is clicked, the right hand side listbox items are made into an array that cross-references the indexOf values of the listbox on the left.

              And this is where the issues begin so far as naming the output files.

               

              Trying to implement your code, I keep falling down at the first hurdle. If I substitute your code for calling in the CSV data from what I had

               

              var csvFile = File.openDialog('select TXT file', 'Text Files:*.txt;*.TXT');
              csvFile.open('r');  
              var myDropPresets = csvFile.readln().split(/\t/); // determines how many headers are in the header row
              
              

               

              to

               

              var csvFile = File.openDialog('select TXT file', 'Text Files:*.txt;*.TXT');
              var headers = csvFile[0].split(/\t/),  nh = headers.length;  
              
              

              I get the following:

               

              Screen Shot 2015-08-09 at 9.29.32 am.png

              This still happens if I put the csvFile.open('r'); instruction above the var headers line, and if I declare the nh headers as a variable on the next line.

              Also happens if I wrap the first variables in a while (!csvFile.eof) instruction.

              Happy to PM the code BTW.

               

              Colin

              • 4. Re: Arrays from listbox and file naming
                Marc Autret Level 4

                Hi Colin,

                 

                The code I've posted is just a quick and basic illustration, it needs of course to be adjusted to your program. In particular the variable CSV in my example is assumed to reflect the lines of your csv file.

                In your test csvFile[0] doesn't make sense. What you want is something like this:

                 

                var csvFile = File.openDialog('select TXT file', 'Text Files:*.txt;*.TXT');
                
                var CSV = csvFile && csvFile.open('r') && csvFile.read().split(/[\r\n]+/); // get the lines
                if( CSV ) csvFile.close(); else throw "No input file";
                
                
                // then you can test my code:
                
                var headers = CSV[0].split(/\t/),
                    nh = headers.length;
                // etc.
                
                

                 

                I insist, my code only shows a generic approach based on an associative data structure in order to deal with undetermined field names. I do not pretend it is a complete script.

                 

                @+

                Marc

                • 5. Re: Arrays from listbox and file naming
                  Colin Flashman Adobe Community Professional

                  Hello again Marc.

                   

                  That's a lot better! I was getting all kinds of "object is not a function" errors until adding lines 3 & 4 of your recent code.

                   

                  Your script has done the trick, BUT I have made a modification to it:

                   

                  var data = [],  
                  n = CSV.length,  
                  i, t, a, j;  
                  for( i=0 ; i < n ; ++i )  
                  {  
                  t = (data[i]={});       // new empty item.  
                  a = CSV[i].split(/\t/);   // array of values.  
                  a.length = nh;            // make sure `a` has `nh` placeholders.  
                  for( j=-1 ; ++j < nh ; t[headers[j]]=a[j] );  
                  a.length = 0;             // cleanup.  
                  }             
                  
                  

                   

                  the for loop now has i=0 rather than 1; and t=(data[i]... rather than data[i-1] as I DO NEED the array to start at zero. The reason has to do with the UI - there are text boxes that allow the user to input a start and end record. These values automatically appear when the UI opens - the start record edittext value is 1, and the end value is taken from the csv length. The loop that merges and names everything at the end has the following variables being called:

                   

                  results[i+start]

                   

                  where start is the edittext field and contains a 1 by default; or where the user can enter a different start number. The loop that calls everything in starts at 0.

                   

                  Confused? It looks like this:

                   

                  //Loop through the records  
                  for(var  i = 0; i < myTotal; i++)  
                   {  
                   with(myDoc.dataMergeProperties.dataMergePreferences)  
                   {recordSelection = RecordSelection.ONE_RECORD;  
                    recordNumber = i+start;}  
                  if (win.gr2.PDF.value == true) 
                  {
                  myDoc.dataMergeProperties.exportFile((finished + results[i+start]+'.pdf'), myPreset, );
                  }
                  
                  

                   

                  But as I said, it is now working!

                   

                  Unfortunately, I'm just starting to learn about the term "scope creep" and am considering some more bells and whistles... groan!

                   

                  Colin