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

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
}

Related

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;
}

Google Apps Script - How to Update Code to Get Unique Values From Column And Filter Based On Them Instead of Manually Inputting Them

I currently have some code (pasted below) that I use in order to take subsets of a dataset and paste them in separate tabs in the same Google Sheets file. I'm currently manually inputting the values with which I filter the dataset in a list and then looping though each value in the list. I would like to update the code to look through the column and pick up on the unique values in the column and turn the unique values into a list that I would then look through using the rest of the code. I'm having trouble figuring out how to do this though.
Here is a link to the image of my dataset:
enter image description here
Below is my code. I would really like to update the const list = "" part to not be manually inputted anymore but to grab the unique values from the Product Type column (column # 4).
function getSubsetDataComplaints() {
const shName = "RawData";
const list = ["Toy Cars", "Barbie Dolls", "Videogames", "Role Playing Games","Trading Card Games","Food"];
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [headers, ...values] = ss.getSheetByName(shName).getDataRange().getValues()
list.forEach(elem => {
const result = [headers, ...values.filter(r => r[3].includes(elem))]
const sheet = ss.insertSheet(elem);
sheet.getRange(1,1, result.length, result[0].length).setValues(result);
})
}
Try
const shName = "RawData";
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [headers, ...values] = ss.getSheetByName(shName).getDataRange().getValues()
const list = values.map(r => r[3]).flat().filter(onlyUnique).sort()
and add this function
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
your complete code
function getSubsetDataComplaints() {
const shName = "RawData";
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [headers, ...values] = ss.getSheetByName(shName).getDataRange().getValues()
const list = values.map(r => r[3]).flat().filter(onlyUnique).sort()
list.forEach(elem => {
try {
if (elem != '') {
const result = [headers, ...values.filter(r => r[3].includes(elem))]
const sheet = ss.insertSheet(elem);
sheet.getRange(1, 1, result.length, result[0].length).setValues(result);
}
} catch (e) {
Browser.msgBox(e)
}
})
}
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}

Error message "Header not defined" in 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.

How can I check if a numerical value is within a range of cells in google sheets?

I would like to find if a certain value is in a range using app scripts for google sheets.
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getDataRange("A5:A5000");
var values = rangeBikeNumbers.getValues();
If I have my range rangeBikeNumbers, how can I check if the number "42" for example is in that range. I have searched for hours now and have beeb unable to find any answer to this. indexOf only seems to return -1, regardless of whether or not the value is in the range.
var indexDataNumber = values.indexOf(42); for example always ends up being -1
I believe your goal as follows.
You want to check whether the value of 42 is existing in the range of A5:A5000.
In this case, I would like to propose to use TextFinder. Because when TexiFinder is used, the process cost is low. Ref By the way, getDataRange has not arguments. From your script, I thought that you might want var rangeBikeNumbers = sheet.getRange("A5:A5000");.
When this is reflected to your script, it becomes as follows.
Modified script:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getRange("A5:A5000");
var find = rangeBikeNumbers.createTextFinder("42").matchEntireCell(true).findNext();
if (find) {
// In this case, the value of 42 is existing in the range.
} else {
// In this case, the value of 42 is NOT existing in the range.
}
}
Note:
About var indexDataNumber = values.indexOf(42); for example always ends up being -1, I think that the reason of this issue is due to that values is 2 dimensional array. If you want to use this, you can also use the following script.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rangeBikeNumbers = sheet.getRange("A5:A5000");
var values = rangeBikeNumbers.getValues();
var find = values.map(([e]) => e).indexOf(42); // of values.flat().indexOf(42);
if (find > -1) {
// In this case, the value of 42 is existing in the range.
} else {
// In this case, the value of 42 is NOT existing in the range.
}
}
References:
Benchmark: Process Costs for Searching Values in Spreadsheet using Google Apps Script
getDataRange()
getRange(a1Notation)
createTextFinder(findText)
Select any active range that you wish to search and it will search for the seed in that at range. The seed is currently defaulted to 42 but you can change it.
function findSeedInRange(seed = 42) {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getActiveRange();
const row = rg.getRow();
const col = rg.getColumn();
var found = false;
rg.getValues().forEach((r, i) => {
r.forEach((c, j) => {
if (c == seed) {
let r = sh.getRange(i + row, j + col).getA1Notation();
ui.alert(`Found ${seed} in ${r}`);
found = true;
}
})
})
if(!found) {
ui.alert(`Did not find ${seed}`);
} else {
ui.alert('That is all.')
}
}
Here's another approach:
function findSeedInRange() {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = sh.getActiveRange();
const resp = ui.prompt('Enter Seed', 'Enter Seed', ui.ButtonSet.OK_CANCEL)
if (resp.getSelectedButton() == ui.Button.OK) {
var seed = parseInt(resp.getResponseText());
const row = rg.getRow();
const col = rg.getColumn();
var found = false;
rg.getValues().forEach((r, i) => {
r.forEach((c, j) => {
if (c == seed) {
let r = sh.getRange(i + row, j + col).getA1Notation();
ui.alert(`Found ${seed} in ${r}`);
found = true;
}
});
});
if (!found) {
ui.alert(`Did not find ${seed}`);
} else {
ui.alert('That is all.')
}
} else {
ui.alert('Operation cancelled.')
}
}

Change range in ConditionalFormatRuleBuilder

Goal: Change range (sheetname, not the a1Notation) inside a ConditionalFormatRuleBuilder.copy()
Error: Conditional format rule cannot reference a different sheet.
I am trying to use the copy method thats is (not so) explained. With the copy i know i have al the arguments for the new conditional formatting i need. Only thing i need to change is the sheetname. Add ranges is working fine, but change/clear the ranges i can't seem to figure out. I found a post, but i want it to make more generic. This example is fine if you know the conditions to work with.
In the docs there is also a .build() is that a option i need in implement?
MainFunction:
function copyFormattingToTargets() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
//const input = SpreadsheetApp.getUi().prompt("Copy formatting from:").getResponseText();
const input = 'Data';
const targets = ['Log','Test'];
const templateSheet = ss.getSheetByName(input);
const inputRules = templateSheet.getConditionalFormatRules();
const rules = convertRules(inputRules,targets);
targets.forEach(target => {
const sheet = ss.getSheetByName(target);
target.clearConditionalFormatRules();
target.setConditionalFormatRules(rules);
})
}
ConvertFunction:
function convertRules(rules,sheetnames){
const output = [];
const ss = SpreadsheetApp.getActiveSpreadsheet();
sheetnames.forEach(sh => {
rules.forEach(rule => {
const copy = rule.copy();
const newRanges = [];
const oldRanges = copy.getRanges();
oldRanges.forEach(range => {
const buildRange = ss.getSheetByName(sh).getRange(range.getA1Notation());
newRanges.push(buildRange);
});
copy.setRanges(newRanges);
output.push(copy);
});
});
return output;
}
When you used ConditionalFormatRuleBuilder.copy(), it will return a ConditionalFormatRuleBuilder type data.
You need to use ConditionalFormatRuleBuilder.build() to generate a ConditionalFormatRule based on your modified range which can be used to set sheet's conditional format rules using Sheet.setConditionalFormatRules()
Example Code: (Copy Conditional Formatting Rules from Sheet1 to Sheet2 and change its range by adding column offset of 2)
function myFunction() {
var sourceSh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var targetSh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
var rules = sourceSh.getConditionalFormatRules();
var newRules = [];
rules.forEach((rule, index) => {
Logger.log("****rule****");
var ruleBuilder = rule.copy();
var ranges = ruleBuilder.getRanges();
ranges.forEach(range => {
Logger.log(range.getA1Notation());
})
//Select C1:C1000 as new range
var newRange = targetSh.getRange(1,index + 3,1000,1);
ruleBuilder.setRanges([newRange]);
Logger.log("****new range****");
var ranges = ruleBuilder.getRanges();
ranges.forEach(range => {
Logger.log(range.getA1Notation());
})
Logger.log("****build modified rule****");
var newRule = ruleBuilder.build();
newRules.push(newRule);
});
targetSh.setConditionalFormatRules(newRules);
}
OUTPUT:
Sheet1:
Sheet2:
Your Code:
function convertRules(rules,sheetnames){
const output = [];
const ss = SpreadsheetApp.getActiveSpreadsheet();
sheetnames.forEach(sh => {
rules.forEach(rule => {
const copy = rule.copy();
const newRanges = [];
const oldRanges = copy.getRanges();
oldRanges.forEach(range => {
const buildRange = ss.getSheetByName(sh).getRange(range.getA1Notation());
newRanges.push(buildRange);
});
copy.setRanges(newRanges);
copy.build(); // Build conditional format rules based on modified range
output.push(copy);
});
});
return output;
}
I could be wrong because I don't use format rules much. But it seems to me that rules is an array of individual rule items. And the convertRules() returns that array so:
Perhaps this code might be something like this:
targets.forEach((target,i) => {
const sheet = ss.getSheetByName(target);
target.clearConditionalFormatRules();
target.setConditionalFormatRules(rules[i]);
})
I'm not really sure about this so if I'm wrong then I'll be glad to delete the answer.