"edit" trigger does not activate when altering Google Sheets via mobile app - mobile

Im trying to move a row from one sheet to another using a script.
The problem: i'm using a phone app as the input to the spreadsheet, but for some reason it doesn't activate the "edit" trigger?
Here is my script:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Sheet1" && r.getColumn() == 14 && r.getValue() == "Finished") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Finished");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}

Related

Automatic WhatsApp Mesaage send from Google Sheet if anythng change

I want to know if there is a way, if there is something change in a specific cell automatic WhatsApp should send
First register in https://panel.rapiwha.com/landing/login.php.
Link your phone and get the API key from the web site.
Then try the below code in your Google Sheet.
//
function onEdit(e) {
var api_key="xxxxxxx"; //from the Rapiwah web site
var mob="919123456789"; // with country code without +
var s = e.source.getActiveSheet(), r ;
var shtn=s.getName();
if(shtn=="Plan") { //checks that we're on the correct sheet
r = e.range;
var old=e.oldValue;
var nval=r.getValue();
var linecol = r.getColumn();
var linerow = r.getRow();
if(linerow === 3 && linecol===4 ) { //checks the B3 cell was changed
var wpurl= 'https://panel.rapiwha.com/send_message.php?apikey='+api_key+'&number='+mob+'&text=Cell B3 in Plan Sheet changed from '+old+' to ' + nval;
var formurl3 = wpurl.replace(/#/g, "");
var formurl4 = formurl3.replace(/,/g, "");
var formurl = formurl4.replace(/ /g, "%20");
var response = UrlFetchApp.fetch(formurl);
}
}
}
//

How do you use multiple scripts in the same spreadsheet?

I have a spreadsheet that tracks legal cases from start to finish. In that spreadsheet I used a script to create dependent dropdown menus on multiple rows (I'll share the script at the end). The main dropdown menu tells me what Department the case is in. The Departments are Transcription; Doctor; Scheduling; Records; QA; and Billing.
I also have a script that is supposed to moves an entire row of data to a separate tab labeled Billing when the department dropdown menu is set to Billing. I had this script working before I made the dependent dropdown menus and was just using data validation to create my dropdown menu.
Both of these scripts work separately but when I try to use them together the dependent dropdown menus quit working and when the department is set to Billing that row disappears like its supposed to except it doesn't show up on the Billing tab like its supposed to. I have no idea where it goes.
Can someone please tell me how to get both scripts to work at the same time? And, why the row of data disappears when the department is set to Billing but doesn't go to the Billing tab?
Dependent Dropdown Menu Script
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Database");
var wsOptions = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("options");
var options = wsOptions.getRange(2,1,wsOptions.getLastRow()-1,2).getValues();
function myFunction() {
var list = ["a","b","c","f"];
var cell = ws.getRange("J2");
applyValidationToCell(list,cell);
}
function onEdit(event){
var activeCell = event.range;
var value = activeCell.getValue();
var row = activeCell.getRow();
var column = activeCell.getColumn();
var wsName = activeCell.getSheet().getName();
if(wsName == "Database" && column === 5 && row > 1){
if(value === ""){
ws.getRange(row,10).clearContent();
ws.getRange(row,10).clearValidations();
} else{
ws.getRange(row,10).clearContent();
var filteredOptions = options.filter(function(options){ return options[0] === value });
var listToApply = filteredOptions.map(function(options){ return options[1] });
Logger.log(listToApply);
var cell = ws.getRange(row,10);
applyValidationToCell(listToApply,cell);
}
}
}
function applyValidationToCell(list,cell){
var rule = SpreadsheetApp
.newDataValidation()
.requireValueInList(list)
.setAllowInvalid(false)
.build();
cell.setDataValidation(rule);
}
Billing Script
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Database");
var wsOptions = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("options");
var options = wsOptions.getRange(2, 1,wsOptions.getLastRow()-1,2).getValues();
function myFunction() {
var list = ["a","b","g"];
var cell = ws.getRange("K2");
applyValidationToCell(list,cell);
}
function onEdit(e){
var activeCell = e.range;
var val = activeCell.getValue();
var r = activeCell.getRow();
var c = activeCell.getColumn();
var wsName = activeCell.getSheet().getName();
if(wsName == "Database" && c === 5 && r > 1){
var filteredOptions = options.filter(function(o){return o[0] === val});
var listToApply = filteredOptions.map(function(o){ return o[1]});
console.log(listToApply);
var cell = ws.getRange(r, 10);
applyValidationToCell(listToApply,cell);
}
}
function applyValidationToCell(list,cell){
var rule = SpreadsheetApp
.newDataValidation()
.requireValueInList(list)
.setAllowInvalid(false)
.build();
cell.setDataValidation(rule);
}
function onEdit(e) {
const src = e.source.getActiveSheet();
const r = e.range;
if (r.columnStart != 5|| r.rowStart == 2 || e.value == src.getName()) return;
const dest = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(e.value);
src.getRange(r.rowStart,1,1,30).moveTo(dest.getRange(dest.getLastRow()+1,1,1,30));
src.deleteRow(r.rowStart);
}
All functions have to have a unique name and all functions have access to all sheets. onedit triggers are generated on every user edit and it's up to the onEdit function to route edits from the appropriate sheets using information gathered from the event object. Note: Sheet Name = e.range.getSheet().getName() where e is populated by the event object in the function declaration function onEdit(e) {}
The if statement below limits access to edits that occur on Sheet name "Database" and e.range.columnStart == 5 and rows greater than one
if(wsName == "Database" && column === 5 && row > 1){
if(value === ""){
ws.getRange(row,10).clearContent();
ws.getRange(row,10).clearValidations();
} else{
ws.getRange(row,10).clearContent();
var filteredOptions = options.filter(function(options){ return options[0] === value });
var listToApply = filteredOptions.map(function(options){ return options[1] });
Logger.log(listToApply);
var cell = ws.getRange(row,10);
applyValidationToCell(listToApply,cell);
}
}
if you require actions from other sheets then you must add more if statements or other conditional logic to route the appropriate edit traffic to the the correct processing statements.

Code to reduce execution time by array / caching??? Google App Script

I'm using the following code to compile a list of hastags which have been seperated from a list of twitter tweets.
This code ignores all rows where the hashtag cell has no value or an error value.
function CopyProcessedHashtags() {
var sSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = sSheet.getSheetByName("SortedIntoRows");
var targetSheet = sSheet.getSheetByName("HashtagsList");
var lastRow = sourceSheet.getLastRow();
for (var i = 3; i <= lastRow; i++) {
var cell = sourceSheet.getRange("E" + i);
var val = cell.getValue();
if (val != "") {
if (val != "#VALUE!") {
if (val != "#REF!") {
var sourceRange = sourceSheet.getRange("B" + i + ":" + "E" + i);
var targetRow = targetSheet.getLastRow();
targetSheet.insertRowAfter(targetRow+1);
var targetRange = targetSheet.getRange("B" + (targetRow+1));
sourceRange.copyTo(targetRange, {contentsOnly: true});
}}}}}
};
The trouble is that there is sometimes thousands of rows and execution time is well over 6 mins.
I know batching can reduce execution time...
EXAMPLE
for (var i = 1; i <= 100; i++) {
SpreadsheetApp.getActiveSheet().deleteRow(i);
to
SpreadsheetApp.getActiveSheet().deleteRows(i, 100);
... I just don't know how to rewrite my code to work in this manner.
Still very new to google app script.
If somebody could give me a nudge in the right direction you'r help would be VERY much appreciated.
Thanks!!
You can use getValues() to retrieve all data within the range and filter it using Array.filter().
Example:
Code:
function CopyProcessedHashtags() {
var sSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = sSheet.getSheetByName("SortedIntoRows");
var targetSheet = sSheet.getSheetByName("HashtagsList");
var data = sourceSheet.getDataRange().getValues();
var filteredData = data.filter(dataRow => (dataRow[4] != "#VALUE!" && dataRow[4] != "#REF!" && dataRow[4] != ""));
targetSheet.getRange(1,1, filteredData.length, filteredData[0].length).setValues(filteredData);
}
Sample Data:
Output:
References:
getValues()
setValues()
Array.filter()

Compare rows in Two Google Sheets, Once a match found Change Font Color

I am trying to create two sheets: Sheet 1: a template into which a student will enter a translation of a word into a cell; Sheet 2: a key which checks it against all possible answers: The two sheets look like this:
Here is a Link To Show the Two Spreadsheets
I am trying to take the provided answer for each column in the template (A:1), and match it against all possible answers listed in the corresponding column in the key (if it matches anything between A:1:A:3 in the key). If there's a match, the font should change to green and it should move to the next column. If there's no match the font turns red before moving to the next column.
I've run the logger, and it seems like it iterates through the data from each sheet correctly. The problem seems to be in comparing the 2 columns.
var ui = SpreadsheetApp.getUi();
var ssA= SpreadsheetApp.getActiveSpreadsheet();
var worksheet = ssA.getSheetByName("Worksheet");
var rangeData = worksheet.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
var searchRange = worksheet.getRange(2, 2, lastRow-1, lastColumn-1);
var ssB = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1jKuxXo6o5YJjSUrQmaHR0n1ydvw7PgUl29I9mtycF_g/edit#gid=0");
var worksheetB = ssB.getSheetByName("Key");
var rangeDataB = worksheetB.getDataRange();
var lastColumnB = rangeDataB.getLastColumn();
var lastRowB = rangeDataB.getLastRow();
var searchRangeB = worksheetB. getRange(2, 2, lastRowB -1, lastColumnB -1);
function onOpen(){
ui.createMenu("Check My Translation.")
.addItem("Check Set 1", "transcheck")
.addToUi()
function transcheck(){
//iterate through the two corresponding columns in template and reference sheet
for (i = 1; i < lastColumn; i++){
for (j = 1; j < lastRow; j++){
//create the ranges to be compared and store as vars. cell and cellB
var cell = searchRange.getCell(j,i).getValue();
var cellB = searchRangeB.getCell(j,i).getValue();
//check for matches, change font accordingly
if (cell[0] === cellB[0]){
worksheet.getRange(j+1,i+1).setFontColor("green");
}else if(cell[0] =! cellB[0]){
worksheet.getRange(j+1,i+1).setFontColor("red");
I don't get any error messages; it just doesn't match the cells/change colors correctly.
There are several } missing at the end of your code and the correct operator is != instead of =!. Also, as a user mentioned, getValue() function retrieves a single value and not an array as getValues() [1], which is the recommended function to use in this case to avoid hitting the Google quotas [2]:
function onOpen(){
var ui = SpreadsheetApp.getUi();
ui.createMenu("Check My Translation.")
.addItem("Check Set 1", "transcheck")
.addToUi()
}
function transcheck(){
var ssA= SpreadsheetApp.getActiveSpreadsheet();
var worksheet = ssA.getSheetByName("Worksheet");
var rangeData = worksheet.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
var searchRange = worksheet.getRange(2, 2, lastRow-1, lastColumn-1).getValues();
var worksheetB = ssA.getSheetByName("Key");
var rangeDataB = worksheetB.getDataRange();
var lastColumnB = rangeDataB.getLastColumn();
var lastRowB = rangeDataB.getLastRow();
var searchRangeB = worksheetB. getRange(2, 2, lastRowB -1, lastColumnB -1).getValues();
//iterate through the two corresponding columns in template and reference sheet
for (var i = 0; i < searchRange.length; i++){
for (var j = 0; j < searchRange[0].length; j++){
//create the ranges to be compared and store as vars. cell and cellB
var cell = searchRange[i][j];
var k = 0;
while(k<3) {
var cellB = searchRangeB[k][j];
//check for matches, change font accordingly
if (cell == cellB){
worksheet.getRange(i+2,j+2).setFontColor("green");
break;
}else if(cell != cellB){
worksheet.getRange(i+2,j+2).setFontColor("red");
}
k++
}
}
}
}
[1] https://developers.google.com/apps-script/reference/spreadsheet/range#getValues()
[2] https://developers.google.com/apps-script/guides/services/quotas

Need a custom function to repeat for each row shown and minimize API calls

EDIT: I need help combining functions into 1 and adding a trigger.
In my spreadsheet I have rows 4-100 for customer service calls that are filtered from a "ServiceData" worksheet by either choosing a "Service Month" or "Service Day" (ie. "7/11" shows only 5 rows where "July" would show 65 rows) . Each row item has corresponding Place IDs for origin/destination in column K and L with an order # (as in 1st, 2nd, 3rd... service call of the day) in column J .
TravelTime spreadsheet
I'm using the following custom function travelTime() in cells M4:M100 to calculate driving duration and distance between 2 place IDs:
function travelTime(origin,destination) {
var API_KEY = PropertiesService.getScriptProperties().getProperty('keyMaps');
var baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=";
var queryUrl = baseUrl + "place_id:" + origin + "&destinations=" +
"place_id:" + destination + "&mode=driving" + "&key=" + API_KEY;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var time = JSON.parse(json);
return [[ time.rows[0].elements[0].duration.text,
time.rows[0].elements[0].distance.text ]] ;
A major issue is that many unnecessary service calls are being made to the API when I'm making edits to the "ServiceData" spreadsheet (ie. service date changes when a particular day is over-scheduled) and not needing the travel time updated until I'm done working through a schedule .
After researching quite a bit there seems to be several options I could be using; caching, looping, arrays, and putting everything into a script then attach to a button to only run when ready. Considering I'm a newbie, putting all these options together are definitely beyond my skill level and could really use some help.
EDIT with new functions:
So after more researching I have been able to put together the following functions that when each are run independently work great. Now the problem I'm having is putting these all together in particular adjusting the original travelTime()into newTravelTime(). I have made an attempt towards the right direction below but can't figure out how to get the API call in there .
function newTravelTime() {//<--**having issues how to write this function
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
var sourceV = sourceR.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
if (sourceV[i][2] == "") {
var origin = sourceV[i][0];//ori place IDs for API query
var destination = sourceV[i][1];//des place IDs API api query
}
array.push([sourceV[i][2]]);
}
sourceSheet.getRange(4, 13, array.length, 1).setValues(array);
I'd like to create a final getTravelTime() with all the functions and add an OnEdit trigger when either "Service Month" or "Service Day" changes in cells B1 or B2 to run them. If there is any advice with my functions themselves I would really appreciate some help, I am very new with this and trying.
///checks if origin/destination are already in the cacheSheet then return travel time to sourceSheet
function getCachedTravelTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
var sourceV = sourceR.getValues();
var cacheSheet = ss.getSheetByName("TravelTimeCache");
var cacheR = cacheSheet.getRange(2, 1, cacheSheet.getLastRow()-1, 4);
var cacheV = cacheR.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
for (var j = 0; j < cacheV.length; j++) {
//if origin/destination columns from sourceSheet match columns on cacheSheet
if (sourceV[i][0]+sourceV[i][1] == cacheV[j][0]+cacheV[j][1]) {
sourceV[i][2] = cacheV[j][2]; //column with travel duration
sourceV[i][3] = cacheV[j][3]; //column with travel distance
}
}
array.push([sourceV[i][2], sourceV[i][3]]);
}
sourceSheet.getRange(4, 13, array.length, 2).setValues(array);
}
///if origin or destination are blank, label as 'missing value'
function missingOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceID = sourceSheet.getRange(4, 3, sourceSheet.getLastRow()-3, 12);
var sourceV = sourceID.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
// if ID has a value
if (sourceV[i][0] != "") {
// if origin or destination is blank
if (sourceV[i][8] == "" || sourceV[i][9] == "") {
sourceV[i][10] = 'missing value';
}
}
array.push([sourceV[i][10]]);
}
sourceSheet.getRange(4, 13, array.length, 1).setValues(array);
}
///if cache not found - get the new travelTime for that origin/destination on sourceSheet...
function newTravelTime() {//<--
}
///...and store the new travelTime() in cacheSheet
function storeTravelTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("MonthlySA");
var sourceR = sourceSheet.getRange(4, 11, sourceSheet.getLastRow()-3, 4);
var sourceV = sourceR.getValues();
var cacheSheet = ss.getSheetByName("TravelTimeCache");
var cacheR = cacheSheet.getRange(2, 1, cacheSheet.getLastRow()-1, 4);
var cacheV = cacheR.getValues();
var array = [];
for (var i = 0; i < sourceV.length; i++) {
var duplicate = false;
for (var j = 0; j < cacheV.length; j++) {
if (sourceV[i][0]+sourceV[i][1] == cacheV[j][0]+cacheV[j][1]) {
duplicate = true;
}
}
if(!duplicate){ //if origin/destination columns from sourceSheet are NOT matched on cacheSheet
array.push([sourceV[i][0], sourceV[i][1], sourceV[i][2], sourceV[i][3]]);//columns with new data
}
}
//add new data to last row of cacheSheet
cacheSheet.getRange(cacheSheet.getLastRow()+1, 1, array.length, 4).setValues(array);
}
One of the easiest solution thats coming to mind is Caching. Instead of making API calls everytime check if we have already made that call previously.
Something like this
function getTravelTime(origin, destination) {
var travelTime = getTravelTimeFromPreviousCall(origin, destination);
if (travelTime != null) {
return travelTime;
} else {
var travelTime = fetchTravelTime(origin, destination);
storeTravelTime(origin, destination, travelTime);
return travelTime;
}
}
function fetchTravelTime(origin, destination) {
var API_KEY = PropertiesService.getScriptProperties().getProperty('keyMaps');
var baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=";
var queryUrl = baseUrl + "place_id:" + origin + "&destinations=" + "place_id:" + destination + "&mode=driving" + "&key=" + API_KEY;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var time = JSON.parse(json);
return time.rows[0].elements[0].duration.text;
}
For this we can define our cache something like :
A sheet with column -
origin
destination
travel time
And we need to define following functions:
getTravelTimeFromPreviousCall(origin, destination) : In this we need to check cache and return travel time for that origin & destination, if not found then return null
storeTravelTime(origin, destination, time) : This will only store travel time for future use in cache sheet
You can try something like this :
function getTravelTimeFromPreviousCall(origin, destination) {
var cacheSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(CACHE_SHEET_NAME);
var cacheData = sheet.getDataRange().getValues();
for (var i=0; i<cacheData.length; i++) {
if (cacheData[i][ORIGIN_COLUMN_INDEX]==origin && cacheData[i][DESTINATION_COLUMN_INDEX]==destination) {
return cacheData[i][TRAVEL_TIME_COLUMN_INDEX];
}
}
return null;
}
function storeTravelTime(origin, destination, travelTime) {
var cacheSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(CACHE_SHEET_NAME);
sheet.appendRow([origin, destination, travelTime]);
}
Please fix loop variable, array indexes & constants, as per your sheet.

Resources