How to find the last column using google apps script? - google-apps-script

I want to copy the data from G11:G22 to the last column of the sheet starting from row 11 by using onEdit function. So, the script will be triggered once I select Transfer in cell F7. After running the script, the data should appear in range H11:H22 since column H is the last column in the sheet. How should I achieved that? This is what I have tried:
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var lastrow = ss.getLastRow()
for( var k = 2; k <= lastrow ; k++) {
var m = 1;
while( sheet.getRange(k,m).isBlank() == false) {
m = m +1;
}
var lastcolumn = m
}
var destRange = ss.getRange(11,lastcolumn+1)
if (e.range.columnStart == 6 && e.range.rowStart == 7){
if (e.value == 'Transfer'){
var source = ss.getRange("G11:G22")
source.copyTo (destRange, {contentsOnly: true})
e.range.clearContent()
}
}
}
I think the problem comes from the For loop but I have no idea what is wrong. Hope to get some helps on this, thank you.

Based on your screenshot I suppose you can use getLastColumn()
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSheet();
var destRange = ss.getRange(11, ss.getLastColumn() + 1)
if (e.range.columnStart == 6 && e.range.rowStart == 7) {
if (e.value == 'Transfer') {
var source = ss.getRange("G11:G22")
source.copyTo(destRange, { contentsOnly: true })
e.range.clearContent()
}
}
}
No need for loop to get the last column if there is no more filled cels on the right side of the sheet.

Related

Use range instead of array of indivdual cells

I have this code which works for cells A2-A4:
//The function onEdit ensures that checkboxes deleted (by mistake) in the sheet are immediately re-created.
function onEdit(e) {
var spreadsheet = SpreadsheetApp.getActive();
if(spreadsheet.getSheetName()=='MySheet') { //to avoid executing for another sheet
var checkboxCells = [
'A2','A3','A4'];
var range = e.range;
var value = range.getValue();
var a1Notation = range.getA1Notation();
if (checkboxCells.indexOf(a1Notation) != -1 && value != 'TRUE' && value != 'FALSE') {
range.insertCheckboxes();
}
}
};
However I need to work with a 1D range (since there are many cells) and hence I am trying to use this:
//The function onEdit ensures that checkboxes deleted (by mistake) in the sheet are immediately re-created.
function onEdit(e) {
var spreadsheet = SpreadsheetApp.getActive();
if(spreadsheet.getSheetName()=='MySheet') { //to avoid executing for another sheet
var checkboxCells = spreadsheet.getSheetByName('MySheet')
.getRange('MyRange').getA1Notation();
var range = e.range;
var value = range.getValue();
var a1Notation = range.getA1Notation();
if (checkboxCells.indexOf(a1Notation) != -1 && value != 'TRUE' && value != 'FALSE') {
range.insertCheckboxes();
}
}
};
I think this second version should work, but it does not.
Why does it not work?
Modification points:
In order to check whether the edited cell is included in the specific range you expect, the following sample script can be used. (var { range } = e;)
var checkboxCells = sheet.getRange('MyRange');
var startRow = checkboxCells.getRow();
var endRow = startRow + checkboxCells.getNumRows() - 1;
var startCol = checkboxCells.getColumn();
var endCol = startCol + checkboxCells.getNumColumns() - 1;
var check = range.rowStart >= startRow && range.rowEnd <= endRow && range.columnStart >= startCol && range.columnEnd <= endCol;
In order to check whether the edited cell is the checkbox, isChecked() can be used. In this case, when the cell is the checkbox, true or false are returned. When the cell is not the checkbox, null is returned. I thought that this might be able to be used.
When these points are reflected in your script, how about the following modification?
Modified script:
Please set your sheet name and your range.
function onEdit(e) {
var { range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() == 'MySheet') {
var checkboxCells = sheet.getRange('MyRange');
var startRow = checkboxCells.getRow();
var endRow = startRow + checkboxCells.getNumRows() - 1;
var startCol = checkboxCells.getColumn();
var endCol = startCol + checkboxCells.getNumColumns() - 1;
var check = range.rowStart >= startRow && range.rowEnd <= endRow && range.columnStart >= startCol && range.columnEnd <= endCol;
if (check && range.isChecked() === null) {
range.insertCheckboxes();
}
}
}
In this modified script, when a cell is edited, when the edited cell is included in MyRange, it checks whether the edited cell is a checkbox when the edited cell is not a checkbox, the checkboxes are inserted into the edited cell.
Reference:
isChecked()

When info is pasted into Cell 'J3' A time stamp is placed in 'AA1', and A time stamp is placed in the next empty cell of column 'AI'

I'm trying to make a script that when info is pasted into Cell 'J3' it will make a time stamp in cell 'AA1' and additionally it will also make a time stamp in column 'AI' in the first cell that doesn't already have a time stamp.
This would be so the sheet makes a time stamp that gets overridden each time that info is pasted into the sheet, but also make a static time stamp separately of each time info is pasted into the sheet.
I have a script that makes the timestamp that only updates when info is pasted into 'J3'
function:
onEdit(e) {
const sh = e.range.getSheet();
if (e.range.columnStart == 10 && e.range.rowStart == 3 && !e.value) {
sh.getRange("AA1").setValue(new Date());
}
}
I also have a script that takes info from one tab and copies it over to another tab on the first available row.
function Delete() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Testing'), true);
jumpToFirstEmptyBasic()
spreadsheet.getRange('Live Data!S2:AE1001').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Live Data'), true);
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A2:M1001').activate();
spreadsheet.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
spreadsheet.getRange('Live Data!A2')
};
function jumpToFirstEmptyBasic() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Testing"); // << Change the sheet name to your own sheet.
var lastRow = sheet.getLastRow();
Logger.log(lastRow);
sheet.getRange(lastRow+1,1).activate();
};
Over all, I'm trying to add some of the seconds code to the first so that it has a second function of making a list of time stamps on the same tab whenever info is pasted into the 'J3' cell.
So far this is what I have put together, but it doesn't work, and I'm not experienced enough with python to find out why:
function onEdit(e) {
const sh = e.range.getSheet();
if (e.range.columnStart == 10 && e.range.rowStart == 3 && !e.value) {
sh.getRange("AA1").setValue(new Date());
jumpToFirstEmptyBasic()
sh.getRange(lastRow).setValue(new Date());
}
function jumpToFirstEmptyBasic() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lastRow = ss.getLastRow();
Logger.log(lastRow);
ss.getRange(lastRow+1,1).activate();
};
}
Try this:
function onEdit(e) {
e.source.toast("Entry");
const sh = e.range.getSheet();
if (e.range.columnStart == 10 && e.range.rowStart == 3) {
e.source.toast("Gate1");
let lr = Number(getColumnHeight(27, sh, e.source));
sh.getRange(Number(lr) + 1, 27).setValue(new Date());
sh.getRange("AA1").setValue(new Date();)
e.source.toast(`The are ${Number(lr) + 1} timestamps on this page.`);
}
}
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;
//const h = Utilities.formatString('col: %s len: %s', col, rcA.length - s);
//Logger.log(h);
//SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(h).setWidth(150).setHeight(100), 'Col Length')
}
This may also work for you. But I find it unreliable at times and I'm not sure why.
function onEdit(e) {
e.source.toast("Entry");
const sh = e.range.getSheet();
if (e.range.columnStart == 10 && e.range.rowStart == 3) {
e.source.toast("Gate1");
let lr = Number(sh.getRange("AA1").getDataRegion(SpreadsheetApp.Dimension.ROWS).getHeight());
sh.getRange(Number(lr) + 1, 27).setValue(new Date());
sh.getRange("AA1").setValue(new Date());
e.source.toast(`The are ${Number(lr) + 1} timestamps on this page.`);
}
}
But if it works for you it will relieve you of using the helper function getColumnHeight();

Google script - optimization (populate multiple columns if cell value)

I have a script that populates with today's date column J when column A is filled.
function Populate() {
var sheetNameToWatch = "MASTER";
var columnNumberToWatch = /* column A */ 1;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
var val=sheet.getActiveCell().getValue()
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && val!= "" ) {
var targetCell = sheet.getRange(range.getRow(), range.getColumn()+9
);
targetCell.setValue("" + Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd"));
}
}
I think it's quite slow and I also would like to fill more columns at once, aside from the date on Column J:
Column I: "No payment"
Column L: "PENDING"
In order to fill multiple columns and try to make it work faster, I've also tested another version:
function Populate2(e)
{
var sheet = e.source.getActiveSheet();
if (sheet.getName() !== 'MASTER'|| e.range.getColumn() !== 1)
{
return;
}
for(var i=0;i<e.range.getNumRows();i++)
{
var offset=e.range.getRow() + i;
sheet.getRange('I'+ offset).setValue("No Payment");
sheet.getRange('J'+ offset).setValue(new Date());
sheet.getRange('L'+ offset).setValue("PENDING");
}
}
The last version has the problem that even if I clean the column A, the values are filled.
Couldn't figure out which version - if any - would be the best approach to improve regarding efficiency, and how.
Can anyone give me a hand?
Thank you in advance.
I believe your goal is as follows.
You want to reduce the process cost of your script.
When the values of column "A" are removed, you want to clear the columns "I", "J", and "L".
In this case, how about the following modification?
Modified script:
function Populate2(e) {
var range = e.range;
var sheet = e.source.getActiveSheet();
if (sheet.getSheetName() !== 'MASTER' || range.columnStart !== 1) {
return;
}
var values = range.getDisplayValues();
var { noPayment, date, pending, clear } = values.reduce((o, [a], i) => {
var row = range.rowStart + i;
if (a == "") {
o.clear.push(...["I", "J", "L"].map(e => e + row));
} else {
o.noPayment.push("I" + row);
o.date.push("J" + row);
o.pending.push("L" + row);
}
return o;
}, { noPayment: [], date: [], pending: [], clear: [] });
if (noPayment.length > 0) {
sheet.getRangeList(noPayment).setValue("No Payment");
sheet.getRangeList(date).setValue(new Date());
sheet.getRangeList(pending).setValue("PENDING");
}
if (clear.length > 0) {
sheet.getRangeList(clear).clearContent();
}
}
In this modification, the values are put to the cells using the range list. And also, the cells are cleared using the range list.
Note:
From your question, this modified script supposes that your function Populate2 is installed as OnEdit trigger. Please be careful about this.
I think that in your script, onEdit simple trigger might be also used. But, I'm not sure about your actual situation. So I used Populate2 in your script.
References:
getRangeList(a1Notations)
Class RangeList
Try this:
function Populate2(e) {
//e.source.toast('Entry');
var sh = e.range.getSheet();
if (sh.getName() !== 'Master' || e.range.columnStart !== 1) { return; }
//e.source.toast('flag1')
let n = e.range.rowEnd - e.range.rowStart + 1;
let dt = new Date();
let i = [...Array.from(new Array(n).keys(),x => ["No Payment"])];
let j = [...Array.from(new Array(n).keys(),x => [dt])];
let l = [...Array.from(new Array(n).keys(),x => ["PENDING"])];
sh.getRange(e.range.rowStart, 9, n).setValues(i);
sh.getRange(e.range.rowStart, 10, n).setValues(j);
sh.getRange(e.range.rowStart, 12, n).setValues(l);
}
or
function Populate2(e) {
//e.source.toast('Entry');
var sh = e.range.getSheet();
if (sh.getName() !== 'Master' || e.range.columnStart !== 1) { return; }
//e.source.toast('flag1')
let n = e.range.rowEnd - e.range.rowStart + 1;
let dt = new Date();
let i = [...Array.from(new Array(n).keys(),x => ["No Payment",dt])];
let l = [...Array.from(new Array(n).keys(),x => ["PENDING"])];
sh.getRange(e.range.rowStart, 9, n, 2).setValues(i);
sh.getRange(e.range.rowStart, 12, n).setValues(l);
}
Array.from()
Array Constructor

Modifying a flexible Google Sheets onEdit script to accommodate a wider range of inputs

Below is a script that allows me to combine five different onEdit functions into a single function.
function onEdit(e){
if (e.range.getA1Notation() != "H12" || e.value != "submitResponse") return;
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getActiveSheet();
var m = sh.getSheetByName("Master");
ss.getRange(2,1,ss.getLastRow(),3).copyTo(m.getRange(m.getLastRow()+1,1,ss.getLastRow(),3));
ss.getRange('H12').clearContent();
e.range.clearContents();
}
I'm now hoping to make this script more flexible. See Sheet1 of my spreadsheet: https://docs.google.com/spreadsheets/d/1EoOIQxWyKWOvtlCrmJNI76FAxGhzgXrE4s0F05tw2MY/edit#gid=0
It would be great if instead of limiting myself to H12, I could use the entire column of H:H to submit each corresponding row. So when I change H2 to submitResponse, it copies/pastes A2:G2 to the last row of the Master. When I change H3 to submitResponse, it copies/pastes A3:G3 to the last row of the Master. And so forth.
I tried my hand at this but no luck on execution. I guess I'm missing something.
​function onEdit(e) {
var ss = e.source;
var s = ss.getActiveSheet();
var r = e.range;
var actionCol = 8;
var rowIndex = r.getRowIndex();
var colIndex = r.getColumnIndex();
var colNumber = s.getLastColumn()-1;
if (e.value == "submitResponse" && colIndex == actionCol) ;
var sourceRange = s.getRange(rowIndex, 1, 1, colNumber);
var targetRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Master").getRange(getLastRow()+1, 1, 1, colNumber);
sourceRange.copyTo(targetRange,SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
Try this:
function onEdit(e){
var sh=e.range.getSheet();
if (sh.getName()=='Sheet1' && e.range.columnStart==8 && e.range.rowStart>1 && e.value=="submitResponse") {
var msh=e.source.getSheetByName("Master");
msh.appendRow(sh.getRange(e.range.rowStart,1,1,7).getDisplayValues()[0]);
}
}
function onEdit(e){
var sh=e.range.getSheet();
if (sh.getName()=='Sheet1' && e.range.columnStart==8 && e.range.rowStart>1 && e.value=="Yes") {
e.range.setValue('');
var msh=e.source.getSheetByName("Master");
msh.appendRow(sh.getRange(e.range.rowStart,1,1,7).getDisplayValues()[0]);
}
}

Copy down Formula in Google Sheets when cell is edited with any number or text

So I have been working on a script to copy a formula down when a cell is edited to Yes in the column next to. This is working.
My question is I'd like to copy down the formula if there's a text or number in that a cell.
How can I add this my script.
I am fairly knew to this so any advice or documentation would be appreciated.
function onEdit(){
var sheetNameToWatch = "Request for Purchases";
var columnNumberToWatch = 10;
var valueToWatch="Yes";
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getRange("K2").setFormula("=(c2*d2)");
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
var val = sheet.getActiveCell().getValue();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && val==valueToWatch ) {
var targetCell = sheet.getRange(range.getRow(), range.getColumn()+1);
var d = ss.getRange("K2").copyTo(targetCell);
}
}
Is this what your trying to do?
function onEdit(e){
var sh=e.range.getSheet();
if(sh.getName()!='Request for Purchases') return;
if(e.range.columnStart==10 && e.value="Yes" ) {
e.range.offset(0,1).setFormula("=(C2*D2)");
}
}