Modify Tinyurl Google Script Function for Sheets - google-apps-script

I've been using this function to shorten links (Get range, if length over 200, copy and paste over new tinyurl link in same cell). But I wanna modify it to take active cell or active range instead of input range from UI prompt response. In this case I probably wouldn't need the if(x.length > 200) condition either. I've tried to research for some solutions that I could implement but its too complex for my beginner skills and understanding of original code to modify. Is there an easy fix I could do to it?
function tinyUrl() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
var final = [];
var response = ui.prompt('Enter range:');
if(response.getSelectedButton() == ui.Button.Ok) {
try{
var values = [].concat.apply([], ss.getRange(response.getResponseText()).getValues()).filter(String);
SpreadsheetApp.getActiveSpreadsheet().toast('Processing range: '+ss.getRange(response.getResponseText()).getA1Notation(),'Task Started');
values.forEach(x => {
if(x.length > 200) {
final.push(["IMPORTDATA(concatenate(\"http:// tiny url.com/api-create.php?url="+x+"\"),\"\")"]);
}else{
final.push(["=HYPERLINK(\""+x+"\")"]);
}
})
}catch(e){
SpreadsheetApp.getUi().alert(response.getResponseText()+' is not valid range!');
}
}else{
return;
}
var r = response.getResponseText().split(":");
if(r[0].length=1 &&r[1].length == 1){
ss.getRange(r[0]+"1:"+r[0]+final.lenght).setFormulas(final);
}else{
ss.getRange(r[0]+":"+r[0].slice(0,1)+final.length).setFormulas(final);
}
}

The exact solution depends on your actual application. I think your question is how to detect where to apply your custom function. But let's take a step back.
There are two ways to interact with existing data in sheet and a custom function via AppsScript.
One is that you can directly call your custom function with ranges in the sheet. If the input range is a single cell, the data is read directly. If the input range spans more than a single cell, the data is read as nested lists: a list of columns which are lists of rows. For example, A1:B2 will be read as [[A1, B1], [A2, B2]].
So, for example, you can always call your custom function tinyurl() wherever you input url in your sheet and let your custom function decide when to shorten it. Since your custom function is called in-place, there is no detection issue; UI prompt is not required.
The downside is that if you call your custom function in too many places, there will be delay in getting results. And I don't think the results are always stored with the sheet. (ie. when you open the sheet again, all cells with tinyurl() may refresh and cause delay.)
Second method is via the Range Class which is what you are using. Instead of using UI prompt though, you can add onEdit() trigger to your custom function and let your function either check for currently selected cell via SpreadsheetApp.getActive().getActiveRange() or scan for urls over the sheet where you intend for user inputs to be stored.
The downside of using onEdit() trigger is the UI lag. Relying on actively selected range can also be problematic. Yet if you scan over the whole sheet, well, you have to do that. At the end though, you get to store the resultant URLs permanently with the sheet. So after the initial lag, you won't have delays in the future.
In this route, you may find getLastRow(), getLastColumn() in Sheet and getNextDataCell() in Range convenient. If you choose to process the whole sheet, you may instead use the onOpen() trigger.
I would prefer to store all URLs in one place and use the 1st option. Thus, the custom function is only called once and that's useful for minimizing delay. Other parts of the sheet can reference cells in that centralized range.
The exact solution depends on your actual application.

Related

Google Sheets script that runs based on the face value of a cell while ignoring formulas

I made a Google Sheet to check every media that plays on a certain channel on TV using a lot of workaround formulas within the cells themselves. A part of this sheet is a column (G) that tells me whether or not the specific episode/media/whatever is currently playing, has played in the past or will be played later today/at a later date using a "NOW" function. Next to that column there is another (F) where the user is able to write a "V", and in the case the show is playing but the user hasn't checked it yet, it writes "Check Me" See Example.
I wanted to create a button that will automatically change that "Check Me" into a "V" but the problem is that "Check Me" is based on a simple formula written throughout column F (=IF(G5="Playing","Check Me","")), so when I tried to run a script I found here on StackOverflow:
function Test() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("F5:F700");
range.setValues(range.getValues().map(function(row) {
return [row[0].replace(/Check Me/, "V")];
}));
}
(Can't remember the exact thread I got it from and it's been two days since I found it with lots of similar searches in my history, so I apologize for not crediting)
together with its intended use, it also straight up deleted all the rest of the formulas from the column, probably due to the formula itself containing "Check Me" but I might be mistaken.
To be honest, before this week I barely ever worked with either Google Sheets, much less JavaScript or even coding in general, so I'm pretty much restrained to changing values and very minor modifications in scripts I find online.
The only idea I had as to how to solve it is to add an "if IsBlank" but regarding face value of the cell only rather than its contents, but I don't know how to do it or whether it is even possible in the first place. At the very least, google shows no results on the subject. Is there a way to add that function? or perhaps a different method altogether to make that button work? (it's a drawing I will assign a script to)
Because you're using a map function to update the range, you'll need to get the formulas using getFormulas() in addition to the display values using either getValues() or getDisplayValues(). Using only the display values, as you're currently doing, will cause you to lose the formulas when you update the sheet. Conversely, using only the formulas would cause you to lose all of the display values that don't have a formula, so you'll need both. Try this and see if does what you want:
function Test() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("F5:F700");
// Get all cell formulas and display values
var formulas = range.getFormulas();
var values = range.getValues();
range.setValues(formulas.map(function(row, index) {
var formula = row[0];
var value = values[index][0];
// Check only the display value
if (value == "Check Me") {
return ["V"];
} else {
// Return formula if it exists, else return value
return [formula || value];
}
}));
}

How to get user input in google app script?

Here is what I want to achieve:
I want to delete 'n' number of rows from my google spreadsheet document. This 'n' can vary depending on number of wrong entries inserted in the document (I know this number before running the function). And I want to give myself a flexibility to choose this number (just like console input in C, C++ or any other languages).
Some researching shows solution via SpreadsheetApp.getUi() mode. But it is giving me error: Exception: Cannot call SpreadsheetApp.getUi() from this context.
I don't want to open my spreadsheet as it is huge in size & takes time to load. Purpose of deleting rows pragmatically is that I don't have to open it, else its all 'moo' point.
Another solution could be to just create an variable and change is manually before running script. But it could create bad data if I forget to change that variable someday (I want to make it idiot-proof).
Is there any way to get user input for standalone google app script? and without opening that particular google sheet?
You can always put the script into a blank sheet and treat it as a placeholder for your functions and have the ui prompt pop there. This way, you don't need to open your large sheet. You can always access other sheets when in another via Apps Script. This would be easier and you just need to transfer your script here.
Code:
function showPrompt() {
var ui = SpreadsheetApp.getUi();
var result = ui.prompt(
'Rows to delete?',
'Input:',
ui.ButtonSet.OK_CANCEL);
var button = result.getSelectedButton();
var numRows = result.getResponseText();
if (button == ui.Button.OK) {
// call function and pass the value
deleteSheetRows(numRows);
}
}
function deleteSheetRows(numRows) {
// url of the sheet with data
var url = "https://docs.google.com/spreadsheets/d/***************/";
var sheet = SpreadsheetApp.openByUrl(url);
// do what you need to do here for that sheet using "numRows" value
Logger.log("deleting "+numRows+" rows");
}
Output:
You can create a function in web app just write doGet() or doPost() function and call it with your input.
refer https://developers.google.com/apps-script/guides/web
Take input number of rows which is n in your case, and add your code to delete rows from SpreadSheet.
you can pass input for get by using query parameter like:
?n=4
and you can use n in doGet() method.

How to evaluate a spreadsheet formula within a custom function?

In a spreadsheet I can enter =SIN(45)+123 in a cell, and it will be evaluated.
How can I evaluate spreadsheet functions within a custom function, something like an "eval"
function that would work like this :
function myFunc() {
return Sheet.eval("=SIN(45)+123")
}
is it possible ?
Note that I don't care about the SIN function in particular, what I want is to have access to the complete arsenal of spreadsheet functions (PMT, QUERY, NPER, etc..)
Spreadsheet functions from Apps-Script
Not possible - This has been asked many times. Suggest you check the google-apps-script issue list to see if anything has changed. But last I checked, there is no way to do it, and they have no plan to add it. https://code.google.com/p/google-apps-script-issues/issues/list
Ethercalc - java script spreadsheet formulas
If you need to, you can always copy the code from "ethercalc" as it has a java script versions of the spreadsheet formulas.
https://github.com/audreyt/ethercalc
I know this is an old question, but it might help someone.
just assign the formula to a range, then grab the value.
//asign a formula to the range
var range = sheet.getRange("A1").setFormula("=SUM(D1:D100)");
//get the result
var result = range.getValue();
//clean up
range.setFormula("");
I got this working. Using set value will do the trick. Thus something like this:
function MyFun1(){
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('A1').setValue(Myfun2())
}
function MyFun2(){
return "=SIN(45)+123"
}
Hope this helps!
I think you need to divide this issue up into two different concerns.
Do you want to grab data that is already on the spreadsheet, perform a calculation, and then print a result, or do you want to use the sin() function on calculations in code unrelated to the data in the spreadsheet?
If you are trying to do the latter, you should be able to reference spreadsheet functions by using Math.sin() in your Google Apps Script. For more information on using the sin() function in JavaScript, check this post out: http://www.w3schools.com/jsref/jsref_sin.asp
If you are trying to do the former, then what you should do is use a readRows() function (more information available here: http://gassnippets.blogspot.com/2012/11/create-your-first-google-apps-script.html) to load your spreadsheet data into a variable (or variables) in memory, perform your calculations, and print the final result out to the spreadsheet using a similar function.
Let me know if this helps.
I came across this question in an attempt to find a way to evaluate part of a function like it is possible in Excel.
Here is my dirty workaround - instead of outputting the result in an msgbox, you could simply store the value or displayvalue of the activecell in a variable and use it to your liking.
Notice however, that the function will temporarily overwrite whatever you have in your currently selected cell and it will need to recalculate the sheet before the result is available. Hence it's not a viable solution if you need to evaluate multiple cell values.
function evalPart() {
var ui = SpreadsheetApp.getUi();
myPart = Browser.inputBox("Enter formula part:", ui.ButtonSet.OK_CANCEL);
if (myPart != "cancel") {
myActiveCell = SpreadsheetApp.getActiveSpreadsheet().getActiveCell();
myBackup = myActiveCell.getFormula();
myActiveCell.setFormula(myPart);
Browser.msgBox("Result of \\n \\n" + myPart + " \\n \\n " + myActiveCell.getDisplayValue());
myActiveCell.setFormula(myBackup);
}
}
I don't know if it's possible with high-level functions. However, it's possible with some common and easy-to-understand functions like (sum, subtract etc).
Following is the code I used to set values after the calculation is done in scripting itself.
function MyFun1() {
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('A1').setValue(MyFun2());
}
function MyFun2() {
var one = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dashboard");
var two = one.getRange('A2').getValue();
var three = one.getRange('A3').getValue(); return two*three;
}
Don't forget to add a trigger "onEdit" on Myfun1() to automatically update the return value.

Google spreadsheet custom function: How to get a continuously updated valued?

I wrote a custom google app script function in a script associated with my google doc spreadsheet. The function calls a third party service to get data. I can put the function in a cell:
=myfunction("something")
and it returns the correct value from the service. However, how can I keep this value updated so that it's showing the latest data from the service?
Update
For example:
=temperature("90120")
For getting the current temperature in a given zip code. Also my sheet may have dozens or hundreds of these so I'd prefer something that is performant and maintainable. It doesn't truly need to be continuous, polling once a minute or ideally more frequently could work. I'm wondering if there's some way from the script to set a timer to run to update a range of cells?
Not sure why you need dozens or hundreds.
1. Is the spreadsheet used by another process?
2. Is the spreadsheet visually reviewed by actual users?
If #1, you could replace the spreadsheet with a custom API via the content service to return JSON results for all temperatures.
If #2, you may hit limits or performance issues with so many functions firing so often. Why should fire the functions if no one is viewing the results. Alternatively, you could make it an on-demand with a custom menu option.
I have a similar problem.
This is how I am doing it atm, but its not the best solution. I am looking for a better one.
If any value at sheet Prices and column D changes.
Meaning if any cell value changes in the whole column it updates the custom function value.
//Search Price sheet with the given name. Return price. dummy param updates google ss once the "Prices" sheet values changed.
function searchPrice(price,dummy)
{
var SPREADSHEET_NAME = "Prices";
var SEARCH_COL_IDX = 2;
var RETURN_COL_IDX = 3;
var values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SPREADSHEET_NAME).getDataRange().getValues();
for (var i = 0; i < values.length; i++)
{
var row = values[i];
if (row[SEARCH_COL_IDX] == price)
{
return row[RETURN_COL_IDX];
}
}
}
This is how you call it =searchPrice(B8,Prices!D:D)
Just give your custom function a dummy param. It doesn't do anything in the custom function.

Google script FIlter issue

I am programming a help desk system using google script, forms and spreadsheet.
To filter the queries the submissions are placed into different sheets depending on category, this is done through the FILTER function. however every time a new submission is made the filter function does not update, (it uses the CONTINUE function to cover the other cells)
instead the cell with the FILTER function must be selected and crtl+shift+E must be entered
is there a way around this?
I have tried two methods
the first was looking to have a function to enter the shortcut, but is this possible?
the second is auto entering the continue function everytime a new submission is made, I have this working however google sheets does not recognise the named range, (the continue function has the set up CONTINUE(original cell, rows away, columns away) its the original cell that it does not identify, instead I must manually select the cell and re-write the exact same cell reference.
Thank you for your help, if you need to see my code please ask :)
This is the code for the second option where I try to enter the function manually to the cells.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var numEntry = ss.getSheetByName('Home').getRange("B8").getValue() + 2;
var cat = ss.getSheetByName('Software problem').getRange(numEntry, 4, 1, 9);
cat.getCell(1, 1).setValue('=CONTINUE(D2, '+(numEntry-1)+', 1)');
Your option 1: Have a script enter keystrokes automatically? Not supported in apps-script.
Your Option 2: It shouldn't be necessary to programmatically insert CONTINUE, as the required CONTINUEs for your FILTER should be automatic, when rows in your filter range match the expressed criteria. Something else is wrong, so don't get caught up with this red herring.
You mention "google sheets does not recognise the named range" - I'd like to know what you mean by that, because I suspect this is where your solution will be. You can use named ranges within FILTER statements. You can also use open-ended ranges, like FormInput!A1:X or FormInput!E1:E.
If you're trying to manipulate named ranges using scripts, then you may have run into a known issue, "removeNamedRange() only removes named ranges that were created via Apps Script". (To get around that, manually delete the named range, then create it only from script.)
Here's a function I use to create a named range for all data on a sheet. You could adapt this to your situation. (I use this with QUERY functions instead of FILTER, you might want to consider that as an alternative.)
function setNamedRangeFromSheet(sheetName) {
// Cannot remove a named range that was added via UI - http://code.google.com/p/google-apps-script-issues/issues/detail?id=1041
var ss = SpreadsheetApp.getActiveSpreadsheet();
try { ss.removeNamedRange(sheetName) } catch (error) {};
var sheet = ss.getSheetByName(sheetName);
var range = sheet.getDataRange();
ss.setNamedRange(sheetName,range);
}
Using FILTER, you need to match the length of your sourceArray (which can be a named range) and any criteria arrays you use. To programmatically create a named range for a single-column criteria within your sourceArray, and of the same length, use getNumRows() on the sourceArray range.
Now, within your submission handling function, triggered on form submit, you'd have something like this. (I assume your trouble reports are coming into a single sheet, "FormInput" - adjust as necessary.)
...
var ss = SpreadsheetApp.getActiveSpreadsheet();
try { ss.removeNamedRange("FormInput") } catch (error) {};
var sheet = ss.getSheetByName("FormInput");
var inputRange = sheet.getDataRange();
ss.setNamedRange("FormInput",inputRange);
try { ss.removeNamedRange("Criteria") } catch (error) {};
var criteriaCol = 4; // Another guess, that Column E contains our criteria
var criteriaRange = sheet.getRange(0,criteriaCol,inputRange.getNumRows(),1);
ss.setNamedRange("Criteria",criteriaRange);
...
And with that in place, the content of A1 on your "Software problem" sheet just needs to contain the following. (Assuming that you're looking for "Bug"s.):
=FILTER(FormInput,Criteria="Bug")
I mentioned open-ended ranges earlier. If you aren't doing enough manipulation of data to justify named ranges, you could set up your filter like this, and not have to change it as new input came in:
=FILTER(FormInput!A1:X,FormInput!E1:E="Bug")