Print multiple data per ID in Google Sheet - google-apps-script

In this spreadsheet, I have a sheet called Form Responses 1 that contains the students' data and a sheet called TEMPLATE that contains the template that needs to be printed per student. The problem is that I can only print the spreadsheet one at a time by changing the data in the C2 cell. Is there any way for me to print it quickly and easily to save time and effort?
In Google Cloud Print, I want it to say Page 1 = Student 1, Page 2 = Student 2, Page 3 = Student 3, and so on. Is it possible?

In your situation, how about creating a new Google Spreadsheet including each page? When this is reflected in a sample script, it becomes as follows.
Sample script:
function myFunction() {
const srcSs = SpreadsheetApp.getActiveSpreadsheet();
const sheet = srcSs.getSheetByName("TEMPLATE");
const values = sheet.getRange("C2").getDataValidation().getCriteriaValues()[0].getValues().flat().filter(String);
const dstSs = SpreadsheetApp.create("tempSpreadsheet");
values.forEach(v => {
sheet.getRange("C2").setValue(v);
SpreadsheetApp.flush();
const tempSheet = sheet.copyTo(srcSs);
const range = tempSheet.getDataRange();
range.copyTo(range, {contentsOnly: true});
tempSheet.getRange("B2:2").clear().clearDataValidations();
tempSheet.getDrawings().forEach(e => e.remove());
tempSheet.copyTo(dstSs);
srcSs.deleteSheet(tempSheet);
});
dstSs.deleteSheet(dstSs.getSheets()[0]);
}
When this script is run, a new Google Spreadsheet of tempSpreadsheet is created to the root folder. When you see it, each tab has each sheet without using the formulas. By this, you can print out the Spreadsheet by one manual process.
Note:
When I saw your sample Spreadsheet, /** #OnlyCurrentDoc */ is used in your script. When you use my proposed script, please remove /** #OnlyCurrentDoc */. By this, the scope of https://www.googleapis.com/auth/spreadsheets is automatically detected and used it. Please be careful about this.
This sample script is for your sample Spreadsheet. So when the spreadsheet is changed, this script might not be able to be used. Please be careful about this.
References:
create(name)
copyTo(spreadsheet) of Class Sheet
copyTo(destination) Class Range

Related

Need to find duplicate data in another sheet and delete row

Need some help.
I need Script to check data in B3 Sheet 2
and find Data in colum A in Sheet 1 and Delete row duplicate data
Thank you.
Sample
https://i.stack.imgur.com/3sT3X.jpg
https://i.stack.imgur.com/zTRtg.jpg
I believe your goal is as follows.
From I need Script to check data in B3 Sheet 2 and find Data in colum A in Sheet 1 and Delete row duplicate data, you want to delete rows of "Sheet1" by searching the values of column "A" using a value of "B3" of "Sheet2".
You want to achieve this using Google Apps Script.
In this case, how about the following sample script?
Sample script 1:
In this sample, TextFinder is used. If the number of deleting rows is small, this script might be useful. Please copy and paste the following script to the script editor of Spreadsheet. And, please confirm your sheet names again. And, please run sample1.
function sample1() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet1 = ss.getSheetByName("Sheet1");
const sheet2 = ss.getSheetByName("Sheet2");
const search = sheet2.getRange("B3").getDisplayValue();
sheet1.getRange("A2:A" + sheet1.getLastRow()).createTextFinder(search).findAll().reverse().forEach(r => sheet1.deleteRow(r.getRow()));
}
Sample script 2:
In this sample, filter and setValues are used. If the number of deleting rows is large, this script might be useful. Please copy and paste the following script to the script editor of Spreadsheet. And, please confirm your sheet names again. And, please run sample2.
function sample2() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet1 = ss.getSheetByName("Sheet1");
const sheet2 = ss.getSheetByName("Sheet2");
const search = sheet2.getRange("B3").getValue();
const range = sheet1.getDataRange();
const values = range.getValues().filter(([a]) => a != search);
range.clearContent().offset(0, 0, values.length, values[0].length).setValues(values);
}
Note:
When this script is run, I think that your showing goal will be achieved.
But, if your actual Spreadsheet is different from your question and/or if your question has hidden requests, this script might not be able to be used. Please be careful about this.
References:
createTextFinder(findText)
filter()
setValues(values)

How to set a named range for a data validation programmatically (in Google apps script) in a Google spreadsheet?

Use Case
Example. I have a named range Apples (address "Sheet10!B2:B"), which in use for data validation for plenty of sheet cells. The data range for Apples can be changed (in a script), e.g. to "Sheet10!D2:D".
It works from UI
I can set manually a named range as a data source of data validation.
In this case, the data validation of a cell will always refer to the named range Apples with updated the data range.
How to make it in Google Apps Script?
GAS Limits
The code, for setting data validation, should look like this, if you have a namedRange object:
mySheet.getRange('F5')
.setDataValidation(
SpreadsheetApp.newDataValidation()
.requireValueInRange(
namedRange.getRange()
)
.setAllowInvalid(false)
.build()
);
DataValidationBuilder.requireValueInRange() does not work here as it requires only class Range (it cannot get NamedRange), and no reference to a named range will be used.
Is there a workaround or so?
UPD1 - Spreadsheet.getRangeByName() does not work
Getting range by name does not help, the data validation will get actual range address.
SpreadsheetApp.getActive().getRangeByName("Apples")
UPD2 No way to make it so far in GAS
As #TheMaster posted, it's not possible at this moment.
Please set +1 for posts:
https://issuetracker.google.com/issues/143913035
https://issuetracker.google.com/issues/203557342
P.S. It looks like the only solution will work is Google Sheets API.
I thought that in your situation, I thought that when Sheets API is used, your goal might be able to be used.
Workaround 1:
This workaround uses Sheets API.
Usage:
1. Prepare a Google Spreadsheet.
Please create a new Google Spreadsheet.
From Example. I have a named range Apples (address "Sheet10!B2:B"), which in use for data validation for plenty of sheet cells. The data range for Apples can be changed (in a script), e.g. to "Sheet10!D2:D"., please insert a sheet of "Sheet10" and put sample values to the cells "B2:B" and "D2:D".
Please set the named range Sheet10!B2:B as Apple.
2. Sample script.
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And, please enable Sheets API at Advanced Google services.
function myFunction() {
const namedRangeName = "Apple"; // Please set the name of the named range.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet10");
const requests = [{ updateCells: { range: { sheetId: sheet.getSheetId(), startRowIndex: 0, endRowIndex: 1, startColumnIndex: 0, endColumnIndex: 1 }, rows: [{ values: [{ dataValidation: { condition: { values: [{ userEnteredValue: "=" + namedRangeName }], type: "ONE_OF_RANGE" }, showCustomUi: true } }] }], fields: "dataValidation" } }];
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
In this request, the name of the named range is directly put to userEnteredValue.
3. Testing.
When this script is run to the above sample Spreadsheet, the following result is obtained.
When this demonstration is seen, first, you can see the named range of "Apple" which has the cells "B1:B1000". When a script is run, data validation is put to the cell "A1" with the named range of "Apple". In this case, the values of data validation indicate "B1:B1000". When the range named range "Apple" is changed from "B1:B1000" to "D1:D1000" and the data validation of "A1" is confirmed, it is found that the values are changed from "B1:B1000" to "D1:D1000".
Workaround 2:
This workaround uses the Google Spreadsheet service (SpreadsheetApp). In the current stage, it seems that the Google Spreadsheet service (SpreadsheetApp) cannot directly achieve your goal. This has already been mentioned in the discussions in the comment and TheMaster's answer. When you want to achieve this, how about checking whether the range of the named range is changed using OnChange as following workaround 2?
Usage:
1. Prepare a Google Spreadsheet.
Please create a new Google Spreadsheet.
From Example. I have a named range Apples (address "Sheet10!B2:B"), which in use for data validation for plenty of sheet cells. The data range for Apples can be changed (in a script), e.g. to "Sheet10!D2:D"., please insert a sheet of "Sheet10" and put sample values to the cells "B2:B" and "D2:D".
Please set the named range Sheet10!B2:B as Apple.
2. Sample script.
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And, please install OnChange trigger to the function onChange.
First, please run createDataValidation. By this, data validation is put to the cell "A1" of "Sheet10". In this case, the set range is the range retrieved from the named range "Apple". So, in this case, the range is Sheet10!B2:B1000.
As the next step, please change the range of the named range from Sheet10!B2:B1000 to Sheet10!D2:D1000. By this, onChange` function is automatically run by the installed OnChange trigger. By this, the data validation of "A2" is updated. By this, the values of data validation are changed.
const namedRangeName = "Apple"; // Please set the name of the named range.
const datavalidationCell = "Sheet10!A2"; // As a sample. data validation is put to this cell.
function onChange(e) {
if (e.changeType != "OTHER") return;
const range = e.source.getRangeByName(namedRangeName);
const a1Notation = `'${range.getSheet().getSheetName()}'!${range.getA1Notation()}`;
const prop = PropertiesService.getScriptProperties();
const previousRange = prop.getProperty("previousRange");
if (previousRange != a1Notation) {
const rule = SpreadsheetApp.newDataValidation().requireValueInRange(e.source.getRangeByName(namedRangeName)).setAllowInvalid(false).build();
e.source.getRange(datavalidationCell).setDataValidation(rule);
}
prop.setProperty("previousRange", a1Notation);
}
// First, please run this function.
function createDataValidation() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const rule = SpreadsheetApp.newDataValidation().requireValueInRange(ss.getRangeByName(namedRangeName)).setAllowInvalid(false).build();
ss.getRange(datavalidationCell).setDataValidation(rule);
const prop = PropertiesService.getScriptProperties();
const range = ss.getRangeByName(namedRangeName);
const a1Notation = `'${range.getSheet().getSheetName()}'!${range.getA1Notation()}`;
prop.setProperty("previousRange", a1Notation);
}
References:
Method: spreadsheets.batchUpdate
UpdateCellsRequest
DataValidationRule
Currently, This seems to be impossible. This is however a known issue. +1 this feature request, if you want this implemented.
https://issuetracker.google.com/issues/143913035
Workarounds from the tracker issue creator:
If a validation rule is manually created with a NamedRange via the Sheets GUI, it can then be copied programmatically using Range.getDataValidations(), and subsequently used to programmatically create new DataValidations. DataValidations created this way maintain their connection to the NamedRange, and behave like their manually created counterparts. This demonstrates that the functionality to 'use' NamedRanges for data validation rules is already possible with Apps Scripts, but not the option to 'create' them.
As a half-answer, if you want just validation and can live without the drop-down list of valid values, you can programmatically set a custom formula that references the named range. This reference to the named range will not get expanded in the AppsScript, so future changes to the Named Range's actual range will percolate to the validator. Like so:
mySheet.getRange('F5')
.setDataValidation(
SpreadsheetApp.newDataValidation()
.requireFormulaSatisfied(
'=EQ(F5, VLOOKUP(F5, ' + namedRange.getName() + ', 1))'
)
.setAllowInvalid(false)
.build()
);
(The formula just checks that the value in the cell being tested is equal to what VLOOKUP finds for that cell, in the first column -- I'm assuming the named range content is sorted.)
Use getRangeByName()
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
var cell = sh.getRange(1, 10);//location where datavalidation is applied
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(ss.getRangeByName("MyList")).build();
cell.setDataValidation(rule);
}

Google Slides Apps Script retrive a shape in a page

how to retrieve a particular shape base on the text of the shape.
example: I would like to retrieve a shape in which text starts with "Issue" and get the content of the text to input it in Google Sheets
thanks for helping!
I believe your goal as follows.
You want to retrieve the texts from the shapes in Google Slides.
When the top of text is Issue, you want to retrieve the text, and want to put the retrieved texts to Google Spreadsheet.
You want to achieve this using Google Apps Script.
In this case, how about the following sample script? Unfortunately, from your question, I cannot understand about the output situation you expect. So the following sample script puts the retrieved values to the 1st column.
Sample script:
Please copy and paste the following script to the script editor. And, please set the variables and run myFunction.
function myFunction() {
const presentationId = "###"; // Please set the presentation ID (Google Slides ID).
const spreadsheetId = "###"; // Please set the Spreadsheet ID.
const sheetName = "Sheet1"; // Please set the sheet name.
const searchText = "Issue";
// Check texts and retrieve texts from Google Slides.
const regex = new RegExp(`^${searchText}`);
const slides = SlidesApp.openById(presentationId).getSlides();
const values = slides.flatMap(slide => slide.getShapes().reduce((ar, shape) => {
const text = shape.getText().asString().trim();
if (regex.test(text)) ar.push([text]);
return ar;
}, []));
// Put the retrieved texts to Google Spreadsheet.
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}
Note:
Unfortunately, I'm not sure whether above sample script is what you need. So when above script is not useful for your situation, in order to correctly understand about your goal, can you provide the sample Slides and the sample result you expect? By this, I would like to modify the script.
References:
getShapes()
getText()
setValues(values)

Script that adds new row to sheets in selected AND another Google Spreadsheet

I'm working on a Spreadsheet to keep track of team member's project hours. I've created a Spreadsheet per team member for them to fill out weekly, and a project overview Spreadsheet that takes in all data through IMPORTRANGE.
To be able to quickly add a new project I want a macro to insert a new row in the Project overview + the separate Spreadsheets per team member. However I can't figure out how to write the correct code for the separate team member Spreadsheets. What's going wrong here?
If possible I'd also like to make a macro to DELETE a row in the project overview + team member spreadsheets, and one to HIDE a row...
Project overview
Team member Kate
Team member David
My current code:
function InsertRow() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
var row = SpreadsheetApp.getActiveRange().getRow();
// Array holding the names of the sheets to exclude from the execution
// I only managed to make it work when I exclude the sheet that I actually want to affect instead of the other way around?
var exclude = (["PROJECTS"] ||
SpreadsheetApp.openById("1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI").getSheets() ||
SpreadsheetApp.openById("1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU").getSheets())
for(var s in allsheets){
var sheet = allsheets[s];
// Stop iteration execution if the condition is meet.
if(exclude.indexOf(sheet.getName())==-1) continue;
sheet.insertRowBefore(row);
}
}
As I see it you have a couple of options, which I'll be listing here as A, B, and C. Please note that you might need two different .GS files as you are linking to two sheets
A
Try code found on google app script documentation
I found the google apps script documentation for this command found here, so you might want to check that for this questions and others , but here is the exact code included
// The code below opens a spreadsheet using its ID and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openById("abc1234567");
Logger.log(ss.getName());
B
use open by url instead of open by id
Your issue might be that your current id isn't correct, I have no way of knowing, so here is some alternate code here (link to documentation here)
// The code below opens a spreadsheet using its id and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openByUrl(
'https://docs.google.com/spreadsheets/d/abc1234567/edit');
Logger.log(ss.getName());
C
Tie the google script to one sheet
This last option doesn't require any code, just an explanation. Instead of trying to link your script to two separate sheets, you might be able to automatically link it to a single google sheet and create two pages in the sheets file that you treat as two different sheets but are one thing. This might not be what you want, but I included it anyways. You link the sheet to the code automatically by:
1 opening your sheet
2 going to "tools"
3 clicking script editor
4 copy and paste your code (except for the "open by id" part)
5 success!
Your exclude variable doesn't contain what you think it does. You're using an "or" operator (||), which will take the first "truthy" value and skip the rest.
console.log((["PROJECTS"] || 'something else')); // ["PROJECTS"]
Moreover, you don't have a good way of telling which spreadsheet belongs to which team member. To solve that problem, you can create an object.
const teamSpreadsheetIds = {
'DAVID': 'ABC',
'KATE': '123',
};
console.log(teamSpreadsheetIds['DAVID']); // ABC
With the teamSpreadsheetIds object, you can now go about updating your team member sheets locally as well as their individual spreadsheets. The "PROJECTS" sheet is unique, so there's only one check for it.
function InsertRow() {
const ss = SpreadsheetApp.getActive();
const allSheets = ss.getSheets();
const row = SpreadsheetApp.getActiveRange().getRow();
const teamSpreadsheetIds = {
'DAVID': '1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU',
'KATE': '1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI',
};
for (let sheet of allSheets) {
const sheetName = sheet.getName();
const memberSpreadsheetId = teamSpreadsheetIds[sheetName];
const isSkippable = memberSpreadsheetId === undefined && sheetName !== 'PROJECTS';
if (isSkippable) { continue };
// Insert a row in the local sheet
sheet.insertRowBefore(row);
// Get the member sheet and insert a row
if (memberSpreadsheetId) {
const memberSpreadsheet = SpreadsheetApp.openById(memberSpreadsheetId);
const memberSheet = memberSpreadsheet.getSheets()[0]; // Assumes the first sheet is the one to modify
memberSheet.insertRowBefore(row);
}
}
}

How to copy format and values, not formulas, when creating a spreadsheet backup in Google Apps Script?

The goal is to create frozen backups of a spreadsheet with multiple sheets. The backup spreadsheet needs to copy over values and format, but not the formulas.
Some of the sheets have =IMPORTRANGE, which becomes a problem if the spreadsheet is copied first and then the values copied over the same range, again, to get rid of formulas, because the new spreadsheet requires access to the imported range in the middle of the function (which leads to #REF in A1).
This question was clearly asked many times, but none of the answers seem to solve this issue. I have looked at these questions (and more, actually): Link 1, Link 2, Link 3, Link 4, Link 5, and Link 6. I tried implementing snippets from the answers to those questions or combinations of them to no avail. It became to difficult to track every bit of snippet I tested.
I tried using copyTo() either to copy entire sheets in a forEach function, copy with the options {contentsOnly:true} and {formatOnly:true}, but have been unsuccessful. Either copyTo() wants the ranges to be in the same spreadsheet/sheet or the getDataRange() doesn't match the backup range...
Here's the script I'm currently using, which successfully creates a copy of the entire spreadsheet with values only. I cannot recall from which question I got it from.
function copyEntireSpreadsheet(){
var ss,ssName,sheet,sheetName,data,destination
ss = SpreadsheetApp.openById("id").getSheets();
ssName = SpreadsheetApp.openById("id").getName();
destination = SpreadsheetApp.create(ssName + " - " + new Date().toLocaleString());
for (var i = 0; i < ss.length; i++){
sheet = ss[i];
sheetName = sheet.getSheetName();
data = sheet.getSheetValues(1, 1, sheet.getLastRow(), sheet.getLastColumn());
destination.insertSheet(sheetName);
destination.getSheets()[i+1].getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).setValues(data);
}
destination.deleteSheet(destination.getSheetByName('Sheet1'));
}
The source spreadsheet has a lot of formatting with merged cells, conditional formatting, etc., which would be ideal to copy. I can also hard code certain sheets into the script, if that would make any difference with an alternative method.
In short: I'm looking for a frozen backup of a spreadsheet with multiple sheets. Values and formatting need to be copied, not the formulas.
I might be missing something in one of the answers to all those questions, so I'll keep trying. In the meantime, any help/direction would be appreciated. Thanks in advance!
SOLVED: #Tanaike's second sample script below is an excellent workaround to copying sheets that have references to other spreadsheets and hence require access to them before being overwritten by values only. Big thanks to #Tanaike for the extensive help with this -- much appreciated.
You want to copy the values and format of all sheets in a Spreadsheet to new Spreadsheet.
In this case, you want to copy only values without the formulas.
The source spreadsheet has a lot of formatting with merged cells, conditional formatting, etc.
The source Spreadsheet includes the values put with IMPORTRANGE.
You want to achieve this using Google Apps Script.
The flow of sample script for above goal is as follows.
Flow:
Copy all sheets in the source Spreadsheet as the temporal sheets.
At the copied sheets, the cells are overwritten by only the values. By this, the formulas can be removed.
If the source Spreadsheet is copied, the values retrieved with IMPORTRANGE become #REF. Because it is required to authorize at the new copied Spreadsheet. In order to avoid this, the temporal sheets are copied in the source Spreadsheet.
Copy the source Spreadsheet.
Delete the temporal sheets in the source Spreadsheet.
Delete the original sheets in the destination Spreadsheet.
By above flow, only the values can be copied without the authorization of IMPORTRANGE.
Sample script:
function copyEntireSpreadsheet() {
var id = "###"; // Please set the source Spreadsheet ID.
var ss = SpreadsheetApp.openById(id);
var srcSheets = ss.getSheets();
var tempSheets = srcSheets.map(function(sheet, i) {
var sheetName = sheet.getSheetName();
var dstSheet = sheet.copyTo(ss).setName(sheetName + "_temp");
var src = dstSheet.getDataRange();
src.copyTo(src, {contentsOnly: true});
return dstSheet;
});
var destination = ss.copy(ss.getName() + " - " + new Date().toLocaleString());
tempSheets.forEach(function(sheet) {ss.deleteSheet(sheet)});
var dstSheets = destination.getSheets();
dstSheets.forEach(function(sheet) {
var sheetName = sheet.getSheetName();
if (sheetName.indexOf("_temp") == -1) {
destination.deleteSheet(sheet);
} else {
sheet.setName(sheetName.slice(0, -5));
}
});
}
References:
copy(name)
copyTo(destination, options)