30 Replies Latest reply on Jan 11, 2017 3:56 PM by Marc Autret

    How to get reference to the first body row on page in a long table?

    Kasyan Servetsky Level 5

      Dear forum,

       

      I have a long table which is threaded among several text frames (pages). I want to get the reference to the first body row in each frame so that to check if it contains a product name. (I can do this by checking if all the cells in the row have been merged.)

      04-01-2017 21-36-26.png

      Since a table looks like a single character for script, I don’t see a straightforward way to achieve my goal.

      So far, I solved it in a sloppy way: I check the baseline of the 1st insertion point of the 1st cell in the row.

      else if (RoundString(row.cells[0].insertionPoints[0].baseline, 1) == firstRowBaseline && mainRow != null) {
      

       

      Is there a more elegant solution?

      Thank you in advance!

       

      Regards,
      Kasyan

        • 2. Re: How to get reference to the first body row on page in a long table?
          Kasyan Servetsky Level 5

          Hi Trevor,

           

          Thank you very much for pointing me in the right direction!

          I used the approach you used in your second script.

          Here's the code in case someone is interested:

          main();
          
          function main() {
              var doc = app.activeDocument,
              table = doc.stories[1].tables[0];
              tableTest(table);
          }
          
          function tableTest(table) {
              var textFrame, previousTextFrame, currentTextFrame,
              rows = table.rows;
              
              previousTextFrame = rows[0].cells[0].insertionPoints[0].parentTextFrames[0];
          
              for (var i = 0; i < rows.length; i++) {
                  row = rows[i];
                  if (row.rowType != RowTypes.BODY_ROW) continue; // skip headers & footers
                  
                  currentTextFrame = row.cells[0].insertionPoints[0].parentTextFrames[0];
                  // the first body row in the first frame, or text frame has changed
                  if ((i - table.headerRowCount == 0 && currentTextFrame == previousTextFrame) || currentTextFrame != previousTextFrame) {
                      row.fillColor = "Yellow"; 
                  }
                  else {
                      row.fillColor = "Magenta"; 
                  }
              
                  previousTextFrame = currentTextFrame;
              }
          }
          

           

          Regards,
          Kasyan

          • 3. Re: How to get reference to the first body row on page in a long table?
            Laubender Adobe Community Professional & MVP

            Hi Kasyan,

            also prepare for the case where a row has no cells at all.

             

            So test for:

            row.cells.length
            

             

            Could be that the value of length is: 0

             

            How could that happen?

             

            1. Copy a range R of rows from table A

            2. Paste the range to a new text frame => that will become table B

            3. Add a column at the right edge of table B

            4. Merge the cells in table B columnwise, but not the new added column

            5. Select and copy the merged cells of table B only

            6. Select the first cell in R of table A

            7. Paste the copied cells

             

            Now table A has rows where the length of cells is zero.

             

            Here a screenshot from a sample where I set the contents of each cell to its name:

             

            2-TableWith-7-Rows-Showing-10-Rows.png

            The Table panel is showing 10 rows and is obviously following: table.rows.length

            The Story Editor panel is showing 7 rows. That's the ones with cells that can be edited.

             

            If you run a snippet like that on the table where I loop through all the rows of the table:

             

            // Select textFrame and run this script:
            var rows = app.selection[0].tables[0].rows.everyItem().getElements();
            var rowsLength = rows.length;
            for(var n=0;n<rowsLength;n++)
            {
                $.writeln("row"+"\t"+n+"\t"+"cells.length:"+"\t"+rows[n].cells.length);
            };
            

             

            The result of cells.length is that:

             

            /*

            row    0    cells.length:    6

            row    1    cells.length:    6

            row    2    cells.length:    0

            row    3    cells.length:    0

            row    4    cells.length:    0

            row    5    cells.length:    6

            row    6    cells.length:    6

            row    7    cells.length:    6

            row    8    cells.length:    6

            row    9    cells.length:    6

               

            */

             

            Regards,
            Uwe

            • 4. Re: How to get reference to the first body row on page in a long table?
              Laubender Adobe Community Professional & MVP

              If I'm looking for the rowType of every row of such a table:

               

              // Select textFrame and run this script:
              var rows = app.selection[0].tables[0].rows.everyItem().getElements();
              var rowsLength = rows.length;
              for(var n=0;n<rowsLength;n++)
              {
                  // Write the rowType of every row to the JavaScript Console of the ESTK:
                  $.writeln("row-index"+"\t"+n+"\t"+"rows[n].rowType.toString():"+"\t"+rows[n].rowType.toString() );
              };
              

               

              The result is this:

               

              /*

              row-index    0    rows[n].rowType.toString():    BODY_ROW

              row-index    1    rows[n].rowType.toString():    BODY_ROW

              row-index    2    rows[n].rowType.toString():    BODY_ROW

              row-index    3    rows[n].rowType.toString():    BODY_ROW

              row-index    4    rows[n].rowType.toString():    BODY_ROW

              row-index    5    rows[n].rowType.toString():    BODY_ROW

              row-index    6    rows[n].rowType.toString():    BODY_ROW

              row-index    7    rows[n].rowType.toString():    BODY_ROW

              row-index    8    rows[n].rowType.toString():    BODY_ROW

              row-index    9    rows[n].rowType.toString():    BODY_ROW

              */

               

              Regards,
              Uwe

              • 5. Re: How to get reference to the first body row on page in a long table?
                Laubender Adobe Community Professional & MVP

                Here the thing in detail:

                 

                1. Copy some rows (yellow range) to the clipboard

                2. Paste to the page (or to an empty text frame) and add a column to the new table

                 

                1-HowToGetRowsWithNoCells.png

                3. Merge the yellow cells columnwise.

                4. Select the merged cells and copy

                5. Select the first cell of the range in the old table

                 

                2-HowToGetRowsWithNoCells.png

                6. Paste the copied merged cells:

                 

                3-HowToGetRowsWithNoCells.png

                7. Run a script to get the actual names of each cell of the table as contents:Result:

                 

                4-HowToGetRowsWithNoCells.png

                 

                Bottom line of this exercise:

                If you get customer's documents and running scripts on tables it could well be that some rows of a table contain no cells at all.

                 

                Regards,
                Uwe

                1 person found this helpful
                • 6. Re: How to get reference to the first body row on page in a long table?
                  Laubender Adobe Community Professional & MVP

                  And the same can be said for columns.

                  It is possible, that some columns of a table contain no cells at all.

                   

                  Here an example where columns[2].cells.length is 0 :

                  ( 5 columns are visible, still the Tables panel is saying that 6 columns are in the table.  )

                  Column-index-2-contains-no-cells.png

                   

                  Regards,
                  Uwe

                  • 7. Re: How to get reference to the first body row on page in a long table?
                    Kasyan Servetsky Level 5

                    Hi Uwe,

                     

                    Thank you for your reply! I didn't know that such situation is possible.

                    However, I'm writing a script testing it in a document provided by my client and it doesn't happen to me. The main purpose of the script is to duplicate the 'product name' at the top of each page and apply alternative fills using the product's color (tinted, say, by 12%). If some rows are added/removed, the script updates them. It's a sort of the 2-nd level of header rows.

                     

                    Regards,
                    Kasyan

                    • 8. Re: How to get reference to the first body row on page in a long table?
                      Laubender Adobe Community Professional & MVP

                      Hi Kasyan,

                      to catch the first cells of every row that contains any cells can be done reliably with:

                       

                      var firstCells = table.rows.everyItem().cells[0].getElements();
                      

                       

                      You now could loop the firstCells array to look after firstCells[n].parentRow.rowType and e.g. the id of the parentTextFrames[0] of its first insertion point. Note: There are cases where parentTextFrames[0] is undefined because all text in the cell is overset.

                       

                      Regards,

                      Uwe

                      • 9. Re: How to get reference to the first body row on page in a long table?
                        Laubender Adobe Community Professional & MVP

                        Even an empty cell can be overset, e.g. if its height is fixed and the point size of the first insertion point is too high so that the insertion point cannot be shown in the cell:

                         

                        Empty-cell-is-overset.png

                        Note: InDesign's preflight will not recognize this problem.

                        The Story Editor window does.

                         

                        Regards,
                        Uwe

                        1 person found this helpful
                        • 10. Re: How to get reference to the first body row on page in a long table?
                          Kasyan Servetsky Level 5

                          Hi Uwe,

                           

                          Thank you for your tips. I just started working on the script so haven't got to dealing with overset cells and tables.

                           

                          I tried using

                          var rows = table.rows.everyItem().getElements();
                          

                          instead of

                          var rows = table.rows;
                          

                          thinking it would make things run faster, but it resulted in a total mess. I guess 'static array' doesn't work in this case (unlike 'alive collection')

                           

                          Regards,
                          Kasyan

                          • 11. Re: How to get reference to the first body row on page in a long table?
                            Laubender Adobe Community Professional & MVP

                            Hi Kasyan,

                            I thought something like this could work:

                             

                            var doc = app.documents[0];
                            var table = doc.stories[0].tables[0];
                            var color = doc.colors.itemByName("Yellow");
                            
                            var firstCellsArray = table.rows.everyItem().cells[0].getElements();
                            var firstCellsArrayLength = firstCellsArray.length;
                            
                            /*
                            Add some code here to get the first row after your first header rows.
                            */
                            
                            for(var n=1;n<firstCellsArrayLength;n++)
                            {
                                // In case a footer row is also there:
                                if(firstCellsArray[n].rowType == RowTypes.FOOTER_ROW){continue};
                               
                                var textFrameIDbefore = firstCellsArray[n-1].insertionPoints[0].parentTextFrames[0].id;
                                var currentTextFrameID = firstCellsArray[n].insertionPoints[0].parentTextFrames[0].id;
                               
                                if(currentTextFrameID != textFrameIDbefore)
                                {
                                    var rowAfterHeader = firstCellsArray[n].parentRow;
                                    doSomethingWithRow(rowAfterHeader);
                                }
                            };
                            
                            function doSomethingWithRow(tableRow)
                            {
                                tableRow.fillColor = color ;
                            };
                            

                             

                            I tested with a table with 2 header rows and 1 footer row with CS6 on OSX.

                            Also with a table with 2 header rows and no footer row.

                             

                            Regards,
                            Uwe

                            • 12. Re: How to get reference to the first body row on page in a long table?
                              Laubender Adobe Community Professional & MVP

                              Here a variant of the above that is looking for first rows in text frames, that contain only one cell.

                              That should fit your criteria on merged cells that I can see in your screenshot of the table.

                               

                              Important note: Do not test with your original table first, because:

                              1. The first table of the first story in your document might not be your target table.

                              2. The snippet adds new contents to a qualifying row.

                              3. Will color that row "Yellow"

                               

                              However, I implemented a global undo.

                               

                              app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;
                              app.doScript
                                  (
                                 
                                  checkFirstTableRowInTextFrame,
                                  ScriptLanguage.JAVASCRIPT,
                                  [],
                                  UndoModes.ENTIRE_SCRIPT,
                                  "Check first table row in every text frame that has one cell only."
                                 
                                  );
                                 
                              function checkFirstTableRowInTextFrame()
                              {
                                  if(app.documents.length == 0){return};
                                  if(app.documents[0].stories.length == 0){return};
                                  if(app.documents[0].stories[0].tables.length == 0){return};
                              
                                  var doc = app.documents[0];
                              
                                  //Adapt the target to your needs:
                                  var table = doc.stories[0].tables[0];
                                 
                                  var color = doc.colors.itemByName("Yellow");
                              
                                  var firstCellsArray = table.rows.everyItem().cells[0].getElements();
                                  var firstCellsArrayLength = firstCellsArray.length;
                              
                                  for(var n=1;n<firstCellsArrayLength;n++)
                                  {
                                     
                                      if(firstCellsArray[n].rowType == RowTypes.FOOTER_ROW){continue};
                                     
                                      var textFrameIDbefore = firstCellsArray[n-1].insertionPoints[0].parentTextFrames[0].id;
                                      var currentTextFrameID = firstCellsArray[n].insertionPoints[0].parentTextFrames[0].id;
                                     
                                      if(currentTextFrameID != textFrameIDbefore)
                                      {
                                          var rowAfterHeader = firstCellsArray[n].parentRow;
                                          doSomethingWithRow( rowAfterHeader , color );
                                      }
                                  };
                              
                                  function doSomethingWithRow(tableRow , color )
                                  {
                                      var cellLength = tableRow.cells.everyItem().getElements().length;
                                      $.writeln(cellLength);
                                      if(cellLength == 1)
                                      {
                                          tableRow.contents = ["Header"];
                                          tableRow.fillColor = color;
                                      }
                                  };
                              
                              };
                              

                               

                              Tested on several variants of a table, that is running through several text frames and some (not all) of its first rows are merged to one cell.

                              Variants were: 0 footer rows, 1 footer row, 2 footer rows, 2 header rows.

                               

                              Regards,

                              Uwe

                              1 person found this helpful
                              • 13. Re: How to get reference to the first body row on page in a long table?
                                Laubender Adobe Community Professional & MVP

                                In fact basically we could change the loop a bit:

                                 

                                var table = app.selection[0].tables[0];
                                var colorFirstBodyCell= app.documents[0].colors.itemByName("Yellow");
                                
                                var firstCellsArray = table.rows.everyItem().cells[0].getElements(); 
                                var firstCellsArrayLength = firstCellsArray.length;
                                
                                var numberOfHeaderRows = table.headerRowCount;
                                var numberOfFooterRows = table.footerRowCount;
                                
                                for(var n=numberOfHeaderRows;n<firstCellsArrayLength-numberOfFooterRows;n++)
                                {
                                   
                                    var textFrameIDbefore = firstCellsArray[n-1].insertionPoints[0].parentTextFrames[0].id; 
                                    var currentTextFrameID = firstCellsArray[n].insertionPoints[0].parentTextFrames[0].id; 
                                   
                                    if(currentTextFrameID != textFrameIDbefore) 
                                    { 
                                        var rowAfterHeader = firstCellsArray[n].parentRow; 
                                        rowAfterHeader.fillColor =  colorFirstBodyCell;
                                    } 
                                };
                                

                                 

                                Because of the following situation that makes the role of header and footer rows in the index of rows a bit more clear.

                                The contents of all first cells are their names that reflect the position of indexOfColumn : indexOfRow in the table.

                                 

                                Below the table is the list of rowTypes in order of the index of the rows:

                                 

                                TheRoleOfFooterRowsInTheIndexofRows.png

                                 

                                Regards,
                                Uwe

                                1 person found this helpful
                                • 14. Re: How to get reference to the first body row on page in a long table?
                                  Kasyan Servetsky Level 5

                                  Hi Uwe,

                                   

                                  You tips are very interesting to me and I'd like to post them on my site if you don't mind.

                                  From the outset I must be wasn't clear enough about what I'm trying to achieve and where I've got so far.

                                  Here I posted the before & after test files (CC 2017) and the current version of the script.

                                  At start I have this.

                                  07-01-2017 22-39-23.png

                                  A long table -- catalog -- with two header rows (gray) and one footer row (empty).

                                  The task is to duplicate the product name (e.g. Suzuki on the screenshot) at the top on the next page, and apply alternative fills using the product's color tinted by XX%, like so:

                                  07-01-2017 22-00-59.png

                                  However, the table can be edited: more models (rows) added or removed so the script should update it. For example, I added 5 rows so the 'Suzuki' row on the right page moved downwards:

                                  07-01-2017 22-03-27.png

                                  I run the script again and it fixed the problem

                                  07-01-2017 22-53-28.png

                                  By the way, at first I used

                                  app.doScript(PreCheck, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "\"" + scriptName + "\" Script");
                                  

                                  to trigger the script so the user could Undo-Redo it, but it ruined the reference to the table on the 2nd run, for some reason, so I uncommented it.

                                  Here's the whole script:

                                  var scriptName = "Test tables",
                                  doc, table;
                                  
                                  PreCheck();
                                  //~ app.doScript(PreCheck, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "\"" + scriptName + "\" Script");
                                  //===================================== FUNCTIONS ======================================
                                  function Main() {
                                      var row, newRow, textFrame, previousTextFrame, currentTextFrame;
                                      var startTime = new Date();
                                      
                                      var mainRow = null, 
                                      previousMainRowContents = null,
                                      fillTint = 50,
                                      countRowsInRange = 0;
                                  
                                      var whiteSwatch = MakeColor("Blanc", ColorSpace.CMYK, ColorModel.process, [0, 0, 0, 0]);
                                      var rows = table.rows;
                                      
                                      previousTextFrame = rows[0].cells[0].insertionPoints[0].parentTextFrames[0];
                                      
                                      for (var i = 0; i < rows.length; i++) {
                                          row = rows[i];
                                          
                                          if (row.rowType != RowTypes.BODY_ROW) {
                                              continue; // skip headers & footers
                                          }
                                      
                                          currentTextFrame = row.cells[0].insertionPoints[0].parentTextFrames[0];
                                          
                                          if (row.cells.length == 1) { // main row
                                              mainRow = row;
                                              countRowsInRange = 0; // reset alternative fills counter
                                  
                                              if (previousMainRowContents == null) {
                                                  previousMainRowContents = mainRow.contents;
                                              }
                                          }
                                          else if (currentTextFrame != previousTextFrame) {
                                              newRow = rows.add(LocationOptions.BEFORE, row);
                                              countRowsInRange = 0; // reset alternative fills counter
                                              newRow.cells[0].merge(newRow);
                                              newRow.cells[0].label = "duplicated";
                                              newRow.cells[0].appliedCellStyle = mainRow.cells[0].appliedCellStyle;
                                              newRow.cells[0].fillColor = mainRow.cells[0].fillColor;
                                              newRow.cells[0].fillTint = mainRow.cells[0].fillTint;
                                              mainRow.cells[0].texts[0].duplicate(LocationOptions.AT_BEGINNING, newRow.cells[0]);
                                          }
                                          else if (countRowsInRange % 2 == 0) {
                                              row.fillColor = mainRow.fillColor;
                                              row.fillTint = fillTint;
                                          }
                                          else {
                                              row.fillColor = whiteSwatch;            
                                          }
                                          
                                          previousTextFrame = currentTextFrame;
                                          countRowsInRange++;
                                      }
                                  
                                      var endTime = new Date();
                                      var duration = GetDuration(startTime, endTime);
                                      
                                      //alert("Finished.", scriptName, false);
                                      $.writeln("DONE: time elapsed: " + duration);
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  function CleanUp() {
                                      var row,
                                      rows = table.rows;
                                      
                                      for (var i = rows.length - 1; i >= 0; i--) {
                                          row = rows[i];
                                          if (row.rowType != RowTypes.BODY_ROW) continue; // skip headers & footers
                                          
                                          if (row.cells.length == 1 && row.cells[0].label == "duplicated") { // main row
                                              try {
                                                  row.remove();
                                              }
                                              catch(err) {
                                                  $.writeln(err.message + ", line: " + err.line);
                                              }
                                          }
                                          else if (row.cells.length > 1) {
                                              row.fillColor = doc.swatches.itemByName("None");
                                          }
                                      }
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  function FindTable(obj) {
                                      if (app.selection[0].constructor.name == "TextFrame" && app.selection[0].tables.length == 1) { // a text frame is selected
                                          obj = app.selection[0].tables[0];
                                      }
                                      else {
                                          while (obj.constructor.name != "Table") {
                                              obj = obj.parent;
                                              if (obj.constructor.name == "Application") {
                                                  ErrorExit("Can't get the table.", true);
                                              }
                                          }
                                      }
                                  
                                      return obj;
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  function PreCheck() {
                                      if (app.documents.length == 0) ErrorExit("Please open a document and try again.", true);
                                      doc = app.activeDocument;
                                      if (doc.converted) ErrorExit("The current document has been modified by being converted from older version of InDesign. Please save the document and try again.", true);
                                      if (!doc.saved) ErrorExit("The current document has not been saved since it was created. Please save the document and try again.", true);
                                  
                                      if (app.selection.length == 0 || app.selection.length > 1) ErrorExit("One text frame containing the table, or something in the table should be selected, or the cursor should be inserted into the table.", true);
                                  
                                      table = FindTable(app.selection[0]);
                                      if (table.constructor.name != "Table") ErrorExit("Can't get the table.", true);
                                  
                                      CleanUp();
                                      Main();
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  function GetDuration(startTime, endTime) {
                                      var str;
                                      var duration = (endTime - startTime)/1000;
                                      duration = Math.round(duration);
                                      if (duration >= 60) {
                                          var minutes = Math.floor(duration/60);
                                          var seconds = duration - (minutes * 60);
                                          str = minutes + ((minutes != 1) ? " minutes, " :  " minute, ") + seconds + ((seconds != 1) ? " seconds" : " second");
                                          if (minutes >= 60) {
                                              var hours = Math.floor(minutes/60);
                                              minutes = minutes - (hours * 60);
                                              str = hours + ((hours != 1) ? " hours, " : " hour, ") + minutes + ((minutes != 1) ? " minutes, " :  " minute, ") + seconds + ((seconds != 1) ? " seconds" : " second");
                                          }
                                      }
                                      else {
                                          str = duration + ((duration != 1) ? " seconds" : " second");
                                      }
                                  
                                      return str;
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  function ErrorExit(error, icon) {
                                      alert(error, scriptName, icon);
                                      exit();
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  function MakeColor(colorName, colorSpace, colorModel, colorValue) {
                                      var color = doc.colors.item(colorName);
                                      if (!color.isValid) {
                                          color = doc.colors.add({name: colorName, space: colorSpace, model: colorModel, colorValue: colorValue});
                                      }
                                      return color;
                                  }
                                  //--------------------------------------------------------------------------------------------------------------------------------------------------------
                                  
                                  

                                   

                                  Regards,
                                  Kasyan

                                  • 15. Re: How to get reference to the first body row on page in a long table?
                                    Laubender Adobe Community Professional & MVP

                                    Hi Kasyan,

                                    go ahead and publish my tips.

                                     

                                    Now it's clear to me what you like to achieve.

                                    Glad, it's working now…

                                     

                                    Hm. Did not test your code yet. So I'm not sure why the doScript() approach does not work in this case.
                                    Maybe you should wrap all your code into one function and call that function with doScript()?

                                     

                                    I think you could make your code a bit faster by using the approach with getElements(). Less access to actual unresolved DOM objects after writing all relevant first cells of all rows to an array.

                                     

                                    You also could avoid the test for rowType if you change the for loop a bit. Now that we know that index numbers for header rows are always at the start of the index of rows (that's obvious) and the index numbers of footer rows are always at the end of the index of rows (not so obvious; see my answer 13).

                                     

                                    Regards,
                                    Uwe

                                    • 16. Re: How to get reference to the first body row on page in a long table?
                                      Kasyan Servetsky Level 5

                                      Hi Uwe,

                                       

                                      Hm. Did not test your code yet. So I'm not sure why the doScript() approach does not work in this case.

                                      Maybe you should wrap all your code into one function and call that function with doScript()?

                                       

                                      Wrapping the whole code into one function or calling a chain of functions work exactly in the same way with doScript() method. I tested this before and re-tested it now. The latter is more convenient for me from the standpoint of readability.

                                      Here's a brief description of what goes wrong:

                                      1. I select the table: e.g. by selecting a text frame
                                      2. Run the script for the first time -- everything goes as expected
                                      3. Then I Undo the whole script (Ctrl + Z). The frame is still selected.
                                      4. Finally I run the script for the 2nd time and it throws the 'Object is invalid' error while trying to get reference to the table's rows

                                      08-01-2017 17-52-04.png

                                      In 'Call stack' I switch to the PreCheck function where the table variable is defined -- it's invalid.

                                      08-01-2017 17-52-20.png

                                      In the FindTable function the obj variable is also invalid.

                                      08-01-2017 20-43-54.png

                                      If I close and reopen the document, the above-mentioned variables become valid again. I don't know why this happens: haven't dug into this issue too deep so far.

                                       

                                      I think you could make your code a bit faster by using the approach with getElements(). Less access to actual unresolved DOM objects after writing all relevant first cells of all rows to an array.

                                      I tried this approach thinking it would make the script run faster, but it didn't work for me.

                                      Namely I changed a couple of lines:

                                      var rows = table.rows;
                                      

                                      to

                                      var rows = table.rows.everyItem().getElements();
                                      

                                      and

                                      newRow = rows.add(LocationOptions.BEFORE, row);
                                      

                                      to

                                      newRow = table.rows.add(LocationOptions.BEFORE, row);
                                      

                                       

                                      which resulted in a mess

                                      08-01-2017 19-18-50.png

                                      As I already said in my previous post, I think this happened because I used static array instead of live collection. I have to use the latter because the script is constantly adding new rows during execution and the rows collection is changing for that reason.

                                      You also could avoid the test for rowType if you change the for loop a bit. Now that we know that index numbers for header rows are always at the start of the index of rows (that's obvious) and the index numbers of footer rows are always at the end of the index of rows (not so obvious; see my answer 13).

                                      I understand your idea. But do you think it would make the script to run faster?

                                      Timing in ESTK shows me it doesn't take long to check if the row isn't body row. A few nano-secs doesn't matter, I think; anyway, it is not a bottleneck in the script.

                                      08-01-2017 21-11-49.png

                                      08-01-2017 21-12-21.png

                                       

                                      Regards,
                                      Kasyan

                                      • 17. Re: How to get reference to the first body row on page in a long table?
                                        Laubender Adobe Community Professional & MVP

                                        Hi Kasyan,

                                        a brief comment on the getElements() approach.

                                         

                                        The first time we gather all rows in one array by using getElements()—you call it static and it is static in the sense that it captures the rows at a given time—we have to work with that array from back to forth if we want to add rows, because the index of rows will then be effected downstream only, so to say.

                                         

                                        In my examples I did not add (or remove) rows, just colored some rows and changed the contents of some cells. So I did not run into trouble looping forward. Maybe getElements() is not applicable to your script structure here, but generally I think it should be possible to work with getElements() when adding rows. But that will mean a total rewrite of your script.

                                         

                                        Your other problem with the undo:

                                         

                                        Currently I have no other idea than to:

                                        1. Save the document before running the script with as Save As and a version number.

                                        2. Running the script.

                                        3. Save the document with a version number.

                                         

                                        Regards,

                                        Uwe

                                        • 18. Re: How to get reference to the first body row on page in a long table?
                                          Kasyan Servetsky Level 5

                                          Hi Uwe,

                                           

                                          In practice, using .everyItem().getElements() is not always faster than using collections.

                                          I tested the script against the Test-Before.indd (the link to both is in a previous post) on my home PC a few times and it takes to complete:

                                          4 secs with dynamic collection

                                          5 secs with static array

                                           

                                          Adding the 'Undo-Redo' feature is not a client's requirement; it's just a habit of mine. In this particular case, it didn't work for some reason. For pure academic interest, I'd like to know why.

                                           

                                          Regards,

                                          Kasyan

                                          • 19. Re: How to get reference to the first body row on page in a long table?
                                            Trevorׅ Adobe Community Professional

                                            Hi Kasyan,

                                             

                                            Try .everyItem().getElements().slice()

                                            Adding the slice can make a significant difference with large collections.

                                            I'm not saying it will. Also it avoids the [too many elements] error.

                                            I just did a search on it and, regarding the performance it could be that it's only with older versions.

                                            See Re: Get the index of the current page?

                                            See also Re: Big performance issue while removing tabs by indents

                                             

                                            Worth a go for the extra 8 characters.

                                             

                                            Trevor

                                            1 person found this helpful
                                            • 20. Re: How to get reference to the first body row on page in a long table?
                                              Kasyan Servetsky Level 5

                                              Hi Trevor,

                                               

                                              Thanks for the links. I'll look into this.

                                              As far as I remember, the '.everyItem().getElements().slice()' tip originates from Kris Coppieters: he used this trick in the first version of InDesign and said it helped him somehow to avoid some problems but couldn't explain how exactly it worked and which problems it solved; he simply used it out of habit. But again: it relates to an antique version of InDesign; I don't think anybody uses it nowadays.

                                               

                                              Anyway, I don't think I can use static array -- .everyItem().getElements() -- in my script because it constantly adds new rows so I have to use a live collection which is dynamically updated. I mentioned this in my previous posts #10 and #16.

                                               

                                              Regards,
                                              Kasyan

                                              • 21. Re: How to get reference to the first body row on page in a long table?
                                                Trevorׅ Adobe Community Professional

                                                Kasyan Servetsky wrote:

                                                 

                                                I don't think anybody uses it nowadays.

                                                Definitely wrong about that, I use it.

                                                But doing a quick search on the forum for '.everyItem().getElements().slice' does feature by far mostly me, so I see your point.

                                                 

                                                I had a script on CS5 that was unworkable without the slice(), I'm pretty sure about that.

                                                I did get a "too many elements" in InDesign CC2017 without using the slice which when I added the slice didn't get but I haven't been able to duplicate this.

                                                • 22. Re: How to get reference to the first body row on page in a long table?
                                                  Kasyan Servetsky Level 5

                                                  Hi Trevor,

                                                   

                                                  I made a test using the script and file (with a table of 240 rows) in my post #14 (following your tips here).

                                                   

                                                  To slice, or not to slice? That is the question.

                                                  In  both cases:

                                                  var rows = table.rows.everyItem().getElements().slice(0);
                                                  

                                                  and

                                                  var rows = table.rows.everyItem().getElements();
                                                  

                                                  I get exactly the same result: it takes 4-5 secs to complete the script.

                                                   

                                                  Disabling redraw

                                                  app.scriptPreferences.enableRedraw = false;
                                                  

                                                  makes the script one sec faster.

                                                   

                                                  doScript

                                                  1. UndoModes.FAST_ENTIRE_SCRIPT
                                                  2. UndoModes.ENTIRE_SCRIPT
                                                  3. without doScript

                                                  All the three options produce the same timing

                                                   

                                                  while or for loop

                                                  In the above mentioned post you also suggested to use

                                                  l = paras.length;
                                                  while (l--)
                                                  {
                                                       p = paras[l];
                                                        ......
                                                  }
                                                  paras = null;
                                                  

                                                  instead of

                                                  p =  paras.pop()
                                                  

                                                   

                                                  Do you think

                                                  var myarray = [],
                                                  i = myarray.length;
                                                  while (i--) {
                                                      // do something with myarray[i]
                                                  }
                                                  

                                                  may be faster then

                                                  for (var i = myArray.length - 1; i >= 0; i--) {
                                                      // do something with mAarray[i]
                                                  }
                                                  

                                                  I don't know: never tested it. I personally have a habit of using 'for-loop'. I guess (but don't know for sure) they're both equivalent.

                                                   

                                                  So far I'm working with a simple file: prefer to move from the simple to complex as I was taught  in an art school. Later I'm going to test it with larger tables and probably I'll get more precise results.

                                                   

                                                  Regards,
                                                  Kasyan

                                                  • 23. Re: How to get reference to the first body row on page in a long table?
                                                    Trevorׅ Adobe Community Professional

                                                    Hi Kasyan

                                                     

                                                    1) Slice is not going to make any real difference for such an amount of items. The collections I were referring to were in the 1000s

                                                    2) pop is a function [l] is a reference, functions / operations basically take longer, with some early web browsers this was not necessarily so but on Adobe's (ancient) and all modern  js engines it is. The difference is about twice the speed so you could probably save about a nanosecond on your 240 items

                                                    The are cases when one would want to use pop.

                                                    3) Yes -- is quicker again over 2 times so for your 240 items you might save another 1/2 nanosecond

                                                    I find the while format quicker to type, might be just psychological but I'm a bit of a psyco so that's fine with me.

                                                    I have a nice script for testing function speeds, I just use an include to test the functions but here a full example.

                                                    There's another well know hi-res timer but it's no good as it highly weighs in favor of the 1st function were as my one doesn't, also for various reasons the high res is a waist of time.

                                                    Imagine timing how long it takes for a leaf to fall from the top of the Eiffel tower in milliseconds. Pointless, the next leaf is going to be a different size, the wind might be different, one of the spectators might sneeze or otherwise exchange gasses and effect the timing etc.

                                                     

                                                    Anyways

                                                     

                                                     

                                                     

                                                    function compare(snippets, /* a function or array of functions to time */
                                                                     runs, /* [Amount of times to call the functions] */
                                                                     functionArguments, /* [Amount of loop] */
                                                                     digits /* [round results to n decimal points] */) {
                                                        var l, a = [], f, r, i, c, result = [], results = [], ranking = [], functionNames = [],
                                                            testsStarted, testsTook, start, end, t, n, snip,
                                                            quickest, slowest, quicker,slower, looser, winner, speed;
                                                        var DEFAULT_RUNS = 10; // Avoiding use of const for wider compatibility
                                                        var DEFAULT_DIGITS = 3;
                                                        var STARS = '\n**********************************************************************\n';
                                                        if (!snippets) {
                                                            throw "The first argument of the compare function must be a function or an array of functions";
                                                        }
                                                        if (!(snippets instanceof Array)) {
                                                            snippets = [snippets];
                                                        }
                                                        l = snippets.length;
                                                        // if runs is not a positive integer then assign it a default value
                                                        runs = ~~runs; // converts runs into an integer
                                                        if (runs < 1) {
                                                            runs = DEFAULT_RUNS;
                                                        }
                                                        // if digits is not a positive integer then assign it a default value
                                                        if (digits === undefined) {
                                                            digits = DEFAULT_DIGITS;
                                                        }
                                                        digits = ~~digits; // converts digits into an integer
                                                    
                                                        /** If only one function is to be tested then we'll just do a simple timing test */
                                                    
                                                        if (snippets.length === 1) {
                                                            n = runs;
                                                            t = 0;
                                                            while (n--) {
                                                                /* See below note on the use of the Date function */
                                                                start = new Date();
                                                                // call the function
                                                                snippets[0](functionArguments);
                                                                end = new Date();
                                                                t += end - start;
                                                            }
                                                            // Average out the times
                                                            t /= runs;
                                                            return 'Average execution time: ' + toDigits(t, digits) + 'ms';
                                                        }
                                                        /** For multiple funbctions */
                                                        // There is sometimes a bias towards the first tested function
                                                        // to "help" reduce this we shall randomly shuffle the execution order of the functions
                                                        // create an array of the snippet indexes so it can be shuffled
                                                        for (f = 0; f < l; f++) {
                                                            for (r = 0; r < runs; r++) {
                                                                a.push(f);
                                                            }
                                                        }
                                                    
                                                        function shuffle(arr, preserve) { // This is my (Trevor) implementation of Fisher-Yates shuffle
                                                            var a, i, r, v;
                                                            // duplicate the array if one wants to preserve the order of the original array
                                                            a = (preserve) ? arr.slice() : arr;
                                                            i = a.length;
                                                            while (i--) {
                                                                // generate a random integer for an array index
                                                                r = ((1 + i) * Math.random()) | 0;
                                                                v = a[r]; // get the value of that index
                                                                // swap the values;
                                                                a[r] = a[i];
                                                                a[i] = v;
                                                            }
                                                            return a;
                                                        }
                                                    
                                                        shuffle(a);
                                                        n = a.length;
                                                        // record test start time
                                                        testsStarted = new Date();
                                                        /** execute the snippets in the random order **/
                                                        while (n--) {
                                                            i = a[n];
                                                            /* There is an opinion that one should use more accurate time measuring methods than the Date method
                                                               While it is certainly true that when the clock is changed in the middle of executing the comparison
                                                               the results will be inaccurate however the "noise" and external factors that cause the discrepancy
                                                               in the result and even the time it takes to call even an empty function or to loop through a loop
                                                               in my opinion render a higher resolution recording pointless */
                                                            start = new Date();
                                                            // call the function
                                                            snippets[i](functionArguments);
                                                            end = new Date();
                                                            t = end - start;
                                                            // add the execution time to the results,
                                                            // if the result for that index is currently undefined set the result to the execution time
                                                            results[i] = (results[i] + t) || t;
                                                        }
                                                        testsTook = new Date() - testsStarted;
                                                        // rank the results
                                                        for (c = 0; c < l; c++) {
                                                            results[c] /= runs; // average out the results
                                                            ranking[c] = {snippet: c, rank: results[c], name: snippets[c].name};
                                                        }
                                                        ranking.sort(function(a, b) {return +(a.rank > b.rank) || -(a.rank !== b.rank)});
                                                    
                                                        // For reporting the ranked function names
                                                        for (c = 0; c < l; c++) {
                                                            functionNames[c] = ranking[c].name
                                                        }
                                                    
                                                        function toDigits(x, n) {
                                                            if (n === undefined) {
                                                                return x;
                                                            }
                                                            return (~~(Math.pow(10, n) * x)) / Math.pow(10, n);
                                                        }
                                                    
                                                        quickest = ranking[0].rank;
                                                        slowest = ranking[l - 1].rank;
                                                        looser = l - 1;
                                                        winner = 0;
                                                    
                                                        result.push(['Test Started: ' + testsStarted,
                                                                     'Functions Tested: (Listed in order of performance) "' + functionNames.join('", "') + '"',
                                                                     'Arguments: ' + functionArguments,
                                                                     'Runs: ' + runs,
                                                                     'Test Took: ' + testsTook + 'ms'].join('\n'));
                                                    
                                                    
                                                        for (c = 0; c < l; c++) {
                                                            snip = ranking[c];
                                                            speed = snip.rank
                                                            quicker = slowest / speed;
                                                            slower = speed / quickest;
                                                            result.push(['Function ' + (snip.snippet + 1) + ') "' + snip.name + '"',
                                                                         'Average execution time: ' + (speed) + 'ms',
                                                                         'Rank: ' + (c + 1)+ ' (1 is best)',
                                                                         (looser === c) ? '** SLOWEST **' : toDigits(quicker, digits) + ' times (' + toDigits(quicker * 100 - 100, digits)+ '%) quicker than slowest function',
                                                                         (winner === c) ? '** QUICKEST **' : toDigits(slower, digits) + ' times (' + toDigits(100 - 100 / slower, digits)+ '%) slower than quickest function',
                                                                         ].join('\n'));
                                                        }
                                                        return STARS + result.join(STARS) + STARS;
                                                    }
                                                    
                                                    /************************************************************************************************************************************************************
                                                    **************************************************************************** Sample use ****************************************************************
                                                    ************************************************************************************************************************************************************/
                                                    
                                                    function While(n){
                                                        var n;
                                                        while (n--);
                                                    }
                                                    
                                                    function For(n){
                                                        for (; n>=0; n--);
                                                    }
                                                    
                                                    $.writeln(compare ([ For, While], 10, 1e6, 3));
                                                    

                                                     

                                                    Sample output

                                                     

                                                     

                                                    **********************************************************************

                                                    Test Started: Wed Jan 11 2017 01:00:27 GMT+0200

                                                    Functions Tested: (Listed in order of performance) "While", "For"

                                                    Arguments: 1000000

                                                    Runs: 10

                                                    Test Took: 1158ms

                                                    **********************************************************************

                                                    Function 2) "While"

                                                    Average execution time: 36.6ms

                                                    Rank: 1 (1 is best)

                                                    2.163 times (116.393%) quicker than slowest function

                                                    ** QUICKEST **

                                                    **********************************************************************

                                                    Function 1) "For"

                                                    Average execution time: 79.2ms

                                                    Rank: 2 (1 is best)

                                                    ** SLOWEST **

                                                    2.163 times (53.787%) slower than quickest function

                                                    **********************************************************************

                                                    1 person found this helpful
                                                    • 24. Re: How to get reference to the first body row on page in a long table?
                                                      Kasyan Servetsky Level 5

                                                      Hi Trevor,

                                                       

                                                      Thank you very much for your answers and the script! I wish I had it before.

                                                      However, it's not clear to me what the 3rd argument does: functionArguments, /* [Amount of loop] */

                                                      In your example you set it to '1e6' (in HEX). Its DEC equivalent is 486  -- according to my Calculator app -- but the script reports

                                                      Arguments: 1000000

                                                      in the console.

                                                      In other words, which value should I use here?

                                                       

                                                      Regards,
                                                      Kasyan

                                                      • 25. Re: How to get reference to the first body row on page in a long table?
                                                        Trevorׅ Adobe Community Professional

                                                        Hi Kasyan

                                                         

                                                        1e6 is not HEX it's DEC the number after the e is the amount of zeros.

                                                        1e1 == 10

                                                        1e-1 == 0.1

                                                        So 1e6 is 1000000 just in less keystrokes. It's a bit less error prone when there's a lot of 0's

                                                        0xe1 == 225 "0x" intro means HEX number so e1  is 225

                                                        0123 == 83 "0" intro means OCT so 123 is 83

                                                        Therefore:

                                                        100 === 1e2

                                                        1e2 === 0x64

                                                        0x64 === 0144

                                                        All this is true is our little Adobeland world, in the wider world there are other ones see New number and Math features in ES6

                                                         

                                                        I still plan on writing a few blog articles on my site and one of  the topics I plan on covering is symbolism.

                                                        It will cover the above and a wide range of things like the use all different types of symbols and the pros and cons (there's quite a lot of both) of using them.

                                                         

                                                        10.4532446463423216 | 0 === 10 // quicker than Math.floor(4325436.3546346724357) but not too readable.

                                                        If you want you can be the very first and probably the very last person to subscribe to my site. (Not even my dads subscribed and he's a really nice guy)

                                                        http://creative-scripts.com/

                                                        I can guarantee 02 things 100%

                                                        1e0) I will not pass on your mail to anybody or send any spam

                                                        0x2) You will not get sick of the multitude of post notifications, My last 2 (and first 2) posts were November 2014

                                                        Still I do have plans for a couple of quite good one, not likely to be more than 3 a year.

                                                        The other week I was contacted by someone from a large multinational who saw the site and ordered a script from me that payed for the site so far

                                                         

                                                        Regards

                                                         

                                                        Trevor

                                                        1 person found this helpful
                                                        • 26. Re: How to get reference to the first body row on page in a long table?
                                                          Kasyan Servetsky Level 5

                                                          Thank you again, Trevor!

                                                           

                                                          Now everything is clear to me. I had no chance to study maths at school so had no idea about such elementary things.

                                                          Yes, I subscribed to your blog: sure I won't be the one and only your subscriber if you start writing some posts there.

                                                           

                                                          Regards,
                                                          Kasyan

                                                          • 27. Re: How to get reference to the first body row on page in a long table?
                                                            Marc Autret Level 4

                                                            Great snippet Trevor, thanks a lot for sharing.

                                                             

                                                            Now let me split hairs over your while vs. for benchmark. The test seems to establish that the while syntax is about 2X faster than the for syntax. And indeed I got similar results (from CS4 to CC) using your sample code. However, IMHO it doesn't actually compare equivalent statements.

                                                             

                                                            The strict equivalent of your While function

                                                             

                                                            function While(n){  
                                                                while (n--);  
                                                            }  
                                                            

                                                             

                                                            should syntactically be

                                                             

                                                            function For(n){  
                                                                for ( ; n-- ; );  
                                                            }  
                                                            
                                                            

                                                             

                                                            which as you can notice behaves the same. So I think there is a bias when you ‘translate’ it into

                                                             

                                                            function For(n){  
                                                                for (; n>=0; n--);  
                                                            }
                                                            

                                                             

                                                            since you artificially dissociate the post-decrement operation (n--) from the boolean test, this dissociation being required neither in while nor for. Having two atomic expressions (n>=0 and n--) in your For function makes it, indeed, noticeably slower. But my version seems to me a more accurate translation and you will then check that it runs in almost the same time.

                                                             

                                                            There remains, I admit, a tiny gap of a fraction of a millisecond in the 1e6 test, which means that while is about 1.03 or 1.04 faster than for in that configuration. The reason, I think, is that for is formally more complete than while and has three placeholders in its inner structure, which could be considered having more arguments and then requiring an additional, incompressible, formal stage. But, as soon as you have an initialization step and/or some additional process between two iterations, I really don't think it has been proved here that while beats for at a perceptive level. To me, the while construct is only a formal shortcut of the for construct when the 1st and 3rd placeholders are not used. Otherwise, for remains the most generic form of a loop in the sense that any while syntax can always be rewritten using for.

                                                             

                                                            </split-hairs-mode>

                                                             

                                                            Best,

                                                            Marc

                                                            1 person found this helpful
                                                            • 28. Re: How to get reference to the first body row on page in a long table?
                                                              Kasyan Servetsky Level 5

                                                              Hi Marc,

                                                               

                                                              Thank you for your comment. It's very interesting!

                                                               

                                                              Regards,
                                                              Kasyan

                                                              • 29. Re: How to get reference to the first body row on page in a long table?
                                                                Trevorׅ Adobe Community Professional

                                                                @Marc,

                                                                 

                                                                True hair split there, but I was comparing 2 functions that people use and not 2 truly comparable functions.

                                                                 

                                                                @Kasyan

                                                                 

                                                                I don't use the above script too much, for these sorts of petty functions the extra time is normally very insignificant and only relevant for server usage that have to be really efficient. The results are very interesting never the less. If you stick some DOM function in the loop that's going to normally going to make the fine tuning pretty pointless. see the below example.

                                                                 

                                                                The real value of the above snippet is for real functions.

                                                                 

                                                                #include "/Users/Trevor/Documents/Adobe Scripts/Scripts Panel/Functions/function tests/Compare Functions.jsx";
                                                                var doc = app.activeDocument;
                                                                
                                                                function While(n) {
                                                                    var r;
                                                                    while (n--) {
                                                                        r = doc.rectangles.add();
                                                                        r.remove();
                                                                    }
                                                                }
                                                                
                                                                function For(n) {
                                                                    var r;
                                                                    for (; n >= 0; n--) {
                                                                        r = doc.rectangles.add();
                                                                        r.remove();
                                                                    }
                                                                }
                                                                
                                                                $.writeln(compare([For, While], 1, 1e5, 3));
                                                                
                                                                /**********************************************************************
                                                                Test Started: Wed Jan 11 2017 18:57:07 GMT+0200
                                                                Functions Tested: (Listed in order of performance) "For", "While"
                                                                Arguments: 100000
                                                                Runs: 1
                                                                Test Took: 835608ms
                                                                **********************************************************************
                                                                Function 1) "For"
                                                                Average execution time: 417254ms
                                                                Rank: 1 (1 is best)
                                                                1.002 times (0.263%) quicker than slowest function
                                                                ** QUICKEST **
                                                                **********************************************************************
                                                                Function 2) "While"
                                                                Average execution time: 418353ms
                                                                Rank: 2 (1 is best)
                                                                ** SLOWEST **
                                                                1.002 times (0.262%) slower than quickest function
                                                                **********************************************************************/
                                                                

                                                                 

                                                                 

                                                                Trevor

                                                                • 30. Re: How to get reference to the first body row on page in a long table?
                                                                  Marc Autret Level 4

                                                                  Hi again Trevor,

                                                                   

                                                                  Sorry to be insistent but my point was, while is not significantly faster than for. That's it. Not a very important fact in itself, we all agree and that's why I was talking about splitting hairs, but it would be technically wrong to claim that ExtendScript's implementation of while may intrinsically provide opportunities for optimization. There are reasons to use while and reasons to use for, and all are syntactic. I don't know exactly what you mean by “I was comparing 2 functions that people use,” since no serious JS developer would ever use a syntax like for(  ; n > 0 ; n-- ); as soon as for(  ; n-- ; ); is available to her/him. Of course I assume s/he knows what this exactly means, as well as you assume “people” know what exactly means the syntax while( n-- ). So, while still admitting my point is not crucial, I took time to argue and illustrate it, and I maintain your benchmark might be misunderstood… whatever the coding style “people use” ;-)

                                                                   

                                                                  Thanks for reading.

                                                                   

                                                                  Best,

                                                                  Marc

                                                                  2 people found this helpful