0

I created a javascript code which, on the click of a button, sends some data to a Google Spreadsheet. This works.

However, when I want to add a formula formulated within a ARRAYFORMULA() function, this stops working.

The formula isn't that complicated, and is added to $L2 (where there's already a row of data):

=ARRAYFORMULA(IF(ISBLANK($K$2:$K);"";split($K$2:$K;"/")))

And when checking in the project view within script.google.com, the data transfer was succesful. It just isn't loaded into the spreadsheet.

When removing the =ARRAYFORMULA() part of the formula and adding it to the first row on $L2, the scripts work fine and data is added whenever I push the button.

So this works:

=IF(ISBLANK($K$2:$K);"";split($K$2:$K;"/"))

However this requires me to copy the code to new rows, which will be a hassle when more columns require formulas.

As for the Google Script, I've used a (modified version) of Martin Hawksey's script (so that double entries based on column C are ignored: eventually I want double entries to be overwritten but I've not figured that out yet) but using Hawksey's original doesn't make a difference in terms of this issue.

    function doPost(e) {

  try {
    Logger.log(e); // the Google Script version of console.log see: Class Logger
    record_data(e);

    // names and order of form elements (if set)
    var orderParameter = e.parameters.formDataNameOrder;
    var dataOrder;
    if (orderParameter) {
      dataOrder = JSON.parse(orderParameter);
    }

    return ContentService    // return json success results
          .createTextOutput(
            JSON.stringify({"result":"success",
                            "data": JSON.stringify(e.parameters) }))
          .setMimeType(ContentService.MimeType.JSON);
  } catch(error) { // if error return this
    Logger.log(error);
    return ContentService
          .createTextOutput(JSON.stringify({"result":"error", "error": error}))
          .setMimeType(ContentService.MimeType.JSON);
  }
}


/**
 * record_data inserts the data received from the html form submission
 * e is the data received from the POST
 */
function record_data(e) {
  var lock = LockService.getDocumentLock();
  lock.waitLock(30000); // hold off up to 30 sec to avoid concurrent writing

  try {
    Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it

    // select the 'responses' sheet by default
    var doc = SpreadsheetApp.getActiveSpreadsheet();
    var sheetName = e.parameters.formGoogleSheetName || "responses";
    var sheet = doc.getSheetByName(sheetName);

    var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    var newHeader = oldHeader.slice();
    var fieldsFromForm = getDataColumns(e.parameters);
    var row = [new Date()]; // first element in the row should always be a timestamp

    // loop through the header columns
    for (var i = 1; i < oldHeader.length; i++) { // start at 1 to avoid Timestamp column
      var field = oldHeader[i];
      var output = getFieldFromData(field, e.parameters);
      row.push(output);

      // mark as stored by removing from form fields
      var formIndex = fieldsFromForm.indexOf(field);
      if (formIndex > -1) {
        fieldsFromForm.splice(formIndex, 1);
      }
    }

    // set any new fields in our form
    for (var i = 0; i < fieldsFromForm.length; i++) {
      var field = fieldsFromForm[i];
      var output = getFieldFromData(field, e.parameters);
      row.push(output);
      newHeader.push(field);
    }
    var exists = sheet 
      .getDataRange()
      .getValues()
      .map(function(r) {
        Logger.log(r);
        return r[2];
      })
      .indexOf(row[2]) > -1
      //row.push(exists);

      if (!exists) {    
        // more efficient to set values as [][] array than individually
        var nextRow = sheet.getLastRow() + 1; // get next row
        sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);

        // update header row with any new data
        if (newHeader.length > oldHeader.length) {
          sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);
        }
     }
  }
  catch(error) {
    Logger.log(error);
  }
  finally {
    lock.releaseLock();
    return;
  }

}

function getDataColumns(data) {
  return Object.keys(data).filter(function(column) {
    return !(column === 'formDataNameOrder' || column === 'formGoogleSheetName' || column === 'formGoogleSendEmail' || column === 'honeypot');
  });
}

function getFieldFromData(field, data) {
  var values = data[field] || '';
  var output = values.join ? values.join(', ') : values;
  return output;
}

The original code -which doesn't work with ARRAYFORMULA() either-:

function doPost(e) {

  try {
    Logger.log(e); // the Google Script version of console.log see: Class Logger
    record_data(e);

    // names and order of form elements (if set)
    var orderParameter = e.parameters.formDataNameOrder;
    var dataOrder;
    if (orderParameter) {
      dataOrder = JSON.parse(orderParameter);
    }

    return ContentService    // return json success results
          .createTextOutput(
            JSON.stringify({"result":"success",
                            "data": JSON.stringify(e.parameters) }))
          .setMimeType(ContentService.MimeType.JSON);
  } catch(error) { // if error return this
    Logger.log(error);
    return ContentService
          .createTextOutput(JSON.stringify({"result":"error", "error": error}))
          .setMimeType(ContentService.MimeType.JSON);
  }
}


/**
 * record_data inserts the data received from the html form submission
 * e is the data received from the POST
 */
function record_data(e) {
  var lock = LockService.getDocumentLock();
  lock.waitLock(30000); // hold off up to 30 sec to avoid concurrent writing

  try {
    Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it

    // select the 'responses' sheet by default
    var doc = SpreadsheetApp.getActiveSpreadsheet();
    var sheetName = e.parameters.formGoogleSheetName || "responses";
    var sheet = doc.getSheetByName(sheetName);

    var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    var newHeader = oldHeader.slice();
    var fieldsFromForm = getDataColumns(e.parameters);
    var row = [new Date()]; // first element in the row should always be a timestamp

    // loop through the header columns
    for (var i = 1; i < oldHeader.length; i++) { // start at 1 to avoid Timestamp column
      var field = oldHeader[i];
      var output = getFieldFromData(field, e.parameters);
      row.push(output);

      // mark as stored by removing from form fields
      var formIndex = fieldsFromForm.indexOf(field);
      if (formIndex > -1) {
        fieldsFromForm.splice(formIndex, 1);
      }
    }

    // set any new fields in our form
    for (var i = 0; i < fieldsFromForm.length; i++) {
      var field = fieldsFromForm[i];
      var output = getFieldFromData(field, e.parameters);
      row.push(output);
      newHeader.push(field);
    }

    // more efficient to set values as [][] array than individually
    var nextRow = sheet.getLastRow() + 1; // get next row
    sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);

    // update header row with any new data
    if (newHeader.length > oldHeader.length) {
      sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);
    }
  }
  catch(error) {
    Logger.log(error);
  }
  finally {
    lock.releaseLock();
    return;
  }

}

function getDataColumns(data) {
  return Object.keys(data).filter(function(column) {
    return !(column === 'formDataNameOrder' || column === 'formGoogleSheetName' || column === 'formGoogleSendEmail' || column === 'honeypot');
  });
}

function getFieldFromData(field, data) {
  var values = data[field] || '';
  var output = values.join ? values.join(', ') : values;
  return output;
}

Link to the Google Spreadsheet I've used to test this. I've added two sheets: one with the ARRAYFORMULA() and one without (just the regular formula), so you can test with the same data I've got. Also, the script seems to automatically add a date, is there a way to remove this? I don't need a date.

The authentication should be set up correctly (since everything else seems to work) and as I've said, the data seems to be succesfully transferred to Google: just not into the actual rows. I can't see anything wrong in the console, but I must admit I'm not that fluent in google-coding, haha.

The javascript code used:

    function sendData(e) {
    var url = 'https://script.google.com/macros/s/[url]/exec';
    var xhr = new XMLHttpRequest();
    xhr.open('POST', url);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            console.log('done?');
        }
    };
// load variables into data array
    var data = {'Naam': naam, 'Ras': ras, 'Geslacht': geslacht, 'Dressuur': dressuur, 'Springen': springen, 'Western': western, 'Mennen': mennen, 'GI': gi};
    var encoded = Object.keys(data).map(function(k) {
        return encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
    }).join('&');
    xhr.send(encoded);
}

So, to sum up my question:
How to incorporate formulas formulated with =ARRAYFORMULA() into a sheet which automatically adds new rows when clicked on a website-button?

Lisa
  • 897
  • 9
  • 27
  • If you're using `getLastRow()` somewhere that could be the problem because the array formula can mess up that calculation. Look at the very bottom of the spreadsheet to see if anything is there. – Cooper May 26 '20 at 22:54
  • You may want to just calculate a column height. – Cooper May 26 '20 at 22:55
  • @Cooper Yes in the code.gs this code is used on row 87 (var nextRow = sheet.getLastRow() + 1; // get next row). getLastColumn as well, on row 50 (var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];). How would I adjust those so that I can use the Array Formula? – Lisa May 26 '20 at 23:01
  • 1
    For setting formulas use [setFormula()](https://developers.google.com/apps-script/reference/spreadsheet/range#setformulaformula) (or `setFormulas()`) instead of `setValue()` or `setValues()`. – ziganotschka May 27 '20 at 08:43
  • @TheMaster That seems to solve the issue! Is it just as simple as replacing each instance of $K2:$K with K2:INDEX(K2:K,COUNTA(K2:K)) ? Do I need to use the $ anymore, or is that not needed? – Lisa May 27 '20 at 11:16
  • @ziganotschka I've replaced those, thanks! – Lisa May 27 '20 at 11:18
  • Btw, sometimes when I change something the values that are loaded into the spreadsheet include an '=' which gives a whole bunch of errors. So an = is added to the name (see current version of google sheets document). When reverting back the change, this doesn't get fixed. Is this some kind of a bug within sheets? The exact same code on a different spreadsheet with the same specs gives different results. When I don't have this issue it still displays a 'false' in the last row even though the 'push' code is commented out or removed. Google is really confusing. – Lisa May 27 '20 at 12:14
  • $ is not needed – TheMaster May 27 '20 at 14:51
  • @TheMaster Does this solution (or that of Ziganotschka) has anything to do with the pushed 'false' even though the code that pushes it is removed, or the suddenly added '='s in front of the data (which make de data seem as formulas to sheets resulting in a lot of parsing errors)? Or is that caused by anything else? (I left the data in the sheet if that's helpful) – Lisa May 27 '20 at 18:04
  • @Lisa Seems like a sufficiently different error to warrant a different question. Preferably, Ask a new question focusing on the issue. `setFormulas` shouldn't be used to setValues – TheMaster May 28 '20 at 07:12
  • @TheMaster thanks, will do! Wasn’t sure it had anything to do with these solutions. Can you post your solution as an answer? Then I can accept it! – Lisa May 28 '20 at 11:17
  • @Lisa If the duplicate answered your question, accept it as a duplicate. There's nothing more that I'd like to add to that and I can't just copy paste the same answer. If you think there's extra info that can help others, You can add it as a solution here. If not, accept the question as a duplicate – TheMaster May 28 '20 at 11:37

0 Answers0