Google sheet sidebar Google script show values from cells - google-apps-script

I'm trying to create a sidebar that will display cell values from the given row in which the user is currently on. For example, user is in cell B3. Side bar to show values from cells B1, B6 and B10.
I've tried several solutions, the latest being this one. Sadly, none of these work. Any ideas?
Additional information, 03/09/21:
I've created a sample spreadsheet with all the scripts.
The sidebar is now loading, but I cannot get it to update using the getRecord function. Keep getting the following error: NetworkError: Connection failure due to HTTP 502
Thanks

In your SidebarJavascript.html file, it doesn't look to me that you are passing the record to your callback. See if this helps:
function poll(interval) {
interval = interval || 1000;
setTimeout(function() {
google.script.run
.withSuccessHandler(showRecord) // <--replace this
.withSuccessHandler(function (record) { //<-- with this
showRecord(record);
})
.withFailureHandler(
function(msg, element) {
showStatus(msg, $('#button-bar'));
element.disabled = false;
})
.getRecord();
}, interval);
};
I don't know if this will make a difference.

Related

Detecting broken Image functions created in an Array Formula in Google Sheet

In this sheet:
https://docs.google.com/spreadsheets/d/1gK3nlf4SY0y1tN1Cz_CMI4oZDncziQxWfuD-3rMa6Cw
In Column A there are random numbers between 100 and 300.
In Column B there is an ARRAYFORMULA that pulls images from a directory: =ARRAYFORMULA(IMAGE("https://www.roadpics.net/Serie38/Images/photo" & B2:B25 & ".jpg"))
The challenge is to detect in Column C which cells in Column B have images. A simple True/False will suffice.
I was hoping for a solution to detect images in cells regardless of origin (manually placed, in-cell formula, array formula...) but it seems that is not possible under the current constraints of Google Drive.
So I took #TheMaster's advice and instead I check the URL and show an image only if the URL is valid. If not, I throw a NA error to allow me to detect and take the appropriate action (in my case, attempt another image URL).
Here's my code:
function VALIDATEURL(_link) {
if (_link.map) { //Is true if _link is an array
return _link.map(VALIDATEURL); //Recurse over array
} else { //Not an array
var code = 0; //Sets temp code
if (/^http/i.test(_link)) {
try {
code = UrlFetchApp.fetch(_link).getResponseCode();
} catch (e) {}
}
return code === 200; //If response code is 200, return true, else false
}
}
I then call the custom function with the following formula (URLs are in Column D):
=IF(
VALIDATEURL(D2),
IMAGE(D2),
NA()
)
Hope that helps someone with a similar issue.

Google Sheets with Google App Script: How to write a 'status' message to the cell before returning the final result?

I have a function that can take a while to return the output. Is there a way to have it print a message in the cell, before overwriting the message with the output a short time later? The function can take 30 seconds to run and it may be used in 20-30 cells, hence it would be nice to see which cell is still calculating and which is done.
function do_calc() {
print("Now doing the calculations...")
// ... (bunch of code here that does the calculations)
return output;
}
I tried to use setvalue() but it says I don't have permission to do that in a custom function.
UPDATE: added picture
screenshot
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var active_range = sheet.getActiveRange();
sheet.getRange(active_range.getRowIndex(), 10).setValue('Running...');
Utilities.sleep(10000);
return 'Finished';
}
Issues:
Like I said in the comment you can't return twice because the first return statement will cancel out the code that comes after that.
Also set methods (like setValue) are not allowed in custom function as clearly stated in the official documentation.
Solution:
The solution would be to incorporate the built in google sheets formula IFERROR.
=iferror(myFunction(),"Running...")
where myFunction is:
function myFunction() {
try{
// some code that delays
Utilities.sleep(10000);
}
catch(e){
return e.message;
}
return 'Finished';
}
I added a try...catch to make sure you return the error messages that are related to the script. Otherwise, iferror will hide them.
Be careful!
A custom function call must return within 30 seconds. If this time is surpassed then the custom function will return the error:
Exceeded maximum execution time
which will not be shown because you have used iferror which will cover the error.

Display the red marker when returning a value in Google Sheets

I'm using Custom functions and Document Cache. Would like to return the red marker in the top right corner if there is a problem, but still display an answer from before that is in the cache.
Example calling URLFetch and had a failure, then accessing the value from before to return and stating there was a failure
One possibility is to have your custom function return an array formula. In the [0][0] index, the normal or cached value would be displayed, while in the [0][1] index, an error message would be displayed.
Example:
A2: =MyCustomFormula(A1)
function MyCustomFormula(cell) {
var oldValue = cache.get("key"), newValue;
try {
newValue = UrlFetchApp.fetch(....);
} catch (e) {
return [[oldValue, e.message]];
}
return [[newValue, ""]];
}
This obviously requires a free cell next to / below the cell containing the custom function call.

Run server-side code with google chart select

I have a google visualization table that I'm publishing in a web app.
Background:
I run a script that lists all the documents in a google folder in a spreadsheet. I then push that list into the google table I have published in the web app.
The need:
I want to manage those that same list of documents directly from the web app. I want to be able to to move the document from one folder to another when I select the applicable row on the table.
Two things I have accomplished:
I have a script that will move the document to a specific folder on
the google drive using it's doc id.
I have an event listener on the table so that when you click a row and
then click the delete icon, you get a prompt that asks, "are sure
you want to archive [enter document name]?" When I click ok, I get my
test prompt that says "document archived". When I click no, I get my
test prompt that says "request cancelled". So from this, I know I
have the appropriate code (at least that's how it seems).
What I'm struggling with:
I can't seem to get the codes above to work together. The event listener is providing me the url of the document which I have parsed to give me only the id. This is what I was hoping to use to get the rest of the code to run, but I think because I'm trying to interact with the server-side from the client-side, it's not working. Can anyone help me figure it out? I know that I need to use google.script.run.withSuccessHandler when running a server side script from the client side, but I don't know how it applies to this case the docid I need is being collected on table select. Any help is appreciated and I hope the above makes sense!
// Draw Dashboard
h2dashboard.bind([h2stringFilter, h2typeFilter], [h2chart]);
h2dashboard.draw(h2dataView);
google.visualization.events.addOneTimeListener(h2chart, 'ready', function() {
google.visualization.events.addListener(h2chart.getChart(), 'select', function() {
var selection = h2chart.getChart().getSelection();
var dt = h2chart.getDataTable();
// Get Value of clicked row
if (selection.length) {
var item = selection[0];
var docurl = dt.getValue(item.row, 1);
var docname = dt.getValue(item.row, 0);
var source = dt.getValue(item.row, 3);
// When button is clicked, show confirm box with value
$(document).ready(function() {
$("#hu2archive").on("click", function() {
var answer = confirm("Are you sure you want to archive " + docname + "?");
if (answer === true) {
var archive = DriveApp.getFolderById("FOLDER ID");
var docid = docurl.match(/[-\w]{25,}/); // This is where I'm grabbing the value from the row.
var doc = DriveApp.getFileById(docid);
doc.makeCopy(archive).setName(doc.getName());
source.removeFile(doc);
alert(docname + " has been archived!");
} else {
alert("Request cancelled");
}
});
});
}
});
});
I just got it! What I was having a hard time understanding was how to pass a variable from the client side to code.gs. I have only run a script in code.gs from the client side on button submit but never passed anything back.
So I ended up changing my code to the below which passes the variable I need into a successhandler where archiveDoc is the function in my code.gs and docurl is the name of the variable I need to pass from the eventlistener.
if (answer === true) { google.script.run.withSuccessHandler(onSuccess).withFailureHandler(err).archiveDoc(docurl);
I'm still new to coding so I just learned something new! So thanks Spencer Easton. I did in fact answer my own question.

Trigger an email when a cell is written into from another app (IFTTT)

So here's what I've been working on. I'm a basketball coach and have a spreadsheet that pulls in all of my players' tweets from IFTTT.com (it basically takes the RSS feed of a twitter list and when it is updated, it updates the spreadsheet).
I have been working on coding that basically says "if a player tweets an inappropriate word, email me immediately."
I've got the code figured out that if I just type in an inappropriate word, it'll turn the cell red and email me. However, I have not figured out how to get the code to email me after IFTTT automatically updates the spreadsheet with tweets.
Here is my code thus far. Right now I've just got one "trigger" word that is "players" just to try and get the spreadsheet to work. Here's the code:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();//Get the spreadsheet
var sheet = ss.getActiveSheet()//Get the active sheet
var cell = ss.getActiveCell().activate();//Get the active cell.
var badCell = cell.getA1Notation();//Get the cells A1 notation.
var badCellContent = cell.getValue();//Get the value of that cell.
if (badCellContent.match("players")){
cell.setBackgroundColor("red")
MailApp.sendEmail("antadrag#gmail.com", "Notice of possible inappropriate tweet", "This tweet says: " + badCellContent + ".");
}
}
Here is a link to the spreadsheet I'm working with right now: https://docs.google.com/spreadsheets/d/1g5XaIycy69a3T2YcWhcbBy0hYrxSfoEEz8c4-zP63O8/edit#gid=0
Any help or guidance on this is greatly appreciated! Thanks!
I originally wrote this answer for your previous question, so it includes answers to some of your comments from there, but since you're continuing to go asking the community to write this step-by-step , here's the next step.
The issue I'm running into is that if three tweets come into the spreadsheet at the same time, with my code, it's only going to update the most recent cell, not all three. Does that make sense?
Yes, it does make sense.
When an onEdit() trigger function calls Spreadsheet Service functions to get current info from the sheet, it enters a "Race condition". If any changes occur in the sheet after the change that triggered onEdit(), and the time when it gets scheduled, those changes will be visible when it runs. That's what you see when you assume that the change you're processing is in the last row - by the time you're processing it, there may be a new last row.
Good news, though - the attributes of the event object passed to onEdit contain the details of the change. (The parameter e.) See Event objects.
By using e.range and e.value you'll find you have the location and content of the edited cell that kicked the trigger. If additional tweets arrive before the trigger is serviced, your function won't be tricked into processing the last row.
In new sheets, the onEdit() can get triggered for multiple-cell changes, such as cut & paste. However unlikely that it may happen, it's worth covering.
Well, after getting the spreadsheet all setup & actually using the trigger from IFTTT, it doesn't work. :( I'm assuming it's not dubbing it as the active cell whenever it automatically pulls it into the spreadsheet. Any idea on a workaround on that?
Q: When is an edit not an edit? A: When it's made by a script. In that case, it's a change. You can add an installable on Change function to catch those events. Unfortunately, the change event is less verbose than an edit event, so you are forced to read the spreadsheet to figure out what has changed. My habit is to have the change handler simulate an edit by constructing a fake event (just as we'd do for testing), and passing it to the onEdit function.
So give this a try. This script:
handles a list of "bad words". (Could just as easily be monitoring for mentions of your product or cause.)
has an onEdit() function that uses the event object to evaluate the row(s) that triggered the function call.
colors "bad" tweets
has a function for testing the onEdit() trigger, based on How can I test a trigger function in GAS?
includes playCatchUp(e), an installable trigger function (change and/or time-based) that will evaluate any rows that have not been evaluated before. Script property "Last Processed Row" is used to track that row value. (If you plan to remove rows, you'll need to adjust the property.)
Has the sendMail function commented out.
Enjoy!
// Array of bad words. Could be replaced with values from a range in spreadsheet.
var badWords = [
"array",
"of",
"unacceptable",
"words",
"separated",
"by",
"commas"
];
function onEdit(e) {
if (!e) throw new Error( "Event object required. Test using test_onEdit()" );
Logger.log( e.range.getA1Notation() );
// e.value is only available if a single cell was edited
if (e.hasOwnProperty("value")) {
var tweets = [[e.value]];
}
else {
tweets = e.range.getValues();
}
var colors = e.range.getBackgrounds();
for (var i=0; i<tweets.length; i++) {
var tweet = tweets[i][0];
for (var j=0; j< badWords.length; j++) {
var badWord = badWords[j];
if (tweet.match(badWord)) {
Logger.log("Notice of possible inappropriate tweet: " + tweet);
colors[i][0] = "red";
//MailApp.sendEmail(myEmail, "Notice of possible inappropriate tweet", tweet);
break;
}
}
}
e.range.setBackgrounds(colors);
PropertiesService.getDocumentProperties()
.setProperty("Last Processed Row",
(e.range.getRowIndex()+tweets.length-1).toString());
}
// Test function, adapted from https://stackoverflow.com/a/16089067/1677912
function test_onEdit() {
var fakeEvent = {};
fakeEvent.authMode = ScriptApp.AuthMode.LIMITED;
fakeEvent.user = "amin#example.com";
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
// e.value is only available if a single cell was edited
if (fakeEvent.range.getNumRows() === 1 && fakeEvent.range.getNumColumns() === 1) {
fakeEvent.value = fakeEvent.range.getValue();
}
onEdit(fakeEvent);
}
// Installable trigger to handle change or timed events
// Something may or may not have changed, but we won't know exactly what
function playCatchUp(e) {
// Check why we've been called
if (!e)
Logger.log("playCatchUp called without Event");
else {
// If onChange and the change is an edit - no work to do here
if (e.hasOwnProperty("changeType") && e.changeType === "EDIT") return;
// If timed trigger, nothing special to do.
if (e.hasOwnProperty("year")) {
var date = new Date(e.year, e.month, e["day-of-month"], e.hour, e.minute, e.second);
Logger.log("Timed trigger: " + date.toString() );
}
}
// Find out where to start processing tweets
// The first time this runs, the property will be null, yielding NaN
var lastProcRow = parseInt(PropertiesService.getDocumentProperties()
.getProperty("Last Processed Row"));
if (isNaN(lastProcRow)) lastProcRow = 0;
// Build a fake event to pass to onEdit()
var fakeEvent = {};
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
var numRows = fakeEvent.range.getLastRow() - lastProcRow;
if (numRows > 0) {
fakeEvent.range = fakeEvent.range.offset(lastProcRow, 0, numRows);
onEdit(fakeEvent);
}
else {
Logger.log("All caught up.");
}
}