I have the below spreadsheet that I would like to AutoFill the persons name. the issue is that there are blank rows between the names. Each name is in line with a sku2 and needs to be inline with all locations. there can be up to 10 blank rows (due to how many locations).
if I could loop this maybe
function LoopTillLr() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A2').activate();
spreadsheet.getActiveRange().autoFillToNeighbor(SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
};
Appreciate any help
If you only want to replicate the NAME values against variable LOCATION values then use this script:
function myFunction() {
var ss = SpreadsheetApp.getActiveSheet();
var lastRow = ss.getDataRange().getLastRow();
for (var i = 1; i < lastRow+1; i++) {
if (ss.getRange(i,1).getValue() == "") {
var value = ss.getRange(i-1,1).getValue();
ss.getRange(i,1).setValue(value);
}
}
}
Ensure that A2 is not empty else the script will fail.
If it is a lot of records, you can create a function and run it. The following does this until the end of the sheet, so make sure to delete all the rows towards the end which you do not need or adjust the range in the 2nd row.
function autoFillDown(){
const range = SpreadsheetApp.getActiveSheet().getRange("A:A");
const rows = range.getValues();
let outputArray = [];
rows.forEach( row => {
// if it contains a name, leave it
if( row[0].length > 1){
outputArray.push( [row[0]] )
// otherwise replace it with the value above it
} else {
outputArray.push( [outputArray[outputArray.length-1]] );
}
});
range.setValues( outputArray );
}
Related
I have a Google sheets to change the App store content with each version of our app, for example: changing the App description, changing the App icon, changing the keywords, etc. However, we don't change all of these items with every update. My goal is to have the "C" column track which of these in column "B" was last changed in what version and put the sheet name in the "C" column from that sheet onwards so that it updates automatically instead of me doing so manually.
So basically, it would be something like: If B2 changed, put the sheet name in C2 from this sheet onwards without affecting the previous sheets. and if C2 = the current sheet name, highlight C2.
I'm sorry if I couldn't explain it well, but hopefully, my sheet will help get the situation better
Sheet Link
I managed to get the sheet name from an app script, but I couldn't link it with the "B" column, but it get the name only once and doesn't update it if the sheet name is changed or when I duplicate it.
Here's the function:
function mySheetName() {
var key = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
return key;
}
I tried to use the =Cell formula, but that didn't have what's required.
Tried searching for a get-around but didn't find anything similar to my current situation.
I created a simple example that should solve your problem.
Just add this auxiliary functions on your app script, and set the values on "C" column to:
=IF(GetPreviousSheetValue(ROW(), COLUMN()-1, dummy!$A$1) <> INDIRECT(ADDRESS(ROW(), COLUMN()-1)), GetSheetName(), GetPreviousSheetValue(ROW(), COLUMN(), dummy!$A$1))
The app script functios are:
function onEdit(e) {
SpreadsheetApp.getActive().getSheetByName('dummy').getRange('A1').setValue(Math.random());
}
function GetSheetName() {
return SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
}
function GetAllSheets() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets();
}
function GetAllSheetNames() {
var out = new Array();
var sheets = GetAllSheets();
for (var i = 0 ; i < sheets.length; i++) {
out.push( [ sheets[i].getName() ] );
}
return out;
}
function GetPreviousSheetName(dummy) {
let sheetName = GetSheetName();
let allSheetNames = GetAllSheetNames();
for (var i = 0 ; i < allSheetNames.length; i++) {
if (sheetName == allSheetNames[i] && i + 1 < allSheetNames.length) {
return allSheetNames[i + 1];
}
}
return "";
}
function GetPreviousSheetValue(row, col, dummy) {
let sheetName = GetSheetName();
let allSheets = GetAllSheets();
for (var i = 0 ; i < allSheets.length; i++) {
if (sheetName == allSheets[i].getName() && i + 1 < allSheets.length) {
return allSheets[i + 1].getRange(row, col).getValue();
}
}
return "";
}
PS: Since google cache the functions return values, we have to add a dummy parameter to GetPreviousSheetName and GetPreviousSheetValue so these functions re-run and update the cells correctly whenever the sheets changes. For more info take a look at link
And this is the end spreadsheet:
Image1
Image2
I have a google app script program that looks throughout a spreadsheet and moves rows with empty values in column 5 into another sheet (block 3 of code).
However, I keep getting a #error upon transfer because in certain rows, some phone numbers have a + in front of them. I tried to turn the entire number into a string (block 2 of code with foreach loop), but the error remains.
function insertRecord() {
var sheet_2 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var sheet_1 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var rows = sheet_1.getDataRange().getValues();
rows.forEach(function(element) {
iterator=element.length;
for (i=0;i<iterator+1;i++){
if (typeof element[i]=== Number){
element[i]=element[i].toString
}
}
});
rows
.filter(row => row[5] == '')
.map(row_w_empty5col => sheet_2.appendRow(row_w_empty5col));
}
When I saw your script, at for (i=0;i<iterator+1;i++){, I thought that i<iterator+1 will be i<iterator. And, toString of element[i]=element[i].toString will be toString(). And also, when appendRow is used in a loop, the process cost will be high. Ref
And, about moves rows with empty values in column 5 into another sheet, in your script, .filter(row => row[5] == '') is used. In this case, the column 6 is checked. In order to check the column 5 (column "E"), it is row[4].
From your current issue and your goal, in your situation, how about copying the number format? When this is reflected in your script, it becomes as follows.
Modified script:
function insertRecord() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet_2 = ss.getSheets()[1];
var sheet_1 = ss.getSheets()[0];
var range = sheet_1.getDataRange();
var rows = range.getValues();
var f = range.getNumberFormats();
var n = rows.reduce((ar, r, i) => {
if (r[4].toString() == "") ar.push(i);
return ar;
}, []);
var values = n.map(e => rows[e]);
var formats = n.map(e => f[e]);
sheet_2.getRange(sheet_2.getLastRow() + 1, 1, values.length, values[0].length).setValues(values).setNumberFormats(formats);
}
References:
getNumberFormats()
setNumberFormats(numberFormats)
Update: Changed my code a little bit, and it works now.
function emailEditor() {
var app=SpreadsheetApp;
var targetSht=app.getActiveSpreadsheet().getSheetByName('Linkedin Only');
var spreadsheet=app.getActiveSpreadsheet().getSheets().length;
for (i=0;i<spreadsheet;i++){
var shts=app.getActiveSpreadsheet().getSheets()[i];
var originalData=shts.getDataRange().getValues();
var newData=originalData.filter(function(item){
return item[5]===''; //user declared input here
});
var begin=targetSht.getDataRange().getValues().length;
targetSht.getRange(begin+1,1,newData.length,newData[0].length).setValues(newData);
}
}
I have a budget spreadsheet with tabs for every pay period. These tabs are created as needed and don't have names I can easily know in advance. For instance, one will be "10/15 - 10/28" because that's the pay period. Next month I create a new one with "10/29 - 11/11." I'd like to be able to sum a value across all sheets. For example, every sheet has a row named "Save," some sheets have a row named "Rent", but not every sheet will contain rows with those names and when they do they won't always be in the same cell number.
Sample sheet
I've seen some examples where there's a bunch of SUMIFs and every sheet is manually named but I'd much rather not have to do that because this sheet gets copied fairly often and the sheet names will never be the same.
=SUMIFS('Tab 1' !A1:A10, 'Tab 1'!B1:B10, "Rent")
+SUMIFS('Tab 2' !A1:A10, 'Tab 2'!B1:B10, "Rent")
+SUMIFS('Tab 3' !A1:A10, 'Tab 3'!B1:B10, "Rent")
Is this possible with either a standard formula or a script?
Sample Data
Desired final tab
Column 1's values are known in advance so those can be hardcoded. For instance, there will never be a random "yet more stuff" appear which I wouldn't sum up by adding a new row to the final tab.
While there's another answer that works for this, I think the use of text finders and getRange, getValue and setFormula in loops is not the best approach, since it greatly increases the amount of calls to the spreadsheet service, slowing down the script (see Minimize calls to other services).
Method 1. onEdit trigger:
An option would be to use an onEdit trigger to do the following whenever a user edits the spreadsheet:
Loop through all sheets (excluding Totals).
For each sheet, loop through all data.
For each row, check if the category has been found previously.
If it has not been found, add it (and the corresponding amount) to an array storing the totals (called items in the function below).
If it has been found, add the current amount to the previous total.
Write the resulting data to Totals.
It could be something like this (check inline comments for more details):
const TOTAL_SHEET_NAME = "Totals";
const FIRST_ROW = 4;
function onEdit(e) {
const ss = e.source;
const targetSheet = ss.getSheetByName(TOTAL_SHEET_NAME);
const sourceSheets = ss.getSheets().filter(sheet => sheet.getName() !== TOTAL_SHEET_NAME);
let items = [["Category", "Amount"]];
sourceSheets.forEach(sheet => { // Loop through all source sheets
const values = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 2).getValues();
values.forEach(row => { // Loop through data in a sheet
const [category, amount] = row;
const item = items.find(item => item[0] === category); // Find category
if (!item) { // If category doesn't exist, create it
items.push([category, amount]);
} else { // If category exists, update the amount
item[1] += amount;
}
});
});
targetSheet.getRange(FIRST_ROW-1, 1, items.length, items[0].length).setValues(items);
}
Method 2. Custom function:
Another option would be to use an Apps Script Custom Function.
In this case, writing the data via setValues is not necessary, returning the results would be enough:
const TOTAL_SHEET_NAME = "Totals";
const FIRST_ROW = 4;
function CALCULATE_TOTALS() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sourceSheets = ss.getSheets().filter(sheet => sheet.getName() !== TOTAL_SHEET_NAME);
let items = [["Category", "Amount"]];
sourceSheets.forEach(sheet => { // Loop through all source sheets
const values = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 2).getValues();
values.forEach(row => { // Loop through data in a sheet
const [category, amount] = row;
const item = items.find(item => item[0] === category); // Find category
if (!item) { // If category doesn't exist, create it
items.push([category, amount]);
} else { // If category exists, update the amount
item[1] += amount;
}
});
});
return items;
}
Once the script is saved, you can use this function the same you would use any sheets built-in function:
The problem with this approach is that the formula won't recalculate automatically when changing any of the source data. In order to do that, see the above method.
Method 3. onSelectionChange trigger:
From your comment:
I'd love to be able to trigger it when the totals sheet is opened but that doesn't appear to be possible
You can do this by using an onSelectionChange trigger in combination with PropertiesService.
The idea would be that, every time a user changes cell selection, the function should check whether current sheet is Totals and whether the previously active sheet is not Totals. If that's the case, this means the user just opened the Totals sheet, and the results should update.
It could be something like this:
function onSelectionChange(e) {
const range = e.range;
const sheet = range.getSheet();
const sheetName = sheet.getName();
const previousSheetName = PropertiesService.getUserProperties().getProperty("PREVIOUS_SHEET");
if (sheetName === TOTAL_SHEET_NAME && previousSheetName !== TOTAL_SHEET_NAME) {
updateTotals(e);
}
PropertiesService.getUserProperties().setProperty("PREVIOUS_SHEET", sheetName);
}
function updateTotals(e) {
const ss = e.source;
const targetSheet = ss.getSheetByName(TOTAL_SHEET_NAME);
const sourceSheets = ss.getSheets().filter(sheet => sheet.getName() !== TOTAL_SHEET_NAME);
let items = [["Category", "Amount"]];
sourceSheets.forEach(sheet => { // Loop through all source sheets
const values = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 2).getValues();
values.forEach(row => { // Loop through data in a sheet
const [category, amount] = row;
const item = items.find(item => item[0] === category); // Find category
if (!item) { // If category doesn't exist, create it
items.push([category, amount]);
} else { // If category exists, update the amount
item[1] += amount;
}
});
});
targetSheet.getRange(FIRST_ROW-1, 1, items.length, items[0].length).setValues(items);
}
Note: Please notice that, in order for this trigger to work, you need to refresh the spreadsheet once the trigger is added and every time the spreadsheet is opened (ref).
Reference:
onEdit(e)
Custom Functions in Google Sheets
onSelectionChange(e)
I wrote 2 scripts:
budgetTotal which takes a budgetCategory parameter, for example "Rent", and loops through all the sheets in the file to sum up the amounts listed on each sheet for that category.
budgetCreation which looks at your Totals sheet and writes these budgetTotal formulas in for each category you have listed.
I ran into a challenge which was, as I added new sheets the formulas wouldn't be aware and update the totals. So, what I did was create a simple button that executes the budgetCreation script. This way, as you add new payroll weeks you just need to press the button and - voila! - the totals update.
There might be a better way to do this using onEdit or onChange triggers but this felt like a decent starting place.
Here's a copy of the sheet with the button in place.
const ws=SpreadsheetApp.getActiveSpreadsheet()
const ss=ws.getActiveSheet()
const totals=ws.getSheetByName("Totals")
function budgetCreation(){
var budgetStart = totals.createTextFinder("Category").findNext()
var budgetStartRow = budgetStart.getRow()+1
var budgetEndRow = ss.getRange(budgetStart.getA1Notation()).getDataRegion().getLastRow()
var budgetCategoies = budgetEndRow - budgetStartRow + 1
ss.getRange(budgetStartRow,2,budgetCategoies,1).clear()
for (i=0; i<budgetCategoies; i++){
var budCat = ss.getRange(budgetStartRow+i,1).getValue()
var budFormula = `=budgetTotal(\"${budCat}\")`
ss.getRange(budgetStartRow+i,2).setFormula(budFormula)
}
}
function budgetTotal(budgetCategory) {
var sheets = ws.getSheets()
var total = 0
for (i=0; i<sheets.length; i++){
if (sheets[i].getName() != totals.getName()){
var totalFinder = sheets[i].createTextFinder(budgetCategory).findNext()
if (totalFinder == null){
total = 0
} else {
var totalValueFinder = sheets[i].getRange(totalFinder.getRow(),totalFinder.getColumn()+1).getValue()
total += totalValueFinder
}
}
}
return total
}
I have an html form where the client's data is inserted in and it appends row with the values on to a google sheet.
In the form, there's a field that searches and returns the clients data when searching for a specific value (id number).
function getID(IDsearch){
var ws = SpreadsheetApp.getActiveSheet();
var data = ws.getRange(3, 1, ws.getLastRow(), 36).getValues();
var dataInput = data.map(function(r){return r[7];});
var position = dataInput.indexOf(IDsearch);
var dataArray = ws.getRange(position+3, 1, 1, 36).getValues();
if(position > -1){
return dataArray;
} else {
return position;
}
}
After this runs, all the input fields in the form are populated with the data from that row.
I need to edit the values in the form and when submit it should overwrite/update the existing row with that id number.
In google sheets documentation, I've found the spreadsheets.values.update method, but I cannot figure this out. I'm pretty new in this and any help would be appreciated.
Thanks everyone!
You want to achieve the following flow.
Input "ID" to id="insertID" and click "Search by ID".
Show the values from Spreadsheet by searching "ID".
Edit the values of id="name" and id="ID".
When "Save data" is clicked, you want to update the values on the Spreadsheet.
From your replying, shared Spreadsheet and script, I could understand like above. If my understanding is correct, how about ths following modification? Please think of this as just one of several possible answers.
Modification points:
In your case, processForm at Google Apps Script side is required to be modified.
Search the row using formObject and overwrite the values of cells.
Modified script:
When your script is modified, please modify processForm at Google Apps Script side as follows. I remove the Spreadsheet ID from the URL. So please set it, before you test the script.
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/###/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Database");
// I added and modified below script.
var ranges = ws.getRange(4, 2, ws.getLastRow() - 3, 1).createTextFinder(formObject.ID).findAll();
if (ranges.length > 0) {
for (var i = 0; i < ranges.length; i++) {
ranges[i].offset(0, -1, 1, 2).setValues([[formObject.name, formObject.ID]]);
}
} else {
ws.appendRow([formObject.name, formObject.ID]);
}
}
In this modification, when the same IDs are existing, all rows of the same IDs are overwritten. For example, if you want to modify the 1st one, please modify to ranges[0].offset(0, -1, 1, 2).setValues([[formObject.name, formObject.ID]]);.
Reference:
Class TextFinder
Try this:
function getID(IDsearch){
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();//dont know what the sheet is
var rg=sh.getRange(3,1,sh.getLastRow()-2,36);
var data=rg.getValues();
var idA=sh.getRange(3,8,sh.getLastRow()-2,1).getValues().map(function(r){return r[0];});//it looked like column 8 was your id column
var idx=idA.indexOf(IDsearch);
if(idx>-1) {
return ws.getRange(pos + 3,1,1,36).getValues()[0];//flattened the row to a 1d array
}else{
return idx;
}
}
#dianadfonseca, as #Tanaike points out, without more detail about your data structure, people will be speculating in order to answer your question. As I will be...
Please read the following answer, and tailor it to your needs if it works for you.
Example:
function getRow(id){
var ws = SpreadsheetApp.getActiveSheet();
// Number of headers to skip
var numHeaders = 2;
// the starting row
var startRow = numHeaders + 1;
// The column where the IDs are is known
var idCol = 8;
// The number of rows with data not headers
var numRows = ws.getDataRange().getLastRow() - numHeaders;
// An array with the ids to find a match in
// getRange() returns a 2D array, so you can transpose it to flatten it
var ids = ws.getRange(startRow,idCol,numRows).getValues();
ids = transpose(ids)[0];
// Get the index where id matches in ids
var row = ids.indexOf(id);
// If there's a match
if(row > -1){
// Correct row indexing
row = row + startRow;
}
return row;
}
function updateRow(row,data){
var ws = SpreadsheetApp.getActiveSheet();
// The column for each property is known
var propertyOneCol = 1;
// Update property using setValue()
ws.getRange(row,propertyOneCol).setValue(data.propertyOne);
// And so on...
}
// Transpose to avoid looping through the array
function transpose(a)
{
return Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
}
You can take a look the spreadsheet used for this example here with its bound script to play around.
Here is the function I used for testing
function test(){
// You are receiving this from your form
var data = {"propertyOne":"Juan","propertyTwo":20, "id":123467};
var id = data.id;
updateRow(getRow(id),data);
}
I currently have a column of data titled JobID. In this column, there are duplicates from an import that runs daily and grabs the latest data on the JobID's in question and appends them to the top of the sheet.
Therefore the most recent JobID rows are the ones with the data we need.
I'd like to know if there is a script that can be run on the sheet called 'History' to look up the column JobID, search every row below for duplicates and remove them, leaving the top, most recent JobID rows in the sheet.
I know that it is really easy to remove duplicates using the "Remove Duplicates" tool in Google Sheets... but I'm lazy and I'm trying to automate as much of this process as possible.
The script I have below runs without an error but is still not doing what I need it to. Wondering where I am going wrong here:
function removeDuplicates() {
//Get current active Spreadsheet
var sheet = SpreadsheetApp.getActive();
var history = sheet.getSheetByName("History");
//Get all values from the spreadsheet's rows
var data = history.getDataRange().getValues();
//Create an array for non-duplicates
var newData = [];
//Iterate through a row's cells
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if (row.join() == newData[j].join()) {
duplicate = true;
}
}
//If not a duplicate, put in newData array
if (!duplicate) {
newData.push(row);
}
}
//Delete the old Sheet and insert the newData array
history.clearContents();
history.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}
Remove Duplicate JobIDs
This function will keep the ones nearest to the top of the list. If you want to go the other way then resort the list in reverse order.
function removeDuplicates() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("History");
var vA=sh.getDataRange().getValues();
var hA=vA[0];
var hObj={};
hA.forEach(function(e,i){hObj[e]=i;});//header title to index
var uA=[];
var d=0;
for(var i=0;i<vA.length;i++) {
if(uA.indexOf(vA[i][hObj['JobID']])==-1) {
uA.push(vA[i][hObj['JobID']]);
}else{
sh.deleteRow(i+1-d++);
}
}
}
Remove Duplicate JobIDs in Python
Based on Cooper's answer I wrote the same function in Python:
gsheet_id = "the-gsheet-id"
sh = gc.open_by_url("https://docs.google.com/spreadsheets/d/%s/edit#gid=0" % gsheet_id)
wks = sh[0]
def removeDuplicates(gwks):
headerRow = gwks[1]
columnToIndex = {}
i = 0
for column in headerRow:
columnToIndex[column] = i
i += 1
uniqueArray = []
d = 0
row_i = 0
for row in gwks:
row_i += 1
if gwks[row_i][columnToIndex['JobID']] not in uniqueArray:
uniqueArray.append(gwks[row_i][columnToIndex['JobID']])
else:
d += 1
gwks.delete_rows(row_i + 1 - d, 1)
removeDuplicates(wks)