Very new to the Google Sheets script editing, but I'm trying to move rows between sheets. I've done this successfully using getLastRow however now I'm attempting to do this based on a specific column. The reason for this is that the sheet the info is heading to has preset data validation fields and checkboxes, resulting in the row going to row 1000 every time.
I'm looking for when an option is selected on the dropdown menu on the current sheet, it will find the first free row based on a certain column, and replace that row (which is essentially empty except for the preset fields).
Currently I have as follows:
function onEdit(event) {
// Move rows to different sheets based on action selected
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = SpreadsheetApp.getActiveRange();
if(range.getColumn() == 5 && range.getValue() == "Active") {
var row = range.getRow();
var numColumns = sheet.getLastColumn();
var targetSheet = spreadsheet.getSheetByName("Active");
var target = targetSheet.getRange(targetSheet.getLastRowSpecial(8) + 1, 1);
sheet.getRange(row, 1, 1, numColumns).moveTo(target);
sheet.deleteRow(row);
and I found the below and other examples when googling but nothing seems to work:
function getLastRowSpecial(column){
// Get the last row with data for the whole sheet.
var numRows = targetSheet.getLastRow();
// Get all data for the given column
var data = targetSheet.getRange(1, column, numRows).getValues();
// Iterate backwards and find first non empty cell
for(var i = data.length - 1 ; i >= 0 ; i--){
if (data[i][0] != null && data[i][0] != ""){
return i + 1;
}
}
}
Any help is greatly appreciated!
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//e.source.toast(e.range.columnStart, e.value)
var sh=e.range.getSheet();
if(e.range.columnStart==8 && e.value=="Active") {
var tsh=e.source.getSheetByName("Active");
var target=tsh.getRange(getColumnHeight(8,tsh,e.source) + 1, 1);
sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).moveTo(target);
sh.deleteRow(e.range.rowStart);
}
}
function getColumnHeight(col,sh,ss){
var ss=ss||SpreadsheetApp.getActive();
var sh=sh||ss.getActiveSheet();
var col=col||sh.getActiveCell().getColumn();
var s=0;
var h=0;
if(sh.getLastRow()) {
var v=sh.getRange(1,col,sh.getLastRow(),1).getValues().map(function(r){return r[0];});
v.forEach(function(e,i){if(e==''){s++;}else{s=0;}h++;});
}
return (h-s);
}
Related
I need to create function which adds rows with copied formulas from above rows. After the script is launched it should result in accurate number (set 5 i this code) of blank rows at the end of the sheet.
The code I managed to create counts what number of rows should be added but adds only one row with copied formulas at the end.
Please help me edit this code to multiple the result of the function by "rowstoadd" parameter.
Sheet image
function autoaddRows() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Harmonogram');
var range = sheet.getRange("B2:B").getValues();
var lastRowB = range.filter(String).length + 2;
var lastRowA = sheet.getLastRow();
var blanknrows = sheet.getLastRow() - lastRowB;
if (blanknrows < 5) {
let rowstoadd = 5 - blanknrows;
Browser.msgBox(rowstoadd);
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
let lastRowIndex = sheet.getLastRow();
let existingRange = getRowRange(sheet, lastRowIndex);
sheet.insertRowAfter(lastRowIndex);
let newRange = getRowRange(sheet, ++lastRowIndex);
existingRange.copyTo(newRange);
newRange.setFormulas(newRange.getFormulas());
newRange.clearNote();
}
function getRowRange(sheet, rowIndex) {
return sheet.getRange(rowIndex, 1, 1, sheet.getLastColumn());
}
}
I believe your goal is as follows.
You want to keep 5 new empty rows.
For example, when the last row of column "A" is 10 and the last row of column "B" is 9, you want to add 4 new rows.
Also, you want to put the formula to the column "B" of the inserted rows.
In this case, how about the following modified script?
Modified script:
function autoaddRows() {
var addRows = 5;
var sheet = SpreadsheetApp.getActive().getSheetByName('Harmonogram');
var range = sheet.getRange("B2:B").getValues();
var lastRowB = range.filter(String).length + 1;
var lastRow = sheet.getLastRow();
var blanknrows = lastRow - lastRowB;
var diff = addRows - blanknrows;
if (diff > 0) {
sheet.insertRowsAfter(lastRow, diff);
// var range = sheet.getRange("B" + lastRowB);
// range.copyTo(range.offset(1, 0, diff + 1), SpreadsheetApp.CopyPasteType.PASTE_FORMULA, false);
var numberOfCol = sheet.getLastColumn() - 1;
var range = sheet.getRange(lastRowB, 2, 1, numberOfCol);
range.copyTo(range.offset(1, 0, diff + 1, numberOfCol), SpreadsheetApp.CopyPasteType.PASTE_FORMULA, false);
range.clearNote();
}
}
In this modification, the difference between the last rows between column "A" and column "B" is retrieved. And the empty rows are inserted using the difference.
In your script, newRange.clearNote(); is used. So, I also add range.clearNote();. If you want to remove it, please remove it.
References:
insertRowsAfter(afterPosition, howMany)
copyTo(destination, copyPasteType, transposed)
I would like to create a simple google apps script to copy specific column into another sheets.
Previously I tried using getLastRow but I get stuck to modify it.
var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow();
Here is my spreadsheet: https://docs.google.com/spreadsheets/d/1rGvmlKCmbjDSCLCC2Kujft5e4ngbSLzJd2NYu0sxISs/edit?usp=sharing
And here is the modified script so far:
function pasteMultiCol(sourceSheet, destinationSheet,sourceColumns,destinationColumns, doneColumn){
var sourceDataRange = sourceSheet.getDataRange();
var sourceDataValues = sourceDataRange.getValues();
var sourcesheetFirstRow = 0;
var sourceSheetLastRow = sourceDataRange.getLastRow();
var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow();
var pendingCount = 0;
//Find the row start for copying
for(i = 0; i < sourceDataValues.length; i++){
if(sourceDataValues[i][doneColumn-1] === "Copied"){
sourcesheetFirstRow++;
};
if(sourceDataValues[i][doneColumn-1] === ""){
pendingCount++;
};
};
//Update Source sheet first row to take into account the header
var header = sourceSheetLastRow-(sourcesheetFirstRow + pendingCount);
sourcesheetFirstRow = sourcesheetFirstRow+header;
// if the first row equals the last row then there is no data to paste.
if(sourcesheetFirstRow === sourceSheetLastRow){return};
var sourceSheetRowLength = sourceSheetLastRow - sourcesheetFirstRow;
//Iterate through each column
for(i = 0; i < destinationColumns.length; i++){
var destinationRange = destinationSheet.getRange(destinationSheetLastRow+1,
destinationColumns[i],
sourceSheetRowLength,
1);
var sourceValues = sourceDataValues.slice(sourcesheetFirstRow-1,sourceSheetLastRow);
var columnValues =[]
for(j = header; j < sourceValues.length; j++){
columnValues.push([sourceValues[j][sourceColumns[i]-1]]);
};
destinationRange.setValues(columnValues);
};
//Change Source Sheet to Copied.
var copiedArray =[];
for(i=0; i<sourceSheetRowLength; i++){copiedArray.push(["Copied"])};
var copiedRange = sourceSheet.getRange(sourcesheetFirstRow+1,doneColumn,sourceSheetRowLength,1)
copiedRange.setValues(copiedArray);
};
function runsies(){
var ss = SpreadsheetApp.openById("1snMyf8YZZ0cGlbMIvZY-fAXrI_dJpPbl7rKcYCkPDpk");
var source = ss.getSheetByName("Source");
var destination = ss.getSheetByName("Destination");
var sourceCols = [4,5,6,7,8,9,10];
var destinationCols = [7,8,9,10,11,12,13];
var doneCol = 12
//Run our copy and append function
pasteMultiCol(source,destination, sourceCols, destinationCols, doneCol);
};
Your code is taken from my tutorial in my blog article Copy Selected Columns in One Sheet and Add Them To The Bottom of Different Selected Columns in Another Sheet and it just needs a tweak.
I think the issue might be that you have a bunch of formulas in other columns in your "Destination" Sheet tab. So getting the last row of the sheet will result in getting the last row considering all the data including your other formulas.
You might find this explanation in a follow up blog article I wrote helpful: Google Apps Script: Get the last row of a data range when other columns have content like hidden formulas and check boxes
In short, you can change the destinationSheetLastRow variable to something simple like this.
var destinationSheetLastRow = (()=>{
let destinationSheetFirstRow = 7; // The first row of data after your header.
//Get a sample range to find the last value in the paste columns.
let sampleRange = destinationSheet.getRange(destinationSheetFirstRow,
destinationColumns[0],
destinationSheet.getLastRow())
.getValues();
let sampleLastRow = 0;
while(sampleLastRow < sampleRange.length){
if (sampleRange[sampleLastRow][0] == ""){
break;
}
sampleLastRow++;
};
return sampleLastRow;
})()
I have a very large data set with clients and prices but not all clients get charged every price. When I filter out a client, I need to be able to run a macro to hide all of the columns that do not have a price associated with the header. I had the macro in Excel working fine but cannot transfer it into google sheets.
Excel VBA that worked perfectly:
Sub KolumnHider()
Dim wf As WorksheetFunction
Dim i As Long, r As Range
Set wf = Application.WorksheetFunction
For i = 1 To 1000
Set r = Cells(1, i).EntireColumn
If wf.Subtotal(3, r) < 2 Then r.Hidden = True
Next i
End Sub
This formula below is almost what I need. My issue is that I need it to hide the columns based on what is showing. When I filter out the data, I then want to run the macro and hide the columns with empty cells. It works when I filter to the certain row that 'ss.GetRange(3,1,1)' implies. For this example, If I filter to row 3 it works, but I have to change the code to say 6,1,1 for it to hide the correct info on row 6. I need it to hide only the row showing. Please help!
function hideEmptyHeaders() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var headers = ss.getRange(3, 1, 1, ss.getMaxColumns()).getValues()[0];
var columnIndex = 0, numColumns = 0;
headers.forEach(function(header, index) {
if (!header) {
if (!columnIndex)
columnIndex = index + 1;
numColumns++;
} else if (columnIndex > 0) {
ss.hideColumns(columnIndex, numColumns);
columnIndex = numColumns = 0;
}
});
if (columnIndex > 0) ss.hideColumns(columnIndex, numColumns);
}
Try this:
function hideBlankColumns() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var width=rg.getWidth();//could also be sh.getMaxColumns()
for(var c=1;c<=width;c++) {
if(getColHeight(c,sh,ss)==0) {
sh.hideColumns(c);
}
}
}
function getColHeight(col,sh,ss){
var ss=ss || SpreadsheetApp.getActive();
var sh=sh || ss.getActiveSheet();
var col=col || sh.getActiveCell().getColumn();
var rg=sh.getRange(1,col,sh.getLastRow(),1);
var vA=rg.getValues();
if(vA) {
while(vA.length && vA[vA.length-1][0].length==0){
vA.splice(vA.length-1,1);
}
return vA.length;
}
return 0;
}
Try this,
function myFunction() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var i;
var j;
for(i=1;i<=1000;i++)
{
var count = 0;
var r = sheet.getLastRow();
for(j=1;j<r;j++)
{
if(!sheet.getRange(j,i).isBlank())
{
count++;
}
}
if(count < 2)
{
sheet.hideColumns(i);
}
}
}
Problem
You have rows hidden by a filter
You want to hide all columns, which do not have contents in the non-hidden cells
Solution
You need a function that returns you the hidden rows
You loop through all columns
For each column you loop through all rows and check if the row is not hidden
For all non-hidden rows, you check if the cell in given column has contents
If non of the non-hidden cells in a column has contents, you hide this column
If I understood right that this is what you want to do - here is a script that allows you to do so.
function getIndexesOfFilteredRows(ssId, sheetId) {
var hiddenRows = [];
// get row metadata to find the hidden rows
var fields = "sheets(data(rowMetadata(hiddenByFilter)),properties/sheetId)";
var sheets = Sheets.Spreadsheets.get(ssId, {fields: fields}).sheets;
//Find the right sheet
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].properties.sheetId == sheetId) {
var data = sheets[i].data;
var rows = data[0].rowMetadata;
for (var j = 0; j < rows.length; j++) {
//push the indexes of all hodden rows into an array
if (rows[j].hiddenByFilter) hiddenRows.push(j);
}
}
}
//return indexes of hidden rows
return hiddenRows;
}
function hideEmptyHeaders() {
var ssId='XXXXX';
var sheetId=0;// adjust if necessary
//get the rows that are hidden by a filter
var hidden=getIndexesOfFilteredRows(ssId, sheetId);
var sheet=SpreadsheetApp.openById(ssId).getSheets()[sheetId];
//get all sheet contents
var rangeValues=sheet.getDataRange().getValues();
//check for every column either the not hidden part of the column is empty
for(var j=0;j<rangeValues[0].length;j++) {
// loop through all data rows
for(var i=0;i<rangeValues.length;i++) {
//check if the row is not hidden
if((hidden.indexOf(i)+1)==false) {
// if the row is not hidden, check if the cell in column j has content
if(rangeValues[i][j]!=""||rangeValues[i][j]!="") {
//if the cell has content, jump to the next column, otherwise check first all the other rows
break;
}
}
// if no content has been found in column j after ite5rating through all non-hidden rows, hide column j
if(i==(rangeValues.length-1)) {
sheet.hideColumns(j+1)
}
}
}
}
I am a begginer in java script and I'm trying to do a bad-ass Montly finance sheet to operate through my cel phone. Hehe
I built a template for the first part of the process, where you can find here:
https://docs.google.com/spreadsheets/d/1QbM78R7wYqKMwQOHJ6BA4M389c6dLZICRi75khqswO4/edit?usp=sharing
I have two Sheets in my Google sheets:
1 - INPUT
2 - EXPENSES
I wanna copy rows from INPUT and paste in EXPENSES as soon as I input OK on colunm E.
I already found a very good script here in the Forum that does that, by ScampMichael.
function onEdit(event) { //Script by ScampMichael https://support.google.com/docs/forum/AAAABuH1jm0hR40qh02UWE/?hl=en
// assumes source data in sheet named INPUT
// target sheet of move to named EXPENSES
// test column with yes/no is col 5 or E
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "INPUT" && r.getColumn() == 5 && r.getValue() == "OK") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("EXPENSES");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
It's working fine for my purposes. However, I wanna customize it and add extra functions to it, which are:
1 - When it copies rows from INPUT and pastes in EXPENSES, it should always create a new row below the last row in EXPENSES and it should always leave one empty row in INPUT with the formatting and data validation boxes as this template comes with. Right now it's not what the script is doing. When it moves the data to expenses sheet, it deletes the original row from input and creates a blank new one without the formatting and data validation boxes.
2 - I would like the script to add a TIME STAMP in column D (date) in EXPENSES in case the same correspondent cell is empty from when it copies from INPUT. In other words, if I do not input the date of the expense, after it moves from input to expense, it shall add the date of the current day the row was copied.
Could anyone help me on this, please?
Thank you so much if you read this far. :)
Give this a try- I am sure there are better ways- But I think it does function...
function onEdit(event) { //Script by ScampMichael https://support.google.com/docs/forum/AAAABuH1jm0hR40qh02UWE/?hl=en
// assumes source data in sheet named INPUT
// target sheet of move to named EXPENSES
// test column with yes/no is col 5 or E
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "INPUT" && r.getColumn() == 5 && r.getValue() == "OK") {
var cellD2 = SpreadsheetApp.getActive().getRange('D2');
if (cellD2.getValue() == 0 || cellD2.getValue() == "") {
cellD2.setValue(new Date())
}
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("EXPENSES");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
setValidation();
}
function setValidation() {
// Set the data validation for cell A2 to require a number greater than 0.
var cellA2 = SpreadsheetApp.getActive().getRange('A2');
var rule2 = SpreadsheetApp.newDataValidation().setAllowInvalid(false).requireNumberGreaterThan(0).build();
cellA2.setDataValidation(rule2);
var cellB2 = SpreadsheetApp.getActive().getRange('B2');
var ruleB2 = SpreadsheetApp.newDataValidation().requireValueInList(['Pharmacy', 'Rent','Supermarket'], false).build();
cellB2.setDataValidation(ruleB2);
var cellC2 = SpreadsheetApp.getActive().getRange('C2');
var ruleC2 = SpreadsheetApp.newDataValidation().setAllowInvalid(false).requireNumberGreaterThan(0).build();
cellC2.setDataValidation(ruleC2);
var cellD2 = SpreadsheetApp.getActive().getRange('D2');
var ruleD2 = SpreadsheetApp.newDataValidation().requireDate().build();
cellD2.setDataValidation(ruleD2);
}
How can I change the background color for only the rows that were (true) as per function checkDate(row) on the originating sheet "Pasco"? Is this possible?
A little bit about the script:
A date range is inputted through function getDateRange(), all rows in sheet "Pasco" is checked for if they meet that date range through function checkDate(row). If it does meet the date range (true), function filterRows() essentially filters the rows from "Pasco" sheet, and moves them over to another sheet "Copy of Pasco".
Another way of asking my question, how can I get a range of all the rows that were "true" in sheet "Pasco". If "Pasco" wasn't sorted by date, this could mean multiple ranges, right? Once I have a range I'd be able to change background easy.
If you are to test the script, please create two sheets, 'Pasco' and 'Copy of Pasco'. In 'Pasco' Starting from row 2, place some dates down column I (column 8). To see the filtering in action. 'Copy of Pasco' will be deleted/created on each run.
Thank you for your time =)
var globalStartDate;
var globalEndDate;
function getDateRange(){
var startui = SpreadsheetApp.getUi();
var startprompt = startui.prompt('Start Date', 'Enter a date in m/d/y format', startui.ButtonSet.OK_CANCEL);
var startdate = new Date(startprompt.getResponseText());
var startdatemilliseconds = startdate.getTime();
Logger.log(startdate);
Logger.log(startdatemilliseconds);
globalStartDate = startdatemilliseconds;
var endui = SpreadsheetApp.getUi();
var endprompt = endui.prompt('End Date', 'Enter a date in m/d/y format', endui.ButtonSet.OK_CANCEL);
var enddate = new Date(endprompt.getResponseText());
var enddatemilliseconds = enddate.getTime();
Logger.log(enddate);
Logger.log(enddatemilliseconds);
globalEndDate = enddatemilliseconds;
}
function checkDate(row) {
Logger.log(row[8].getTime() <= globalEndDate && row[8].getTime() >= globalStartDate);
return (row[8].getTime() <= globalEndDate && row[8].getTime() >= globalStartDate); // Check column H
}
function filterRows() {
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = Spreadsheet.getSheetByName('Pasco');
var sheetdelete = Spreadsheet.getSheetByName('Copy of Pasco');
Spreadsheet.deleteSheet(sheetdelete);
Spreadsheet.setActiveSheet(sheet1);
Spreadsheet.duplicateActiveSheet();
var headers = 1; // # rows to skip
var sheet2 = Spreadsheet.getSheetByName('Copy of Pasco');
var range = sheet1.getDataRange();
var data = range.getValues();
var headerData = data.splice(0,headers); // Skip header rows
getDateRange();
var filteredData = data.filter( checkDate );
var outputData = headerData.concat(filteredData); // Put headers back
Logger.log(filteredData)
sheet2.clearContents(); // Clear content, keep format
// Save filtered values
sheet2.getRange(1, 1, outputData.length, outputData[0].length).setValues(outputData);
}
Sorry I don't have time to read through your code and give you a complete answer but you could just add a loop to go through the sheet and set the background colour of each row with 'true'.
In my script below I assume 'true' is in column A.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var data = sheet.getRange(1, 1, sheet.getLastRow()).getValues();
var lastCol = sheet.getMaxColumns();
for (var i = 0; i < data.length; i ++){
if(data[i][0] == true){
sheet.getRange(i + 1, 1, 1, lastCol).setBackground('Yellow');
}
}
}
EDIT
Insert this code after you call getDateRange() in the filter rows function.
var lastCol = sheet1.getMaxColumns();
for(var i = headers; i < data.length ; i++){
if(data[i][8].getTime() <= globalEndDate && data[i][8].getTime() >= globalStartDate){
sheet1.getRange(i, 1, 1, lastCol).setBackground('Yellow');
}
}
Your filter rows function should now look like this:
function filterRows() {
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = Spreadsheet.getSheetByName('Pasco');
var sheetdelete = Spreadsheet.getSheetByName('Copy of Pasco');
Spreadsheet.deleteSheet(sheetdelete);
Spreadsheet.setActiveSheet(sheet1);
Spreadsheet.duplicateActiveSheet();
var headers = 1; // # rows to skip
var sheet2 = Spreadsheet.getSheetByName('Copy of Pasco');
var range = sheet1.getDataRange();
var data = range.getValues();
var headerData = data.splice(0,headers); // Skip header rows
getDateRange();
var lastCol = sheet1.getMaxColumns();
for(var i = headers; i < data.length ; i++){
if(data[i][8].getTime() <= globalEndDate && data[i][8].getTime() >= globalStartDate){
sheet1.getRange(i + headers, 1, 1, lastCol).setBackground('Yellow');
}
}
var filteredData = data.filter( checkDate );
var outputData = headerData.concat(filteredData); // Put headers back
Logger.log(filteredData)
sheet2.clearContents(); // Clear content, keep format
// Save filtered values
sheet2.getRange(1, 1, outputData.length, outputData[0].length).setValues(outputData);
}