I have a Google Sheets script that copies a range of cells from one tab to another. The problem is that I'd like it to only copy the non-blank range of cells, but instead it copies the entire range including blank rows. There are many versions of this problem already discussed, but I can't seem to find the right solution so I'm asking it again with all the specifics below.
The range I'm copying is comprised of:
Column A contains a formula that has a text output if column B is
non-blank. If column B is blank, then the formula in column A
creates a blank entry ("").
Columns B:J is an =IMPORTRANGE from a different sheet with the range set to A5:H (open ended range).
Example Source data (imported into the main sheet)
Example Main sheet (this contains the script, which can be triggered in the menu at the top under "Copy Data")
Here's the current script:
// custom menu function
function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('Copy Data');
var item = menu.addItem('Copy Data','copyData');
item.addToUi();
}
function copyData() {
// START1: get current sheet and tabs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var current = ss.getSheetByName('ImportRange');
var database = ss.getSheetByName('RunningList');
// count rows to snap
var current_rows = current.getLastRow();
var database_rows = database.getLastRow() + 1;
var database_rows_new = current_rows + database_rows - 3;
var rows_new = current.getRange('A3:J' + current_rows).getValues();
var nonblank_values = rows_new.filter(String);
// snap rows, can run this on a trigger to be timed
database.getRange(database_rows, 1, nonblank_values.length, nonblank_values[0].length).setValues(nonblank_values);
}
Thank you for your time reviewing this problem.
EDIT 1
When I debug the script, it looks like the filter function is not actually filtering out blank rows. So how would I actually do that?
Debugging Info:
I believe I've found a suitable solution that currently works for my use case. I'm sure there are ways to improve it if you'd like to share your thoughts. I found a script to count nonblank rows here, and integrated it along with some adjustments to the rest of the script:
// add custom menu function "Copy Data"
function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('Copy Data');
var item = menu.addItem('Copy Data','copyData');
item.addToUi();
}
// function to identify last populated row of any tab (based on column A)
function getLastPopulatedRow(sheet) {
var data = sheet.getDataRange().getValues();
for (var i = data.length-1; i > 0; i--) {
for (var j = 0; j < data[0].length; j++) {
if (data[i][j]) return i+1;
}
}
return 0;
}
// function to copy data from one tab to another
function copyData() {
// step 1: get current sheet and tabs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var current = ss.getSheetByName('ImportRange');
var database = ss.getSheetByName('RunningList');
// step 2: count number of new rows needed and grab non-blank rows from first tab
var current_lastrow = getLastPopulatedRow(current);
var database_rows = getLastPopulatedRow(database) + 1;
var database_rows_new = current_lastrow + database_rows - 3;
var rows_new = current.getRange('A3:I' + current_lastrow).getValues();
// step 3: add values to second tab
database.getRange("A" + database_rows + ":I" + database_rows_new).setValues(rows_new);
}
Related
I am trying to edit a google sheets budgeting template. I need a script that looks at previous memos that have been assigned a category and will match newly entered memos with a category if it has already been matched above.
Memos consist of multiple words and a match should only happen if the exact words are present.
What Spreadsheet Looks Like
Spreadsheet Link
I don't know if this is relevant but the template consists of multiple sheets.
I found someone else's code (Source) trying to do what I do but I cant get it to work. This Is what they did...
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
ui.createMenu('Aspire Budgeting')
.addItem('Auto Fill Category. Click on first empty Category in Transaction first', 'autoFillCategory')
.addToUi();
}
function autoFillCategory() {
// to use this script import new Transactions then click on the first empty category in Transactions.
var sheet = SpreadsheetApp.getActiveSheet();
// startRow is off set by 1 in getDataRange
var startRow = sheet.getActiveCell().getRow() - 1;
// categoryColumn is off set by 1 in getDataRange
var categoryColumn = sheet.getActiveCell().getColumn() -1;
var memoColumn = categoryColumn + 2
var data = sheet.getDataRange().getValues();
// Check to make sure the current cell is set to Transactions -> Categories
if (sheet.getName() == "Transactions" && data[6][categoryColumn] == "Category" && startRow > 8) {
for (var currRow = startRow; currRow < data.length; currRow++) {
// memoValue to search for
var memoValue = data[currRow][memoColumn];
// SpreadsheetApp.getUi().alert(currRow);
var previousCategory = "";
//Search for the previous instance of memoValue.
for (var i = 0; i < currRow; i++) {
var row = data[i];
if (row[memoColumn] == memoValue) {
previousCategory=row[categoryColumn];
sheet.getRange(currRow + 1,categoryColumn + 1).setValue(previousCategory);
break;
}
}
}
} else {
SpreadsheetApp.getUi().alert("Before running this script import new transactions then click on the first empty category in transactions you want to search for.");
return;
}
}
I dont really have much coding experience so I don't really where Im going wrong
I am trying to create a data entry form that submits data to a data sheets first open row. The problem is that the data sheet has formula in one of the columns so it is not truly empty. This is causing the current script to take the cells with formula into consideration and only selecting the rows after it.
Could you guys please assist me with a workaround to the issue.
Current script looks like this:
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("FORM"); //Form Sheet
var datasheet = ss.getSheetByName("DATA"); //Data Sheet
//Input Values
var values = [[formSS.getRange("D4").getValue(),
formSS.getRange("D8").getValue(),
formSS.getRange("D12").getValue(),
formSS.getRange("D16").getValue(),
formSS.getRange("D20").getValue(),
formSS.getRange("D24").getValue(),
formSS.getRange("D28").getValue(),
formSS.getRange("L32").getValue()]];
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 8).setValues(values);
}
You can find the first free row by evaluating the row contents
Sample
var freeRow;
var columnI = datasheet.getRange("I1:I" + datasheet.getLastRow()).getDisplayValues().flat();
for(var i = 0; i < columnI.length; i++){
if(columnI[i] == "") {
freeRow = i + 1;
break;
}
}
datasheet.getRange(freeRow, 1, 1, 8).setValues(values);
In addition, you are using a Form submit trigger, you use event objects
Sample
function submitData(e) {
var range = e.range;
var row = range.getRow();
// this is the row into which the latest form response has been inserted - do with it what you need
...
}
Sheet1
Sheet2
I'm wondering if anyone could help me create a appscript for terminal invoicing and database purposes? When I click "SUBMIT" as a button in sheet 1 image, it must reflect line by line like in Sheet 2 image. (see sheet 1 image and sheet 2 image)
-after clicking submit it sheet 1 must auto clear
-when I input another set of data after clearing it needs to keep going down line per line in sheet 2
You can use the following code for that:
function myFunction() {
// declarations
var ss = SpreadsheetApp.getActive();
var sourceSheet = ss.getSheetByName('YOUR_SOURCE_SHEET_NAME');
var database = ss.getSheetByName('YOUR_DATABASE_SHEET_NAME');
// obtain common variables for each item
var dateOfOrder = sourceSheet.getRange('B1').getValue();
var dateOfDelivery = sourceSheet.getRange('B2').getValue();
var agent = sourceSheet.getRange('B8').getValue();
var customer = sourceSheet.getRange('B3').getValue();
// compute variables to initiate reading item rows
var rows = [];
var lastItemRow = sourceSheet.getLastRow();
var firstItemRow = 11;
var nItems = lastItemRow - firstItemRow + 1;
if (nItems < 1) return;
// get row-level information and append to database sheet
var itemNames = sourceSheet.getRange(firstItemRow, 1, nItems, 1).getValues();
var itemQuantities = sourceSheet.getRange(firstItemRow, 2, nItems, 1).getValues();
var itemPrices = sourceSheet.getRange(firstItemRow, 3, nItems, 1).getValues();
for (var i=0; i<nItems; i++) {
var itemName = itemNames[i][0];
var itemQuantity = itemQuantities[i][0];
var itemPrice = itemPrices[i][0];
database.appendRow([dateOfOrder, dateOfDelivery, agent, customer, itemName,
itemQuantity, itemPrice, itemQuantity * itemPrice]);
}
// clear source sheet
sourceSheet.getRange("B1:B8").clear();
sourceSheet.getRange("A11:H").clear();
}
The idea is to first obtain data from your source sheet (using getRange() along with getValue() or getValues()) and afterwards insert it using appendRow().
Finally, you can clear the range using the clear() method.
In order to create a button that calls this script, I suggest you check out this (Google spreadsheet - Making buttons that add data into cells into another sheet when pressed) other Stackoverflow answer. When assigning the script to the image, the name of it should be "myFunction" (without the quotes).
I am trying to copy and paste some data one after the others from one sheet to another with a google app script. The thing is that my script doesn't paste the data on the correct row. I would like it to paste the first data in cell A2. Then, when I run the script again, in cell A3 etc. But when I run the script, it pastes the data in cell A266
function Agregation () {
var spreadsheet = SpreadsheetApp.getActive();
var ss = spreadsheet.getSheetByName('Données');
var ts = spreadsheet.getSheetByName('Classement');
var lr = ts.getLastRow();
Logger.log(lr);
ss.getRange('A2:E7').copyTo(ts.getRange('A0'+(lr+1)), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
For documentation purposes.
The code was good, but the "Classement" sheet was not empty, since it had a non-empty cell at 'A265', the call to ts.getLastRow(); was returning 265. After clearing the sheet and running the code it was working as intended.
A way of moving forward could be to call ts.clear() before the copy is done to ensure the sheet is empty.
UPDATE
After testing, updated the code in the following way:
function Agregation() {
var spreadsheet = SpreadsheetApp.getActive();
var ss = spreadsheet.getSheetByName('Test données');
var ts = spreadsheet.getSheetByName('Test script');
var lr = ts.getLastRow();
Logger.log(lr);
ss.getRange('A2:E7').copyTo(ts.getRange('A'+(lr+1)), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
var toDelete = ts.getRange("A" + ts.getLastRow() - 3 + ":A" + ts.getLastRow()).getValues();
for (var i = 0; i < 4; i++){
if (toDelete[i][0] == ""){
Logger.log(i);
ts.deleteRow((ts.getLastRow() - 3) + i);
}
}
};
It adds the first line and then checks the next 3 to see if it is "", if so, it deletes the rows, so on the next run there wont be spaces
I have tried and combined a few pieces of script to delete rows, but this does not reset the counter. Help resetting responses would be appreciated.
My copy sheet function, and delete all rows function works, but the counter remains, showing 58 responses.
I use the triggers to set the copy and delete functions to occur daily. (sheet url excluding the "docs.google.com..." 0AvTM4SfinH2NdGp1MHdzWms2QnpUMnFiMHJXd1dlV1E&usp) This is what I have so far:
function CopySheet() {
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getSheets()[0];// here I chose to always get the first sheet in the spreadsheet
var inputRange = ss.getRange(1,1,ss.getLastRow(),7);
var data = inputRange.getValues();
var newData = [];
newData.push(['Timestamp','Full Name?','Email?','RAG']);
for(var n=1;n<data.length;++n){ // skip headers by starting at 1
for(var c=0;c<7;c=c+3){
var row = [];
if(c==0){row.push(data[n][0]) ; c++}else{row.push('')};
row.push(data[n][c])
row.push(data[n][c+1]);
row.push(data[n][c+1+1]);//Keep adding a new row and +1 for each extra column
newData.push(row);
}
}
//This next bit creates a copy of the sheet. I would rather a spreadsheet copy but could only get document copy to work
sh.insertSheet().getRange(1,1,newData.length,newData[0].length).setValues(newData);
var doc = DocumentApp.create('Responses document'); // create document
var file = DocsList.getFileById(doc.getId());
file.removeFromFolder(DocsList.getRootFolder());
file.addToFolder(DocsList.getFolder("Folder 1"));
var table = doc.getBody().appendTable(newData); // create table in a separate process so I can set the style below
var style = {};
style[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER; // this one has no effect
style[DocumentApp.Attribute.FONT_FAMILY] = DocumentApp.FontFamily.ARIAL;
style[DocumentApp.Attribute.FONT_SIZE] = 10;
style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#0000ff';
style[DocumentApp.Attribute.BORDER_COLOR] = '#dddddd' ;
table.setAttributes(style);
}
//This section deletes the sheet, leaving the headers; "function deleteAllResponses()" at the bottom should reset counter but does not work
function DeleteSheet() {
SpreadsheetApp.flush();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var datarange = sheet.getDataRange();
var lastrow = datarange.getLastRow();
var values = datarange.getValues();// get all data in a 2D array
for (i=lastrow;i>=2;i--) {
var tempdate = values[i-1][2]; // arrays are 0 indexed so row1 = values[0] and col3 = [2], If I add more columns I need to up this number
{
sheet.deleteRow(i);
function deleteAllResponses() {}
}
}
}
If you mean the counter responses shown on the form:
One option may be to use deleteAllResponses() (read carefully the documentation) from Class Form.
A minimal implementation can be:
/* CODE FOR DEMONSTRATION PURPOSES */
function deleteAllResponses() {
var form, urlForm = SpreadsheetApp.getActiveSpreadsheet().getFormUrl();
if (urlForm) {
form = FormApp.openByUrl(urlForm);
if (form) form.deleteAllResponses();
}
}
#user2847142, #brian-tompsett, #wchiquito
New Google Forms allows you to delete even individual responses from within a Google Form itself without the need of a script.
There is now a simpler method than the answer given by #wchiquito.
--This is now possible on the New Google Forms--
Google announcement on the 10th of February 2016. (googleappsupdates.blogspot.com/2016/02/new-google-forms-now-default-option.html)
How to delete ALL of the responses:
How to delete individual responses:
To delete individual responses you click on the "Responses" tab and choose "Individual". You locate the record you wish to delete and click on the trash can icon to delete that individual response.
This will also reset the counter.
However. The response/s will NOT be deleted from the connected to the form spreadsheet. You will have to manually delete those ones (or using a script).