Gsheet - Check last empty row and set data as array per row - google-apps-script

I do not know how to optimize this. I just want to check the last empty row and paste the array data horizontally. This is my current setup where I manually set the location of each row. I tried using .setValues and [[row[1], row[2]] but the problem is I am only referencing 1 row with getLastRow().
const sourceData = source.getDataRange().getValues().slice(1);
sourceData.forEach((row, i)=> {
if(row[0] == "") {
return;
} else {
let id = row[0];
var database = SpreadsheetApp.openById(id);
var copyToSheet = database.getSheetByName("Sheet1");
var lastRow = copyToSheet.getLastRow();
copyToSheet.getRange(lastRow + 1, 1).setValue(row[2]);
copyToSheet.getRange(lastRow + 1, 2).setValue(row[3]);
copyToSheet.getRange(lastRow + 1, 3).setValue(row[4]);
}
});

Try this:
function lfunk() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getDataRange().getValues().slice(1);
vs.forEach((row, i) => {
if (row[0]) {
var ssi = SpreadsheetApp.openById(row[0]);
var shi = ssi.getSheetByName("Sheet1");
sh1.getRange(shi.getLastRow() + 1, 1, 1, 3).setValues([[row[2], row[3], row[4]]])
}
});
}
This will still potentially take a while because you have to wait to open up all of the spreadsheets

Related

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

Pop() and append() to next row

In this spreadsheet, I have a row with all the employee data, with values coming from another table; In order to create the layout requested, I need 3 rows per employee, each row starting with the |1|,|1.1|,|1.1|, I found some code here that allows me to append two rows to every row of data, but no idea how to move the |1.1|,GTF,'100' to the next row, and then, |1.1|,PPA,'15' to the next one.
This is what i have:
|1|,'900','04000','0000','0','0000','0','11','0','S','A',|1.1|,GTF,'100',|1.1|,PPA,'15'
|1|,'986','01000','0000','0','0000','0','14','0','S','A',|1.1|,GTF,'110',|1.1|,PPA,'30'
|1|,'1046','01000','0000','0','0000','0','14','0','S','A',|1.1|,GTF,'120',|1.1|,PPA,'45'
And what Im looking for is to look like this:
|1|,'900','04000','0000','0','0000','0','11','0','S','A'
|1.1|,GTF,'100'
|1.1|,PPA,'15'
|1|,'986','01000','0000','0','0000','0','14','0','S','A',
|1.1|,GTF,'110'
|1.1|,PPA,'30'
function insertRows() {
var startRow = 1;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Prenom");
var sourceRange = sheet.getRange(startRow, 1, sheet.getLastRow());
var sheetData = sourceRange.getValues();
var numRows = sourceRange.getNumRows() - startRow;
// Logger.log(numRows);
for (var i=numRows; i > -1; i--) {
if (sheetData[i].join("")) {
sheet.insertRowsAfter(i + startRow, 2);
}
}
}
Which leaves me with two empty rows. ty
You have to split the cell value every time |1.1| is found. There are several ways to do this. For example, you can combine indexOf and splice and in the sample below.
After you have done that, use setValues to overwrite the original data.
Code sample:
function insertRows() {
var startRow = 1;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Prenom");
var sourceRange = sheet.getRange(startRow, 1, sheet.getLastRow());
var sheetData = sourceRange.getValues().flat();
sheetData = sheetData.map(cell => {
const array = cell.split(",");
const outputArray = [];
let index = 0;
while (index >= 0) {
index = array.indexOf("|1.1|", 1);
if (index !== -1) section = array.splice(0, index).join(",");
else section = array.join(",");
outputArray.push(section);
}
return outputArray;
}).flat().map(row => [row]);
sheet.getRange(startRow, 1, sheetData.length, sheetData[0].length).setValues(sheetData);
}
function separate() {
const s = `|1|,'900','04000','0000','0','0000','0','11','0','S','A',|1.1|,GTF,'100',|1.1|,PPA,'15'
|1|,'986','01000','0000','0','0000','0','14','0','S','A',|1.1|,GTF,'110',|1.1|,PPA,'30'
|1|,'1046','01000','0000','0','0000','0','14','0','S','A',|1.1|,GTF,'120',|1.1|,PPA,'45'`;
let r = s.match(/([^']+[^|]+)/g);
Logger.log(r.join('\n'));
}
Execution log
12:02:55 PM Notice Execution started
12:02:55 PM Info ||1|,'900','04000','0000','0','0000','0','11','0','S','A',
|1.1|,GTF,'100',
|1.1|,PPA,'15'
|1|,'986','01000','0000','0','0000','0','14','0','S','A',
|1.1|,GTF,'110',
|1.1|,PPA,'30'
|1|,'1046','01000','0000','0','0000','0','14','0','S','A',
|1.1|,GTF,'120',
|1.1|,PPA,'45'
12:02:56 PM Notice Execution completed
function separate() {
const s = `|1|,'900','04000','0000','0','0000','0','11','0','S','A',|1.1|,GTF,'100',|1.1|,PPA,'15'
|1|,'986','01000','0000','0','0000','0','14','0','S','A',|1.1|,GTF,'110',|1.1|,PPA,'30'
|1|,'1046','01000','0000','0','0000','0','14','0','S','A',|1.1|,GTF,'120',|1.1|,PPA,'45'`;
let r = s.match(/([^']+[^|]+)/g).map(e => [e]);
let sh = SpreadsheetApp.getActiveSheet();
sh.getRange(1,1,r.length).setValues(r);
}

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), [])
),
]);
};

Adding a timestamp when specific data is entered from another sheet in Google Sheet

I have a Google Sheet containing two sheets, one named Form and the other Data. Information on the Data sheet is usually entered via the Form sheet, where a button click on the Form sheet sends the data over to the bottom row on the Data sheet.
I'm trying to figure out how I can add a timestamp to the appropriate cell in column E on the Data sheet when a cell on the same row in column D receives the word 'CLEANED' either as a submission from the Form sheet or as a direct manual entry on the Data sheet.
This code currently adds the timestamp, but only when I change a value in column D directly on the Data sheet (not when values are added via the Form sheet), and it adds the timestamp for any value that's added.
var ColumnToCheck = 4;
var DateTimeLocation = [0,1];
var sheetName = 'Data'
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
if( sheet.getSheetName() == sheetName ) {
var selectedCell = ss.getActiveCell();
if( selectedCell.getColumn() == ColumnToCheck) {
var dateTimeCell = selectedCell.offset(DateTimeLocation[0],DateTimeLocation[1]);
dateTimeCell.setValue(new Date());
}
}
}
I am using this code to transfer data from the Form sheet:
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Form");
var dataSheet = ss.getSheetByName("Data");
var values = formSS.getRange("B2:B5").getValues().reduce(function(a, b) {
return a.concat(b)
});
var partNum = values[0];
var row;
dataSheet.getDataRange().getValues().forEach(function(r, i) {
if (r[0] === partNum) {
row = i + 1
}
})
row = row ? row : dataSheet.getLastRow() + 1;
var data = dataSheet.getRange(row, 1, 1, 4).getValues()[0].map(function (el, ind){
return el = values[ind] ? values[ind] : el;
})
dataSheet.getRange(row, 1, 1, 4).setValues([data]);
formSS.getRange("B2:B5").clearContent()
}
What would be the best way to have the date added automatically when only the word 'CLEANED' is entered? The date shouldn't clear if the word 'CLEANED' is removed or changed to a different word.
EDIT - SOLUTION
Thanks to Sourabh Choraria, I was able to change my UPDATE function to this in order to incorporate my needs (my additions to his code are commented):
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Form");
var dataSheet = ss.getSheetByName("Data");
var values = formSS.getRange("B3:B6").getValues().reduce(function(a, b) {
return a.concat(b)
});
var partNum = values[0];
var row;
dataSheet.getDataRange().getValues().forEach(function(r, i) {
if (r[0] === partNum) {
row = i + 1
}
})
row = row ? row : dataSheet.getLastRow() + 1;
var data = dataSheet.getRange(row, 1, 1, 4).getValues()[0].map(function (el, ind){
return el = values[ind] ? values[ind] : el;
})
var statusValue = formSS.getRange("B6").getValue(); //added
if (statusValue != 'CLEANED') { //added
dataSheet.getRange(row, 1, 1, 4).setValues([data]); //added
} //added
if (statusValue == 'CLEANED') { //added
var now = [new Date()];
var newData = data.concat(now)
dataSheet.getRange(row, 1, 1, 5).setValues([newData]);
} //added
formSS.getRange("B3:B6").clearContent()
}
Update the submitData() function with the following code -
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Form");
var dataSheet = ss.getSheetByName("Data");
var values = formSS.getRange("B2:B5").getValues().reduce(function(a, b) {
return a.concat(b)
});
var partNum = values[0];
var row;
dataSheet.getDataRange().getValues().forEach(function(r, i) {
if (r[0] === partNum) {
row = i + 1
}
})
row = row ? row : dataSheet.getLastRow() + 1;
var data = dataSheet.getRange(row, 1, 1, 4).getValues()[0].map(function (el, ind){
return el = values[ind] ? values[ind] : el;
})
// modification begins here
var now = [new Date()];
var newData = data.concat(now)
dataSheet.getRange(row, 1, 1, 5).setValues([newData]);
// modification ends here
formSS.getRange("B2:B5").clearContent()
}
Basically, I'm declaring a new array variable (now) and concatenating it to the existing data array, thus forming a "newData" variable and then updating the setValues function to accommodate this change.
Hope this helps and thanks for sharing the view access to the sheet - I made a copy of that and tested the solution directly :)
Edit note1
As for updating the sheet only when the dropdown selected is 'CLEANED', kindly modify your onEdit() function to accommodate the following IF condition -
var statusValue = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form').getRange("B6").getValue();
if (statusValue == 'CLEANED') {
// update timestamp as required
}

How to check particular value in google spreadsheet

I picked this code online and I am trying to check if the value in the column B is 'Done' then the value will be be copied otherwise not. Here is the code I am using:
copy sheet function below will copy the datat from source sheet to destination sheet but what I want that it will only pick the row if the col B value contains Done
function copySheet() {
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Source");
var destSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Destination");
var columns_to_be_copied =['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U'];
var columns_to_be_pasted =['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U'];
for (column in columns_to_be_copied) {
var copy_range_string = columns_to_be_copied[column] + ':' + columns_to_be_copied[column];
var paste_range_string = columns_to_be_pasted[column] + ':' + columns_to_be_pasted[column];
var source = sourceSheet.getRange(copy_range_string);
var destination = destSheet.getRange(paste_range_string);
if(findInColumn('A','Done') !== -1) {
copyTo(source,destination );
}
}
}
function copyTo(source,destination) {
var sourceSheet = source.getSheet();
var destSheet = destination.getSheet();
var sourceData = source.getValues();
var dest = destSheet.getRange(
destination.getRow(), // Top row of destination
destination.getColumn(), // left col of destination
sourceData.length, // # rows in source
sourceData[0].length); // # cols in source (elements in first row)
dest.setValues(sourceData);
SpreadsheetApp.flush();
}
function findInColumn(column, data) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sprint");
var column = sheet.getRange(column + ":" + column); // like A:A
var values = column.getValues();
var row = 0;
while (values[row] && values[row][0] !== data) {
row++;
}
if (values[row][0] === data)
return row+1;
else
return -1;
}
As I am a fan of simple and easy to read (even after long time) solutions I would suggest the following script:
function main() {
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Source');
var destinationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Destination');
copyFromTo(sourceSheet, destinationSheet);
}
function copyFromTo(sourceSheet, destinationSheet) {
const ColumnB = 1; //Array indexing starts from 0
const FilterValue = 'Done';
var sourceValues = sourceSheet.getSheetValues(1, 1, 100, 28); //startRow, startColumn, numRows, numColumns
var filteredValues = sourceValues.filter(function(row) {
return row[ColumnB] === FilterValue;
});
destinationSheet.getRange(1, 1, filteredValues.length, filteredValues[0].length).setValues(filteredValues);
}
It's about the same function. I just modified it to facilitate my debugging process. It copies the columns from source to destination if Sprint has "Done" in that column.
function copySheet() {
var srcsh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Source");
var dessh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Destination");
var from = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U'];
var to = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U'];
for(var i=0;i<from.length;i++) {
var fromrg = from[i] + ':' + from[i];
var torg = to[i] + ':' + to[i];
var src = srcsh.getRange(fromrg);
var des = dessh.getRange(torg);
if(findInColumn(from[i],'Done')!== -1){
src.copyTo(des);
}
}
}
function findInColumn(col, data) {
var col=col || 'A';//This is here for initial testing so I could run the function without parameters.
var data=data || 'Done';
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sprint");
var rg = sh.getRange(col + "1:" + col + sh.getMaxRows());//MaxRows is kind of big but I was not sure what else you'd like to do and I dont know what your Sprint sheet looks like.
var vA = rg.getValues();
var rv=-1;
for(var i=0;i<vA.length;i++){
if(vA[i][0]==data){
rv=i+1;
break;
}
}
return rv;
}
I see that you changed the question a bit. This function looks in your sprint sheet as you show in your answer and copies from source to destination only those columns that have the word "Done" on any row of that column. But it checks every column in your "columns_to_be_copied" which I called "from". Originally, that's what your function was trying to do. So I just wanted to be clear what this function is doing. If it's not what you want then leave a comment and I'll delete it.