In my Sheets projects I often have to write values at the end of the sheet programmatically. I use data validations, some of which don't accept invalid values. My problem is that if a write operation fails, I end up with an incomplete input. I want my operations to either succeed 100% or not at all.
Suppose I have a single-column sheet that only accepts A, B or C values:
First I thought I could validate the input values before writing:
const sheet = ss.getActiveSheet(); // Single-column sheetconst dataRange = sheet.getDataRange(); // Single-column range that only accepts A, B or C values.const writeValues = [["A"], ["B"], ["C"], ["D"]]; // Invalid mock input. D is not valid.const writeRange = sheet.getRange(dataRange.getNumRows() + 1, 1, writeValues.length, writeValues[0].length);writeRange.getDataValidations().forEach((row, i) => row.forEach((validation, j) => { if (validation && !validation.getAllowInvalid()) { // Interrupt if writeValues[i][j] is not valid. }}));writeRange.setValues(writeValues); // Will fail because D is not a valid input value.However this doesn't work, because for some reason writeRange doesn't have any validations. It seems like they get applied only after the range initialization, but before writing.
Then I thought I could just sacrifice some effectiveness with a try/catch block that deletes the newly created rows afterwards if the write operation fails.
const sheet = ss.getActiveSheet(); // Single-column sheetconst dataRange = sheet.getDataRange(); // Single-column range that only accepts "A", "B" or "C" values.const writeValues = [["A"], ["B"], ["C"], ["D"]]; // Invalid mock input. "D" is not valid.const writeRange = sheet.getRange(dataRange.getNumRows() + 1, 1, writeValues.length, writeValues[0].length);try { writeRange.setValues(writeValues);} catch(error) { sheet.deleteRows(writeRange.getRow(), writeValues.length); // Should remove the incompletely written range. throw error;}But it turns out that setValues throws an error that is not catchable! This issue has been submitted 7 years ago and still persists.
Any ideas for a workaround?
EDIT: I can use the first solution if I simply insert the required rows before initializing the range:
const sheet = ss.getActiveSheet();const dataRange = sheet.getDataRange();const firstRow = dataRange.getNumRows() + 1;sheet.insertRowsAfter(dataRange.getNumRows(), writeValues.length);const writeRange = sheet.getRange(firstRow, 1, writeValues.length, writeValues[0].length);writeRange.getDataValidations().forEach((row, i) => row.forEach((rule, j) => { if (rule && !rule.getAllowInvalid()) { const args = rule.getCriteriaValues(); if (args[0] && values[i][j] && !args[0].includes(values[i][j])) { sheet.deleteRows(firstRow, writeValues.length); throw new Error(`"${writeValues[i][j]}"" is invalid.`); } }}));writeRange.setValues(writeValues);
