Transform a JavaScript Array into a JSON entity

Currently working on a Mail Merge project using Google Apps Script, I've encountered an issue with displaying inline images in the email body. After sending the email using GmailApp.sendEmail(), all inline images are shown as attachments instead of being displayed inline.

I believe that by converting the imgVars array to a JSON object similar to the example provided in the GAS documentation, it might be possible to display inline images:

  MailApp.sendEmail(
    "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c0b3a7eea1b0b0b3b3a3b2a9b0b480a7ada1a9aceea3afad">[email protected]</a>",
    "Logos",
    "", 
    { htmlBody: 
      "inline Google Logo<img src='cid:googleLogo'> images! <br/> inline YouTube Logo   <img src='cid:youTubeLogo'>",
     inlineImages: 
     { googleLogo: googleLogoBlob,
       youTubeLogo: youtTubeLogoBlob
     }
    }
  );

My goal is to convert an array structure like this:

var array = { item1, item2, item3 };

Into a JSON object structured like this:

var json = { item1Name: item1,
             item2Name: item2,
             item3Name: item3
           };

In my Mail Merge script, this snippet focuses on handling inline images:

  //---------------------------------------------------------------
  // If there are inline images in the body of the email
  // Find them and store them in an array, imgVars
  //---------------------------------------------------------------
  if(emailTemplate.search(/<\img/ != -1)) {  
  var inlineImages = {};

  // Extract all images from the email body
  var imgVars = emailTemplate.match(/<img[^>]+>/g);

  // For each image, extract its respective title attribute
  for (i in imgVars) {
    var title = imgVars[i].match(/title="([^\"]+\")/);
    if (title != null) {
    title = title[1].substr(0, title[1].length-1);
    for (j in attachments) {
       if (attachments[j].getName() == title) {
        inlineImages[title] = attachments[j].copyBlob();
        attachments.splice(j,1);
       }
    }
    var newImg = imgVars[i].replace(/src="[^\"]+\"/,"src=\"cid:"+title+"\"");
    emailTemplate = emailTemplate.replace(imgVars[i],newImg);
    }
  }
 }

 objects = getRowsData(dataSheet, dataRange);
 for (var i = 0; i < objects.length; ++i) {   
   var rowData = objects[i];
   if(rowData.emailSent != "EMAIL_SENT") {

     // Replace markers (for instance ${"First Name"}) with the 
     // corresponding value in a row object (for instance rowData.firstName).

     var emailText = fillInTemplateFromObject(emailTemplate, rowData);     
     var emailSubject = fillInTemplateFromObject(selectedTemplate.getSubject(), rowData);

     GmailApp.sendEmail(rowData.emailAddress, emailSubject, emailText,
                      {name: e.parameter.name, 
                       attachments: attachments, 
                       htmlBody: emailText, 
                       cc: cc, 
                       bcc: bcc, 
                       inlineImages: inlineImages});      

Answer №1

Here are some important points to consider:

> var array = { item1, item2, item3 };

This code snippet is incorrect syntactically. An array literal should be declared as follows:

var array = [ item1, item2, item3 ];

[...]

> if (emailTemplate.search(/<\img/ != -1)) {

The backslash before img in the regular expression unnecessary. It's best practice to include a trailing space and enable case insensitivity for better pattern matching in HTML, like this: /<img /i

> var imgVars = emailTemplate.match(/<img[^>]+>/g);

Instead of using regular expressions to parse HTML, it's recommended to convert the HTML into a document fragment for easier manipulation.

>  var imgVars = emailTemplate.match(/<img[^>]+>/g);

Keep in mind that String.prototype.match returns an array.

> for (i in imgVars) {

Avoid using for..in with arrays due to potential issues such as properties being returned in unexpected orders or including properties from Array.prototype modifications by browsers. Stick to a plain for loop instead.

> var title = imgVars[i].match(/title="([^\"]+\")/);

Be cautious when using for..in loops on arrays, especially when accessing properties that may cause errors. Consider using a hasOwnProperty test or switch to a traditional for loop for better handling.

> for (j in attachments) {

If attachments is an array, opt for a standard for loop over for..in to avoid unexpected property visitation order issues. Additionally, don't mix for..in with methods like splice for reliable results since property enumeration behavior can vary between browsers.

Converting HTML to a document fragment simplifies extracting and managing elements. Utilize DOM methods for manipulating img element properties and constructing objects. Native JSON conversion methods can then be employed as needed.

Answer №2

You can achieve a 100% fidelity from a draft or canned response with ease. I have successfully implemented this code snippet in a mail merge feature that now supports inline images (including embedded blobs and external references) as well as attachments:

...
//selectedTemplate is a Gmail Message (draft/canned response)
var emailTemplate = selectedTemplate.getBody();
var attachments = selectedTemplate.getAttachments();
var to = selectedTemplate.getTo();
var cc = selectedTemplate.getCc();
var bcc = Session.getActiveUser().getEmail();

if(emailTemplate.search(/<\img/ != -1)){  
    var inlineImages = {};
    var imgVars = emailTemplate.match(/<img[^>]+>/g);
    for(i in imgVars){
      var title = imgVars[i].match(/title="([^\"]+\")/);
      if (title) {
        title = title[1].substr(0, title[1].length-1);
        var titleEncoded = title.replace(/ /g,"-");
        for(j in attachments){
          if(attachments[j].getName() == title){
            inlineImages[titleEncoded] = attachments[j].copyBlob().setName(titleEncoded);
            attachments.splice(j,1);
          }
        }
        var newImg = imgVars[i].replace(/src="[^\"]+\"/,"src=\"cid:"+titleEncoded+"\"");
        emailTemplate = emailTemplate.replace(imgVars[i],newImg);
      }
    }
  }
...
GmailApp.sendEmail(...,
                  {attachments: attachments, ...,
                   inlineImages: inlineImages});

This has been proven effective with domain users on a daily basis. Feel free to implement this in your workflow.

Answer №3

It's a bit disappointing that the inline images lack a proper title. They are only identified by alt=Inline Image 1, which isn't very informative regarding the attachment name. The img tag only contains alt and src attributes.

Unfortunately, there doesn't appear to be a direct way to associate the inline image with its corresponding attachment, aside from assuming the first inline image corresponds to the first attachment, and so on.

For context, I'm utilizing the Gmail web interface to compose this draft and adding images through the "Inserting Images" lab feature.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to click a button on HTML file

In my current project, there is a piece of code responsible for checking if the user is logged in or not. If the user hasn't logged in yet, they are redirected to the login page. Once the user logs in successfully, they should be able to upload conten ...

Looking for assistance with deleting a child element in an XML file using PHP

I need help figuring out how to delete a child from my jobs.xml file with a PHP script. My jobs.xml file looks like this: <jobs> <event jobid="1"> <title>jobtitle</title> <desc>description</desc> &l ...

Mastering server requests in Angular 5

I have come across recommendations stating that server requests should be made via services and not components in order to ensure reusability of functions by other components. Ultimately, the server response is needed in the component. My query pertains t ...

Following a series of Observables in Angular 2+ in a sequential order

Apologies if this question has been answered elsewhere, I attempted to search for it but I'm not exactly sure what I should be looking for. Imagine I have this complex object: userRequest: { id: number, subject: string, ... orderIds: ...

Time when the client request was initiated

When an event occurs in the client browser, it triggers a log request to the server. My goal is to obtain the most accurate timestamp for the event. However, we've encountered issues with relying on Javascript as some browsers provide inaccurate times ...

What is the correct way to invoke a function from an external JavaScript file using TypeScript?

We are currently experimenting with incorporating Typescript and Webpack into our existing AngularJS project. While I have managed to generate the webpack bundle, we are facing an issue at runtime where the program is unable to locate certain functions in ...

Is it better to use scale.set() or simply increase the size of the model in Three.js?

When it comes to scaling 3D models in Three.js (or any other 3D renderers), what is considered the best practice? Recently, I encountered a situation where I loaded a model only to realize that its size was too small. In order to adjust the size, I used m ...

What is the proper way to delete a callback from a promise object created by $q.defer() in AngularJS?

When working with AngularJS, the $q.defer() promise object has the ability to receive multiple notify callbacks without overwriting previous ones. var def = $q.defer(); def.promise.then(null, null, callback1); def.promise.then(null, null, callback2); If ...

Utilize the v-if directive with nested object properties for dynamic conditional rendering

I need to verify if the item object has a property called 'url' set. If it's not present, I would like to display a placeholder image. Here is an example of what I want to achieve: <img v-if="item.relationships.main ...

What could be causing the HelloWorld program in AngularJS to malfunction?

To access the codes, you can visit http://jsfiddle.net/hh1mye5b/1/ Alternatively, you can refer to: <!doctype html> <html ng-app="dcApp"> <head> </head> <body> <div> <div ng-controller="SpeciesController"> ...

Retrieve data from Postgres by querying for rows where the json column contains more than one array element in the json data

We are currently utilizing a Postgres database where we have a table with a column of type JSON. The format of the JSON data is as follows: { "name" : "XXX", "id" : "123", "course" :[ { "name" : "java", "tutor":"YYYY" }, ...

Encountering a "dependency resolution error" while deploying a React application with Parcel on Heroku

I've developed a compact application and I'm in the process of deploying it to Heroku. However, I keep encountering an error stating: '@emotion/is-prop-valid' dependency cannot be resolved. It's worth mentioning that this project d ...

Instructions for sending a PNG or JPEG image in binary format from a React app to a Node.js server

I am in the process of transferring a file from a react web app to a node.js server. To begin, I have an HTML input of type file where users can upload their files. Once a user uploads a file, a post request is triggered to my Node.js server. Within my N ...

Controlling opacity with jQuery animate() function using a click event

I have a specific requirement for an animation. When the button is clicked, I need the element to transition from 0 opacity to full 1 opacity within 300 milliseconds. The issue I am facing is that when the button is clicked, the animation does not work a ...

Error: 'require' function is not recognized in the latest JavaScript file

I am interested in using anime.js. To install this library, I need to follow these steps: $ npm install animejs --save const anime = require('animejs'); The code should be written in a js file named "anime.js" Here is the code for "anime.js": ...

The Angular module instantiation failed with the error message: "[$injector:modulerr] Failed to

Struggling with setting up basic AngularJS functionality for a project, especially when trying to include angular-route. Both components are version 1.4.8. My approach involves using gulp-require to concatenate my JS files. Here is my main javascript file: ...

Is there a method to instruct crawlers to overlook specific sections of a document?

I understand that there are various methods to control the access of crawlers/spiders to documents such as robots.txt, meta tags, link attributes, etc. However, in my particular case, I am looking to exclude only a specific portion of a document. This por ...

Error: Unable to locate module './clock' in the directory '/home/n/MyWork/my-app/src'

Upon attempting to start up the project, I encountered an error message stating: "Module not found: Can't resolve './clock' in '/home/n/MyWork/my-app/src'" The structure of the project is as follows: My-app --node-modules --public ...

Deploying a node add-on to a server bypassing the use of localhost

Currently, I have developed a node application that runs successfully on my local server. The project consists of an index.html file located in a public directory, along with the main app.js file. By executing the command node app.js in the terminal, the a ...

JavaScript error leading to ERR_ABORTED message showing up in the console

When I try to load my HTML page, my JavaScript code keeps throwing an error in the console. I am attempting to import some JavaScript code into VSCode using an app module for future reuse. The code is being run through a local server. Error Message: GET ...