Google Sheets Script to copy rows duplicates the rows several times - google-apps-script

I am currently using this script to move (selected) rows from one google sheet into another using checkbox.
It moves the rows fine but the move keeps duplicating if I do not physically go back and uncheck each row.
1)Am I using the correct script to copy the selected data?
2)and if so what can be done to prevent duplicates?
3)How do i make the checkbox to be a part of the code as well?
Thanks for whatever help I can get.
function moveChecked(){
const sh = SpreadsheetApp.getActive();
const ss = sh.getSheetByName("Original Data Sheet");
const outSheet = sh.getSheetByName("Sheet1");
let data = ss.getRange(2,1,ss.getLastRow()-1,48).getValues();
let out = [];
for (let i = 0; i< data.length; i++){
if (data[i][0] == true){
out.push(data[i]);
}
}
outSheet.getRange(outSheet.getLastRow()+1,1,out.length,48).setValues(out);
}

Move Checked
function moveChecked() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getRange(2, 1, sh.getLastRow() - 1, 48).getValues();
const osh = ss.getSheetByName("Sheet1");
let out = [];
//let d = 0;
vs.forEach((r, i) => {
if (r[0] == true) {
out.push(r);
//sh.deleteRow(i + 2 - d++)
}
});
osh.getRange(osh.getLastRow() + 1, 1, out.length, out[0].length).setValues(out)
}

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

Copying and Pasting, but Pasting to Wrong Location

I hope I post this correctly, first timer. Sorry in advance for any rules or formalities I violate.
Goal: I am trying to copy a range of data from a source tab, then paste the data into a target tab in the first available column in row 2, which is B2 (there is a header A1:Z1).
Issue: I want it to post the data in B2 as its the first open cell in row 2, but the code sees the first available column as AA as there is no header, so it posts the range starting in AA:2.
Help? Haha.
function movelog() {
var sheetFrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Entry");
var sheetTo = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Log");
// Copy from 3rd row, 5th column (E), all rows for one column
var valuesToCopy = sheetFrom.getRange(3, 5, sheetFrom.getLastRow(), 1).getValues();
//Paste to another sheet from first cell onwards
sheetTo.getRange(2,sheetTo.getLastColumn()+1,valuesToCopy.length,1).setValues(valuesToCopy);
}
Move Log
function movelog() {
const ss = SpreadsheetApp.getActive();
const sheetFrom = ss.getSheetByName("Entry");
const sheetTo = ss.getSheetByName("Log");
const valuesToCopy = sheetFrom.getRange(3, 5, sheetFrom.getLastRow() - 2).getValues();
sheetTo.getRange(2, getRowWidth(2, sheetTo, ss) + 1, valuesToCopy.length, 1).setValues(valuesToCopy);
}
function getRowWidth(row, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var row = row || sh.getActiveCell().getRow();
var rcA = [];
if (sh.getLastColumn()) { rcA = sh.getRange(row, 1, 1, sh.getLastColumn()).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;
}

Appscript Not Working Across Different Tabs

I have the below working script but would like to have it working in multiple tabs, instead of having to create several triggers and overload the sheet.
This is the script:
function clearSomeCellsReading () {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Reading');
const sr = 1;
const vs = sh.getRange(sr,21,sh.getLastRow() - sr + 1).getValues().flat();
vs.forEach((e,i) => {
if(e > 29) sh.getRange(i + sr,16,1,3).clearContent();
})
}
Apart from "('Reading')", I have other 20 locations that I would like to incorporate, ie. Bristol, Watford...
All the other tabs are exactly the same to this one, all copies of each other with just different data.
I tried duplicating the script and just changing the tab name but I keep getting errors...
Any help would be greatly appreciated.
Thank you!
I think this might be what you're looking for. If you have some distinct text to identify the Sheets you want to run the procedure on, that would make it dynamic. Otherwise you can specify the exact names of the sheets you want to run it on as shown below.
function allSheetsMacro(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const theSheetsToInclude = ["Reading","Bristol","Watford"];
const allSheets = ss.getSheets();
for(var i=0;i<allSheets.length;i++){
var aSheet = allSheets[i];
if(theSheetsToInclude.includes(aSheet.getName())){
//or whatever rule to find your sheets.
clearSomeCellsReading_(aSheet);
}
}
}
function clearSomeCellsReading_(sh) {
//original function but being called each time from first function.
const sr = 1;
const vs = sh.getRange(sr,21,sh.getLastRow() - sr + 1).getValues().flat();
vs.forEach((e,i) => {
if(e > 29) sh.getRange(i + sr,16,1,3).clearContent();
})
}
function clearSomeCellsReading () {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const sr = 1;
const vs = sh.getRange(sr,21,sh.getLastRow() - sr + 1).getValues().flat();
vs.forEach((e,i) => {
if(e > 29) sh.getRange(i + sr,16,1,3).clearContent();
})
}
or maybe this:
function clearSomeCellsReading() {
const shts = ['Reading', 'others'];
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
if (~shts.indexOf(sh.getName())) {
const sr = 1;
const vs = sh.getRange(sr, 21, sh.getLastRow() - sr + 1).getValues().flat();
vs.forEach((e, i) => {
if (e > 29) sh.getRange(i + sr, 16, 1, 3).clearContent();
})
}
}

Script to reduce font size to fit cell width (and height) in Google Sheets

I want to use a script to automatically adjust the font size of multiple cells that are mostly populated via formula and have dynamic values. This will save me a tonne of time formatting these individual cells.
I saw what looked like a great answer regarding this on a similar question, but when I tried to run the script I get this error. "TypeError: Cannot read property 'getRange' of null". Unfortunately I am a brand new StackOverflow user and so I don't have enough reputation to comment on that post...
Important notes:
I have multiple non-contiguous ranges I need to apply this font size
shrinking to.
Some of the cells containing the text are merged cells
I am quite new to javascript/apps scripts but I'm a fast learner and I would really appreciate any guidance you can provide.
The solution code from the other post is copied below to save jumping back and forth between windows. Thanks so much!
function myFunction() {
const autoResizeFont = (range, toLarge) => {
const sheet = range.getSheet();
const ss = sheet.getParent();
const startColumn = range.getColumn();
const endColumn = range.getColumn() + range.getNumColumns();
const startRow = range.getRow();
const endRow = range.getRow() + range.getNumRows();
const columnObj = [];
for (let c = startColumn; c < endColumn; c++) {
columnObj.push({
column: c,
width: sheet.getColumnWidth(c)
});
}
const tempSheet = ss.insertSheet("tempForAutoresizeFont");
sheet.activate();
const tempRange = tempSheet.getRange("A1");
for (let r = startRow; r < endRow; r++) {
for (let c = 0; c < columnObj.length; c++) {
const srcRange = sheet.getRange(r, columnObj[c].column);
tempSheet.setColumnWidth(1, columnObj[c].width);
srcRange.copyTo(tempRange);
tempSheet.autoResizeColumn(1);
const resizedWidth = tempSheet.getColumnWidth(1);
tempSheet.setColumnWidth(1, columnObj[c].width);
const ratio = columnObj[c].width / resizedWidth;
if (ratio > 1 && !toLarge) continue;
const fontSize = srcRange.getFontSize();
srcRange.setFontSize(Math.ceil(fontSize * ratio));
}
}
ss.deleteSheet(tempSheet);
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
const targetRange = "A1:A6";
const toLarge = false;
autoResizeFont(sheet.getRange(targetRange), toLarge);
}
If you make the following changes to that example:
const sheet = ss.getSheetByName("Sheet1");//change the sheet name to whatever you want
const targetRange = sheet.getActiveRange().getA1Notation();//make this change and you can just select the range you want and run myFunction();
Read the comments above

Consolidate data into one master Google sheet using App Scripts

I am trying to consolidate data from multiple tabs into a consolidated sheet. Each tab is an individual form and has the same format. On the consolidated sheet, I want to re-arrange the data so the data field name is in a column, and data values are in rows. I tried the following:
function consolidateData(){
// defined all variables
var sheetNames = [];
var dataSheet = [];
var dataValues = [];
var conso=[];
var header = [["Faculty Name","Faculty ID","Date of Joining"]];
var ws = SpreadsheetApp.getActiveSpreadsheet();
// get all sheets
var allsheets = ws.getSheets();
for(var s in allsheets)
var sheet = allsheets[s];
sheetNames[s] = sheet.getName();
dataSheet[s] = ws.getSheetByName(sheetNames[s]);
// writing data into new sheet
var newSheet = ws.insertSheet().setName("Consolidated_Data");
newSheet.getRange("A1:C1").setValues(header);
var name = dataSheet[s].getRange("B1").getValue();
var id = dataSheet[s].getRange("B3").getValue();
var doj = dataSheet[s].getRange("B5").getValue();
var faculty = [(name),(id),(doj)];//convert into array
var facultycount = faculty.length;
for (var i = 0; i < faculty.length; i++)
//Loop through all rows and write them out starting on ROW2
{
newSheet.getRange(2 + i, 1).setValue(faculty[0]);//
newSheet.getRange(2 + i, 2).setValue(faculty[1]);//
newSheet.getRange(2 + i, 3).setValue(faculty[2]);//
}
}
There are four tabs and I expect to see results from each tab in the Consolidated_Data tab. But I only saw the last tab data got inserted repeatedly. Can anyone help? Thank you. Consolidated Data Sheet Example of an individual tab
While traversing through all your sheets, you haven't used curly braces after the for loop -
for(var s in allsheets)
So it's running the loop and the value of s stays at the last index of allsheets.
However, might I suggest a simplified version I have tested out -
function consolidateData () {
const headers = ["Faculty Name", "Faculty ID", "Date of joining"];
const rows = { "name": 0, "id": 2, "doj": 4 };
const consolidatedSheetName = "Consolidated_Data";
const ss = SpreadsheetApp.getActive();
const sheets = ss.getSheets();
let consolidatedValues = [];
// Setting headers
consolidatedValues.push(headers);
// Fetching values
for(let sheet of sheets) {
if(sheet.getName()==consolidatedSheetName) { continue; }
let data = sheet.getRange("B1:B5").getValues();
let faculty = [ data[rows.name][0], data[rows.id][0], data[rows.doj][0] ];
consolidatedValues.push(faculty);
}
// Adding to sheet
let consolidatedSheet = ss.getSheetByName(consolidatedSheetName);
if(!consolidatedSheet) {
consolidatedSheet = ss.insertSheet().setName(consolidatedSheetName);
}
let range = consolidatedSheet.getRange(1, 1, consolidatedValues.length, consolidatedValues[0].length); // 1, 1, 3, 3
range.setValues(consolidatedValues);
}
Issue:
The script is overwriting the same range here:
newSheet.getRange(2 + i, 1).setValue(faculty[0]);
Solution:
Add 1 for each row added:
newSheet.getRange(2 + i + s, 1).setValue(faculty[0]);
Or use sheet#appendRow()
newSheet.appendRow(faculty);
If you practice best practices, Your script can be simplified like this:
const consolidate = () => {
const ws = SpreadsheetApp.getActiveSpreadsheet(),
oldSheets = ws.getSheets(),
newSheet = ws.insertSheet().setName('Consolidated_Data');
newSheet.getRange(1, 1, 1 + oldSheets.length * 3, 3).setValues([
['Faculty Name', 'Faculty ID', 'Date of Joining'],
...oldSheets.map(sheet =>
sheet
.getRange('B1:B5')
.getValues()
.reduce((a, c, i) => (i % 2 === 0 ? [...a, c[0]] : a), [])
),
]);
};