skip to Main Content

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

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?

2

Answers


  1. Chosen as BEST ANSWER

    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().

    /**
     * Create the report slides
     * @param {Google} google Google instance
     * @param {Report} report Report instance
     */
    function createReportSlides(google, report, project) {
      let styles = [
        "CUSTOM_1",       // layout 0
        "CUSTOM_1_2",     // layout 1
        "CUSTOM_1_2_1",   // layout 2
        "CUSTOM_1_2_1_1", // layout 3
      ]
      let iCounter = 0; // Item counter
      let curTitle = ""; // Current title
    
      // Uses global variable for list of topics ('title': str, 'items': [])
      TOPICS.forEach((t, i) => {
        let title = t.title;
        let slide;
    
        if (title != curTitle) {
          /**
           * @todo Add topic overview slide. Copy/move the correct slide or add?
           */
          curTitle = title;
          iCounter = 0;
          let objects = {};
        }
    
        t.items.forEach((item) => {
            let summaries = report.getItemSummary(project, title, item);
    
            if (summaries.length > 0) {
              if(iCounter == 0) {
                slide = google.addSlide(google.getLayout(styles[i]));
                objects = slide.getPlaceholders().reduce((o, e) => (
                  // Store reference to placeholders based on Alt Title
                  // Alt Title is Header_x or Body_x or empty
                  o[e.asShape().getParentPlaceholder().getTitle() || "_"] = e, o), {}
                );
              }
    
              objects[`Header_${lCounter}`].asShape().getText().setText(item);
              objects[`Body_${lCounter}`].asShape().getText().setText(summaries.join("v")); // v = vertical tab (same as shift+return)
    
              iCounter = (iCounter+1)%3;
            }
        });
      });
    }
    

  2. 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:

    • 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:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search