Skip to main content
Participating Frequently
January 2, 2009
Question

[JS] :: [CSX] - PageItem Stacking Order

  • January 2, 2009
  • 16 replies
  • 10971 views
Hi folks,

Does any one here have any knowledge and or experience on how indesign handles the stacking order of page items.

I have a simple script which can be viewed here:
http://home.exetel.com.au/thunder/img/simpleScript.js

The indesign file I am opening can be downloaded here:
http://home.exetel.com.au/thunder/img/sample%20Folder.zip

I am trying to use the index property of pageItem to determine the proper stacking order--I am trying to sort the page Items from top down.

In This example inDesign reports that the object 'little circle' is underneath 'big Circle' and the index numbers in general do not seem to match how I have placed the objects on top of each other in the document.

Is this the correct way to determine stacking order?
This topic has been closed for replies.

16 replies

Participating Frequently
November 16, 2023

It seems that even the array "allPageItems" is built in the correct z-order, beginning from the top to the bottom, regardless of the object type - just as if the objects are put together as they appear in the Layers panel.
However, this is just an empiric finding -- so:  Could someone of the Adobe folks please confirm this finding as the actual InDesign behaviour or tell us that we just have been lucky so far? Thanks a lot!

Braniac
November 16, 2023

Rudi said: "Could someone of the Adobe folks please confirm this finding as the actual InDesign behaviour or tell us that we just have been lucky so far?"

 

Hi Rudi,

I would ask that at InDesign Prerelease.

Doubt that you'll get a reaction from InDesign engineers here in the forum…

 

Regards,
Uwe Laubender
( Adobe Community Expert )

Participating Frequently
November 17, 2023

Hi Uwe, I'm afraid I've been no longer active in the Prerelease (nor in the Developer) forums for years now for various reasons.
Sadly, there is no property "z-index" or "stacking_order" or so that could give me the appropriate index for each object on the spread directly. However, I perfectly understand that altering the z-index of an object by script would, in turn, make it necessary to re-stack all other items on the respective spread which could have unpredictable side effects.

Anyway: Given that the "allPageItems" array reflects the correct stacking order, then easy workarounds can be used eg to find out the relative position of two objects - i.e. which one is on top of which one - which is the point of stake in my current project.

Cheers, Rudi

Braniac
August 25, 2022

To all seeking answers in this thread:

 

This thread started in January 2009. At that time InDesign CS4 version 6.0 was just out ( October 2008 ).

The answer for Page Item Stacking Order back then was different than it is now.

 

All that changed with InDesign CS5 version 7.0 released in May 2010.

 

Regards,
Uwe Laubender
( Adobe Community Professional )

Braniac
August 24, 2022

Hi @Johan5C42 ,

the allPageItems array reflects the stacking order of elements.

Spread by spread, if the parent of a page item is the spread.

 

Regards,
Uwe Laubender
( Adobe Community Professional )

Braniac
August 24, 2022

Perhaps I should go more into details with the allPageItems array of a specific spread.

In code, select the top most item on the spread:

 

 

var doc = app.documents[0];
var firstSpread = doc.spreads[0];

var firstSpreadAllPageItems = firstSpread.allPageItems;

var stackingOrderOfItems = [];

for( var n=0; n<firstSpreadAllPageItems.length; n++ )
{
	// THIS WAS THE WRONG APPROACH SO IT FAILED TO EXCLUDE THE NESTED ITEMS:
	// if(!firstSpreadAllPageItems[n].parent == firstSpread ){ continue };
	// WHAT IS WORKING (Corrected after tested Johan's sample document below):
	if( firstSpreadAllPageItems[n].parent.constructor.name != "Spread" ){ continue };
	stackingOrderOfItems[stackingOrderOfItems.length++] =
	firstSpreadAllPageItems[n];
};

// Select the top most item on the spread:
stackingOrderOfItems[0].select();

 

 

 

Or the one that is at the bottom:

 

 

stackingOrderOfItems[stackingOrderOfItems.length-1].select();

 

 

 

Regards,
Uwe Laubender
( Adobe Community Professional )

 

NOTE: Message and code edited after @Johan5C42 tested the code not working as expected.

New Participant
August 25, 2022

Thank you Uwe! What a great community this is!

I did a test of your script, and it indeed finds the top and bottom items but if you take a look at all the listed items the order is incorrect.

I have made a brute force script that lists the correct order (reversed from your script, but that can be fixed 😉 )

My test file is attached, with it I get the following results:

Result allPageItems script:
[object Rectangle],[object Group],[object TextFrame],[object Oval],[object Polygon],[object TextFrame],[object Oval],[object TextFrame]

 

Result Brute force script:
[object TextFrame],[object Oval],[object TextFrame],[object Polygon],[object Group],[object Rectangle]

 

Regards

Johan


Brute force method:

 

var myDoc = app.documents[0];
var myPage = myDoc.pages[0];
var arrayToGroup =[];
var myPageItems = myDoc.pageItems.everyItem().getElements();

var decimatedPageItems = myPageItems;
zOrderedItems = [];

for(var mpi=myPageItems.length-1;mpi>=0;mpi--){
    findObjectInTheVeryBack(decimatedPageItems,zOrderedItems);
}

function findObjectInTheVeryBack(pageItems,zOrderedItems){
    //Brute force method
    for (var i  = 0 ; i < pageItems.length; i++) {
        var theItem = pageItems[i];

        theItem.select();

        var menuActionBack = app.menuActions.item("$ID/Send to Back");
        var notInTheVeryBack = menuActionBack.enabled;
        
        if ( notInTheVeryBack == false){
             //remove object from original array and return, add to new array, send to front :O
            zOrderedItems.push(theItem);
            var menuActionFront = app.menuActions.item("$ID/Bring to Front");
            if(menuActionFront.enabled) menuActionFront.invoke();
            pageItems.splice(i,1);
            return(pageItems,zOrderedItems);
        }
    }
};

 

 

Braniac
December 23, 2014

Hi,

What I'm seeing is almost the exact opposite of one of the critical assumptions here! 

If I use this;

var pis = container.pageItems.everyItem().getElements ();    

Based on what you were saying above, I should see an array with a mix of object types in the correct zOrder.

What I am actually seeing is Rectangles, then Polygons, then TextFrames...

This is based on InDesign CC, so I don't know whether something has changed?

However, more pertinently, is there an efficient way for me to derive the .index value reliably? I am seeing times of 5 minutes+ to interrogate the .index value on a list of page items where a designer copy-pasted vector art as a background...

Thanks,
G

New Participant
August 3, 2017

"

var pis = container.pageItems.everyItem().getElements ();  

Based on what you were saying above, I should see an array with a mix of object types in the correct zOrder.

What I am actually seeing is Rectangles, then Polygons, then TextFrames...

"

I have the same problem with Indesign CC (2017). Did you find a solution for this problem?

EDIT: found solution in this comment: https://forums.adobe.com/message/4091126#4091126

New Participant
August 24, 2022

Cheers @samia84304672  ...do you remember this solution? The page of the link is gone... 😕😕 

I am looking for an answer to this too

 

Braniac
January 7, 2009
Harbs.

I knew that arrays process much quicker than collections, but didn't know that .slice(0) sped up things even more -- thanks very much for the pointer. But I can't reproduce that. I did a test similar to yours (a document with 400 pages, a text frame on each page, then tested if each object was a text frame), and it turned out that adding .everyItem().getElements() reduced the script's running time by a factor 20, which is what you found, too, but then adding .slice(0) didn't matter at all (see timings below). Maybe any fractional advantage in processing the array is cancelled by the overhead created by .slice(). So it looks as if Kris is not entirely correct when he says

The '.slice(0)' is there to force a duplication of the array.

If it were omitted, InDesign would merely copy a reference to
theRealm.allPageItems, and we'd be no better off.


Maybe the differences are due to the type of test (.slice(0) may have a distinct advantage on longer/shorter arrays) or type of computer (you can measure the difference, I can't). Interestingly, Kris adds another optimalisation by assigning the array's length to a variable:

var realmItems = theRealm.allPageItems.slice(0);

var realmLength = realmItems.length;
for (var idx = 0; idx < realmLength; idx++)


This has long been known to speed up array-processing, but again its effect turns out to depend on the type of test and computer. The speed tests show that collections benefit, but arrays don't always. On my PC, assigning array length to a variable and using that in the loop doesn't make any difference, but on an older computer that I used previously, which was much slower (0.8 vs 3.2 GHz), it did make a big difference: it halved an 'array-intensive' script's running time.

Anyway, here are some timings. 400-page document, text frame on each page, document has no undo-stack. Timings are highest and lowest of ten runs. Time differences such as 0.14 vs. 0.12 can be considered meaningless.

Peter

3.6-3.7 (seconds)

t = app.activeDocument.textFrames;
for (i = 0; i < t.length; i++)
if (t instanceof TextFrame) {}

0.06-0.11
t = app.activeDocument.textFrames.everyItem().getElements();

0.06-0.14
t = app.activeDocument.textFrames.everyItem().getElements().slice(0);

2.5-2.6
t = app.activeDocument.textFrames;
t_length = t.length;
for (i = 0; i < t_length; i++)
if (t instanceof TextFrame) {}

0.06-0.14
t = app.activeDocument.textFrames.everyItem().getElements();

0.06-0.12
t = app.activeDocument.textFrames.everyItem().getElements().slice(0);


PS: there have been claims that there might be a speed difference between 'x instance of y' and 'x.constructor.name == "y"', but that's not borne out by repeating the above tests using constructor.name.

P.
Harbs.
Braniac
January 7, 2009
Hi Peter,

Yes, you are correct. I was looking at the timing data in the ESTK, and
I forgot to take the overhead of slice(0) into account.

Without doing further testing, it looks like slice(0) doesn't hurt but
it doesn't help either (for arrays created by getElements() ).

--
Harbs
http://www.in-tools.com
Harbs.
Braniac
January 7, 2009
I just realized that I did the tests on CS4. I wonder if CS/CS2/CS3 will
give different results...

--
Harbs
http://www.in-tools.com
_Ben_J_D_Author
Participating Frequently
January 6, 2009
Oh I get it--the pageItems Array is already in the right order and we should use the array's index not the index property ...

Nice one Harbs!
Braniac
January 6, 2009
Hi Harbs,

The rest of us were missing something obvious! You make it look so easy :)
But as a matter of interest, what's the ".slice(0)" doing there at the end of the first line?

Peter
Harbs.
Braniac
January 6, 2009
Peter Kahrel wrote:
> But as a matter of interest, what's the ".slice(0)" doing there at the end of the first line?
>

I'm not sure if it really makes a difference in this case (I've never
bothered to check), but here's an article written by Kris Coppieters
which explains the idea behind it...
http://www.niemannross.com/developer/wiki/index.php?title=Avoiding_slowness_with_object_arrays

and here's one on collections:
http://www.niemannross.com/developer/wiki/index.php?title=Converting_a_collection_into_an_array

FWIW, I use this function to get an array when I deal with a large
number of objects and I don't specifically need a dynamic collection
over an array. I don't need to worry about whether I'm starting out with
an array or a collection and the difference in performance is tremendous:

function AsArray(collection){
if(collection instanceof Array){return collection.slice(0)}
return collection.everyItem().getElements().slice(0);
}

--
Harbs
http://www.in-tools.com
Harbs.
Braniac
January 6, 2009
Peter,

Your question finally prompted me to do some timing tests:

I created 400 text frames for testing purposes.

Accessing pageItems to check if they were text frames took about 20
times as long when they were collections than with
everyItem().getElements(). adding the "slice(0)" cut the time by about a
third. So slice(0) doesn't make a very big difference in this case, but
it does make a difference...

--
Harbs
http://www.in-tools.com
Harbs.
Braniac
January 6, 2009
Hi guys,

Unless I'm missing something obvious, this technique should work for getting the stacking order of objects (without duplicating, etc.):

1)
var items = page.pageItems.everyItem().getElements().slice(0)
(to get an array of all top level page items.)
2) for each item you'd find the index in the items array. Here's a function I use:

function GetItemIndex(array,item){
for(var i=0;i<array.length;i++){
if(array == item){return i}
}
return null;
}

3) then you just compare the indexes.

Harbs
Known Participant
January 6, 2009
Harbs,

the same way we can check order of selected objects in older versions of ID - but this was removed in CS3 and this misled me ;)

robin

--
www.adobescripts.com
_Ben_J_D_Author
Participating Frequently
January 6, 2009
Wow thanks Peter for your optimized/improved script that makes things a lot more elegant and efficient. Thank you Robin for your insight also. In my project I can work with the page Items while they are grouped and ungroup them all at the end. Different layer constructs do not concern me for this project only different pages.

Cheers
Ben
Braniac
January 5, 2009
Robin,

>in VBS - after grouping selected items - order is from top to bottom

That's the case in JS, too. But the moment you ungroup, each object type gets its own range of indexes again. Maybe Ben can do what he wants to do while the objects are grouped.

You're right that layers are another consideration. Anyway, all I wanted to point out to Ben was that what he tried to achieve needn't be that complicated.

Peter
Known Participant
January 5, 2009
Peter,

I know that your solution is faster and simpler ;)
and I know that all objects back to ID-order after ungrouping - but when objects are temp group - order of IDs could be stored in array for later use

robin

--
www.adobescripts.com