Error message "Header not defined" in Apps Script - google-apps-script

I am trying to pull data from google sheet into google form to create a dropdown list in the google form. the Script that I copied from the internet is throwing an error with the message "Header is not defined". Can someone please tell me what changes to make in the script to make this script work? the debugger says error is in line 6.
function getDataFromGoogleSheets() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("CompanyName");
const data = sheet.getDataRange().getDisplayValue();
const choices = {}
header.forEach(function(title,index){
choices[title] = data.map(row=>row [index]).filter(e=>e!=="");
});
return choices;
}
function populateGoogleForms(){
const GOOGLE_FORM_ID = "1H80nNJXb3hekZp7CZOZ0FGNpEwCiQnpHL17y3w8WSNk";
const googleForm = FormApp.openById(GOOGLE_FORM_ID);
const items = googleForm.getItems();
const choices = getDataFromGoogleSheets();
items.forEach(function(item){
const itemTitle = item.getTitle();
if(itemTitle in choices) {
const itemType = item.getType();
switch (itemType){
case FormApp.ItemType.LIST:
item.asListItem().setChoiceValues(choices[itemTitle]);
break;
default:
Logger.log("ignore question", itemTitle)
}
}
});
}

function getDataFromGoogleSheets() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("CompanyName");
const data = sheet.getDataRange().getDisplayValue();//this should be getDisplayValues();
const choices = {}
header.forEach(function(title,index){//you have not defined header
choices[title] = data.map(row=>row [index]).filter(e=>e!=="");
});
return choices;
}
I might guess that it could be as simple as this
const [header, ... data] = sheet.getDataRange().getDisplayValues();
But I'd have to know more about your spreadsheet to be sure.

Related

Search function data entry form

With some help from here i've made a data form, whereby data is put from an input sheet to a data sheet and then the input sheet is emptied again.
Now I want to be able to find the data back in the input sheet when I put the right code.
How do I do this?
This is the script to put data from the input sheet towards the data sheet.
function Process() {
const ss = SpreadsheetApp.getActive();
const dataSheet = ss.getSheetByName('NIEUW Invoerbestand');
const inputSheet =ss.getSheetByName('Input')
const idCell = ss.getRangeByName('Barcode');
const id = idCell.getValue();
const cellFound = dataSheet.getRange('A:A')
.createTextFinder(id)
.matchCase(true)
.matchEntireCell(true)
.findNext()
if (!cellFound) return;
const fieldRange = inputSheet.getRange('Input!E4:E231');
const fieldValues = [fieldRange.getValues().flat()];
dataSheet
.getRange(cellFound.getRow(), 6)
.offset(0, 0, fieldValues.length, fieldValues[0].length)
.setValues(fieldValues);
fieldRange.clearContent();
idCell.clearContent();
}
I've tried this script, but this gives the error 'forEach is not a function'
const searchCell = inputSheet.getRange ("B2")
const searchValue = searchCell.getValue()
const data = dataSheet.getRange ("A4:HY").getValues()
const recordsFound = data.filter( r => r[0] == searchValue)
if(recordsFound.lenght === 0) return
fieldRange.forEach ((f,i) => inputSheet.getRange(f).setValue(recordsFound[0][i+6]))
}

Is there an alternative to appendRow in AppsScript that only prints certain columns while leaving others in the row untouched

I'm building a calculator to use for pricing purposes. It has a primary "Calculator" sheet, where an admin can enter data and then generate a new result to the "DataLog" sheet. The "DataLog" sheet stores the results (columns A through X) and calculates the resulting price (columns Y through AO). There are also a few workflow columns that need to be present for each row (Columns AP through AS).
I am currently using appendRow() to print the data to the "DataLog" sheet. The issue is that appendRow() finds the first empty row, and since columns Y through AS are not empty because they contain necessary formulas/workflow, it prints to the bottom of the sheet. I am looking for a way to print the data where 1) it checks only a certain column for an empty row (column A or C, for example) and prints to that row, and 2) does not overwrite the formula/workflow columns (Y through AS).
Is there a way to do this using appendRow() or is there another function I should be using? Other than this one issue of where to print the results, everything works just as I want it to, but I cannot seem to find a way to resolve this issue.
EDIT: The reason the formula and workflow must be present within "DataLog" is that there are situations where after an entry has been filled out and printed changes need to be made to row, thereby changing the final price. So I cannot calculate the price within the function and print that as a static number.
Here is a copy of the calculator: https://docs.google.com/spreadsheets/d/1vsVZeOUUqhdiW1unz6dPuiP5yw24ENrv1-49kXqBnx4/edit#gid=0
Here is a copy of the code I am using:
function ClearCells() {
var sheet = SpreadsheetApp.getActive().getSheetByName('CALCULATOR');
sheet.getRange('G9:H9').clearContent();
sheet.getRange('G11').clearContent();
sheet.getRange('G14:H14').clearContent();
sheet.getRange('G6').clearContent();
sheet.getRange('I6').clearContent();
sheet.getRange('I17:I21').clearContent();
sheet.getRange('I24:I29').clearContent();
sheet.getRange('I32').clearContent();
sheet.getRange('K5').clearContent();
sheet.getRange('K15').clearContent();
}
function FinalizePrice() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sourceRangeFL = ss.getRangeByName('FirstLast');
const sourceValsFL = sourceRangeFL.getValues().flat();
const sourceRangeEN = ss.getRangeByName('EntityName');
const sourceValsEN = sourceRangeEN.getValues().flat();
const sourceRangeEP = ss.getRangeByName('EmailPhone');
const sourceValsEP = sourceRangeEP.getValues().flat();
const sourceRangeRT = ss.getRangeByName('ReturnType');
const sourceValsRT = sourceRangeRT.getValues().flat();
const sourceRangeRE = ss.getRangeByName('Returning');
const sourceValsRE = sourceRangeRE.getValues().flat();
const sourceRangeBQ = ss.getRangeByName('BasicQuestions');
const sourceValsBQ = sourceRangeBQ.getValues().flat();
const sourceRangeSEQ = ss.getRangeByName('SchEQuestions');
const sourceValsSEQ = sourceRangeSEQ.getValues().flat();
const sourceRangeEQ = ss.getRangeByName('EntityQuestions');
const sourceValsEQ = sourceRangeEQ.getValues().flat();
const sourceRangePYP = ss.getRangeByName('PYP');
const sourceValsPYP = sourceRangePYP.getValues().flat();
const sourceRangeADJ = ss.getRangeByName('Adjustment')
const sourceValsADJ = sourceRangeADJ.getValues().flat();
const sourceRangeAN = ss.getRangeByName('AdjustmentNote')
const sourceValsAN = sourceRangeAN.getValues().flat();
const sourceVals = [...sourceValsFL, ...sourceValsEN, ...sourceValsEP, ...sourceValsRT, ...sourceValsRE, ...sourceValsBQ, ...sourceValsSEQ, ...sourceValsEQ, ...sourceValsPYP, ...sourceValsADJ, ...sourceValsAN]
console.log(sourceVals)
const anyEmptyCell = sourceVals.findIndex(cell => cell === "");
if(anyEmptyCell !== -1){
const ui = SpreadsheetApp.getUi();
ui.alert(
"Input Incomplete",
"Please enter a value in ALL input cells before submitting",
ui.ButtonSet.OK
);
return;
}
const date = new Date();
const email = Session.getActiveUser().getEmail();
const data = [date, email, ...sourceVals];
const destinationSheet = ss.getSheetByName("DataLog");
destinationSheet.appendRow(data);
console.log(data);
sourceRangeFL.clearContent();
sourceRangeEN.clearContent();
sourceRangeEP.clearContent();
sourceRangeRT.clearContent();
sourceRangeRE.clearContent();
sourceRangeBQ.clearContent();
sourceRangeSEQ.clearContent();
sourceRangeEQ.clearContent();
sourceRangePYP.clearContent();
sourceRangeADJ.clearContent();
sourceRangeAN.clearContent();
ss.toast("Success: Item added to the Data Log!");
}
I know this is incomplete but for the purpose of discussion here's how I would clear content in your situation.
function ClearCells() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]).getRanges().forEach(r => r.clearContent();)
}
If you wished to append the values of your individual ranges into a row you could do it like this:
function appendRangeValues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1')
const rgl = sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]);
const rglb = breakUpRangeList(ss,sh,rgl);
const vs = rglb.getRanges().map(r => r.getValue());
Logger.log(JSON.stringify(vs))
osh.getRange(osh.getLastRow() + 1, 1, 1, vs.length).setValues([vs]);
}
But I'm guessing that you want to skip over cell functions and other columns so let me know what you want and may be we can find a solution that fits your needs
The breakUpRangeList function is something I wrote a while back to break up ranges into their individual cells which I find easier to deal with.
function breakUpRangeList(ss=SpreadsheetApp.getActive(),sh=ss.getSheetByName("Sheet0"),rgl) {
let b = [];
rgl.getRanges().forEach(rg => {
rg.getValues().forEach((r,i) => {
let row = rg.getRow() + i;
r.forEach((c, j) => {
let col = rg.getColumn() + j;
b.push(sh.getRange(row, col).getA1Notation())
})
})
})
b = [...new Set(b)];
Logger.log(JSON.stringify(b));
return sh.getRangeList(b);
}
Try this:
function appendRangeValues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1')
const rgl = sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]);
const rglb = breakUpRangeList(ss,sh,rgl);
const vs = rglb.getRanges().map(r => r.getValue());
Logger.log(JSON.stringify(vs))
osh.getRange(getColumnHeight(3,osh,ss) + 1, 1, 1, vs.length).setValues([vs]);
}
function getColumnHeight(col, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var col = col || sh.getActiveCell().getColumn();
var rcA = [];
if (sh.getLastRow()){ rcA = sh.getRange(1, col, sh.getLastRow(), 1).getValues().flat().reverse(); }
let s = 0;
for (let i = 0; i < rcA.length; i++) {
if (rcA[i].toString().length == 0) {
s++;
} else {
break;
}
}
return rcA.length - s;
}

Trying to automate a Google Slides presentation from Sheets using App Script. Experiencing an error and inconsistent results when I run the script

I am not a developer in any way, just trying to use a simple code to automate a presentation In Google Slides. I am pasting the code and error I am experiencing below!
function fillTemplate() {
var Presentation_ID="1lManUPdT6g5t4q0kanfy1wWwX6wF9bB9kRNn9VVsvPQ";
var Presentation=SlidesApp.openById(Presentation_ID);
var values= SpreadsheetApp.getActive().getDataRange().getValues();
values.forEach(function(row){
var templateVariable= row[0];
var templateValue= row[2];
Presentation.replaceAllText(templateVariable, templateValue);
});
}
Error
Exception: The match text should not be empty.
(anonymous) # Code.gs:10
fillTemplate # Code.gs:7
This should prevent the error
function fillTemplate() {
const Presentation = SlidesApp.openById("id");
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Your Sheet Name");
var vs = sh.getDataRange().getValues();
vs.forEach((r, i) => {
if (r[2] && r[0]) {
Presentation.replaceAllText(r[0], r[2]);
} else {
Logger.log(`Error on iteration ${i}`)
}
});
}

App Script: What's the shorter way to get the range of multiple cells

I am trying to copy the data from the Source sheet to my Target sheet. Is there a version where I can shorten this code? It seems to be that the .getRange can only accept one string.
When I try this line of code const sRange = sSheetDay.getRange('I4', '15', 'I6'); and so on, it always says it can't find the range. I know my questioning is not that good, especially I'm really a beginner at this. Hopefully someone has the same experience.
My main goal to this is to copy the data from a specific cell. And I can't seem to find the solution.
function copyDataFilters() {
const dTable = SpreadsheetApp.getActiveSpreadsheet();
const sSheetDay = dTable.getSheetByName('Day 1');
const sRange = sSheetDay.getRange('I4');
const sRange2 = sSheetDay.getRange('I5');
const sRange3 = sSheetDay.getRange('I6');
const sRange4 = sSheetDay.getRange('I7');
const sRange5 = sSheetDay.getRange('I8');
const sRange6 = sSheetDay.getRange('I9');
const sRange7 = sSheetDay.getRange('I12');
const sRange8 = sSheetDay.getRange('I13');
const sRange9 = sSheetDay.getRange('I16');
const sRange10 = sSheetDay.getRange('I17');
const sRange11 = sSheetDay.getRange('I20');
const sRange12 = sSheetDay.getRange('I21');
const sRange13 = sSheetDay.getRange('I24');
const sRange14 = sSheetDay.getRange('I25');
const sRange15 = sSheetDay.getRange('I27');
const sValue = sRange.getValues();
const sValue2 = sRange2.getValues();
const sValue3 = sRange3.getValues();
const sValue4 = sRange4.getValues();
const sValue5 = sRange5.getValues();
const sValue6 = sRange6.getValues();
const sValue7 = sRange7.getValues();
const sValue8 = sRange8.getValues();
const sValue9 = sRange9.getValues();
const sValue10 = sRange10.getValues();
const sValue11 = sRange11.getValues();
const sValue12 = sRange12.getValues();
const sValue13 = sRange13.getValues();
const sValue14 = sRange14.getValues();
const sValue15 = sRange15.getValues();
const targetSheet = dTable.getSheetByName('All filters report');
const targetRange = targetSheet.getRange('B4:C4');
const targetRange2 = targetSheet.getRange('B5:C5');
const targetRange3 = targetSheet.getRange('B6:C6');
const targetRange4 = targetSheet.getRange('B7:C7');
const targetRange5 = targetSheet.getRange('B8:C8');
const targetRange6 = targetSheet.getRange('B9:C9');
const targetRange7 = targetSheet.getRange('B11:C11');
const targetRange8 = targetSheet.getRange('B12:C12');
const targetRange9 = targetSheet.getRange('B14:C14');
const targetRange10 = targetSheet.getRange('B15:C15');
const targetRange11 = targetSheet.getRange('B17:C17');
const targetRange12 = targetSheet.getRange('B18:C18');
const targetRange13 = targetSheet.getRange('B20:C20');
const targetRange14 = targetSheet.getRange('B21:C21');
const targetRange315 = targetSheet.getRange('B24:C24');
targetRange.setValue(sValue);
targetRange2.setValue(sValue2);
targetRange3.setValue(sValue3);
targetRange4.setValue(sValue4);
targetRange5.setValue(sValue5);
targetRange6.setValue(sValue6);
targetRange7.setValue(sValue7);
targetRange8.setValue(sValue8);
targetRange9.setValue(sValue9);
targetRange10.setValue(sValue10);
targetRange11.setValue(sValue11);
targetRange12.setValue(sValue12);
targetRange13.setValue(sValue13);
targetRange14.setValue(sValue14);
targetRange315.setValue(sValue15);
}
Use getRangeList
From the references
// Get a list of ranges A1:D4, F1:H4.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rangeList = sheet.getRangeList(['A1:D4', 'F1:H4']);
References
https://developers.google.com/apps-script/reference/spreadsheet/sheet?hl=en#getrangelista1notations
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet?hl=en#getrangelista1notations
function elfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
//you can do this with values
const [v4,v5,v6,v7,v8,v9,,,v12,v13,,,v16,v17,v18,v19,v20,v21,,,v24,v25,v26,v27] = sh.getRange("I4:I27").getValues();
Logger.log(v4);
//unfortunatey you cannot do this with ranges. It resullts in an error
const [I4,I5,I6,I7,I8,I9,,,I12,I13,,,I16,I17,I18,I19,I20,I21,,,I24,I25,I26,I27] = sh.getRange("I4:I27");
Logger.log(I4.getValue());
}
So were stuck with handling multiple ranges with a rangelist as shown Ruben's answer
In the current stage, when the different values are trying to be got from and put to the scattered cells, the Spreadsheet service (SpreadsheetApp) has no methods for directly achieving this goal. So, in order to directly achieve this goal, as the current workaround, Sheets API can be used. When this is reflected in a sample script by including the goal of your script, it becomes as follows.
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
function copyDataFilters2() {
const srcSheet = "Day 1";
const dstSheet = "All filters report";
const src = ["I4", "I5", "I6", "I7", "I8", "I9", "I12", "I13", "I16", "I17", "I20", "I21", "I24", "I25", "I27"].map(e => `'${srcSheet}'!${e}`);
const dst = ["B4:C4", "B5:C5", "B6:C6", "B7:C7", "B8:C8", "B9:C9", "B11:C11", "B12:C12", "B14:C14", "B15:C15", "B17:C17", "B18:C18", "B20:C20", "B21:C21", "B24:C24"].map(e => `'${dstSheet}'!${e}`);
const ssId = SpreadsheetApp.getActiveSpreadsheet().getId();
const values = Sheets.Spreadsheets.Values.batchGet(ssId, { ranges: src }).valueRanges.map(({ values }) => values ? [Array(2).fill(values[0][0])] : [[""]]);
const data = values.map((e, i) => ({ values: e, range: dst[i] }));
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ssId);
}
When this script is run, the values are retrieved from the cells "I4", "I5", "I6", "I7", "I8", "I9", "I12", "I13", "I16", "I17", "I20", "I21", "I24", "I25", "I27" of "Day 1" sheet, and the retrieved values are put to the cells of "B4:C4", "B5:C5", "B6:C6", "B7:C7", "B8:C8", "B9:C9", "B11:C11", "B12:C12", "B14:C14", "B15:C15", "B17:C17", "B18:C18", "B20:C20", "B21:C21", "B24:C24".
References:
map()
Method: spreadsheets.values.batchGet
Method: spreadsheets.values.batchUpdate

search if value exist in another sheet and display an alert google appscript

I have the following if condition that checks if the a value entered in a form exists in another sheet and if thats the case it will display an alert to the user, but i also wanted to check if the value was found in another sheet that the cell next to it is not empty
var valueToSearch2 = formS.getRange("B5").getValue();
var logSValues = logS.getDataRange().getValues();
if(logSValues.filter(row => row.includes(valueToSearch2)).length)
{
SpreadsheetApp.getUi().alert("Stop Processing");
return
}
Try this:
function checkTheEntireSheet() {
const ss = SpreadsheetApp.getActive();
const formS = ss.getSheetByName('Sheet0');
const vts = formS.getRange("B5").getValue();
const logS = ss.getSheetByName('Sheet1');
let tf = logS.createTextFinder(vts).findAll();
if (tf.length > 0) {
SpreadsheetApp.getUi().alert("Stop Processing");
}
}
const valueToSearch2 = formS.getRange("B5").getValue();
const logSValues = logS.getDataRange().getValues();
const found = logSValues.some(row => {
const foundIndex = row.findIndex(cell=>cell==valueToSearch2)
if(foundIndex<0){
return false // search term not found
}
const emptyRight = !row[foundIndex+1]
const emptyLeft = foundIndex>0 && !row[foundIndex-1]
// additional condition, must be also empty either to the right or to the left
return emptyLeft || emptyRight
})
if(found){
SpreadsheetApp.getUi().alert("Stop Processing");
return
}