EDIT: Final solution has been added as answer but I marked Tanaike’s answer as solution as he pointed me towards the right direction.
I’m setting up a Google AppScript to automate a bit of reporting.
I’m retrieving some data and then want to create slides as needed to report on all the data.
The presentation I’m using as template has different 3-columns lay-outs with subheader placeholders for the column titles and body placeholders for the column text.
Let’s assume the slide layouts are similar to the drawing below.
Using alt text for those fields I was able to somewhat discover the lay-out of the slides.
function getPlaceholders(layout) {
layout.getPlaceholders().forEach((e, i) => {
Logger.log("[%s] %s %s", i, e.getPageElementType(), p.getDescription());
});
}
Will print something like [0.0] SHAPE Header 1
where Column 1 header
is the Alt text I set for that placeholder.
But I’m running into 2 issues:
- The order of the placeholder elements doesn’t follow the visual order of the slide.
On the first slide the order of the headers and body is 2-0-1 - The Lay-outs appear to have some errors by which some fields aren’t recognized as placeholders, even though the slide does show the default placeholder text in those fields.
It appears that copying the fields from a previous slide causes this issue and I need to redo all fields manually for each layout.
The script below will add only 2 slides.
The first slide will have the fields filled in
Header 0-2 | Header 0-0 | Header 0-1 |
---|---|---|
Body 0-2 | Body 0-0 | Body 0-1 |
The 2nd slide doesn’t contain any text in the fields as the scripts seems to fail to discover them.
function test(g) {
let styles = [
"CUSTOM_1",
"CUSTOM_1_2",
"CUSTOM_1_2_1",
"CUSTOM_1_2_1_1"
]
styles.forEach((style, index) => {
let newslide = g.addSlide(g.getLayout(style))
Logger.log("[%s] %s", index, style);
newslide.getPlaceholders().forEach((p, i) => {
// Won't print alt text as those aren't carried from template layouts to actual slides
Logger.log(" [%s] %s %s %s", i, p.getPageElementType(), p.getTitle(), p.getDescription());
});
for (let i = 0; i < 3; i++) {
newslide.getPlaceholder(SlidesApp.PlaceholderType.SUBTITLE, i).asShape().getText().setText(`Header ${index}-${i}`);
newslide.getPlaceholder(SlidesApp.PlaceholderType.BODY, i).asShape().getText().setText(`Body ${index}-${i}`);
}
});
}
This will report e.g.
10:04:39 AM Info [0.0] CUSTOM_1
10:04:39 AM Info [0.0] SHAPE
10:04:39 AM Info [1.0] SHAPE
10:04:39 AM Info [2.0] SHAPE
10:04:39 AM Info [3.0] SHAPE
10:04:39 AM Info [4.0] SHAPE
10:04:39 AM Info [5.0] SHAPE
10:04:39 AM Info [6.0] SHAPE
10:04:40 AM Info [1.0] CUSTOM_1_2
10:04:40 AM Info [0.0] SHAPE
10:04:40 AM Info [1.0] SHAPE
10:04:40 AM Info [2.0] SHAPE
10:04:40 AM Info [3.0] SHAPE
10:04:40 AM Info [4.0] SHAPE
10:04:40 AM Info [5.0] SHAPE
10:04:40 AM Info [6.0] SHAPE
10:04:40 AM Error
TypeError: Cannot read properties of null (reading 'asShape')
(anonymous) @ App.gs:115
test @ App.gs:106
main @ App.gs:132
It looks like the placeholders on the 2nd slide aren’t recognized as being of type SlidesApp.PlaceholderType.SUBTITLE
.
So I modified the for loop as follows:
for (let i = 0; i < 3; i++) {
newslide.getPlaceholders()[2*i].asShape().getText().setText(`Header ${index}-${i}`);
newslide.getPlaceholders()[2*i+1].asShape().getText().setText(`Body ${index}-${i}`);
}
This, however, still doesn’t result in nice slides as the order of the elements still isn’t logical.
Slide 1
Header 0-2 | Header 0-0 | Header 0-1 |
---|---|---|
Body 0-2 | Body 0-0 | Body 0-1 |
Slide 2
Body 1-0 | Header 1-1 | Header 1-0 |
---|---|---|
Header 1-2 | Body 1-2 | Click to add text |
Slide 3
Header 2-1 | Header 2-0 | Body 2-0 |
---|---|---|
Click to add text | Header 2-2 | Body 2-2 |
Slide 4
Header 3-1 | Header 3-0 | Body 3-0 |
---|---|---|
Click to add text | Header 3-2 | Body 3-2 |
What I want to achieve is to be able to programmatically fill these slides in a logical order.
I.e. (in pseudocode)
let index = 0;
let slide;
report.forEach((r) => {
if (index == 0) { slide = addSlide(layout_x) };
slide.placeholder(type=subhead)[index].setText(r.title);
slide.placeholder(type=body)[index].setText(r.content);
index = (index + 1) % 3;
});
TL;DR;
How can I set up my template for the presentation so that I get a consistent and logical order of placeholder elements?
2
Answers
This is a redacted copy of the function I eventually ended up with. Thanks again to @Tanaike for pointing me in the right direction.
I could probably store references to the TextRange objects instead of the Placeholder objects in the
reduce()
function to avoid duplicating.asShape().getText()
.As another approach instead of the title and description, I would like to propose using the object ID of the objects in the layout. When a layout is set to a slide, the object ID of the layout can be seen as the value of parentObjectId. When the objects (for example, their shape.) are retrieved from a layout, the object ID, title, and description can be retrieved. Using them, each object ID corresponding to each title can be obtained. By this, even when the layout is set to a slide, the origins of the inserted objects can be known using the parentObjectId. When this is reflected in a simple sample script, it becomes as follows.
1. Sample layout
Please prepare the following custom layout. And, set the title of each shape as "subtitle1", "body1", "subtitle2", "body2".
2. Set layout
Please set the created custom layout to the 1st page of the Google Slides. It’s as follows.
3. Sample script
In order to correctly insert the texts to each placeholder, the following sample script is used. Please set the custom layout name to
layoutName
. And, please set the title and the inserted text as the key and value, respectively.In this sample, the texts of "sample title 1", "sample body 1", "sample title 2", "sample body 2" are inserted into the placeholders of "subtitle1", "body1", "subtitle2", "body2", respectively.
4. Testing
When this script is run to the above input situation, the following result is obtained.
Note:
This is a simple sample script for explaining my approach. So, please modify this to your actual situation.
In this sample, in order to easily manage, I used the object ID and title of placehoder. Byt, if you have already known the object IDs of all placeholders, I think that you can directly create
object
in the above script. In that case,const newObject = ,,,
is not required to be used.Reference: