In Google Sheets set dynamic dropdown with Apps Script - google-apps-script

I'm trying to set a dynamic dropdown in Google Sheet using Apps Script. I managed to get most parts working except setting the data validation in the cells necessary:
function sheetByName(ssId, sheetName) {
var ss = SpreadsheetApp.openById(ssId);
var sheet = ss.getSheetByName(sheetName);
return sheet;
};
function columnByName(sheet, columnName) {
var data = sheet.getDataRange().getValues();
var column = data[0].indexOf(columnName);
return column;
};
function columnValues(sheet, index) {
var data = sheet.getDataRange().getValues();
var values = [];
for(n=1; n<data.length; ++n) {
values.push(data[n][index]);
}
return values;
}
function columnSetDataValidation(sheet, index, options) {
var data = sheet.getDataRange().getValues();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.setAllowInvalid(true)
.build();
for(n=1; n<data.length; ++n) {
var cell = data[n][index];
};
};
function dropDownBedrijven() {
var sheetCollegas = sheetByName("<<ID HERE>>", "Collegas");
var sheetBedrijven = sheetByName("<<ID HERE>>", "Bedrijven");
var getColumnIndexInBedijven = columnByName(sheetBedrijven, "Bedrijf");
var getColumnIndexInCollegas = columnByName(sheetCollegas, "Bedrijf");
var bedrijven = columnValues(sheetBedrijven, getColumnIndexInBedijven).filter(item => item);
columnSetDataValidation(sheetCollegas, getColumnIndexInCollegas, bedrijven);
};
I can't manage to get the function columnSetDataValidation to set data validation in the required cells.
Do you have any idea how to go about it?

You need to use range.setDataValidation(rule) with a range.
In your function columnSetDataValidation you are correctly building the rule, but are failing to assign the rule to a range. You are looping over the values of the range and then changing the value of var cell until the loop ends. Nowhere did you call range.setDataValidation(rule).
Try the following solution:
function columnSetDataValidation(sheet, index, options) {
var range = sheet.getDataRange();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.setAllowInvalid(true)
.build();
for(n = 1; n < range.getLastRow(); ++n) {
var cell = range.getCell(n,index);
cell.setDataValidation(rule);
};
};
References:
Range.getCell(row, column)
Range.setDataValidation(rule)

Try this:
function columnSetDataValidation() {
const ss=SpreadsheetApp.getActive();
const sheet=ss.getSheetByName('Sheet1');
const range = sheet.getRange(2,4,sheet.getLastRow()-1);//putting validation in column 4
const options=[1,2,3,4,5];
const rule = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.setAllowInvalid(true)
.build();
range.setDataValidation(rule);
}

Related

Trying to getlastrow

Hello I am trying to have a continual list that copies values from one sheet to another in google sheets. My problem is that it is overwriting on the second sheet. I can't figure out how to find the last row before it inserts values. Here is my code.
function SubmitData() {
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('Validation')
var drng = sht.getDataRange();
var rng = sht.getRange(6,2, drng.getLastRow(),drng.getLastColumn());
var rngA = rng.getValues();//Array of input values
var rngB = [];//Array where values that past the condition will go
var b = 0;//Output iterator
for(var i = 0; i < rngA.length; i++)
{
rngB[b]=[];//Initial new array
rngB[b].push(rngA[i][0],rngA[i][2]);
b++;
}
var shtout = s.getSheetByName('Track Data');
var outrng = shtout.getRange(2,1, rngB.length,2);//Make the output range the same size as the output array
outrng.setValues(rngB);
I don't see any condition in your code, you're just remapping
function SubmitData() {
const s = SpreadsheetApp.getActive();
const sht = s.getSheetByName('Validation');
const shtout = s.getSheetByName('Track Data');
const rng = sht.getRange(6, 2, sht.getLastRow() - 5, sht.getLastColumn() - 1);
const vs = rng.getValues();
let vO = vs.map(r => [r[0], r[2]]);
shtout.getRange(shtout.getLastRow() + 1, 1, vO.length, vO[0].length).setValues(vO);
}
Here is a modified script, easier to read. If you have the "sheet" in a variable you can call the getLastRow() method. Then plus 1.
You also can push an array inside an array.
Be aware that bad written functions can plus the lastrow. The "" will be see ad NOT empty. For instance:
//BAD:
=IF(A10 = "","","This is not empty")
//GOOD:
=IF(A10 = "",,"This is not empty")
The script:
function SubmitData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const inputSheet = ss.getSheetByName('Validation')
const inputValues = inputSheet.getRange(6,2, inputSheet.getLastRow(),inputSheet.getLastColumn()).getValues()
const output = [];
inputValues.forEach(row => {
output.push([row[0].row[2]])
})
const outputSheet = ss.getSheetByName('Track Data');
outputSheet.getRange(outputSheet.getLastRow() + 1,1,output.length,output[0].length).setValues(output)
}

Execute app script with 2 tags which will determine filter to apply and spreadsheet sheet

I'm on a project in which I get stuck just on the final step.
let me explain:
my project to filter data and move the filtered data to another spreadsheet. All work properly without issues but something happened and the issue is that I need to input dynamic data as filter and sheet name. I created 2 variables location which will determine the filter and sectionSelect which will determine the sheet name.
my goal is to send the data through the web with its tag to be filtered in the desired sheet.
FYI: the app script is bound to a gsheet.
Here is the code:
function doGet(e) {
var ss = SpreadsheetApp.openByUrl("sheetURL")
var sheet = ss.getSheetByName(Location);
return TagData(e,sheet);
}
function doPost(e) {
var ss = SpreadsheetApp.openByUrl("sheetURL")
var sheet = ss.getSheetByName(Location);
return TagData(e,sheet);
}
function TagData(e) {
var Location = e.parameter.Location; // send from app with respective tag
var sectiontSelect = e.parameter.sectiontSelect; // send from app with respective tag
sheet.append([Location, sectiontSelect])
}
function FilterOnText() {
var ss = SpreadsheetApp.getActive()
var range = ss.getDataRange();
var filter = range.getFilter() || range.createFilter()
var text = SpreadsheetApp.newFilterCriteria().whenTextContains(Location); // the location will be as per the location variable in the TagData function
filter.setColumnFilterCriteria(1, text);
}
function titleAsDate() {
const currentDate = Utilities.formatDate(new Date(), "GMT+4", "dd-MM-yyyy HH:mm:ss");
return SpreadsheetApp.create("Report of the " + currentDate);
}
function copyWithValues() {
const spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
const sourceSheet = spreadSheet.getSheetByName(sectionSelect); // fromApp variable will define which sheet the data will be copied (situated in the TagData function)
const temp_sheet = spreadSheet.insertSheet('temp_sheet');
const sourceRange = sourceSheet.getFilter().getRange();
sourceRange.copyTo(
temp_sheet.getRange('A1'),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL,
false);
SpreadsheetApp.flush();
const sourceValues = temp_sheet.getDataRange().getValues();
const targetSpreadsheet = titleAsDate();
const rowCount = sourceValues.length;
const columnCount = sourceValues[0].length;
const targetSheet = targetSpreadsheet.getSheetByName('Sheet1').setName("Report"); // renamed sheet
const targetRange = targetSheet.getRange(1, 1, rowCount, columnCount);
targetRange.setValues(sourceValues);
spreadSheet.deleteSheet(temp_sheet);
}
function MoveFiles(){
var files = DriveApp.getRootFolder().getFiles();
var file = files.next();
var destination = DriveApp.getFolderById("1wan7PLhl4UFEoznmsN_BVa2y4AtFaCOr");
destination.addFile(file)
var pull = DriveApp.getRootFolder();
pull.removeFile(file);
}
function clearFilter() { // clearance of filters applied in first function
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("testo");
sheet.getFilter().remove();
}
Explanation:
Two issues:
TagData does not return anything (as Cooper pointed out)
TagData(e) has only one parameter e but you are passing two parameters TagData(e,sheet) when you call it from the doGet and doPost functions.
Not sure if this will solve all your issues, but it will solve the ones I mentioned above.
Solution:
Remove the return statements from doGet and doPost because of issue 1).
Add the sheet parameter in the TagData function.
Resulting code:
function doGet(e) {
var ss = SpreadsheetApp.openByUrl("sheetURL")
var sheet = ss.getSheetByName(Location);
TagData(e,sheet); // modified
}
function doPost(e) {
var ss = SpreadsheetApp.openByUrl("sheetURL")
var sheet = ss.getSheetByName(Location);
TagData(e,sheet); // modified
}
// added the sheet parameter
function TagData(e,sheet) {
var Location = e.parameter.Location; // send from app with respective tag
var sectiontSelect = e.parameter.sectiontSelect; // send from app with respective tag
sheet.append([Location, sectiontSelect]);
}
// rest of your code..
Or try this:
function doGet(e) {
var ss = SpreadsheetApp.openByUrl("sheetURL")
var sheet = ss.getSheetByName(Location);
var params = JSON.stringify(TagData(e,sheet)); // modified
return HtmlService.createHtmlOutput(params); // added
}
function doPost(e) {
var ss = SpreadsheetApp.openByUrl("sheetURL")
var sheet = ss.getSheetByName(Location);
ContentService.createTextOutput(JSON.stringify(e))
}
// added the sheet parameter
function TagData(e,sheet) {
var Location = e.parameter.Location; // send from app with respective tag
var sectiontSelect = e.parameter.sectiontSelect; // send from app with respective tag
sheet.append([Location, sectiontSelect]);
return { Location: Location, sectiontSelect: sectiontSelect }
}

How to build conditional formattings with script?

I'm using the google script to manage the google admin. Below is the script to list all google users in the domain.
If I create conditional formatting directly on the sheet, the rules are deleted whenever the script is launched. That's why I'd like to include the conditional formatting within the script itself, but it's not working?.
Basically what I want are :
if the value of the columns 'is Super Admin?' or 'is Delegated Admin?' are TRUE, then I'd like the rows to be colored in bold red.
And I'm trying to find out how to convert the results of lastLoginTime and creationTime (which are in string ISO8601 to datevalue, I know how to do it using formula, but I don't know how to using the script)
I'd really appreciate it if anyone can guide me.
Many many thanks in advance.
function listAllUser() {
var sheet11 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SheetNAME");
var sheet11range = sheet11.getRange("I3:Q")
sheet11range.clear()
var data = [];// array to store values
data.push(['Last Name','First Name','Email','orgUnitPath', 'is Suspended?','is Super Admin?','is Delegated Admin?', 'LastLoginTime','Creation']);// store headers
var pageToken, page;
do {
page = AdminDirectory.Users.list({
domain: 'DomainNAME',
pageToken: pageToken,
});
var users = page.users;
if (users) {
for (var i = 0; i < users.length; i++) {
try{
var user = users[i];
data.push([user.name.familyName,user.name.givenName,user.primaryEmail,user.orgUnitPath, user.suspended,user.isAdmin,user.isDelegatedAdmin, user.lastLoginTime , user.creationTime ]);//store in an array of arrays (one for each row)}
}catch (e){}
}}
pageToken = page.nextPageToken;
} while (pageToken);
sheet11.getRange(2,9,data.length,data[0].length).setValues(data).setVerticalAlignment("middle").setWrap(true);
var t1 = sheet11.getRange("I1:P1").merge();
var t1bis = t1.setValue("List of all USERS");
var dated = sheet11.getRange("Q1")
dated.setValue("Last updated: " + Utilities.formatDate(new Date(),Session.getScriptTimeZone(),'dd-MM-yy hh:mm'));
var range = sheet11.getRange("I1:Q2");
range.setFontWeight("Bold").setFontColor("Blue").setHorizontalAlignment("center").setVerticalAlignment("middle").setBackground("#beedda").setWrap(true);
var isSuperAdm = sheet11.getRange("N3:N").getValue();
var isDelegatAdm = sheet11.getRange("O3:O").getValue();
if(isSuperAdm==true || isDelegatAdm==true){
range.setFontWeight('bold').setFontColor('red')
}
}
After looking around for some tutorials, I finally succeeded to have what I wanted, formating the cell :
when the lastLoginTime was before the specific date
when the value of 'is Super Admin?' and 'is Delegated Admin?' are true
function cellsFormatting(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SheetName");
var range1 = sheet.getRange("A1:Q2");
range1.setFontWeight("Bold").setFontColor("Blue").setHorizontalAlignment("center").setVerticalAlignment("middle").setBackground("#beedda").setWrap(true);
var range2 = sheet.getRange ("A3:Q");
range2.setVerticalAlignment('middle').setWrap(true);
}
function setRangeToRedBoldFont(cellRange){
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(cellRange);
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenTextEqualTo("TRUE")
.setFontColor("#FF0000")
.setBold(true)
.setRanges([range])
.build();
var rules = sheet.getConditionalFormatRules();
rules.push(rule);
sheet.setConditionalFormatRules(rules);
}
function setRangeToGrayItalicFont(cellRange){
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(cellRange);
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied("=DATEVALUE(MID($P3;1;10)) + TIMEVALUE(MID($P3;12;8))<date(2018;8;31)")
.setFontColor("#A9A9A9")
.setItalic(true)
.setRanges([range])
.build();
var rules = sheet.getConditionalFormatRules();
rules.push(rule);
sheet.setConditionalFormatRules(rules);
}
function listAllUser() {
var sheet11 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SheetName");
var sheet11range = sheet11.getRange("I3:Q")
sheet11range.clear();
cellsFormatting();
var t1 = sheet11.getRange("I1:P1").merge();
var t1bis = t1.setValue("List of all USERS");
var dated = sheet11.getRange("Q1")
dated.setValue("Last updated: " + Utilities.formatDate(new Date(),Session.getScriptTimeZone(),'dd-MM-yy hh:mm')); //will input the date & time when the script was launched
var data = [];// array to store values
data.push(['Last Name','First Name','Email','orgUnitPath', 'is Suspended?','is Super Admin?','is Delegated Admin?', 'LastLoginTime','Creation']);// store headers
var pageToken, page;
do {
page = AdminDirectory.Users.list({
domain: 'DomainNAME',
pageToken: pageToken,
});
var users = page.users;
if (users) {
for (var i = 0; i < users.length; i++) {
try{
var user = users[i];
data.push([user.name.familyName,user.name.givenName,user.primaryEmail,user.orgUnitPath, user.suspended,user.isAdmin,user.isDelegatedAdmin, user.lastLoginTime , user.creationTime ]);//store in an array of arrays (one for each row)}
}catch (e){}
}}
pageToken = page.nextPageToken;
} while (pageToken);
sheet11.getRange(2,9,data.length,data[0].length).setValues(data);
var formatRange = "I3:Q";
setRangeToRedBoldFont(formatRange);
setRangeToGrayItalicFont (formatRange);
}
For the row color:
For user.isAdmin & user.isDelegatedAdmin
function colorLine() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('SheetName');
const rg=sh.getRange(3,9,sh.getLastRow(),9);
const vs=rg.getValues();
vs.forEach((r,i)=>{
if(r[5]||r[6]) {
sh.getRange(i+3,1,1,sh.getLastColumn()).setFontWeight('bold').setFontColor('red');
}
});
}

Converting my script to use getRangeData instead of using an iterative loop (GoogleAppScript)

I am having issues converting my last script to work with getDataRange. I got some help converting my original functions, seen below:
function twentyDKP() {
alertBox20DKP()
}
function alertBox20DKP() {
var sh=SpreadsheetApp.getUi();
var response=sh.alert("Add 20 DKP to all raiders?", sh.ButtonSet.YES_NO);
if(response==sh.Button.YES) {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var raiders = activeSheet.getRange(1, 12).getValue();
// In your situation, the range is the same. So "range" is declared here.
var range = activeSheet.getRange(4, 2, raiders);
// Create values for putting to the range.
var values = range.getValues().map(function(row) {return [row[0] + 20]});
// Put the created values to the range.
range.setValues(values);
// Update the cells. Before "alert" is shown.
SpreadsheetApp.flush();
var complete=sh.alert("20 DKP has been added to all raiders.", sh.ButtonSet.OK);
}
}
However, I now want to do the same with my subtraction script that would rely on two ranges of data. I'm still very new to coding, and am basically getting by on youtube tutorials and advice from this forum. How would I implement the same change to the below code?
function spentDKP() {
alertBoxSpentDKP()
}
function alertBoxSpentDKP() {
var sh=SpreadsheetApp.getUi();
var response=sh.alert("Subtract spent DKP of all raiders?", sh.ButtonSet.YES_NO);
if(response==sh.Button.YES) {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var raiders = activeSheet.getRange(1, 12).getValue()+4;
for(var i=4;i<raiders;i++){
var DKP = activeSheet.getRange(i,2).getValue()
var spentDKP = activeSheet.getRange(i,4).getValue();
if(spentDKP>0){
activeSheet.getRange(i,2).setValue(DKP-spentDKP)
}
}
var complete=sh.alert("All DKP has been subtracted, please clear the loot window to reset values.", sh.ButtonSet.OK);
}
}
Many thanks in advance.
Try replacing this
var raiders = activeSheet.getRange(1, 12).getValue()+4;
for(var i=4;i<raiders;i++){
var DKP = activeSheet.getRange(i,2).getValue()
var spentDKP = activeSheet.getRange(i,4).getValue();
if(spentDKP>0){
activeSheet.getRange(i,2).setValue(DKP-spentDKP)
}
}
with this
var raiders = activeSheet.getRange(1, 12).getValue();
var range = activeSheet.getRange(4, 2, raiders, 3); // Get the range with the data to be processed
var inputValues = range.getValues(); // Get the data from that range
var outputValues = inputValues.map(Subtract); // Use a function to subtract the spent DKP from the DKP balance available
range.setValues(outputValues); // post the calculated values back to the sheet
And add a helper function for map:
// Helper function for map
function Subtract(inputValuesRow) {
if (inputValuesRow[2] > 0) {
inputValuesRow[0] -= inputValuesRow[2];
}
return inputValuesRow; // This has now been change where column 4 value has been subtracted from the column 2 value
}
Edit
To preserve the formulas in the middle column, remove the Subtract helper function. And use this as the replacement:
var raiders = activeSheet.getRange(1, 12).getValue();
var range = activeSheet.getRange(4, 2, raiders, 3); // Get the range with the data to be processed
var inputValues = range.getValues(); // Get the data from that range
var outputValues = [];
for (var r = 0; r < inputValues.length; r++) {
if ( inputValues[r][2] > 0 ) {
outputValues.push([inputValues[r][0] - inputValues[r][2]])
} else {
outputValues.push([inputValues[r][0]])
}
}
activeSheet.getRange(4, 2, raiders, 1).setValues(outputValues);

Functions all executing at the same time

I am trying to copy data from one sheet, paste it into another and then delete the row that have a "flag" attached to them (Column D). When I execute this code, it just deletes the flag and not the whole row. What is going on here? The functions seem to execute in the wrong order.
Tried using Utilities.sleep(), as well as
separating both into different functions
using a for loop writing out all the columns I want deleted
applying flush to the functions before executing the next
//copies the code
function myFunction() {
var url = 'https://docs.google.com/spreadsheets/d/1ie2Fyj2piVV8XC0XUGX-fTAHcQ0UUUdec4Mtr1QdXtQ/edit#gid=293227072'
var ss = SpreadsheetApp.openByUrl(url)
var sheet = ss.getSheetByName('Bad Emails Input')
var range = sheet.getRange("K:N")
var values = range.getValues()
var target = ss.getSheetByName("worklist")
target.getRange("A:D").setValues(values)
SpreadsheetApp.flush()
Utilities.sleep(5 * 1000)
}
//deletes the rows with flags in them
function myFunction2() {
var url = 'https://docs.google.com/spreadsheets/d/1ie2Fyj2piVV8XC0XUGX-fTAHcQ0UUUdec4Mtr1QdXtQ/edit#gid=293227072'
var ss = SpreadsheetApp.openByUrl(url)
var sheet = ss.getSheetByName('Bad Emails Input')
var range = sheet.getRange("K:N")
var values = range.getValues()
var target = ss.getSheetByName("worklist")
Utilities.sleep(6 * 1000)
var oldnews = target.getRange("D:D")
for (i = oldnews.length; i > 0; i--){
if (oldnews[i] !== undefined) {
target.getRange(i,1)
target.getRange(i,2)
target.getRange(i,3)
target.getRange(i,4)
}
}
SpreadsheetApp.flush()
}
It's not really clear what your asking. So I just guessed at some of this.
function myFunction() {
var ss = SpreadsheetApp.openById("id");
var sheet = ss.getSheetByName('Bad Emails Input')
var range = sheet.getRange(1,11,sheet.getLastRow(),4);
var values = range.getValues()
var target = ss.getSheetByName("worklist")
target.getRange(1,1,values.length,values[0].length).setValues(values);
SpreadsheetApp.flush();
var d=0;
for(var i=0;i<values.length;i++) {
if(values[i][3]) {
target.deleteRow(i+1-d++);
}
}
}
Try the below code:
function myFunction() {
var url = 'https://docs.google.com/spreadsheets/d/1ie2Fyj2piVV8XC0XUGX-fTAHcQ0UUUdec4Mtr1QdXtQ/edit#gid=293227072'
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName('Bad Emails Input');
var range = sheet.getRange("K:N");
var values = range.getValues();
var target = ss.getSheetByName("worklist");
target.getRange("A:D").setValues(values);
SpreadsheetApp.flush();
//If you have a header row change the below to A2:D and count to 2
var data=ss.getSheetByName("worklist").getRange("A:D").getValues(),count=1;
for (i in data) {
var rowdata=data[i];
if (!rowdata[0] || rowdata[0]==''){break;}
if (rowdata[3] == 'flag') {//change the flag to however it is in column D
ss.getSheetByName("worklist").deleteRow(count);
count++;
}
}
}