javascriptgoogle-apps-scriptgoogle-appsgoogle-slidesgoogle-slides-api

How to order placeholder elements in Google Slides for usage in Google Apps Script


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. drawing of slide layout

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.

Subtitle placeholderAlt text

But I'm running into 2 issues:

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.

Example drawing

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

Example drawing

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?


Solution

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

    enter image description here

    2. Set layout

    Please set the created custom layout to the 1st page of the Google Slides. It's as follows.

    enter image description here

    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.

    function myFunction() {
      // Please set your custom layout name.
      const layoutName = "CUSTOM";
    
      // Please set the title and the inserted text as the key and value, respectively.
      const object = {
        subtitle1: "sample title 1",
        body1: "sample body 1",
        subtitle2: "sample title 2",
        body2: "sample body 2",
      };
    
      // Create an object including the parent object ID and the inserting text.
      const s = SlidesApp.getActivePresentation();
      const layout = s.getLayouts().find(e => e.getLayoutName() == layoutName);
      const newObject = layout.getPageElements().reduce((o, e) => (o[e.getObjectId()] = object[e.getTitle()] || "", o), {});
    
      // Insert the texts to each object of the placeholder.
      const slide = s.getSlides()[0]; // As a sample, 1st page is used.
      slide.getShapes().forEach(s => {
        const parentObjectId = s.getParentPlaceholder().getObjectId();
        if (newObject[parentObjectId]) {
          s.getText().setText(newObject[parentObjectId]);
        }
      });
    }
    

    4. Testing

    When this script is run to the above input situation, the following result is obtained.

    enter image description here

    Note:

    Reference: