1
votes

I have a Google Apps Script WebApp that automates emails for particular events with webhooks.

I have an array of objects which i need to map to the html template file in the same directory.

For example, my Code.gs file looks like this:

function doThing() {
  if ('Some condtition' === true) {
    let template = HtmlService.createTemplateFromFile('my-template')

    let items = [
      { name: 'John Smith', age: 20, address: '123 fakestreet' },
      { name: 'Sam Smith', age: 27, address: '123 fakestreet' },
      { name: 'Phil Smith', age: 10, address: '123 fakestreet' }
    ]

    template.items = items

    MailApp.sendEmail({
      to: '[email protected]',
      subject: 'This is an automated email',
      htmlBody: template.evaluate().getContent()
    })
  }
}

And the my-template.html file looks like this:

<body>
<!-- This is how to call the passed variable -->
    <?= items ?> 

    <!-- I want to map my items like this -->
    <div>
        <div>John Smith</div>
        <div>20</div>
        <div>123 fakestreet</div>
    </div>
    <div>
        <div>Sam Smith</div>
        <div>27</div>
        <div>123 fakestreet</div>
    </div>
    <div>
        <div>Phil Smith</div>
        <div>10</div>
        <div>123 fakestreet</div>
    </div>
</body>

I have tried using script tags in the html file but they don't seem to be being executed, probably because its emailed.

I have also tried to map each item to a string and pass the string to the html like this:

let items = '<div>
        <div>John Smith</div>
        <div>20</div>
        <div>123 fakestreet</div>
    </div>
    <div>
        <div>Sam Smith</div>
        <div>27</div>
        <div>123 fakestreet</div>
    </div>...'

But that just writes the string with the tags.

2
Read examples in the documentation, specifically standard scriplets. See tag info page for more details. - TheMaster
In your situation, is it required to use the HTML template? For example, how about directly using the HTML body like htmlBody: "<body>" + items.reduce((s, e) => (s += "<div>" + Object.entries(e).map(([,f]) => "<div>" + f + "</div>").join("") + "</div>"), "") + "</body>"? - Tanaike

2 Answers

2
votes

You need to loop over items using standard scriplets:

<? for(const item of items){?>
   <div>
        <div><?= item.name ?></div>
        <div><?= item.age ?></div>
        <div><?= item.address ?></div>
    </div>
<? } ?>
1
votes

Scriplets

As per docs, there are exactly three types of scriplets:

  1. Standard - enclosed code is executed with no output
  2. Printing - return value is escaped and outputted
  3. Force-printing - same as printing, but value is not escaped

The difference between printing and force-printing might be easier to grasp visually:

document.querySelector('#escaped').textContent += '<a href="#">this is a link</a>';
<p id="escaped">
  Here lies an escaped output: 
</p>

<p>
  Here lives an unescaped output: 
  <a href="https://stackguides.com/questions/60611359/map-items-to-html-file-with-google-scripts-file">this is a link</a>
<p>

What else to try

Since you probalby use V8 and are likely not dealing with untrusted input (in items) Array, you can take advantage of the newest features (+ I agree that evaluating a template for a simple markup is overkill):

No template

const divify = (text) => `<div>${text}</div>`;

const itemsMarkup = items
  .map(item => {
    const { address, age, name } = item;
    return divify(divify(name) + divify(age) + divify(address));
  })
  .join("");

Notes

  1. === true in conditionals is usually excessive, they will execute the block associated with truthy / falsy result of evaluating the expression (note that strings are also truthy): if('Some condtition').
  2. because its emailed - no, evaluation is performed before sendEmail(), key htmlBody receives an html string resulting from template evaluation and subsequent content extraction.
  3. mid-edit-note: since TheMaster's answer and Tanaike's comment deal with the problem, so I modified the answer to be a supplement explanation.
  4. Do you really need the <body> tag, divs will be rendered just fine (if you are feeling pedantic - wrap them into a <div>)?

Reference

  1. Truthy values in JS
  2. Object initializer reference
  3. Templated HTML guide