Copy link to clipboard
Copied
Hi,
I'm not a programmer and I have a basic question about two options, which is the best option?:
- Doing a task in every page
for (loop pages) {
doTask ();
}
function doTask () {
//do something
}- Or, in every page doing the task
doTask();
function doTask () {
for (loop pages) {
//do something
}
}
there is no difference, it depends the task...
Thanks
P.D.:
It's a hugh ammount of pages, the task... add two text frames
Strictly from a programming perspective, there is absolutely no difference. The code gets transformed to the intermediary bytecode format that will be two nested loops (-- actually, that's a big simplification, but enough for the purpose).
In ExtendScript context, however, depending on the actual needs, you have the possibility of a huge optimisation:
```
Page.prototype.doTask=function() {
//do stuff here, the page is under 'this'
}
doc.pages.everyItem().doTask();
```
This would call the `doTask` for al
...Thank you @m1b and @Manan Joshi for your responses.
If I understand, the best way to reuse the function is:
var pages = [## //initial,
### //final];
doTask(pages);
function doTask(pages_param){
for (loop inital to final) {
//do something
}
}
@Vamitul - ks also thank you for your solution but beyond the purpose of my script because I have to filter the pages by applied master spread. I'll keep in mind to next time.
Copy link to clipboard
Copied
hi @nicosh, ,The first way is more flexible, meaning you could repurpose the function to process an arbitrary collection of pages. Performance wise, it may depend on the specifics of your task but probably both would be fine.
- Mark
Copy link to clipboard
Copied
It all depends upon how you envision the function to be used now and in future. Now if this function might be at some point be used with an arbitary collection of pages sourced via any means then the first option is fine. In that option you are not hardcoding the collection to iterate. If you always know that you need to iterate only a certain collection you can hardcode iterating that as in the second option. Or better you could send in the collection to work with as an argument so that you have best of both options.
-Manan
Copy link to clipboard
Copied
Strictly from a programming perspective, there is absolutely no difference. The code gets transformed to the intermediary bytecode format that will be two nested loops (-- actually, that's a big simplification, but enough for the purpose).
In ExtendScript context, however, depending on the actual needs, you have the possibility of a huge optimisation:
```
Page.prototype.doTask=function() {
//do stuff here, the page is under 'this'
}
doc.pages.everyItem().doTask();
```
This would call the `doTask` for all the pages without looping (basically in parallel).
Copy link to clipboard
Copied
I think there is a caveat I had read somewhere about adding properties to InDesign objects in JSX. They might fail in some cases. I am not totally recalling that part. @Vamitul - ks do you happen to know about this? If so please do mention some details on that part as well.
-Manan
Copy link to clipboard
Copied
Ahh, yes, a blast from the past. This was discussed and tested about 15 years ago, or more, but a lot of the information has been lost when the forums have been reorganized.
I'll try to summarize and explain:
First thing to understand is that "native InDesign objects" in ExtendScript are not JavaScript objects, but wrappers for commands (or "object specifiers") to access the internal document DataBase. So, a bit of code like:
`doc.pages.item(5)` is nothing more than a pointer to a POSSIBLE location in the DB. If the document has only 3 pages, the call itself is ok, but the "location" is empty, so trying to resolve it will cause an error ("Object is invalid").
When it comes to extending the prototypes, this means that trying to add a PROPERTY to the native objects is doomed to fail badly because the objects only exist when resolved. METHODS, on the other hand, work just fine because they are instructions on how to manipulate a specific type of object, with the fantastic advantage that those instructions can be applied in a single step, using the collection access (`everyItem()`).
Copy link to clipboard
Copied
Thanks @Vamitul - ks. So i was right in remembering that this was discussed before. I am glad you recalled it and posted it here for future reference. No i know something new. Your idea of using everyItem this way was not something I ever thought before. I will use this somewhere soon
-Manan
Copy link to clipboard
Copied
Hi @Vamitul - ks, this is an absolute revelation! Like Manan, I had also had no success (years and years ago!) adding properties to DOM objects. Adding methods to the prototype just never occurred to me! Mind blown!! 🤯
Copy link to clipboard
Copied
i'm trying to use your solution and i don't know how. This is my little code
myDoc= app.activeDocument;
paragraph.prototype.sameSize = function(){
this.pointSize = 10;
}
myDoc.stories.everyItem().paragraphs.everyItem().sameSize();I'm trying that every paragraph has 10 point size.
'paragraph' is undefined, error 2.
Thanks in advance
Copy link to clipboard
Copied
`Paragraph.prototype.sameSize`: use Capital, because you are extending the class.
When you have time, read at least the initial chapters from https://eloquentjavascript.net/
It's one of the best beginner-friendly JavaScript courses I've ever found. Not everything works the same in ExtendScript (since ExtendScript is an absolutely ancient dialect of JS), but it will help you a lot.
Copy link to clipboard
Copied
@Vamitul - ks thank you, just an uppercase.
I have this one with me, i have to read faster and study more 😉
Copy link to clipboard
Copied
@Vamitul - ks do you have a performative approach to checking each before performing an operation on it?
For example:
Paragraph.prototype.fixBigText = function () {
if (this.pointSize > 12)
this.pointSize = 10;
};
app.activeDocument.stories.everyItem().paragraphs.everyItem().fixBigText();
This doesn't work because `this` is the Paragraphs.everyItem object, not a particular paragraph. How would you approach this example? Or just do it the normal way in this case?
- Mark
Copy link to clipboard
Copied
@m1b Some other collection methods produce similar results - especially itemByRange(), but also item() and itemByName() where the name is not distinct. The result from all of them is still a single (specifier) object, it only specifies multiple native objects. E.g. the toSpecifier() method still yields a string, it might just be more complicated. Also global resolve(theString) should produce the specifier object.
When you access a property getter, you get an array of values across all specified objects, while a setter passes the value to all individual objects. For your fixBigText that means you'd have to move the loop inside your function. The matching expanding operation for "this" is getElements() . The same way the result of method calls also returns an array instead of the original value type.
One big exception: if there is only one element specified – e.g. you have only a single paragraph. That's because the scripting subsystem has no real means to distinguish the operations on a single object - it is all passed as array.
More details to consider - sometimes not all properties are applicable or set, especially if they are causing an error while the same property on other object succeeds. For example, one of your stories above is locked. I know that each individual operation backing the getters etc. can specify individual error handling, but never tried how that works out.
A while ago I mapped the Javascript object model to a strongly typed language (Java) and ended up with a separate sub-type that I called "Plural" for each native classs, to take account of these differences. Actually there were some more of these - when you do a nested everyItem/everyItem exactly as with your fixBigText example, the footprint of the type again differs. There are some other special cases.
For the same reason it also is not trivial to generate Typescript definitions, they are either overly specific and miss the returned array mentioned above, or they use all wildcards defeating their purpose. Same for accepted types (in setters) and many other gotchas. E.g. most of the time item() yields a single result, you have to know when not.
Sorry to the original poster, this is all a mine field of side effects asking for trouble, and far beyond the scope of "I'm not a programmer", but since @Vamitul - ks opened that can of worms … To take the safe route I'd rather recommend to watch out for examples that work with getElements() and traditional loops.
On prototype methods – there is a concept called "shims" - where "modern" javascript developers attempt to emulate features of their preferred javascript dialect by assigning equivalent functions to the prototype. I once spent several days to track down that an "expert" had placed such a hack into a startup script adding to Array.prototype, causing strange behaviour in my own use of arrays (weird entries encountered in "for( key in myArray) …" ). Better refrain from that bad practice. Extending native objects (Paragraph of above etc.) might work for you, but the official way is per plug-in and identifiers used there are allocated / coordinated with Adobe to avoid collisions. No problem if you break only your own code, but since scripts are easily shared and cloned and combined and whatever, better avoid that in general.
Copy link to clipboard
Copied
Thanks for your response @Dirk Becker I follow what you are saying. I might still be tempted to attach my method to the prototype ... except, now I'm having trouble seeing an advantage. At first I thought it would be parellelization as @Vamitul - ks mentioned, but it really isn't any more "parallel" than the everyItem object is normally. Am I wrong? I haven't got time right now to do timing benchmarks, but my guess is it wouldn't be any faster.
(Sorry @nicosh we've taken over your thread! Did you get an answer you could use?)
- Mark
Copy link to clipboard
Copied
@m1b like always: it depends. You have to look at each statement individually. There are other optimizations possible that might result in faster code, or slowdown. Always measure whether your change improves the situation. Hint: $.hiresTimer
For nested loops (your double everyItem on stories and paragraphs) I remember examples where unrolling the outer (stories) loop yielded a heavy improvement. More complicated though because also XML structure was involved.
Scripting languages use a structure called "Script Request" for their operations on the "target" array of native objects.
Getting everyItem().pointSize is one request, it yields the entire result array for the targeted objects. So it is potentiallyy faster.
If you grab the target via getElements() and iterate there, rather than iterate the results with an otherwise similar loop, you fire off one request per target object, so it is some overhead. The internals also allow for optimizations that detect a multi-object target and take shortcuts for that. Rarely used/implemented though, the default implementation is the same loop thru all target objects.
With text you also have to consider side effects of composition. Not for stories and paragraphs, but your targets can severely change if you come e.g. via text frames and lines. You might find edge cases where composition is not complete when you come along with the next request. But before you get there we'd talk semantics, e.g. what is the pointSize of a paragraph or any chunk of text beyond the text style run?
Other optimizations are real. E.g. you can access multiple properties with one call (via the .properties property) and that's easier to do for one target object. I'd rather optimize a script towards that because it is actually used (IDML and friends take advantage of it).
The overhead for a script request is also relatively small for ExtendScript while it is larger for UXP. UXP is working in a separate thread so the script request must also cross back and forth across threads. And so forth …
Copy link to clipboard
Copied
Great info @Dirk Becker and very sensible. And there were some interesting ideas in there for me to explore!
- Mark
Copy link to clipboard
Copied
Thank you @m1b and @Manan Joshi for your responses.
If I understand, the best way to reuse the function is:
var pages = [## //initial,
### //final];
doTask(pages);
function doTask(pages_param){
for (loop inital to final) {
//do something
}
}
@Vamitul - ks also thank you for your solution but beyond the purpose of my script because I have to filter the pages by applied master spread. I'll keep in mind to next time.
Copy link to clipboard
Copied
Yeah this should be perfect. Then you can call it like
doTask(pages)
doTask(app.documents[0].pages)
-Manan
Find more inspiration, events, and resources on the new Adobe Community
Explore Now