Skip to main content
Inspiring
April 2, 2019
Answered

Get parent story of a table, row, column or cell

  • April 2, 2019
  • 1 reply
  • 5363 views

In the script I'm working on, I need to get the parent story of the selection. This will typically be a Text object which is super easy with `mySelection.parentStory`. But in many cases it could be an object that doesn't have a parentStory method, like a table, row, column or cell. If it's a table, I worked out that I can get the parent story with `mySelection.storyOffset.parentStory`, but this doesn't work for rows, columns or cells (which have no storyOffset method). I could try navigating up the DOM tree with various checks and balances, and maybe end up with something like `mySelection.parent.parent.storyOffset.parentStory`, but that seems like a crazy way to do something that I'd have thought would be simple.

Is there a simpler way?

This topic has been closed for replies.
Correct answer Kals

I had to remove my last reply, because the suggested line of code is not working.

Don't know why I thought it is, because insertionPoint has no property storyOffset.

But this will work for every selected text:

app.selection[0].insertionPoints[0].parentStory

Regardless if the text is in a table cell or not.

Tested with InDesign CC 2019 on Windows 10 and InDesign CS6 as well.

Regards,
Uwe


Laubender: So I think the best I can come up with is this variation of your idea:

var mySelection = app.selection[0];

var myStory = mySelection.hasOwnProperty("storyOffset") ? mySelection.storyOffset.parentStory : mySelection.insertionPoints[0].parentStory;

It seems to work for every possible selection.

1 reply

willcampbell7
Legend
April 2, 2019

Not that it's simple, but one way to get there is to climb up the parent tree checking for the constructor name until you find "Story".

In most cases I've encountered it seems the only time a text selection lacks the property parentStory is when the parent constructor is "Cell". So in some scripts I have code such as below, to signal when I'm inside a table.

// (assuming 'selection' is var of selected text)

var parent = selection.parent;

if (parent.constructor.name != "Cell") {

    parent = selection.parentStory;

}

And if constructor does == cell, then you have to climb up the parent tree (or not, depending on what you're trying to get done).

Maybe that will help. Also perhaps helpful, below is a function I wrote to test if a selection is text or not. Here you can see the list of constructor names you might encounter. Perhaps not an answer to your question but hopefully guide you in the right direction.

function selectionIsText(selection) {

    switch (selection.constructor.name) {

        case "Character":

        case "Word":

        case "TextStyleRange":

        case "Line":

        case "Paragraph":

        case "TextColumn":

        case "Text":

        case "Cell":

        case "Column":

        case "Row":

        case "Table":

            return true;

    }

    return false;

}

William Campbell
KalsAuthor
Inspiring
April 2, 2019

Thanks for these ideas William.

williamc3112933  wrote

Not that it's simple, but one way to get there is to climb up the parent tree checking for the constructor name until you find "Story".

The problem is, you can go all the way up the tree and never see Story at all. If I select a cell and loop through consecutive parents (being careful to avoid an infinite loop, since the Application seems to think that it is its own parent!), I get this:

Cell

Table

TextFrame

Spread

Document

Application

As you can see, TextFrame is the parent of Table, not Story. According to the docs, the parent of a Table could be Story, along with a dozen other possibilities, but in my testing it is not. That's another thing I don't understand about the parent–child relationship—it seems so random. Thankfully, someone at Adobe thought to give us a parentStory property for text. Why not for tables and cells too? Who knows.

So anyway, at this stage I guess I need to loop through, check for Table, then use my old friend `storyOffset.parentStory` on the Table. I just thought there must be a more sensible way, but I'm slowly learning that Adobe's API is anything but sensible at times.

williamc3112933  wrote

Also perhaps helpful, below is a function I wrote to test if a selection is text or not. Here you can see the list of constructor names you might encounter. Perhaps not an answer to your question but hopefully guide you in the right direction.

Ah yes, that looks very much like some code out of Adobe's JavaScript Scripting Guide (under 'Working with text selections') which I actually used myself in an earlier version of my script. I've since changed it to just:

if (app.selection[0].hasOwnProperty("findGrep")) { … }

Since the findGrep() method is what I actually want to call on the text, this is perfect for filtering out invalid sections, and satisfyingly concise. :-) (Unlike the code I'm going to have to write to find the parent story in all possible circumstances!)

Marc Autret
Legend
April 4, 2019

Excellent as always Marc Autret​, i have a few questions. The code snippet you supplied will report the parent story for objects like cell, table etc and not for objects like text, word, character, am i right in understanding this? So we will have to augment this code snippet with another piece for objects that directly support a property like parentStory

Secondly it was very ingenious of you for using the toSpecifier for this algorithm. Can you give some use cases where this approach may be used, something that you might have written, code snippet would not be needed just an idea would do good, i would like to leverage this, just want to teach myself to think along this path as well.

Thanks,

-Manan


Hi Manan,

1. You're right. Since Text objects already have a parentStory, we should add a line in the code:

const getStory = function(/*?any*/x)

//----------------------------------

{

    x||((x=app.properties.selection)&&(x=x[0]));

    if( x!==Object(x) || !('toSpecifier' in x) ) return false;

    // Text objects already have a parentStory!

    // ---

    if( 'parentStory' in x ) return x.parentStory;

    x = x.toSpecifier().match(/\/document\[.+?\/\/text-frame\[@id=\d+\]/);

    if( !x || !(x=resolve(x[0])).isValid ) return false;

    return x.parentStory;

};

And as you have observed, addressing this simple case is required, because the specifier of basic Text objects does not involve a text-frame component at all. Thanks for pointing that out.

2. My reasoning about toSpecifier() is, it reveals in one hit the internal ‘path’ of any DOM object. This method is extremely fast compared to the multiple DOM commands we usually invoke to handle complex objects. The specifier is returned as a String, so getting the desired information is just a matter of parsing a JS string, which no longer involves the DOM. This improves time performance in loops and functions that browse the hierarchy of complicate objects, sets, or ranges. An obvious use case is, given whatever DOM entity, which document does it belong to? Instead of looking for the parent of parent of (…), just extract the root segment of the specifier and resolve it. Done. The method toSpecifier() —and its counterpart, resolve()—are useful to make your code more agnostic: the path tells everything about a target, you don't have to assume additional facts. I use this approach in various routines of my script IndexMatic, which needs to quickly scan entire documents and classify text contents.

Best,

Marc