• Global community
    • Language:
      • Deutsch
      • English
      • EspaƱol
      • FranƧais
      • PortuguĆŖs
  • ę—„ęœ¬čŖžć‚³ćƒŸćƒ„ćƒ‹ćƒ†ć‚£
    Dedicated community for Japanese speakers
  • ķ•œźµ­ ģ»¤ė®¤ė‹ˆķ‹°
    Dedicated community for Korean speakers
Exit
0

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

Guru ,
Jan 04, 2017 Jan 04, 2017

Copy link to clipboard

Copied

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

TOPICS
Scripting

Views

8.1K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Guru , Jan 04, 2017 Jan 04, 2017

Votes

Translate

Translate
Guru ,
Jan 04, 2017 Jan 04, 2017

Copy link to clipboard

Copied

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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;

        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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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.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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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.rowType.toString():"+"\t"+rows.rowType.toString() );

};

The result is this:

/*

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

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

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

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

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

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

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

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

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

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

*/

Regards,
Uwe

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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.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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 06, 2017 Jan 06, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 07, 2017 Jan 07, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 07, 2017 Jan 07, 2017

Copy link to clipboard

Copied

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.rowType == RowTypes.FOOTER_ROW){continue};

  

    var textFrameIDbefore = firstCellsArray[n-1].insertionPoints[0].parentTextFrames[0].id;

    var currentTextFrameID = firstCellsArray.insertionPoints[0].parentTextFrames[0].id;

  

    if(currentTextFrameID != textFrameIDbefore)

    {

        var rowAfterHeader = firstCellsArray.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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 07, 2017 Jan 07, 2017

Copy link to clipboard

Copied

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.rowType == RowTypes.FOOTER_ROW){continue};

      

        var textFrameIDbefore = firstCellsArray[n-1].insertionPoints[0].parentTextFrames[0].id;

        var currentTextFrameID = firstCellsArray.insertionPoints[0].parentTextFrames[0].id;

      

        if(currentTextFrameID != textFrameIDbefore)

        {

            var rowAfterHeader = firstCellsArray.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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 07, 2017 Jan 07, 2017

Copy link to clipboard

Copied

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.insertionPoints[0].parentTextFrames[0].id;

  

    if(currentTextFrameID != textFrameIDbefore)

    {

        var rowAfterHeader = firstCellsArray.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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 07, 2017 Jan 07, 2017

Copy link to clipboard

Copied

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;

       

        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;

        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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 07, 2017 Jan 07, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 08, 2017 Jan 08, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 08, 2017 Jan 08, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 09, 2017 Jan 09, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 09, 2017 Jan 09, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 09, 2017 Jan 09, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 09, 2017 Jan 09, 2017

Copy link to clipboard

Copied

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 10, 2017 Jan 10, 2017

Copy link to clipboard

Copied

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;

      ......

}

paras = null;

instead of

p =  paras.pop()

Do you think

var myarray = [],

i = myarray.length;

while (i--) {

    // do something with myarray

}

may be faster then

for (var i = myArray.length - 1; i >= 0; i--) {

    // do something with mAarray

}

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 10, 2017 Jan 10, 2017

Copy link to clipboard

Copied

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 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; // get the value of that index

            // swap the values;

            a = a;

            a = 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;

        /* 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(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 = (results + t) || t;

    }

    testsTook = new Date() - testsStarted;

    // rank the results

    for (c = 0; c < l; c++) {

        results /= runs; // average out the results

        ranking = {snippet: c, rank: results, name: snippets.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 = ranking.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;

        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

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guru ,
Jan 11, 2017 Jan 11, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines