I've seen this question return quite a few results here, but I'm having trouble getting it to work for my use case. Basically, I have a simple script that I want to run on multiple sheets in my spreadsheet (not every sheet, just the ones I define). I've tried defining the sheets and running a for loop, but it fails when calling the method getRange. Anyway, here's my original script that defines only one sheet, and this works as expected:
var sheetToSort = "Sheet1";
var columnToSortBy = 1;
var rangeToSort = "A2:AB";
function onEdit() {
var sheet = SpreadsheetApp.getActiveSheet();
var editedCell = sheet.getActiveCell();
if (editedCell.getColumn() == columnToSortBy && sheet.getName() == sheetToSort) {
sortFormResponsesSheet();
}
}
function sortFormResponsesSheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetToSort);
var range = sheet.getRange(rangeToSort);
range.sort( { column : columnToSortBy, ascending: false } );
}
This is an example of putting settings into an object and then looping through the object.
function onEdit() {
const sheet = SpreadsheetApp.getActiveSheet(),
editedCell = sheet.getActiveCell(),
editedColumn = editedCell.getColumn(),
sheetName = sheet.getName();
// Define all the sheets to sort using an object literal
const objSheetsToSort = {
"Sheet1": {columnToSortBy: 1, rangeToSort: "A2:AB"},
"Sheet2": {columnToSortBy: 2, rangeToSort: "A2:AB"},
"Sheet3": {columnToSortBy: 3, rangeToSort: "A2:AB"},
"Sheet4": {columnToSortBy: 4, rangeToSort: "A2:AB"}
}
// Get the sort info for this sheet (or `undefined` if we didn't define how to sort the edited sheet).
var config = objSheetsToSort[sheetName];
if (config && editedColumn === config.columnToSortBy) {
// We want to sort the edited sheet.
sortSheet_(sheet, config);
}
}
// This function can only be called by other functions in this Script Project, not manually or via menu.
function sortSheet_(sheetToSort, objectOfSettings) {
Logger.log('sheetToSort: ' + sheetToSort.getName())
var range = sheetToSort.getRange(objectOfSettings.rangeToSort);
range.sort( { column : objectOfSettings.columnToSortBy, ascending: false } );
}
Related
I am new to Google Sheets Apps Script, and I've tried dozens of scripts to make this work but haven't been successful. I'm trying to write a script that copies Topic Data from Column 'C' if a neighboring checkbox in Column 'B' is checked, and pastes that Topic Data into the next empty cell starting from the 5th row in Column 'A'. Any help will be greatly be appreciated! Thank you.
Sample Sheet:
https://docs.google.com/spreadsheets/d/1g9tn907Ve4rGFo7UI1NwYDBQqK5Y26TOSD_KnEfSWKw/edit?usp=sharing
This is my latest attempt:
function onEdit(){
var ss = SpreadsheetApp.getActive().getActiveSheet();
var selection = ss.getActiveCell().getA1Notation().split("");
var row = ss.getActiveCell().getRow();
//Gets the checkbox value
var checkBoxValue = ss.getActiveCell().getValue().toString();
if(selection[0] != "B") return;
switch(checkBoxValue){
case checkBoxValue = "true":
ss.getRange("C"+row).copyTo(ss.getRange('A5:A').getValues().filter(String).length + 5);
break;
}
}
I've been able to retrieve Data when a checkbox is checked but I can't figure out how to paste the Data into the next empty cell in Column 'A'. The script above was the latest iteration but this one in particular was one that I borrowed from other boards to see if I could adapt it...No Luck.
I believe your goal is as follows.
When the checkbox of column "B" is checked, you want to copy the value of column "C" to the next row of the last row of column "A".
From your showing script, you want to use onEdit.
In this case, how about the following modification?
Modified script:
When onEdit is used, the event object can be used. In this modification, the event object is used.
function onEdit(e) {
// Ref: https://stackoverflow.com/a/44563639
Object.prototype.get1stNonEmptyRowFromBottom = function (columnNumber, offsetRow = 1) {
const search = this.getRange(offsetRow, columnNumber, this.getMaxRows()).createTextFinder(".").useRegularExpression(true).findPrevious();
return search ? search.getRow() : offsetRow;
};
var sheetName = "Research";
var { range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != sheetName || range.columnStart != 2 || !range.isChecked()) return;
range.offset(0, 1).copyTo(sheet.getRange(sheet.get1stNonEmptyRowFromBottom(1) + 1, 1), { contentsOnly: true });
}
In this case, please check the checkbox of column "B". By this, the script is run. When you directly run this script, an error occurs. Please be careful about this.
Note:
In the above modification, the 1st empty row is searched from the bottom. If you want to search it from the top, please use the following modified script.
function onEdit(e) {
// Ref: https://stackoverflow.com/a/44563639
Object.prototype.get1stEmptyRowFromTop = function (columnNumber, offsetRow = 1) {
const range = this.getRange(offsetRow, columnNumber, 2);
const values = range.getDisplayValues();
if (values[0][0] && values[1][0]) {
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
} else if (values[0][0] && !values[1][0]) {
return offsetRow + 1;
}
return offsetRow;
};
var sheetName = "Research";
var { range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != sheetName || range.columnStart != 2 || !range.isChecked()) return;
range.offset(0, 1).copyTo(sheet.getRange(sheet.get1stEmptyRowFromTop(1), 1), { contentsOnly: true });
}
If you want to uncheck after the value was copied, please add range.uncheck() to the last line of the script.
References:
Simple Triggers
Event Objects
I have a project that I've been working on for a bit. I've received some excellent help here, and I think I'm almost done and just need one more bit of help to get it working.
The script looks at a Google Sheet and takes a place name entered in Column A and uses the Google Places API to find requested information about it (address, phone number, etc.)
The last bit of help that I need will be able to implement the cell input component. The last user to help me said that
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
would be able use TextFinder to check if the place url exists in the Sheet. If the result of TextFinder is 0, it will call COMBINED2() to get the place information and populate the Sheet with writeToSheet()
They noted that
You can use a cell input in your COMBINED2 by using
ss.getRange(range).getValue()
Not having a coding background, I have been able to stitch most of this together on my own, but I could use a bit of help in adding that capability to my code. Any help or guidance would be great.
Here is the code in full:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "40.74516247433546, -73.98621366765816"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function getColumnLastRow(range){
var ss = SpreadsheetApp.getActiveSheet();
var inputs = ss.getRange(range).getValues();
return inputs.filter(String).length;
}
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Update
Here is a link to a Shared Sheet in case anyone wants to work on it with me.
https://docs.google.com/spreadsheets/d/1KGsk6nkin1CUgpjfHU_AdhF17T_Eh41_g4MLb1CG_Tk/edit#gid=2100307022
Here is what I might not have articulated properly.
I wanted to be able to enter the names of places in Column A
Then, I want to be able to run the function with the custom menu feature. If TextFinder does not find the Place URL for the given place, it will look up the data and write it to the Sheet.
I wanted to limit the number of API calls with this and to make sure the data was written to the Sheet so that it does not need to be pulled each time the Sheet is reopened.
Finished Product
Big thanks to Lamblichus for sticking this out with me. I hope this helps other people some day.
Here is the finished code:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "ENTER_GPS_COORDINATES_HERE"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'ENTER_API_KEY_HERE';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
if (place.opening_hours && place.opening_hours.weekday_text) {
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
}
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function writeToSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const FIRST_ROW = 2;
const sourceData = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 6)
.getValues().filter(row => String(row[0]));
for (let i = 0; i < sourceData.length; i++) {
const sourceRow = sourceData[i];
if (sourceRow[4] === "") {
const text = sourceRow[0];
const data = COMBINED2(text);
sheet.getRange(FIRST_ROW+i, 2, 1, data.length).setValues([data]);
}
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Desired goal:
If I understand you correctly, for each value in column A, you want to retrieve some related data from Maps API and paste it to columns B-F, if column E is not currently populated.
Issues:
You are only providing the last value from column A to COMBINED2, but you want to loop through all values in column A and fetch the desired information for all of them (as long as the Place URL -column E- is not already populated).
If you want to avoid calling Maps API if the Place URL is not populated, using TextFinder after calling Maps API doesn't make sense; you don't limit your calls to the API if you do that. If you just want to check whether the Place URL column is populated, I'd suggest checking whether the cell is empty or not, and calling Maps API if it's empty.
Proposed workflow:
Retrieve all values from the sheet, including not just column A but also E (for practical purposes, all 6 columns are fetched in the sample below, since it can be done in one call), using Range.getValues().
Iterate through the rows (for example, using for), and for each row, check that the cell in E is populated.
If the cell in E (Place URL) is empty, use the value in A as the parameter for COMBINED2 and write the resulting data to columns B-F, as you are currently doing.
Code sample:
function writeToSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const FIRST_ROW = 2;
const sourceData = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 6)
.getValues().filter(row => String(row[0]));
for (let i = 0; i < sourceData.length; i++) {
const sourceRow = sourceData[i];
if (sourceRow[4] === "") {
const text = sourceRow[0];
const data = COMBINED2(text);
sheet.getRange(FIRST_ROW+i, 2, 1, data.length).setValues([data]);
}
}
}
Update:
For names in which Places API doesn't return opening_hours, consider checking if this exists first:
function COMBINED2(text) {
// ... REST OF YOUR FUNCTION ...
var weekdays = '';
if (place.opening_hours && place.opening_hours.weekday_text) {
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
}
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
By using the event trigger function...
function onEdit(e){
SpreadsheetApp.getActiveSheet().getRange(insert your range in A1 format).setValue("anything you want to add into the cell")
}
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
u need to specifically tell google apps script that the function is as such so that your function will execute when a event object known as e has happened.
You can read more about it on Simple Triggers
I am using this script function to check if my cell functions in sheet has any errors or not.
Here is the code but it does not seems to be working. It keeps on saying no error when i have an error in a cell
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcename = "Sheet1";
var source = ss.getSheetByName(sourcename);
var cell = source.getRange("A1:AG30");
function isError2(cell) {
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
if (errorValues.includes(cell) != true) {
Logger.log("no error");
} else{
Logger.log("some error");
}
}
function isError2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcename = "Sheet1";
var source = ss.getSheetByName(sourcename);
var cell = source.getRange("A1:AG30");
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
if (errorValues.includes(cell) != true) {
Logger.log("no error");
} else{
Logger.log("some error");
}
}
Updated the approach but still having no luck with the desired output
var mysheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var sheet1 = SpreadsheetApp.setActiveSheet(mysheet);
function findErrors(sheet) {
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
var singleSheetArray = [];
var name = sheet1.getName();
// how many cells in the sheet currently
var maxRows = sheet1.getMaxRows();
var maxCols = sheet1.getMaxColumns();
var totalCells = maxRows * maxCols;
// how many cells have data in them
var r = sheet1.getLastRow();
var c = sheet1.getLastColumn();
var data_counter = r * c;
if (data_counter !== 0) {
var dataRange = sheet1.getRange(1,1,r,c);
var dataValues = dataRange.getValues();
dataValues.forEach(function(row) {
row.forEach(function(cell) {
if ((errorValues.indexOf(cell) === -1) ) {
SpreadsheetApp.getUi().alert("no errors in "+cell);
data_counter --;
}
});
});
}
}
Problem
Unable to check whether the cell has an error
Explanation
The issue you are facing is a simple type mismatch. getRange() method returns an instance of Range, while you try to compare it to a member of a errorValues array, which consists of strings. Therefore, errorValues.includes(cell) will always be false, hence first block of the conditional statement executing.
Solution
Use getValues() on the range, it will return you a 2-dimensional array of values. If you are only interested in one row (which you probably are), extract it and loop over the cells with some (or every) method, doing the same comparison.
Notes
On using global variables in custom functions and in GAS in general. You can use them, GAS environment is a JavaScript runtime with a convenience layer that simplifies working with Google APIs, nearly everything that's valid in JS is valid here. That being said, do treat global variables as if they don't exist - unless you know exactly what you are doing.
References
getRange method reference
getValues method reference
Custom functions guide
every method reference on MDN (see some there)
Try to move the 4 variables inside your function. Apps script does not support global variables. So the function does not recognize the var cell.
EDIT: Detect formula errors in Google Sheets using Script
I have a spreadsheet, and I would like to use google scripts (JS based) to put all of that data into an array and then search through that array for information. The code I have below does not work, and returns nothing in the logger:
function openNCR() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var NCRData = ss.getSheets()[1];
var data = NCRData.getDataRange.getValues();
var search = 'Open NCR'
var target_rows = []
data.forEach(function(element, index) {
if (element == search) {
target_rows.push(index)
}
})
Logger.log(target_rows);
}
Yet when I make the change below in terms of how to pull the information, the code works great! What is the key difference here in the above and below? Doesn't getDataRange.getValues pull all the values within that sheet? If that is the case, then why do I need to define a specific column to search through as per below (column 3 is the column that contains the information "Open NCR" or "Closed NCR")?
function openNCR() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var NCRData = ss.getSheets()[1];
var data = NCRData.getRange(1, 3, NCRData.getLastRow()).getValues()
var search = 'Open NCR'
var target_rows = []
data.forEach(function(element, index) {
if (element == search) {
target_rows.push(index)
}
})
Logger.log(target_rows);
}
.getDataRange() is a method from the class 'Sheet' and thus requires brackets.
var data = NCRData.getDataRange().getValues();
I'm trying to update a cells value dependent on on another cell in the same row. This is mean't to be done by the setRankID function, but no matter what I've tried it just spits out errors.
function onOpen() {
var ui = SpreadsheetApp.getUi();
var ss = SpreadsheetApp.getActiveSpreadsheet();
ui.createMenu('Daniagaming EMS Tools')
.addItem('Opdater RID\'s', 'setRankID')
.addToUi();
}
function setRankID() {
var ss = SpreadsheetApp.getActiveSpreadsheet(),
sheet = ss.getActiveSheet(),
range = ss.range,
columnNumberToWatch = 1; // A = 1, B = 2 etc.
if(sheet.getName() === "Ark1" && range.columnStart === columnNumberToWatch) {
if(ss.value === "Commisioner") {
sheet.getRange(range.rowStart, 2).setValue("1");
}
}
}
What I wan't it to do is when using the function through the UI, to check cells in column A and update the value of column B dependent on column A's value.
Like if column A has the value "Test1" then column B should be set to the value "Toast".
Here's a simple example of what I think you were asking for. It's hard to tell because your setRankID function has a lot errors.
function simpleExample()
{
var ss=SpreadsheetApp.getActive();
var sht=ss.getSheetByName('deleteDupes');
var rng=sht.getDataRange();
var rngA=rng.getValues();
for(var i=1;i<rngA.length;i++)
{
if(rngA[i][0]=='Test1')
{
sht.getRange(i+1,2).setValue('Toast');
}
else
if(rngA[i][0])
{
sht.getRange(i+1,2).setValue(rngA[i][0]);
}
}
}
Here's what my spreadsheet looks like before running the function:
Here's what it looks like after: